#include "stdafx.h"
#include "resource.h"
#include "ListboxEx.h"
// Global variables.
static unsigned int const g_DragListMsg = RegisterWindowMessage( DRAGLISTMSGSTRING );
#ifdef _DEBUG
# define new DEBUG_NEW
# undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
// Local defines
#define ALT_KEY_PRESSED( uFlag ) ( ((uFlag) & (1 << 3)) != 0 )
#define LBEX_LASTITEM_MAGIC 0x45424558 // 'LBEX'
BEGIN_MESSAGE_MAP( CListBoxEx, CDragListBox )
//{{AFX_MSG_MAP(CListBoxEx)
ON_WM_CREATE()
ON_WM_LBUTTONDOWN()
ON_WM_LBUTTONDBLCLK()
ON_WM_LBUTTONUP()
ON_WM_SYSKEYDOWN()
ON_WM_KEYDOWN()
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
IMPLEMENT_DYNCREATE( CListBoxEx, CDragListBox )
CListBoxEx::CListBoxEx()
{
m_pBuddy = NULL;
m_iSelected = -1;
m_bAllowDrag = TRUE;
}
CListBoxEx::~CListBoxEx(){}
BOOL CListBoxEx::PreCreateWindow( CREATESTRUCT & cs )
{
cs.style |= LBEX_STYLE;
cs.dwExStyle |= LBEX_EXSTYLE;
return CWnd::PreCreateWindow(cs);
}
void CListBoxEx::PreSubclassWindow()
{
ModifyStyle( 0, LBEX_STYLE, SWP_SHOWWINDOW );
ModifyStyleEx( 0, LBEX_EXSTYLE, SWP_SHOWWINDOW );
CDragListBox::PreSubclassWindow();
}
int CListBoxEx::OnCreate( LPCREATESTRUCT lpCreateStruct )
{
if ( CDragListBox::OnCreate(lpCreateStruct) == -1 )
return -1;
return 0;
}
// Draw functions //
// DESCRIPTION: Called by the framework when a list box with an owner-draw style is created.
void CListBoxEx::MeasureItem( LPMEASUREITEMSTRUCT lpMeasureItemStruct )
{
// Get current font metrics
TEXTMETRIC metrics;
HDC dc = ::GetDC( m_hWnd );
GetTextMetrics( dc, &metrics );
::ReleaseDC( m_hWnd, dc );
// Set the height
lpMeasureItemStruct->itemHeight = metrics.tmHeight + metrics.tmExternalLeading;
}
// DESCRIPTION: Called by the framework when a visual aspect of an owner-draw list box changes.
void CListBoxEx::DrawItem( LPDRAWITEMSTRUCT lpDrawItemStruct )
{
// If there are no list box items, skip this message.
if ( lpDrawItemStruct->itemID == -1 )
return;
CString strItemText;
CRect rcText( lpDrawItemStruct->rcItem );
COLORREF clrItemText,clrOldTextColor;
// Put a bit of room to the left of the text
rcText.left += 8;
// Act upon the item state
switch ( lpDrawItemStruct->itemAction )
{
case ODA_SELECT:
case ODA_DRAWENTIRE:
// Is the item selected?
if ( lpDrawItemStruct->itemState & ODS_SELECTED )
{
clrItemText = GetSysColor( COLOR_HIGHLIGHTTEXT );
// Clear the rectangle
FillRect( lpDrawItemStruct->hDC, &lpDrawItemStruct->rcItem, (HBRUSH)(COLOR_ACTIVECAPTION+1) );
}
else
{
clrItemText = GetSysColor( COLOR_WINDOWTEXT );
// Clear the rectangle
FillRect( lpDrawItemStruct->hDC, &lpDrawItemStruct->rcItem, (HBRUSH)(COLOR_WINDOW+1) );
}
clrOldTextColor = SetTextColor( lpDrawItemStruct->hDC,clrItemText );
SetBkMode( lpDrawItemStruct->hDC,TRANSPARENT );
// Display the text associated with the item.
if ( lpDrawItemStruct->itemData != LBEX_LASTITEM_MAGIC )
{
GetText( lpDrawItemStruct->itemID,strItemText );
DrawText( lpDrawItemStruct->hDC,LPCTSTR( strItemText ),strItemText.GetLength(),
&rcText,DT_SINGLELINE | DT_VCENTER );
}
else
{
DrawText( lpDrawItemStruct->hDC,"--- Last Item",strItemText.GetLength(),
&rcText,DT_SINGLELINE | DT_VCENTER );
}
// Is the item selected?
if ( lpDrawItemStruct->itemState & ODS_SELECTED )
{
SetTextColor( lpDrawItemStruct->hDC,clrOldTextColor );
DrawFocusRect( lpDrawItemStruct->hDC, &lpDrawItemStruct->rcItem );
}
break;
}
}
// DESCRIPTION: Draws the insertion guide before the item with the indicated index.
void CListBoxEx::DrawSeparator( int nIndex )
{
if ( nIndex == -1 )
return;
CBrush* pBrush = CDC::GetHalftoneBrush();
CRect rect;
GetClientRect(&rect);
CRgn rgn;
rgn.CreateRectRgnIndirect( &rect );
CDC* pDC = GetDC();
// Prevent drawing outside of listbox
// This can happen at the top of the listbox since the listbox's DC is the parent's DC
pDC->SelectClipRgn( &rgn );
GetItemRect( nIndex, &rect );
rect.bottom = rect.top+2;
rect.top -= 2;
rect.left += 5;
rect.right -= 5;
CBrush* pBrushOld = pDC->SelectObject(pBrush);
// Draw main line
pDC->PatBlt( rect.left, rect.top, rect.Width(), rect.Height(), PATINVERT );
// Draw vertical lines
pDC->PatBlt( rect.left-3, rect.top-4, 3, rect.Height()+8, PATINVERT );
pDC->PatBlt( rect.right, rect.top-4, 3, rect.Height()+8, PATINVERT );
pDC->SelectObject( pBrushOld );
ReleaseDC( pDC );
}
// DESCRIPTION: Called to draw the insertion guide before the item with the indicated index.
void CListBoxEx::DrawInsert( int nIndex )
{
if ( m_nLast != nIndex )
{
DrawSeparator( m_nLast );
DrawSeparator( nIndex );
}
// Set last selected
m_nLast = nIndex;
}
// Messages //
BOOL CListBoxEx::OnChildNotify( UINT nMessage, WPARAM wParam, LPARAM lParam, LRESULT *pLResult )
{
if ( nMessage == g_DragListMsg )
{
ASSERT( pLResult != NULL );
if (m_bAllowDrag )
{
LPDRAGLISTINFO pInfo = (LPDRAGLISTINFO)lParam;
ASSERT( pInfo != NULL );
switch ( pInfo->uNotification )
{
case DL_BEGINDRAG:
TRACE( "Begin Dragging\n" );
// Removed from the MFC implementation
//*pLResult = BeginDrag(pInfo->ptCursor);
*pLResult = TRUE;
break;
case DL_CANCELDRAG:
TRACE( "Cancel Drag\n" );
CancelDrag( pInfo->ptCursor );
break;
case DL_DRAGGING:
TRACE( "Dragging\n" );
*pLResult = Dragging( pInfo->ptCursor );
break;
case DL_DROPPED:
TRACE( "Dropped\n" );
Dropped( GetCurSel(), pInfo->ptCursor );
break;
}
}
else
*pLResult = FALSE;
return TRUE;
}
return CListBox::OnChildNotify( nMessage, wParam, lParam, pLResult );
}
void CListBoxEx::OnKeyDown( UINT nChar, UINT nRepCnt, UINT nFlags )
{
CDragListBox::OnKeyDown( nChar, nRepCnt, nFlags );
if ( (nChar == VK_DELETE) && (m_iSelected != -1) )
DeleteString( m_iSelected );
}
void CListBoxEx::OnSysKeyDown( UINT nChar, UINT nRepCnt, UINT nFlags )
{
if ( ALT_KEY_PRESSED( nFlags ) && m_bAllowDrag )
{
switch ( nChar )
{
case VK_UP:
case VK_LEFT:
MoveItemUp( GetCurSel() );
break;
case VK_DOWN:
case VK_RIGHT:
MoveItemDown( GetCurSel() );
break;
}
}
CDragListBox::OnSysKeyDown( nChar, nRepCnt, nFlags );
}
void CListBoxEx::OnLButtonDown( UINT nFlags, CPoint point )
{
int iItem;
ClientToScreen( &point );
iItem = ItemFromPt( point, FALSE );
TRACE1( "LButtonDown: %d\n", iItem );
if ( iItem != -1 )
{
if ( m_iSelected != iItem )
{
// Update info
m_iSelected = iItem;
}
}
CDragListBox::OnLButtonDown( nFlags, point );
}
void CListBoxEx::OnLButtonDblClk( UINT nFlags, CPoint point )
{
CDragListBox::OnLButtonDblClk( nFlags, point );
}
void CListBoxEx::OnLButtonUp( UINT nFlags, CPoint point )
{
CDragListBox::OnLButtonUp( nFlags, point );
}
// Public functions //
// DESCRIPTION: Set item text and data
void CListBoxEx::SetItem( int iItem, LPCTSTR szItemText, DWORD dwItemData )
{
ASSERT( iItem < GetCount() );
SendMessage( WM_SETREDRAW, FALSE, 0 );
DeleteString( iItem );
InsertString( iItem, szItemText );
SetItemData( iItem, dwItemData );
SendMessage( WM_SETREDRAW, TRUE, 0 );
}
void CListBoxEx::SetItemText( int iItem, LPCTSTR szItemText )
{
ASSERT( iItem < GetCount() );
DWORD dwItemData;
dwItemData = GetItemData( iItem );
SetItem( iItem, szItemText, dwItemData );
}
// DESCRIPTION: Called to swap the two items
void CListBoxEx::SwapItems( int iFirstItem, int iSecondItem )
{
ASSERT( iFirstItem < GetCount() );
ASSERT( iSecondItem < GetCount() );
if ( iFirstItem != iSecondItem )
{
// Cache the first item data
CString strFirstItem;
DWORD dwFirstItemData;
GetText( iFirstItem, strFirstItem );
dwFirstItemData = GetItemData( iFirstItem );
// Cache the second item data
CString strSecondItem;
DWORD dwSecondItemData;
GetText( iSecondItem, strSecondItem );
dwSecondItemData = GetItemData( iSecondItem );
// Insert the items in reverse order
if ( iFirstItem < iSecondItem )
{
SetItem( iFirstItem, strSecondItem, dwSecondItemData );
SetItem( iSecondItem, strFirstItem, dwFirstItemData );
}
else
{
SetItem( iSecondItem, strFirstItem, dwFirstItemData );
SetItem( iFirstItem, strSecondItem, dwSecondItemData );
}
}
}
int CListBoxEx::MoveItemUp( int iItem )
{
ASSERT( iItem > 0 );
if ( iItem > 0 )
{
SwapItems( iItem, iItem-1 );
SetCurSel( iItem - 1 );
}
return iItem;
}
int CListBoxEx::MoveItemDown( int iItem )
{
ASSERT( iItem >= 0 );
if ( iItem != GetCount()-1 )
{
SwapItems( iItem, iItem+1 );
SetCurSel( iItem + 1 );
}
return iItem;
}
// CListBoxExBuddy class //
BEGIN_MESSAGE_MAP(CListBoxExBuddy, CWnd)
//{{AFX_MSG_MAP(CListBoxExBuddy)
ON_WM_PAINT()
ON_WM_MOUSEMOVE()
ON_WM_LBUTTONDOWN()
ON_WM_LBUTTONUP()
ON_WM_SIZE()
ON_WM_NCMOUSEMOVE()
ON_WM_CREATE()
//}}AFX_MSG_MAP
//ON_NOTIFY( TTN_NEEDTEXT, LBB_TTIP_ID, OnTooltipNeedText )
END_MESSAGE_MAP()
CListBoxExBuddy::CListBoxExBuddy()
{
m_ButtonBitmap.LoadBitmap(IDB_BMP_LIST_ICONS);
// Init other data
m_bButtonPressed = FALSE;
m_iButton = __BMP_NUMBTN;
m_pListBoxEx = NULL;
m_arcButtons = new CRect[ __BMP_NUMBTN ];
#ifdef _DEBUG
// Verify the dimensions
BITMAP BmpInfo;
m_ButtonBitmap.GetObject( sizeof(BITMAP), &BmpInfo );
ASSERT( BmpInfo.bmWidth == __BMP_WIDTH );
ASSERT( BmpInfo.bmHeight == __BMP_HEIGHT );
#endif
}
CListBoxExBuddy::~CListBoxExBuddy()
{
delete[] m_arcButtons;
}
// Initialization //
int CListBoxExBuddy::OnCreate( LPCREATESTRUCT lpCreateStruct )
{
if ( CWnd::OnCreate(lpCreateStruct) == -1 )
return -1;
CreateTooltips();
return 0;
}
void CListBoxExBuddy::PreSubclassWindow()
{
CreateTooltips();
// Send a WM_SIZE message, as WM_CREATE would do
CRect rcClient;
GetClientRect( &rcClient );
OnSize( 0, rcClient.Width(), rcClient.Height() );
CWnd::PreSubclassWindow();
}
static void DrawBitmap(CDC & dc,CBitmap & bmp,int x,int y,int cx,int cy)
{
CDC memDC;
memDC.CreateCompatibleDC( &dc );
CBitmap *poldbmp = memDC.SelectObject(&bmp);
dc.BitBlt(x, y, cx, cy, &memDC,0, 0, SRCCOPY);
memDC.SelectObject( poldbmp );
memDC.DeleteDC();
}
void CListBoxExBuddy::OnPaint()
{
CPaintDC dc( this ); // device context for painting
// Create a compatible memory DC
CDC memDC;
memDC.CreateCompatibleDC( &dc );
// Get aware of the size of the client area
CRect rcClient;
GetClientRect( &rcClient );
// This is used to center the button bitmap
int nBmpTopY = (rcClient.Height() - __BMP_HEIGHT) / 2;
// To store old selected objects
CBitmap *pOldBmp;
CFont *pOldFont;
// Select the font
CFont font;
font.Attach( (HFONT)GetStockObject( DEFAULT_GUI_FONT ) );
pOldFont = memDC.SelectObject( &font );
// Select the out-of-screen bitmap
CBitmap memBmp;
memBmp.CreateCompatibleBitmap( &dc,rcClient.Width(),rcClient.Height() );
pOldBmp = memDC.SelectObject( &memBmp );
// Erase the background
CBrush brush;
brush.CreateSolidBrush( ::GetSysColor(COLOR_3DFACE) );
memDC.FillRect( &rcClient, &brush );
brush.DeleteObject();
// Prepare to draw the text transparently
memDC.SetBkMode( TRANSPARENT );
memDC.SetTextColor( ::GetSysColor(COLOR_WINDOWTEXT) );
// Draw the text
CString strWindowText;
GetWindowText( strWindowText );
memDC.DrawText( strWindowText, rcClient, DT_SINGLELINE | DT_VCENTER );
// Draw the button bitmap
DrawBitmap( memDC,m_ButtonBitmap, rcClient.right - __BMP_WIDTH - 2,nBmpTopY,__BMP_WIDTH,__BMP_HEIGHT );
// Draw the button edge
if ( m_iButton != __BMP_NUMBTN)
{
CRect rcButtonEdge( rcClient.right - (__BMP_NUMBTN - m_iButton)*__BMP_BTNWID - 2,
nBmpTopY,
rcClient.right - (__BMP_NUMBTN - m_iButton - 1)*__BMP_BTNWID - 2,
__BMP_HEIGHT + nBmpTopY );
memDC.DrawEdge( &rcButtonEdge, m_bButtonPressed ? BDR_SUNKENOUTER : BDR_RAISEDINNER, BF_RECT );
}
dc.BitBlt(2, 0, rcClient.Width()-2, rcClient.Height(), &memDC, 0, 0, SRCCOPY);
memDC.SelectObject( pOldBmp );
// Select the font out of the device context
memDC.SelectObject( pOldFont );
}
// DESCRIPTION: Finds a button given a point.
int CListBoxExBuddy::FindButton( const CPoint & point )
{
// Find the button
for ( UINT iIndex = 0; iIndex < __BMP_NUMBTN; iIndex++ )
{
if ( m_arcButtons[iIndex].PtInRect( point ) )
break;
}
return iIndex;
}
// DESCRIPTION: Called to redraw a button.
void CListBoxExBuddy::InvalidateButton(int iIndex, BOOL bUpdateWindow /*= TRUE */)
{
if ( iIndex < __BMP_NUMBTN && iIndex !=0)
InvalidateRect( &m_arcButtons[ iIndex ], FALSE );
if ( bUpdateWindow )
UpdateWindow();
}
void CListBoxExBuddy::OnMouseMove( UINT nFlags, CPoint point )
{
if ( !m_bButtonPressed )
{
UINT iIndex = FindButton( point );
// If found a button, update info
if ( iIndex != m_iButton && iIndex !=0)
{
InvalidateButton( m_iButton, FALSE );
m_iButton = iIndex;
InvalidateButton( m_iButton, TRUE );
}
}
CWnd::OnMouseMove(nFlags, point);
}
void CListBoxExBuddy::OnLButtonDown( UINT nFlags,CPoint point )
{
// Capture the mouse
SetCapture();
// Find the button
m_iButton = FindButton( point );
// Redraw the button
if ( m_iButton != __BMP_NUMBTN )
{
m_bButtonPressed = TRUE;
// Redraw only the affected button
InvalidateRect( &m_arcButtons[ m_iButton ], FALSE );
UpdateWindow();
}
CWnd::OnLButtonDown(nFlags, point);
}
void CListBoxExBuddy::OnLButtonUp( UINT nFlags,CPoint point )
{
// Find the button
UINT iButton = FindButton( point );
// Accept only clicks that occur on the same button where the mouse was pressed
if ( iButton == m_iButton )
{
// Take action, if necessary
if ( m_iButton != __BMP_NUMBTN)
DoClick( m_iButton );
}
// Set default conditions
m_bButtonPressed = FALSE;
// Redraw
Invalidate( FALSE );
// Memorize last
m_iButton = iButton;
// Release mouse capture
ReleaseCapture();
// Call base
CWnd::OnLButtonUp(nFlags, point);
}
// DESCRIPTION: Called when a click occurs on one of the action button.
void CListBoxExBuddy::DoClick( int iIndex )
{
int iSelected = m_pListBoxEx->GetCurSel();
switch ( iIndex )
{
case __BTN_DEL:
if ( iSelected != -1 ) m_pListBoxEx->DeleteString( iSelected );
break;
case __BTN_UP:
if ( iSelected != -1 ) m_pListBoxEx->MoveItemUp( iSelected );
break;
case __BTN_DOWN:
if ( iSelected != -1 ) m_pListBoxEx->MoveItemDown( m_pListBoxEx->GetCurSel() );
break;
}
}
void CListBoxExBuddy::OnNcMouseMove( UINT nHitTest, CPoint point )
{
// Redraw the affected button
InvalidateButton( m_iButton, FALSE );
m_iButton = FindButton( point );
InvalidateButton( m_iButton, TRUE );
// Call base
CWnd::OnNcMouseMove(nHitTest, point);
}
// Tooltip Management //
void CListBoxExBuddy::CreateTooltips()
{
m_ToolTip.Create( this );
// Set tip common data
TOOLINFO ttInfo;
ttInfo.cbSize = sizeof( TOOLINFO );
ttInfo.uFlags = TTF_SUBCLASS;
ttInfo.hwnd = m_hWnd;
ttInfo.rect = CRect( 0, 0, 0, 0 ); // OnSize will resize it
ttInfo.hinst = NULL;
ttInfo.lpszText = LPSTR_TEXTCALLBACK;
ttInfo.lParam = 0;
// Add tooltips for each button
for ( int iTip = 0; iTip < __BMP_NUMBTN; iTip++ )
{
ttInfo.uId = iTip+1;
m_ToolTip.SendMessage( TTM_ADDTOOL, 0, (LPARAM)&ttInfo );
m_ToolTip.Activate( TRUE );
}
}
void CListBoxExBuddy::SetTipText( UINT nID, LPTSTR szTipText )
{
TCHAR *aszTips[] = { _T("Delete"),
_T("Move Up"),
_T("Move Down") };
// Set tooltip text
if (nID < __BMP_NUMBTN)
_tcscpy( szTipText, aszTips[ nID ] );
}
void CListBoxExBuddy::OnSize( UINT nType, int cx, int cy )
{
// Get aware of the size of the client area
CRect rcClient;
GetClientRect( &rcClient );
// This is used to center the button bitmap
int nBmpTopY = (rcClient.Height() - __BMP_HEIGHT) / 2;
// Update buttons positions
TOOLINFO ttInfo;
for ( int iIndex = 0; iIndex < __BMP_NUMBTN; iIndex++ )
{
m_arcButtons[ iIndex ].top = nBmpTopY;
m_arcButtons[ iIndex ].left = cx - (__BMP_NUMBTN-iIndex)*__BMP_BTNWID;
m_arcButtons[ iIndex ].bottom = __BMP_HEIGHT + nBmpTopY;
m_arcButtons[ iIndex ].right = cx - (__BMP_NUMBTN-iIndex-1)*__BMP_BTNWID;
// Resize tooltip area
ttInfo.cbSize = sizeof( TOOLINFO );
ttInfo.hwnd = m_hWnd;
ttInfo.uId = iIndex+1;
ttInfo.rect = m_arcButtons[ iIndex ];
m_ToolTip.SendMessage( TTM_NEWTOOLRECT, 0, (LPARAM)&ttInfo );
}
// Call base
CWnd::OnSize( nType, cx, cy );
}
BOOL CListBoxExBuddy::OnNotify( WPARAM wParam, LPARAM lParam, LRESULT *pResult )
{
UINT nCode = ((NMHDR *)lParam)->code;
// Get tooltip notification
if ( nCode == TTN_GETDISPINFO )
{
UINT nID = ((NMHDR *)lParam)->idFrom - 1;
SetTipText( nID,
((NMTTDISPINFO *)lParam)->szText );
return TRUE;
}
// Call base
return CWnd::OnNotify( wParam, lParam, pResult );
}