// ========================================================================== // 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 // 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 } LONG COXEditListHeader::OnSetText(UINT wParam, LONG_PTR lParam) { UNREFERENCED_PARAMETER(wParam); // save the window text m_sText=(LPCTSTR)lParam; RedrawWindow(); return TRUE; } LONG COXEditListHeader::OnGetText(UINT wParam, LONG 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; } LONG COXEditListHeader::OnGetTextLength(UINT wParam, LONG 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; nItemiItem) && ( ((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; } }