// ==========================================================================
// Class Implementation : COXEditList
// ==========================================================================
// Source file : editlist.cpp
// Version: 9.3
// This software along with its related components, documentation and files ("The Libraries")
// is � 1994-2007 The Code Project (1612916 Ontario Limited) and use of The Libraries is
// governed by a software license agreement ("Agreement"). Copies of the Agreement are
// available at The Code Project (www.codeproject.com), as part of the package you downloaded
// to obtain this file, or directly from our office. For a copy of the license governing
// this software, you may contact us at legalaffairs@codeproject.com, or by calling 416-849-8900.
// //////////////////////////////////////////////////////////////////////////
#include "stdafx.h" // standard MFC include
#include "OXEditList.h" // class specification
//#include "OXEditListRes.h" // For toolbar
#include "commctrl.h" // For toolbar text handler
#include <afxpriv.h> // for WM_IDLEUPDATECMDUI
#include "UTB64Bit.h"
#ifdef _DEBUG
#undef THIS_FILE
static char BASED_CODE THIS_FILE[] =__FILE__;
static char BASED_CODE _FILE_NAME_[]="OXEditList";
#else
#define _FILE_NAME_ __FILE__
#endif
#define new DEBUG_NEW
// Determine number of elements in an array (not bytes)
#ifndef _countof
#define _countof(array) (sizeof(array)/sizeof(array[0]))
#endif // _countof
/////////////////////////////////////////////////////////////////////////////
// COXEditListHeader
COXEditListHeader::COXEditListHeader()
{
// set default values
VERIFY(SetTextColor());
VERIFY(SetBorderColors());
SetVertOriented(FALSE);
m_sText.Empty();
}
COXEditListHeader::~COXEditListHeader()
{
}
BEGIN_MESSAGE_MAP(COXEditListHeader, CStatic)
//{{AFX_MSG_MAP(COXEditListHeader)
ON_WM_CREATE()
ON_WM_PAINT()
ON_MESSAGE(WM_SETTEXT,OnSetText)
ON_MESSAGE(WM_GETTEXT,OnGetText)
ON_MESSAGE(WM_GETTEXTLENGTH,OnGetTextLength)
ON_WM_ERASEBKGND()
ON_WM_SIZE()
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
/////////////////////////////////////////////////////////////////////////////
// COXEditListHeader message handlers
void COXEditListHeader::PreSubclassWindow()
{
// TODO: Add your specialized code here and/or call the base class
// make sure there wasn't any border styles specified,
// we don't support them
ModifyStyle(WS_BORDER,0);
ModifyStyleEx(WS_EX_CLIENTEDGE|WS_EX_STATICEDGE|
WS_EX_WINDOWEDGE|WS_EX_DLGMODALFRAME,0,SWP_FRAMECHANGED);
/////////////////////////////////
// save the window text
GetWindowText(m_sText);
CStatic::PreSubclassWindow();
}
int COXEditListHeader::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
// make sure there wasn't any border styles specified,
// we don't support them
ASSERT((lpCreateStruct->style&WS_BORDER)==0);
ASSERT((lpCreateStruct->dwExStyle&(WS_EX_CLIENTEDGE|WS_EX_STATICEDGE|
WS_EX_WINDOWEDGE|WS_EX_DLGMODALFRAME))==0);
lpCreateStruct->style&=~WS_BORDER;
lpCreateStruct->dwExStyle&=~(WS_EX_CLIENTEDGE|WS_EX_STATICEDGE|
WS_EX_WINDOWEDGE|WS_EX_DLGMODALFRAME);
/////////////////////////////////
if(CStatic::OnCreate(lpCreateStruct)==-1)
return -1;
// save the window text
m_sText=lpCreateStruct->lpszName;
return 0;
}
void COXEditListHeader::OnPaint()
{
CPaintDC dc(this); // device context for painting
// TODO: Add your message handler code here
CRect rectClient;
GetClientRect(rectClient);
// draw border
dc.Draw3dRect(rectClient,m_clrTopLeft,m_clrBottomRight);
// draw text
//
if(!m_sText.IsEmpty())
{
// setup environment
dc.SetBkMode(TRANSPARENT);
dc.SetTextColor(m_clrText);
CFont* pOldFont=NULL;
CFont fontVert;
CFont* pFont=GetFont();
if(pFont)
{
if(m_bVertOriented)
{
// setup font for vertically oriented mode
LOGFONT lf;
VERIFY(pFont->GetLogFont(&lf));
lf.lfEscapement=900;
lf.lfOrientation=900;
VERIFY(fontVert.CreateFontIndirect(&lf));
pOldFont=dc.SelectObject(&fontVert);
}
else
pOldFont=dc.SelectObject(pFont);
}
CRect rectText(0,0,0,0);
rectClient.DeflateRect(2,2);
dc.IntersectClipRect(rectClient);
rectText=rectClient;
CRect rectHelper;
if(m_bVertOriented)
{
// adjust rectangle to display verical text
rectHelper=rectText;
rectText.top=rectHelper.left;
rectText.bottom=rectHelper.right;
rectText.left=rectHelper.top;
rectText.right=rectHelper.bottom;
}
// calculate the rect to display text in
UINT nFormat=DT_LEFT|DT_SINGLELINE;
dc.DrawText(m_sText,&rectText,nFormat|DT_CALCRECT);
rectHelper=rectText;
if(m_bVertOriented)
{
rectText.top=rectHelper.left;
rectText.bottom=rectHelper.right;
rectText.left=rectHelper.top;
rectText.right=rectHelper.bottom;
rectHelper=rectText;
}
// adjust coordinates
if(m_bVertOriented)
{
rectText.left+=(rectClient.Width()-rectHelper.Width())/2+
(rectClient.Width()-rectHelper.Width())%2;
rectText.right=rectText.left+rectHelper.Width();
}
else
{
rectText.top+=(rectClient.Height()-rectHelper.Height())/2+
(rectClient.Height()-rectHelper.Height())%2;
rectText.bottom=rectText.top+rectHelper.Height();
}
// adjust the text box depending on text alignment style
DWORD dwStyle=GetStyle()&0x0000000f;
switch(dwStyle)
{
// SS_CENTER
case 1:
if(m_bVertOriented)
{
rectText.top+=(rectClient.Height()-rectHelper.Height())/2+
(rectClient.Height()-rectHelper.Height())%2;
rectText.bottom=rectText.top+rectHelper.Height();
}
else
{
rectText.left+=(rectClient.Width()-rectHelper.Width())/2;
rectText.right=rectText.left+rectHelper.Width();
}
break;
// SS_RIGHT
case 2:
if(m_bVertOriented)
{
rectText.bottom=rectClient.bottom;
rectText.top=rectText.bottom-rectHelper.Height();
}
else
{
rectText.right=rectClient.right;
rectText.left=rectText.right-rectHelper.Width();
}
break;
// SS_LEFT
default:
if(m_bVertOriented)
{
rectText.top=rectClient.top;
rectText.bottom=rectText.top+rectHelper.Height();
}
else
{
rectText.left=rectClient.left;
rectText.right=rectText.left+rectHelper.Width();
}
}
rectHelper=rectText;
if(m_bVertOriented)
{
rectHelper.bottom+=rectText.Width();
rectHelper.right+=rectText.Height();
}
// draw text
dc.DrawText(m_sText,&rectHelper,DT_BOTTOM|DT_LEFT|DT_SINGLELINE);
if(pOldFont!=NULL)
dc.SelectObject(pOldFont);
}
// Do not call CStatic::OnPaint() for painting messages
}
// v9.3 - update 03 - 64-bit - changed these to LRESULT, WPARAM, LPARAM from LONG, UINT, LONG_PTR
LRESULT COXEditListHeader::OnSetText(WPARAM wParam, LPARAM lParam)
{
UNREFERENCED_PARAMETER(wParam);
// save the window text
m_sText=(LPCTSTR)lParam;
RedrawWindow();
return TRUE;
}
// v9.3 - update 03 - 64-bit - changed these to LRESULT, WPARAM, LPARAM from LONG, UINT, LONG
LRESULT COXEditListHeader::OnGetText(WPARAM wParam, LPARAM lParam)
{
if(wParam>0)
{
wParam=wParam>(UINT)m_sText.GetLength()+1 ?
m_sText.GetLength()+1 : wParam;
VERIFY(lstrcpyn((LPTSTR)(LONG_PTR)lParam,
m_sText,wParam-1)!=NULL);
}
return (LONG)wParam;
}
// v9.3 - update 03 - 64-bit - changed these to LRESULT, WPARAM, LPARAM from LONG, UINT, LONG
LRESULT COXEditListHeader::OnGetTextLength(WPARAM wParam, LPARAM lParam)
{
UNREFERENCED_PARAMETER(wParam);
UNREFERENCED_PARAMETER(lParam);
return (LONG)m_sText.GetLength();
}
BOOL COXEditListHeader::OnEraseBkgnd(CDC* pDC)
{
// TODO: Add your message handler code here and/or call default
// provide background filling (we don't want it to be transparent!!!)
CRect rectClient;
GetClientRect(rectClient);
pDC->FillSolidRect(rectClient,::GetSysColor(COLOR_BTNFACE));
return TRUE;
}
void COXEditListHeader::OnSize(UINT nType, int cx, int cy)
{
CStatic::OnSize(nType, cx, cy);
// TODO: Add your message handler code here
// redraw the control while resizing it
RedrawWindow();
}
/////////////////////////////////////////////////////////////////////////////
// Definition of static members
COXEditList::EToolbarPosition COXEditList::TBP_FIRST=COXEditList::TBPNone;
COXEditList::EToolbarPosition COXEditList::TBP_LAST=COXEditList::TBPVerticalRightBottom;
///////////////////////////////////////////////////////////////////
// Data members -------------------------------------------------------------
// protected:
// EToolbarPosition m_eToolbarPosition;
// --- The current position of the toolbar seen from this control
// BOOL m_bAllowDuplicates;
// --- Whether entries with the same name are allowed in the list
// (When not the user will be warned by a message box when he
// tries to add an already existing name
// BOOL m_bOrderedList;
// --- Whether the user can move an item up and down (TRUE) or not (FALSE)
// BOOL m_bIsDeleting
// --- Whether the control is in a delete process or not
// CToolBar m_toolbar;
// --- The toolbar window
// This window iss a sibling of this control, but this control is its owner
// This way it receives important messages from the toolbar
// BOOL m_bPostInitialized;
// --- Whether the function PostInit has been executed
// CString m_sOriginalEditText;
// --- The content of the label when the user started editing it
// CString m_sDuplicateErrorMsg;
// --- The error text that should be shown when a duplicate name is
// detected and is not allowed (m_bAllowDuplicates==FALSE)
// int m_nNewImageItem;
// --- The image index that will be assgined to a new item
// private:
// Member functions ---------------------------------------------------------
// public:
BEGIN_MESSAGE_MAP(COXEditList, COXGridList)
//{{AFX_MSG_MAP(COXEditList)
ON_NOTIFY_REFLECT_EX(LVN_ITEMCHANGED, OnItemChanged)
ON_WM_KEYDOWN()
ON_NOTIFY_REFLECT_EX(LVN_BEGINLABELEDIT, OnBeginlabeledit)
ON_NOTIFY_REFLECT_EX(LVN_ENDLABELEDIT, OnEndlabeledit)
ON_COMMAND(ID_OX_EDITLIST_NEW, OnNewItem)
ON_COMMAND(ID_OX_EDITLIST_DELETE, OnDeleteItem)
ON_UPDATE_COMMAND_UI(ID_OX_EDITLIST_DELETE, OnUpdateDeleteItem)
ON_COMMAND(ID_OX_EDITLIST_UP, OnMoveItemUp)
ON_UPDATE_COMMAND_UI(ID_OX_EDITLIST_UP, OnUpdateMoveItemUp)
ON_COMMAND(ID_OX_EDITLIST_DOWN, OnMoveItemDown)
ON_UPDATE_COMMAND_UI(ID_OX_EDITLIST_DOWN, OnUpdateMoveItemDown)
ON_MESSAGE(WM_IDLEUPDATECMDUI, OnIdleUpdateCmdUI)
ON_WM_SYSKEYDOWN()
ON_NOTIFY_EX_RANGE(TTN_NEEDTEXT, 0, 0xFFFF, OnToolTipText)
ON_WM_WINDOWPOSCHANGED()
//}}AFX_MSG_MAP
ON_MESSAGE(LVM_INSERTITEM, OnInsertItem)
ON_MESSAGE(LVM_DELETEITEM, OnDeleteItem)
ON_MESSAGE(LVM_DELETEALLITEMS, OnDeleteAllItems)
ON_MESSAGE(WM_POST_INIT, OnPostInit)
END_MESSAGE_MAP()
COXEditList::COXEditList(EToolbarPosition eToolbarPosition /*=TBPHorizontalTopRight*/,
BOOL bAllowDuplicates /*=FALSE*/, BOOL bOrderedList /*=TRUE*/)
:
m_eToolbarPosition(eToolbarPosition),
m_bAllowDuplicates(bAllowDuplicates),
m_bOrderedList(bOrderedList),
m_bPostInitialized(FALSE),
m_sOriginalEditText(),
m_nNewImageItem(1),
m_sHeaderText(_T("")),
m_bEditable(TRUE)
{
// ... eToolbarPosition must be valid
ASSERT(TBP_FIRST <= eToolbarPosition);
ASSERT(eToolbarPosition <= TBP_LAST);
// ... Use a default error message
VERIFY(m_sDuplicateErrorMsg.LoadString(IDS_OX_EDITLISTDUPLERROR)); //"This item is already part of the list, please use another name"
ASSERT_VALID(this);
}
void COXEditList::InitGrid()
{
// First call bass class implementation
COXGridList::InitGrid();
// Create a header as sibling of this control
VERIFY(m_header.Create(NULL,WS_CHILD,CRect(0,0,0,0),GetParent()));
static CFont fontVertEnabled;
if((HFONT)fontVertEnabled==NULL)
VERIFY(fontVertEnabled.CreatePointFont(100,_T("Arial")));
m_header.SetFont(&fontVertEnabled);
// Create a toolbar as sibling of this control
// ... Invisible by default, we will show it when the correct dimensions are knwon
// in PositionToolbar
VERIFY(m_toolbar.Create(&m_header,WS_CHILD|CBRS_TOOLTIPS));
// ... Must find the resource
// (Make sure OXEdit.rc is included in your resource file)
ASSERT(AfxFindResourceHandle(MAKEINTRESOURCE(IDR_OX_EDITLIST_TOOLS), RT_TOOLBAR) != NULL);
VERIFY(m_toolbar.LoadToolBar(IDR_OX_EDITLIST_TOOLS));
m_toolbar.SetOwner(this);
if (!m_bOrderedList)
{
// Not an ordered list, remove the move up and down buttons
SetButtonCount(m_toolbar, 2);
}
#if _MFC_VER>0x0421
m_toolbar.SetBorders(0,0,0,0);
#else
m_toolbar.m_cxLeftBorder=m_toolbar.m_cxRightBorder=
m_toolbar.m_cyTopBorder=m_toolbar.m_cyBottomBorder=0;
#endif
// Move the toolbar to the right position
PositionToolbar(m_eToolbarPosition);
// Because subclassing is not finished yet
// and so our message handlers are not functional
// we defer some initialization to later
VERIFY(m_bPostInitialized==FALSE);
PostMessage(WM_POST_INIT);
}
void COXEditList::DrawItem(LPDRAWITEMSTRUCT lpDIS)
{
ASSERT_VALID(this);
// First call bass class implementation
COXGridList::DrawItem(lpDIS);
// Draw an extra recatangle on top of the last (empty) item
if((int)lpDIS->itemID==GetItemCount()-1 && IsWindowEnabled())
{
// We draw a rectangle of half the width of the item
CRect rect(lpDIS->rcItem);
if (m_nImageColumn==0)
// ... Images in first column : move rectangle
rect.left += GetImagesWidth();
rect.right=rect.left + rect.Width() / 2;
rect.InflateRect(-1, -1);
CDC* pDC=CDC::FromHandle(lpDIS->hDC);
pDC->DrawFocusRect(rect);
}
}
void COXEditList::SetDuplicateErrorMsg(LPCTSTR pszDuplicateErrorMsg)
{
m_sDuplicateErrorMsg=pszDuplicateErrorMsg;
}
BOOL COXEditList::EditNewItem()
{
int nLastItemIndex=GetItemCount() - 1;
// ... Last item should have an empty label
ASSERT(GetItemText(nLastItemIndex, 0).IsEmpty());
// Just start editing the last item
SetFocus();
EnsureVisible(nLastItemIndex, 0, FALSE);
return (EditLabel(nLastItemIndex, 0) != NULL);
}
BOOL COXEditList::CanDelete() const
{
// Check whether any item is selected (apart from the last one)
int nSelectedIndex=GetCurSel();
return (0 <= nSelectedIndex) && (nSelectedIndex < GetItemCount() - 1);
}
BOOL COXEditList::DeleteSelectedItems()
{
// Should not call this function when it is of no use
// GUI should be disabled when necessary
if (!CanDelete())
{
TRACE0("COXEditList::DeleteSelectedItems called when NOT CanDelete()\n");
return FALSE;
}
BOOL bSuccess=TRUE;
// ... Store current focus item
int nFocusItem=GetCurFocus();
// Delete all the selected items
// This might also include the empty (last) item, which deletion will fail
int nItemIndex=-1;
// while calling deleteItem an OnChangeState is called, this function is not permitted
// to set the focus again a that time, so exclude this behaviour
m_bIsDeleting=TRUE;
while (bSuccess &&
(nItemIndex=GetNextItem(nItemIndex, LVNI_SELECTED)) != -1)
{
// ... Delete may fail for the empty item
bSuccess=DeleteItem(nItemIndex);
// ... Requery this index again,
// because now this index is used by the next item
nItemIndex--;
}
// Reset the default OnChangeState behaviour
m_bIsDeleting=FALSE;
// ... Restore current focus item (by first removing the focus, like this we are
// sure that OnChangeState will be called to set the selection also)
if (SetCurFocus(nFocusItem, FALSE))
SetCurFocus(nFocusItem);
else
{
// ... If it failed put focus on the last item
// Remove focus
SetCurFocus(GetItemCount() - 1, FALSE);
// Set focus
SetCurFocus(GetItemCount() - 1);
}
// If we tried to delete the last item, everything succeeded
// otherwise bSuccess contains the correct value
return (nItemIndex==GetItemCount() - 2 ? TRUE : bSuccess);
}
BOOL COXEditList::CanMoveUp() const
{
// prevent user's ability to move items while in edit mode
if ( GetEditControl() != NULL )
return FALSE;
// Check whether selected item is not the first item and not the last (empty) item
int nFocusIndex=GetCurFocus();
return m_bOrderedList && (0 < nFocusIndex) && (nFocusIndex < GetItemCount() - 1);
}
BOOL COXEditList::MoveItemUp()
{
// Should not call this function when it is of no use
// GUI should be disabled when necessary
if (!CanMoveUp())
{
TRACE0("COXEditList::MoveItemUp called when NOT CanMoveUp()\n");
return FALSE;
}
int nFocusIndex=GetCurFocus();
if (SwapItems(nFocusIndex - 1, nFocusIndex))
{
EnsureVisible(nFocusIndex - 1, 0, TRUE);
return TRUE;
}
else
return FALSE;
}
BOOL COXEditList::CanMoveDown() const
{
// prevent user's ability to move items while in edit mode
if ( GetEditControl() != NULL )
return FALSE;
// Check whether selected item is not the last (empty) item or that before the last
int nFocusIndex=GetCurFocus();
return m_bOrderedList && (0 <= nFocusIndex) &&
(nFocusIndex < GetItemCount() - 2);
}
BOOL COXEditList::MoveItemDown()
{
// Should not call this function when it is of no use
// GUI should be disabled when necessary
if (!CanMoveDown())
{
TRACE0("COXEditList::MoveItemDown called when NOT CanMoveDown()\n");
return FALSE;
}
int nFocusIndex=GetCurFocus();
if (SwapItems(nFocusIndex, nFocusIndex + 1))
{
EnsureVisible(nFocusIndex + 1, 0, TRUE);
return TRUE;
}
else
return FALSE;
}
BOOL COXEditList::SwapItems(int nIndex1, int nIndex2)
{
LV_ITEM lvi1;
LV_ITEM lvi2;
memset(&lvi1, 0, sizeof(lvi1));
memset(&lvi2, 0, sizeof(lvi2));
// Initialize the requested struct for both items
// ... Get everything apart from the text (which we will get seperately)
lvi1.mask=LVIF_IMAGE | LVIF_PARAM | LVIF_STATE;
lvi1.iItem=nIndex1;
lvi1.iSubItem=0;
lvi1.stateMask=0xFFFFFFFF;
// ... Get everything apart from the text (which we will get seperately)
lvi2.mask=LVIF_IMAGE | LVIF_PARAM | LVIF_STATE;
lvi2.iItem=nIndex2;
lvi2.iSubItem=0;
lvi2.stateMask=0xFFFFFFFF;
if (!GetItem(&lvi1) || !GetItem(&lvi2))
{
TRACE2("COXEditList::SwapItems : Failed to get the item %i or %i\n", nIndex1, nIndex2);
return FALSE;
}
// We got the data, now lets swap them
lvi1.iItem=nIndex2;
lvi2.iItem=nIndex1;
if (!SetItem(&lvi1) || !SetItem(&lvi2))
{
TRACE2("COXEditList::SwapItems : Failed to set the item %i or %i\n", nIndex1, nIndex2);
return FALSE;
}
// Now we will swap the text of the label and of each subitem
CString sText1;
CString sText2;
for (int nSubIndex=0; nSubIndex < GetNumCols(); nSubIndex++)
{
sText1=GetItemText(nIndex1, nSubIndex);
sText2=GetItemText(nIndex2, nSubIndex);
if (!SetItemText(nIndex2, nSubIndex, sText1) || !SetItemText(nIndex1, nSubIndex, sText2))
{
TRACE2("COXEditList::SwapItems : Failed to set the item text of %i or %i\n", nIndex1, nIndex2);
return FALSE;
}
}
// If we arrive here everything has gone OK
return TRUE;
}
void COXEditList::PositionToolbar(EToolbarPosition eToolbarPosition/*=TBPHorizontalTopRight*/)
{
ASSERT(TBP_FIRST <= eToolbarPosition);
ASSERT(eToolbarPosition <= TBP_LAST);
// Store new setting
m_eToolbarPosition=eToolbarPosition;
m_header.ShowWindow((eToolbarPosition==TBPNone) ? SW_HIDE : SW_SHOW);
m_toolbar.ShowWindow((eToolbarPosition==TBPNone) ? SW_HIDE : SW_SHOW);
if(eToolbarPosition==TBPNone)
{
// Invisible toolbar : nothing more to do
ASSERT_VALID(this);
return;
}
CRect rect;
CRect rectToolbar(0,0,0,0);
CRect rectHeader(0,0,0,0);
CSize toolSize;
CSize offsetHeader;
CSize offsetToolbar;
GetWindowRect(rect);
GetParent()->ScreenToClient(rect);
// First calculate the size of the toolbar
BOOL bHorizontal=(eToolbarPosition==TBPHorizontalTopLeft) ||
(eToolbarPosition==TBPHorizontalTopCenter) ||
(eToolbarPosition==TBPHorizontalTopRight) ||
(eToolbarPosition==TBPHorizontalBottomLeft) ||
(eToolbarPosition==TBPHorizontalBottomCenter) ||
(eToolbarPosition==TBPHorizontalBottomRight);
// set the orientation of header control
m_header.SetVertOriented(!bHorizontal);
switch(eToolbarPosition)
{
// Horizontal
case TBPHorizontalTopLeft:
case TBPHorizontalBottomLeft:
case TBPVerticalLeftTop:
case TBPVerticalRightTop:
m_header.ModifyStyle(NULL,SS_RIGHT);
break;
case TBPHorizontalTopCenter:
case TBPHorizontalTopRight:
case TBPHorizontalBottomCenter:
case TBPHorizontalBottomRight:
case TBPVerticalLeftCenter:
case TBPVerticalLeftBottom:
case TBPVerticalRightCenter:
case TBPVerticalRightBottom:
m_header.ModifyStyle(SS_RIGHT,NULL);
break;
default:
TRACE1("COXEditList::PositionToolbar : Unexpected case in switch (%i)\n", eToolbarPosition);
ASSERT(FALSE);
break;
}
// ... Change bar style to reflect the correct orientation
DWORD nOldStyle=m_toolbar.GetBarStyle();
m_toolbar.SetBarStyle(bHorizontal ? (nOldStyle | CBRS_ORIENT_HORZ) :
(nOldStyle & ~CBRS_ORIENT_HORZ));
// ... Get the bar size
toolSize=m_toolbar.CalcFixedLayout(FALSE,bHorizontal);
// adjust toolbar rect
rectToolbar.right=rectToolbar.left + toolSize.cx;
rectToolbar.bottom=rectToolbar.top + toolSize.cy;
// ... Get the border size of the toolbar
// If the bar is vertical m_cxLeftBorder is used to adjust the top (cy) and
// m_cxRightBorder is used to adjust the bottom (cy) !
int nLeftBorder=1;
int nRightBorder=1;
int nTopBorder=1;
int nBottomBorder=1;
// adjust header rect
if(bHorizontal)
{
rectHeader.top-=nTopBorder;
rectHeader.right=rectHeader.left+rect.Width();
rectHeader.bottom=rectHeader.top+toolSize.cy+nTopBorder+nBottomBorder;
}
else
{
rectHeader.left-=nTopBorder;
rectHeader.bottom=rectHeader.left+rect.Height();
rectHeader.right=rectHeader.left+toolSize.cx+nLeftBorder+nRightBorder;
}
// Then adjust the position of the header and toolbar
// ... Handle all possible cases for header
switch(eToolbarPosition)
{
// Horizontal
case TBPHorizontalTopLeft:
case TBPHorizontalTopCenter:
case TBPHorizontalTopRight:
offsetHeader.cx=rect.left;
offsetHeader.cy=rect.top - rectHeader.Height();
break;
case TBPHorizontalBottomLeft:
case TBPHorizontalBottomCenter:
case TBPHorizontalBottomRight:
offsetHeader.cx=rect.left;
offsetHeader.cy=rect.bottom+nTopBorder+nBottomBorder;
break;
// Vertical
case TBPVerticalLeftTop:
case TBPVerticalLeftCenter:
case TBPVerticalLeftBottom:
offsetHeader.cx=rect.left-rectHeader.Width();
offsetHeader.cy=rect.top;
break;
case TBPVerticalRightTop:
case TBPVerticalRightCenter:
case TBPVerticalRightBottom:
// ... Right (Horizontal)
offsetHeader.cx=rect.right+nLeftBorder+nRightBorder;
offsetHeader.cy=rect.top;
break;
default:
TRACE1("COXEditList::PositionToolbar : Unexpected case in switch (%i)\n", eToolbarPosition);
ASSERT(FALSE);
break;
}
// ... Handle all possible cases for toolbar
switch (eToolbarPosition)
{
// Horizontal
case TBPHorizontalTopLeft:
case TBPHorizontalBottomLeft:
offsetToolbar.cx=nLeftBorder;
offsetToolbar.cy=nTopBorder;
break;
case TBPHorizontalTopCenter:
case TBPHorizontalBottomCenter:
offsetToolbar.cx=((rectHeader.Width()>rectToolbar.Width()+
nLeftBorder+nRightBorder) ?
(rectHeader.Width()-rectToolbar.Width())/2 : nLeftBorder);
offsetToolbar.cy=nTopBorder;
break;
case TBPHorizontalTopRight:
case TBPHorizontalBottomRight:
offsetToolbar.cx=rectHeader.Width()-rectToolbar.Width()-nRightBorder;
offsetToolbar.cy=nTopBorder;
break;
// Vertical
case TBPVerticalLeftTop:
case TBPVerticalRightTop:
offsetToolbar.cy=nTopBorder;
offsetToolbar.cx=nLeftBorder;
break;
case TBPVerticalLeftCenter:
case TBPVerticalRightCenter:
offsetToolbar.cy=((rectHeader.Height()>rectToolbar.Height()+
nTopBorder+nBottomBorder) ?
(rectHeader.Height()-rectToolbar.Height())/2 : nTopBorder);
offsetToolbar.cx=nLeftBorder;
break;
case TBPVerticalLeftBottom:
case TBPVerticalRightBottom:
offsetToolbar.cy=rectHeader.Height()-rectToolbar.Height()-nBottomBorder;
offsetToolbar.cx=nLeftBorder;
break;
default:
TRACE1("COXEditList::PositionToolbar : Unexpected case in switch (%i)\n", eToolbarPosition);
ASSERT(FALSE);
break;
}
rectHeader+=offsetHeader;
rectToolbar+=offsetToolbar;
// Reposition the header
m_header.MoveWindow(rectHeader);
m_header.Invalidate();
// Reposition the toolbar
m_toolbar.MoveWindow(rectToolbar);
// ... If already visible : redraw
m_toolbar.Invalidate();
ASSERT_VALID(this);
}
COXEditList::EToolbarPosition COXEditList::GetToolbarPosition() const
{
ASSERT_VALID(this);
return m_eToolbarPosition;
}
BOOL COXEditList::OnIdle(LONG lCount /*=0 */)
{
// Update the state of the GUI now
// ... We only have to update the first time the idle loop is executed
if (lCount==0)
{
SendMessage(WM_IDLEUPDATECMDUI, (WPARAM)TRUE /* bDisableIfNoHandler */, 0);
}
// ... We do not need more idle processing
return FALSE;
}
BOOL COXEditList::SortColumn(int nColumn /*=0 */)
{
ASSERT((0 <= nColumn) && (nColumn < GetNumCols()));
COXGridListSortInfo gridListSortInfo(this, nColumn);
return SortItems(EditListCompareProc, (LPARAM)(&gridListSortInfo));
}
#ifdef _DEBUG
void COXEditList::AssertValid() const
{
COXGridList::AssertValid();
ASSERT(TBP_FIRST <= m_eToolbarPosition);
ASSERT(m_eToolbarPosition <= TBP_LAST);
}
void COXEditList::Dump(CDumpContext& dc) const
{
COXGridList::Dump(dc);
dc << "\nm_eToolbarPosition : " << (LONG)m_eToolbarPosition;
dc << "\nm_bAllowDuplicates : " << m_bAllowDuplicates;
dc << "\nm_bOrderedList : " << m_bOrderedList;
dc << "\nm_toolbar : " << m_toolbar;
dc << "\nm_bPostInitialized : " << m_bPostInitialized;
dc << "\nm_sOriginalEditText : " << m_sOriginalEditText;
dc << "\nm_sDuplicateErrorMsg : " << m_sDuplicateErrorMsg;
dc << "\nm_nNewImageItem : " << m_nNewImageItem;
dc << "\n";
}
#endif //_DEBUG
COXEditList::~COXEditList()
{
ASSERT_VALID(this);
}
BOOL COXEditList::IsFrameWnd() const
// --- In :
// --- Out :
// --- Returns : Whether this window is a frame window
// --- Effect :
{
// Mimic a frame window to get the idle Update CmdUI messages from the toolbar
return TRUE;
}
int COXEditList::GetImagesWidth()
// --- In :
// --- Out :
// --- Returns : The width in pixels of the images in the first column
// (small image and state image)
// --- Effect :
{
if (GetImageColumn() != 0)
// First column does not contain any images
return 0;
// Only small images can be shown in report mode
CImageList* pSmallImageList=GetImageList(LVSIL_SMALL);
CImageList* pStateImageList=GetImageList(LVSIL_STATE);
LV_ITEM lvI;
lvI.mask=LVIF_IMAGE | LVIF_STATE;
lvI.iItem=0;
lvI.iSubItem =0;
lvI.state=0;
lvI.stateMask=ALLSTATEIMAGEMASKS;
CSize stateImageSize(0,0);
CSize smallImageSize(0,0);
// Query the image and state this item is linked with
if (GetItem(&lvI))
{
// We need the width of the images. It will be used to determine from
// where we can begin writing the text
IMAGEINFO imageInfo;
// First get the size of the small image and the state image
// ... Convert from one-based to zero-based index
int nStateIndex=STATEIMAGEMASKTOINDEX(lvI.state) - 1;
if ((pStateImageList != NULL) && (0 <= nStateIndex))
{
pStateImageList->GetImageInfo(nStateIndex, &imageInfo);
stateImageSize=CRect(imageInfo.rcImage).Size();
}
if (pSmallImageList != NULL)
{
pSmallImageList->GetImageInfo(lvI.iImage, &imageInfo);
smallImageSize=CRect(imageInfo.rcImage).Size();
}
}
return stateImageSize.cx + smallImageSize.cx;
}
void COXEditList::AdjustEmptyChanged()
// --- In :
// --- Out :
// --- Returns :
// --- Effect : Adjust the control after the the last (empty) item has
// been filled out
{
int nLastItemIndex=GetItemCount() - 1;
ASSERT(!GetItemText(nLastItemIndex, 0).IsEmpty());
// Change the image of this item from 0 (none) to m_nNewImageItem if that image exists
IMAGEINFO imageInfo;
CImageList* pSmallImageList=GetImageList(LVSIL_SMALL);
if ((pSmallImageList != NULL) && (pSmallImageList->GetImageInfo(m_nNewImageItem, &imageInfo)))
{
LV_ITEM lvi;
memset(&lvi, 0, sizeof(lvi));
lvi.iItem=nLastItemIndex;
lvi.mask=LVIF_IMAGE;
lvi.iImage=m_nNewImageItem;
VERIFY(SetItem(&lvi));
}
// ... Remove selection of item
VERIFY(SetItemState(nLastItemIndex, 0, LVIS_SELECTED));
// Last item has been filled out, add a new empty one
VERIFY(InsertItem(nLastItemIndex + 1, _T(""), 0)==nLastItemIndex + 1);
// ... Give it focus
VERIFY(SetItemState(nLastItemIndex + 1, LVIS_FOCUSED, LVIS_FOCUSED));
// If the new item is not visible, make it visible
if (GetTopIndex() + GetCountPerPage() - 1 < nLastItemIndex + 1)
{
VERIFY(EnsureVisible(nLastItemIndex + 1, 0, FALSE));
// Because the window scrolled up by making the new item visible
// we need to invalidate an extra region above the edit box
// (bacause part of edit box has scrolled up as well)
CRect editRect;
VERIFY(GetItemRect(nLastItemIndex + 1,&editRect,LVIR_BOUNDS));
//ASSERT_VALID(GetEditControl());
//GetEditControl()->GetWindowRect(editRect);
ScreenToClient(editRect);
editRect.top -= editRect.Height();
InvalidateRect(editRect);
}
}
BOOL COXEditList::SetButtonCount(CToolBar& toolbar, int nCount)
// --- In : toolbar : The toolbar to use
// nCount : The number of buttons that have to remain
// --- Out :
// --- Returns : Whether it succeeded or not
// --- Effect : Removes some of the buttons of the toolbar
// This function will never add new buttons
{
if (toolbar.GetCount() < nCount)
{
TRACE0("COXEditList::SetButtonCount : Cannot be used to add new buttons\n");
return FALSE;
}
// Get the present buttons
UINT* rgID=new UINT[nCount];
// ... We are not interested in the style and image, but we have to provide a parameter
UINT nStyle;
int nImage;
for (int nIndex=0; nIndex < nCount; nIndex++)
{
toolbar.GetButtonInfo(nIndex, rgID[nIndex], nStyle, nImage);
}
// Set the new buttons
BOOL bSuccess=toolbar.SetButtons(rgID, nCount);
if (!bSuccess)
TRACE0("COXEditList::SetButtonCount : Failed to set the new buttons\n");
// ... Clean up array of IDs
delete[] rgID;
return bSuccess;
}
int CALLBACK COXEditList::EditListCompareProc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
// --- In : lParam1 : lParam of the first item to compare
// lParam2 : lParam of the second item to compare
// lParamSort : parem of the sort object (COXGridListSortInfo)
// --- Out :
// --- Returns : A negative value if the first item should precede the second,
// a positive value if the first item should follow the second,
// or zero if the two items are equivalent.
// --- Effect : Compares two items of the control
// The COXGridListSortInfo contains information about which subitem to use
{
#if defined (_WINDLL)
#if defined (_AFXDLL)
AFX_MANAGE_STATE(AfxGetAppModuleState());
#else
AFX_MANAGE_STATE(AfxGetStaticModuleState());
#endif
#endif
COXGridListSortInfo* pGridListSortInfo=(COXGridListSortInfo*)lParamSort;
ASSERT(pGridListSortInfo != NULL);
ASSERT(AfxIsValidAddress(pGridListSortInfo, sizeof(COXGridListSortInfo)));
int nIndex1, nIndex2;
CString sItemText1, sItemText2;
// Lookup the first and second Item
nIndex1= PtrToInt(pGridListSortInfo->m_pThis->FindOriginalItemData(lParam1));
nIndex2= PtrToInt(pGridListSortInfo->m_pThis->FindOriginalItemData(lParam2));
// Make sure the last (empty) row stays always last
int iResult;
int nLastItem=pGridListSortInfo->m_pThis->GetItemCount() - 1;
if (nIndex1==nLastItem)
iResult=1;
else if (nIndex2==nLastItem)
iResult=-1;
else
{
// query the text
sItemText1=pGridListSortInfo->m_pThis->GetItemText(nIndex1, pGridListSortInfo->m_nSubIndex);
sItemText2=pGridListSortInfo->m_pThis->GetItemText(nIndex2, pGridListSortInfo->m_nSubIndex);
// Compare both string on a No Case basis
iResult=sItemText1.CompareNoCase(sItemText2);
}
return(iResult);
}
BOOL COXEditList::OnItemChanged(NMHDR* pNMHDR, LRESULT* pResult)
{
NM_LISTVIEW* pNMListView=(NM_LISTVIEW*)pNMHDR;
*pResult=0;
// If this item changed state and has focus and is not yet selected, we select it
if (!m_bIsDeleting &&
((pNMListView->uChanged & LVIF_STATE)==LVIF_STATE) &&
((pNMListView->uNewState & LVIS_FOCUSED)==LVIS_FOCUSED) &&
((pNMListView->uNewState & LVIS_SELECTED) != LVIS_SELECTED) )
{
SetCurSel(pNMListView->iItem);
}
if ((pNMListView->uChanged & LVIF_TEXT)==LVIF_TEXT)
{
if (pNMListView->iItem < GetItemCount() - 1)
{
if (GetItemText(pNMListView->iItem, 0).IsEmpty())
{
// If the text of this item changed to an empty text and
// it is not the last item : delete it
VERIFY(DeleteItem(pNMListView->iItem));
VERIFY(SetItemState(pNMListView->iItem, LVIS_FOCUSED, LVIS_FOCUSED));
}
}
else
{
if (!GetItemText(pNMListView->iItem, 0).IsEmpty())
{
// If the text of the last item was filled out, add a new empty item
ASSERT(pNMListView->iItem==GetItemCount() - 1);
// ... SSet image of new item and add a new empty item
AdjustEmptyChanged();
}
}
}
// Call the base class implementation
return COXGridList::OnListCtrlNotify(pNMHDR, pResult);
}
void COXEditList::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
{
COXGridList::OnKeyDown(nChar, nRepCnt, nFlags);
if((nChar==VK_DELETE) && CanDelete())
{
DeleteSelectedItems();
}
}
void COXEditList::OnSysKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
{
// ... Call base class implementation
COXGridList::OnSysKeyDown(nChar, nRepCnt, nFlags);
// Move item when Alt + arrow Up/Down was pressed
if((nChar==VK_UP) && CanMoveUp())
{
MoveItemUp();
}
else if((nChar==VK_DOWN) && CanMoveDown())
{
MoveItemDown();
}
}
BOOL COXEditList::OnBeginlabeledit(NMHDR* pNMHDR, LRESULT* pResult)
{
// First call the base class implementation (TRUE=eaten)
if (COXGridList::OnBeginlabeledit(pNMHDR, pResult))
return TRUE;
LV_DISPINFO* pDispInfo=(LV_DISPINFO*)pNMHDR;
// ... Never eat this message
BOOL bEaten=FALSE;
int nItem=pDispInfo->item.iItem;
// Store the old text of the item so we can restore it if necessary
m_sOriginalEditText=GetItemText(nItem,0);
// set internal edit control to use whole cient area
COXGridEdit* pGridEdit=GetGridEditCtrl();
ASSERT(pGridEdit!=NULL);
pGridEdit->SetFitToClient(TRUE);
// Do not change the result (already filled out by the base class)
// *pResult;
return bEaten;
}
BOOL COXEditList::OnEndlabeledit(NMHDR* pNMHDR, LRESULT* pResult)
{
// First call the base class implementation (TRUE=eaten)
if (COXGridList::OnEndlabeledit(pNMHDR, pResult))
return TRUE;
LV_DISPINFO* pDispInfo=(LV_DISPINFO*)pNMHDR;
// ... Never eat this message
BOOL bEaten=FALSE;
// Check whether the user cancelled editing
if ((pDispInfo->item.pszText==NULL) || (pDispInfo->item.iItem==-1))
return bEaten;
int nItem=pDispInfo->item.iItem;
CString sNewText=pDispInfo->item.pszText;
// ... Only check for duplicates in column 0
if ((0==m_nEditSubItem) && !sNewText.IsEmpty())
{
// Check for duplicate names
LV_FINDINFO lvfi;
memset(&lvfi, 0, sizeof(lvfi));
lvfi.flags=LVFI_STRING | LVFI_WRAP;
lvfi.psz=pDispInfo->item.pszText;
if (!m_bAllowDuplicates && (FindItem(&lvfi, nItem) != nItem))
{
// Duplicate names detected, restore old name
AfxMessageBox(m_sDuplicateErrorMsg, MB_ICONEXCLAMATION);
SetItemText(nItem, 0, m_sOriginalEditText);
}
}
// Do not change the result (already filled out by the base class)
// *pResult;
return bEaten;
}
void COXEditList::OnNewItem()
{
EditNewItem();
}
void COXEditList::OnDeleteItem()
{
DeleteSelectedItems();
}
void COXEditList::OnUpdateDeleteItem(CCmdUI* pCmdUI)
{
pCmdUI->Enable(CanDelete());
}
void COXEditList::OnMoveItemUp()
{
MoveItemUp();
}
void COXEditList::OnUpdateMoveItemUp(CCmdUI* pCmdUI)
{
pCmdUI->Enable(CanMoveUp());
}
void COXEditList::OnMoveItemDown()
{
MoveItemDown();
}
void COXEditList::OnUpdateMoveItemDown(CCmdUI* pCmdUI)
{
pCmdUI->Enable(CanMoveDown());
}
LRESULT COXEditList::OnIdleUpdateCmdUI(WPARAM wParam, LPARAM lParam)
{
// Pass on to the toolbar
m_toolbar.SendMessage(WM_IDLEUPDATECMDUI, wParam, lParam);
return 0L;
}
void COXEditList::OnWindowPosChanged(WINDOWPOS* lpwndpos)
{
if (m_bPostInitialized)
{
// Make the column of the list as wide as possible but
// leave space for scroll bar
CRect listRect;
GetClientRect(listRect);
SetColumnWidth(0, listRect.Width());
EnsureVisible(GetCurFocus(),0,FALSE);
}
PositionToolbar(m_eToolbarPosition);
COXGridList::OnWindowPosChanged(lpwndpos);
}
BOOL COXEditList::OnToolTipText(UINT /* nControlID */, NMHDR* pNMHDR, LRESULT* pResult)
{
ASSERT_VALID(this);
// This code is almost the same as in CFrameWnd::OnToolTipText
// see WinFrm.cpp
ASSERT(pNMHDR->code==TTN_NEEDTEXTA || pNMHDR->code==TTN_NEEDTEXTW);
// need to handle both ANSI and UNICODE versions of the message
TOOLTIPTEXTA* pTTTA=(TOOLTIPTEXTA*)pNMHDR;
TOOLTIPTEXTW* pTTTW=(TOOLTIPTEXTW*)pNMHDR;
TCHAR szFullText[256];
CString strTipText;
UINT_PTR nID=pNMHDR->idFrom;
if (pNMHDR->code==TTN_NEEDTEXTA && (pTTTA->uFlags & TTF_IDISHWND) ||
pNMHDR->code==TTN_NEEDTEXTW && (pTTTW->uFlags & TTF_IDISHWND))
{
// ... idFrom is actually the HWND of the tool
nID=((UINT_PTR)(WORD)::GetDlgCtrlID((HWND)nID));
}
// ... nID will be zero on a separator
if (nID != 0)
{
// AfxLoadString is an undocumented function, it still takes a UINT in the latest SDK.
AfxLoadString((UINT)nID, szFullText);
// ... this is the command id, not the button index
AfxExtractSubString(strTipText, szFullText, 1, '\n');
}
#ifndef _UNICODE
if (pNMHDR->code==TTN_NEEDTEXTA)
lstrcpyn(pTTTA->szText, strTipText, _countof(pTTTA->szText));
else
_mbstowcsz(pTTTW->szText, strTipText, _countof(pTTTW->szText));
#else
if (pNMHDR->code==TTN_NEEDTEXTA)
_wcstombsz(pTTTA->szText, strTipText, _countof(pTTTA->szText));
else
lstrcpyn(pTTTW->szText, strTipText, _countof(pTTTW->szText));
#endif
// Bring the tooltip window above other popup windows
::SetWindowPos(pNMHDR->hwndFrom, HWND_TOP, 0, 0, 0, 0,
SWP_NOACTIVATE|SWP_NOSIZE|SWP_NOMOVE);
// Message was handled
// ... Message does not use result
*pResult=0;
return TRUE;
}
LRESULT COXEditList::OnPostInit(WPARAM /* wParam */, LPARAM /* lParam */)
{
// ... Window must be valid
ASSERT(m_hWnd != NULL);
ASSERT(::IsWindow(m_hWnd));
if (m_bPostInitialized)
// Already initialized
return TRUE;
// Mark that PostInitialize has been entered
m_bPostInitialized=TRUE;
// Set initial values
InsertColumn(0, _T(""));
SetEditable(m_bEditable);
SetAutoEdit();
SetMultipleSelection();
SetBkColor(::GetSysColor(COLOR_WINDOW));
// Make the column of the list as wide as possible but leave space for scroll bar
CRect listRect;
GetClientRect(listRect);
SetColumnWidth(0, listRect.Width());
// .. Add one extra (empty column)
InsertItem(GetItemCount(), _T(""), 0);
Invalidate();
// ... Set the focus and selection to the first item
SetItemState(0, LVIS_FOCUSED | LVIS_SELECTED, LVIS_FOCUSED | LVIS_SELECTED);
return TRUE;
}
LRESULT COXEditList::OnDeleteItem(WPARAM wParam, LPARAM lParam)
{
// Do not delete the last item
int nItem=(int)wParam;
if(nItem==GetItemCount()-1)
{
return (LRESULT)FALSE;
}
// Pass the message to the base class
return COXGridList::OnDeleteItem(wParam, lParam);
}
LRESULT COXEditList::OnDeleteAllItems(WPARAM wParam, LPARAM lParam)
{
UNREFERENCED_PARAMETER(wParam);
UNREFERENCED_PARAMETER(lParam);
// Do not delete the last item
int nCount=GetItemCount();
for(int nItem=0; nItem<nCount-1; nItem++)
{
DeleteItem(0);
}
return (LRESULT)TRUE;
}
LRESULT COXEditList::OnInsertItem(WPARAM wParam, LPARAM lParam)
{
const LV_ITEM* pLviOriginal=(const LV_ITEM*)lParam;
BOOL bUseCopy=FALSE;
LV_ITEM lviCopy;
// Do not insert a non-empty item after last (empty) item
int nLastItemIndex=GetItemCount();
if (m_bPostInitialized && (nLastItemIndex <= pLviOriginal->iItem) &&
( ((pLviOriginal->mask & LVIF_TEXT) != LVIF_TEXT) ||
!CString(pLviOriginal->pszText).IsEmpty()))
{
ASSERT(AfxIsValidAddress(pLviOriginal, sizeof(LV_ITEM)));
memcpy(&lviCopy, pLviOriginal, sizeof(LV_ITEM));
bUseCopy=TRUE;
lviCopy.iItem=nLastItemIndex - 1;
}
// Pass the message to the base class
LRESULT result=COXGridList::OnInsertItem(wParam, bUseCopy ?
(LPARAM)&lviCopy : lParam);
return result;
}
void COXEditList::SetEditable(BOOL bEdit/*=TRUE*/, int nColumn/*=-1*/)
{
if(m_bPostInitialized)
{
COXGridList::SetEditable(bEdit,nColumn);
}
else
{
m_bEditable=bEdit;
}
}