Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

3289 lines
80 KiB

//============================================================================
// 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 <windows.h>
#include <windowsx.h>
#include <commctrl.h>
#include <debug.h>
#include <nouiutil.h>
#include <uiutil.h>
#include <list.h>
#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;
}