//============================================================================ // Copyright (c) 1995, Microsoft Corporation // // File: treelist.c // // History: // Abolade Gbadegesin Nov-20-1995 Created. // // Implementation routines for TreeList control. // // The TreeList control is implemented as a custom control, // which creates and manages an owner-draw listview. //============================================================================ #include #include #include #include #include #include #include #include "treelist.h" #include "tldef.h" #if 0 #define TLTRACE (0x80000002) #else #define TLTRACE (0x00000002) #endif //---------------------------------------------------------------------------- // Function: TL_Init // // Registers the TreeList window class. //---------------------------------------------------------------------------- BOOL TL_Init( HINSTANCE hInstance ) { INT i; HICON hicon; WNDCLASS wc; // // do nothing if the class is already registered // if (GetClassInfo(hInstance, WC_TREELIST, &wc)) { return TRUE; } // // setup the wndclass structure, and register // wc.lpfnWndProc = TL_WndProc; wc.hCursor = LoadCursor(hInstance, IDC_ARROW); wc.hIcon = NULL; wc.lpszMenuName = NULL; wc.hInstance = hInstance; wc.lpszClassName = WC_TREELIST; wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); wc.style = CS_DBLCLKS; wc.cbWndExtra = sizeof(TL *); wc.cbClsExtra = 0; return RegisterClass(&wc); } //---------------------------------------------------------------------------- // Function: TL_WndProc // // This function handles messages for TreeList windows //---------------------------------------------------------------------------- LRESULT CALLBACK TL_WndProc( HWND hwnd, UINT uiMsg, WPARAM wParam, LPARAM lParam ) { TL *ptl; if (NULL == hwnd) { return (LRESULT)FALSE; } // // attempt to retrieve the data pointer for the window. // on WM_NCCREATE, this fails, so we allocate the data. // ptl = TL_GetPtr(hwnd); if (ptl == NULL) { if (uiMsg != WM_NCCREATE) { return DefWindowProc(hwnd, uiMsg, wParam, lParam); } // // allocate a block of memory // ptl = (TL *)Malloc(sizeof(TL)); if (ptl == NULL) { return (LRESULT)FALSE; } // // save the pointer in the window's private bytes // ptl->hwnd = hwnd; // //Reset Error code, TL_SetPtr won't reset error code when //it succeeds // SetLastError(0); if ((0 == TL_SetPtr(hwnd, ptl)) && (0 != GetLastError())) { Free(ptl); return (LRESULT)FALSE; } return DefWindowProc(hwnd, uiMsg, wParam, lParam); } // // if the window is being destroyed, free the block allocated // and set the private bytes pointer to NULL // if (uiMsg == WM_NCDESTROY) { Free(ptl); TL_SetPtr(hwnd, NULL); return (LRESULT)0; } switch (uiMsg) { HANDLE_MSG(ptl, WM_CREATE, TL_OnCreate); HANDLE_MSG(ptl, WM_DESTROY, TL_OnDestroy); HANDLE_MSG(ptl, WM_DRAWITEM, TL_OnDrawItem); HANDLE_MSG(ptl, WM_MEASUREITEM, TL_OnMeasureItem); HANDLE_MSG(ptl, WM_NOTIFY, TL_OnNotify); case WM_ERASEBKGND: { TL_OnEraseBackground(ptl, (HDC)wParam); return (LRESULT)TRUE; } case WM_HELP: { // // change the control-id and HWND for the help to our values // and pass the message on to our parent // HELPINFO *phi = (HELPINFO *)lParam; phi->iCtrlId = ptl->iCtrlId; phi->hItemHandle = ptl->hwnd; return SendMessage(ptl->hwndParent, WM_HELP, 0L, lParam); } case WM_SYSCOLORCHANGE: { // // notify the listview window that a color has changed // TL_CreateTreeImages(ptl); FORWARD_WM_SYSCOLORCHANGE(ptl->hwndList, SendMessage); // ListView_SetBkColor(ptl->hwndList, GetSysColor(COLOR_WINDOW)); return (LRESULT)0; } case WM_SETFOCUS: { // // if we receive the focus, give it to the listview instead // SetFocus(ptl->hwndList); return (LRESULT)0; } case WM_WINDOWPOSCHANGED: { TL_OnWindowPosChanged(ptl, (WINDOWPOS *)lParam); return 0; } // // the following cases handle TreeList-defined messages // case TLM_INSERTITEM: { return (LRESULT)TL_OnInsertItem(ptl, (TL_INSERTSTRUCT *)lParam); } case TLM_DELETEITEM: { return (LRESULT)TL_OnDeleteItem(ptl, (HTLITEM)lParam); } case TLM_DELETEALLITEMS: { return (LRESULT)TL_OnDeleteAllItems(ptl); } case TLM_GETITEM: { return (LRESULT)TL_OnGetItem(ptl, (LV_ITEM *)lParam); } case TLM_SETITEM: { return (LRESULT)TL_OnSetItem(ptl, (LV_ITEM *)lParam); } case TLM_GETITEMCOUNT: { return (LRESULT)TL_OnGetItemCount(ptl); } case TLM_GETNEXTITEM: { return (LRESULT)TL_OnGetNextItem(ptl, (UINT)wParam,(HTLITEM)lParam); } case TLM_EXPAND: { return (LRESULT)TL_OnExpand(ptl, (UINT)wParam, (HTLITEM)lParam); } case TLM_SETIMAGELIST: { return (LRESULT)ListView_SetImageList( ptl->hwndList, (HIMAGELIST)lParam, LVSIL_SMALL ); } case TLM_GETIMAGELIST: { return (LRESULT)ListView_GetImageList(ptl->hwndList, LVSIL_SMALL); } case TLM_INSERTCOLUMN: { return (LRESULT)TL_OnInsertColumn( ptl, (INT)wParam, (LV_COLUMN *)lParam ); } case TLM_DELETECOLUMN: { return (LRESULT)TL_OnDeleteColumn(ptl, (INT)wParam); } case TLM_SETSELECTION: { return (LRESULT)TL_OnSetSelection(ptl, (HTLITEM)lParam); } case TLM_REDRAW: { return (LRESULT)TL_OnRedraw(ptl); } case TLM_ISITEMEXPANDED: { return (LRESULT)TL_IsExpanded((TLITEM *)lParam); } case TLM_GETCOLUMNWIDTH: { return (LRESULT)SendMessage( ptl->hwndList, LVM_GETCOLUMNWIDTH, wParam, lParam ); } case TLM_SETCOLUMNWIDTH: { return (LRESULT)SendMessage( ptl->hwndList, LVM_SETCOLUMNWIDTH, wParam, lParam ); } } // // let the default processing be done // return DefWindowProc(hwnd, uiMsg, wParam, lParam); } //---------------------------------------------------------------------------- // Function: TL_OnCreate // // This is called after WM_CREATE, and it initializes the window structure, // as well as creating the listview which will contain the items added //---------------------------------------------------------------------------- BOOL TL_OnCreate( TL *ptl, CREATESTRUCT *pcs ) { RECT rc; HD_ITEM hdi; HWND hwndList; TLITEM *pRoot; DWORD dwStyle, dwExStyle; // // initialize the window structure // ptl->hbrBk = NULL; ptl->hbmp = NULL; ptl->hbmpStart = NULL; ptl->hbmpMem = NULL; ptl->hdcImages = NULL; ptl->hwndList = NULL; ptl->iCtrlId = PtrToUlong(pcs->hMenu); ptl->hwndParent = pcs->hwndParent; ptl->nColumns = 0; // // initialize the invisible root item // pRoot = &ptl->root; pRoot->pParent = NULL; pRoot->iLevel = -1; pRoot->iIndex = -1; pRoot->nChildren = 0; pRoot->uiFlag = TLI_EXPANDED; pRoot->pszText = TEXT("ROOT"); InitializeListHead(&pRoot->lhChildren); InitializeListHead(&pRoot->lhSubitems); // // we pass on some of our window style bits to the listview // when we create it as our child; we also remove certain styles // which are never appropriate for the contained listview // dwStyle = pcs->style & ~(LVS_TYPESTYLEMASK | LVS_SORTASCENDING | LVS_SORTDESCENDING); dwStyle |= WS_CHILD | LVS_REPORT | LVS_SINGLESEL | LVS_OWNERDRAWFIXED; dwExStyle = pcs->dwExStyle & ~(WS_EX_CLIENTEDGE | WS_EX_WINDOWEDGE | WS_EX_STATICEDGE); // // create the listview window // GetClientRect(ptl->hwnd, &rc); hwndList = CreateWindowEx( dwExStyle, WC_LISTVIEW, NULL, dwStyle, 0, 0, rc.right, rc.bottom, ptl->hwnd, NULL, pcs->hInstance, NULL ); if (hwndList == NULL) { return FALSE; } // ListView_SetBkColor(hwndList, GetSysColor(COLOR_WINDOW)); // // We to set the background color to "NONE" to prevent the listview // from erasing its background itself. Removing the background color // causes the listview to forward its WM_ERASEBKGND messages to its parent, // which is our tree-list. We handle the WM_ERASEBKGND messages // efficiently by only erasing the background when absolutely necessary, // and this eliminates the flicker normally seen when windows are updated // frequently. // ListView_SetBkColor(hwndList, CLR_NONE); ptl->hwndList = hwndList; return TRUE; } //---------------------------------------------------------------------------- // Function: TL_OnDestroy // // Delete all the items in the tree, and free the image bitmap // used for drawing the tree structure //---------------------------------------------------------------------------- VOID TL_OnDestroy( TL *ptl ) { TL_OnDeleteAllItems(ptl); if (ptl->hdcImages != NULL) { if (ptl->hbmp) { SelectObject(ptl->hdcImages, ptl->hbmpStart); DeleteObject(ptl->hbmp); } DeleteDC(ptl->hdcImages); } if (ptl->hbmpMem) { DeleteObject(ptl->hbmpMem); } } //---------------------------------------------------------------------------- // Function: TL_OnWindowPosChanged // // When the window width changes, we destroy our off-screen bitmap. //---------------------------------------------------------------------------- VOID TL_OnWindowPosChanged( TL *ptl, WINDOWPOS *pwp ) { RECT rc; GetClientRect(ptl->hwnd, &rc); SetWindowPos( ptl->hwndList, ptl->hwnd, 0, 0, rc.right, rc.bottom, pwp->flags ); } //---------------------------------------------------------------------------- // Function: TL_OnEraseBackground // // When we are asked to erase the background, first test to see if // the update region is completely in the item-area for the listbox. If so, // we know we'll be called to update each item, so we can ignore this // request to erase our background. //---------------------------------------------------------------------------- VOID TL_OnEraseBackground( TL *ptl, HDC hdc ) { RECT rc; INT count; HBRUSH hbrOld; LV_HITTESTINFO lvhi; // // Retrieve the rectangle to be erased // GetClipBox(hdc, &rc); TRACEX4( TLTRACE, "WM_ERASEBKGND: ClipBox: (%d, %d) (%d %d)", rc.left, rc.top, rc.right, rc.bottom ); // // Retrieve the count of listview items. // This is necessary because the smooth-scrolling code triggers // a repaint inside the ListView_DeleteItem() processing, // at which point our indices may be out of sync (i.e. we have more items // than the listview). // The count retrieved is used to do a sanity-check // on the treelist-item indices below. // count = ListView_GetItemCount(ptl->hwndList); TRACEX1(TLTRACE, "WM_ERASEBKGND: Count: %d", count); // // If there are no treelist items, we always have to erase. // If there are treelist items, we only have to erase // if part of the erase-region lies below our lowest item. // while (!IsListEmpty(&ptl->root.lhChildren)) { // one-time loop RECT rctop; INT iTopIndex; INT cyUpdate; TLITEM *pItem; LIST_ENTRY *phead; // // We need to factor in the height of the header-control, if any; // to this end, we get the bounding rectangle of the topmost item // visible in the listview, and then we use the top of that item // as the basis for our computations below // iTopIndex = ListView_GetTopIndex(ptl->hwndList); TRACEX1(TLTRACE, "WM_ERASEBKGND: TopIndex: %d", iTopIndex); ListView_GetItemRect(ptl->hwndList, iTopIndex, &rctop, LVIR_BOUNDS); TRACEX1(TLTRACE, "WM_ERASEBKGND: rctop.top: %d", rctop.top); rc.top = rctop.top; // // If the area to be erased extends further right in the window // than our items do, we'll have to erase // if (rctop.right < rc.right) { TRACEX2( TLTRACE, "WM_ERASEBKGND: rctop.right < rc.right (%d < %d)", rctop.right, rc.right ); break; } // // Get the total height of the area to be updated; // this excludes the area occupied by the header-control. // cyUpdate = rc.bottom - rctop.top; TRACEX1(TLTRACE, "WM_ERASEBKGND: CyUpdate: %d", cyUpdate); // // Get the lowest item; it is the one at the tail of the item-list // phead = ptl->root.lhChildren.Blink; pItem = CONTAINING_RECORD(phead, TLITEM, leSiblings); TRACEX1(TLTRACE, "WM_ERASEBKGND: CyItem: %d", ptl->cyItem); // // If the lowest item or one of its visible descendants is lower // than the bottom of the update region, we don't have to erase; // therefore, we walk down the list of the lowest item's descendants, // checking each time whether the descendant is lower than the region // we've been asked to erase. // do { TRACEX1( TLTRACE, "WM_ERASEBKGND: pItem->iIndex: %d", pItem->iIndex ); // // force the erasure if the item's index is higher // than the number of listview items // if (pItem->iIndex >= count) { break; } // // defer the erasure if the item is lower than the bottom // of the update-rect // if ((pItem->iIndex - iTopIndex + 1) * (INT)ptl->cyItem > cyUpdate) { TRACEX(TLTRACE, "WM_ERASEBKGND: DEFERRING"); return; } // // move on to the item's lowest child; // if it has none, it means the erase-region's lowest edge // is lower than our lowest item, and that means // that we'll have to erase it now instead of just letting it // get updated when we handle the WM_DRAWITEM // if (IsListEmpty(&pItem->lhChildren)) { pItem = NULL; } else { phead = pItem->lhChildren.Blink; pItem = CONTAINING_RECORD(phead, TLITEM, leSiblings); } } while (pItem && TL_IsVisible(pItem)); break; } // // One of the points was not on an item, so erase // TRACEX(TLTRACE, "WM_ERASEBKGND: ERASING"); hbrOld = SelectObject(hdc, ptl->hbrBk); PatBlt( hdc, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, PATCOPY ); SelectObject(hdc, hbrOld); } //---------------------------------------------------------------------------- // Function: TL_OnDrawItem // // This is called by the listview when an item needs to be drawn. //---------------------------------------------------------------------------- BOOL TL_OnDrawItem( TL *ptl, CONST DRAWITEMSTRUCT *pdis ) { // // make sure this is from our listview // if (pdis->CtlType != ODT_LISTVIEW) { return FALSE; } switch (pdis->itemAction) { // // currently listviews always send ODA_DRAWENTIRE, // but handle all cases anyway // case ODA_DRAWENTIRE: case ODA_FOCUS: case ODA_SELECT: return TL_DrawItem(ptl, pdis); } return TRUE; } //---------------------------------------------------------------------------- // Function: TL_DrawItem // // This function does the actual drawing for a treelist item //---------------------------------------------------------------------------- BOOL TL_DrawItem( TL *ptl, CONST DRAWITEMSTRUCT *pdis ) { HDC hdcMem; TCHAR *psz; RECT rc, rcItem; HBITMAP hbmpOld; HIMAGELIST himl; TLSUBITEM *pSubitem; HFONT hfont, hfontOld; TLITEM *pItem, *pParent; LIST_ENTRY *ple, *phead; INT cxIndent, cxImage, cyImage, i, tx, x, y, xcol; // // the itemData contains the lParam passed in ListView_InsertItem; // this lParam is the TLITEM pointer for the tree-item, so we retrieve it // and use the information it contains to draw the item // cxIndent = ptl->cxIndent; pItem = (TLITEM *)pdis->itemData; rcItem.left = 0; rcItem.top = 0; rcItem.right = pdis->rcItem.right - pdis->rcItem.left; rcItem.bottom = pdis->rcItem.bottom - pdis->rcItem.top; // // create a compatible DC // hdcMem = CreateCompatibleDC(pdis->hDC); if(NULL == hdcMem) { return FALSE; } if (ptl->hbmpMem) { if (rcItem.right > (INT)ptl->cxBmp || rcItem.bottom > (INT)ptl->cyBmp) { DeleteObject(ptl->hbmpMem); ptl->hbmpMem = NULL; } } if (!ptl->hbmpMem) { ptl->hbmpMem = CreateCompatibleBitmap( pdis->hDC, rcItem.right, rcItem.bottom ); ptl->cxBmp = rcItem.right; ptl->cyBmp = rcItem.bottom; } hbmpOld = SelectObject(hdcMem, ptl->hbmpMem); hfontOld = SelectObject(hdcMem, GetWindowFont(pdis->hwndItem)); // // erase the background // #if 0 ptl->hbrBk = FORWARD_WM_CTLCOLOREDIT( ptl->hwndParent, hdcMem, ptl->hwnd, SendMessage ); #endif FillRect(hdcMem, &rcItem, ptl->hbrBk); // // set the text background color based on whether or not // the item is selected // if (pdis->itemState & ODS_SELECTED) { SetTextColor(hdcMem, GetSysColor(COLOR_HIGHLIGHTTEXT)); SetBkColor(hdcMem, GetSysColor(COLOR_HIGHLIGHT)); } else { SetTextColor(hdcMem, GetSysColor(COLOR_WINDOWTEXT)); SetBkColor(hdcMem, GetSysColor(COLOR_WINDOW)); } // // compute the starting position as a multiple of // the item's level and the indentation per level // x = rcItem.left + pItem->iLevel * cxIndent; y = rcItem.top; xcol = rcItem.left + ListView_GetColumnWidth(pdis->hwndItem, 0); tx = x; x += cxIndent; // // now draw the item's tree image; // only draw as much as will fit in the first column // if (tx < xcol) { BitBlt( hdcMem, tx, y, min(cxIndent, xcol - tx), ptl->cyItem, ptl->hdcImages, pItem->iImage * cxIndent, 0, SRCCOPY ); } // // draw the lines going down from the item's ancestors // to the item's ancestors' corresponding siblings; // in other words, for each ancestor which is not its parent's last child, // there should be a line going down from that ancestor to its next sibling // and the line will pass through the rows for all of that item's expanded // descendants. // note that we do not draw lines at the root-level // pParent = pItem->pParent; for (i = pItem->iLevel - 1, tx -= cxIndent; i > 0; i--, tx -= cxIndent) { if (tx < xcol && pParent->leSiblings.Flink != &pParent->pParent->lhChildren) { BitBlt( hdcMem, tx, y, min(cxIndent, xcol - tx), ptl->cyItem, ptl->hdcImages, TL_VerticalLine * cxIndent, 0, SRCCOPY ); } pParent = pParent->pParent; } // // draw the state image, if there is one, // and increment the left position by the width of the image // himl = ListView_GetImageList(pdis->hwndItem, LVSIL_STATE); if (himl != NULL && TL_StateImageValue(pItem)) { ImageList_GetIconSize(himl, &cxImage, &cyImage); ImageList_Draw( himl, TL_StateImageIndex(pItem), hdcMem, x, y + (ptl->cyItem - cyImage), ILD_NORMAL ); x += cxImage; } // // draw the image, if there is an image list, // and increment the left position by the width of the image // himl = ListView_GetImageList(pdis->hwndItem, LVSIL_SMALL); if (himl != NULL && (pItem->lvi.mask & LVIF_IMAGE)) { ImageList_GetIconSize(himl, &cxImage, &cyImage); ImageList_Draw( himl, pItem->lvi.iImage, hdcMem, x, y + (ptl->cyItem - cyImage) / 2, ILD_NORMAL ); x += cxImage; } // // compute the rectangle in the first column // which will be the clipping boundary for text // rc.left = x; rc.right = xcol; rc.top = rcItem.top; rc.bottom = rcItem.bottom; // // draw the first column's text // if (pItem->lvi.mask & LVIF_TEXT) { // // center the text vertically in the item-rectangle // psz = Ellipsisize(hdcMem, pItem->pszText, rc.right - rc.left, 0); ExtTextOut( hdcMem, rc.left + 2, rc.top + (ptl->cyItem - ptl->cyText) / 2, ETO_CLIPPED | ETO_OPAQUE, &rc, psz ? psz : pItem->pszText, lstrlen(psz ? psz : pItem->pszText), NULL ); Free0(psz); } // // draw the subitems' texts // i = 1; phead = &pItem->lhSubitems; for (ple = phead->Flink; ple != phead; ple = ple->Flink) { pSubitem = (TLSUBITEM *)CONTAINING_RECORD(ple, TLSUBITEM, leItems); // // we need to draw blank texts for subitems which have not been set; // this enables us to save memory on items which don't have // certain subitems set // for ( ; i < pSubitem->iSubItem; i++) { rc.left = rc.right; rc.right = rc.left + ListView_GetColumnWidth(pdis->hwndItem, i); ExtTextOut( hdcMem, rc.left + 2, rc.top + (ptl->cyItem - ptl->cyText) / 2, ETO_CLIPPED | ETO_OPAQUE, &rc, TEXT(""), 0, NULL ); } // // now draw the text for the current item // rc.left = rc.right; rc.right = rc.left + ListView_GetColumnWidth( pdis->hwndItem, pSubitem->iSubItem ); psz = Ellipsisize(hdcMem, pSubitem->pszText, rc.right - rc.left, 0); ExtTextOut( hdcMem, rc.left + 2, rc.top + (ptl->cyItem - ptl->cyText) / 2, ETO_CLIPPED | ETO_OPAQUE, &rc, psz ? psz : pSubitem->pszText, lstrlen(psz ? psz : pSubitem->pszText), NULL ); Free0(psz); ++i; } // // we need to draw blank texts for subitems which have not been set // for ( ; i < (INT)ptl->nColumns; i++) { rc.left = rc.right; rc.right = rc.left + ListView_GetColumnWidth(pdis->hwndItem, i); ExtTextOut( hdcMem, rc.left + 2, rc.top + (ptl->cyItem - ptl->cyText) / 2, ETO_CLIPPED | ETO_OPAQUE, &rc, TEXT(""), 0, NULL ); } // // restore the original background and text color // #if 0 if (pdis->itemState & ODS_SELECTED) { SetTextColor(pdis->hDC, GetSysColor(COLOR_WINDOWTEXT)); SetBkColor(pdis->hDC, GetSysColor(COLOR_WINDOW)); } #endif // // draw the focus rectangle if necessary // if (pdis->itemState & ODS_FOCUS) { rc = rcItem; rc.left = min(x, xcol); DrawFocusRect(hdcMem, &rc); } // // Blt the changes to the screen DC // BitBlt( pdis->hDC, pdis->rcItem.left, pdis->rcItem.top, rcItem.right, rcItem.bottom, hdcMem, rcItem.left, rcItem.top, SRCCOPY ); SelectObject(hdcMem, hbmpOld); SelectObject(hdcMem, hfontOld); DeleteDC(hdcMem); return TRUE; } //---------------------------------------------------------------------------- // Function: TL_OnMeasureItem // // This is called by the listview when it needs to know // the height of each item; we use this opportunity to rebuild // the bitmap which holds images used for drawing tree lines. // // TODO: the listview currently seems to ignore the value we set, // and instead uses the height of a small icon (SM_CYSMICON). //---------------------------------------------------------------------------- VOID TL_OnMeasureItem( TL *ptl, MEASUREITEMSTRUCT *pmis ) { HDC hdc; INT cyIcon; HFONT hfont; TEXTMETRIC tm; HWND hwndList; if (pmis->CtlType != ODT_LISTVIEW) { return; } // // retrieve the listview, its font, and its device context // hwndList = GetDlgItem(ptl->hwnd, pmis->CtlID); hfont = GetWindowFont(hwndList); hdc = GetDC(hwndList); if(NULL == hdc) { return; } SelectObject(hdc, hfont); // // get the height of the listview's font // if (!GetTextMetrics(hdc, &tm)) { ReleaseDC(hwndList, hdc); return; } ptl->cyText = tm.tmHeight; pmis->itemHeight = ptl->cyText; // // make sure the item height is at least as high as a small icon // cyIcon = GetSystemMetrics(SM_CYSMICON); if (pmis->itemHeight < (UINT)cyIcon) { pmis->itemHeight = cyIcon; } pmis->itemHeight += GetSystemMetrics(SM_CYBORDER); #if 0 pmis->itemHeight = (pmis->itemHeight + 1) & ~1; #endif ptl->cyItem = pmis->itemHeight; ptl->cxIndent = GetSystemMetrics(SM_CXSMICON); ReleaseDC(hwndList, hdc); // // rebuild the images used in drawing tree lines // TL_CreateTreeImages(ptl); } //---------------------------------------------------------------------------- // Function: TL_CreateColorBitmap // // Utility function fro creating a color bitmap //---------------------------------------------------------------------------- HBITMAP TL_CreateColorBitmap( INT cx, INT cy ) { HDC hdc; HBITMAP hbmp; hdc = GetDC(NULL); if(NULL == hdc) { return NULL; } hbmp = CreateCompatibleBitmap(hdc, cx, cy); ReleaseDC(NULL, hdc); return hbmp; } //---------------------------------------------------------------------------- // Function: TL_CreateTreeImages // // This function builds a list of images which are scaled to // the height of each item in the tree. The appearance of the images // is shown in ASCII text in the code below //---------------------------------------------------------------------------- VOID TL_CreateTreeImages( TL *ptl ) { HDC hdc; RECT rc; HBITMAP hbmpOld; INT cxIndent, x, c, xmid, ymid; HBRUSH hbrOld, hbrGrayText, hbrWinText; // // invalidate the listview's client area, to force a redraw // if (ptl->hwndList != NULL) { InvalidateRect(ptl->hwndList, NULL, TRUE); } // // create a device context if necessary // if (ptl->hdcImages == NULL) { ptl->hdcImages = CreateCompatibleDC(NULL); if(NULL == ptl->hdcImages) { return; } } hdc = ptl->hdcImages; cxIndent = ptl->cxIndent; ptl->hbrBk = FORWARD_WM_CTLCOLOREDIT( ptl->hwndParent, hdc, ptl->hwnd, SendMessage ); // // create the bitmap to be used // hbmpOld = ptl->hbmp; ptl->hbmp = TL_CreateColorBitmap(TL_ImageCount * cxIndent, ptl->cyItem); if (hbmpOld == NULL) { ptl->hbmpStart = SelectObject(hdc, ptl->hbmp); } else { SelectObject(hdc, ptl->hbmp); DeleteObject(hbmpOld); } // // retreive system color brushes for drawing the tree images // hbrWinText = GetSysColorBrush(COLOR_WINDOWTEXT); hbrGrayText = GetSysColorBrush(COLOR_GRAYTEXT); hbrOld = SelectObject(hdc, hbrGrayText); rc.top = 0; rc.bottom = ptl->cyItem; rc.left = 0; rc.right = TL_ImageCount * cxIndent; // // fill the image with the background color // FillRect(hdc, &rc, ptl->hbrBk); xmid = cxIndent / 2; ymid = ((ptl->cyItem / 2) + 1) & ~1; c = min(xmid, ymid) / 2; // | // | x = TL_VerticalLine * cxIndent; TL_DottedLine(hdc, x + xmid, 0, ptl->cyItem, TRUE); // // --- // x = TL_RootChildless * cxIndent; TL_DottedLine(hdc, x + xmid, ymid, cxIndent - xmid, FALSE); // // +-+ // |+|-- // +-+ // x = TL_RootParentCollapsed * cxIndent; TL_DottedLine(hdc, x + xmid, ymid, cxIndent - xmid, FALSE); TL_DrawButton( hdc, x + xmid, ymid, c, hbrWinText, hbrGrayText, ptl->hbrBk, TRUE ); // // +-+ // |-|-- // +-+ // x = TL_RootParentExpanded * cxIndent; SelectObject(hdc, hbrGrayText); TL_DottedLine(hdc, x + xmid, ymid, cxIndent - xmid, FALSE); TL_DrawButton( hdc, x + xmid, ymid, c, hbrWinText, hbrGrayText, ptl->hbrBk, FALSE ); // // | // +-- // | // x = TL_MidChildless * cxIndent; SelectObject(hdc, hbrGrayText); TL_DottedLine(hdc, x + xmid, 0, ptl->cyItem, TRUE); TL_DottedLine(hdc, x + xmid, ymid, cxIndent - xmid, FALSE); // // | // +-+ // |+|-- // +-+ // | // x = TL_MidParentCollapsed * cxIndent; TL_DottedLine(hdc, x + xmid, 0, ptl->cyItem, TRUE); TL_DottedLine(hdc, x + xmid, ymid, cxIndent - xmid, FALSE); TL_DrawButton( hdc, x + xmid, ymid, c, hbrWinText, hbrGrayText, ptl->hbrBk, TRUE ); // // | // +-+ // |-|-- // +-+ // | // x = TL_MidParentExpanded * cxIndent; SelectObject(hdc, hbrGrayText); TL_DottedLine(hdc, x + xmid, 0, ptl->cyItem, TRUE); TL_DottedLine(hdc, x + xmid, ymid, cxIndent - xmid, FALSE); TL_DrawButton( hdc, x + xmid, ymid, c, hbrWinText, hbrGrayText, ptl->hbrBk, FALSE ); // // | // +-- // x = TL_EndChildless * cxIndent; SelectObject(hdc, hbrGrayText); TL_DottedLine(hdc, x + xmid, 0, ymid, TRUE); TL_DottedLine(hdc, x + xmid, ymid, cxIndent - xmid, FALSE); // // | // +-+ // |+|-- // +-+ // x = TL_EndParentCollapsed * cxIndent; TL_DottedLine(hdc, x + xmid, 0, ymid, TRUE); TL_DottedLine(hdc, x + xmid, ymid, cxIndent - xmid, FALSE); TL_DrawButton( hdc, x + xmid, ymid, c, hbrWinText, hbrGrayText, ptl->hbrBk, TRUE ); // // | // +-+ // |-|-- // +-+ // x = TL_EndParentExpanded * cxIndent; SelectObject(hdc, hbrGrayText); TL_DottedLine(hdc, x + xmid, 0, ymid, TRUE); TL_DottedLine(hdc, x + xmid, ymid, cxIndent - xmid, FALSE); TL_DrawButton( hdc, x + xmid, ymid, c, hbrWinText, hbrGrayText, ptl->hbrBk, FALSE ); if (hbrOld != NULL) { SelectObject(hdc, hbrOld); } DeleteObject(hbrGrayText); DeleteObject(hbrWinText); } //---------------------------------------------------------------------------- // Function: TL_DottedLine // // Draws a dotted line eiter vertically or horizontally, // with the specified dimension as its length. //---------------------------------------------------------------------------- VOID TL_DottedLine( HDC hdc, INT x, INT y, INT dim, BOOL fVertical ) { for ( ; dim > 0; dim -= 2) { PatBlt(hdc, x, y, 1, 1, PATCOPY); if (fVertical) { y += 2; } else { x += 2; } } } //---------------------------------------------------------------------------- // Function: TL_DrawButton // // Draws a button with a plus or a minus, centered at the given location //---------------------------------------------------------------------------- VOID TL_DrawButton( HDC hdc, INT x, INT y, INT dim, HBRUSH hbrSign, HBRUSH hbrBox, HBRUSH hbrBk, BOOL bCollapsed ) { int n; int p = (dim * 7) / 10; n = p * 2 + 1; // // first fill with the background color // SelectObject(hdc, hbrBk); PatBlt(hdc, x - dim, y - dim, dim * 2, dim * 2, PATCOPY); // // draw the sign // SelectObject(hdc, hbrSign); if (p >= 5) { PatBlt(hdc, x - p, y - 1, n, 3, PATCOPY); if (bCollapsed) { PatBlt(hdc, x - 1, y - p, 3, n, PATCOPY); } SelectObject(hdc, hbrBk); p--; n -= 2; } PatBlt(hdc, x - p, y, n, 1, PATCOPY); if (bCollapsed) { PatBlt(hdc, x, y - p, 1, n, PATCOPY); } n = dim * 2 + 1; // // draw the box around the sign // SelectObject(hdc, hbrBox); PatBlt(hdc, x - dim, y - dim, n, 1, PATCOPY); PatBlt(hdc, x - dim, y - dim, 1, n, PATCOPY); PatBlt(hdc, x - dim, y + dim, n, 1, PATCOPY); PatBlt(hdc, x + dim, y - dim, 1, n, PATCOPY); } //---------------------------------------------------------------------------- // Function: TL_UpdateListIndices // // This function updates the indices for all items in the tree // which are visually below the specified item pStart, assuming that // the list index for pStart is correct. Consider the case // in the diagram below: // // -- child 1 // | |- child 1,1 // | -- child 1,2 // |- child 2 // -- child 3 // | |- child 3,1 // | | |- child 3,1,1 // | | -- child 3,1,2 // | |- child 3,2 // | -- child 3,3 // -- child 4 // // Suppose that pStart points to "child 3,1". To set the indices, // we first update the indices for all descendants of pStart, // and then we update the indices for the siblings of pStart's ancestors // which are after pStart's ancestors in the tree. //---------------------------------------------------------------------------- VOID TL_UpdateListIndices( TL *ptl, TLITEM *pStart ) { INT iIndex; iIndex = pStart->iIndex; if (pStart->nChildren > 0) { // // if the item is visible, set its index; // otherwise pass in NULL to set its index // and that of its descendants to -1 // if (TL_IsExpanded(pStart) && (pStart == &ptl->root || TL_IsVisible(pStart))) { TL_UpdateDescendantIndices(ptl, pStart, &iIndex); } else { TL_UpdateDescendantIndices(ptl, pStart, NULL); } } if (pStart->pParent != NULL) { TL_UpdateAncestorIndices(ptl, pStart, &iIndex); } ptl->root.iIndex = -1; } //---------------------------------------------------------------------------- // Function: TL_UpdateDescendantIndices // // This function updates the indices of the descendants // of the item specified. An item is not considered to be // a descendant of itself. //---------------------------------------------------------------------------- VOID TL_UpdateDescendantIndices( TL *ptl, TLITEM *pStart, INT *piIndex ) { // // go through list of children setting indices // TLITEM *pItem; LIST_ENTRY *ple; for (ple = pStart->lhChildren.Flink; ple != &pStart->lhChildren; ple = ple->Flink) { pItem = (TLITEM *)CONTAINING_RECORD(ple, TLITEM, leSiblings); // // set the index of the child // pItem->iIndex = (piIndex ? ++(*piIndex) : -1); // // set the indices of the child's descendants // if (pItem->nChildren > 0) { // // if the item is visible, set its index; // otherwise pass in NULL to set its index // and that of its descendants to -1 // if (TL_IsExpanded(pItem) && TL_IsVisible(pItem)) { TL_UpdateDescendantIndices(ptl, pItem, piIndex); } else { TL_UpdateDescendantIndices(ptl, pItem, NULL); } } } } //---------------------------------------------------------------------------- // Function: TL_UpdateAncestorIndices // // This function updates the indices of the items which are // visually below the specified item in the listview. //---------------------------------------------------------------------------- VOID TL_UpdateAncestorIndices( TL *ptl, TLITEM *pStart, INT *piIndex ) { TLITEM *pItem; LIST_ENTRY *ple; // // first set inidices for the siblings beneath this item; // note that we begin walking the siblings AFTER the item passed in, // for (ple = pStart->leSiblings.Flink; ple != &pStart->pParent->lhChildren; ple = ple->Flink) { pItem = (TLITEM *)CONTAINING_RECORD(ple, TLITEM, leSiblings); // // set the index for the sibling // pItem->iIndex = (piIndex ? ++(*piIndex) : -1); if (pItem->nChildren > 0) { // // if the item is visible, set its index; // otherwise pass in NULL to set its index // and that of its descendants to -1 // if (TL_IsExpanded(pItem) && TL_IsVisible(pItem)) { TL_UpdateDescendantIndices(ptl, pItem, piIndex); } else { TL_UpdateDescendantIndices(ptl, pItem, NULL); } } } // // now set indices for the parent siblings which are beneath the parent // // TODO - OPTIMIZATION: this is post-recursion and therefore, it can // be replaced by a loop, which at the very least would save stack // space // if (pStart->pParent->pParent != NULL) { TL_UpdateAncestorIndices(ptl, pStart->pParent, piIndex); } } //---------------------------------------------------------------------------- // Function: TL_NotifyParent // // Forwards a notification to the treelist's parent //---------------------------------------------------------------------------- BOOL TL_NotifyParent( TL *ptl, NMHDR *pnmh ) { return (BOOL)SendMessage( ptl->hwndParent, WM_NOTIFY, (WPARAM)ptl->hwnd, (LPARAM)pnmh ); } //---------------------------------------------------------------------------- // Function: TL_OnNotify // // Handles notifications from the listview window and its header control //---------------------------------------------------------------------------- LRESULT TL_OnNotify( TL *ptl, INT iCtrlId, NMHDR *pnmh ) { NMHDR nmh; TLITEM *pItem; // // notify parent of the message // if (TL_NotifyParent(ptl, pnmh)) { return FALSE; } switch (pnmh->code) { case HDN_ENDTRACK: { // // we need to redraw ourselves, AFTER the header resets; // hence the use of PostMessage instead of SendMessage // PostMessage(ptl->hwnd, TLM_REDRAW, (WPARAM)0, (LPARAM)0); return FALSE; } case NM_CLICK: case NM_DBLCLK: { // // do a hit-test; // POINT pt; INT iLeft; LV_ITEM lvi; LV_HITTESTINFO lvhi; if (!GetCursorPos(&lvhi.pt)) { return FALSE; } ScreenToClient(ptl->hwndList, &lvhi.pt); if (ListView_HitTest(ptl->hwndList, &lvhi) == -1) { return FALSE; } if (!(lvhi.flags & LVHT_ONITEM)) { return FALSE; } // // see which part of the item was clicked // if (!ListView_GetItemPosition(ptl->hwndList, lvhi.iItem, &pt)) { return FALSE; } // // retrieve the item clicked // lvi.iItem = lvhi.iItem; lvi.iSubItem = 0; lvi.mask = LVIF_PARAM; if (!ListView_GetItem(ptl->hwndList, &lvi)) { return FALSE; } pItem = (TLITEM *)lvi.lParam; // // compute the position of the item's tree image // iLeft = pItem->iLevel * ptl->cxIndent; if (lvhi.pt.x > (pt.x + iLeft)) { // // the hit was to the right of the item's tree image // if (lvhi.pt.x < (pt.x + iLeft + (INT)ptl->cxIndent)) { // // the hit was on the item's tree image // if (pItem->nChildren > 0) { // // the +/- button was clicked, toggle expansion // return TL_OnExpand(ptl, TLE_TOGGLE, (HTLITEM)pItem); } } else { // // the hit was on the item's state icon, image, or text // // // see if the parent wants to handle it // nmh.code = pnmh->code; nmh.idFrom = 0; nmh.hwndFrom = ptl->hwnd; TL_NotifyParent(ptl, &nmh); if (nmh.idFrom != 0) { return TRUE; } if (pnmh->code == NM_DBLCLK && pItem->nChildren > 0) { // // the item was double-clicked, toggle expansion // return TL_OnExpand( ptl, TLE_TOGGLE, (HTLITEM)pItem ); } } } return FALSE; } case NM_RETURN: { // // get current selection; // if a parent item, toggle expand-state // LV_ITEM lvi; lvi.iItem = ListView_GetNextItem(ptl->hwndList, -1, LVNI_SELECTED); if (lvi.iItem == -1) { return FALSE; } lvi.iSubItem = 0; lvi.mask = LVIF_PARAM; if (!ListView_GetItem(ptl->hwndList, &lvi)) { return FALSE; } pItem = (TLITEM *)lvi.lParam; if (pItem->nChildren > 0) { // // the item has children, toggle expand state // return TL_OnExpand(ptl, TLE_TOGGLE, (HTLITEM)pItem); } return FALSE; } case LVN_KEYDOWN: { // // get key pressed and current selection; // if a parent item and key is '+', expand; // if key is '-' or left key, collapse // if key is VK_RIGHT, expand and move to first child; // if key is VK_LEFT, collapse parent and move to parent // LV_ITEM lvi; LV_KEYDOWN *plvk; plvk = (LV_KEYDOWN *)pnmh; switch (plvk->wVKey) { case VK_RIGHT: case VK_ADD: { // // retrieve the item // lvi.iItem = ListView_GetNextItem( ptl->hwndList, -1, LVNI_SELECTED ); if (lvi.iItem == -1) { return FALSE; } lvi.iSubItem = 0; lvi.mask = LVIF_PARAM; if (!ListView_GetItem(ptl->hwndList, &lvi)) { return FALSE; } // // expand the item if it is collapsed // pItem = (TLITEM *)lvi.lParam; if (pItem->nChildren <= 0) { return FALSE; } if (!TL_IsExpanded(pItem)) { return TL_OnExpand(ptl, TLE_EXPAND, (HTLITEM)pItem); } else if (plvk->wVKey == VK_RIGHT) { // // the key was VK_RIGHT, // so we select the item's child // pItem = (TLITEM *)CONTAINING_RECORD( pItem->lhChildren.Flink, TLITEM, leSiblings ); if (TL_OnSetSelection(ptl, (HTLITEM)pItem)) { return ListView_EnsureVisible( ptl->hwndList, pItem->iIndex, FALSE ); } } break; } case VK_LEFT: case VK_SUBTRACT: { // // retrieve the current selection // lvi.iItem = ListView_GetNextItem( ptl->hwndList, -1, LVNI_SELECTED ); if (lvi.iItem == -1) { return FALSE; } lvi.iSubItem = 0; lvi.mask = LVIF_PARAM; if (!ListView_GetItem(ptl->hwndList, &lvi)) { return FALSE; } // // collapse the item if it is expanded; // otherwise, if the key is VK_LEFT, // select the item's parent // pItem = (TLITEM *)lvi.lParam; if (pItem->nChildren > 0) { return TL_OnExpand(ptl, TLE_COLLAPSE, (HTLITEM)pItem); } else if (plvk->wVKey == VK_LEFT && pItem->pParent != &ptl->root) { if (TL_OnSetSelection(ptl, (HTLITEM)pItem->pParent)) { return ListView_EnsureVisible( ptl->hwndList, pItem->pParent->iIndex, FALSE ); } } break; } } return FALSE; } case LVN_ITEMCHANGED: { NMTREELIST nmtl; NM_LISTVIEW *pnmlv; pnmlv = (NM_LISTVIEW *)pnmh; if ((pnmlv->uChanged & LVIF_STATE)) { if (pnmlv->uNewState & LVIS_SELECTED) { // // the new state is selected; // notify the parent that the selection has changed // nmtl.hdr.hwndFrom = ptl->hwnd; nmtl.hdr.code = TLN_SELCHANGED; nmtl.hItem = (HTLITEM)pnmlv->lParam; nmtl.lParam = ((TLITEM *)nmtl.hItem)->lParam; return TL_NotifyParent(ptl, (NMHDR *)&nmtl); } } return FALSE; } case LVN_DELETEITEM: { INT iItem; LV_ITEM lvi; TLITEM *pNext; NM_LISTVIEW *pnmlv; // // get the item which is selected // pnmlv = (NM_LISTVIEW *)pnmh; iItem = ListView_GetNextItem(ptl->hwndList, -1, LVNI_SELECTED); if (iItem != -1) { return FALSE; } // // the deleted item was selected, // so select another item // lvi.mask = LVIF_PARAM; lvi.iItem = pnmlv->iItem; lvi.iSubItem = 0; if (!ListView_GetItem(ptl->hwndList, &lvi)) { return FALSE; } pItem = (TLITEM *)lvi.lParam; // // choose sibling item before this one // pNext = (TLITEM *)TL_OnGetNextItem( ptl, TLGN_PREVSIBLING, (HTLITEM)pItem ); if (pNext == NULL) { // // that failed, so choose the sibling after this one // pNext = (TLITEM *)TL_OnGetNextItem( ptl, TLGN_NEXTSIBLING, (HTLITEM)pItem ); if (pNext == NULL) { // // that failed too, so choose the parent // so long as the parent isn't the root // pNext = pItem->pParent; if (pNext == &ptl->root) { return FALSE; } } } return TL_OnSetSelection(ptl, (HTLITEM)pNext); } } return FALSE; } //---------------------------------------------------------------------------- // Function: TL_UpdateImage // // This function updates the tree image for an item // when the item's state changes; this is called when an item // is inserted or deleted, or expanded or collapsed. // Insertion or deletions can have side-effects, as follows: // // (1) an item is inserted as the child of a previously childless item; // the parent's image changes to show a collapsed button // // (2) an item is inserted as the last child of a parent which // had children; the image of the item which used to be // the parent's last child changes: // parent parent // -- old ---> |- old // -- new // // (3) the reverse of case 1, i.e. an item is removed which was // the only child of a parent item; the parent's image changes // to show that it is childless // // (4) the reverse of case 2, i.e. an item is removed which was // the last child of a parent which has other children; // the image of the item which will now be the last child changes // // In all of these cases, the item to which the side-effect occurs // is written into ppChanged; so the caller can update the image // for that item as well //---------------------------------------------------------------------------- VOID TL_UpdateImage( TL *ptl, TLITEM *pItem, TLITEM **ppChanged ) { INT iImage; TLITEM *pChanged; if (ppChanged == NULL) { ppChanged = &pChanged; } *ppChanged = NULL; // // special case for root-level items // if (pItem->pParent == &ptl->root) { if (pItem->nChildren == 0) { pItem->iImage = TL_RootChildless; } else { pItem->iImage = TL_IsExpanded(pItem) ? TL_RootParentExpanded : TL_RootParentCollapsed; } } else if (pItem->leSiblings.Flink == &pItem->pParent->lhChildren) { // // item is last of its parent's children // if (pItem->nChildren == 0) { pItem->iImage = TL_EndChildless; } else { pItem->iImage = TL_IsExpanded(pItem) ? TL_EndParentExpanded : TL_EndParentCollapsed; } // // if this is the only child, the parent was childless and // its image should change; otherwise, the child before this one // used to be the last child and its image should change // if (pItem->leSiblings.Blink == &pItem->pParent->lhChildren) { *ppChanged = pItem->pParent; } else { *ppChanged = (TLITEM *)CONTAINING_RECORD( pItem->leSiblings.Blink, TLITEM, leSiblings ); } } else { // // item is not last of its parent's children // if (pItem->nChildren == 0) { pItem->iImage = TL_MidChildless; } else { pItem->iImage = TL_IsExpanded(pItem) ? TL_MidParentExpanded : TL_MidParentCollapsed; } } return; } //---------------------------------------------------------------------------- // Function: TL_OnInsertItem // // Inserts an item with the properties specified in the given LV_ITEM, // and returns a handle to the item inserted //---------------------------------------------------------------------------- HTLITEM TL_OnInsertItem( TL *ptl, TL_INSERTSTRUCT *ptlis ) { LV_ITEM *plvi; LIST_ENTRY *ple, *phead; BOOL bParentVisible; TLITEM *pItem, *pChanged, *pTemp; if (ptlis == NULL) { return NULL; } // // set up the new item // pItem = (TLITEM *)Malloc(sizeof(TLITEM)); if (pItem == NULL) { return NULL; } ZeroMemory(pItem, sizeof(TLITEM)); if (ptlis->plvi->mask & LVIF_TEXT) { pItem->pszText = StrDup(ptlis->plvi->pszText); if (pItem->pszText == NULL) { Free(pItem); return NULL; } } // // set up the private members // pItem->uiFlag = 0; pItem->nChildren = 0; InitializeListHead(&pItem->lhSubitems); InitializeListHead(&pItem->lhChildren); pItem->pParent = (TLITEM *)ptlis->hParent; if (pItem->pParent == NULL) { pItem->pParent = &ptl->root; } ++pItem->pParent->nChildren; pItem->iLevel = pItem->pParent->iLevel + 1; // // set up the listview item // plvi = ptlis->plvi; pItem->lvi = *plvi; pItem->lParam = plvi->lParam; pItem->lvi.lParam = (LPARAM)pItem; pItem->lvi.pszText = pItem->pszText; pItem->lvi.mask |= LVIF_PARAM; // // insert this item amongst its siblings // // switch (PtrToUlong(ptlis->hInsertAfter)) { if(ptlis->hInsertAfter == TLI_FIRST) { InsertHeadList(&pItem->pParent->lhChildren, &pItem->leSiblings); } else if (ptlis->hInsertAfter == TLI_LAST) { InsertTailList(&pItem->pParent->lhChildren, &pItem->leSiblings); } else if (ptlis->hInsertAfter == TLI_SORT) { phead = &pItem->pParent->lhChildren; for (ple = phead->Flink; ple != phead; ple = ple->Flink) { pTemp = (TLITEM *)CONTAINING_RECORD(ple, TLITEM, leSiblings); if (lstrcmp(pItem->pszText, pTemp->pszText) < 0) { break; } } InsertTailList(ple, &pItem->leSiblings); } else { TLITEM *pPrev; pPrev = (TLITEM *)ptlis->hInsertAfter; InsertHeadList(&pPrev->leSiblings, &pItem->leSiblings); } //} // // set the item's image. if this was inserted // as the last child, we need to change the image // for the original last child, if any; otherwise, // if this is the first child of its parent, the image // for the parent must be changed // TL_UpdateImage(ptl, pItem, &pChanged); if (pChanged != NULL) { TL_UpdateImage(ptl, pChanged, NULL); } if (pItem->pParent != &ptl->root && !TL_IsVisible(pItem->pParent)) { pItem->iIndex = -1; } else { // // the item's parent is visible; // update the indices after the item's parent // TL_UpdateListIndices(ptl, pItem->pParent); // // insert the item in the list if its parent is expanded // if (TL_IsExpanded(pItem->pParent)) { INT iItem, iCol; // // In owner-draw mode, there is a bug in the listview code // where if an item has the focus but is not selected, // and then a new item is inserted above it and selected, // the focus rectangle remains on the item which had the focus // // To work around this, clear the focus if it is on the item // below the item just inserted // iItem = ListView_GetNextItem(ptl->hwndList, -1, LVNI_FOCUSED); pItem->lvi.iItem = pItem->iIndex; pItem->lvi.iSubItem = 0; ListView_InsertItem(ptl->hwndList, &pItem->lvi); // // if the item below this had the focus, clear the focus // if (iItem != -1 && iItem >= pItem->iIndex) { ListView_SetItemState( ptl->hwndList, -1, 0, LVNI_FOCUSED ); } // // There is a bug in the listview code which shows up // when an item is inserted with no subitem, // and than an item is inserted above it with a subitem. // When a third item is inserted at the bottom of the list, // the insertion fails because there are now three items but // the last subitem belongs to item 1. // (See cairoshl\commctrl\listview.c, ListView_OnInsertItem()) // // We get around this by setting blank text for each column // for (iCol = 1; iCol < (INT)ptl->nColumns; iCol++) { ListView_SetItemText( ptl->hwndList, pItem->iIndex, iCol, TEXT("") ); } // // redraw the changed item as well // if (pChanged != NULL) { pChanged->lvi.iItem = pChanged->iIndex; ListView_RedrawItems( ptl->hwndList, pChanged->lvi.iItem, pChanged->lvi.iItem ); } } else if (pChanged != NULL && pChanged == pItem->pParent) { // // the parent is visible, and it has changed, so redraw it // ListView_RedrawItems( ptl->hwndList, pChanged->iIndex, pChanged->iIndex ); } } return (HTLITEM)pItem; } //---------------------------------------------------------------------------- // Function: TL_OnDeleteItem // // Removes the item with the specified handle from the treelist. //---------------------------------------------------------------------------- BOOL TL_OnDeleteItem( TL *ptl, HTLITEM hItem ) { TLITEM *pItem, *pChanged, *pParent; pItem = (TLITEM *)hItem; pParent = pItem->pParent; // // if the item is visible and expanded, // collapse it to simplify the deletion // if (TL_IsVisible(pItem) && TL_IsExpanded(pItem)) { TL_OnExpand(ptl, TLE_COLLAPSE, hItem); } // // see if there is a sibling after this item. // if there is, nothing changes when the item is deleted // pChanged = TL_OnGetNextItem(ptl, TLGN_NEXTSIBLING, (HTLITEM)pItem); if (pChanged != NULL) { pChanged = NULL; } else { // // this item is the last of its parent's children, so the change // is to the item's previous sibling, if any // pChanged = TL_OnGetNextItem(ptl, TLGN_PREVSIBLING, (HTLITEM)pItem); if (pChanged == NULL) { // // this item is its parent's only child, so the change // is to the item's parent // if (pParent != &ptl->root) { pChanged = pParent; } } } // // delete the item and its descendants // TL_DeleteAndNotify(ptl, pItem); // // if there was a side-effect, update the affected item // if (pChanged != NULL) { TL_UpdateImage(ptl, pChanged, NULL); } // // update the indices of the items below the deleted item // TL_UpdateListIndices(ptl, pParent); return TRUE; } //---------------------------------------------------------------------------- // Function: TL_DeleteAndNotify // // This function performs a recursive deletion on a subtree, // notifying the treelist's parent as each item is removed //---------------------------------------------------------------------------- VOID TL_DeleteAndNotify( TL *ptl, TLITEM *pItem ) { NMTREELIST nmtl; TLSUBITEM *pSubitem; LIST_ENTRY *ple, *phead; TLITEM *pChild, *pChanged; // // do deletions on all descendants first // note that the entry will be removed inside the recursive call, // hence we walk the last be always picking off its head // phead = &pItem->lhChildren; for (ple = phead->Flink; ple != phead; ple = phead->Flink) { pChild = (TLITEM *)CONTAINING_RECORD(ple, TLITEM, leSiblings); TL_DeleteAndNotify(ptl, pChild); } // // notify the owner before completing the deletion // nmtl.hdr.hwndFrom = ptl->hwnd; nmtl.hdr.code = TLN_DELETEITEM; nmtl.hItem = (HTLITEM)pItem; nmtl.lParam = pItem->lParam; TL_NotifyParent(ptl, (NMHDR *)&nmtl); // // remove the entry from the listview if it is visible // if (TL_IsVisible(pItem)) { ListView_DeleteItem(ptl->hwndList, pItem->iIndex); } // // remove the entry from the list of its siblings // RemoveEntryList(&pItem->leSiblings); --pItem->pParent->nChildren; if (pItem->pParent->nChildren == 0 && pItem->pParent != &ptl->root) { pItem->pParent->uiFlag &= ~TLI_EXPANDED; } // // free the memory used by all its subitems, and free this item itself // while (!IsListEmpty(&pItem->lhSubitems)) { ple = RemoveHeadList(&pItem->lhSubitems); pSubitem = (TLSUBITEM *)CONTAINING_RECORD(ple, TLSUBITEM, leItems); Free0(pSubitem->pszText); Free(pSubitem); } Free0(pItem->pszText); Free(pItem); } //---------------------------------------------------------------------------- // Function: TL_OnDeleteAllItems // // This function handles the case of deleting all items in the tree. //---------------------------------------------------------------------------- BOOL TL_OnDeleteAllItems( TL *ptl ) { LIST_ENTRY *ple, *phead; TLITEM *pItem, *pParent; ListView_DeleteAllItems(ptl->hwndList); phead = &ptl->root.lhChildren; for (ple = phead->Flink; ple != phead; ple = phead->Flink) { pItem = (TLITEM *)CONTAINING_RECORD(ple, TLITEM, leSiblings); TL_DeleteAndNotify(ptl, pItem); } return TRUE; } //---------------------------------------------------------------------------- // Function: TL_OnGetItem // // This function is called to retrieve a specific item from the treelist //---------------------------------------------------------------------------- BOOL TL_OnGetItem( TL *ptl, LV_ITEM *plvi ) { PTSTR psz; TLITEM *pItem; TLSUBITEM *pSubitem; LIST_ENTRY *ple, *phead; psz = NULL; pItem = (TLITEM *)UlongToPtr(plvi->iItem); // // get a pointer to the text for the item (or subitem) // if (plvi->iSubItem == 0) { psz = pItem->pszText; } else if (plvi->mask & LVIF_TEXT) { phead = &pItem->lhSubitems; for (ple = phead->Flink; ple != phead; ple = ple->Flink) { pSubitem = (TLSUBITEM *)CONTAINING_RECORD(ple, TLSUBITEM, leItems); if (pSubitem->iSubItem == plvi->iSubItem) { psz = pSubitem->pszText; break; } } if (psz == NULL) { return FALSE; } } // // retrieve the fields requested // if (plvi->mask & LVIF_TEXT) { lstrcpyn(plvi->pszText, psz, plvi->cchTextMax); } if (plvi->mask & LVIF_IMAGE) { plvi->iImage = pItem->lvi.iImage; } if (plvi->mask & LVIF_PARAM) { plvi->lParam = pItem->lParam; } if (plvi->mask & LVIF_STATE) { plvi->state = pItem->lvi.state; } return TRUE; } //---------------------------------------------------------------------------- // Function: TL_OnSetItem // // This function changes a specific item (or subitem). //---------------------------------------------------------------------------- BOOL TL_OnSetItem( TL *ptl, LV_ITEM *plvi ) { PTSTR *psz; UINT uiMask; BOOL bSuccess; TLITEM *pItem; TLSUBITEM *pSubitem; LIST_ENTRY *ple, *phead; psz = NULL; uiMask = 0; pItem = (TLITEM *)UlongToPtr(plvi->iItem); // // retrieve the text pointer for the item (or subitem) // if (plvi->iSubItem == 0) { psz = &pItem->pszText; } else if (plvi->mask & LVIF_TEXT) { // // search for the specified subitem // phead = &pItem->lhSubitems; for (ple = phead->Flink; ple != phead; ple = ple->Flink) { pSubitem = (TLSUBITEM *)CONTAINING_RECORD(ple, TLSUBITEM, leItems); if (pSubitem->iSubItem > plvi->iSubItem) { break; } else if (pSubitem->iSubItem == plvi->iSubItem) { psz = &pSubitem->pszText; break; } } if (psz == NULL) { // // create a new subitem // pSubitem = (TLSUBITEM *)Malloc(sizeof(TLSUBITEM)); if (pSubitem == NULL) { return FALSE; } InsertTailList(ple, &pSubitem->leItems); pSubitem->iSubItem = plvi->iSubItem; pSubitem->pszText = NULL; psz = &pSubitem->pszText; } } // // update the fields to be changed // if (plvi->mask & LVIF_TEXT) { PTSTR pszTemp; uiMask |= LVIF_TEXT; pszTemp = StrDup(plvi->pszText); if (!pszTemp) { return FALSE; } Free0(*psz); *psz = pszTemp; } if (plvi->mask & LVIF_IMAGE) { uiMask |= LVIF_IMAGE; pItem->lvi.iImage = plvi->iImage; } if (plvi->mask & LVIF_PARAM) { pItem->lParam = plvi->lParam; } if (plvi->mask & LVIF_STATE) { uiMask |= LVIF_STATE; pItem->lvi.stateMask = plvi->stateMask; pItem->lvi.state = plvi->state; } bSuccess = TRUE; pItem->lvi.mask |= uiMask; // // update the item's appearance if it is visible // if (TL_IsVisible(pItem)) { UINT uiOldMask = pItem->lvi.mask; pItem->lvi.mask = uiMask; if(NULL != psz) { pItem->lvi.pszText = *psz; } pItem->lvi.iSubItem = plvi->iSubItem; bSuccess = ListView_SetItem(ptl->hwndList, &pItem->lvi); if (bSuccess) { ListView_RedrawItems(ptl->hwndList, pItem->iIndex, pItem->iIndex); } pItem->lvi.mask = uiOldMask; pItem->lvi.pszText = pItem->pszText; pItem->lvi.iSubItem = 0; } return bSuccess; } //---------------------------------------------------------------------------- // Function: TL_GetItemCount // // This function retrieves a count of the items in the treelist //---------------------------------------------------------------------------- UINT TL_OnGetItemCount( TL *ptl ) { INT iCount = 0; // // count the items in the subtree rooted at the invisible root, // and decrement by one to exclude the root itself // TL_CountItems(&ptl->root, &iCount); return (UINT)(iCount - 1); } //---------------------------------------------------------------------------- // Function: TL_CountItems // // This function recursively counts the items in the specified subtree //---------------------------------------------------------------------------- VOID TL_CountItems( TLITEM *pParent, INT *piCount ) { TLITEM *pItem; LIST_ENTRY *ple, *phead; ++(*piCount); phead = &pParent->lhChildren; for (ple = phead->Flink; ple != phead; ple = ple->Flink) { pItem = (TLITEM *)CONTAINING_RECORD(ple, TLITEM, leSiblings); TL_CountItems(pItem, piCount); } return; } //---------------------------------------------------------------------------- // Function: TL_OnGetNextItem // // This function retrieves an item with a given property, // or relative to a specified item //---------------------------------------------------------------------------- HTLITEM TL_OnGetNextItem( TL *ptl, UINT uiFlag, HTLITEM hItem ) { TLITEM *pItem; LIST_ENTRY *ple, *phead; pItem = (TLITEM *)hItem; switch (uiFlag) { case TLGN_FIRST: { if (IsListEmpty(&ptl->root.lhChildren)) { return NULL; } ple = ptl->root.lhChildren.Flink; return (HTLITEM)CONTAINING_RECORD(ple, TLITEM, leSiblings); } case TLGN_PARENT: { if (pItem->pParent == &ptl->root) { return NULL; } return (HTLITEM)pItem->pParent; } case TLGN_CHILD: { if (IsListEmpty(&pItem->lhChildren)) { return NULL; } ple = pItem->lhChildren.Flink; return (HTLITEM)CONTAINING_RECORD(ple, TLITEM, leSiblings); } case TLGN_NEXTSIBLING: { phead = &pItem->pParent->lhChildren; ple = pItem->leSiblings.Flink; if (ple == phead) { return NULL; } return (HTLITEM)CONTAINING_RECORD(ple, TLITEM, leSiblings); } case TLGN_PREVSIBLING: { phead = &pItem->pParent->lhChildren; ple = pItem->leSiblings.Blink; if (ple == phead) { return NULL; } return (HTLITEM)CONTAINING_RECORD(ple, TLITEM, leSiblings); } case TLGN_ENUMERATE: { TLITEM *pNext; if (pItem == NULL) { return TL_OnGetNextItem(ptl, TLGN_FIRST, NULL); } pNext = (TLITEM *)TL_OnGetNextItem(ptl, TLGN_CHILD, hItem); if (pNext == NULL) { pNext = TL_OnGetNextItem(ptl, TLGN_NEXTSIBLING, hItem); if (pNext == NULL) { for (pItem = pItem->pParent; pItem != &ptl->root; pItem = pItem->pParent) { pNext = TL_OnGetNextItem( ptl, TLGN_NEXTSIBLING, (HTLITEM)pItem ); if (pNext != NULL) { break; } } } } return pNext; } case TLGN_SELECTION: { INT iItem; LV_ITEM lvi; iItem = ListView_GetNextItem(ptl->hwndList, -1, LVNI_SELECTED); if (iItem == -1) { iItem = ListView_GetNextItem(ptl->hwndList, -1, LVNI_FOCUSED); if (iItem == -1) { return NULL; } } lvi.iItem = iItem; lvi.iSubItem = 0; lvi.mask = LVIF_PARAM; if (!ListView_GetItem(ptl->hwndList, &lvi)) { return NULL; } return (HTLITEM)lvi.lParam; } } return NULL; } //---------------------------------------------------------------------------- // Function: TL_OnExpand // // This is called to expand or collapse an item, // or to toggle the expand-state of an item //---------------------------------------------------------------------------- BOOL TL_OnExpand( TL *ptl, UINT uiFlag, HTLITEM hItem ) { TLITEM *pItem; BOOL bSuccess; pItem = (TLITEM *)hItem; if (pItem->uiFlag & TLI_EXPANDED) { // item is expanded already, do nothing if (uiFlag == TLE_EXPAND) { return TRUE; } bSuccess = TL_ItemCollapse(ptl, pItem); } else { // item is collapsed already, do nothing if (uiFlag == TLE_COLLAPSE) { return TRUE; } bSuccess = TL_ItemExpand(ptl, pItem); } // // update the list indices and redraw the item expanded/collapsed // if (bSuccess) { ListView_RedrawItems(ptl->hwndList, pItem->iIndex, pItem->iIndex); } return bSuccess; } //---------------------------------------------------------------------------- // Function: TL_ItemCollapse // // Collapses an item //---------------------------------------------------------------------------- BOOL TL_ItemCollapse( TL *ptl, TLITEM *pItem ) { INT i, iItem; TLITEM *pChild; LIST_ENTRY *ple, *phead; if (pItem->nChildren == 0 || !TL_IsExpanded(pItem)) { return FALSE; } // // first collapse all descendants; // note that this is done in reverse order, // so that the indices of the higher items remain valid // while the lower ones are being removed // phead = &pItem->lhChildren; for (ple = phead->Blink; ple != phead; ple = ple->Blink) { pChild = (TLITEM *)CONTAINING_RECORD(ple, TLITEM, leSiblings); TL_ItemCollapse(ptl, pChild); } // // delete all this item's children (they are now collapsed); // since the listview shifts items up when an item is deleted, // we delete items n through m by deleting item n (m-n)+1 times // iItem = pItem->iIndex; for (i = 0; i < (INT)pItem->nChildren; i++) { ListView_DeleteItem(ptl->hwndList, iItem + 1); } pItem->uiFlag &= ~TLI_EXPANDED; TL_UpdateImage(ptl, pItem, NULL); TL_UpdateListIndices(ptl, pItem); return TRUE; } //---------------------------------------------------------------------------- // Function: TL_ItemExpand // // Expands an item //---------------------------------------------------------------------------- BOOL TL_ItemExpand( TL *ptl, TLITEM *pItem ) { INT i; TLITEM *pChild; TLSUBITEM *pSubitem; LIST_ENTRY *ple, *phead, *ples, *psubhead; if (pItem->nChildren == 0 || TL_IsExpanded(pItem)) { return FALSE; } // // update the expand-state and image for the item, // and then recompute the indices of its children // pItem->uiFlag |= TLI_EXPANDED; TL_UpdateImage(ptl, pItem, NULL); TL_UpdateListIndices(ptl, pItem); // // insert items below this one; // we also need to set the sub-item text for each inserted item // phead = &pItem->lhChildren; for (ple = phead->Flink; ple != phead; ple = ple->Flink) { pChild = (TLITEM *)CONTAINING_RECORD(ple, TLITEM, leSiblings); pChild->lvi.iItem = pChild->iIndex; pChild->lvi.iSubItem = 0; TL_UpdateImage(ptl, pChild, NULL); ListView_InsertItem(ptl->hwndList, &pChild->lvi); psubhead = &pChild->lhSubitems; i = 1; for (ples = psubhead->Flink; ples != psubhead; ples = ples->Flink) { pSubitem = (TLSUBITEM *)CONTAINING_RECORD(ples, TLSUBITEM, leItems); for ( ; i < pSubitem->iSubItem; i++) { ListView_SetItemText( ptl->hwndList, pChild->iIndex, i, TEXT("") ); } ListView_SetItemText( ptl->hwndList, pChild->iIndex, pSubitem->iSubItem, pSubitem->pszText ); ++i; } for ( ; i < (INT)ptl->nColumns; i++) { ListView_SetItemText( ptl->hwndList, pChild->iIndex, i, TEXT("") ); } } return TRUE; } //---------------------------------------------------------------------------- // Function: TL_OnInsertColumn // // Inserts a column. Memory for subitem's is not allocated until // the subitems' texts aer actually set in TL_OnSetItem //---------------------------------------------------------------------------- INT TL_OnInsertColumn( TL *ptl, INT iCol, LV_COLUMN *pCol ) { if ((iCol = ListView_InsertColumn(ptl->hwndList, iCol, pCol)) != -1) { ++ptl->nColumns; } return iCol; } //---------------------------------------------------------------------------- // Function: TL_OnDeleteColumn // // Deletes a column, and removes all subitems corresponding to the column //---------------------------------------------------------------------------- BOOL TL_OnDeleteColumn( TL *ptl, INT iCol ) { TLITEM *pItem; TLSUBITEM *pSubitem; LIST_ENTRY *ple, *phead; if (!ListView_DeleteColumn(ptl->hwndList, iCol)) { return FALSE; } --ptl->nColumns; // // delete the subitems which correspond to this column // pItem = NULL; while (pItem = TL_Enumerate(ptl, pItem)) { phead = &pItem->lhSubitems; for (ple = phead->Flink; ple != phead; ple = ple->Flink) { pSubitem = (TLSUBITEM *)CONTAINING_RECORD(ple, TLSUBITEM, leItems); if (pSubitem->iSubItem > iCol) { // // the column was never set, so do nothing // break; } else if (pSubitem->iSubItem == iCol) { RemoveEntryList(&pSubitem->leItems); Free0(pSubitem->pszText); Free(pSubitem); break; } } } return TRUE; } //---------------------------------------------------------------------------- // Function: TL_OnSetSelection // // Changes the currently selected treelist item //---------------------------------------------------------------------------- BOOL TL_OnSetSelection( TL *ptl, HTLITEM hItem ) { TLITEM *pItem; pItem = (TLITEM *)hItem; if (!TL_IsVisible(pItem)) { return FALSE; } ListView_SetItemState( ptl->hwndList, pItem->iIndex, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED | LVIS_FOCUSED ); return TRUE; } //---------------------------------------------------------------------------- // Function: TL_OnRedraw // // Forces a redraw of the treelist by invalidating its entire client area //---------------------------------------------------------------------------- BOOL TL_OnRedraw( TL *ptl ) { InvalidateRect(ptl->hwndList, NULL, TRUE); return TRUE; }