/*
** Copyright (C) 1999 Albert L. Faber
**  
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
** 
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.
** 
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software 
** Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/

#include "Stdafx.h"
#include "LineList.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

/////////////////////////////////////////////////////////////////////////////
// LListCtrl


/////////////////////////////////////////////////////////////////////////////
// GetCurSel -
//		an obvious CListCtrl deficiency, tell us index of current selection.
/////////////////////////////////////////////////////////////////////////////
int LListCtrl::GetCurSel() const
{
	return GetNextItem(-1,LVNI_ALL|LVNI_SELECTED);
}

/////////////////////////////////////////////////////////////////////////////
// SetCurSel -
//		an obvious CListCtrl deficiency, specify and make visible a new selection.
/////////////////////////////////////////////////////////////////////////////
int LListCtrl::SetCurSel(int nSelect) 
{
	if(!SetItemState(nSelect,LVIS_SELECTED,LVIS_SELECTED))
	{
		return LB_ERR;
	}
	if(!EnsureVisible(nSelect,FALSE))
	{
		return LB_ERR;
	}
	return TRUE;
}

BEGIN_MESSAGE_MAP(LListCtrl, CListCtrl)
    //{{AFX_MSG_MAP(LListCtrl)
	ON_NOTIFY_REFLECT(LVN_INSERTITEM, OnInsertitem)
	ON_WM_GETDLGCODE()
	ON_NOTIFY_REFLECT(NM_CLICK, OnClick)
	//}}AFX_MSG_MAP
//	ON_NOTIFY_REFLECT(NM_CLICK, OnClick)
//	ON_NOTIFY_REFLECT(LVN_BEGINLABELEDIT, OnBeginlabeledit)
END_MESSAGE_MAP()


/////////////////////////////////////////////////////////////////////////////
// OnGetDialogCode - 
//		allow our control to recieve crack at parent dialog's key codes
//		this allows the control to catch RETURN rather than the dialog's
//		default push button and hence to support up arrow/down arrow/return 
//		item selection (must also place a NM_RETURN message handler in the 
//		dialog's message map). 
/////////////////////////////////////////////////////////////////////////////
//UINT LListCtrl::OnGetDlgCode()
//{
    //return CListCtrl::OnGetDlgCode() | DLGC_WANTALLKEYS;
//}

/////////////////////////////////////////////////////////////////////////////
// OnInsertItem - 
//		make item selectable and hi-lighted by entire row, not just first 
//		column sub-item (an undocumented tid-bit of the Net). Yes there must
//		be a better place for this but I'll be damned if I can find it. 
//		OnCreate(), Create(), etc, as added by ClassWizard do not hook, 
//		place it here for now.
/////////////////////////////////////////////////////////////////////////////
void LListCtrl::OnInsertitem(NMHDR* pNMHDR, LRESULT* pResult) 
{
	ListView_SetExtendedListViewStyle(m_hWnd,LVS_EX_FULLROWSELECT);
}


// hard coded margins in inches and pixels:
#define PRINTMARGIN_Y 0.50		
#define PRINTMARGIN_X 0.50
#define PRINTMARGINY ((int)((double)pDC->GetDeviceCaps(LOGPIXELSY)*PRINTMARGIN_Y))
#define PRINTMARGINX ((int)((double)pDC->GetDeviceCaps(LOGPIXELSX)*PRINTMARGIN_X))
#define LINESPACING	 ((csCharSize.cy * 6)/5)

void LListCtrl::OnPrint(CString strHeader, CString strFooter, DWORD dwFlags) 
{
	CRect	crPrintedPage;
	CSize	csCharSize;
	CPoint	cpCurPos;
	DWORD	dwLinesPerPage;
	DWORD	dwPagesTotal;
	DWORD	dwLinesTotal;
	CFont	Font;
	CDC	   *pDC;

	// confirm null-list print:
	if( GetItemCount() == 0 && AfxMessageBox( _T( "" ), MB_OKCANCEL | MB_ICONQUESTION) == IDCANCEL )
	{
		return;
	}

	// Create and do a print setup dialog (bPrintSetupOnly=TRUE to get a more useful dialog, loses the "print all/select" and "copies" controls):
	CPrintDialog dlg(TRUE,PD_ALLPAGES|PD_RETURNDC,NULL);
	dlg.m_pd.Flags |= PD_NOSELECTION;
	if(dwFlags&LLC_LANDSCAPE)
	{	
//	    ((theApp*)AfxGetApp())->SetPortraitLandscape(dlg.m_pd,DMORIENT_LANDSCAPE);
	}
	if(dlg.DoModal() != IDOK || dlg.m_pd.hDC == NULL)
	{
		return;
	}

	// create a new CDC object to access printer device context:
	pDC = new CDC;
	pDC->Attach(dlg.m_pd.hDC);
	
	// setup a printer font if none provided:
	csCharSize.cy = ((pDC->GetDeviceCaps(LOGPIXELSY)*10)/72);
	
	Font.CreateFont(-((int)csCharSize.cy),0,0,0,FW_NORMAL,0,0,0,DEFAULT_CHARSET
	                    ,OUT_CHARACTER_PRECIS,CLIP_CHARACTER_PRECIS,
		                DEFAULT_QUALITY, DEFAULT_PITCH | FF_DONTCARE, _T( "Courier New" ) );

	
	// activate the printer font:
	CFont *pOldFont = pDC->SelectObject(&Font);
	
	// get size, lines/page, and page count:
	TEXTMETRIC tm;
	pDC->GetTextMetrics(&tm);
	csCharSize.cx	= tm.tmAveCharWidth;	
	csCharSize.cy	= tm.tmHeight + tm.tmExternalLeading;
	dwLinesTotal	= GetItemCount();
	dwLinesPerPage	= (pDC->GetDeviceCaps(VERTRES) - 2*PRINTMARGINY - 3*LINESPACING) / LINESPACING;
	dwPagesTotal	= max(1,(dwLinesTotal + (dwLinesPerPage-1))/dwLinesPerPage);
	crPrintedPage.left   = PRINTMARGINX;
	crPrintedPage.right  = pDC->GetDeviceCaps(HORZRES) - PRINTMARGINX;
	crPrintedPage.top	 = PRINTMARGINY;
	crPrintedPage.bottom = pDC->GetDeviceCaps(VERTRES) - PRINTMARGINY;

	// prep for printing:
	pDC->StartDoc( _T( "Error Stack Document" ) );

	// print page loop:
	for(DWORD dwPage=0; dwPage<dwPagesTotal; dwPage++ )
	{
		pDC->StartPage();
		print_header(pDC, crPrintedPage, csCharSize, cpCurPos, strHeader, dwFlags);
		// print line loop:
		for(DWORD dwLine=0; dwLine<dwLinesPerPage && dwPage*dwLinesPerPage+dwLine<(DWORD)GetItemCount(); dwLine++ )
		{
			print_line(pDC, crPrintedPage, csCharSize, cpCurPos, dwPage*dwLinesPerPage+dwLine);
		}
		print_footer(pDC, crPrintedPage, csCharSize, strFooter, dwPage+1, dwPagesTotal);
		pDC->EndPage();
	}

	// end printing:
	pDC->EndDoc();

	// clean up fonts:
	pDC->SelectObject(pOldFont);
	Font.DeleteObject();

	// clean up CDC:
	pDC->DeleteDC();
	pDC->Detach();
	delete pDC;
}

void LListCtrl::print_header(CDC *pDC,CRect crPrintedPage,CSize csCharSize, CPoint& cpCurPos, CString& strHeader, DWORD dwFlags )
{
	CString		strTime;
	LV_COLUMN	lvcColumn;
	DWORD		dwColumnWidths=0;
	TCHAR		pszBuffer[ MAX_PATH ];
	int			iCol=0;
	CRect		r;

	// format time string for above margin header:
	CTime m_ctTime = CTime::GetCurrentTime();
	if(!(dwFlags&LLC_HDRNODATE))
	{
		strTime = m_ctTime.Format("%m/%d/%y");
	}
	if(!(dwFlags&LLC_HDRNOTIME))
	{
		if(!(dwFlags&LLC_HDRNODATE))
		{
			strTime += " - ";
		}
		strTime += m_ctTime.Format("%I:%M:%S %p");
	}

	// print it and user supplied header above the margin:
	cpCurPos.y = crPrintedPage.top - LINESPACING;
	pDC->TextOut(crPrintedPage.left,cpCurPos.y,strHeader);
	pDC->TextOut(crPrintedPage.right-pDC->GetTextExtent(strTime).cx,cpCurPos.y,strTime);
	cpCurPos.y += LINESPACING;

	// underline it:
	pDC->MoveTo(crPrintedPage.left,crPrintedPage.top);
	pDC->LineTo(crPrintedPage.right,crPrintedPage.top);

	// display sub-header as on-screen column headers:
	cpCurPos.x = crPrintedPage.left;
	cpCurPos.y = crPrintedPage.top + LINESPACING/2;
	GetClientRect(&r);
	lvcColumn.mask = LVCF_WIDTH | LVCF_FMT | LVCF_TEXT;
	lvcColumn.pszText = pszBuffer;
	lvcColumn.cchTextMax = MAX_PATH;
	while( GetColumn( iCol, &lvcColumn ) )
	{
		// postition column wise:
		cpCurPos.x = crPrintedPage.left + (dwColumnWidths*crPrintedPage.Width())/r.Width();
		if(lvcColumn.fmt&LVCFMT_CENTER)
		{
			cpCurPos.x += (lvcColumn.cx*crPrintedPage.Width())/(2*r.Width());
			cpCurPos.x -= pDC->GetTextExtent(lvcColumn.pszText).cx/2;
		}
		else if(lvcColumn.fmt&LVCFMT_RIGHT)
		{
			cpCurPos.x += (lvcColumn.cx*crPrintedPage.Width())/r.Width();
			cpCurPos.x -= pDC->GetTextExtent(lvcColumn.pszText).cx;
		}
		// print:
		pDC->TextOut(cpCurPos.x,cpCurPos.y,lvcColumn.pszText);
		dwColumnWidths += lvcColumn.cx;
		iCol++;
	}

	// underline it:
	cpCurPos.y += (3*LINESPACING)/2;
	cpCurPos.x = crPrintedPage.left + (dwColumnWidths*crPrintedPage.Width())/r.Width();
	pDC->MoveTo(crPrintedPage.left,cpCurPos.y);
	pDC->LineTo(crPrintedPage.right,cpCurPos.y);

	// prep for first data line:
	cpCurPos.y += LINESPACING/2;
}

void LListCtrl::print_line(CDC *pDC, CRect crPrintedPage, CSize csCharSize, CPoint& cpCurPos, DWORD dwLine) 
{
	LV_COLUMN	lvcColumn;
	DWORD		dwColumnWidths=0;
	int			iCol=0;
	CRect		r;

	// display item text as on-screen:
	GetClientRect(&r);
	cpCurPos.x = crPrintedPage.left;
	lvcColumn.mask = LVCF_WIDTH | LVCF_FMT;
	while( GetColumn( iCol, &lvcColumn ) )
	{
		// postition column wise:
		cpCurPos.x = crPrintedPage.left + (dwColumnWidths*crPrintedPage.Width())/r.Width();
		if(lvcColumn.fmt&LVCFMT_CENTER)
		{
			cpCurPos.x += (lvcColumn.cx*crPrintedPage.Width())/(2*r.Width());
			cpCurPos.x -= pDC->GetTextExtent(GetItemText(dwLine,iCol)).cx/2;
		}
		else if(lvcColumn.fmt&LVCFMT_RIGHT)
		{
			cpCurPos.x += (lvcColumn.cx*crPrintedPage.Width())/r.Width();
			cpCurPos.x -= pDC->GetTextExtent(GetItemText(dwLine,iCol)).cx;
		}
		// print
		pDC->TextOut(cpCurPos.x,cpCurPos.y,GetItemText(dwLine,iCol));
		dwColumnWidths += lvcColumn.cx;
		iCol++;
	}
	// advance for next line call:
	cpCurPos.y += LINESPACING;
}

void LListCtrl::print_footer(CDC *pDC,CRect crPrintedPage,CSize csCharSize, CString& strFooter, DWORD dwPage, DWORD dwPagesTotal) 
{
	// overline it:
	pDC->MoveTo(crPrintedPage.left,crPrintedPage.bottom);
	pDC->LineTo(crPrintedPage.right,crPrintedPage.bottom);

	// print user footer:
	pDC->TextOut(crPrintedPage.left,crPrintedPage.bottom+LINESPACING,strFooter);

	// format footer as page n of N:
	CString strPage;
	strPage.Format( _T( "%d of %d" ), dwPage, dwPagesTotal );

	// print it:
	pDC->TextOut(crPrintedPage.right-pDC->GetTextExtent(strPage).cx,crPrintedPage.bottom+LINESPACING,strPage);
}


int LListCtrl::SelectAll() 
{
	for (int i=0;i<GetItemCount();i++)
	{
		SetCurSel(i);
	}

	// Scroll up to the first selected
	SetCurSel(0);

	SetItemState(-1,LVIS_FOCUSED,LVIS_FOCUSED);

	return TRUE;
}


#define VK_C		67
#define VK_V		86
#define VK_X		88
#define VK_Z		90
BOOL LListCtrl::PreTranslateMessage(MSG* pMsg)
{
	// If edit control is visible in tree view control, sending a
	// WM_KEYDOWN message to the edit control will dismiss the edit
	// control.  When ENTER key was sent to the edit control, the parent
	// window of the tree view control is responsible for updating the
	// item's label in TVN_ENDLABELEDIT notification code.
	if ( pMsg->message == WM_KEYDOWN )
	{
		CHAR ckey=toupper( pMsg->wParam &0xFF );

		if ( VK_RETURN == pMsg->wParam )
			m_bEditNext = TRUE;
		if ( VK_ESCAPE == pMsg->wParam )
			m_bEditNext = FALSE;


		if( GetKeyState( VK_CONTROL )<-1 && (ckey== 'A') )
		{
			SelectAll();
			return TRUE;
		}
		if( GetKeyState( VK_CONTROL )<-1 && (ckey== 'N') )
		{
			SelectNone();
			return TRUE;
		}


		CEdit* edit = GetEditControl();
		if (edit)
		{
//			LTRACE("Control key status = %d %d\n",LOBYTE(GetKeyState( VK_CONTROL )),HIWORD(GetKeyState( VK_CONTROL )));

			if( GetKeyState( VK_CONTROL )<-1 && (ckey== _T( 'C' ) ) )
			{
				edit->Copy();
				return TRUE;
			}
			if( GetKeyState( VK_CONTROL )<-1 && (ckey== _T( 'V' ) ) )
			{
				edit->Paste();
				return TRUE;
			}
			if( GetKeyState( VK_CONTROL )<-1 && (ckey== _T( 'X' ) ) )
			{
				edit->Cut();
				return TRUE;
			}
			if( GetKeyState( VK_CONTROL )<-1 && (ckey== _T( 'Z' ) ) )
			{
				edit->Undo();
				return TRUE;
			}
			if( pMsg->wParam == VK_RETURN || pMsg->wParam == VK_ESCAPE || pMsg->wParam == VK_CONTROL || pMsg->wParam == VK_INSERT || pMsg->wParam == VK_SHIFT )
			{
				edit->SendMessage(WM_KEYDOWN, pMsg->wParam, pMsg->lParam);
				return TRUE;
			}
		}
	}
	return CListCtrl::PreTranslateMessage(pMsg);
}

int LListCtrl::SelectNone() 
{
	// -1 changes the state of all items in the list.
	return SetItemState(-1, ~LVIS_SELECTED, LVIS_SELECTED);

//	return SetCurSel(-1);
}

void LListCtrl::OnEndlabeledit(NMHDR* pNMHDR, LRESULT* pResult) 
{
/*
	LV_DISPINFO* pDispInfo = (LV_DISPINFO*)pNMHDR;
 	LV_ITEM	*pItem = &pDispInfo->item;

	if ( pItem->pszText != NULL )
		SetItemText( pItem->iItem, pItem->iSubItem, pItem->pszText );

*/	
	int nFocused = GetNextItem(-1,LVNI_FOCUSED);

	// select next item
	nFocused++;

