project64/Source/Project64/UserInterface/WTLControls/ClistCtrl/ListCtrl.h

3852 lines
109 KiB
C++

/////////////////////////////////////////////////////////////////////////////
//
// CListCtrl - A WTL list control with Windows Vista style item selection.
//
// Revision: 1.5
// Last modified: 9th April 2006
//
/////////////////////////////////////////////////////////////////////////////
#pragma once
#include <set>
#include <algorithm>
#include <wtl/atlctrlx.h>
#include <wtl/atlframe.h>
#include <wtl/atlmisc.h>
#include <wtl/atlgdi.h>
#include "DragDrop.h"
#include "DropArrows.h"
#include "TitleTip.h"
#include "ListEdit.h"
#include "ListCombo.h"
#include "ListDate.h"
struct CListColumn
{
stdstr m_strText;
int m_nWidth;
BOOL m_bFixed;
UINT m_nFormat;
UINT m_nFlags;
int m_nImage;
int m_nIndex;
CListArray < stdstr > m_aComboList;
};
template < class T >
class CListImpl : public CWindowImpl< CListImpl< T > >,
public CDoubleBufferImpl< CListImpl< T > >
{
public:
CListImpl()
{
m_bSortEnabled = TRUE; // Added by Rowan 05/12/2006
m_bShowHeader = TRUE;
m_bSortAscending = TRUE;
m_bButtonDown = FALSE;
m_bMouseOver = FALSE;
m_bColumnSizing = FALSE;
m_bBeginSelect = FALSE;
m_bSingleSelect = FALSE;
m_bFocusSubItem = FALSE;
m_bGroupSelect = FALSE;
m_bEnableHorizScroll = FALSE;
m_bEnableVertScroll = FALSE;
m_bShowHorizScroll = TRUE;
m_bShowVertScroll = TRUE;
m_bShowSort = TRUE;
m_bResizeTimer = FALSE;
m_bDragDrop = FALSE;
m_bSmoothScroll = TRUE;
m_bEditItem = FALSE;
m_bScrolling = FALSE;
m_bScrollDown = FALSE;
m_bTileBackground = FALSE;
m_nMouseWheelScroll = 3;
m_nTotalWidth = 0;
m_nHeaderHeight = 0;
m_nItemHeight = 0;
m_nFirstSelected = NULL_ITEM;
m_nFocusItem = NULL_ITEM;
m_nFocusSubItem = NULL_SUBITEM;
m_nHotItem = NULL_ITEM;
m_nHotSubItem = NULL_SUBITEM;
m_nTitleTipItem = NULL_ITEM;
m_nTitleTipSubItem = NULL_SUBITEM;
m_nSortColumn = NULL_COLUMN;
m_nHighlightColumn = NULL_COLUMN;
m_nDragColumn = NULL_COLUMN;
m_nHotColumn = NULL_COLUMN;
m_nHotDivider = NULL_COLUMN;
m_nColumnSizing = NULL_COLUMN;
m_nScrollOffset = 0;
m_nScrollDelta = 0;
m_nScrollUnit = 0;
m_nStartScrollPos = 0;
m_nStartSize = 0;
m_nStartPos = 0;
m_ptDownPoint = 0;
m_ptSelectPoint = 0;
m_rcGroupSelect = 0;
m_dwSearchTick = 0;
m_dwScrollTick = 0;
m_strSearchString = _T( "" );
}
~CListImpl()
{
}
protected:
BOOL m_bSortEnabled; // Added by Rowan 05/12/2006 to disable sorting
BOOL m_bShowHeader;
BOOL m_bShowSort;
BOOL m_bSortAscending;
BOOL m_bButtonDown;
BOOL m_bMouseOver;
BOOL m_bColumnSizing;
BOOL m_bBeginSelect;
BOOL m_bSingleSelect;
BOOL m_bFocusSubItem;
BOOL m_bGroupSelect;
BOOL m_bShowHorizScroll;
BOOL m_bShowVertScroll;
BOOL m_bEnableHorizScroll;
BOOL m_bEnableVertScroll;
BOOL m_bResizeTimer;
BOOL m_bDragDrop;
BOOL m_bSmoothScroll;
BOOL m_bEditItem;
BOOL m_bScrolling;
BOOL m_bScrollDown;
BOOL m_bTileBackground;
CPoint m_ptDownPoint;
CPoint m_ptSelectPoint;
CRect m_rcGroupSelect;
int m_nItemHeight;
int m_nHeaderHeight;
int m_nFirstSelected;
int m_nFocusItem;
int m_nFocusSubItem;
int m_nHotItem;
int m_nHotSubItem;
int m_nTitleTipItem;
int m_nTitleTipSubItem;
int m_nMouseWheelScroll;
int m_nTotalWidth;
int m_nSortColumn;
int m_nDragColumn;
int m_nHighlightColumn;
int m_nHotColumn;
int m_nHotDivider;
int m_nColumnSizing;
int m_nScrollOffset;
int m_nScrollDelta;
int m_nScrollUnit;
int m_nStartScrollPos;
int m_nStartSize;
int m_nStartPos;
DWORD m_dwSearchTick;
DWORD m_dwScrollTick;
stdstr m_strSearchString;
CBitmap m_bmpScrollList;
CBitmap m_bmpBackground;
CLIPFORMAT m_nHeaderClipboardFormat;
COLORREF m_rgbBackground;
COLORREF m_rgbHeaderBackground;
COLORREF m_rgbHeaderBorder;
COLORREF m_rgbHeaderShadow;
COLORREF m_rgbHeaderText;
COLORREF m_rgbHeaderHighlight;
COLORREF m_rgbSelectedItem;
COLORREF m_rgbSelectedText;
COLORREF m_rgbItemText;
COLORREF m_rgbSelectOuter;
COLORREF m_rgbSelectInner;
COLORREF m_rgbSelectTop;
COLORREF m_rgbSelectBottom;
COLORREF m_rgbNoFocusTop;
COLORREF m_rgbNoFocusBottom;
COLORREF m_rgbNoFocusOuter;
COLORREF m_rgbNoFocusInner;
COLORREF m_rgbFocusTop;
COLORREF m_rgbFocusBottom;
COLORREF m_rgbProgressTop;
COLORREF m_rgbProgressBottom;
COLORREF m_rgbItemFocus;
COLORREF m_rgbHyperLink;
CCursor m_curDivider;
CCursor m_curHyperLink;
CFont m_fntListFont;
CFont m_fntUnderlineFont;
CImageList m_ilListItems;
CImageList m_ilItemImages;
CDragDrop < CListImpl > m_oleDragDrop;
CToolTipCtrl m_ttToolTip;
CDropArrows m_wndDropArrows;
CTitleTip m_wndTitleTip;
CListEdit m_wndItemEdit;
CListCombo m_wndItemCombo;
CListDate m_wndItemDate;
CListArray < CListColumn > m_aColumns;
set < int > m_setSelectedItems;
public:
BOOL SubclassWindow( HWND hWnd )
{
T* pT;
pT = static_cast<T*>(this);
return CWindowImpl< CListImpl >::SubclassWindow( hWnd ) ? pT->Initialise() : FALSE;
}
void RegisterClass()
{
T* pT = static_cast<T*>(this);
pT = pT;
pT->GetWndClassInfo().m_wc.lpfnWndProc = m_pfnSuperWindowProc;
pT->GetWndClassInfo().Register( &m_pfnSuperWindowProc );
}
BOOL Initialise()
{
// load list images
if ( !m_ilListItems.CreateFromImage( IDB_LISTITEMS, 16, 0, RGB( 255, 0, 255 ), IMAGE_BITMAP, LR_CREATEDIBSECTION ) )
return FALSE;
if ( m_curDivider.LoadCursor( IDC_DIVIDER ) == NULL )
return FALSE;
if ( m_curHyperLink.LoadCursor( IDC_HYPERLINK ) == NULL )
return FALSE;
// load interface settings
if ( !LoadSettings() )
return FALSE;
// give control a static border
ModifyStyle( WS_BORDER, WS_CLIPCHILDREN );
ModifyStyleEx( WS_EX_CLIENTEDGE, WS_EX_STATICEDGE, SWP_FRAMECHANGED );
// register drag drop
m_oleDragDrop.Register( this );
m_oleDragDrop.AddTargetFormat( m_nHeaderClipboardFormat );
m_oleDragDrop.AddSourceFormat( m_nHeaderClipboardFormat );
// create the tooltip
if ( !m_ttToolTip.Create( m_hWnd ) )
return FALSE;
m_ttToolTip.SetMaxTipWidth( SHRT_MAX );
return TRUE;
}
BOOL LoadSettings()
{
m_rgbBackground = GetSysColor( COLOR_WINDOW );
m_rgbHeaderBackground = GetSysColor( COLOR_BTNFACE );
m_rgbHeaderBorder = GetSysColor( COLOR_3DHIGHLIGHT );
m_rgbHeaderShadow = GetSysColor( COLOR_3DSHADOW );
m_rgbHeaderText = GetSysColor( COLOR_WINDOWTEXT );
m_rgbHeaderHighlight = RGB( 130, 140, 180 );
m_rgbSelectedItem = GetSysColor( COLOR_HIGHLIGHT );
m_rgbSelectedText = GetSysColor( COLOR_HIGHLIGHTTEXT );
m_rgbItemText = GetSysColor( COLOR_WINDOWTEXT );
m_rgbSelectOuter = RGB( 170, 200, 245 );
m_rgbSelectInner = RGB( 230, 250, 250 );
m_rgbSelectTop = RGB( 210, 240, 250 );
m_rgbSelectBottom = RGB( 185, 215, 250 );
m_rgbNoFocusTop = RGB( 250, 250, 250 );
m_rgbNoFocusBottom = RGB( 235, 235, 235 );
m_rgbNoFocusOuter = RGB( 220, 220, 220 );
m_rgbNoFocusInner = RGB( 245, 245, 245 );
m_rgbFocusTop = RGB( 235, 245, 245 );
m_rgbFocusBottom = RGB( 225, 235, 245 );
m_rgbProgressTop = RGB( 170, 240, 170 );
m_rgbProgressBottom = RGB( 45, 210, 50 );
m_rgbItemFocus = RGB( 180, 190, 210 );
m_rgbHyperLink = RGB( 0, 0, 200 );
m_nHeaderClipboardFormat = (CLIPFORMAT)RegisterClipboardFormat( _T( "HEADERCLIPBOARDFORMAT" ) );
// get number of lines to scroll
#if (_WIN32_WINNT >= 0x0400) || (_WIN32_WINDOWS > 0x0400)
SystemParametersInfo( SPI_GETWHEELSCROLLLINES, 0, &m_nMouseWheelScroll, 0 );
#endif
// get system message font
CLogFont logFont;
logFont.SetMessageBoxFont();
if ( !m_fntListFont.IsNull() )
m_fntListFont.DeleteObject();
if ( m_fntListFont.CreateFontIndirect( &logFont ) == NULL )
return FALSE;
// get system underline font
logFont.lfUnderline = BYTE(TRUE);
if ( !m_fntUnderlineFont.IsNull() )
m_fntUnderlineFont.DeleteObject();
if ( m_fntUnderlineFont.CreateFontIndirect( &logFont ) == NULL )
return FALSE;
CClientDC dcClient( m_hWnd );
HFONT hOldFont = dcClient.SelectFont( m_fntListFont );
CSize sizeExtent;
if ( !dcClient.GetTextExtent( _T( "Height" ), -1, &sizeExtent ) )
return FALSE;
dcClient.SelectFont( hOldFont );
// has system font changed
if ( m_nItemHeight != sizeExtent.cy + ITEM_HEIGHT_MARGIN )
{
m_nItemHeight = sizeExtent.cy + ITEM_HEIGHT_MARGIN;
m_nHeaderHeight = m_nItemHeight;
// create drop arrows window
if ( m_wndDropArrows.IsWindow() )
m_wndDropArrows.DestroyWindow();
if ( !m_wndDropArrows.Create( m_hWnd, m_nHeaderHeight, TRUE ) )
return FALSE;
}
// create titletip window
if ( m_wndTitleTip.IsWindow() )
m_wndTitleTip.DestroyWindow();
if ( !m_wndTitleTip.Create( m_hWnd ) )
return FALSE;
return TRUE;
}
// Added by Rowan 05/12/2006
void SetSortEnabled(BOOL bSortEnabled)
{
m_bSortEnabled = bSortEnabled;
}
void ShowHeader( BOOL bShowHeader = TRUE )
{
m_bShowHeader = bShowHeader;
ResetScrollBars();
Invalidate();
}
void ShowHeaderSort( BOOL bShowSort = TRUE )
{
m_bShowSort = bShowSort;
Invalidate();
}
void SetSingleSelect( BOOL bSingleSelect = TRUE )
{
m_bSingleSelect = bSingleSelect;
Invalidate();
}
void SetFocusSubItem( BOOL bFocusSubItem = TRUE )
{
m_bFocusSubItem = bFocusSubItem;
Invalidate();
}
void SetDragDrop( BOOL bDragDrop = TRUE )
{
m_bDragDrop = bDragDrop;
}
void SetSmoothScroll( BOOL bSmoothScroll = TRUE )
{
m_bSmoothScroll = bSmoothScroll;
}
void SetBackgroundImage( HBITMAP hBackgroundImage, BOOL bTileImage = FALSE )
{
m_bmpBackground = hBackgroundImage;
m_bTileBackground = bTileImage;
}
void SetImageList( CImageList& ilItemImages )
{
m_ilItemImages = ilItemImages;
}
UINT ValidateFlags( UINT nFlags )
{
if ( nFlags & ITEM_FLAGS_CENTRE )
nFlags &= ~( ITEM_FLAGS_LEFT | ITEM_FLAGS_RIGHT );
if ( nFlags & ITEM_FLAGS_RIGHT )
nFlags &= ~ITEM_FLAGS_LEFT;
if ( nFlags & ITEM_FLAGS_DATE_ONLY )
nFlags &= ~ITEM_FLAGS_TIME_ONLY;
if ( nFlags & ( ITEM_FLAGS_EDIT_NUMBER | ITEM_FLAGS_EDIT_FLOAT ) )
nFlags &= ~ITEM_FLAGS_EDIT_UPPER;
if ( !( nFlags & ( ITEM_FLAGS_EDIT_NUMBER | ITEM_FLAGS_EDIT_FLOAT ) ) )
nFlags &= ~( ITEM_FLAGS_EDIT_NEGATIVE | ITEM_FLAGS_EDIT_OPERATOR );
if ( nFlags & ITEM_FLAGS_COMBO_EDIT )
nFlags &= ~( ITEM_FLAGS_DATE_ONLY | ITEM_FLAGS_TIME_ONLY | ITEM_FLAGS_DATETIME_NONE );
return nFlags;
}
void AddColumn( CListColumn& listColumn )
{
// minimum column width
if ( listColumn.m_strText.empty() && listColumn.m_nImage != ITEM_IMAGE_NONE )
{
CSize sizeIcon;
m_ilListItems.GetIconSize( sizeIcon );
listColumn.m_nWidth = sizeIcon.cx + 5;
listColumn.m_nFlags |= ITEM_FLAGS_CENTRE;
}
// correct incompatible flag mask values
listColumn.m_nFlags = ValidateFlags( listColumn.m_nFlags );
// initial data index
listColumn.m_nIndex = GetColumnCount();
m_aColumns.Add( listColumn );
ResetScrollBars();
Invalidate();
}
void AddColumn( LPCTSTR lpszText, int nWidth = 0, int nImage = ITEM_IMAGE_NONE, BOOL bFixed = FALSE, UINT nFormat = ITEM_FORMAT_NONE, UINT nFlags = ITEM_FLAGS_NONE )
{
CListColumn listColumn;
listColumn.m_strText = lpszText;
listColumn.m_nWidth = nWidth;
listColumn.m_bFixed = bFixed;
listColumn.m_nFormat = nFormat;
listColumn.m_nFlags = nFlags;
listColumn.m_nImage = nImage;
AddColumn( listColumn );
}
void RemoveAllCoumns ( void )
{
m_aColumns.RemoveAll();
ResetScrollBars();
Invalidate();
}
BOOL GetHasEditItem ()
{
return m_bEditItem;
}
int GetColumnCount()
{
return m_aColumns.GetSize();
}
BOOL GetColumn( int nColumn, CListColumn& listColumn )
{
if ( nColumn < 0 || nColumn >= GetColumnCount() )
return FALSE;
listColumn = m_aColumns[ nColumn ];
return TRUE;
}
int GetTotalWidth( BOOL bRecalc = FALSE )
{
if ( bRecalc )
{
m_nTotalWidth = 0;
for ( int nColumn = 0; nColumn < GetColumnCount(); nColumn++ )
m_nTotalWidth += GetColumnWidth( nColumn );
}
return m_nTotalWidth - 1;
}
int GetTotalHeight()
{
T* pT = static_cast<T*>(this);
return max( ( pT->GetItemCount() * m_nItemHeight ) + ( m_bShowHeader ? m_nHeaderHeight : 0 ), 1 );
}
BOOL SetColumnWidth( int nColumn, int nWidth )
{
if ( nColumn < 0 || nColumn >= GetColumnCount() )
return FALSE;
// set new column size if not fixed
if ( !m_aColumns[ nColumn ].m_bFixed )
{
m_aColumns[ nColumn ].m_nWidth = nWidth;
ResetScrollBars();
Invalidate();
}
return TRUE;
}
int GetColumnWidth( int nColumn )
{
CListColumn listColumn;
return GetColumn( nColumn, listColumn ) ? listColumn.m_nWidth : 0;
}
int GetColumnIndex( int nColumn )
{
CListColumn listColumn;
return GetColumn( nColumn, listColumn ) ? listColumn.m_nIndex : 0;
}
int IndexToOrder( int nIndex )
{
for ( int nColumn = 0; nColumn < GetColumnCount(); nColumn++ )
{
if ( GetColumnIndex( nColumn ) == nIndex )
return nColumn;
}
return -1;
}
BOOL SetColumnFormat( int nColumn, UINT nFormat, UINT nFlags = ITEM_FLAGS_NONE )
{
if ( nColumn < 0 || nColumn >= GetColumnCount() )
return FALSE;
m_aColumns[ nColumn ].m_nFormat = nFormat;
m_aColumns[ nColumn ].m_nFlags = ValidateFlags( nFlags );
return TRUE;
}
BOOL SetColumnFormat( int nColumn, UINT nFormat, UINT nFlags, CListArray < stdstr >& aComboList )
{
if ( nColumn < 0 || nColumn >= GetColumnCount() )
return FALSE;
m_aColumns[ nColumn ].m_nFormat = nFormat;
m_aColumns[ nColumn ].m_nFlags = ValidateFlags( nFlags );
m_aColumns[ nColumn ].m_aComboList = aComboList;
return TRUE;
}
UINT GetColumnFormat( int nColumn )
{
CListColumn listColumn;
return GetColumn( nColumn, listColumn ) ? listColumn.m_nFormat : ITEM_FORMAT_NONE;
}
UINT GetColumnFlags( int nColumn )
{
CListColumn listColumn;
return GetColumn( nColumn, listColumn ) ? listColumn.m_nFlags : ITEM_FLAGS_NONE;
}
BOOL GetColumnComboList( int nColumn, CListArray < stdstr >& aComboList )
{
CListColumn listColumn;
if ( !GetColumn( nColumn, listColumn ) )
return FALSE;
aComboList = listColumn.m_aComboList;
return !aComboList.IsEmpty();
}
BOOL GetColumnRect( int nColumn, CRect& rcColumn )
{
if ( nColumn < 0 || nColumn >= GetColumnCount() )
return FALSE;
GetClientRect( rcColumn );
rcColumn.bottom = m_nHeaderHeight;
for ( int nColumnOrder = 0; nColumnOrder < GetColumnCount(); nColumnOrder++ )
{
int nWidth = GetColumnWidth( nColumnOrder );
if ( nColumn == nColumnOrder )
{
rcColumn.right = rcColumn.left + nWidth;
break;
}
rcColumn.left += nWidth;
}
// offset column by scroll position
rcColumn.OffsetRect( -GetScrollPos( SB_HORZ ), 0 );
return TRUE;
}
BOOL AddItem()
{
ResetScrollBars();
return Invalidate();
}
BOOL DeleteItem( int nItem )
{
m_setSelectedItems.erase( nItem );
ResetScrollBars();
return Invalidate();
}
BOOL DeleteAllItems()
{
m_setSelectedItems.clear();
ResetScrollBars();
return Invalidate();
}
int GetItemCount()
{
ATLASSERT( FALSE ); // must be implemented in a derived class
return 0;
}
stdstr GetItemText( int nItem, int nSubItem )
{
ATLASSERT( FALSE ); // must be implemented in a derived class
return _T( "" );
}
BOOL GetItemDate( int nItem, int nSubItem, SYSTEMTIME& stItemDate )
{
T* pT = static_cast<T*>(this);
ZeroMemory( &stItemDate, sizeof( SYSTEMTIME ) );
stdstr strItemText = pT->GetItemText( nItem, nSubItem );
if ( strItemText.empty() )
return FALSE;
// get date-time from item text: yyyymmddhhmmss
stItemDate.wYear = (WORD)_ttoi( strItemText.substr(0, 4 ).c_str() );
stItemDate.wMonth = (WORD)_ttoi( strItemText.substr( 4, 2 ).c_str() );
stItemDate.wDay = (WORD)_ttoi( strItemText.substr( 6, 2 ).c_str() );
stItemDate.wHour = (WORD)_ttoi( strItemText.substr( 8, 2 ).c_str() );
stItemDate.wMinute = (WORD)_ttoi( strItemText.substr( 10, 2 ).c_str() );
stItemDate.wSecond = (WORD)_ttoi( strItemText.substr( 12, 2 ).c_str() );
stItemDate.wMilliseconds = 0;
return TRUE;
}
int GetItemImage( int nItem, int nSubItem )
{
return ITEM_IMAGE_NONE; // may be implemented in a derived class
}
UINT GetItemFormat( int nItem, int nSubItem )
{
return GetColumnFormat( IndexToOrder( nSubItem ) ); // may be implemented in a derived class
}
UINT GetItemFlags( int nItem, int nSubItem )
{
return GetColumnFlags( IndexToOrder( nSubItem ) ); // may be implemented in a derived class
}
BOOL GetItemComboList( int nItem, int nSubItem, CListArray < stdstr >& aComboList )
{
return GetColumnComboList( IndexToOrder( nSubItem ), aComboList ); // may be implemented in a derived class
}
HFONT GetItemFont( int /*nItem*/, int /*nSubItem*/ )
{
return m_fntListFont; // may be implemented in a derived class
}
BOOL GetItemColours( int nItem, int nSubItem, COLORREF& rgbBackground, COLORREF& rgbText )
{
rgbBackground = m_rgbBackground;
rgbText = m_rgbItemText;
return TRUE;
}
stdstr virtual GetItemToolTip( int /*nItem*/, int /*nSubItem*/ )
{
return _T( "" ); // may be implemented in a derived class
}
stdstr virtual GetHeaderToolTip(int /*column*/)
{
return _T(""); //implemented by child class
}
BOOL SetItemText( int nItem, int nSubItem, LPCTSTR lpszText )
{
ATLASSERT( FALSE ); // must be implemented in a derived class
return FALSE;
}
BOOL SetItemComboIndex( int nItem, int nSubItem, int nIndex )
{
ATLASSERT( FALSE ); // must be implemented in a derived class
return FALSE;
}
BOOL SetItemDate( int nItem, int nSubItem, SYSTEMTIME& stItemDate )
{
T* pT = static_cast<T*>(this);
// set date-time in format (yyyymmddhhmmss)
stdstr strFormatDate;
strFormatDate.Format( _T( "%04d%02d%02d%02d%02d%02d" ), stItemDate.wYear, stItemDate.wMonth, stItemDate.wDay, stItemDate.wHour, stItemDate.wMinute, stItemDate.wSecond );
return pT->SetItemText( nItem, nSubItem, strFormatDate.c_str() );
}
BOOL SetItemCheck( int nItem, int nSubItem, int nCheckValue )
{
T* pT = static_cast<T*>(this);
switch ( pT->GetItemFormat( nItem, nSubItem ) )
{
case ITEM_FORMAT_CHECKBOX: return pT->SetItemText( nItem, nSubItem, nCheckValue > 0 ? _T( "1" ) : _T( "0" ) );
case ITEM_FORMAT_CHECKBOX_3STATE: if ( nCheckValue < 0 )
return pT->SetItemText( nItem, nSubItem, _T( "-1" ) );
if ( nCheckValue > 0 )
return pT->SetItemText( nItem, nSubItem, _T( "1" ) );
return pT->SetItemText( nItem, nSubItem, _T( "0" ) );
}
return FALSE;
}
BOOL SetItemImage( int nItem, int nSubItem, int nImage )
{
ATLASSERT( FALSE ); // must be implemented in a derived class
return FALSE;
}
BOOL SetItemFormat( int nItem, int nSubItem, UINT nFormat, UINT nFlags = ITEM_FLAGS_NONE )
{
ATLASSERT( FALSE ); // must be implemented in a derived class
return FALSE;
}
BOOL SetItemFormat( int nItem, int nSubItem, UINT nFormat, UINT nFlags, CListArray < stdstr >& aComboList )
{
ATLASSERT( FALSE ); // must be implemented in a derived class
return FALSE;
}
BOOL SetItemFont( int nItem, int nSubItem, HFONT hFont )
{
ATLASSERT( FALSE ); // must be implemented in a derived class
return FALSE;
}
BOOL SetItemColours( int nItem, int nSubItem, COLORREF rgbBackground, COLORREF rgbText )
{
ATLASSERT( FALSE ); // must be implemented in a derived class
return FALSE;
}
void ReverseItems()
{
ATLASSERT( FALSE ); // must be implemented in a derived class
}
void SortItems( int nColumn, BOOL bAscending )
{
ATLASSERT( FALSE ); // must be implemented in a derived class
}
BOOL GetItemRect( int nItem, int nSubItem, CRect& rcItem )
{
T* pT = static_cast<T*>(this);
int nTopItem = GetTopItem();
if ( nItem < nTopItem || nItem >= pT->GetItemCount() || nItem >= nTopItem + GetCountPerPage() )
return FALSE;
CRect rcClient;
GetClientRect( rcClient );
// calculate item rect based on scroll position
rcItem = rcClient;
rcItem.top = ( m_bShowHeader ? m_nHeaderHeight : 0 ) + ( ( nItem - nTopItem ) * m_nItemHeight );
rcItem.bottom = rcItem.top + m_nItemHeight;
rcItem.right = min( rcClient.right, GetTotalWidth() );
if ( nSubItem != NULL_SUBITEM )
{
CRect rcColumn;
if ( !GetColumnRect( nSubItem, rcColumn ) )
return FALSE;
rcItem.left = rcColumn.left;
rcItem.right = rcColumn.right;
}
return TRUE;
}
BOOL GetItemRect( int nItem, CRect& rcItem )
{
return GetItemRect( nItem, NULL_SUBITEM, rcItem );
}
BOOL InvalidateItem( int nItem, int nSubItem = NULL_SUBITEM )
{
CRect rcItem;
return GetItemRect( nItem, nSubItem, rcItem ) ? InvalidateRect( rcItem ) : FALSE;
}
BOOL InvalidateHeader()
{
if ( !m_bShowHeader )
return TRUE;
CRect rcHeader;
if ( !GetClientRect( rcHeader ) )
return FALSE;
rcHeader.bottom = m_nHeaderHeight;
return InvalidateRect( rcHeader );
}
int GetTopItem()
{
return (int)( GetScrollPos( SB_VERT ) / m_nItemHeight );
}
int GetCountPerPage( BOOL bPartial = TRUE )
{
CRect rcClient;
GetClientRect( rcClient );
rcClient.top = ( m_bShowHeader ? m_nHeaderHeight : 0 );
// calculate number of items per control height (include partial item)
div_t divHeight = div( rcClient.Height(), m_nItemHeight );
// round up to nearest item count
return max( bPartial && divHeight.rem > 0 ? divHeight.quot + 1 : divHeight.quot, 1 );
}
BOOL IsItemVisible( int nItem, int nSubItem = NULL_SUBITEM, BOOL bPartial = TRUE )
{
T* pT = static_cast<T*>(this);
int nTopItem = GetTopItem();
if ( nItem < nTopItem || nItem >= pT->GetItemCount() )
return FALSE;
// check whether item is visible
if ( nItem < nTopItem || nItem >= nTopItem + GetCountPerPage( bPartial ) )
return FALSE;
// check whether subitem is visible
if ( m_bFocusSubItem && nSubItem != NULL_SUBITEM )
{
CRect rcColumn;
if ( !GetColumnRect( nSubItem, rcColumn ) )
return FALSE;
CRect rcClient;
GetClientRect( rcClient );
if ( rcColumn.left < rcClient.left || rcColumn.right > rcClient.right )
return FALSE;
}
return TRUE;
}
BOOL EnsureItemVisible( int nItem, int nSubItem = NULL_SUBITEM )
{
if ( IsItemVisible( nItem, nSubItem, FALSE ) )
return TRUE;
HideTitleTip();
CRect rcClient;
GetClientRect( rcClient );
rcClient.top = ( m_bShowHeader ? m_nHeaderHeight : 0 );
CRect rcItem;
rcItem.top = ( m_bShowHeader ? m_nHeaderHeight : 0 ) + ( ( nItem - GetTopItem() ) * m_nItemHeight );
rcItem.bottom = rcItem.top + m_nItemHeight;
if ( rcItem.top < rcClient.top || rcItem.bottom > rcClient.bottom )
{
int nScrollItem = NULL_ITEM;
// scroll list up/down to include item
if ( rcItem.top < rcClient.top || rcItem.Height() > rcClient.Height() )
nScrollItem = nItem;
else if ( rcItem.bottom > rcClient.bottom )
nScrollItem = nItem - ( GetCountPerPage( FALSE ) - 1 );
if ( nScrollItem != NULL_ITEM )
SetScrollPos( SB_VERT, nScrollItem * m_nItemHeight );
}
if ( m_bFocusSubItem && nSubItem != NULL_SUBITEM )
{
CRect rcColumn;
if ( !GetColumnRect( nSubItem, rcColumn ) )
return FALSE;
CRect rcClient;
GetClientRect( rcClient );
int nScrollPos = 0;
// scroll list left/right to include subitem
if ( rcColumn.Width() > rcClient.Width() || rcColumn.left < 0 )
nScrollPos = rcColumn.left;
else if ( rcColumn.right > rcClient.right )
nScrollPos = rcColumn.right - rcClient.right;
if ( nScrollPos != 0 )
SetScrollPos( SB_HORZ, GetScrollPos( SB_HORZ ) + nScrollPos );
}
return Invalidate();
}
void ShowScrollBar( int nScrollBar, BOOL bShow = TRUE )
{
switch ( nScrollBar )
{
case SB_HORZ: m_bShowHorizScroll = bShow;
break;
case SB_VERT: m_bShowVertScroll = bShow;
break;
case SB_BOTH: m_bShowHorizScroll = bShow;
m_bShowVertScroll = bShow;
break;
}
ResetScrollBars();
Invalidate();
}
void ResetScrollBars( int nScrollBar = SB_BOTH, int nScrollPos = -1, BOOL bRecalc = TRUE )
{
T* pT = static_cast<T*>(this);
CRect rcClient;
GetClientRect( rcClient );
SCROLLINFO infoScroll;
infoScroll.cbSize = sizeof( SCROLLINFO );
infoScroll.fMask = nScrollPos < 0 ? SIF_PAGE | SIF_RANGE : SIF_PAGE | SIF_RANGE | SIF_POS;
infoScroll.nPos = nScrollPos;
infoScroll.nMin = 0;
if ( ( nScrollBar == SB_BOTH || nScrollBar == SB_VERT ) && m_bShowVertScroll )
{
infoScroll.nMax = ( pT->GetItemCount() * m_nItemHeight ) + ( m_bShowHeader ? m_nHeaderHeight : 0 );
infoScroll.nPage = rcClient.Height() - ( m_bShowHeader ? m_nHeaderHeight : 0 );
// are we within client range?
if ( (UINT)infoScroll.nMax <= infoScroll.nPage + ( m_bShowHeader ? m_nHeaderHeight : 0 ) )
infoScroll.nMax = 0;
// set vertical scroll bar
m_bEnableVertScroll = SetScrollInfo( SB_VERT, &infoScroll, TRUE ) ? ( infoScroll.nMax > 0 ) : FALSE;
}
if ( ( nScrollBar == SB_BOTH || nScrollBar == SB_HORZ ) && m_bShowHorizScroll )
{
infoScroll.nMax = GetTotalWidth( bRecalc );
infoScroll.nPage = rcClient.Width();
// are we within client range?
if ( infoScroll.nPage >= (UINT)infoScroll.nMax )
infoScroll.nMax = 0;
// set horizontal scroll bar
m_bEnableHorizScroll = SetScrollInfo( SB_HORZ, &infoScroll, TRUE ) ? ( infoScroll.nMax > (int)infoScroll.nPage ) : FALSE;
}
}
BOOL IsScrollBarVisible( int nScrollBar )
{
switch ( nScrollBar )
{
case SB_HORZ: return m_bEnableHorizScroll;
case SB_VERT: return m_bEnableVertScroll;
case SB_BOTH: return ( m_bEnableHorizScroll && m_bEnableVertScroll );
default: return FALSE;
}
}
BOOL ResetSelected()
{
m_setSelectedItems.clear();
m_nFocusItem = NULL_ITEM;
m_nFocusSubItem = NULL_SUBITEM;
m_nFirstSelected = NULL_ITEM;
return Invalidate();
}
BOOL SelectItem( int nItem, int nSubItem = NULL_SUBITEM, UINT nFlags = 0 )
{
T* pT = static_cast<T*>(this);
if ( nItem < 0 || nItem >= pT->GetItemCount() )
return FALSE;
BOOL bSelectItem = TRUE;
BOOL bSelectRange = !m_bSingleSelect && ( nFlags & MK_SHIFT );
BOOL bNewSelect = !( bSelectRange || ( nFlags & MK_CONTROL ) );
BOOL bEnsureVisible = FALSE;
// are we starting a new select sequence?
if ( bNewSelect || bSelectRange )
{
// are we simply reselecting the same item?
if ( m_setSelectedItems.size() == 1 && *m_setSelectedItems.begin() == nItem )
{
bSelectItem = FALSE;
m_nFirstSelected = nItem;
m_nFocusItem = nItem;
m_nFocusSubItem = nSubItem;
}
else
m_setSelectedItems.clear();
}
else // we adding to or removing from select sequence
{
if ( m_bSingleSelect )
m_setSelectedItems.clear();
set < int >::iterator posSelectedItem = m_setSelectedItems.find( nItem );
// is this item already selected?
if ( posSelectedItem != m_setSelectedItems.end() )
{
bSelectItem = FALSE;
m_setSelectedItems.erase( posSelectedItem );
m_nFirstSelected = nItem;
m_nFocusItem = nItem;
m_nFocusSubItem = m_setSelectedItems.size() > 1 ? NULL_SUBITEM : nSubItem;
}
}
// are we adding this item to the select sequence?
if ( bSelectItem )
{
bEnsureVisible = TRUE;
if ( bSelectRange )
{
if ( m_nFirstSelected == NULL_ITEM )
m_nFirstSelected = nItem;
for ( int nSelectedItem = min( m_nFirstSelected, nItem ); nSelectedItem <= max( m_nFirstSelected, nItem ); nSelectedItem++ )
m_setSelectedItems.insert( nSelectedItem );
}
else
{
m_nFirstSelected = nItem;
m_setSelectedItems.insert( nItem );
}
m_nFocusItem = nItem;
m_nFocusSubItem = m_setSelectedItems.size() > 1 ? NULL_SUBITEM : nSubItem;
// notify parent of selected item
NotifyParent( m_nFocusItem, m_nFocusSubItem, LCN_SELECTED );
}
// start visible timer (scrolls list to partially hidden item)
if ( !IsItemVisible( nItem, m_setSelectedItems.size() > 1 ? NULL_SUBITEM : nSubItem, FALSE ) )
SetTimer( ITEM_VISIBLE_TIMER, ITEM_VISIBLE_PERIOD );
else if ( m_nFocusItem != NULL_ITEM && m_nFocusSubItem != NULL_SUBITEM )
EditItem( m_nFocusItem, m_nFocusSubItem );
return Invalidate();
}
BOOL IsSelected( int nItem )
{
set < int >::iterator posSelectedItem = m_setSelectedItems.find( nItem );
return ( posSelectedItem != m_setSelectedItems.end() );
}
BOOL GetSelectedItems( CListArray < int >& aSelectedItems )
{
aSelectedItems.RemoveAll();
for ( set < int >::iterator posSelectedItem = m_setSelectedItems.begin(); posSelectedItem != m_setSelectedItems.end(); ++posSelectedItem )
aSelectedItems.Add( *posSelectedItem );
return !aSelectedItems.IsEmpty();
}
BOOL SetFocusItem( int nItem, int nSubItem = NULL_SUBITEM )
{
m_nFocusItem = nItem;
m_nFocusSubItem = nSubItem;
return EnsureItemVisible( m_nFocusItem, m_nFocusSubItem );
}
BOOL GetFocusItem( int& nItem, int& nSubItem )
{
nItem = IsSelected( m_nFocusItem ) ? m_nFocusItem : ( m_setSelectedItems.empty() ? NULL_ITEM : *m_setSelectedItems.begin() );
nSubItem = !m_bFocusSubItem || nItem == NULL_ITEM ? NULL_SUBITEM : m_nFocusSubItem;
return ( nItem != NULL_ITEM );
}
int GetFocusItem()
{
return IsSelected( m_nFocusItem ) ? m_nFocusItem : ( m_setSelectedItems.empty() ? NULL_ITEM : *m_setSelectedItems.begin() );
}
BOOL HitTestHeader( CPoint point, int& nColumn, UINT& nFlags )
{
// reset hittest flags
nFlags = HITTEST_FLAG_NONE;
if ( !m_bShowHeader )
return FALSE;
CRect rcClient;
if ( !GetClientRect( rcClient ) )
return FALSE;
// are we over the header?
if ( point.y < rcClient.top || point.y > m_nHeaderHeight )
return FALSE;
int nDividerPos = 0;
int nColumnCount = GetColumnCount();
// get hit-test subitem
for ( nColumn = 0; nColumn < nColumnCount; nColumn++ )
{
int nColumnWidth = GetColumnWidth( nColumn );
nDividerPos += nColumnWidth;
// offset divider position with current scroll position
int nRelativePos = nDividerPos - GetScrollPos( SB_HORZ );
// are we over the divider zone?
if ( point.x >= nRelativePos - DRAG_HEADER_OFFSET - 1 && point.x <= nRelativePos + DRAG_HEADER_OFFSET )
{
nFlags |= HITTEST_FLAG_HEADER_DIVIDER;
// are we to the left of the divider (or over last column divider)?
if ( ( point.x >= nRelativePos - DRAG_HEADER_OFFSET - 1 && point.x < nRelativePos ) || nColumn + 1 >= nColumnCount - 1 )
{
nFlags |= HITTEST_FLAG_HEADER_LEFT;
return TRUE;
}
// find last zero-length column after this column
for ( int nNextColumn = nColumn + 1; nNextColumn < nColumnCount; nNextColumn++ )
{
if ( GetColumnWidth( nNextColumn ) > 0 )
break;
nColumn = nNextColumn;
}
nFlags |= HITTEST_FLAG_HEADER_RIGHT;
return TRUE;
}
// are we over a column?
if ( point.x > nRelativePos - nColumnWidth && point.x < nRelativePos )
return TRUE;
}
return FALSE;
}
BOOL HitTest( CPoint point, int& nItem, int& nSubItem )
{
T* pT = static_cast<T*>(this);
// are we over the header?
if ( point.y < ( m_bShowHeader ? m_nHeaderHeight : 0 ) )
return FALSE;
// calculate hit test item
nItem = GetTopItem() + (int)( ( point.y - ( m_bShowHeader ? m_nHeaderHeight : 0 ) ) / m_nItemHeight );
if ( nItem < 0 || nItem >= pT->GetItemCount() )
return FALSE;
int nTotalWidth = 0;
int nColumnCount = GetColumnCount();
// get hit-test subitem
for ( nSubItem = 0; nSubItem < nColumnCount; nSubItem++ )
{
int nColumnWidth = GetColumnWidth( nSubItem );
nTotalWidth += nColumnWidth;
// offset position with current scroll position
int nRelativePos = nTotalWidth - GetScrollPos( SB_HORZ );
// are we over a subitem?
if ( point.x > nRelativePos - nColumnWidth && point.x < nRelativePos )
return TRUE;
}
return FALSE;
}
BOOL AutoSizeColumn( int nColumn )
{
T* pT = static_cast<T*>(this);
CListColumn listColumn;
if ( !GetColumn( nColumn, listColumn ) || listColumn.m_bFixed )
return FALSE;
CClientDC dcClient( m_hWnd );
HFONT hOldFont = dcClient.SelectFont( m_fntListFont );
// set to column text width if zero-length
CSize sizeExtent;
if ( !dcClient.GetTextExtent( listColumn.m_strText.c_str(), -1, &sizeExtent ) )
return FALSE;
int nMaxWidth = sizeExtent.cx + ITEM_WIDTH_MARGIN;
CSize sizeIcon = 0;
if ( !m_ilItemImages.IsNull() )
m_ilItemImages.GetIconSize( sizeIcon );
// calculate maximum column width required
for ( int nItem = 0; nItem < pT->GetItemCount(); nItem++ )
{
if ( !dcClient.GetTextExtent( pT->GetItemText( nItem, listColumn.m_nIndex ), -1, &sizeExtent ) )
return FALSE;
if ( !m_ilItemImages.IsNull() && pT->GetItemImage( nItem, listColumn.m_nIndex ) != ITEM_IMAGE_NONE )
sizeExtent.cx += sizeIcon.cx;
nMaxWidth = max( nMaxWidth, (int)sizeExtent.cx + ITEM_WIDTH_MARGIN );
}
dcClient.SelectFont( hOldFont );
return SetColumnWidth( nColumn, nMaxWidth );
}
void ResizeColumn( BOOL bColumnScroll = FALSE )
{
HideTitleTip();
int nCurrentPos = GET_X_LPARAM( GetMessagePos() );
CRect rcClient;
GetClientRect( rcClient );
int nScrollLimit = GetTotalWidth() - rcClient.Width();
if ( bColumnScroll )
{
// have we finished scrolling list to accommodate new column size?
if ( !m_bColumnSizing || !m_bEnableHorizScroll || nCurrentPos - m_nStartScrollPos > 0 )
{
KillTimer( RESIZE_COLUMN_TIMER );
// reset resize start point
m_nStartPos = nCurrentPos;
m_bResizeTimer = FALSE;
}
else if ( nCurrentPos < m_nStartPos && GetScrollPos( SB_HORZ ) >= nScrollLimit )
{
// reset start column size
m_nStartSize = max( GetColumnWidth( m_nColumnSizing ) + ( nCurrentPos - m_nStartScrollPos ), 0 );
// resize column
SetColumnWidth( m_nColumnSizing, m_nStartSize );
}
}
else
{
int nColumnSize = max( m_nStartSize + ( nCurrentPos - m_nStartPos ), 0 );
// are we scrolled fully to the right and wanting to reduce the size of a column?
if ( m_bEnableHorizScroll && GetScrollPos( SB_HORZ ) >= nScrollLimit && nColumnSize < GetColumnWidth( m_nColumnSizing ) )
{
if ( !m_bResizeTimer )
{
// only start the scroll timer once
m_bResizeTimer = TRUE;
// set new start scroll position
m_nStartScrollPos = nCurrentPos;
// start column resize / scroll timer
SetTimer( RESIZE_COLUMN_TIMER, RESIZE_COLUMN_PERIOD );
}
}
else
{
// resizing is done in scroll timer (if started)
if ( !m_bResizeTimer )
SetColumnWidth( m_nColumnSizing, nColumnSize );
}
}
}
void DragColumn()
{
HideTitleTip();
CRect rcColumn;
if ( !GetColumnRect( m_nHighlightColumn, rcColumn ) )
return;
CRect rcHeaderItem( rcColumn );
rcHeaderItem.MoveToXY( 0, 0 );
CListColumn listColumn;
if ( !GetColumn( m_nHighlightColumn, listColumn ) )
return;
// store drag column
m_nDragColumn = m_nHighlightColumn;
CClientDC dcClient( m_hWnd );
CDC dcHeader;
dcHeader.CreateCompatibleDC( dcClient );
int nContextState = dcHeader.SaveDC();
// create drag header bitmap
CBitmapHandle bmpHeader;
bmpHeader.CreateCompatibleBitmap( dcClient, rcHeaderItem.Width(), rcHeaderItem.Height() );
dcHeader.SelectBitmap( bmpHeader );
dcHeader.SetBkColor( m_rgbHeaderBackground );
dcHeader.ExtTextOut( rcHeaderItem.left, rcHeaderItem.top, ETO_OPAQUE, rcHeaderItem, _T( "" ), 0, NULL );
dcHeader.Draw3dRect( rcHeaderItem, m_rgbHeaderBorder, m_rgbHeaderShadow );
CRect rcHeaderText( rcHeaderItem );
rcHeaderText.left += m_nHighlightColumn == 0 ? 4 : 3;
rcHeaderText.OffsetRect( 0, 1 );
// margin header text
rcHeaderText.DeflateRect( 4, 0, 5, 0 );
// has this header item an associated image?
if ( listColumn.m_nImage != ITEM_IMAGE_NONE )
{
CSize sizeIcon;
m_ilListItems.GetIconSize( sizeIcon );
CRect rcHeaderImage;
rcHeaderImage.left = listColumn.m_strText.empty() ? ( ( rcHeaderText.left + rcHeaderText.right ) / 2 ) - ( sizeIcon.cx / 2 ) - ( 0 ) : rcHeaderText.left;
rcHeaderImage.right = min( rcHeaderImage.left + sizeIcon.cx, rcHeaderItem.right );
rcHeaderImage.top = ( ( rcHeaderItem.top + rcHeaderItem.bottom ) / 2 ) - ( sizeIcon.cy / 2 );
rcHeaderImage.bottom = min( rcHeaderImage.top + sizeIcon.cy, rcHeaderItem.bottom );
m_ilListItems.DrawEx( listColumn.m_nImage, dcHeader, rcHeaderImage, CLR_DEFAULT, CLR_DEFAULT, ILD_TRANSPARENT );
// offset header text (for image)
rcHeaderText.left += sizeIcon.cx + 4;
}
dcHeader.SelectFont( m_fntListFont );
dcHeader.SetTextColor( m_rgbHeaderText );
dcHeader.SetBkMode( TRANSPARENT );
UINT nFormat = DT_SINGLELINE | DT_NOPREFIX | DT_VCENTER | DT_END_ELLIPSIS;
if ( listColumn.m_nFlags & ITEM_FLAGS_CENTRE )
nFormat |= DT_CENTER;
else if ( listColumn.m_nFlags & ITEM_FLAGS_RIGHT )
nFormat |= DT_RIGHT;
else
nFormat |= DT_LEFT;
// draw header text
if ( !listColumn.m_strText.empty() )
dcHeader.DrawText( listColumn.m_strText.c_str(), (int)listColumn.m_strText.length(), rcHeaderText, nFormat );
dcHeader.RestoreDC( nContextState );
SHDRAGIMAGE shDragImage;
ZeroMemory( &shDragImage, sizeof( SHDRAGIMAGE ) );
shDragImage.sizeDragImage.cx = rcHeaderItem.Width();
shDragImage.sizeDragImage.cy = rcHeaderItem.Height();
shDragImage.ptOffset.x = rcColumn.Width() / 2;
shDragImage.ptOffset.y = rcColumn.Height() / 2;
shDragImage.hbmpDragImage = bmpHeader;
shDragImage.crColorKey = m_rgbBackground;
// start header drag operation
m_oleDragDrop.DoDragDrop( &shDragImage, DROPEFFECT_MOVE );
// hide drop arrows after moving column
m_wndDropArrows.Hide();
if ( m_bButtonDown )
{
ReleaseCapture();
m_bButtonDown = FALSE;
m_bBeginSelect = FALSE;
m_ptDownPoint = 0;
m_ptSelectPoint = 0;
}
// finish moving a column
if ( m_nHighlightColumn != NULL_COLUMN )
{
m_nHighlightColumn = NULL_COLUMN;
InvalidateHeader();
}
m_nDragColumn = NULL_COLUMN;
}
BOOL DropColumn( CPoint point )
{
if ( !m_bShowHeader )
return FALSE;
m_nHotDivider = NULL_COLUMN;
m_nHotColumn = NULL_COLUMN;
UINT nHeaderFlags = HITTEST_FLAG_NONE;
// are we over the header?
if ( HitTestHeader( point, m_nHotColumn, nHeaderFlags ) )
{
CRect rcColumn;
if ( !GetColumnRect( m_nHotColumn, rcColumn ) )
return FALSE;
m_nHotDivider = point.x < ( ( rcColumn.left + rcColumn.right ) / 2 ) ? m_nHotColumn : m_nHotColumn + 1;
if ( m_nHotDivider == m_nDragColumn || m_nHotDivider == m_nDragColumn + 1 )
m_nHotDivider = NULL_COLUMN;
}
if ( m_nHotDivider != NULL_COLUMN )
{
CRect rcHeader;
GetClientRect( rcHeader );
rcHeader.bottom = m_nHeaderHeight;
CPoint ptDivider( 0, rcHeader.Height() / 2 );
CRect rcColumn;
int nColumnCount = GetColumnCount();
// set closest divider position
if ( GetColumnRect( m_nHotDivider < nColumnCount ? m_nHotDivider : nColumnCount - 1, rcColumn ) )
ptDivider.x = m_nHotDivider < nColumnCount ? rcColumn.left : rcColumn.right;
ClientToScreen( &ptDivider );
// track drop window
m_wndDropArrows.Show( ptDivider );
return TRUE;
}
m_wndDropArrows.Hide();
return FALSE;
}
BOOL SortColumn( int nColumn )
{
T* pT = static_cast<T*>(this);
if ( !m_bShowHeader || !m_bShowSort )
return FALSE;
int nSortIndex = GetColumnIndex( nColumn );
CWaitCursor curWait;
if ( nSortIndex != m_nSortColumn )
{
// sort by new column
m_bSortAscending = TRUE;
m_nSortColumn = nSortIndex;
pT->SortItems( m_nSortColumn, m_bSortAscending );
}
else
{
// toggle sort order if sorting same column
m_bSortAscending = !m_bSortAscending;
pT->ReverseItems();
}
return ResetSelected();
}
BOOL GetSortColumn( int& nColumn, BOOL& bAscending )
{
if ( !m_bShowHeader || !m_bShowSort || m_nSortColumn == NULL_COLUMN )
return FALSE;
nColumn = m_nSortColumn;
bAscending = m_bSortAscending;
return TRUE;
}
BOOL DragItem()
{
ATLASSERT( FALSE ); // must be implemented in a derived class
return FALSE;
}
BOOL GroupSelect( CPoint point )
{
HideTitleTip();
int nHorzScroll = GetScrollPos( SB_HORZ );
int nVertScroll = GetScrollPos( SB_VERT );
m_rcGroupSelect.left = min( m_ptSelectPoint.x, point.x + nHorzScroll );
m_rcGroupSelect.right = max( m_ptSelectPoint.x, point.x + nHorzScroll );
m_rcGroupSelect.top = min( m_ptSelectPoint.y, point.y + nVertScroll );
m_rcGroupSelect.bottom = max( m_ptSelectPoint.y, point.y + nVertScroll );
if ( m_rcGroupSelect.IsRectEmpty() )
return FALSE;
// select items in group
AutoSelect( point );
// start auto scroll timer
SetTimer( ITEM_AUTOSCROLL_TIMER, ITEM_SCROLL_PERIOD );
DWORD dwCurrentTick = GetTickCount();
// timer messages are a low priority, therefore we need to simulate the timer when moving the mouse
if ( ( dwCurrentTick - m_dwScrollTick ) > ITEM_SCROLL_PERIOD - 10 )
{
if ( AutoScroll( point ) )
m_dwScrollTick = dwCurrentTick;
}
// redraw list immediately
return RedrawWindow();
}
void AutoSelect( CPoint /*point*/ )
{
m_setSelectedItems.clear();
if ( m_rcGroupSelect.left < GetTotalWidth() )
{
int nHorzScroll = GetScrollPos( SB_HORZ );
int nVertScroll = GetScrollPos( SB_VERT );
CRect rcGroupSelect( m_rcGroupSelect );
rcGroupSelect.OffsetRect( -nHorzScroll, -nVertScroll );
int nTopItem = GetTopItem();
int nLastItem = nTopItem + ( ( rcGroupSelect.bottom - ( m_bShowHeader ? m_nHeaderHeight : 0 ) ) / m_nItemHeight );
nTopItem += ( ( rcGroupSelect.top - ( m_bShowHeader ? m_nHeaderHeight : 0 ) ) / m_nItemHeight ) - ( ( rcGroupSelect.top < 0 ) ? 1 : 0 );
for ( int nItem = nTopItem; nItem <= nLastItem; nItem++ )
{
if ( m_setSelectedItems.empty() )
m_nFirstSelected = nItem;
m_setSelectedItems.insert( nItem );
m_nFocusItem = nItem;
m_nFocusSubItem = NULL_SUBITEM;
}
}
if ( m_setSelectedItems.empty() )
m_nFirstSelected = NULL_ITEM;
}
BOOL AutoScroll( CPoint point )
{
CRect rcClient;
GetClientRect( rcClient );
rcClient.top = ( m_bShowHeader ? m_nHeaderHeight : 0 );
int nHorzScroll = GetScrollPos( SB_HORZ );
int nVertScroll = GetScrollPos( SB_VERT );
BOOL bAutoScroll = FALSE;
if ( point.y < rcClient.top )
{
SendMessage( WM_VSCROLL, MAKEWPARAM( SB_LINEUP, 0 ) );
int nAutoScroll = GetScrollPos( SB_VERT );
if ( nVertScroll != nAutoScroll )
{
m_rcGroupSelect.top = rcClient.top + nAutoScroll - 1;
m_rcGroupSelect.bottom = max( m_ptSelectPoint.y, point.y + nAutoScroll - 1 );
bAutoScroll = TRUE;
}
}
if ( point.y > rcClient.bottom )
{
SendMessage( WM_VSCROLL, MAKEWPARAM( SB_LINEDOWN, 0 ) );
int nAutoScroll = GetScrollPos( SB_VERT );
if ( nVertScroll != nAutoScroll )
{
m_rcGroupSelect.top = min( m_ptSelectPoint.y, point.y + nAutoScroll + 1 );
m_rcGroupSelect.bottom = rcClient.bottom + nAutoScroll + 1;
bAutoScroll = TRUE;
}
}
if ( point.x < rcClient.left )
{
SendMessage( WM_HSCROLL, MAKEWPARAM( SB_LINELEFT, 0 ) );
int nAutoScroll = GetScrollPos( SB_HORZ );
if ( nHorzScroll != nAutoScroll )
{
m_rcGroupSelect.left = rcClient.left + nAutoScroll - 1;
m_rcGroupSelect.right = max( m_ptSelectPoint.x, point.x + nAutoScroll - 1 );
bAutoScroll = TRUE;
}
}
if ( point.x > rcClient.right )
{
SendMessage( WM_HSCROLL, MAKEWPARAM( SB_LINERIGHT, 0 ) );
int nAutoScroll = GetScrollPos( SB_HORZ );
if ( nHorzScroll != nAutoScroll )
{
m_rcGroupSelect.left = min( m_ptSelectPoint.x, point.x + nAutoScroll + 1 );
m_rcGroupSelect.right = rcClient.right + nAutoScroll + 1;
bAutoScroll = TRUE;
}
}
// was scrolling performed?
return bAutoScroll;
}
BOOL BeginScroll( int nBeginItem, int nEndItem )
{
T* pT = static_cast<T*>(this);
// any scroll required?
if ( nBeginItem == nEndItem )
return FALSE;
// calculate scroll offset
m_nScrollOffset = abs( nEndItem - nBeginItem ) * m_nItemHeight;
m_nScrollUnit = min( max( m_nScrollOffset / m_nItemHeight, ITEM_SCROLL_UNIT_MIN ), ITEM_SCROLL_UNIT_MAX );
m_nScrollDelta = ( m_nScrollOffset - m_nScrollUnit ) / m_nScrollUnit;
m_bScrollDown = ( nBeginItem < nEndItem );
CClientDC dcClient( m_hWnd );
CDC dcScrollList;
dcScrollList.CreateCompatibleDC( dcClient );
int nContextState = dcScrollList.SaveDC();
CRect rcScrollList;
GetClientRect( rcScrollList );
rcScrollList.bottom = ( GetCountPerPage() + abs( nEndItem - nBeginItem ) ) * m_nItemHeight;
if ( !m_bmpScrollList.IsNull() )
m_bmpScrollList.DeleteObject();
m_bmpScrollList.CreateCompatibleBitmap( dcClient, rcScrollList.Width(), rcScrollList.Height() );
dcScrollList.SelectBitmap( m_bmpScrollList );
pT->DrawBkgnd( dcScrollList.m_hDC );
CRect rcItem;
rcItem.left = -GetScrollPos( SB_HORZ );
rcItem.right = GetTotalWidth();
rcItem.top = 0;
rcItem.bottom = rcItem.top;
// draw all visible items into bitmap
for ( int nItem = min( nBeginItem, nEndItem ); nItem < pT->GetItemCount(); rcItem.top = rcItem.bottom, nItem++ )
{
rcItem.bottom = rcItem.top + m_nItemHeight;
if ( rcItem.top > rcScrollList.bottom )
break;
// may be implemented in a derived class
pT->DrawItem( dcScrollList.m_hDC, nItem, rcItem );
}
dcScrollList.RestoreDC( nContextState );
ScrollList();
// start scrolling timer
SetTimer( ITEM_SCROLL_TIMER, ITEM_SCROLL_PERIOD );
return TRUE;
}
BOOL EndScroll()
{
KillTimer( ITEM_SCROLL_TIMER );
if ( !m_bmpScrollList.IsNull() )
m_bmpScrollList.DeleteObject();
return Invalidate();
}
BOOL ScrollList()
{
if ( m_nScrollOffset <= m_nScrollUnit )
m_nScrollOffset--;
else
{
m_nScrollOffset -= m_nScrollDelta;
if ( m_nScrollOffset < m_nScrollDelta )
m_nScrollOffset = m_nScrollUnit;
}
if ( m_bmpScrollList.IsNull() || m_nScrollOffset < 0 )
return FALSE;
CClientDC dcClient( m_hWnd );
CDC dcScrollList;
dcScrollList.CreateCompatibleDC( dcClient );
CRect rcClient;
GetClientRect( rcClient );
rcClient.top = ( m_bShowHeader ? m_nHeaderHeight : 0 );
HBITMAP hOldBitmap = dcScrollList.SelectBitmap( m_bmpScrollList );
CSize sizScrollBitmap;
m_bmpScrollList.GetSize( sizScrollBitmap );
// draw scrolled list
dcClient.BitBlt( 0, rcClient.top, rcClient.Width(), rcClient.Height(), dcScrollList, 0, m_bScrollDown ? ( sizScrollBitmap.cy - ( GetCountPerPage() * m_nItemHeight ) - m_nScrollOffset ) : m_nScrollOffset, SRCCOPY );
dcScrollList.SelectBitmap( hOldBitmap );
return TRUE;
}
BOOL EditItem( int nItem, int nSubItem = NULL_SUBITEM )
{
T* pT = static_cast<T*>(this);
if ( !EnsureItemVisible( nItem, nSubItem ) )
return FALSE;
if ( GetFocus() != m_hWnd )
return FALSE;
CRect rcSubItem;
if ( !GetItemRect( nItem, nSubItem, rcSubItem ) )
return FALSE;
int nIndex = GetColumnIndex( nSubItem );
if ( pT->GetItemFlags( nItem, nIndex ) & ITEM_FLAGS_READ_ONLY )
return TRUE;
switch ( pT->GetItemFormat( nItem, nIndex ) )
{
case ITEM_FORMAT_EDIT: m_bEditItem = TRUE;
if ( !RedrawWindow() )
return FALSE;
if ( !m_wndItemEdit.Create( m_hWnd, nItem, nSubItem, rcSubItem, pT->GetItemFlags( nItem, nIndex ), pT->GetItemText( nItem, nIndex ), pT->GetItemMaxEditLen(nItem, nIndex ) ) )
return FALSE;
m_wndItemEdit.SetFont(GetItemFont(nItem, nIndex));
break;
case ITEM_FORMAT_DATETIME: {
m_bEditItem = TRUE;
if ( !RedrawWindow() )
return FALSE;
SYSTEMTIME stItemDate;
GetItemDate( nItem, nIndex, stItemDate );
if ( !m_wndItemDate.Create( m_hWnd, nItem, nSubItem, rcSubItem, pT->GetItemFlags( nItem, nIndex ), stItemDate ) )
return FALSE;
}
break;
case ITEM_FORMAT_COMBO: {
m_bEditItem = TRUE;
if ( !RedrawWindow() )
return FALSE;
CListArray < stdstr > aComboList;
if ( !pT->GetItemComboList( nItem, nIndex, aComboList ) )
return FALSE;
if ( !m_wndItemCombo.Create( m_hWnd, nItem, nSubItem, rcSubItem, pT->GetItemFlags( nItem, nIndex ), pT->GetItemText( nItem, nIndex ), aComboList ) )
return FALSE;
}
break;
}
return TRUE;
}
stdstr FormatDate( SYSTEMTIME& stFormatDate )
{
if ( stFormatDate.wYear == 0 )
return _T( "" );
// format date to local format
TCHAR szDateFormat[ DATE_STRING ];
return GetDateFormat( LOCALE_USER_DEFAULT, DATE_SHORTDATE, &stFormatDate, NULL, szDateFormat, DATE_STRING ) == 0 ? _T( "" ) : szDateFormat;
}
stdstr FormatTime( SYSTEMTIME& stFormatDate )
{
SYSTEMTIME stFormatTime = stFormatDate;
stFormatTime.wYear = 0;
stFormatTime.wMonth = 0;
stFormatTime.wDay = 0;
// format time to local format
TCHAR szTimeFormat[ DATE_STRING ];
return GetTimeFormat( LOCALE_USER_DEFAULT, 0, &stFormatTime, NULL, szTimeFormat, DATE_STRING ) == 0 ? _T( "" ) : szTimeFormat;
}
void NotifyParent( int nItem, int nSubItem, int nMessage )
{
T* pT = static_cast<T*>(this);
CListNotify listNotify;
listNotify.m_hdrNotify.hwndFrom = pT->m_hWnd;
listNotify.m_hdrNotify.idFrom = pT->GetDlgCtrlID();
listNotify.m_hdrNotify.code = nMessage;
listNotify.m_nItem = nItem;
listNotify.m_nSubItem = GetColumnIndex( nSubItem );
listNotify.m_nExitChar = 0;
listNotify.m_lpszItemText = NULL;
listNotify.m_lpItemDate = NULL;
// forward notification to parent
FORWARD_WM_NOTIFY( pT->GetParent(), listNotify.m_hdrNotify.idFrom, &listNotify.m_hdrNotify, ::SendMessage );
}
BOOL ShowTitleTip( CPoint point, int nItem, int nSubItem )
{
T* pT = static_cast<T*>(this);
// do not show titletip if editing
if ( m_bEditItem )
return FALSE;
// is titletip already shown for this item?
if ( nItem == m_nTitleTipItem && nSubItem == m_nTitleTipSubItem )
return FALSE;
CRect rcSubItem;
if ( !GetItemRect( nItem, nSubItem, rcSubItem ) )
{
HideTitleTip();
return FALSE;
}
int nIndex = GetColumnIndex( nSubItem );
CRect rcItemText( rcSubItem );
// margin item text
// rcItemText.left += nSubItem == 0 ? 4 : 3;
// rcItemText.DeflateRect( 4, 0 );
// offset item text (for image)
if ( !m_ilItemImages.IsNull() && pT->GetItemImage( nItem, nIndex ) != ITEM_IMAGE_NONE )
{
CSize sizeIcon;
m_ilItemImages.GetIconSize( sizeIcon );
rcItemText.left += sizeIcon.cx + 4;
}
// is current cursor position over item text (not over an icon)?
if ( !rcItemText.PtInRect( point ) )
return FALSE;
stdstr strItemText;
switch ( pT->GetItemFormat( nItem, nIndex ) )
{
case ITEM_FORMAT_CHECKBOX:
case ITEM_FORMAT_CHECKBOX_3STATE:
case ITEM_FORMAT_PROGRESS: break; // no titletip for checkboxes or progress
case ITEM_FORMAT_DATETIME: {
SYSTEMTIME stItemDate;
if ( !GetItemDate( nItem, nIndex, stItemDate ) )
break;
UINT nItemFlags = pT->GetItemFlags( nItem, nIndex );
if ( nItemFlags & ITEM_FLAGS_DATE_ONLY )
strItemText = FormatDate( stItemDate );
else if ( nItemFlags & ITEM_FLAGS_TIME_ONLY )
strItemText = FormatTime( stItemDate );
else
strItemText = FormatDate( stItemDate ) + _T( " " ) + FormatTime( stItemDate );
}
break;
default: strItemText = pT->GetItemText( nItem, nIndex );
break;
}
if ( strItemText.empty() )
{
HideTitleTip();
return FALSE;
}
ClientToScreen( rcItemText );
if ( !m_wndTitleTip.Show( rcItemText, strItemText.c_str(), pT->GetItemToolTip( nItem, nIndex ).c_str() ) )
{
HideTitleTip();
return FALSE;
}
m_nTitleTipItem = nItem;
m_nTitleTipSubItem = nSubItem;
return TRUE;
}
BOOL HideTitleTip( BOOL bResetItem = TRUE )
{
if ( bResetItem )
{
m_nTitleTipItem = NULL_ITEM;
m_nTitleTipSubItem = NULL_SUBITEM;
}
return m_wndTitleTip.Hide();
}
BEGIN_MSG_MAP_EX(CListImpl)
MSG_WM_CREATE(OnCreate)
MSG_WM_DESTROY(OnDestroy)
MSG_WM_SETFOCUS(OnSetFocus)
MSG_WM_KILLFOCUS(OnKillFocus)
MSG_WM_GETDLGCODE(OnGetDlgCode)
MSG_WM_SIZE(OnSize)
MSG_WM_HSCROLL(OnHScroll)
MSG_WM_VSCROLL(OnVScroll)
MSG_WM_CANCELMODE(OnCancelMode)
MESSAGE_RANGE_HANDLER_EX(WM_MOUSEFIRST,WM_MOUSELAST,OnMouseRange)
MSG_WM_LBUTTONDOWN(OnLButtonDown)
MSG_WM_LBUTTONUP(OnLButtonUp)
MSG_WM_LBUTTONDBLCLK(OnLButtonDblClk)
MSG_WM_RBUTTONDOWN(OnRButtonDown)
MSG_WM_RBUTTONUP(OnRButtonUp)
MSG_WM_MOUSEMOVE(OnMouseMove)
#if(_WIN32_WINNT >= 0x0400)
MSG_WM_MOUSELEAVE(OnMouseLeave)
MSG_WM_MOUSEWHEEL(OnMouseWheel)
#endif
MSG_WM_TIMER(OnTimer)
MSG_WM_KEYDOWN(OnKeyDown)
MSG_WM_SYSKEYDOWN(OnSysKeyDown)
MSG_WM_SETTINGCHANGE(OnSettingsChange)
MSG_WM_SYSCOLORCHANGE(OnSettingsChange)
MSG_WM_FONTCHANGE(OnSettingsChange)
//MSG_WM_THEMECHANGED(OnSettingsChange)
NOTIFY_CODE_HANDLER_EX(LCN_ENDEDIT,OnEndEdit)
MESSAGE_HANDLER(WM_CTLCOLORLISTBOX, OnCtlColorListBox)
MESSAGE_HANDLER(WM_CTLCOLOREDIT, OnCtlColorListBox)
CHAIN_MSG_MAP(CDoubleBufferImpl< CListImpl >)
REFLECT_NOTIFICATIONS()
END_MSG_MAP()
int OnCreate( LPCREATESTRUCT /*lpCreateStruct*/ )
{
T* pT = static_cast<T*>(this);
return pT->Initialise() ? 0 : -1;
}
void OnDestroy()
{
m_oleDragDrop.Revoke();
if ( m_wndDropArrows.IsWindow() )
m_wndDropArrows.DestroyWindow();
if ( m_wndTitleTip.IsWindow() )
m_wndTitleTip.DestroyWindow();
if ( m_wndItemEdit.IsWindow() )
m_wndItemEdit.DestroyWindow();
if ( m_wndItemCombo.IsWindow() )
m_wndItemCombo.DestroyWindow();
if ( m_wndItemDate.IsWindow() )
m_wndItemDate.DestroyWindow();
if ( m_ttToolTip.IsWindow() )
m_ttToolTip.DestroyWindow();
}
void OnSetFocus( HWND /*hOldWnd*/ )
{
Invalidate();
}
void OnKillFocus( HWND /*hNewWnd*/ )
{
Invalidate();
}
UINT OnGetDlgCode( LPMSG /*lpMessage*/ )
{
return DLGC_WANTARROWS | DLGC_WANTTAB | DLGC_WANTCHARS;
}
void OnSize( UINT /*nType*/, CSize /*size*/ )
{
// stop any pending scroll
EndScroll();
// end any pending edit
if ( m_bEditItem )
SetFocus();
ResetScrollBars( SB_BOTH, -1, FALSE );
Invalidate();
}
void OnHScroll( int nSBCode, short /*nPos*/, HWND /*hScrollBar*/ )
{
// stop any pending scroll
EndScroll();
// end any pending edit
if ( m_bEditItem )
SetFocus();
HideTitleTip();
CRect rcClient;
GetClientRect( rcClient );
int nScrollPos = GetScrollPos( SB_HORZ );
switch ( nSBCode )
{
case SB_LEFT: nScrollPos = 0;
break;
case SB_LINELEFT: nScrollPos = max( nScrollPos - ITEM_SCROLL_OFFSET, 0 );
break;
case SB_PAGELEFT: nScrollPos = max( nScrollPos - rcClient.Width(), 0 );
break;
case SB_RIGHT: nScrollPos = rcClient.Width();
break;
case SB_LINERIGHT: nScrollPos = min( nScrollPos + ITEM_SCROLL_OFFSET, GetTotalWidth() );
break;
case SB_PAGERIGHT: nScrollPos = min( nScrollPos + rcClient.Width(), GetTotalWidth() );
break;
case SB_THUMBPOSITION:
case SB_THUMBTRACK: {
SCROLLINFO infoScroll;
ZeroMemory( &infoScroll, sizeof( SCROLLINFO ) );
infoScroll.cbSize = sizeof( SCROLLINFO );
infoScroll.fMask = SIF_TRACKPOS;
// get 32-bit scroll position
if ( !GetScrollInfo( SB_HORZ, &infoScroll ) )
return;
// has scroll position changed?
if ( infoScroll.nTrackPos == nScrollPos )
return;
nScrollPos = infoScroll.nTrackPos;
}
break;
default: return;
}
ResetScrollBars( SB_HORZ, nScrollPos, FALSE );
Invalidate();
}
void OnVScroll( int nSBCode, short /*nPos*/, HWND /*hScrollBar*/ )
{
T* pT = static_cast<T*>(this);
// end any pending edit
if ( m_bEditItem )
SetFocus();
HideTitleTip();
CRect rcClient;
GetClientRect( rcClient );
rcClient.top = ( m_bShowHeader ? m_nHeaderHeight : 0 );
int nScrollPos = GetScrollPos( SB_VERT );
BOOL bScrollList = m_bSmoothScroll;
switch ( nSBCode )
{
case SB_TOP: nScrollPos = 0;
bScrollList = FALSE;
break;
case SB_LINEUP: nScrollPos = max( nScrollPos - m_nItemHeight, 0 );
break;
case SB_PAGEUP: nScrollPos = max( nScrollPos - rcClient.Height(), 0 );
break;
case SB_BOTTOM: nScrollPos = pT->GetItemCount() * m_nItemHeight;
bScrollList = FALSE;
break;
case SB_LINEDOWN: nScrollPos += m_nItemHeight;
break;
case SB_PAGEDOWN: nScrollPos += rcClient.Height();
break;
case SB_THUMBTRACK:
case SB_THUMBPOSITION: {
SCROLLINFO infoScroll;
ZeroMemory( &infoScroll, sizeof( SCROLLINFO ) );
infoScroll.cbSize = sizeof( SCROLLINFO );
infoScroll.fMask = SIF_TRACKPOS;
// get 32-bit scroll position
if ( !GetScrollInfo( SB_VERT, &infoScroll ) )
return;
// has scroll position changed?
if ( infoScroll.nTrackPos == nScrollPos )
return;
nScrollPos = infoScroll.nTrackPos;
bScrollList = FALSE;
}
break;
case SB_ENDSCROLL: m_bScrolling = FALSE;
default: return;
}
// store original top item before scrolling
int nTopItem = GetTopItem();
ResetScrollBars( SB_VERT, nScrollPos, FALSE );
if ( bScrollList && !m_bScrolling )
m_bScrolling = BeginScroll( nTopItem, GetTopItem() );
else
EndScroll();
}
void OnCancelMode()
{
if ( m_bButtonDown )
ReleaseCapture();
HideTitleTip();
m_wndDropArrows.Hide();
m_nDragColumn = NULL_COLUMN;
m_nHighlightColumn = NULL_COLUMN;
}
LRESULT OnMouseRange( UINT nMessage, WPARAM wParam, LPARAM lParam )
{
if ( m_ttToolTip.IsWindow() )
{
MSG msgRelay = { m_hWnd, nMessage, wParam, lParam };
m_ttToolTip.RelayEvent( &msgRelay );
}
SetMsgHandled( FALSE );
return 0;
}
void OnLButtonDown( UINT nFlags, CPoint point )
{
T* pT = static_cast<T*>(this);
// We have a bug here with setcapture() and the tooltip notiying the parent with mouse messages.
// Hard to explain, but what I think happens is that this click is sent by the tooltip, which then
// releases capture, so it gets no more mouse events, thus not receiving the actual double click
// on the tool tip, and what results is two single clicks for this parent control.
// Not sure how to fix - Rowan
// Explorer doesn't actually hide the tip when clicked on, so we can remove this code and it shouldn't really matter
//HideTitleTip(FALSE);
m_bButtonDown = TRUE;
m_ptDownPoint = point;
m_ptSelectPoint = CPoint( point.x + GetScrollPos( SB_HORZ ), point.y + GetScrollPos( SB_VERT ) );
// stop any pending scroll
EndScroll();
SetFocus();
// capture all mouse input
SetCapture();
int nColumn = NULL_COLUMN;
UINT nHeaderFlags = HITTEST_FLAG_NONE;
// are we over the header?
if ( HitTestHeader( point, nColumn, nHeaderFlags ) )
{
CListColumn listColumn;
if ( !GetColumn( nColumn, listColumn ) )
return;
if ( !listColumn.m_bFixed && ( nHeaderFlags & HITTEST_FLAG_HEADER_DIVIDER ) )
{
SetCursor( m_curDivider );
// begin column resizing
m_bColumnSizing = TRUE;
m_nColumnSizing = nColumn;
m_nStartSize = listColumn.m_nWidth;
m_nStartPos = GET_X_LPARAM( GetMessagePos() );
}
else if (m_bSortEnabled) // Added by Rowan 05/12/2006
{
m_nHighlightColumn = nColumn;
InvalidateHeader();
}
return;
}
int nItem = NULL_ITEM;
int nSubItem = NULL_SUBITEM;
if ( !HitTest( point, nItem, nSubItem ) )
{
m_nFirstSelected = NULL_ITEM;
m_bBeginSelect = TRUE;
}
else
{
// do not begin group select from first columns
if ( !( nFlags & MK_SHIFT ) && !( nFlags & MK_CONTROL ) && nSubItem != 0 )
{
m_bBeginSelect = TRUE;
m_nFirstSelected = nItem;
}
// only select item if not already selected
if ( ( nFlags & MK_SHIFT ) || ( nFlags & MK_CONTROL ) || !IsSelected( nItem ) || m_setSelectedItems.size() <= 1 )
SelectItem( nItem, nSubItem, nFlags );
int nIndex = GetColumnIndex( nSubItem );
if ( !( pT->GetItemFlags( nItem, nIndex ) & ITEM_FLAGS_READ_ONLY ) )
{
switch ( pT->GetItemFormat( nItem, nIndex ) )
{
case ITEM_FORMAT_CHECKBOX: m_bBeginSelect = FALSE;
pT->SetItemText( nItem, nIndex, _ttoi( pT->GetItemText( nItem, nIndex ) ) > 0 ? _T( "0" ) : _T( "1" ) );
NotifyParent( nItem, nSubItem, LCN_MODIFIED );
InvalidateItem( nItem );
break;
case ITEM_FORMAT_CHECKBOX_3STATE: {
m_bBeginSelect = FALSE;
int nCheckImage = _ttoi( pT->GetItemText( nItem, nIndex ) );
if ( nCheckImage < 0 )
pT->SetItemText( nItem, nIndex, _T( "0" ) );
else if ( nCheckImage > 0 )
pT->SetItemText( nItem, nIndex, _T( "-1" ) );
else
pT->SetItemText( nItem, nIndex, _T( "1" ) );
NotifyParent( nItem, nSubItem, LCN_MODIFIED );
InvalidateItem( nItem );
}
break;
case ITEM_FORMAT_HYPERLINK: m_bBeginSelect = FALSE;
SetCursor( m_curHyperLink );
NotifyParent( nItem, nSubItem, LCN_HYPERLINK );
break;
}
if (( pT->GetItemFlags( nItem, nIndex ) & ITEM_FLAGS_HIT_NOTIFY ) != 0)
{
NotifyParent( nItem, nSubItem, LCN_HITTEST );
}
}
}
}
void OnLButtonUp( UINT nFlags, CPoint point )
{
if ( m_bButtonDown )
ReleaseCapture();
// finish resizing or selecting a column
if ( m_bColumnSizing || m_nHighlightColumn != NULL_COLUMN )
{
// are we changing the sort order?
if ( !m_bColumnSizing && m_nHighlightColumn != NULL_COLUMN && m_bSortEnabled) // Changed by Rowan 05/12/2006
//if ( !m_bColumnSizing && m_nHighlightColumn != NULL_COLUMN)
SortColumn( m_nHighlightColumn );
m_bColumnSizing = FALSE;
m_nColumnSizing = NULL_COLUMN;
m_nHighlightColumn = NULL_COLUMN;
m_nStartSize = 0;
m_nStartPos = 0;
InvalidateHeader();
}
m_bBeginSelect = FALSE;
m_bButtonDown = FALSE;
m_ptDownPoint = 0;
m_ptSelectPoint = 0;
// have we finished a group select?
if ( m_bGroupSelect )
{
m_bGroupSelect = FALSE;
Invalidate();
}
else
{
int nItem = NULL_ITEM;
int nSubItem = NULL_SUBITEM;
// de-select item if current item is selected
if ( HitTest( point, nItem, nSubItem ) && IsSelected( nItem ) && m_setSelectedItems.size() > 1 && !( nFlags & MK_SHIFT ) && !( nFlags & MK_CONTROL ) )
SelectItem( nItem, nSubItem, nFlags );
// notify parent of left-click item
NotifyParent( nItem, nSubItem, LCN_LEFTCLICK );
}
}
void OnLButtonDblClk( UINT /*nFlags*/, CPoint point )
{
HideTitleTip( FALSE );
// handle double-clicks (for drawing)
SendMessage( WM_LBUTTONDOWN, 0, MAKELPARAM( point.x, point.y ) );
int nColumn = NULL_COLUMN;
UINT nHeaderFlags = HITTEST_FLAG_NONE;
// resize column if double-click on a divider
if ( HitTestHeader( point, nColumn, nHeaderFlags ) && ( nHeaderFlags & HITTEST_FLAG_HEADER_DIVIDER ) )
AutoSizeColumn( nColumn );
int nItem = NULL_ITEM;
int nSubItem = NULL_SUBITEM;
HitTest( point, nItem, nSubItem );
//WriteTraceF(TraceInfo, "List Ctrl Double Click, Item: %d", nItem);
// notify parent of double-clicked item
NotifyParent( nItem, nSubItem, LCN_DBLCLICK );
}
void OnRButtonDown( UINT nFlags, CPoint point )
{
// stop any pending scroll
EndScroll();
SetFocus();
HideTitleTip( FALSE );
int nItem = NULL_ITEM;
int nSubItem = NULL_SUBITEM;
// only select item if not already selected (de-select in OnLButtonUp)
if ( HitTest( point, nItem, nSubItem ) && !IsSelected( nItem ) )
SelectItem( nItem, nSubItem, nFlags );
}
void OnRButtonUp( UINT /*nFlags*/, CPoint point )
{
int nItem = NULL_ITEM;
int nSubItem = NULL_SUBITEM;
if ( !HitTest( point, nItem, nSubItem ) )
ResetSelected();
// notify parent of right-click item
NotifyParent( nItem, nSubItem, LCN_RIGHTCLICK );
}
void OnMouseMove( UINT nFlags, CPoint point )
{
T* pT = static_cast<T*>(this);
if ( !( nFlags & MK_LBUTTON ) )
{
if ( m_bButtonDown )
ReleaseCapture();
m_bButtonDown = FALSE;
}
if ( !m_bMouseOver )
{
m_bMouseOver = TRUE;
TRACKMOUSEEVENT trkMouse;
trkMouse.cbSize = sizeof( TRACKMOUSEEVENT );
trkMouse.dwFlags = TME_LEAVE;
trkMouse.hwndTrack = m_hWnd;
// notify when the mouse leaves button
_TrackMouseEvent( &trkMouse );
}
if ( m_bButtonDown )
{
// are we resizing a column?
if ( m_bColumnSizing )
{
ResizeColumn();
return;
}
// are we beginning to drag a column?
if ( m_nHighlightColumn != NULL_COLUMN && ( point.x < m_ptDownPoint.x - DRAG_HEADER_OFFSET || point.x > m_ptDownPoint.x + DRAG_HEADER_OFFSET || point.y < m_ptDownPoint.y - DRAG_HEADER_OFFSET || point.y > m_ptDownPoint.y + DRAG_HEADER_OFFSET ) )
{
DragColumn();
return;
}
// are we beginning a group select or dragging an item?
if ( point.x < m_ptDownPoint.x - DRAG_ITEM_OFFSET || point.x > m_ptDownPoint.x + DRAG_ITEM_OFFSET || point.y < m_ptDownPoint.y - DRAG_ITEM_OFFSET || point.y > m_ptDownPoint.y + DRAG_ITEM_OFFSET )
{
if ( m_bBeginSelect || !m_bDragDrop )
m_bGroupSelect = ( !m_bSingleSelect && !m_bEditItem );
else
{
int nItem = NULL_ITEM;
int nSubItem = NULL_SUBITEM;
if ( HitTest( point, nItem, nSubItem ) )
{
// select the drag item (if not already selected)
if ( !IsSelected( nItem ) )
SelectItem( nItem, nSubItem, nFlags );
// begin drag item operation
pT->DragItem();
}
}
}
if ( m_bGroupSelect )
{
GroupSelect( point );
return;
}
}
else
{
int nColumn = NULL_COLUMN;
UINT nHeaderFlags = HITTEST_FLAG_NONE;
// are we over the header?
BOOL bHitTestHeader = HitTestHeader( point, nColumn, nHeaderFlags );
if ( bHitTestHeader )
{
HideTitleTip();
CListColumn listColumn;
if ( GetColumn( nColumn, listColumn ) && !listColumn.m_bFixed && ( nHeaderFlags & HITTEST_FLAG_HEADER_DIVIDER ) )
SetCursor( m_curDivider );
else
{
// get tooltip for this item
stdstr strToolTip = pT->GetHeaderToolTip(nColumn);
if(!strToolTip.empty())
{
CRect rcColumn;
if(!GetColumnRect(nColumn, rcColumn))
return;
rcColumn.bottom = m_nHeaderHeight;
m_ttToolTip.Activate( TRUE );
m_ttToolTip.AddTool( m_hWnd, (LPCTSTR)strToolTip.c_str(), rcColumn,TOOLTIP_TOOL_ID);
}
}
return;
}
int nItem = NULL_ITEM;
int nSubItem = NULL_SUBITEM;
if ( !HitTest( point, nItem, nSubItem ) )
{
if ( m_nHotItem != NULL_ITEM && m_nHotSubItem != NULL_SUBITEM )
{
// redraw old hot item
int nIndex = GetColumnIndex( m_nHotSubItem );
if ( pT->GetItemFormat( m_nHotItem, nIndex ) == ITEM_FORMAT_HYPERLINK && !( pT->GetItemFlags( m_nHotItem, nIndex ) & ITEM_FLAGS_READ_ONLY ) )
InvalidateItem( m_nHotItem, m_nHotSubItem );
}
m_ttToolTip.Activate( FALSE );
m_ttToolTip.DelTool( m_hWnd, TOOLTIP_TOOL_ID );
m_nHotItem = NULL_ITEM;
m_nHotSubItem = NULL_SUBITEM;
HideTitleTip();
}
else
{
// has the hot item changed?
if ( nItem != m_nHotItem || nSubItem != m_nHotSubItem )
{
// redraw old hot item
int nIndex = GetColumnIndex( m_nHotSubItem );
if ( pT->GetItemFormat( m_nHotItem, nIndex ) == ITEM_FORMAT_HYPERLINK && !( pT->GetItemFlags( m_nHotItem, nIndex ) & ITEM_FLAGS_READ_ONLY ) )
InvalidateItem( m_nHotItem, m_nHotSubItem );
m_nHotItem = nItem;
m_nHotSubItem = nSubItem;
}
int nIndex = GetColumnIndex( m_nHotSubItem );
UINT nItemFormat = pT->GetItemFormat( m_nHotItem, nIndex );
UINT nItemFlags = pT->GetItemFlags( m_nHotItem, nIndex );
// draw new hot hyperlink item
if ( nItemFormat == ITEM_FORMAT_HYPERLINK && !( nItemFlags & ITEM_FLAGS_READ_ONLY ) )
{
InvalidateItem( m_nHotItem, m_nHotSubItem );
SetCursor( m_curHyperLink );
}
// get tooltip for this item
stdstr strToolTip = pT->GetItemToolTip( m_nHotItem, nIndex );
CRect rcSubItem;
if ( !strToolTip.empty() && GetItemRect( m_nHotItem, rcSubItem ) )
{
m_ttToolTip.Activate( TRUE );
m_ttToolTip.AddTool( m_hWnd, (LPCTSTR)strToolTip.substr(0, SHRT_MAX ).c_str(), rcSubItem, TOOLTIP_TOOL_ID );
}
else
{
m_ttToolTip.Activate( FALSE );
m_ttToolTip.DelTool( m_hWnd, TOOLTIP_TOOL_ID );
}
// show titletips for this item
ShowTitleTip( point, m_nHotItem, m_nHotSubItem );
}
}
}
void OnMouseLeave()
{
m_bMouseOver = FALSE;
if ( m_nHotColumn != NULL_COLUMN )
{
m_nHotColumn = NULL_COLUMN;
InvalidateHeader();
}
if ( m_nHotItem != NULL_ITEM || m_nHotSubItem != NULL_SUBITEM )
{
m_nHotItem = NULL_ITEM;
m_nHotSubItem = NULL_SUBITEM;
Invalidate();
}
}
BOOL OnMouseWheel( UINT /*nFlags*/, short nDelta, CPoint /*point*/ )
{
HideTitleTip();
// end any pending edit
if ( m_bEditItem )
SetFocus();
int nRowsScrolled = m_nMouseWheelScroll * nDelta / 120;
int nScrollPos = GetScrollPos( SB_VERT );
if ( nRowsScrolled > 0 )
nScrollPos = max( nScrollPos - ( nRowsScrolled * m_nItemHeight ), 0 );
else
nScrollPos += ( -nRowsScrolled * m_nItemHeight );
ResetScrollBars( SB_VERT, nScrollPos, FALSE );
Invalidate();
return TRUE;
}
void OnTimer( UINT nIDEvent )
{
switch ( nIDEvent )
{
case RESIZE_COLUMN_TIMER: ResizeColumn( TRUE );
break;
case ITEM_VISIBLE_TIMER: {
KillTimer( ITEM_VISIBLE_TIMER );
int nFocusItem = NULL_ITEM;
int nFocusSubItem = NULL_SUBITEM;
// get current focus item
if ( !GetFocusItem( nFocusItem, nFocusSubItem ) )
break;
// make sure current focus item is visible before editing
if ( !EditItem( nFocusItem, nFocusSubItem ) )
break;
}
break;
case ITEM_AUTOSCROLL_TIMER: if ( !m_bGroupSelect )
KillTimer( ITEM_AUTOSCROLL_TIMER );
else
{
DWORD dwPoint = GetMessagePos();
CPoint ptMouse( GET_X_LPARAM( dwPoint ), GET_Y_LPARAM( dwPoint ) );
ScreenToClient( &ptMouse );
// automatically scroll when group selecting
AutoScroll( ptMouse );
AutoSelect( ptMouse );
}
break;
case ITEM_SCROLL_TIMER: if ( !ScrollList() )
EndScroll();
break;
}
}
void OnKeyDown( TCHAR nChar, UINT /*nRepCnt*/, UINT /*nFlags*/ )
{
T* pT = static_cast<T*>(this);
// stop any pending scroll
EndScroll();
BOOL bCtrlKey = ( ( GetKeyState( VK_CONTROL ) & 0x8000 ) != 0 );
BOOL bShiftKey = ( ( GetKeyState( VK_SHIFT ) & 0x8000 ) != 0 );
CRect rcClient;
GetClientRect( rcClient );
int nFocusItem = NULL_ITEM;
int nFocusSubItem = NULL_SUBITEM;
GetFocusItem( nFocusItem, nFocusSubItem );
switch ( nChar )
{
case VK_DOWN: SetFocusItem( min( nFocusItem + 1, pT->GetItemCount() - 1 ), nFocusSubItem );
break;
case VK_UP: SetFocusItem( max( nFocusItem - 1, 0 ), nFocusSubItem );
break;
case VK_NEXT: SetFocusItem( min( nFocusItem + GetCountPerPage( FALSE ) - 1, pT->GetItemCount() - 1 ), nFocusSubItem );
break;
case VK_PRIOR: SetFocusItem( max( nFocusItem - GetCountPerPage( FALSE ) + 1, 0 ), nFocusSubItem );
break;
case VK_HOME: SetFocusItem( 0, nFocusSubItem );
break;
case VK_END: SetFocusItem( pT->GetItemCount() - 1, nFocusSubItem );
break;
case VK_LEFT: if ( m_bFocusSubItem )
SetFocusItem( nFocusItem, max( nFocusSubItem - 1, 0 ) );
else
SetScrollPos( SB_HORZ, max( GetScrollPos( SB_HORZ ) - ( bCtrlKey ? ITEM_SCROLL_OFFSET * 10 : ITEM_SCROLL_OFFSET ), 0 ) );
break;
case VK_RIGHT: if ( m_bFocusSubItem )
SetFocusItem( nFocusItem, min( nFocusSubItem + 1, GetColumnCount() - 1 ) );
else
SetScrollPos( SB_HORZ, min( GetScrollPos( SB_HORZ ) + ( bCtrlKey ? ITEM_SCROLL_OFFSET * 10 : ITEM_SCROLL_OFFSET ), rcClient.Width() ) );
break;
case VK_TAB: if ( !bCtrlKey && m_bFocusSubItem )
SetFocusItem( nFocusItem, bShiftKey ? max( nFocusSubItem - 1, 0 ) : min( nFocusSubItem + 1, GetColumnCount() - 1 ) );
break;
default: if ( nChar == VK_SPACE )
{
int nIndex = GetColumnIndex( nFocusSubItem );
if ( !( pT->GetItemFlags( nFocusItem, nIndex ) & ITEM_FLAGS_READ_ONLY ) )
{
switch ( pT->GetItemFormat( nFocusItem, nIndex ) )
{
case ITEM_FORMAT_CHECKBOX: pT->SetItemText( nFocusItem, nIndex, _ttoi( pT->GetItemText( nFocusItem, nIndex ) ) > 0 ? _T( "0" ) : _T( "1" ) );
NotifyParent( nFocusItem, nFocusSubItem, LCN_MODIFIED );
InvalidateItem( nFocusItem );
return;
case ITEM_FORMAT_CHECKBOX_3STATE: {
int nCheckImage = _ttoi( pT->GetItemText( nFocusItem, nIndex ) );
if ( nCheckImage < 0 )
pT->SetItemText( nFocusItem, nIndex, _T( "0" ) );
else if ( nCheckImage > 0 )
pT->SetItemText( nFocusItem, nIndex, _T( "-1" ) );
else
pT->SetItemText( nFocusItem, nIndex, _T( "1" ) );
NotifyParent( nFocusItem, nFocusSubItem, LCN_MODIFIED );
InvalidateItem( nFocusItem );
}
return;
}
}
}
if ( bCtrlKey && nChar == _T( 'A' ) && !m_bSingleSelect )
{
m_setSelectedItems.clear();
for ( int nItem = 0; nItem < pT->GetItemCount(); nItem++ )
m_setSelectedItems.insert( nItem );
Invalidate();
return;
}
if ( !bCtrlKey && iswprint( nChar ) && iswupper( nChar ) )
{
int nSortIndex = GetColumnIndex( m_nSortColumn );
int nStartItem = nFocusItem + 1;
DWORD dwCurrentTick = GetTickCount();
stdstr strStart;
strStart += nChar;
// has there been another keypress since last search period?
if ( ( dwCurrentTick - m_dwSearchTick ) < SEARCH_PERIOD )
{
if ( m_strSearchString.substr(0, 1 ) != strStart )
m_strSearchString += nChar;
stdstr strFocusText = pT->GetItemText( nFocusItem, nSortIndex );
// are we continuing to type characters under current focus item?
if ( m_strSearchString.length() > 1 && _tcsicmp(m_strSearchString.c_str(),strFocusText.substr(0, m_strSearchString.length() ).c_str() ) == 0 )
{
m_dwSearchTick = GetTickCount();
return;
}
}
else
{
if ( m_strSearchString.substr(0, 1 ) != strStart )
nStartItem = 0;
m_strSearchString = strStart;
}
m_dwSearchTick = GetTickCount();
// scan for next search string
for ( int nFirst = nStartItem; nFirst < pT->GetItemCount(); nFirst++ )
{
stdstr strItemText = pT->GetItemText( nFirst, nSortIndex );
if ( _tcsicmp(m_strSearchString.c_str(), strItemText.substr(0, m_strSearchString.length() ).c_str() ) == 0 )
{
SelectItem( nFirst, nFocusSubItem, TRUE );
EnsureItemVisible( nFirst, nFocusSubItem );
return;
}
}
// re-scan from top if not found search string
for ( int nSecond = 0; nSecond < pT->GetItemCount(); nSecond++ )
{
stdstr strItemText = pT->GetItemText( nSecond, nSortIndex );
if ( _tcsicmp(m_strSearchString.c_str(), strItemText.substr(0, m_strSearchString.length() ).c_str() ) == 0 )
{
SelectItem( nSecond, nFocusSubItem, TRUE );
EnsureItemVisible( nSecond, nFocusSubItem );
return;
}
}
}
return;
}
if ( !bCtrlKey )
SelectItem( m_nFocusItem, m_nFocusSubItem, bShiftKey ? MK_SHIFT : 0 );
}
void OnSysKeyDown( TCHAR /*nChar*/, UINT /*nRepCnt*/, UINT /*nFlags*/ )
{
HideTitleTip( FALSE );
SetMsgHandled( FALSE );
}
void OnSettingsChange( UINT /*nFlags*/, LPCTSTR /*lpszSection*/ )
{
OnSettingsChange();
}
void OnSettingsChange()
{
LoadSettings();
ResetScrollBars();
Invalidate();
}
LRESULT OnCtlColorListBox( UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& /*bHandled*/ )
{
return DefWindowProc( nMsg, wParam, lParam );
}
LRESULT OnEndEdit( LPNMHDR lpNMHDR )
{
T* pT = static_cast<T*>(this);
CListNotify *pListNotify = reinterpret_cast<CListNotify *>( lpNMHDR );
m_bEditItem = FALSE;
int nIndex = GetColumnIndex( pListNotify->m_nSubItem );
switch ( pListNotify->m_nExitChar )
{
case VK_ESCAPE: break; // do nothing
case VK_DELETE: pT->SetItemText( pListNotify->m_nItem, nIndex, _T( "" ) );
NotifyParent( pListNotify->m_nItem, pListNotify->m_nSubItem, LCN_MODIFIED );
break;
default: if ( pListNotify->m_lpItemDate == NULL )
pT->SetItemText( pListNotify->m_nItem, nIndex, pListNotify->m_lpszItemText );
else
{
if ( _ttoi( pListNotify->m_lpszItemText ) == 0 )
pT->SetItemText( pListNotify->m_nItem, nIndex, _T( "" ) );
else
pT->SetItemDate( pListNotify->m_nItem, nIndex, *pListNotify->m_lpItemDate );
}
if ( pListNotify->m_nExitChar == VK_TAB )
PostMessage( WM_KEYDOWN, (WPARAM)VK_TAB );
NotifyParent( pListNotify->m_nItem, pListNotify->m_nSubItem, LCN_MODIFIED );
break;
}
InvalidateItem( pListNotify->m_nItem );
return 0;
}
DWORD OnDragEnter( FORMATETC& FormatEtc, STGMEDIUM& StgMedium, DWORD /*dwKeyState*/, CPoint point )
{
DWORD dwEffect = DROPEFFECT_NONE;
if ( FormatEtc.cfFormat == m_nHeaderClipboardFormat )
{
LPBYTE lpDragHeader = (LPBYTE)GlobalLock( StgMedium.hGlobal );
if ( lpDragHeader == NULL )
return DROPEFFECT_NONE;
// dragged column must originate from this control
if ( *( (HWND*)lpDragHeader ) == m_hWnd )
dwEffect = DropColumn( point ) ? DROPEFFECT_MOVE : DROPEFFECT_NONE;
GlobalUnlock( StgMedium.hGlobal );
}
return dwEffect;
}
DWORD OnDragOver( FORMATETC& FormatEtc, STGMEDIUM& StgMedium, DWORD /*dwKeyState*/, CPoint point )
{
DWORD dwEffect = DROPEFFECT_NONE;
if ( FormatEtc.cfFormat == m_nHeaderClipboardFormat )
{
LPBYTE lpDragHeader = (LPBYTE)GlobalLock( StgMedium.hGlobal );
if ( lpDragHeader == NULL )
return DROPEFFECT_NONE;
// dragged column must originate from this control
if ( *( (HWND*)lpDragHeader ) == m_hWnd )
dwEffect = DropColumn( point ) ? DROPEFFECT_MOVE : DROPEFFECT_NONE;
GlobalUnlock( StgMedium.hGlobal );
}
return dwEffect;
}
BOOL OnDrop( FORMATETC& FormatEtc, STGMEDIUM& /*StgMedium*/, DWORD /*dwEffect*/, CPoint /*point*/ )
{
if ( FormatEtc.cfFormat == m_nHeaderClipboardFormat )
{
if ( m_nDragColumn != NULL_COLUMN && m_nHotDivider != NULL_COLUMN )
{
CListColumn listColumn;
if ( !GetColumn( m_nDragColumn, listColumn ) )
return FALSE;
// move column to new position
m_aColumns.RemoveAt( m_nDragColumn );
m_aColumns.InsertAt( ( m_nDragColumn < m_nHotColumn ? ( m_nHotDivider == 0 ? 0 : m_nHotDivider - 1 ) : m_nHotDivider ), listColumn );
Invalidate();
}
return TRUE;
}
// not supported
return FALSE;
}
void OnDragLeave()
{
}
BOOL OnRenderData( FORMATETC& FormatEtc, STGMEDIUM *pStgMedium, BOOL /*bDropComplete*/ )
{
if ( FormatEtc.cfFormat == m_nHeaderClipboardFormat )
{
pStgMedium->tymed = TYMED_HGLOBAL;
pStgMedium->hGlobal = GlobalAlloc( GMEM_MOVEABLE, sizeof( HWND ) );
if ( pStgMedium->hGlobal == NULL )
return FALSE;
LPBYTE lpDragHeader = (LPBYTE)GlobalLock( pStgMedium->hGlobal );
if ( lpDragHeader == NULL )
return FALSE;
// store this window handle
*( (HWND*)lpDragHeader ) = m_hWnd;
GlobalUnlock( pStgMedium->hGlobal );
return TRUE;
}
return FALSE;
}
void DoPaint( CDCHandle dcPaint )
{
T* pT = static_cast<T*>(this);
int nContextState = dcPaint.SaveDC();
pT->DrawBkgnd( dcPaint );
pT->DrawList( dcPaint );
pT->DrawSelect( dcPaint );
pT->DrawHeader( dcPaint );
dcPaint.RestoreDC( nContextState );
}
void DrawBkgnd( CDCHandle dcPaint )
{
CRect rcClip;
if ( dcPaint.GetClipBox( rcClip ) == ERROR )
return;
dcPaint.SetBkColor( m_rgbBackground );
dcPaint.ExtTextOut( rcClip.left, rcClip.top, ETO_OPAQUE, rcClip, _T( "" ), 0, NULL );
CRect rcClient;
GetClientRect( rcClient );
rcClient.top = ( m_bShowHeader ? m_nHeaderHeight : 0 );
if ( !m_bmpBackground.IsNull() && rcClip.bottom > rcClient.top )
{
CSize sizBackground;
m_bmpBackground.GetSize( sizBackground );
CDC dcBackgroundImage;
dcBackgroundImage.CreateCompatibleDC( dcPaint );
HBITMAP hOldBitmap = dcBackgroundImage.SelectBitmap( m_bmpBackground );
if ( m_bTileBackground )
{
// calculate tile image maximum rows and columns
div_t divRows = div( (int)rcClient.Height(), (int)sizBackground.cy );
int nTileRows = divRows.rem > 0 ? divRows.quot + 1 : divRows.quot;
div_t divColumns = div( (int)rcClient.Width(), (int)sizBackground.cx );
int nTileColumns = divColumns.rem > 0 ? divColumns.quot + 1 : divColumns.quot;
// draw tiled background image
for ( int nRow = 0; nRow <= nTileRows; nRow++ )
{
for ( int nColumn = 0; nColumn <= nTileColumns; nColumn++ )
dcPaint.BitBlt( nColumn * sizBackground.cx, nRow * sizBackground.cy, sizBackground.cx, sizBackground.cy, dcBackgroundImage, 0, 0, SRCCOPY );
}
}
else
{
CRect rcCentreImage( rcClient );
// horizontally centre image if smaller than the client width
if ( sizBackground.cx < rcClient.Width() )
{
rcCentreImage.left = ( rcClient.Width() / 2 ) - (int)( sizBackground.cx / 2 );
rcCentreImage.right = rcCentreImage.left + sizBackground.cx;
}
// vertically centre image if smaller than the client height
if ( sizBackground.cy + 16 < rcClient.Height() )
{
rcCentreImage.top = ( rcClient.Height() / 2 ) - (int)( ( sizBackground.cy + 16 ) / 2 );
rcCentreImage.bottom = rcCentreImage.top + sizBackground.cy;
}
// draw centred background image
dcPaint.BitBlt( rcCentreImage.left, rcCentreImage.top, rcCentreImage.Width(), rcCentreImage.Height(), dcBackgroundImage, 0, 0, SRCCOPY );
}
dcBackgroundImage.SelectBitmap( hOldBitmap );
}
}
void DrawHeader( CDCHandle dcPaint )
{
if ( !m_bShowHeader )
return;
CRect rcClip;
if ( dcPaint.GetClipBox( rcClip ) == ERROR )
return;
CRect rcHeader;
GetClientRect( rcHeader );
rcHeader.bottom = m_nHeaderHeight;
if ( rcClip.top > rcHeader.bottom )
return;
dcPaint.SetBkColor( m_rgbHeaderBackground );
dcPaint.ExtTextOut( rcHeader.left, rcHeader.top, ETO_OPAQUE, rcHeader, _T( "" ), 0, NULL );
CPen penHighlight;
penHighlight.CreatePen( PS_SOLID, 1, m_rgbHeaderBorder );
CPen penShadow;
penShadow.CreatePen( PS_SOLID, 1, m_rgbHeaderShadow );
CRect rcHeaderItem( rcHeader );
rcHeaderItem.OffsetRect( -GetScrollPos( SB_HORZ ), 0 );
int nHeaderWidth = 0;
for ( int nColumn = 0, nColumnCount = GetColumnCount(); nColumn < nColumnCount; rcHeaderItem.left = rcHeaderItem.right, nColumn++ )
{
CListColumn listColumn;
if ( !GetColumn( nColumn, listColumn ) )
break;
rcHeaderItem.right = rcHeaderItem.left + listColumn.m_nWidth;
nHeaderWidth += rcHeaderItem.Width();
if ( rcHeaderItem.right < rcClip.left )
continue;
if ( rcHeaderItem.left > rcClip.right )
break;
// draw header and divider
if ( nColumn == m_nHighlightColumn )
{
dcPaint.SetBkColor( m_rgbHeaderHighlight );
dcPaint.ExtTextOut( rcHeaderItem.left, rcHeaderItem.top, ETO_OPAQUE, rcHeaderItem, _T( "" ), 0, NULL );
}
dcPaint.SelectPen( penShadow );
dcPaint.MoveTo( rcHeaderItem.right - 1, rcHeaderItem.top + 1 );
dcPaint.LineTo( rcHeaderItem.right - 1, m_nHeaderHeight - 1 );
dcPaint.SelectPen( penHighlight );
dcPaint.MoveTo( rcHeaderItem.right, rcHeaderItem.top + 1 );
dcPaint.LineTo( rcHeaderItem.right, m_nHeaderHeight - 1 );
CRect rcHeaderText( rcHeaderItem );
rcHeaderText.left += nColumn == 0 ? 4 : 3;
rcHeaderText.OffsetRect( 0, 1 );
BOOL bShowArrow = m_bShowSort && ( rcHeaderItem.Width() > 15 );
if(listColumn.m_nImage == ITEM_IMAGE_NONE )
{
// offset text bounding rectangle to account for sorting arrow
if ( bShowArrow && !listColumn.m_bFixed && listColumn.m_nIndex == m_nSortColumn )
rcHeaderText.right -= 15;
}
// margin header text
rcHeaderText.DeflateRect( 4, 0, 5, 0 );
// has this header item an associated image?
if ( listColumn.m_nImage != ITEM_IMAGE_NONE )
{
CSize sizeIcon;
m_ilListItems.GetIconSize( sizeIcon );
CRect rcHeaderImage;
rcHeaderImage.left = listColumn.m_strText.empty() ? ( ( rcHeaderText.left + rcHeaderText.right ) / 2 ) - ( sizeIcon.cx / 2 ) - ( 0 ) : rcHeaderText.left;
rcHeaderImage.right = min( rcHeaderImage.left + sizeIcon.cx, rcHeaderItem.right - 2 );
rcHeaderImage.top = ( ( rcHeaderItem.top + rcHeaderItem.bottom ) / 2 ) - ( sizeIcon.cy / 2 );
rcHeaderImage.bottom = min( rcHeaderImage.top + sizeIcon.cy, rcHeaderItem.bottom );
if(listColumn.m_nIndex == m_nSortColumn)
m_ilListItems.DrawEx( listColumn.m_nImage, dcPaint, rcHeaderImage, CLR_DEFAULT, CLR_DEFAULT, ILD_TRANSPARENT | ILD_SELECTED );
else
m_ilListItems.DrawEx( listColumn.m_nImage, dcPaint, rcHeaderImage, CLR_DEFAULT, CLR_DEFAULT, ILD_TRANSPARENT );
// offset header text (for image)
rcHeaderText.left += sizeIcon.cx + 4;
}
dcPaint.SelectFont( m_fntListFont );
dcPaint.SetTextColor( m_rgbHeaderText );
dcPaint.SetBkMode( TRANSPARENT );
UINT nFormat = DT_SINGLELINE | DT_NOPREFIX | DT_VCENTER | DT_END_ELLIPSIS;
if ( listColumn.m_nFlags & ITEM_FLAGS_CENTRE )
nFormat |= DT_CENTER;
else if ( listColumn.m_nFlags & ITEM_FLAGS_RIGHT )
nFormat |= DT_RIGHT;
else
nFormat |= DT_LEFT;
// draw header text
if ( !rcHeaderText.IsRectEmpty() && !listColumn.m_strText.empty() )
dcPaint.DrawText( listColumn.m_strText.c_str(), (int)listColumn.m_strText.length(), rcHeaderText, nFormat );
// draw sorting arrow
if ( bShowArrow && !listColumn.m_bFixed && listColumn.m_nIndex == m_nSortColumn )
{
CSize sizeIcon;
m_ilListItems.GetIconSize( sizeIcon );
CRect rcSortArrow;
rcSortArrow.left = rcHeaderText.right + 4;
rcSortArrow.right = min( rcSortArrow.left + sizeIcon.cx, rcHeaderItem.right );
rcSortArrow.top = rcHeaderItem.Height() / 2 - 3;
rcSortArrow.bottom = min( rcSortArrow.top + sizeIcon.cy, rcHeaderItem.bottom );
m_ilListItems.DrawEx( m_bSortAscending ? ITEM_IMAGE_UP : ITEM_IMAGE_DOWN, dcPaint, rcSortArrow, CLR_DEFAULT, CLR_DEFAULT, ILD_TRANSPARENT );
}
}
// draw a frame around all header columns
if ( nHeaderWidth > 0 )
dcPaint.Draw3dRect( CRect( rcHeader.left, rcHeader.top, rcHeader.right + 2, rcHeader.bottom ), m_rgbHeaderBorder, m_rgbHeaderShadow );
}
void DrawRoundRect( CDCHandle dcPaint, CRect& rcRect, COLORREF rgbOuter, COLORREF rgbInner )
{
CRect rcRoundRect( rcRect );
CPen penBorder;
penBorder.CreatePen( PS_SOLID, 1, rgbOuter );
CBrush bshInterior;
bshInterior.CreateSolidBrush( m_rgbBackground );
dcPaint.SelectPen( penBorder );
dcPaint.SelectBrush( bshInterior );
dcPaint.RoundRect( rcRoundRect, CPoint( 5, 5 ) );
rcRoundRect.DeflateRect( 1, 1 );
CPen penInnerBorder;
penInnerBorder.CreatePen( PS_SOLID, 1, rgbInner );
dcPaint.SelectPen( penInnerBorder );
dcPaint.RoundRect( rcRoundRect, CPoint( 2, 2 ) );
}
void DrawGradient( CDCHandle dcPaint, CRect& rcRect, COLORREF rgbTop, COLORREF rgbBottom )
{
GRADIENT_RECT grdRect = { 0, 1 };
TRIVERTEX triVertext[ 2 ] = {
rcRect.left,
rcRect.top,
GetRValue( rgbTop ) << 8,
GetGValue( rgbTop ) << 8,
GetBValue( rgbTop ) << 8,
0x0000,
rcRect.right,
rcRect.bottom,
GetRValue( rgbBottom ) << 8,
GetGValue( rgbBottom ) << 8,
GetBValue( rgbBottom ) << 8,
0x0000
};
dcPaint.GradientFill( triVertext, 2, &grdRect, 1, GRADIENT_FILL_RECT_V );
}
void DrawList( CDCHandle dcPaint )
{
T* pT = static_cast<T*>(this);
CRect rcClip;
if ( dcPaint.GetClipBox( rcClip ) == ERROR )
return;
CRect rcItem;
rcItem.left = -GetScrollPos( SB_HORZ );
rcItem.right = GetTotalWidth();
rcItem.top = ( m_bShowHeader ? m_nHeaderHeight : 0 );
rcItem.bottom = rcItem.top;
// draw all visible items
for ( int nItem = GetTopItem(); nItem < pT->GetItemCount(); rcItem.top = rcItem.bottom, nItem++ )
{
rcItem.bottom = rcItem.top + m_nItemHeight;
if ( rcItem.bottom < rcClip.top || rcItem.right < rcClip.left )
continue;
if ( rcItem.top > rcClip.bottom || rcItem.left > rcClip.right )
break;
// may be implemented in a derived class
pT->DrawItem( dcPaint, nItem, rcItem );
}
}
void DrawItem( CDCHandle dcPaint, int nItem, CRect& rcItem )
{
T* pT = static_cast<T*>(this);
CRect rcClip;
if ( dcPaint.GetClipBox( rcClip ) == ERROR )
return;
int nFocusItem = NULL_ITEM;
int nFocusSubItem = NULL_SUBITEM;
GetFocusItem( nFocusItem, nFocusSubItem );
BOOL bSelectedItem = IsSelected( nItem );
//BOOL bControlFocus = ( GetFocus() == m_hWnd || m_bEditItem );
// draw selected background
if ( bSelectedItem )
{
dcPaint.SetBkColor( m_rgbSelectedItem );
dcPaint.ExtTextOut( rcItem.left, rcItem.top, ETO_OPAQUE, rcItem, _T( "" ), 0, NULL );
}
CRect rcSubItem( rcItem );
rcSubItem.right = rcSubItem.left;
for ( int nSubItem = 0, nColumnCount = GetColumnCount(); nSubItem < nColumnCount; rcSubItem.left = rcSubItem.right + 1, nSubItem++ )
{
CListColumn listColumn;
if ( !GetColumn( nSubItem, listColumn ) )
break;
rcSubItem.right = rcSubItem.left + listColumn.m_nWidth - 1;
if ( rcSubItem.right < rcClip.left || rcSubItem.Width() == 0 )
continue;
if ( rcSubItem.left > rcClip.right )
break;
LPCTSTR strItemText = pT->GetItemText( nItem, listColumn.m_nIndex );
int nItemImage = pT->GetItemImage( nItem, listColumn.m_nIndex );
UINT nItemFormat = pT->GetItemFormat( nItem, listColumn.m_nIndex );
UINT nItemFlags = pT->GetItemFlags( nItem, listColumn.m_nIndex );
// custom draw subitem format
if ( nItemFormat == ITEM_FORMAT_CUSTOM )
{
pT->DrawCustomItem( dcPaint, nItem, nSubItem, rcSubItem );
return;
}
BOOL bFocusSubItem = ( m_bFocusSubItem && nFocusItem == nItem && nFocusSubItem == nSubItem );
COLORREF rgbBackground = m_rgbBackground;
COLORREF rgbText = m_rgbItemText;
if ( bFocusSubItem )
{
dcPaint.SetBkColor( m_bEditItem ? m_rgbBackground : m_rgbItemFocus );
dcPaint.ExtTextOut( rcSubItem.left, rcSubItem.top, ETO_OPAQUE, rcSubItem, _T( "" ), 0, NULL );
if ( m_bEditItem )
{
CBrush bshSelectFrame;
bshSelectFrame.CreateSolidBrush( m_rgbItemFocus );
dcPaint.FrameRect( rcSubItem, bshSelectFrame );
}
}
else if ( pT->GetItemColours( nItem, nSubItem, rgbBackground, rgbText ) && rgbBackground != m_rgbBackground )
{
CPen penBorder;
penBorder.CreatePen( PS_SOLID, 1, rgbBackground );
CBrush bshInterior;
bshInterior.CreateSolidBrush( rgbBackground );
dcPaint.SelectPen( penBorder );
dcPaint.SelectBrush( bshInterior );
dcPaint.RoundRect( rcSubItem, CPoint( 3, 3 ) );
}
CRect rcItemText( rcSubItem );
// margin item text
//rcItemText.left += nSubItem == 0 ? 4 : 3;
//rcItemText.DeflateRect( 4, 0 );
// draw subitem image if supplied
if ( !m_ilItemImages.IsNull() && nItemImage != ITEM_IMAGE_NONE && ( !m_bEditItem || ( m_bEditItem && !bFocusSubItem ) ) )
{
CSize sizeIcon;
m_ilItemImages.GetIconSize( sizeIcon );
CRect rcItemImage;
rcItemImage.left = (strItemText[0] == 0) ? ( ( rcItemText.left + rcItemText.right ) / 2 ) - ( sizeIcon.cx / 2 ) - ( 0 ) : rcItemText.left;
rcItemImage.right = min( rcItemImage.left + sizeIcon.cx, rcSubItem.right );
rcItemImage.top = ( ( rcSubItem.top + rcSubItem.bottom ) / 2 ) - ( sizeIcon.cy / 2 );
rcItemImage.bottom = min( rcItemImage.top + sizeIcon.cy, rcSubItem.bottom );
m_ilItemImages.DrawEx( nItemImage, dcPaint, rcItemImage, CLR_DEFAULT, CLR_DEFAULT, ILD_TRANSPARENT );
// offset item text (for image)
rcItemText.left += sizeIcon.cx + 4;
}
if ( rcItemText.IsRectEmpty() )
continue;
COLORREF rgbSelectedText = m_rgbSelectedText;
pT->GetItemSelectedColours( nItem, nSubItem, rgbSelectedText );
dcPaint.SelectFont( pT->GetItemFont( nItem, nSubItem ) );
dcPaint.SetTextColor( ( bSelectedItem && !bFocusSubItem ) ? rgbSelectedText : rgbText );
dcPaint.SetBkMode( TRANSPARENT );
UINT nFormat = DT_SINGLELINE | DT_NOPREFIX | DT_VCENTER | DT_END_ELLIPSIS;
if ( nItemFlags & ITEM_FLAGS_CENTRE )
nFormat |= DT_CENTER;
else if ( nItemFlags & ITEM_FLAGS_RIGHT )
nFormat |= DT_RIGHT;
else
nFormat |= DT_LEFT;
switch ( nItemFormat )
{
case ITEM_FORMAT_DATETIME: if ( strItemText[0] != 0 )
{
SYSTEMTIME stItemDate;
if ( !GetItemDate( nItem, listColumn.m_nIndex, stItemDate ) )
break;
stdstr strItemDate;
if ( nItemFlags & ITEM_FLAGS_DATE_ONLY )
strItemDate = FormatDate( stItemDate );
else if ( nItemFlags & ITEM_FLAGS_TIME_ONLY )
strItemDate = FormatTime( stItemDate );
else
strItemDate = FormatDate( stItemDate ) + _T( " " ) + FormatTime( stItemDate );
dcPaint.DrawText( strItemDate.c_str(), (int)strItemDate.length(), rcItemText, nFormat );
}
break;
case ITEM_FORMAT_CHECKBOX:
case ITEM_FORMAT_CHECKBOX_3STATE: {
CSize sizeIcon;
m_ilListItems.GetIconSize( sizeIcon );
CRect rcCheckBox;
rcCheckBox.left = ( ( rcItemText.left + rcItemText.right ) / 2 ) - ( sizeIcon.cx / 2 ) - 1;
rcCheckBox.right = min( rcCheckBox.left + sizeIcon.cx, rcSubItem.right );
rcCheckBox.top = ( ( rcSubItem.top + rcSubItem.bottom ) / 2 ) - ( sizeIcon.cy / 2 );
rcCheckBox.bottom = min( rcCheckBox.top + sizeIcon.cy, rcSubItem.bottom );
int nCheckValue = _ttoi( strItemText );
if ( nItemFormat == ITEM_FORMAT_CHECKBOX )
m_ilListItems.DrawEx( nCheckValue > 0 ? ITEM_IMAGE_CHECK_ON : ITEM_IMAGE_CHECK_OFF, dcPaint, rcCheckBox, CLR_DEFAULT, CLR_DEFAULT, ILD_TRANSPARENT );
else
{
int nCheckImage = ITEM_IMAGE_3STATE_UNDEF;
if ( nCheckValue < 0 )
nCheckImage = ITEM_IMAGE_3STATE_OFF;
else if ( nCheckValue > 0 )
nCheckImage = ITEM_IMAGE_3STATE_ON;
m_ilListItems.DrawEx( nCheckImage, dcPaint, rcCheckBox, CLR_DEFAULT, CLR_DEFAULT, ILD_TRANSPARENT );
}
}
break;
case ITEM_FORMAT_PROGRESS: {
CRect rcProgress( rcSubItem );
rcProgress.DeflateRect( 3, 2 );
// draw progress border
DrawRoundRect( dcPaint, rcProgress, m_rgbHeaderShadow, m_rgbHeaderBackground );
// fill progress bar area
rcProgress.DeflateRect( 3, 3 );
rcProgress.right = rcProgress.left + (int)( (double)rcProgress.Width() * ( ( max( min( atof( strItemText ), 100 ), 0 ) ) / 100.0 ) );
DrawGradient( dcPaint, rcProgress, m_rgbProgressTop, m_rgbProgressBottom );
}
break;
case ITEM_FORMAT_HYPERLINK: if ( nItem == m_nHotItem && nSubItem == m_nHotSubItem && !( nItemFlags & ITEM_FLAGS_READ_ONLY ) )
{
dcPaint.SelectFont( m_fntUnderlineFont );
dcPaint.SetTextColor( m_rgbHyperLink );
}
default: // draw item text
{
size_t len = strlen(strItemText);
if ( len > 0 )
dcPaint.DrawText( strItemText, (int)len, rcItemText, nFormat );
}
break;
}
}
}
void DrawSelect( CDCHandle dcPaint )
{
if ( !m_bGroupSelect )
return;
int nHorzScroll = GetScrollPos( SB_HORZ );
int nVertScroll = GetScrollPos( SB_VERT );
CRect rcGroupSelect( m_rcGroupSelect );
rcGroupSelect.OffsetRect( -nHorzScroll, -nVertScroll );
CRect rcClient;
GetClientRect( rcClient );
rcClient.top = ( m_bShowHeader ? m_nHeaderHeight : 0 );
// limit box to list client area if scrolled to limits
if ( nHorzScroll > ( GetTotalWidth() - rcClient.Width() ) )
rcGroupSelect.right = min( rcClient.right, rcGroupSelect.right );
if ( nHorzScroll == 0 )
rcGroupSelect.left = max( rcClient.left, rcGroupSelect.left );
if ( nVertScroll > ( GetTotalHeight() - rcClient.Height() ) )
rcGroupSelect.bottom = min( rcClient.bottom, rcGroupSelect.bottom );
if ( nVertScroll == 0 )
rcGroupSelect.top = max( rcClient.top, rcGroupSelect.top );
// limit bitmap to client area
CRect rcSelectArea( rcGroupSelect );
rcSelectArea.IntersectRect( rcSelectArea, rcClient );
CDC dcBackground;
dcBackground.CreateCompatibleDC( dcPaint );
int nBackgroundContext = dcBackground.SaveDC();
CBitmap bmpBackground;
bmpBackground.CreateCompatibleBitmap( dcPaint, rcSelectArea.Width(), rcSelectArea.Height() );
dcBackground.SelectBitmap( bmpBackground );
// take a copy of existing backgroud
dcBackground.BitBlt( 0, 0, rcSelectArea.Width(), rcSelectArea.Height(), dcPaint, rcSelectArea.left, rcSelectArea.top, SRCCOPY );
CDC dcGroupSelect;
dcGroupSelect.CreateCompatibleDC( dcPaint );
int nGroupSelectContext = dcGroupSelect.SaveDC();
CBitmap bmpGroupSelect;
bmpGroupSelect.CreateCompatibleBitmap( dcPaint, rcSelectArea.Width(), rcSelectArea.Height() );
dcGroupSelect.SelectBitmap( bmpGroupSelect );
// draw group select box
dcGroupSelect.SetBkColor( m_rgbItemFocus );
dcGroupSelect.ExtTextOut( 0, 0, ETO_OPAQUE, CRect( CPoint( 0 ), rcSelectArea.Size() ), _T( "" ), 0, NULL );
BLENDFUNCTION blendFunction;
blendFunction.BlendOp = AC_SRC_OVER;
blendFunction.BlendFlags = 0;
blendFunction.SourceConstantAlpha = 180;
blendFunction.AlphaFormat = 0;
// blend existing background with selection box
dcGroupSelect.AlphaBlend( 0, 0, rcSelectArea.Width(), rcSelectArea.Height(), dcBackground, 0, 0, rcSelectArea.Width(), rcSelectArea.Height(), blendFunction );
// draw blended selection box
dcPaint.BitBlt( rcSelectArea.left, rcSelectArea.top, rcSelectArea.Width(), rcSelectArea.Height(), dcGroupSelect, 0, 0, SRCCOPY );
// draw selection box frame
CBrush bshSelectFrame;
bshSelectFrame.CreateSolidBrush( m_rgbItemText );
dcPaint.FrameRect( rcGroupSelect, bshSelectFrame );
dcBackground.RestoreDC( nBackgroundContext );
dcGroupSelect.RestoreDC( nGroupSelectContext );
}
void DrawCustomItem( CDCHandle dcPaint, int /*nItem*/, int /*nSubItem*/, CRect& /*rcSubItem*/ )
{
ATLASSERT( FALSE ); // must be implemented in a derived class
}
};
struct CSubItem
{
stdstr m_strText;
int m_nImage;
UINT m_nFormat;
UINT m_nFlags;
UINT m_nMaxEditLen;
CListArray < stdstr > m_aComboList;
HFONT m_hFont;
COLORREF m_rgbBackground;
COLORREF m_rgbText;
COLORREF m_rgbSelectedText;
};
template < class TData = DWORD >
struct CListItem
{
CListArray < CSubItem > m_aSubItems;
stdstr m_strToolTip;
TData m_tData;
};
template < class TData >
class CListCtrlData : public CListImpl< CListCtrlData< TData > >
{
public:
DECLARE_WND_CLASS( _T( "ListCtrl" ) )
protected:
CListArray < CListItem< TData > > m_aItems;
public:
int AddItem( CListItem< TData >& listItem )
{
if ( !m_aItems.Add( listItem ) )
return -1;
return CListImpl< CListCtrlData >::AddItem() ? GetItemCount() - 1 : -1;
}
int AddItemAt( CListItem< TData >& listItem, int Index )
{
if (Index < 0 )
{
Index = 0;
}
if (Index > GetItemCount())
{
Index = GetItemCount();
}
if ( !m_aItems.AddAt( listItem, Index ) )
return -1;
return CListImpl< CListCtrlData >::AddItem() ? Index : -1;
}
int AddItem( LPCTSTR lpszText, int nImage = ITEM_IMAGE_NONE, UINT nFormat = ITEM_FORMAT_NONE, UINT nFlags = ITEM_FLAGS_NONE )
{
CSubItem listSubItem;
listSubItem.m_nImage = ITEM_IMAGE_NONE;
listSubItem.m_nFormat = nFormat;
listSubItem.m_nFlags = ValidateFlags( nFlags );
listSubItem.m_hFont = NULL;
listSubItem.m_rgbBackground = m_rgbBackground;
listSubItem.m_rgbText = m_rgbItemText;
listSubItem.m_rgbSelectedText = m_rgbSelectedText;
listSubItem.m_nMaxEditLen = -1;
CListItem< TData > listItem;
for ( int nSubItem = 0; nSubItem < GetColumnCount(); nSubItem++ )
listItem.m_aSubItems.Add( listSubItem );
// set item details for first subitem
listItem.m_aSubItems[ 0 ].m_strText = lpszText;
listItem.m_aSubItems[ 0 ].m_nImage = nImage;
return AddItem( listItem );
}
int AddItemAt(int Index, LPCTSTR lpszText, int nImage = ITEM_IMAGE_NONE, UINT nFormat = ITEM_FORMAT_NONE, UINT nFlags = ITEM_FLAGS_NONE )
{
CSubItem listSubItem;
listSubItem.m_nImage = ITEM_IMAGE_NONE;
listSubItem.m_nFormat = nFormat;
listSubItem.m_nFlags = ValidateFlags( nFlags );
listSubItem.m_hFont = NULL;
listSubItem.m_rgbBackground = m_rgbBackground;
listSubItem.m_rgbText = m_rgbItemText;
listSubItem.m_rgbSelectedText = m_rgbSelectedText;
listSubItem.m_nMaxEditLen = (UINT)-1;
CListItem< TData > listItem;
for ( int nSubItem = 0; nSubItem < GetColumnCount(); nSubItem++ )
listItem.m_aSubItems.Add( listSubItem );
// set item details for first subitem
listItem.m_aSubItems[ 0 ].m_strText = lpszText;
listItem.m_aSubItems[ 0 ].m_nImage = nImage;
return AddItemAt( listItem, Index );
}
BOOL DeleteItem( int nItem )
{
if ( nItem < 0 || nItem >= GetItemCount() )
return FALSE;
return m_aItems.RemoveAt( nItem ) ? CListImpl< CListCtrlData >::DeleteItem( nItem ) : FALSE;
}
BOOL DeleteAllItems()
{
m_aItems.RemoveAll();
return CListImpl< CListCtrlData >::DeleteAllItems();
}
int GetItemCount()
{
return m_aItems.GetSize();
}
BOOL GetItem( int nItem, CListItem< TData >& listItem )
{
if ( nItem < 0 || nItem >= GetItemCount() )
return FALSE;
listItem = m_aItems[ nItem ];
return TRUE;
}
BOOL GetItem( int nItem, CListItem< TData >*& listItem )
{
if ( nItem < 0 || nItem >= GetItemCount() )
{
listItem = NULL;
return FALSE;
}
listItem = &m_aItems[ nItem ];
return TRUE;
}
BOOL GetSubItem( int nItem, int nSubItem, CSubItem& listSubItem )
{
CListItem< TData > * listItem;
if ( !GetItem( nItem, listItem ) )
return FALSE;
if ( nSubItem < 0 || nSubItem >= (int)listItem->m_aSubItems.GetSize() )
return FALSE;
listSubItem = listItem->m_aSubItems[ nSubItem ];
return TRUE;
}
BOOL GetSubItem( int nItem, int nSubItem, CSubItem *& listSubItem )
{
CListItem< TData > * listItem;
if ( !GetItem( nItem, listItem ) )
{
listSubItem = NULL;
return FALSE;
}
if ( nSubItem < 0 || nSubItem >= (int)listItem->m_aSubItems.GetSize() )
{
listSubItem = NULL;
return FALSE;
}
listSubItem = &listItem->m_aSubItems[ nSubItem ];
return TRUE;
}
LPCTSTR GetItemText( int nItem, int nSubItem )
{
CSubItem * listSubItem;
return GetSubItem( nItem, nSubItem, listSubItem ) ? listSubItem->m_strText.c_str() : _T( "" );
}
UINT GetItemMaxEditLen( int nItem, int nSubItem )
{
CSubItem * listSubItem;
return GetSubItem( nItem, nSubItem, listSubItem ) ? listSubItem->m_nMaxEditLen : 0;
}
int GetItemImage( int nItem, int nSubItem )
{
CSubItem *listSubItem;
return GetSubItem( nItem, nSubItem, listSubItem ) ? listSubItem->m_nImage : ITEM_IMAGE_NONE;
}
UINT GetItemFormat( int nItem, int nSubItem )
{
CSubItem * listSubItem;
if ( !GetSubItem( nItem, nSubItem, listSubItem ) )
return FALSE;
return listSubItem->m_nFormat == ITEM_FORMAT_NONE ? GetColumnFormat( IndexToOrder( nSubItem ) ) : listSubItem->m_nFormat;
}
UINT GetItemFlags( int nItem, int nSubItem )
{
CSubItem *listSubItem;
if ( !GetSubItem( nItem, nSubItem, listSubItem ) )
return FALSE;
return listSubItem->m_nFlags == ITEM_FLAGS_NONE ? GetColumnFlags( IndexToOrder( nSubItem ) ) : listSubItem->m_nFlags;
}
BOOL GetItemComboList( int nItem, int nSubItem, CListArray < stdstr >& aComboList )
{
CSubItem listSubItem;
if ( !GetSubItem( nItem, nSubItem, listSubItem ) )
return FALSE;
aComboList = listSubItem.m_aComboList;
return aComboList.IsEmpty() ? GetColumnComboList( IndexToOrder( nSubItem ), aComboList ) : !aComboList.IsEmpty();
}
HFONT GetItemFont( int nItem, int nSubItem )
{
CSubItem * listSubItem;
if ( !GetSubItem( nItem, nSubItem, listSubItem ) )
return FALSE;
return listSubItem->m_hFont == NULL ? CListImpl< CListCtrlData >::GetItemFont( nItem, nSubItem ) : listSubItem->m_hFont;
}
BOOL GetItemColours( int nItem, int nSubItem, COLORREF& rgbBackground, COLORREF& rgbText )
{
CSubItem *listSubItem;
if ( !GetSubItem( nItem, nSubItem, listSubItem ) )
return FALSE;
rgbBackground = listSubItem->m_rgbBackground;
rgbText = listSubItem->m_rgbText;
return TRUE;
}
BOOL GetItemSelectedColours( int nItem, int nSubItem, COLORREF& rgbSelectedText )
{
CSubItem *listSubItem;
if ( !GetSubItem( nItem, nSubItem, listSubItem ) )
return FALSE;
rgbSelectedText = listSubItem->m_rgbSelectedText;
return TRUE;
}
stdstr GetItemToolTip( int nItem, int /*nSubItem*/ )
{
CListItem< TData > listItem;
return GetItem( nItem, listItem ) ? listItem.m_strToolTip : _T( "" );
}
BOOL GetItemData( int nItem, TData& tData )
{
CListItem< TData > listItem;
if ( !GetItem( nItem, listItem ) )
return FALSE;
tData = listItem.m_tData;
return TRUE;
}
BOOL SetItemText( int nItem, int nSubItem, LPCTSTR lpszText, bool bInvalidateItem = true)
{
if ( nItem < 0 || nItem >= GetItemCount() )
return FALSE;
if ( nSubItem < 0 || nSubItem >= (int)m_aItems[ nItem ].m_aSubItems.GetSize() )
return FALSE;
m_aItems[ nItem ].m_aSubItems[ nSubItem ].m_strText = lpszText;
// Added by Rowan - 11/12/2006 to fix an updating bug - can add this to the parameter list if needed
if (bInvalidateItem)
InvalidateItem(nItem, nSubItem);
return TRUE;
}
BOOL SetItemComboIndex( int nItem, int nSubItem, int nIndex )
{
CListArray < stdstr > aComboList;
if ( !GetItemComboList( nItem, nSubItem, aComboList ) )
return FALSE;
return SetItemText( nItem, nSubItem, nIndex < 0 || nIndex >= aComboList.GetSize() ? _T( "" ) : aComboList[ nIndex ] );
}
BOOL SetItemImage( int nItem, int nSubItem, int nImage )
{
if ( nItem < 0 || nItem >= GetItemCount() )
return FALSE;
if ( nSubItem < 0 || nSubItem >= (int)m_aItems[ nItem ].m_aSubItems.GetSize() )
return FALSE;
m_aItems[ nItem ].m_aSubItems[ nSubItem ].m_nImage = nImage;
return TRUE;
}
BOOL SetItemFormat( int nItem, int nSubItem, UINT nFormat, UINT nFlags = ITEM_FLAGS_NONE )
{
if ( nItem < 0 || nItem >= GetItemCount() )
return FALSE;
if ( nSubItem < 0 || nSubItem >= (int)m_aItems[ nItem ].m_aSubItems.GetSize() )
return FALSE;
m_aItems[ nItem ].m_aSubItems[ nSubItem ].m_nFormat = nFormat;
m_aItems[ nItem ].m_aSubItems[ nSubItem ].m_nFlags = nFlags;
return TRUE;
}
BOOL SetItemFormat( int nItem, int nSubItem, UINT nFormat, UINT nFlags, CListArray < stdstr >& aComboList )
{
if ( nItem < 0 || nItem >= GetItemCount() )
return FALSE;
if ( nSubItem < 0 || nSubItem >= (int)m_aItems[ nItem ].m_aSubItems.GetSize() )
return FALSE;
m_aItems[ nItem ].m_aSubItems[ nSubItem ].m_nFormat = nFormat;
m_aItems[ nItem ].m_aSubItems[ nSubItem ].m_nFlags = nFlags;
m_aItems[ nItem ].m_aSubItems[ nSubItem ].m_aComboList = aComboList;
return TRUE;
}
BOOL SetItemMaxEditLen( int nItem, int nSubItem, UINT nMaxEditLen )
{
if ( nItem < 0 || nItem >= GetItemCount() )
return FALSE;
if ( nSubItem < 0 || nSubItem >= (int)m_aItems[ nItem ].m_aSubItems.GetSize() )
return FALSE;
m_aItems[ nItem ].m_aSubItems[ nSubItem ].m_nMaxEditLen = nMaxEditLen;
return TRUE;
}
BOOL SetItemFont( int nItem, int nSubItem, HFONT hFont )
{
if ( nItem < 0 || nItem >= GetItemCount() )
return FALSE;
if ( nSubItem < 0 || nSubItem >= (int)m_aItems[ nItem ].m_aSubItems.GetSize() )
return FALSE;
m_aItems[ nItem ].m_aSubItems[ nSubItem ].m_hFont = hFont;
return TRUE;
}
BOOL SetItemColours( int nItem, int nSubItem, COLORREF rgbBackground, COLORREF rgbText )
{
if ( nItem < 0 || nItem >= GetItemCount() )
return FALSE;
if ( nSubItem < 0 || nSubItem >= (int)m_aItems[ nItem ].m_aSubItems.GetSize() )
return FALSE;
m_aItems[ nItem ].m_aSubItems[ nSubItem ].m_rgbBackground = rgbBackground;
m_aItems[ nItem ].m_aSubItems[ nSubItem ].m_rgbText = rgbText;
return TRUE;
}
BOOL SetItemHighlightColours( int nItem, int nSubItem, COLORREF rgbSelectedText )
{
if ( nItem < 0 || nItem >= GetItemCount() )
return FALSE;
if ( nSubItem < 0 || nSubItem >= (int)m_aItems[ nItem ].m_aSubItems.GetSize() )
return FALSE;
m_aItems[ nItem ].m_aSubItems[ nSubItem ].m_rgbSelectedText = rgbSelectedText;
return TRUE;
}
void ReverseItems()
{
m_aItems.Reverse();
}
class CompareItem
{
public:
CompareItem( int nColumn ) : m_nColumn( nColumn ) {}
inline bool operator() ( const CListItem< TData >& listItem1, const CListItem< TData >& listItem2 )
{
return ( _tcscmp(listItem1.m_aSubItems[ m_nColumn ].m_strText.c_str(), listItem2.m_aSubItems[ m_nColumn ].m_strText.c_str() ) < 0 );
}
protected:
int m_nColumn;
};
void SortItems( int nColumn, BOOL /*bAscending*/ )
{
m_aItems.Sort( CompareItem( nColumn ) );
}
BOOL SetItemToolTip( int nItem, LPCTSTR lpszToolTip )
{
if ( nItem < 0 || nItem >= GetItemCount() )
return FALSE;
m_aItems[ nItem ].m_strToolTip = lpszToolTip;
return TRUE;
}
BOOL SetItemData( int nItem, TData& tData )
{
if ( nItem < 0 || nItem >= GetItemCount() )
return FALSE;
m_aItems[ nItem ].m_tData = tData;
return TRUE;
}
};
typedef CListCtrlData< DWORD > CListCtrl;