//============================================================================
// 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;


    //
    // 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;
        TL_SetPtr(hwnd, ptl);

        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: {
            INT icol;
            icol = (LRESULT)SendMessage(
                        ptl->hwndList, LVM_GETCOLUMNWIDTH, wParam, lParam
                        );
            return icol;
        }

        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 = (UINT)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 (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.
//
// BUGBUG: 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);

    SelectObject(hdc, hfont);


    //
    // get the height of the listview's font
    //

    if (!GetTextMetrics(hdc, &tm)) { 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);
    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);
    }

    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
    //
    // BUGBUG: 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 ((INT)ptlis->hInsertAfter) {

        case (INT)TLI_FIRST: {

            InsertHeadList(&pItem->pParent->lhChildren, &pItem->leSiblings);
            break;
        }

        case (INT)TLI_LAST: {

            InsertTailList(&pItem->pParent->lhChildren, &pItem->leSiblings);
            break;
        }

        case (INT)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);

            break;
        }

        default: {

            TLITEM *pPrev;

            pPrev = (TLITEM *)ptlis->hInsertAfter;

            InsertHeadList(&pPrev->leSiblings, &pItem->leSiblings);
            break;
        }
    }



    //
    // 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;

            //
            // BUGBUG:
            // 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
                    );
            }


            //
            // BUGBUG:
            // 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 *)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 *)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;
        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;

    //
    // 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;
}