//	PostMessage( WM_KEYDOWN, 28, 0x1500001 );
//	PostMessage( WM_KEYUP, 28, 0x1500001 );
//	PostMessage( WM_KEYDOWN, 71, 0x3c0001 );
//	PostMessage( WM_KEYUP, 71, 0x3c0001 );
/*
	int	nCount = GetItemCount();

	if ( nFocused < GetItemCount() )
	{
//		SetFocus( nFocused );

		EditLabel( nFocused );
	}
*/
	*pResult = TRUE;
}

CMutex myLockListCtrl;

void LListCtrl::SetHeaderControlText( DWORD dwPos, const CString& strValue )
{
	myLockListCtrl.Lock();
	if ( m_hWnd )
	{
		CHeaderCtrl* pHeaderCtrl = GetHeaderCtrl();

		ASSERT( dwPos < pHeaderCtrl->GetItemCount() );

		HDITEM hdi;

		memset( &hdi, 0, sizeof( HDITEM ) );

		// Get header item data
		pHeaderCtrl->GetItem( dwPos, &hdi );

		// modify item data
		hdi.pszText = (LPTSTR)(LPCTSTR)strValue;
		hdi.mask = HDI_TEXT;

		// Set item data
		pHeaderCtrl->SetItem( dwPos, &hdi );

	}
	myLockListCtrl.Unlock();
}

void LListCtrl::OnClick(NMHDR* pNMHDR, LRESULT* pResult) 
{
	*pResult = 0;
}

