//+------------------------------------------------------------------------- // // Microsoft Windows // Copyright (C) Microsoft Corporation, 1997. // // File: listview.cpp // // Contents: Implements Mobsync Custom Listview/TreeView control // // Classes: CListView // // Notes: // // History: 23-Jul-98 rogerg Created. // //-------------------------------------------------------------------------- #include "lib.h" //+--------------------------------------------------------------------------- // // Member: CListView::CListView, public // // Synopsis: Constructor // // Arguments: hwnd - hwnd of the listView we are wrapping // hwndParent - Parent for this HWND. // idCtrl - ControlID for this item // msgNotify - messageID to use for sending notifyCommand to the Parent. // // Returns: // // Modifies: // // History: 23-Jul-98 rogerg Created. // //---------------------------------------------------------------------------- CListView::CListView(HWND hwnd,HWND hwndParent,int idCtrl,UINT MsgNotify) { Assert(hwnd); m_hwnd = hwnd; m_hwndParent = hwndParent; // if parent null we just don't send notify messages m_idCtrl = idCtrl; m_MsgNotify = MsgNotify; m_pListViewItems = NULL; m_iListViewNodeCount = 0; m_iListViewArraySize = 0; m_iNumColumns = 0; m_iCheckCount = 0; m_dwExStyle = 0; // Up to caller to setup listView as OwnerData Assert(GetWindowLongA(m_hwnd,GWL_STYLE) & LVS_OWNERDATA); ListView_SetCallbackMask(m_hwnd, LVIS_STATEIMAGEMASK); // set for checkmark } //+--------------------------------------------------------------------------- // // Member: CListView::~CListView, public // // Synopsis: Constructor // // Arguments: // // Returns: // // Modifies: // // History: 23-Jul-98 rogerg Created. // //---------------------------------------------------------------------------- CListView::~CListView() { DeleteAllItems(); } //+--------------------------------------------------------------------------- // // Member: CListView::DeleteAllItems, public // // Synopsis: Removes all items from the ListView // // Arguments: // // Returns: // // Modifies: // // History: 23-Jul-98 rogerg Created. // //---------------------------------------------------------------------------- BOOL CListView::DeleteAllItems() { BOOL fReturn; fReturn = ListView_DeleteAllItems(m_hwnd); if (fReturn) { if (m_iListViewNodeCount) { LPLISTVIEWITEM pListViewCurItem; Assert(m_pListViewItems); // loop through the listview items deleting any subitems pListViewCurItem = m_pListViewItems + m_iListViewNodeCount -1; while(pListViewCurItem >= m_pListViewItems) { if(pListViewCurItem->pSubItems) { DeleteListViewItemSubItems(pListViewCurItem); } if (pListViewCurItem->lvItemEx.pszText) { Assert(LVIF_TEXT & pListViewCurItem->lvItemEx.mask); FREE(pListViewCurItem->lvItemEx.pszText); } if (pListViewCurItem->lvItemEx.pBlob) { Assert(LVIFEX_BLOB & pListViewCurItem->lvItemEx.maskEx); FREE(pListViewCurItem->lvItemEx.pBlob); } pListViewCurItem--; } m_iListViewNodeCount = 0; } // free our item buffer if (m_pListViewItems) { FREE(m_pListViewItems); m_pListViewItems = NULL; m_iListViewArraySize = 0; } m_iCheckCount = 0; } return fReturn; } //+--------------------------------------------------------------------------- // // Member: CListView::GetItemCount, public // // Synopsis: returns total number of items in the listview. // // Arguments: // // Returns: // // Modifies: // // History: 23-Jul-98 rogerg Created. // //---------------------------------------------------------------------------- int CListView::GetItemCount() { return m_iListViewNodeCount; } //+--------------------------------------------------------------------------- // // Member: CListView::GetSelectedCount, public // // Synopsis: returns number of selected items from the listview // // Arguments: // // Returns: // // Modifies: // // History: 23-Jul-98 rogerg Created. // //---------------------------------------------------------------------------- UINT CListView::GetSelectedCount() { Assert(m_hwnd); return ListView_GetSelectedCount(m_hwnd); } //+--------------------------------------------------------------------------- // // Member: CListView::GetSelectionMark, public // // Synopsis: returns index of the selection mark // // Arguments: // // Returns: itemId of the selection // // Modifies: // // History: 23-Jul-98 rogerg Created. // //---------------------------------------------------------------------------- int CListView::GetSelectionMark() { int iNativeListViewId; int iReturnItem = -1; LPLISTVIEWITEM pListViewItem; Assert(m_hwnd); iNativeListViewId = ListView_GetSelectionMark(m_hwnd); if (-1 != iNativeListViewId) { pListViewItem = ListViewItemFromNativeListViewItemId(iNativeListViewId); if (pListViewItem) { iReturnItem = pListViewItem->lvItemEx.iItem; Assert(pListViewItem->iNativeListViewItemId == iNativeListViewId); } } return iReturnItem; } //+--------------------------------------------------------------------------- // // Member: CListView::GetImageList, public // // Synopsis: returns specified imagelist // // Arguments: // // Returns: // // Modifies: // // History: 23-Jul-98 rogerg Created. // //---------------------------------------------------------------------------- HIMAGELIST CListView::GetImageList(int iImageList) { Assert(m_hwnd); return ListView_GetImageList(m_hwnd,iImageList); } //+--------------------------------------------------------------------------- // // Member: CListView::SetImageList, public // // Synopsis: sets specified imagelist // // Arguments: // // Returns: // // Modifies: // // History: 23-Jul-98 rogerg Created. // //---------------------------------------------------------------------------- HIMAGELIST CListView::SetImageList(HIMAGELIST himage,int iImageList) { Assert(m_hwnd); return ListView_SetImageList(m_hwnd,himage,iImageList); } //+--------------------------------------------------------------------------- // // Member: CListView::SetExtendedListViewStyle, public // // Synopsis: sets the list view style // // Arguments: // // Returns: // // Modifies: // // History: 23-Jul-98 rogerg Created. // //---------------------------------------------------------------------------- void CListView::SetExtendedListViewStyle(DWORD dwExStyle) { // !!Handle checkboxes ourselves. // AssertSz(0,"impl extended style with checkboxes"); Assert(m_hwnd); ListView_SetExtendedListViewStyle(m_hwnd,dwExStyle); m_dwExStyle = dwExStyle; } //+--------------------------------------------------------------------------- // // Member: CListView::InsertItem, public // // Synopsis: wrapper for ListView_InsertItem // // Arguments: // // Returns: // // Modifies: // // History: 23-Jul-98 rogerg Created. // //---------------------------------------------------------------------------- BOOL CListView::InsertItem(LPLVITEMEX pitem) { LPLISTVIEWITEM pNewListViewItem = NULL; LPLISTVIEWITEM pListViewSubItems = NULL; LPWSTR pszText = NULL; LPLVBLOB pBlob = NULL; int iListViewIndex; // location item will be inserted int iParentIndex = LVI_ROOT; BOOL fInsertNative = FALSE; int iIndent = 0; // indent for the item. int iNativeInsertAtItemID = -1; // Cannot use insert to add a subitem // and need at least one column if (0 != pitem->iSubItem || (0 == m_iNumColumns)) { Assert(0 != m_iNumColumns); Assert(0 == pitem->iSubItem); goto error; } Assert(0 == (pitem->maskEx & ~(LVIFEX_VALIDFLAGMASK))); // if a parent is specified check for special flags and // calc iItem or determine given iItem is invalid // #define LVI_ROOT -1; // itemID to pass in for ParenItemID for root // #define LVI_FIRST -0x0FFFE // #define LVI_LAST -0x0FFFF // While validatin Determine if item should be immediately added to the listview // by a) if has parent that is expanded and has an assigned listviewID // or b) this is a toplevel item. // this is done when validating the itemID and setting the fInsertNative var LPLISTVIEWITEM pParentItem; LISTVIEWITEM lviRootNode; // if have a valid parent look it up else use the root if ((LVIFEX_PARENT & pitem->maskEx) && !(LVI_ROOT == pitem->iParent) ) { pParentItem = ListViewItemFromIndex(pitem->iParent); } else { pParentItem = &lviRootNode; lviRootNode.lvItemEx.iItem = LVI_ROOT; lviRootNode.lvItemEx.iIndent = -1; lviRootNode.fExpanded = TRUE; } if (NULL == pParentItem) { Assert(NULL != pParentItem); goto error; } // found parent so go ahead and set the iIndent iParentIndex = pParentItem->lvItemEx.iItem; iIndent = pParentItem->lvItemEx.iIndent + 1; fInsertNative = pParentItem->fExpanded; // if LVI_FIRST for item then parent item + 1 // else we need to find either the next node // at the same level as the parent or hit the // end of the list. if (LVI_FIRST == pitem->iItem) { iListViewIndex = pParentItem->lvItemEx.iItem + 1; } else { int iNextParentiItem = -1; LPLISTVIEWITEM pNextParent = pParentItem + 1; LPLISTVIEWITEM pLastItem = m_pListViewItems + m_iListViewNodeCount -1; // if parent is the root node then skip since know Nextparent is the // last node. if (pParentItem != &lviRootNode) { // calc of last item assumes have at least one node // if we don't then how can we have a parent? if (m_iListViewNodeCount < 1) { goto error; } while (pNextParent <= pLastItem) { if (pNextParent->lvItemEx.iIndent == pParentItem->lvItemEx.iIndent) { iNextParentiItem = pNextParent->lvItemEx.iItem; break; } ++pNextParent; } } // if out of loop and NexParentItem is still -1 means hit the // end of list if (-1 == iNextParentiItem) { if (m_iListViewNodeCount) { iNextParentiItem = pLastItem->lvItemEx.iItem + 1; } else { iNextParentiItem = 0; } } if (LVI_LAST == pitem->iItem) { iListViewIndex = iNextParentiItem; } else { // if user specified theitem it better fall within a valid range. if (pitem->iItem > iNextParentiItem || pitem->iItem <= pParentItem->lvItemEx.iItem) { Assert(pitem->iItem <= iNextParentiItem); Assert(pitem->iItem > pParentItem->lvItemEx.iItem); goto error; } iListViewIndex = pitem->iItem; } } // make sure buffer is big enough // !!! Warning any pointers items in the ListView Array // will be invalid after the realloc/alloc. if (m_iListViewArraySize < (m_iListViewNodeCount + 1)) { int iNewArraySize = m_iListViewNodeCount + 10; LPLISTVIEWITEM pListViewItemsOrig = m_pListViewItems; ULONG cbAlloc = iNewArraySize*sizeof(LISTVIEWITEM); if (m_pListViewItems) { if (ERROR_SUCCESS != REALLOC((void **)&m_pListViewItems, cbAlloc)) { FREE(m_pListViewItems); m_pListViewItems = NULL; } } else { m_pListViewItems = (LPLISTVIEWITEM) ALLOC(cbAlloc); } // if couldn't alloc or realloc failed then fail the insert if (NULL == m_pListViewItems) { m_pListViewItems = pListViewItemsOrig; goto error; } m_iListViewArraySize = iNewArraySize; } Assert(m_pListViewItems); if (NULL == m_pListViewItems) { goto error; } // if have subitems make sure we can allocate the subitems before // moving all the nodes. This is number of columns minus one since // column offset 0 is stored in main array pListViewSubItems = NULL; if (m_iNumColumns > 1) { ULONG ulAllocSize = (m_iNumColumns -1)*sizeof(LISTVIEWITEM); int iSubItem; LPLISTVIEWITEM pCurSubItem; pListViewSubItems = (LPLISTVIEWITEM) ALLOC(ulAllocSize); if (NULL == pListViewSubItems) { goto error; } pCurSubItem = pListViewSubItems; iSubItem = 1; while (iSubItem < m_iNumColumns) { pCurSubItem->lvItemEx.iItem = iListViewIndex; pCurSubItem->lvItemEx.iSubItem = iSubItem; ++iSubItem; ++pCurSubItem; } } // make sure can allocate text and anything else // that can faile if need to before move // everything down so don't have to undo. if (pitem->mask & LVIF_TEXT) { int cchSize; if (NULL == pitem->pszText) { pszText = NULL; } else { cchSize = (lstrlen(pitem->pszText) + 1); pszText = (LPWSTR) ALLOC(cchSize * sizeof(WCHAR)); if (NULL == pszText) { goto error; } StrCpyN(pszText, pitem->pszText, cchSize); } } if (pitem->maskEx & LVIFEX_BLOB) { ULONG cbSize; if (NULL == pitem->pBlob) { Assert(pitem->pBlob); goto error; } cbSize = pitem->pBlob->cbSize; pBlob = (LPLVBLOB) ALLOC(cbSize); if (NULL == pBlob) { goto error; } memcpy(pBlob,pitem->pBlob,cbSize); } // !!!Nothing should fail after this line other than possibly // inserting into the Native ListView in which case // it will still be in our list but not shown to the User. // Move existing elements down that item is inserted ahead of. // if the item is going to be immediately added to the ListView then pNewListViewItem = m_pListViewItems + iListViewIndex; if (m_iListViewNodeCount) { LPLISTVIEWITEM pListViewMoveItem; pListViewMoveItem = m_pListViewItems + m_iListViewNodeCount -1; Assert(m_iListViewArraySize > m_iListViewNodeCount); while (pListViewMoveItem >= pNewListViewItem) // want >= so move node at current item location { int iMoveParent; ++(pListViewMoveItem->lvItemEx.iItem); // increment the iItem // if parent fails within move range increment the ParentId iMoveParent = pListViewMoveItem->lvItemEx.iParent; if ( (LVI_ROOT != iMoveParent) && (iMoveParent >= iListViewIndex)) { ++(pListViewMoveItem->lvItemEx.iParent); } *(pListViewMoveItem + 1) = *(pListViewMoveItem); --pListViewMoveItem; } } // now insert the item at the specified location ++m_iListViewNodeCount; pNewListViewItem->pSubItems = pListViewSubItems; pNewListViewItem->fExpanded = TRUE; /// Review if want this to be user defined.but we expand children by default pNewListViewItem->iChildren = 0; pNewListViewItem->iNativeListViewItemId = -1; // fixup lvItem data pNewListViewItem->lvItemEx = *pitem; pNewListViewItem->lvItemEx.pszText = pszText; pNewListViewItem->lvItemEx.iItem = iListViewIndex; pNewListViewItem->lvItemEx.iIndent = iIndent; pNewListViewItem->lvItemEx.maskEx |= LVIFEX_PARENT; // always force valid parent pNewListViewItem->lvItemEx.pBlob = pBlob; pNewListViewItem->lvItemEx.iParent = iParentIndex; // Review - For now Don't call SetItem so State CheckBox isn't updated. // Client must call SetItem after the insert to setup the ImageState // Assert that client isn't passing in a statImage on an Insert. Assert(!(pNewListViewItem->lvItemEx.mask & LVIF_STATE) || !(pNewListViewItem->lvItemEx.stateMask & LVIS_STATEIMAGEMASK)); pNewListViewItem->lvItemEx.state = 0; pNewListViewItem->lvItemEx.stateMask = 0; // if have a parent other than the root incrment its children count // !! Note have to find again in case a realloc happened if (iParentIndex != LVI_ROOT) { pParentItem = ListViewItemFromIndex(iParentIndex); Assert(pParentItem); if (pParentItem) { ++(pParentItem->iChildren); } } if (fInsertNative) { // walk back and add 1 to // first item we come to that already is in the listview. if none assigned // iNativeInsertAtItemID should be zero. iNativeInsertAtItemID = 0; LPLISTVIEWITEM pListViewPrevItem; pListViewPrevItem = pNewListViewItem -1; while (pListViewPrevItem >= m_pListViewItems) { if (-1 != pListViewPrevItem->iNativeListViewItemId) { iNativeInsertAtItemID = pListViewPrevItem->iNativeListViewItemId + 1; break; } --pListViewPrevItem; } } else { iNativeInsertAtItemID = -1; } if (-1 != iNativeInsertAtItemID) { LV_ITEM lvi = { 0 }; LPLISTVIEWITEM pListViewMoveItem; LPLISTVIEWITEM pLastItem; Assert(fInsertNative); lvi.iItem = iNativeInsertAtItemID; pNewListViewItem->iNativeListViewItemId = ListView_InsertItem(m_hwnd,&lvi); Assert(iNativeInsertAtItemID == pNewListViewItem->iNativeListViewItemId); if (-1 != pNewListViewItem->iNativeListViewItemId) { // fix up NativeIds of items below pLastItem = m_pListViewItems + m_iListViewNodeCount - 1; pListViewMoveItem = pNewListViewItem + 1; while (pListViewMoveItem <= pLastItem) { if (-1 != pListViewMoveItem->iNativeListViewItemId) { ++(pListViewMoveItem->iNativeListViewItemId); } ++pListViewMoveItem; } } } // after calling native listview fix up the state vars in local item to // not include the low byte pNewListViewItem->lvItemEx.state &= ~0xFF; pNewListViewItem->lvItemEx.stateMask &= ~0xFF; return iListViewIndex; // return new index even if fail to add to native listview error: if (pListViewSubItems) { FREE(pListViewSubItems); } if (pszText) { FREE(pszText); } if (pBlob) { FREE(pBlob); } return FALSE; } //+--------------------------------------------------------------------------- // // Member: CListView::DeleteItem, public // // Synopsis: Deletes the specified lvItem // // Arguments: // // Returns: // // Modifies: // // History: 23-Jul-98 rogerg Created. // //---------------------------------------------------------------------------- BOOL CListView::DeleteItem(int iItem) { LPLISTVIEWITEM pListViewItem = ListViewItemFromIndex(iItem); LPLISTVIEWITEM pListViewCurItem; LPLISTVIEWITEM pListViewLastItem; int iNativeListViewId; int iParent; if (NULL == pListViewItem || (m_iListViewNodeCount < 1)) { Assert(pListViewItem); Assert(m_iListViewNodeCount > 0); // should be at least one item. return FALSE; } // delete the item data and then move all items below this one up // in the array. if the Item is in the ListView and native delete succeeded // decrement their nativeID count. // !REMEMBER TO DECREMENT THE parents iChildren count if have a parent and // total count of number of items in the list view. if (0 != pListViewItem->iChildren) { Assert(0 == pListViewItem->iChildren); // don't support delete of parent nodes. return FALSE; } iNativeListViewId = pListViewItem->iNativeListViewItemId; iParent = pListViewItem->lvItemEx.iParent; // update toplevel vars and item info by calling // setitem to uncheck and clear text, and blob so LVITEMEX pitem; pitem.iItem = iItem; pitem.iSubItem = 0; pitem.mask = LVIF_TEXT; pitem.maskEx = LVIFEX_BLOB; pitem.pszText = NULL; pitem.pBlob = NULL; // only need to set the state if have checkboxes if (m_dwExStyle & LVS_EX_CHECKBOXES) { pitem.mask |= LVIF_STATE; pitem.state = LVIS_STATEIMAGEMASK_UNCHECK; pitem.stateMask = LVIS_STATEIMAGEMASK; } SetItem(&pitem); // update parent to tell it it has one less item. if (LVI_ROOT != iParent) { LPLISTVIEWITEM pListViewItemParent; if (pListViewItemParent = ListViewItemFromIndex(iParent)) { --(pListViewItemParent->iChildren); Assert(pListViewItemParent->iChildren >= 0); } Assert(pListViewItemParent); } // set the current item to the end of the list Assert(m_iListViewNodeCount >= 1); // if no nodes should have already bailed. pListViewLastItem = m_pListViewItems + m_iListViewNodeCount - 1; // delete the SubItems if any associated with the ListView. DeleteListViewItemSubItems(pListViewItem); Assert(NULL == pListViewItem->lvItemEx.pszText); Assert(NULL == pListViewItem->lvItemEx.pBlob); // delete the item from the native listView if fails will just have // a blank item at the bottom of the native listview. if (-1 != iNativeListViewId) { ListView_DeleteItem(m_hwnd,iNativeListViewId); } // decrement the toplevel nodecount --m_iListViewNodeCount; pListViewCurItem = pListViewItem; // move items remaining in the ListView up updateing iNativeListViewId // if appropriate. while (pListViewCurItem < pListViewLastItem) { *(pListViewCurItem) = *(pListViewCurItem + 1); if ( (-1 != iNativeListViewId) && (-1 != pListViewCurItem->iNativeListViewItemId)) { --pListViewCurItem->iNativeListViewItemId; } --(pListViewCurItem->lvItemEx.iItem); // decrement it iItem // if items parentID falls within pListViewItem and this item // range need to update our iParent. // parent should nevert be == iItem since don't allow nodes // with children to be deleted but check <= anyways if (LVI_ROOT != pListViewCurItem->lvItemEx.iParent) { if (iItem <= pListViewCurItem->lvItemEx.iParent) { --(pListViewCurItem->lvItemEx.iParent); } } ++pListViewCurItem; } return TRUE; } //+--------------------------------------------------------------------------- // // Member: CListView::DeleteChildren, public // // Synopsis: Deletes all child nodes associated with the item. // // Arguments: // // Returns: // // Modifies: // // History: 23-Jul-98 rogerg Created. // //---------------------------------------------------------------------------- BOOL CListView::DeleteChildren(int iItem) { LPLISTVIEWITEM pListViewItem = ListViewItemFromIndex(iItem); LPLISTVIEWITEM pListViewCurItem; LPLISTVIEWITEM pLastListViewItem; int iNumChildren; if (!pListViewItem || m_iListViewNodeCount < 1) { Assert(pListViewItem); Assert(m_iListViewNodeCount >= 1); // should at least have one node. return FALSE; } iNumChildren = pListViewItem->iChildren; pLastListViewItem = m_pListViewItems + m_iListViewNodeCount - 1; if (0 > iNumChildren) { Assert(0 <= iNumChildren); // this count should never go negative. return FALSE; } // if no children just return; if (0 == iNumChildren) { return TRUE; } // verify all children don't have any children of there own. if they // do we don't support this. Also verify that don't run off // end of the list in which case we fail too. pListViewCurItem = pListViewItem + iNumChildren; if (pListViewCurItem > pLastListViewItem) { AssertSz(0,"Children run off end of ListView"); return FALSE; } while (pListViewCurItem > pListViewItem) { if (pListViewCurItem->iChildren > 0) { AssertSz(0,"Trying to DeleteChildren when Children have Children"); return FALSE; } --pListViewCurItem; } // all items verified, just loop through deleting the items starting at the bottom. pListViewCurItem = pListViewItem + iNumChildren; while (pListViewCurItem > pListViewItem) { DeleteItem(pListViewCurItem->lvItemEx.iItem); // if any fail delete what we can. --pListViewCurItem; } return TRUE; } //+--------------------------------------------------------------------------- // // Member: CListView::SetItem, public // // Synopsis: wrapper for ListView_SetItem // // Arguments: // // Returns: // // Modifies: // // History: 23-Jul-98 rogerg Created. // //---------------------------------------------------------------------------- BOOL CListView::SetItem(LPLVITEMEX pitem) { int iNativeListViewItemId; LPLISTVIEWITEM pListViewItem = ListViewItemFromIndex(pitem->iItem,pitem->iSubItem,&iNativeListViewItemId); LPLVITEMEX plvItemEx; BOOL fCheckCountChanged = FALSE; LVITEMSTATE fNewCheckCountState; LPWSTR pszNewText = NULL; LPLVBLOB pNewBlob = NULL; if (NULL == pListViewItem) { Assert(pListViewItem); return FALSE; } Assert(pListViewItem->lvItemEx.iSubItem == pitem->iSubItem); Assert(pListViewItem->lvItemEx.iSubItem > 0 || pListViewItem->iNativeListViewItemId == iNativeListViewItemId); plvItemEx = &(pListViewItem->lvItemEx); // allocate new text if (LVIF_TEXT & pitem->mask) { int cchSize; if (NULL == pitem->pszText) { pszNewText = NULL; } else { cchSize = (lstrlen(pitem->pszText) + 1); pszNewText = (LPWSTR) ALLOC(cchSize *sizeof(WCHAR)); if (NULL == pszNewText) { goto error; } StrCpyN(pszNewText, pitem->pszText, cchSize); } } // allocate new blob if (LVIFEX_BLOB & pitem->maskEx) { if (NULL == pitem->pBlob) { pNewBlob = NULL; } else { pNewBlob = (LPLVBLOB) ALLOC(pitem->pBlob->cbSize); if (NULL == pNewBlob) { goto error; } memcpy(pNewBlob,pitem->pBlob,pitem->pBlob->cbSize); } } // now that can't fail update the text and blob field appropriately if (LVIF_TEXT & pitem->mask) { if (plvItemEx->pszText) { FREE(plvItemEx->pszText); } plvItemEx->pszText = pszNewText; plvItemEx->mask |= LVIF_TEXT; pszNewText = NULL; } if (LVIFEX_BLOB & pitem->maskEx) { if (plvItemEx->pBlob) { FREE(plvItemEx->pBlob); } plvItemEx->pBlob = pNewBlob; plvItemEx->mask |= LVIFEX_BLOB; pNewBlob = NULL; } if (LVIF_IMAGE & pitem->mask) { plvItemEx->mask |= LVIF_IMAGE; plvItemEx->iImage = pitem->iImage; } if (LVIF_PARAM & pitem->mask) { plvItemEx->mask |= LVIF_PARAM; plvItemEx->lParam = pitem->lParam; } // update the item state. if (LVIF_STATE & pitem->mask) { plvItemEx->mask |= LVIF_STATE; // only care about #define LVIS_OVERLAYMASK, LVIS_STATEIMAGEMASK if (pitem->stateMask & LVIS_OVERLAYMASK) { plvItemEx->stateMask |= LVIS_OVERLAYMASK; plvItemEx->state = (pitem->state & LVIS_OVERLAYMASK ) + (plvItemEx->state & ~LVIS_OVERLAYMASK); } if (pitem->stateMask & LVIS_STATEIMAGEMASK) { // update the m_iCheckCount (indeterminate doesn't contribute. if ( (plvItemEx->iSubItem == 0) && ( (pitem->state & LVIS_STATEIMAGEMASK) != (plvItemEx->state & LVIS_STATEIMAGEMASK))) { // don't set fCheckCountChange unless it actually did. if ( (pListViewItem->lvItemEx.state & LVIS_STATEIMAGEMASK) == LVIS_STATEIMAGEMASK_CHECK) { fCheckCountChanged = TRUE; fNewCheckCountState = LVITEMEXSTATE_UNCHECKED; --m_iCheckCount; } if ( (pitem->state & LVIS_STATEIMAGEMASK) == LVIS_STATEIMAGEMASK_CHECK) { fCheckCountChanged = TRUE; fNewCheckCountState = LVITEMEXSTATE_CHECKED; ++m_iCheckCount; } Assert(m_iCheckCount >= 0); Assert(m_iCheckCount <= m_iListViewNodeCount); } plvItemEx->stateMask |= LVIS_STATEIMAGEMASK; plvItemEx->state = (pitem->state & LVIS_STATEIMAGEMASK) + (plvItemEx->state & ~LVIS_STATEIMAGEMASK); } } // if the check count changed and we have checkboxes send the notification // if item state has changed send the count notification if (fCheckCountChanged && m_hwndParent && (m_dwExStyle & LVS_EX_CHECKBOXES)) { NMLISTVIEWEXITEMCHECKCOUNT lvCheckCount; lvCheckCount.hdr.hwndFrom = m_hwnd; lvCheckCount.hdr.idFrom = m_idCtrl; lvCheckCount.hdr.code = LVNEX_ITEMCHECKCOUNT; lvCheckCount.iCheckCount = m_iCheckCount; lvCheckCount.iItemId = pitem->iItem; lvCheckCount.dwItemState = fNewCheckCountState; // new state of the item whose checkcount has changed. SendMessage(m_hwndParent,m_MsgNotify,m_idCtrl,(LPARAM) &lvCheckCount); } // if item is in the native list view, redraw item to reflect new state // bug bug, doesn't handle subitems if (-1 != iNativeListViewItemId) { // if state changed pass it along for focus if ((LVIF_STATE & pitem->mask) && (0 == pitem->iSubItem)) { int stateMask = pitem->stateMask & 0xff; if (stateMask) { ListView_SetItemState(m_hwnd,iNativeListViewItemId,pitem->state,stateMask); } } ListView_RedrawItems(m_hwnd,iNativeListViewItemId,iNativeListViewItemId); } return TRUE; error: if (pszNewText) { FREE(pszNewText); } if (pNewBlob) { FREE(pNewBlob); } return FALSE; } //+--------------------------------------------------------------------------- // // Member: CListView::SetItemState, public // // Synopsis: wrapper for ListView_SetItemState // // Arguments: // // Returns: // // Modifies: // // History: 23-Jul-98 rogerg Created. // //---------------------------------------------------------------------------- BOOL CListView::SetItemState(int iItem,UINT state,UINT mask) { LVITEMEX lvitemEx; lvitemEx.iItem = iItem; lvitemEx.iSubItem = 0; lvitemEx.mask = LVIF_STATE ; lvitemEx.state = state; lvitemEx.stateMask = mask; lvitemEx.maskEx = 0; return SetItem(&lvitemEx); } //+--------------------------------------------------------------------------- // // Member: CListView::SetItemlParam, public // // Synopsis: wrapper for setting the lParam // // Arguments: // // Returns: // // Modifies: // // History: 23-Jul-98 rogerg Created. // //---------------------------------------------------------------------------- BOOL CListView::SetItemlParam(int iItem,LPARAM lParam) { LVITEMEX lvitemEx; lvitemEx.iItem = iItem; lvitemEx.iSubItem = 0; lvitemEx.mask = LVIF_PARAM ; lvitemEx.lParam = lParam; lvitemEx.maskEx = 0; return SetItem(&lvitemEx); } //+--------------------------------------------------------------------------- // // Member: CListView::SetItemText, public // // Synopsis: wrapper for setting the item text. // // Arguments: // // Returns: // // Modifies: // // History: 23-Jul-98 rogerg Created. // //---------------------------------------------------------------------------- BOOL CListView::SetItemText(int iItem,int iSubItem,LPWSTR pszText) { LVITEMEX lvitemEx; lvitemEx.iItem = iItem; lvitemEx.iSubItem = iSubItem; lvitemEx.mask = LVIF_TEXT; lvitemEx.pszText = pszText; lvitemEx.maskEx = 0; return SetItem(&lvitemEx); } //+--------------------------------------------------------------------------- // // Member: CListView::GetItem, public // // Synopsis: wrapper for ListView_GetItem // // Arguments: // // Returns: // // Modifies: // // History: 23-Jul-98 rogerg Created. // //---------------------------------------------------------------------------- BOOL CListView::GetItem(LPLVITEMEX pitem) { int iNativeListViewItemId; LPLISTVIEWITEM pListViewItem = ListViewItemFromIndex(pitem->iItem,pitem->iSubItem,&iNativeListViewItemId); if (NULL == pListViewItem) { Assert(pListViewItem); return FALSE; } // add text first. Since it is the only item if (LVIF_TEXT & pitem->mask) { if (!(pListViewItem->lvItemEx.pszText) || (0 == pitem->cchTextMax) || !(pListViewItem->lvItemEx.mask & LVIF_TEXT) ) { pitem->pszText = NULL; } else { int cchListTextSize = lstrlen(pListViewItem->lvItemEx.pszText); StrCpyN(pitem->pszText,pListViewItem->lvItemEx.pszText,pitem->cchTextMax); } } if (LVIF_IMAGE & pitem->mask) { pitem->iImage = pListViewItem->lvItemEx.iImage; } if (LVIF_PARAM & pitem->mask) { pitem->lParam = pListViewItem->lvItemEx.lParam; } // update the item state. if (LVIF_STATE & pitem->mask) { pitem->state = pListViewItem->lvItemEx.state; } return TRUE; } //+--------------------------------------------------------------------------- // // Member: CListView::GetItemText, public // // Synopsis: wrapper for ListView_GetItem // // Arguments: // // Returns: // // Modifies: // // History: 23-Jul-98 rogerg Created. // //---------------------------------------------------------------------------- BOOL CListView::GetItemText(int iItem,int iSubItem,LPWSTR pszText,int cchTextMax) { LVITEMEX lvitem; lvitem.mask = LVIF_TEXT; lvitem.maskEx = 0; lvitem.iItem = iItem; lvitem.iSubItem = iSubItem; lvitem.pszText = pszText; lvitem.cchTextMax = cchTextMax; return GetItem(&lvitem); } //+--------------------------------------------------------------------------- // // Member: CListView::GetItemlParam, public // // Synopsis: wrapper for gettting the lparam // // Arguments: // // Returns: // // Modifies: // // History: 23-Jul-98 rogerg Created. // //---------------------------------------------------------------------------- BOOL CListView::GetItemlParam(int iItem,LPARAM *plParam) { LVITEMEX lvitem; BOOL fReturn; lvitem.mask = LVIF_PARAM; lvitem.maskEx = 0; lvitem.iItem = iItem; lvitem.iSubItem = 0; if (fReturn = GetItem(&lvitem)) { *plParam = lvitem.lParam; } return fReturn; } //+--------------------------------------------------------------------------- // // Member: CListView::GetHwnd, public // // Synopsis: return Hwnd of the ListView // // // Arguments: // // Returns: // // Modifies: // // History: 07-Sep-98 rogerg Created. // //---------------------------------------------------------------------------- HWND CListView::GetHwnd() { return m_hwnd; } //+--------------------------------------------------------------------------- // // Member: CListView::GetParent, public // // Synopsis: return Hwnd of the ListViews Parent // // // Arguments: // // Returns: // // Modifies: // // History: 07-Sep-98 rogerg Created. // //---------------------------------------------------------------------------- HWND CListView::GetParent() { return m_hwndParent; } //+--------------------------------------------------------------------------- // // Member: CListView::GetCheckState, public // // Synopsis: wrapper for ListView_GetCheckState // // return state from LVITEMEXSTATE enum // !!!return -1 if not item match to have same behavior as ListView // // Arguments: // // Returns: // // Modifies: // // History: 23-Jul-98 rogerg Created. // //---------------------------------------------------------------------------- int CListView::GetCheckState(int iItem) { LPLISTVIEWITEM pListViewItem = ListViewItemFromIndex(iItem); UINT state; if (NULL == pListViewItem) { return -1; // return -1 same as for native listbox } // check state is actually define -1 than the image. // Don't know why. Just what the native listview does. // change whant supply our own image that will map exactly state = ((pListViewItem->lvItemEx.state & LVIS_STATEIMAGEMASK) >> 12) -1; #ifdef _DEBUG if (-1 != pListViewItem->iNativeListViewItemId) { UINT lvState = ListView_GetCheckState(m_hwnd,pListViewItem->iNativeListViewItemId); Assert(state == lvState); } #endif // _DEBUG // if this is a toplevel item then okay for state to be // negative -1. Should change this when go indeterminate. // Review - maybe just make -1 indeterinate state. if (-1 == state && 0 == pListViewItem->lvItemEx.iIndent) { state = LVITEMEXSTATE_INDETERMINATE; } Assert(state <= LVITEMEXSTATE_INDETERMINATE); return state; } //+--------------------------------------------------------------------------- // // Member: CListView::GetCheckedItemsCount, public // // Synopsis: returns the number of checked items in the list. // // // Arguments: // // Returns: // // Modifies: // // History: 23-Jul-98 rogerg Created. // //---------------------------------------------------------------------------- int CListView::GetCheckedItemsCount() { return m_iCheckCount; } //+--------------------------------------------------------------------------- // // Member: CListView::SetColumn, public // // Synopsis: wrapper for ListView_SetColumn // // // Arguments: // // Returns: // // Modifies: // // History: 23-Jul-98 rogerg Created. // //---------------------------------------------------------------------------- BOOL CListView::SetColumn(int iCol,LV_COLUMN * pColumn) { Assert(m_hwnd); return ListView_SetColumn(m_hwnd,iCol,pColumn); } //+--------------------------------------------------------------------------- // // Member: CListView::InsertColumn, public // // Synopsis: wrapper for ListView_InsertColumn // // // Arguments: // // Returns: // // Modifies: // // History: 23-Jul-98 rogerg Created. // //---------------------------------------------------------------------------- int CListView::InsertColumn(int iCol,LV_COLUMN * pColumn) { int iReturn; Assert(m_hwnd); iReturn = ListView_InsertColumn(m_hwnd,iCol,pColumn); if (-1 != iReturn) { m_iNumColumns++; // need to realloc any existing listviewItems with subitems and move // the column appropriate //currently only support setting up columns before adding any items Assert(0 == m_iListViewNodeCount); } return iReturn; } //+--------------------------------------------------------------------------- // // Member: CListView::SetColumnWidth, public // // Synopsis: wrapper for ListView_SetColumnWidth // // // Arguments: // // Returns: // // Modifies: // // History: 23-Jul-98 rogerg Created. // //---------------------------------------------------------------------------- BOOL CListView::SetColumnWidth(int iCol,int cx) { Assert(m_hwnd); return ListView_SetColumnWidth(m_hwnd,iCol,cx); } //+--------------------------------------------------------------------------- // // Member: CListView::Expand, public // // Synopsis: expands all children of specified Item // // Arguments: // // Returns: // // Modifies: // // History: 23-Jul-98 rogerg Created. // //---------------------------------------------------------------------------- BOOL CListView::Expand(int iItemId) { LPLISTVIEWITEM pListViewItem = ListViewItemFromIndex(iItemId); if (!pListViewItem) { return FALSE; } return ExpandCollapse(pListViewItem,TRUE); } //+--------------------------------------------------------------------------- // // Member: CListView::Collapse, public // // Synopsis: collapses all children of specified Item // // Arguments: // // Returns: // // Modifies: // // History: 23-Jul-98 rogerg Created. // //---------------------------------------------------------------------------- BOOL CListView::Collapse(int iItemId) { LPLISTVIEWITEM pListViewItem = ListViewItemFromIndex(iItemId); if (!pListViewItem) { return FALSE; } return ExpandCollapse(pListViewItem,FALSE); } //+--------------------------------------------------------------------------- // // Member: CListView::FindItemFromBlob, public // // Synopsis: returns first item in list that matches blob // // Arguments: // // Returns: // // Modifies: // // History: 23-Jul-98 rogerg Created. // //---------------------------------------------------------------------------- int CListView::FindItemFromBlob(LPLVBLOB pBlob) { LPLISTVIEWITEM pListViewItem; // if not items just return if (m_iListViewNodeCount < 1) { return -1; } pListViewItem = m_pListViewItems + m_iListViewNodeCount -1; while(pListViewItem >= m_pListViewItems) { if (IsEqualBlob(pBlob,pListViewItem->lvItemEx.pBlob)) { return pListViewItem->lvItemEx.iItem; } --pListViewItem; } return -1; } //+--------------------------------------------------------------------------- // // Member: CListView::GetItemBlob, public // // Synopsis: finds blob is any associated with an // item and then fill in mem pointed // to by pBlob if cbSize in BlobStruc is > // specified cbBlockSize NULL is returned // // Arguments: // // Returns: NULL on failure // on success a pointer to the passed in buffer // strictly for convenience to the caller. // // Modifies: // // History: 23-Jul-98 rogerg Created. // //---------------------------------------------------------------------------- LPLVBLOB CListView::GetItemBlob(int ItemId,LPLVBLOB pBlob,ULONG cbBlobSize) { LPLISTVIEWITEM pListViewItem = ListViewItemFromIndex(ItemId); LPLVBLOB pItemBlob; if (!pListViewItem || (NULL == pListViewItem->lvItemEx.pBlob) || (NULL == pBlob)) { Assert(pListViewItem); Assert(pBlob); return NULL; } pItemBlob = pListViewItem->lvItemEx.pBlob; // make sure out buffer is big enough if (cbBlobSize < pItemBlob->cbSize) { Assert(cbBlobSize >= pItemBlob->cbSize); return NULL; } memcpy(pBlob,pItemBlob,pItemBlob->cbSize); return pBlob; } //+--------------------------------------------------------------------------- // // Member: CListView::IsEqualBlob, private // // Synopsis: compares two blobs. Valid to pass in NULL put two NULLS is // not a match. // // Arguments: // // Returns: // // Modifies: // // History: 23-Jul-98 rogerg Created. // //---------------------------------------------------------------------------- BOOL CListView::IsEqualBlob(LPLVBLOB pBlob1,LPLVBLOB pBlob2) { if (NULL == pBlob1 || NULL == pBlob2) { return FALSE; } // compare sizes if (pBlob1->cbSize != pBlob2->cbSize) { return FALSE; } if (0 != memcmp(pBlob1,pBlob2,pBlob2->cbSize)) { return FALSE; } return TRUE; } //+--------------------------------------------------------------------------- // // Member: CListView::OnNotify, public // // Synopsis: Called by client whenever a native listview // notifiication is sent. We turn around, package // it up and send it as our notification message. // // Arguments: // // Returns: // // Modifies: // // History: 23-Jul-98 rogerg Created. // //---------------------------------------------------------------------------- LRESULT CListView::OnNotify(LPNMHDR pnmv) { LPNMLISTVIEW pnlv = (LPNMLISTVIEW) pnmv; NMLISTVIEWEX nmvEx; LPLISTVIEWITEM pListViewItem; if ((NULL == pnmv) || ( ((int) pnmv->idFrom) != m_idCtrl) || (NULL == m_hwndParent)) { Assert(pnmv); Assert( ((int) pnmv->idFrom) == m_idCtrl); Assert(m_hwndParent); return 0; } // take care of notifies we handle ourselves. switch(pnmv->code) { case LVN_GETDISPINFOW: { OnGetDisplayInfo(pnmv->code,(LV_DISPINFO *) pnmv); return 0; break; } case NM_DBLCLK: case NM_CLICK: { LV_HITTESTINFO lvhti; RECT Rect; lvhti.pt = pnlv->ptAction; // Have the ListView tell us what element this was on if (-1 != ListView_HitTest(m_hwnd, &lvhti)) { // if flags are onitem change to either label or state depending // on the click positiion if (LVHT_ONITEM == lvhti.flags) { lvhti.flags = LVHT_ONITEMLABEL; if (ListView_GetSubItemRect(m_hwnd,pnlv->iItem,0,LVIR_ICON,&Rect)) { if (lvhti.pt.x < Rect.left) { lvhti.flags = LVHT_ONITEMSTATEICON; } else if (lvhti.pt.x <= Rect.right) { // this doesn't ever get hit since icon is between label and state // but its here for completeness. lvhti.flags = LVHT_ONITEMICON; } } } if (OnHandleUIEvent(pnmv->code,lvhti.flags,0,pnlv->iItem)) { return 0; // don't pass on clicks we process } } break; } case LVN_KEYDOWN: { LV_KEYDOWN* pnkd = (LV_KEYDOWN*) pnmv; int iItem; if (-1 != (iItem = ListView_GetSelectionMark(m_hwnd))) { if(OnHandleUIEvent(pnmv->code,0,pnkd->wVKey,iItem)) { return 0; } } break; } default: break; } Assert(LVNEX_ITEMCHANGED == LVN_ITEMCHANGED); Assert(LVNEX_DBLCLK == NM_DBLCLK); Assert(LVNEX_CLICK == NM_CLICK); // only pass along notifications we know how to handle if (LVN_ITEMCHANGED != pnmv->code && NM_DBLCLK != pnmv->code && NM_CLICK != pnmv->code) { return 0; } // listview can send a iItem of -1 for example when // a double-click or click occurs in empty space. // if get a -1 just pass through if (-1 == pnlv->iItem) { memcpy(&(nmvEx.nmListView),pnmv,sizeof(nmvEx.nmListView)); nmvEx.iParent = -1; nmvEx.pBlob = NULL; } else { pListViewItem = ListViewItemFromNativeListViewItemId(pnlv->iItem); if (NULL == pListViewItem) { // if couldn't find itme Assert(pListViewItem); return 0; } // assumes only pass along notifications of // type LPNMLISTVIEW // fix up the notify structure memcpy(&(nmvEx.nmListView),pnmv,sizeof(nmvEx.nmListView)); nmvEx.nmListView.iItem = pListViewItem->lvItemEx.iItem; // make item point to our internal id nmvEx.iParent = pListViewItem->lvItemEx.iParent; nmvEx.pBlob = pListViewItem->lvItemEx.pBlob; if (LVIF_STATE & pnlv->uChanged ) { // update our internal itemState for the item. // Note don't care about lower state bits if ( (pnlv->uNewState ^ pnlv->uOldState) & LVIS_STATEIMAGEMASK) { pListViewItem->lvItemEx.state = (pnlv->uNewState & LVIS_STATEIMAGEMASK) + (pListViewItem->lvItemEx.state & ~LVIS_STATEIMAGEMASK); } if ( (pnlv->uNewState ^ pnlv->uOldState) & LVIS_OVERLAYMASK) { pListViewItem->lvItemEx.state = (pnlv->uNewState & LVIS_OVERLAYMASK) + (pListViewItem->lvItemEx.state & ~LVIS_OVERLAYMASK); } } } // send off the message return SendMessage(m_hwndParent,m_MsgNotify,m_idCtrl,(LPARAM) &nmvEx); } //+--------------------------------------------------------------------------- // // Member: CListView::ListViewItemFromNativeListViewItemId, private // // Synopsis: Given a native listview control itemId finds our internal ListViewItem // // Arguments: // // Returns: // // Modifies: // // History: 23-Jul-98 rogerg Created. // //---------------------------------------------------------------------------- LPLISTVIEWITEM CListView::ListViewItemFromNativeListViewItemId(int iNativeListViewItemId) { return ListViewItemFromNativeListViewItemId(iNativeListViewItemId,0); } //+--------------------------------------------------------------------------- // // Member: CListView::ListViewItemFromNativeListViewItemId, private // // Synopsis: Given a native listview control itemId finds our internal ListViewItem // // Arguments: iNativeListViewItemId - ItemId of the ListViewItem // iNativeListViewSubItemId - SubItemID of the ListViewItem // piNativeListViewItemId - [out] on succes iteId in nativelistview. // // Returns: // // Modifies: // // History: 23-Jul-98 rogerg Created. // //---------------------------------------------------------------------------- LPLISTVIEWITEM CListView::ListViewItemFromNativeListViewItemId(int iNativeListViewItemId, int iSubItem) { LPLISTVIEWITEM pListViewItem; if (-1 == iNativeListViewItemId) { return NULL; } if (NULL == m_pListViewItems || m_iListViewNodeCount < 1 || (iSubItem > m_iNumColumns - 1)) { Assert(NULL != m_pListViewItems); Assert(m_iListViewNodeCount >= 1); Assert(iSubItem <= (m_iNumColumns - 1)); return NULL; } pListViewItem = m_pListViewItems + m_iListViewNodeCount -1; while(pListViewItem >= m_pListViewItems) { if (iNativeListViewItemId == pListViewItem->iNativeListViewItemId) { break; } --pListViewItem; } if (pListViewItem < m_pListViewItems) { return NULL; } // if subItem is zero then we just return this listviewItem, else // need to walk forward the subItem array. if (0 == iSubItem) { return pListViewItem; } Assert(m_iNumColumns > 1); // should have already caught about but double-check pListViewItem = pListViewItem->pSubItems + iSubItem -1; return pListViewItem; } //+--------------------------------------------------------------------------- // // Member: CListView::ListViewItemFromIndex, private // // Synopsis: Finds internal listviewItem from ItemID we gave to client. // // Arguments: // // Returns: // // Modifies: // // History: 23-Jul-98 rogerg Created. // //---------------------------------------------------------------------------- LPLISTVIEWITEM CListView::ListViewItemFromIndex(int iItemID) { return ListViewItemFromIndex(iItemID,0,NULL); } //+--------------------------------------------------------------------------- // // Member: CListView::ListViewItemFromIndex, private // // Synopsis: Finds internal listviewItem from ItemID we gave to client. // // Arguments: // // Returns: // // Modifies: // // History: 23-Jul-98 rogerg Created. // //---------------------------------------------------------------------------- LPLISTVIEWITEM CListView::ListViewItemFromIndex(int iItemID,int iSubitem,int *piNativeListViewItemId) { LPLISTVIEWITEM pListViewItem; // if item isn't valid return NULL if (iItemID < 0 || iItemID >= m_iListViewNodeCount || (iSubitem > m_iNumColumns - 1)) { Assert(iItemID >= 0); Assert(iSubitem <= m_iNumColumns - 1); // Assert(iItemID < m_iListViewNodeCount); choice dlg Calls GetCheckState until hits -1. return NULL; } pListViewItem = m_pListViewItems + iItemID; if (piNativeListViewItemId) { *piNativeListViewItemId = pListViewItem->iNativeListViewItemId; } if (0 == iSubitem) { return pListViewItem; } pListViewItem = pListViewItem->pSubItems + iSubitem -1; return pListViewItem; } //+--------------------------------------------------------------------------- // // Member: CListView::DeleteListViewItemSubItems, private // // Synopsis: Helper function to delete subItems associated with a ListViewItem // // Arguments: // // Returns: // // Modifies: // // History: 04-Aug-98 rogerg Created. // //---------------------------------------------------------------------------- void CListView::DeleteListViewItemSubItems(LPLISTVIEWITEM pListItem) { LPLISTVIEWITEM pListViewSubItem; // if not subItems or number of columns isn't at least 2 bail. if ((NULL == pListItem->pSubItems) || (m_iNumColumns < 2)) { Assert(NULL == pListItem->pSubItems && m_iNumColumns < 2); // should always match. return; } pListViewSubItem = pListItem->pSubItems + m_iNumColumns -2; // -2 ; covers first subItem and column of main item // free any text associated with the subItems Assert(m_iNumColumns > 1); Assert(pListViewSubItem >= pListItem->pSubItems); while (pListViewSubItem >= pListItem->pSubItems) { if (pListViewSubItem->lvItemEx.pszText) { Assert(LVIF_TEXT & pListViewSubItem->lvItemEx.mask); FREE(pListViewSubItem->lvItemEx.pszText); } --pListViewSubItem; } FREE(pListItem->pSubItems); pListItem->pSubItems = NULL; } //+--------------------------------------------------------------------------- // // Member: CListView::ExpandCollapse, private // // Synopsis: Expands or collapses children of given node. // // Arguments: // // Returns: // // Modifies: // // History: 23-Jul-98 rogerg Created. // //---------------------------------------------------------------------------- BOOL CListView::ExpandCollapse(LPLISTVIEWITEM pListViewItem,BOOL fExpand) { LPLISTVIEWITEM pCurListViewItem = pListViewItem + 1; LPLISTVIEWITEM pLastListViewItem = m_pListViewItems + m_iListViewNodeCount -1; int iIndent = pListViewItem->lvItemEx.iIndent; int iInsertDeleteCount = 0; LV_ITEM lvi = { 0 }; Assert(pListViewItem); Assert(m_iListViewNodeCount); // if specified node isn't in the list view fail if (-1 == pListViewItem->iNativeListViewItemId) { Assert(-1 != pListViewItem->iNativeListViewItemId); return FALSE; } lvi.iItem = pListViewItem->iNativeListViewItemId + 1; while (pCurListViewItem <= pLastListViewItem && pCurListViewItem->lvItemEx.iIndent > iIndent) { if (fExpand) { if ( (-1 == pCurListViewItem->iNativeListViewItemId) && (pCurListViewItem->lvItemEx.iIndent == iIndent + 1)) // only expand next level deep { pCurListViewItem->iNativeListViewItemId = ListView_InsertItem(m_hwnd,&lvi); Assert(pCurListViewItem->iNativeListViewItemId == lvi.iItem); if (-1 != pCurListViewItem->iNativeListViewItemId) { ++lvi.iItem; ++iInsertDeleteCount; } } } else { if (-1 != pCurListViewItem->iNativeListViewItemId) { pCurListViewItem->fExpanded = FALSE; if (ListView_DeleteItem(m_hwnd,lvi.iItem)) { pCurListViewItem->iNativeListViewItemId = -1; --iInsertDeleteCount; } } } ++pCurListViewItem; } // fixup nativeIds of any remaining items in the list while (pCurListViewItem <= pLastListViewItem) { if (-1 != pCurListViewItem->iNativeListViewItemId) { pCurListViewItem->iNativeListViewItemId += iInsertDeleteCount; Assert(pCurListViewItem->iNativeListViewItemId >= 0); Assert(pCurListViewItem->iNativeListViewItemId < m_iListViewNodeCount); } ++pCurListViewItem; } pListViewItem->fExpanded = fExpand; return TRUE; } //+--------------------------------------------------------------------------- // // Member: CListView::OnGetDisplayInfo, private // // Synopsis: Handles Display info notification. // // Arguments: code - code from notification header either // LVN_GETDISPINFOW, LVN_GETDISPINFOA. need this // so know how to handle Text. // // Returns: // // Modifies: // // History: 23-Jul-98 rogerg Created. // //---------------------------------------------------------------------------- void CListView::OnGetDisplayInfo(UINT code,LV_DISPINFO *plvdi) { LPLISTVIEWITEM pListViewItem = ListViewItemFromNativeListViewItemId(plvdi->item.iItem, plvdi->item.iSubItem); if (NULL == pListViewItem) return; // verify subitem iound matches item we asked for. Assert(pListViewItem->lvItemEx.iSubItem == plvdi->item.iSubItem); // The ListView needs text for this row if (plvdi->item.mask & LVIF_TEXT) { if (pListViewItem->lvItemEx.pszText) { if (LVN_GETDISPINFOW == code) { StrCpyN(plvdi->item.pszText,pListViewItem->lvItemEx.pszText,plvdi->item.cchTextMax); } } } // The ListView needs an image if (plvdi->item.mask & LVIF_IMAGE) { // plvdi->item.iItem, plvdi->item.iSubItem, &(plvdi->item.iImage)); plvdi->item.iImage = pListViewItem->lvItemEx.iImage; } // The ListView needs the indent level if (plvdi->item.mask & LVIF_INDENT) { // if (m_fThreadMessages) // m_pTable->GetIndentLevel(plvdi->item.iItem, (LPDWORD) &(plvdi->item.iIndent)); // else // for no checks on top-level set image state to empty pict and // indent to -1. Need additional State Logic if want to // choose if toplevel checks are shown. plvdi->item.iIndent = pListViewItem->lvItemEx.iIndent; if ( (m_dwExStyle & LVS_EX_CHECKBOXES) && (0 == plvdi->item.iIndent) ) { plvdi->item.iIndent = -1; } } // The ListView needs the state image if (plvdi->item.mask & LVIF_STATE) { //nt iIcon = 0; // _GetColumnStateImage(plvdi->item.iItem, plvdi->item.iSubItem, &iIcon); plvdi->item.state = pListViewItem->lvItemEx.state & LVIS_STATEIMAGEMASK; } } //+--------------------------------------------------------------------------- // // Member: CListView::OnHandleUIEvent private // // Synopsis: sent when a click or double-click,keyboard, is sent to // the listview // // Arguments: code - indicates why this function was called // Support NM_DBLCLK, NM_CLICK, LVN_KEYDOWN: // // flags - flags from hitTest. Only valid for DBCLK and CLICK // flag = LVHT_ONITEMSTATEICON | LVHT_ONITEMICON) |LVHT_ONITEMLABEL // wVKey - Virtual key code of key presses. Ony valide cfor LVN_KEYDOWN // iItemNative - ItemID in NativeListView // // // Returns: TRUE - if handled event and notification shouldn't be passed on. // // Modifies: // // History: 23-Jul-98 rogerg Created. // //---------------------------------------------------------------------------- BOOL CListView::OnHandleUIEvent(UINT code,UINT flags,WORD wVKey,int iItemNative) { LPLISTVIEWITEM pListViewItem = ListViewItemFromNativeListViewItemId(iItemNative); int iStateMask; BOOL fItemHasCheckBoxes; // for now this is hardcoded depending on the indent. Need to change this. BOOL fToggleCheckBox = FALSE; BOOL fExpand = FALSE; BOOL fCollapse = FALSE; BOOL fReturn = FALSE; if (NULL == pListViewItem) { return TRUE; // no need passing this on } fItemHasCheckBoxes = pListViewItem->lvItemEx.iIndent ? TRUE : FALSE; // If Item has chechboxes toggle on keyboard space or mouse clicks // over the itemState Icon. // double-clicks on itemIcon togles the whether the branch is expanded/collasse // if left/right keyboard expand collapse switch(code) { case LVN_KEYDOWN: { switch(wVKey) { case VK_SPACE: if (fItemHasCheckBoxes) { fToggleCheckBox = TRUE; } break; case VK_RIGHT: case VK_LEFT: if (pListViewItem->iChildren) { fExpand = VK_RIGHT == wVKey ? TRUE : FALSE; fCollapse = !fExpand; } break; default: break; } } case NM_DBLCLK: if ( (flags & LVHT_ONITEMICON) && (pListViewItem->iChildren)) { fExpand = pListViewItem->fExpanded ? FALSE : TRUE; fCollapse = !fExpand; break; } // double-click falls through to a click. case NM_CLICK: if ((flags & LVHT_ONITEMSTATEICON) && fItemHasCheckBoxes) { fToggleCheckBox = TRUE; } break; default: break; } if (fExpand || fCollapse) { // don't bother if already in current state if (pListViewItem->fExpanded != fExpand) { ExpandCollapse(pListViewItem,fExpand); } return TRUE; } else if (fToggleCheckBox) { // for now just toggle the state. if have children need to set them appropriately. iStateMask = LVITEMEXSTATE_CHECKED == GetCheckState(pListViewItem->lvItemEx.iItem) ? LVIS_STATEIMAGEMASK_UNCHECK : LVIS_STATEIMAGEMASK_CHECK; SetItemState(pListViewItem->lvItemEx.iItem,iStateMask,LVIS_STATEIMAGEMASK); return TRUE; } return fReturn; // default we pass it along }