From 5f9fd77db3fee49f2b61154f3c78c07f7ad16dba Mon Sep 17 00:00:00 2001 From: Chris Morgan Date: Wed, 13 Sep 2000 20:27:30 +0000 Subject: [PATCH] Merged main Wine changes into Corel's treeview control rewritten by Serge Ivanov and Andrew Lewycky. Fixed item focus behavior to match Windows. Fixed item selection when un/expanding items. Implemented WM_SETREDRAW. Added Corel's COMCTL32_CreateToolTip() helper function to commctrl.c. --- dlls/comctl32/comctl32.h | 7 + dlls/comctl32/commctrl.c | 42 + dlls/comctl32/treeview.c | 8488 +++++++++++++++++++++----------------- 3 files changed, 4643 insertions(+), 3894 deletions(-) diff --git a/dlls/comctl32/comctl32.h b/dlls/comctl32/comctl32.h index 93bdb0b46a4..9cb48e01f9c 100644 --- a/dlls/comctl32/comctl32.h +++ b/dlls/comctl32/comctl32.h @@ -7,6 +7,9 @@ * */ +#ifndef __WINE_COMCTL32_H +#define __WINE_COMCTL32_H + extern HMODULE COMCTL32_hModule; /* Property sheet / Wizard */ @@ -62,3 +65,7 @@ extern HMODULE COMCTL32_hModule; #define IDT_CHECK 401 +/* Internal function */ +HWND COMCTL32_CreateToolTip (HWND); + +#endif /* __WINE_COMCTL32_H */ diff --git a/dlls/comctl32/commctrl.c b/dlls/comctl32/commctrl.c index 52a183deb81..1c0fc2ef9e9 100644 --- a/dlls/comctl32/commctrl.c +++ b/dlls/comctl32/commctrl.c @@ -1150,3 +1150,45 @@ VOID WINAPI InitMUILanguage (LANGID uiLang) { COMCTL32_uiLang = uiLang; } + + +/*********************************************************************** + * COMCTL32_CreateToolTip [NOT AN API] + * + * Creates a tooltip for the control specified in hwnd and does all + * necessary setup and notifications. + * + * PARAMS + * hwndOwner [I] Handle to the window that will own the tool tip. + * + * RETURNS + * Success: Handle of tool tip window. + * Failure: NULL + */ +HWND +COMCTL32_CreateToolTip(HWND hwndOwner) +{ + HWND hwndToolTip; + + hwndToolTip = CreateWindowExA(0, TOOLTIPS_CLASSA, NULL, 0, + CW_USEDEFAULT, CW_USEDEFAULT, + CW_USEDEFAULT, CW_USEDEFAULT, hwndOwner, + 0, 0, 0); + + /* Send NM_TOOLTIPSCREATED notification */ + if (hwndToolTip) + { + NMTOOLTIPSCREATED nmttc; + + nmttc.hdr.hwndFrom = hwndOwner; + nmttc.hdr.idFrom = GetWindowLongA(hwndOwner, GWL_ID); + nmttc.hdr.code = NM_TOOLTIPSCREATED; + nmttc.hwndToolTips = hwndToolTip; + + SendMessageA(GetParent(hwndOwner), WM_NOTIFY, + (WPARAM)GetWindowLongA(hwndOwner, GWL_ID), + (LPARAM)&nmttc); + } + + return hwndToolTip; +} diff --git a/dlls/comctl32/treeview.c b/dlls/comctl32/treeview.c index 17b29c52034..ea451fd4f43 100644 --- a/dlls/comctl32/treeview.c +++ b/dlls/comctl32/treeview.c @@ -4,70 +4,40 @@ * Copyright 1998,1999 Alex Priem * Copyright 1999 Sylvain St-Germain * + * Note that TREEVIEW_INFO * and HTREEITEM are the same thing. + * + * Note2: All items always! have valid (allocated) pszText field. + * If item's text == LPSTR_TEXTCALLBACKA we allocate buffer + * of size TEXT_CALLBACK_SIZE in DoSetItem. + * We use callbackMask to keep track of fields to be updated. * * TODO: - * Using DPA to store the item ptr would be good. - * Node label edition is implemented but something happened in wine in the - * two last weeks of march 99 that broke it. - * refreshtreeview: - -small array containing info about positions. - -better implementation of RefreshItem: - 1) draw lines between parents - 2) draw items - 3) draw lines from parent<->items. - -implement partial drawing? - * -drag&drop: TVM_CREATEDRAGIMAGE should create drag bitmap. - * -scrollbars: horizontal scrollbar doesn't work. - * -Unicode messages - * -check custom draw - * -I_CHILDRENCALLBACK - * FIXME: check fontsize. (uRealItemHeight) - * test focusItem (redraw in different color) - uHotItem - Edit: needs timer - better implementation. - * WM_HSCROLL is broken. - * use separate routine to get item text/image. - * - * Separate drawing/calculation. + * missing notifications: NM_SETCURSOR, TVN_GETINFOTIP, TVN_KEYDOWN, + * TVN_SETDISPINFO, TVN_SINGLEEXPAND * - * FIXMEs (for personal use) - Expand: -ctlmacro expands twice ->toggle. - -DblClick: ctlmacro.exe's NM_DBLCLK seems to go wrong (returns FALSE). - -treehelper: stack corruption makes big window. - + * missing styles: TVS_FULLROWSELECT, TVS_INFOTIP, TVS_NOSCROLL, + * TVS_RTLREADING, TVS_TRACKSELECT + * + * missing item styles: TVIS_CUT, TVIS_EXPANDPARTIAL + * + * Make the insertion mark look right. + * Scroll (instead of repaint) as much as possible. */ - +#include #include +#include #include "winbase.h" #include "wingdi.h" #include "commctrl.h" #include "comctl32.h" #include "debugtools.h" -DEFAULT_DEBUG_CHANNEL(treeview); - -/* ffs should be in . */ - -/* Defines, since they do not need to return previous state, and nr - * has no side effects in this file. - */ -#define tv_test_bit(nr,bf) (((LPBYTE)bf)[nr>>3]&(1<<(nr&7))) -#define tv_set_bit(nr,bf) ((LPBYTE)bf)[nr>>3]|=(1<<(nr&7)) -#define tv_clear_bit(nr,bf) ((LPBYTE)bf)[nr>>3]&=~(1<<(nr&7)) - -#define MINIMUM_INDENT 10 -#define TV_REFRESH_DELAY 100 /* 100 ms delay between two refreshes */ -#define TV_DEFAULTITEMHEIGHT 16 -#define TVITEM_ALLOC 16 /* default nr of items to allocate at first try */ - - /* internal structures */ -typedef struct { - UINT mask; - HTREEITEM hItem; +typedef struct _TREEITEM /* HTREEITEM is a _TREEINFO *. */ +{ + UINT callbackMask; UINT state; UINT stateMask; LPSTR pszText; @@ -76,69 +46,80 @@ typedef struct { int iSelectedImage; int cChildren; LPARAM lParam; - int iIntegral; + int iIntegral; /* item height multiplier (1 is normal) */ int iLevel; /* indentation level:0=root level */ - COLORREF clrText; HTREEITEM parent; /* handle to parent or 0 if at root*/ HTREEITEM firstChild; /* handle to first child or 0 if no child*/ - HTREEITEM sibling; /* handle to next item in list, 0 if last */ - HTREEITEM upsibling; /* handle to previous item in list, 0 if first */ - int visible; + HTREEITEM lastChild; + HTREEITEM prevSibling; /* handle to prev item in list, 0 if first */ + HTREEITEM nextSibling; /* handle to next item in list, 0 if last */ RECT rect; - RECT text; - RECT expandBox; /* expand box (+/-) coordinate */ - RECT bitmap; - RECT statebitmap; + LONG linesOffset; + LONG stateOffset; + LONG imageOffset; + LONG textOffset; + LONG textWidth; /* horizontal text extent for pszText */ + LONG visibleOrder; /* visible ordering, 0 is first visible item */ } TREEVIEW_ITEM; -typedef struct +typedef struct tagTREEVIEW_INFO { - UINT uInternalStatus; - UINT bAutoSize; /* merge with uInternalStatus */ + HWND hwnd; + DWORD dwStyle; + HTREEITEM root; + UINT uInternalStatus; INT Timer; UINT uNumItems; /* number of valid TREEVIEW_ITEMs */ - UINT uNumPtrsAlloced; - HTREEITEM uMaxHandle; /* needed for delete_item */ - HTREEITEM TopRootItem; /* handle to first item in treeview */ INT cdmode; /* last custom draw setting */ UINT uScrollTime; /* max. time for scrolling in milliseconds*/ - UINT uItemHeight; /* item height, -1 for default item height */ - UINT uRealItemHeight;/* current item height in pixels */ - UINT uVisibleHeight; /* visible height of treeview in pixels */ - UINT uTotalHeight; /* total height of treeview in pixels */ - UINT uVisibleWidth; - UINT uTotalWidth; + BOOL bRedraw; /* if FALSE we validate but don't redraw in TREEVIEW_Paint() */ + + UINT uItemHeight; /* item height */ + BOOL bHeightSet; + + LONG clientWidth; /* width of control window */ + LONG clientHeight; /* height of control window */ + + LONG treeWidth; /* width of visible tree items */ + LONG treeHeight; /* height of visible tree items */ + UINT uIndent; /* indentation in pixels */ HTREEITEM selectedItem; /* handle to selected item or 0 if none */ - HTREEITEM focusItem; /* handle to item that has focus, 0 if none */ HTREEITEM hotItem; /* handle currently under cursor, 0 if none */ - HTREEITEM editItem; /* handle to item currently editted, 0 if none */ + HTREEITEM focusedItem; /* item that was under the cursor when WM_LBUTTONDOWN was received */ + HTREEITEM firstVisible; /* handle to first visible item */ + LONG maxVisibleOrder; HTREEITEM dropItem; /* handle to item selected by drag cursor */ HTREEITEM insertMarkItem; /* item after which insertion mark is placed */ - BOOL insertBeforeorAfter; /* flag used by TVM_SETINSERTMARK */ + BOOL insertBeforeorAfter; /* flag used by TVM_SETINSERTMARK */ HIMAGELIST dragList; /* Bitmap of dragged item */ - INT cx,cy; /* current x/y place in list */ - COLORREF clrBk; + LONG scrollX; + COLORREF clrBk; COLORREF clrText; COLORREF clrLine; - COLORREF clrInsertMark; + COLORREF clrInsertMark; HFONT hFont; HFONT hBoldFont; HWND hwndToolTip; + HWND hwndEdit; - WNDPROC wpEditOrig; /* needed for subclassing edit control */ - HIMAGELIST himlNormal; + WNDPROC wpEditOrig; /* orig window proc for subclassing edit */ + BOOL bIgnoreEditKillFocus; + BOOL bLabelChanged; + + HIMAGELIST himlNormal; + int normalImageHeight; + int normalImageWidth; HIMAGELIST himlState; - LPTVSORTCB pCallBackSort; /* ptr to TVSORTCB struct for callback sorting */ - TREEVIEW_ITEM *items; /* itemlist */ - INT *freeList; /* bitmap indicating which elements are valid */ - /* 1=valid, 0=free; */ - /* size of list= uNumPtrsAlloced/32 */ + int stateImageHeight; + int stateImageWidth; + HDPA items; } TREEVIEW_INFO; + /* bitflags for infoPtr->uInternalStatus */ #define TV_HSCROLL 0x01 /* treeview too large to fit in window */ @@ -150,136 +131,178 @@ typedef struct /* bitflags for infoPtr->timer */ -#define TV_REFRESH_TIMER 1 #define TV_EDIT_TIMER 2 -#define TV_REFRESH_TIMER_SET 1 -#define TV_EDIT_TIMER_SET 2 - -#define TREEVIEW_GetInfoPtr(hwnd) \ - ((TREEVIEW_INFO *) GetWindowLongA( hwnd, 0)) - -#define FOCUS_BORDER 3 - -static BOOL -TREEVIEW_SendSimpleNotify (HWND hwnd, UINT code); -static BOOL -TREEVIEW_SendTreeviewNotify (HWND hwnd, UINT code, UINT action, - HTREEITEM oldItem, HTREEITEM newItem); -static BOOL -TREEVIEW_SendTreeviewDnDNotify (HWND hwnd, UINT code, HTREEITEM dragItem, - POINT pt); -static BOOL -TREEVIEW_SendDispInfoNotify (HWND hwnd, TREEVIEW_ITEM *wineItem, - UINT code, UINT what); -static BOOL -TREEVIEW_SendCustomDrawNotify (HWND hwnd, DWORD dwDrawStage, HDC hdc, - RECT rc); -static BOOL -TREEVIEW_SendCustomDrawItemNotify (HWND hwnd, HDC hdc, - TREEVIEW_ITEM *tvItem, UINT uItemDrawState); -static LRESULT -TREEVIEW_DoSelectItem (HWND hwnd, INT action, HTREEITEM newSelect, INT cause); -static void -TREEVIEW_Refresh (HWND hwnd, HDC hdc); - -static LRESULT CALLBACK -TREEVIEW_Edit_SubclassProc (HWND hwnd, UINT uMsg, WPARAM wParam, - LPARAM lParam); - -static LRESULT -TREEVIEW_EditLabelA (HWND hwnd, WPARAM wParam, LPARAM lParam); - -static LRESULT -TREEVIEW_EndEditLabelNow (HWND hwnd, WPARAM wParam, LPARAM lParam); +#define TV_EDIT_TIMER_SET 2 +VOID TREEVIEW_Register (VOID); +VOID TREEVIEW_Unregister (VOID); -/* helper functions. Work with the assumption that validity of operands - is checked beforehand, and that tree state is valid. */ +DEFAULT_DEBUG_CHANNEL(treeview); -/* FIXME: MS documentation says `GetNextVisibleItem' returns NULL - if not successfull. Probably only applies to dereferencing infoPtr - (i.e. we are offered a valid treeview structure) - and not whether there is a next `visible' child. - FIXME: check other failures. - */ -/*************************************************************************** - * This method returns the TREEVIEW_ITEM object given the handle - */ -static TREEVIEW_ITEM* TREEVIEW_ValidItem( - TREEVIEW_INFO *infoPtr, - HTREEITEM handle) +#define TEXT_CALLBACK_SIZE 260 + +#define TREEVIEW_LEFT_MARGIN 8 + +#define MINIMUM_INDENT 19 + +#define CALLBACK_MASK_ALL (TVIF_TEXT|TVIF_CHILDREN|TVIF_IMAGE|TVIF_SELECTEDIMAGE) + +#define STATEIMAGEINDEX(x) (((x) >> 12) & 0x0f) +#define OVERLAYIMAGEINDEX(x) (((x) >> 8) & 0x0f) +#define ISVISIBLE(x) ((x)->visibleOrder >= 0) + + +typedef VOID (*TREEVIEW_ItemEnumFunc)(TREEVIEW_INFO *, TREEVIEW_ITEM *,LPVOID); + + +static VOID TREEVIEW_QueueRefresh(TREEVIEW_INFO *); +static VOID TREEVIEW_QueueItemRefresh(TREEVIEW_INFO *, TREEVIEW_ITEM *); + +static LRESULT TREEVIEW_DoSelectItem(TREEVIEW_INFO *, INT, HTREEITEM, INT); +static VOID TREEVIEW_SetFirstVisible(TREEVIEW_INFO *, TREEVIEW_ITEM *, BOOL); +static LRESULT TREEVIEW_EnsureVisible(TREEVIEW_INFO *, HTREEITEM, BOOL); +static LRESULT TREEVIEW_RButtonUp(TREEVIEW_INFO *, LPPOINT); +static LRESULT TREEVIEW_EndEditLabelNow(TREEVIEW_INFO *infoPtr, BOOL bCancel); +static VOID TREEVIEW_UpdateScrollBars(TREEVIEW_INFO *infoPtr); +static LRESULT TREEVIEW_HScroll(TREEVIEW_INFO *, WPARAM); + + +/* Random Utilities *****************************************************/ + +#ifdef NDEBUG +static inline void +TREEVIEW_VerifyTree(TREEVIEW_INFO *infoPtr) { - if ((!handle) || (handle>infoPtr->uMaxHandle)) - return NULL; + (void)infoPtr; +} +#else +/* The definition is at the end of the file. */ +static void TREEVIEW_VerifyTree(TREEVIEW_INFO *infoPtr); +#endif - if (tv_test_bit ((INT)handle, infoPtr->freeList)) - return NULL; +/* Returns the treeview private data if hwnd is a treeview. + * Otherwise returns an undefined value. */ +static TREEVIEW_INFO * +TREEVIEW_GetInfoPtr(HWND hwnd) +{ + return (TREEVIEW_INFO *)GetWindowLongA(hwnd, 0); +} - return &infoPtr->items[(INT)handle]; +/* Don't call this. Nothing wants an item index. */ +static inline int +TREEVIEW_GetItemIndex(TREEVIEW_INFO *infoPtr, HTREEITEM handle) +{ + assert(infoPtr != NULL); + + return DPA_GetPtrIndex(infoPtr->items, handle); } /*************************************************************************** - * This method returns the last expanded child item of a tree node + * This method checks that handle is an item for this tree. */ -static TREEVIEW_ITEM *TREEVIEW_GetLastListItem( - TREEVIEW_INFO *infoPtr, - TREEVIEW_ITEM *tvItem) +static BOOL +TREEVIEW_ValidItem(TREEVIEW_INFO *infoPtr, HTREEITEM handle) { - TREEVIEW_ITEM *wineItem = tvItem; + if (TREEVIEW_GetItemIndex(infoPtr, handle) == -1) + { + TRACE("invalid item %p\n", handle); + return FALSE; + } + else + return TRUE; +} - /* - * Get this item last sibling - */ - while (wineItem->sibling) - wineItem=& infoPtr->items [(INT)wineItem->sibling]; +static HFONT +TREEVIEW_CreateBoldFont(HFONT hOrigFont) +{ + LOGFONTA font; - /* - * If the last sibling has expanded children, restart. - */ - if ( ( wineItem->cChildren > 0 ) && ( wineItem->state & TVIS_EXPANDED) ) - return TREEVIEW_GetLastListItem( - infoPtr, - &(infoPtr->items[(INT)wineItem->firstChild])); + GetObjectA(hOrigFont, sizeof(font), &font); + font.lfWeight = FW_BOLD; + return CreateFontIndirectA(&font); +} - return wineItem; +static inline HFONT +TREEVIEW_FontForItem(TREEVIEW_INFO *infoPtr, TREEVIEW_ITEM *item) +{ + return (item->state & TVIS_BOLD) ? infoPtr->hBoldFont : infoPtr->hFont; +} + +/* for trace/debugging purposes only */ +static const char * +TREEVIEW_ItemName(TREEVIEW_ITEM *item) +{ + if (item == NULL) return ""; + if (item->pszText == LPSTR_TEXTCALLBACKA) return ""; + if (item->pszText == NULL) return ""; + return item->pszText; +} + +/* An item is not a child of itself. */ +static BOOL +TREEVIEW_IsChildOf(TREEVIEW_ITEM *parent, TREEVIEW_ITEM *child) +{ + do + { + child = child->parent; + if (child == parent) return TRUE; + } while (child != NULL); + + return FALSE; +} + + +/* Tree Traversal *******************************************************/ + +/*************************************************************************** + * This method returns the last expanded sibling or child child item + * of a tree node + */ +static TREEVIEW_ITEM * +TREEVIEW_GetLastListItem(TREEVIEW_INFO *infoPtr, TREEVIEW_ITEM *wineItem) +{ + if (!wineItem) + return NULL; + + while (wineItem->lastChild) + { + if (wineItem->state & TVIS_EXPANDED) + wineItem = wineItem->lastChild; + else + break; + } + + if (wineItem == infoPtr->root) + return NULL; + + return wineItem; } /*************************************************************************** - * This method returns the previous physical item in the list not + * This method returns the previous non-hidden item in the list not * considering the tree hierarchy. */ -static TREEVIEW_ITEM *TREEVIEW_GetPrevListItem( - TREEVIEW_INFO *infoPtr, - TREEVIEW_ITEM *tvItem) +static TREEVIEW_ITEM * +TREEVIEW_GetPrevListItem(TREEVIEW_INFO *infoPtr, TREEVIEW_ITEM *tvItem) { - if (tvItem->upsibling) - { - /* - * This item has a upsibling, get the last item. Since, GetLastListItem - * first looks at siblings, we must feed it with the first child. - */ - TREEVIEW_ITEM *upItem = &infoPtr->items[(INT)tvItem->upsibling]; - - if ( ( upItem->cChildren > 0 ) && ( upItem->state & TVIS_EXPANDED) ) - return TREEVIEW_GetLastListItem( - infoPtr, - &infoPtr->items[(INT)upItem->firstChild]); - else - return upItem; - } - else - { - /* - * this item does not have a upsibling, get the parent - */ - if (tvItem->parent) - return &infoPtr->items[(INT)tvItem->parent]; - } + if (tvItem->prevSibling) + { + /* This item has a prevSibling, get the last item in the sibling's tree. */ + TREEVIEW_ITEM *upItem = tvItem->prevSibling; - return NULL; + if ((upItem->state & TVIS_EXPANDED) && upItem->lastChild != NULL) + return TREEVIEW_GetLastListItem(infoPtr, upItem->lastChild); + else + return upItem; + } + else + { + /* this item does not have a prevSibling, get the parent */ + return (tvItem->parent != infoPtr->root) ? tvItem->parent : NULL; + } } @@ -287,36 +310,38 @@ static TREEVIEW_ITEM *TREEVIEW_GetPrevListItem( * This method returns the next physical item in the treeview not * considering the tree hierarchy. */ -static TREEVIEW_ITEM *TREEVIEW_GetNextListItem( - TREEVIEW_INFO *infoPtr, - TREEVIEW_ITEM *tvItem) +static TREEVIEW_ITEM * +TREEVIEW_GetNextListItem(TREEVIEW_INFO *infoPtr, TREEVIEW_ITEM *tvItem) { - TREEVIEW_ITEM *wineItem = NULL; + assert(tvItem != NULL); - /* - * If this item has children and is expanded, return the first child - */ - if ((tvItem->firstChild) && (tvItem->state & TVIS_EXPANDED)) - return (& infoPtr->items[(INT)tvItem->firstChild]); + /* + * If this item has children and is expanded, return the first child + */ + if ((tvItem->state & TVIS_EXPANDED) && tvItem->firstChild != NULL) + { + return tvItem->firstChild; + } - /* - * try to get the sibling - */ - if (tvItem->sibling) - return (& infoPtr->items[(INT)tvItem->sibling]); + /* + * try to get the sibling + */ + if (tvItem->nextSibling) + return tvItem->nextSibling; - /* - * Otherwise, get the parent's sibling. - */ - wineItem=tvItem; - while (wineItem->parent) { - wineItem=& infoPtr->items [(INT)wineItem->parent]; - if (wineItem->sibling) - return (& infoPtr->items [(INT)wineItem->sibling]); - } + /* + * Otherwise, get the parent's sibling. + */ + while (tvItem->parent) + { + tvItem = tvItem->parent; - return NULL; + if (tvItem->nextSibling) + return tvItem->nextSibling; + } + + return NULL; } /*************************************************************************** @@ -326,1422 +351,2347 @@ static TREEVIEW_ITEM *TREEVIEW_GetNextListItem( * Will scroll backward if count is <0. * forward if count is >0. */ -static TREEVIEW_ITEM *TREEVIEW_GetListItem( - TREEVIEW_INFO *infoPtr, - TREEVIEW_ITEM *tvItem, - LONG count) +static TREEVIEW_ITEM * +TREEVIEW_GetListItem(TREEVIEW_INFO *infoPtr, TREEVIEW_ITEM *wineItem, + LONG count) { - TREEVIEW_ITEM *previousItem = NULL; - TREEVIEW_ITEM *wineItem = tvItem; - LONG iter = 0; + TREEVIEW_ITEM *(*next_item)(TREEVIEW_INFO *, TREEVIEW_ITEM *); + TREEVIEW_ITEM *previousItem; - if (count > 0) - { - /* Find count item downward */ - while ((iter++ < count) && (wineItem != NULL)) - { - /* Keep a pointer to the previous in case we ask for more than we got */ - previousItem = wineItem; - wineItem = TREEVIEW_GetNextListItem(infoPtr, wineItem); - } + assert(wineItem != NULL); - if (wineItem == NULL) - wineItem = previousItem; - } - else if (count < 0) - { - /* Find count item upward */ - while ((iter-- > count) && (wineItem != NULL)) + if (count > 0) { - /* Keep a pointer to the previous in case we ask for more than we got */ - previousItem = wineItem; - wineItem = TREEVIEW_GetPrevListItem(infoPtr, wineItem); + next_item = TREEVIEW_GetNextListItem; } + else if (count < 0) + { + count = -count; + next_item = TREEVIEW_GetPrevListItem; + } + else + return wineItem; - if (wineItem == NULL) - wineItem = previousItem; - } - else - wineItem = NULL; + do + { + previousItem = wineItem; + wineItem = next_item(infoPtr, wineItem); - return wineItem; + } while (--count && wineItem != NULL); + + + return wineItem ? wineItem : previousItem; } - -/*************************************************************************** - * This method - */ -static void TREEVIEW_RemoveAllChildren( - HWND hwnd, - TREEVIEW_ITEM *parentItem) +/* Notifications ************************************************************/ + +static BOOL +TREEVIEW_SendSimpleNotify(TREEVIEW_INFO *infoPtr, UINT code) { - TREEVIEW_INFO *infoPtr = TREEVIEW_GetInfoPtr(hwnd); - TREEVIEW_ITEM *killItem; - INT kill; - - kill=(INT)parentItem->firstChild; - while (kill) { - tv_set_bit ( kill, infoPtr->freeList); - killItem=& infoPtr->items[kill]; - if (killItem->pszText!=LPSTR_TEXTCALLBACKA) - COMCTL32_Free (killItem->pszText); - TREEVIEW_SendTreeviewNotify (hwnd, TVN_DELETEITEMA, 0, (HTREEITEM)kill, 0); - if (killItem->firstChild) - TREEVIEW_RemoveAllChildren (hwnd, killItem); - kill=(INT)killItem->sibling; - } + NMHDR nmhdr; + HWND hwnd = infoPtr->hwnd; - if (parentItem->cChildren>0) { - infoPtr->uNumItems -= parentItem->cChildren; - parentItem->firstChild = 0; - parentItem->cChildren = 0; - } + TRACE("%x\n", code); + nmhdr.hwndFrom = hwnd; + nmhdr.idFrom = GetWindowLongA(hwnd, GWL_ID); + nmhdr.code = code; + return (BOOL)SendMessageA(GetParent(hwnd), WM_NOTIFY, + (WPARAM)nmhdr.idFrom, (LPARAM)&nmhdr); } +static VOID +TREEVIEW_TVItemFromItem(UINT mask, TVITEMA *tvItem, TREEVIEW_ITEM *item) +{ + tvItem->mask = mask; + tvItem->hItem = item; + tvItem->state = item->state; + tvItem->stateMask = 0; + tvItem->iImage = item->iImage; + tvItem->pszText = item->pszText; + tvItem->cchTextMax = item->cchTextMax; + tvItem->iImage = item->iImage; + tvItem->iSelectedImage = item->iSelectedImage; + tvItem->cChildren = item->cChildren; + tvItem->lParam = item->lParam; +} + +static BOOL +TREEVIEW_SendTreeviewNotify(TREEVIEW_INFO *infoPtr, UINT code, UINT action, + UINT mask, HTREEITEM oldItem, HTREEITEM newItem) +{ + HWND hwnd = infoPtr->hwnd; + NMTREEVIEWA nmhdr; + + TRACE("code:%x action:%x olditem:%p newitem:%p\n", + code, action, oldItem, newItem); + + ZeroMemory(&nmhdr, sizeof(NMTREEVIEWA)); + + nmhdr.hdr.hwndFrom = hwnd; + nmhdr.hdr.idFrom = GetWindowLongA(hwnd, GWL_ID); + nmhdr.hdr.code = code; + nmhdr.action = action; + + if (oldItem) + TREEVIEW_TVItemFromItem(mask, &nmhdr.itemOld, oldItem); + + if (newItem) + TREEVIEW_TVItemFromItem(mask, &nmhdr.itemNew, newItem); + + nmhdr.ptDrag.x = 0; + nmhdr.ptDrag.y = 0; + + return (BOOL)SendMessageA(GetParent(hwnd), WM_NOTIFY, + (WPARAM)GetWindowLongA(hwnd, GWL_ID), + (LPARAM)&nmhdr); +} + +static BOOL +TREEVIEW_SendTreeviewDnDNotify(TREEVIEW_INFO *infoPtr, UINT code, + HTREEITEM dragItem, POINT pt) +{ + HWND hwnd = infoPtr->hwnd; + NMTREEVIEWA nmhdr; + + TRACE("code:%x dragitem:%p\n", code, dragItem); + + nmhdr.hdr.hwndFrom = hwnd; + nmhdr.hdr.idFrom = GetWindowLongA(hwnd, GWL_ID); + nmhdr.hdr.code = code; + nmhdr.action = 0; + nmhdr.itemNew.mask = TVIF_STATE | TVIF_PARAM | TVIF_HANDLE; + nmhdr.itemNew.hItem = dragItem; + nmhdr.itemNew.state = dragItem->state; + nmhdr.itemNew.lParam = dragItem->lParam; + + nmhdr.ptDrag.x = pt.x; + nmhdr.ptDrag.y = pt.y; + + return (BOOL)SendMessageA(GetParent(hwnd), WM_NOTIFY, + (WPARAM)GetWindowLongA(hwnd, GWL_ID), + (LPARAM)&nmhdr); +} + + +static BOOL +TREEVIEW_SendCustomDrawNotify(TREEVIEW_INFO *infoPtr, DWORD dwDrawStage, + HDC hdc, RECT rc) +{ + HWND hwnd = infoPtr->hwnd; + NMTVCUSTOMDRAW nmcdhdr; + LPNMCUSTOMDRAW nmcd; + + TRACE("drawstage:%lx hdc:%x\n", dwDrawStage, hdc); + + nmcd = &nmcdhdr.nmcd; + nmcd->hdr.hwndFrom = hwnd; + nmcd->hdr.idFrom = GetWindowLongA(hwnd, GWL_ID); + nmcd->hdr.code = NM_CUSTOMDRAW; + nmcd->dwDrawStage = dwDrawStage; + nmcd->hdc = hdc; + nmcd->rc = rc; + nmcd->dwItemSpec = 0; + nmcd->uItemState = 0; + nmcd->lItemlParam = 0; + nmcdhdr.clrText = infoPtr->clrText; + nmcdhdr.clrTextBk = infoPtr->clrBk; + nmcdhdr.iLevel = 0; + + return (BOOL)SendMessageA(GetParent(hwnd), WM_NOTIFY, + (WPARAM)GetWindowLongA(hwnd, GWL_ID), + (LPARAM)&nmcdhdr); +} + + + +/* FIXME: need to find out when the flags in uItemState need to be set */ + +static BOOL +TREEVIEW_SendCustomDrawItemNotify(TREEVIEW_INFO *infoPtr, HDC hdc, + TREEVIEW_ITEM *wineItem, UINT uItemDrawState) +{ + HWND hwnd = infoPtr->hwnd; + NMTVCUSTOMDRAW nmcdhdr; + LPNMCUSTOMDRAW nmcd; + DWORD dwDrawStage, dwItemSpec; + UINT uItemState; + INT retval; + + dwDrawStage = CDDS_ITEM | uItemDrawState; + dwItemSpec = (DWORD)wineItem; + uItemState = 0; + if (wineItem->state & TVIS_SELECTED) + uItemState |= CDIS_SELECTED; + if (wineItem == infoPtr->selectedItem) + uItemState |= CDIS_FOCUS; + if (wineItem == infoPtr->hotItem) + uItemState |= CDIS_HOT; + + nmcd = &nmcdhdr.nmcd; + nmcd->hdr.hwndFrom = hwnd; + nmcd->hdr.idFrom = GetWindowLongA(hwnd, GWL_ID); + nmcd->hdr.code = NM_CUSTOMDRAW; + nmcd->dwDrawStage = dwDrawStage; + nmcd->hdc = hdc; + nmcd->rc = wineItem->rect; + nmcd->dwItemSpec = dwItemSpec; + nmcd->uItemState = uItemState; + nmcd->lItemlParam = wineItem->lParam; + nmcdhdr.clrText = infoPtr->clrText; + nmcdhdr.clrTextBk = infoPtr->clrBk; + nmcdhdr.iLevel = wineItem->iLevel; + + TRACE("drawstage:%lx hdc:%x item:%lx, itemstate:%x, lItemlParam:%lx\n", + nmcd->dwDrawStage, nmcd->hdc, nmcd->dwItemSpec, + nmcd->uItemState, nmcd->lItemlParam); + + retval = SendMessageA(GetParent(hwnd), WM_NOTIFY, + (WPARAM)GetWindowLongA(hwnd, GWL_ID), + (LPARAM)&nmcdhdr); + + infoPtr->clrText = nmcdhdr.clrText; + infoPtr->clrBk = nmcdhdr.clrTextBk; + return (BOOL)retval; +} + +static BOOL +TREEVIEW_BeginLabelEditNotify(TREEVIEW_INFO *infoPtr, TREEVIEW_ITEM *editItem) +{ + HWND hwnd = infoPtr->hwnd; + NMTVDISPINFOA tvdi; + + tvdi.hdr.hwndFrom = hwnd; + tvdi.hdr.idFrom = GetWindowLongA(hwnd, GWL_ID); + tvdi.hdr.code = TVN_BEGINLABELEDITA; + + tvdi.item.mask = TVIF_HANDLE | TVIF_STATE | TVIF_PARAM | TVIF_TEXT; + tvdi.item.hItem = editItem; + tvdi.item.state = editItem->state; + tvdi.item.lParam = editItem->lParam; + tvdi.item.pszText = editItem->pszText; + tvdi.item.cchTextMax = editItem->cchTextMax; + + return SendMessageA(GetParent(hwnd), WM_NOTIFY, tvdi.hdr.idFrom, + (LPARAM)&tvdi); +} static void -TREEVIEW_RemoveItem (HWND hwnd, TREEVIEW_ITEM *wineItem) - +TREEVIEW_UpdateDispInfo(TREEVIEW_INFO *infoPtr, TREEVIEW_ITEM *wineItem, + UINT mask) { - TREEVIEW_INFO *infoPtr = TREEVIEW_GetInfoPtr(hwnd); - TREEVIEW_ITEM *parentItem, *upsiblingItem, *siblingItem; - INT iItem; + NMTVDISPINFOA callback; + HWND hwnd = infoPtr->hwnd; - iItem=(INT)wineItem->hItem; - tv_set_bit(iItem,infoPtr->freeList); - infoPtr->uNumItems--; - parentItem=NULL; - if (wineItem->pszText!=LPSTR_TEXTCALLBACKA) - COMCTL32_Free (wineItem->pszText); + mask &= wineItem->callbackMask; - TREEVIEW_SendTreeviewNotify (hwnd, TVN_DELETEITEMA, 0, (HTREEITEM)iItem, 0); + if (mask == 0) return; - if (wineItem->firstChild) - TREEVIEW_RemoveAllChildren (hwnd,wineItem); + callback.hdr.hwndFrom = hwnd; + callback.hdr.idFrom = GetWindowLongA(hwnd, GWL_ID); + callback.hdr.code = TVN_GETDISPINFOA; - if (wineItem->parent) { - parentItem=& infoPtr->items [(INT)wineItem->parent]; - switch (parentItem->cChildren) { - case I_CHILDRENCALLBACK: - FIXME("we don't handle I_CHILDRENCALLBACK yet\n"); - break; - case 1: - parentItem->cChildren=0; - parentItem->firstChild=0; - return; - default: - parentItem->cChildren--; - if ((INT)parentItem->firstChild==iItem) - parentItem->firstChild=wineItem->sibling; + /* 'state' always contains valid value, as well as 'lParam'. + * All other parameters are uninitialized. + */ + callback.item.pszText = wineItem->pszText; + callback.item.cchTextMax = wineItem->cchTextMax; + callback.item.mask = mask; + callback.item.hItem = wineItem; + callback.item.state = wineItem->state; + callback.item.lParam = wineItem->lParam; + + /* If text is changed we need to recalculate textWidth */ + if (mask & TVIF_TEXT) + wineItem->textWidth = 0; + + SendMessageA(GetParent(hwnd), WM_NOTIFY, callback.hdr.idFrom, (LPARAM)&callback); + + /* It may have changed due to a call to SetItem. */ + mask &= wineItem->callbackMask; + + if ((mask & TVIF_TEXT) && callback.item.pszText != wineItem->pszText) + { + /* Instead of copying text into our buffer user specified its own */ + int len = max(lstrlenA(callback.item.pszText) + 1, TEXT_CALLBACK_SIZE); + LPSTR newText = COMCTL32_ReAlloc(wineItem->pszText, len); + + if (newText) + { + wineItem->pszText = newText; + strcpy(wineItem->pszText, callback.item.pszText); + wineItem->cchTextMax = len; + } + /* If ReAlloc fails we have nothing to do, but keep original text */ + } + + if (mask & TVIF_IMAGE) + wineItem->iImage = callback.item.iImage; + + if (mask & TVIF_SELECTEDIMAGE) + wineItem->iSelectedImage = callback.item.iSelectedImage; + + if (mask & TVIF_CHILDREN) + wineItem->cChildren = callback.item.cChildren; + + /* These members are now permanently set. */ + if (callback.item.mask & TVIF_DI_SETITEM) + wineItem->callbackMask &= ~callback.item.mask; +} + +/*************************************************************************** + * This function uses cChildren field to decide whether the item has + * children or not. + * Note: if this returns TRUE, the child items may not actually exist, + * they could be virtual. + * + * Just use wineItem->firstChild to check for physical children. + */ +static BOOL +TREEVIEW_HasChildren(TREEVIEW_INFO *infoPtr, TREEVIEW_ITEM *wineItem) +{ + TREEVIEW_UpdateDispInfo(infoPtr, wineItem, TVIF_CHILDREN); + + return wineItem->cChildren > 0; +} + + +/* Item Position ********************************************************/ + +/* Compute linesOffset, stateOffset, imageOffset, textOffset of an item. */ +static VOID +TREEVIEW_ComputeItemInternalMetrics(TREEVIEW_INFO *infoPtr, + TREEVIEW_ITEM *item) +{ + /* Same effect, different optimisation. */ +#if 0 + BOOL lar = ((infoPtr->dwStyle & TVS_LINESATROOT) + && (infoPtr->dwStyle & (TVS_HASLINES|TVS_HASBUTTONS))); +#else + BOOL lar = ((infoPtr->dwStyle + & (TVS_LINESATROOT|TVS_HASLINES|TVS_HASBUTTONS)) + > TVS_LINESATROOT); +#endif + + item->linesOffset = infoPtr->uIndent * (item->iLevel + lar - 1) + - infoPtr->scrollX; + item->stateOffset = item->linesOffset + infoPtr->uIndent; + item->imageOffset = item->stateOffset + + (STATEIMAGEINDEX(item->state) ? infoPtr->stateImageWidth : 0); + item->textOffset = item->imageOffset + infoPtr->normalImageWidth; +} + +static VOID +TREEVIEW_ComputeTextWidth(TREEVIEW_INFO *infoPtr, TREEVIEW_ITEM *item, HDC hDC) +{ + HDC hdc; + HFONT hOldFont=0; + SIZE sz; + + /* DRAW's OM docker creates items like this */ + if (item->pszText == NULL) + { + item->textWidth = 0; + return; + } + + if (item->textWidth != 0 && !(item->callbackMask & TVIF_TEXT)) + return; + + if (hDC != 0) + { + hdc = hDC; + } + else + { + hdc = GetDC(infoPtr->hwnd); + hOldFont = SelectObject(hdc, TREEVIEW_FontForItem(infoPtr, item)); + } + + GetTextExtentPoint32A(hdc, item->pszText, strlen(item->pszText), &sz); + item->textWidth = sz.cx; + + if (hDC == 0) + { + SelectObject(hdc, hOldFont); + ReleaseDC(0, hdc); + } +} + +static VOID +TREEVIEW_ComputeItemRect(TREEVIEW_INFO *infoPtr, TREEVIEW_ITEM *item) +{ + item->rect.top = infoPtr->uItemHeight * + (item->visibleOrder - infoPtr->firstVisible->visibleOrder); + + item->rect.bottom = item->rect.top + + infoPtr->uItemHeight * item->iIntegral; + + item->rect.left = 0; + item->rect.right = infoPtr->clientWidth; +} + +/* We know that only items after start need their order updated. */ +static void +TREEVIEW_RecalculateVisibleOrder(TREEVIEW_INFO *infoPtr, TREEVIEW_ITEM *start) +{ + TREEVIEW_ITEM *item; + int order; + + if (!start) + { + start = infoPtr->root->firstChild; + order = 0; + } + else + order = start->visibleOrder; + + for (item = start; item != NULL; + item = TREEVIEW_GetNextListItem(infoPtr, item)) + { + item->visibleOrder = order; + order += item->iIntegral; + } + + infoPtr->maxVisibleOrder = order; + + for (item = start; item != NULL; + item = TREEVIEW_GetNextListItem(infoPtr, item)) + { + TREEVIEW_ComputeItemRect(infoPtr, item); + } +} + + +/* Update metrics of all items in selected subtree. + * root must be expanded + */ +static VOID +TREEVIEW_UpdateSubTree(TREEVIEW_INFO *infoPtr, TREEVIEW_ITEM *root) +{ + TREEVIEW_ITEM *sibling; + HDC hdc; + HFONT hOldFont; + + if (!root->firstChild || !(root->state & TVIS_EXPANDED)) + return; + + root->state &= ~TVIS_EXPANDED; + sibling = TREEVIEW_GetNextListItem(infoPtr, root); + root->state |= TVIS_EXPANDED; + + hdc = GetDC(infoPtr->hwnd); + hOldFont = SelectObject(hdc, infoPtr->hFont); + + for (; root != sibling; + root = TREEVIEW_GetNextListItem(infoPtr, root)) + { + TREEVIEW_ComputeItemInternalMetrics(infoPtr, root); + + if (root->callbackMask & TVIF_TEXT) + TREEVIEW_UpdateDispInfo(infoPtr, root, TVIF_TEXT); + + if (root->textWidth == 0) + { + SelectObject(hdc, TREEVIEW_FontForItem(infoPtr, root)); + TREEVIEW_ComputeTextWidth(infoPtr, root, hdc); + } + } + + SelectObject(hdc, hOldFont); + ReleaseDC(infoPtr->hwnd, hdc); +} + +/* Item Allocation **********************************************************/ + +static TREEVIEW_ITEM * +TREEVIEW_AllocateItem(TREEVIEW_INFO *infoPtr) +{ + TREEVIEW_ITEM *newItem = COMCTL32_Alloc(sizeof(TREEVIEW_ITEM)); + + if (!newItem) + return NULL; + + if (DPA_InsertPtr(infoPtr->items, INT_MAX, newItem) == -1) + { + COMCTL32_Free(newItem); + return NULL; + } + + return newItem; +} + +/* Exact opposite of TREEVIEW_AllocateItem. In particular, it does not + * free item->pszText. */ +static void +TREEVIEW_FreeItem(TREEVIEW_INFO *infoPtr, TREEVIEW_ITEM *item) +{ + DPA_DeletePtr(infoPtr->items, DPA_GetPtrIndex(infoPtr->items, item)); + COMCTL32_Free(item); +} + + +/* Item Insertion *******************************************************/ + +/*************************************************************************** + * This method inserts newItem before sibling as a child of parent. + * sibling can be NULL, but only if parent has no children. + */ +static void +TREEVIEW_InsertBefore(TREEVIEW_ITEM *newItem, TREEVIEW_ITEM *sibling, + TREEVIEW_ITEM *parent) +{ + assert(newItem != NULL); + assert(parent != NULL); + + if (sibling != NULL) + { + assert(sibling->parent == parent); + + if (sibling->prevSibling != NULL) + sibling->prevSibling->nextSibling = newItem; + + newItem->prevSibling = sibling->prevSibling; + sibling->prevSibling = newItem; + } + else + newItem->prevSibling = NULL; + + newItem->nextSibling = sibling; + + if (parent->firstChild == sibling) + parent->firstChild = newItem; + + if (parent->lastChild == NULL) + parent->lastChild = newItem; +} + +/*************************************************************************** + * This method inserts newItem after sibling as a child of parent. + * sibling can be NULL, but only if parent has no children. + */ +static void +TREEVIEW_InsertAfter(TREEVIEW_ITEM *newItem, TREEVIEW_ITEM *sibling, + TREEVIEW_ITEM *parent) +{ + assert(newItem != NULL); + assert(parent != NULL); + + if (sibling != NULL) + { + assert(sibling->parent == parent); + + if (sibling->nextSibling != NULL) + sibling->nextSibling->prevSibling = newItem; + + newItem->nextSibling = sibling->nextSibling; + sibling->nextSibling = newItem; + } + else + newItem->nextSibling = NULL; + + newItem->prevSibling = sibling; + + if (parent->lastChild == sibling) + parent->lastChild = newItem; + + if (parent->firstChild == NULL) + parent->firstChild = newItem; +} + +static BOOL +TREEVIEW_DoSetItem(TREEVIEW_INFO *infoPtr, TREEVIEW_ITEM *wineItem, + const TVITEMEXA *tvItem) +{ + UINT callbackClear = 0; + UINT callbackSet = 0; + + /* Do this first in case it fails. */ + if (tvItem->mask & TVIF_TEXT) + { + if (tvItem->pszText != LPSTR_TEXTCALLBACKA) + { + int len = lstrlenA(tvItem->pszText) + 1; + LPSTR newText = COMCTL32_ReAlloc(wineItem->pszText, len); + + if (newText == NULL) return FALSE; + + callbackClear |= TVIF_TEXT; + + wineItem->pszText = newText; + wineItem->cchTextMax = len; + lstrcpynA(wineItem->pszText, tvItem->pszText, len); + } + else + { + callbackSet |= TVIF_TEXT; + + wineItem->pszText = COMCTL32_ReAlloc(wineItem->pszText, + TEXT_CALLBACK_SIZE); + wineItem->cchTextMax = TEXT_CALLBACK_SIZE; + } + } + + if (tvItem->mask & TVIF_CHILDREN) + { + wineItem->cChildren = tvItem->cChildren; + + if (wineItem->cChildren == I_CHILDRENCALLBACK) + callbackSet |= TVIF_CHILDREN; + else + callbackClear |= TVIF_CHILDREN; + } + + if (tvItem->mask & TVIF_IMAGE) + { + wineItem->iImage = tvItem->iImage; + + if (wineItem->iImage == I_IMAGECALLBACK) + callbackSet |= TVIF_IMAGE; + else + callbackClear |= TVIF_IMAGE; + } + + if (tvItem->mask & TVIF_SELECTEDIMAGE) + { + wineItem->iSelectedImage = tvItem->iSelectedImage; + + if (wineItem->iSelectedImage == I_IMAGECALLBACK) + callbackSet |= TVIF_SELECTEDIMAGE; + else + callbackClear |= TVIF_SELECTEDIMAGE; + } + + if (tvItem->mask & TVIF_PARAM) + wineItem->lParam = tvItem->lParam; + + /* If the application sets TVIF_INTEGRAL without + * supplying a TVITEMEX structure, it's toast. */ + if (tvItem->mask & TVIF_INTEGRAL) + wineItem->iIntegral = tvItem->iIntegral; + + if (tvItem->mask & TVIF_STATE) + { + TRACE("prevstate,state,mask:%x,%x,%x\n", wineItem->state, tvItem->state, + tvItem->stateMask); + wineItem->state &= ~tvItem->stateMask; + wineItem->state |= (tvItem->state & tvItem->stateMask); + } + + wineItem->callbackMask |= callbackSet; + wineItem->callbackMask &= ~callbackClear; + + return TRUE; +} + +/* Note that the new item is pre-zeroed. */ +static LRESULT +TREEVIEW_InsertItemA(TREEVIEW_INFO *infoPtr, LPARAM lParam) +{ + const TVINSERTSTRUCTA *ptdi = (LPTVINSERTSTRUCTA) lParam; + const TVITEMEXA *tvItem = &ptdi->DUMMYUNIONNAME.itemex; + HTREEITEM insertAfter; + TREEVIEW_ITEM *newItem, *parentItem; + BOOL bTextUpdated = FALSE; + + if (ptdi->hParent == TVI_ROOT || ptdi->hParent == 0) + { + parentItem = infoPtr->root; + } + else + { + parentItem = ptdi->hParent; + + if (!TREEVIEW_ValidItem(infoPtr, parentItem)) + { + WARN("invalid parent %p\n", parentItem); + return (LRESULT)(HTREEITEM)NULL; + } + } + + insertAfter = ptdi->hInsertAfter; + + /* Validate this now for convenience. */ + switch ((DWORD)insertAfter) + { + case (DWORD)TVI_FIRST: + case (DWORD)TVI_LAST: + case (DWORD)TVI_SORT: + break; + + default: + if (!TREEVIEW_ValidItem(infoPtr, insertAfter) || + insertAfter->parent != parentItem) + { + WARN("invalid insert after %p\n", insertAfter); + insertAfter = TVI_LAST; + } + } + + TRACE("parent %p position %p: %s\n", parentItem, insertAfter, + (tvItem->mask & TVIF_TEXT) + ? ((tvItem->pszText == LPSTR_TEXTCALLBACKA) ? "" + : tvItem->pszText) + : ""); + + newItem = TREEVIEW_AllocateItem(infoPtr); + if (newItem == NULL) + return (LRESULT)(HTREEITEM)NULL; + + newItem->parent = parentItem; + newItem->iIntegral = 1; + + if (!TREEVIEW_DoSetItem(infoPtr, newItem, tvItem)) + return (LRESULT)(HTREEITEM)NULL; + + /* After this point, nothing can fail. (Except for TVI_SORT.) */ + + infoPtr->uNumItems++; + + switch ((DWORD)insertAfter) + { + case (DWORD)TVI_FIRST: + TREEVIEW_InsertBefore(newItem, parentItem->firstChild, parentItem); + if (infoPtr->firstVisible == parentItem->firstChild) + TREEVIEW_SetFirstVisible(infoPtr, newItem, TRUE); + break; + + case (DWORD)TVI_LAST: + TREEVIEW_InsertAfter(newItem, parentItem->lastChild, parentItem); + break; + + /* hInsertAfter names a specific item we want to insert after */ + default: + TREEVIEW_InsertAfter(newItem, insertAfter, insertAfter->parent); + break; + + case (DWORD)TVI_SORT: + { + TREEVIEW_ITEM *aChild; + TREEVIEW_ITEM *previousChild = NULL; + BOOL bItemInserted = FALSE; + + aChild = parentItem->firstChild; + + bTextUpdated = TRUE; + TREEVIEW_UpdateDispInfo(infoPtr, newItem, TVIF_TEXT); + + /* Iterate the parent children to see where we fit in */ + while (aChild != NULL) + { + INT comp; + + TREEVIEW_UpdateDispInfo(infoPtr, aChild, TVIF_TEXT); + comp = lstrcmpA(newItem->pszText, aChild->pszText); + + if (comp < 0) /* we are smaller than the current one */ + { + TREEVIEW_InsertBefore(newItem, aChild, parentItem); + bItemInserted = TRUE; + break; } - } + else if (comp > 0) /* we are bigger than the current one */ + { + previousChild = aChild; - if (iItem==(INT)infoPtr->TopRootItem) - infoPtr->TopRootItem=(HTREEITEM)wineItem->sibling; - if (wineItem->upsibling) { - upsiblingItem=& infoPtr->items [(INT)wineItem->upsibling]; - upsiblingItem->sibling=wineItem->sibling; - } - if (wineItem->sibling) { - siblingItem=& infoPtr->items [(INT)wineItem->sibling]; - siblingItem->upsibling=wineItem->upsibling; - } + /* This will help us to exit if there is no more sibling */ + aChild = (aChild->nextSibling == 0) + ? NULL + : aChild->nextSibling; + + /* Look at the next item */ + continue; + } + else if (comp == 0) + { + /* + * An item with this name is already existing, therefore, + * we add after the one we found + */ + TREEVIEW_InsertAfter(newItem, aChild, parentItem); + bItemInserted = TRUE; + break; + } + } + + /* + * we reach the end of the child list and the item as not + * yet been inserted, therefore, insert it after the last child. + */ + if ((!bItemInserted) && (aChild == NULL)) + TREEVIEW_InsertAfter(newItem, previousChild, parentItem); + + break; + } + } + + + TRACE("new item %p; parent %p, mask %x\n", newItem, + newItem->parent, tvItem->mask); + + newItem->iLevel = newItem->parent->iLevel + 1; + + if (newItem->parent->cChildren == 0) + newItem->parent->cChildren = 1; + + if (infoPtr->dwStyle & TVS_CHECKBOXES) + { + if (STATEIMAGEINDEX(newItem->state) == 0) + newItem->state |= INDEXTOSTATEIMAGEMASK(1); + } + + if (infoPtr->firstVisible == NULL) + infoPtr->firstVisible = newItem; + + TREEVIEW_VerifyTree(infoPtr); + + if (parentItem == infoPtr->root || + (ISVISIBLE(parentItem) && parentItem->state & TVIS_EXPANDED)) + { + TREEVIEW_ITEM *prev = TREEVIEW_GetPrevListItem(infoPtr, newItem); + + TREEVIEW_RecalculateVisibleOrder(infoPtr, prev); + TREEVIEW_ComputeItemInternalMetrics(infoPtr, newItem); + + if (!bTextUpdated) + TREEVIEW_UpdateDispInfo(infoPtr, newItem, TVIF_TEXT); + + TREEVIEW_ComputeTextWidth(infoPtr, newItem, 0); + TREEVIEW_UpdateScrollBars(infoPtr); + TREEVIEW_QueueRefresh(infoPtr); + } + else + { + newItem->visibleOrder = -1; + + /* refresh treeview if newItem is the first item inserted under parentItem */ + if (ISVISIBLE(parentItem) && newItem->prevSibling == newItem->nextSibling) + { + /* parent got '+' - update it */ + TREEVIEW_QueueItemRefresh(infoPtr, parentItem); + } + } + + return (LRESULT)newItem; } - - - -/* Note:TREEVIEW_RemoveTree doesn't remove infoPtr itself */ - -static void TREEVIEW_RemoveTree (HWND hwnd) - -{ - TREEVIEW_INFO *infoPtr = TREEVIEW_GetInfoPtr(hwnd); - TREEVIEW_ITEM *killItem; - int i; - - for (i = 1; i <= (INT)infoPtr->uMaxHandle; i++) - if (!tv_test_bit (i, infoPtr->freeList)) { - killItem = &infoPtr->items[i]; - if (killItem->pszText != LPSTR_TEXTCALLBACKA) - COMCTL32_Free (killItem->pszText); - TREEVIEW_SendTreeviewNotify(hwnd, TVN_DELETEITEMA, 0, - killItem->hItem, 0); - } - if (infoPtr->uNumPtrsAlloced) { - COMCTL32_Free (infoPtr->items); - COMCTL32_Free (infoPtr->freeList); - infoPtr->uNumItems = 0; - infoPtr->uNumPtrsAlloced = 0; - infoPtr->uMaxHandle = 0; - infoPtr->TopRootItem = 0; - } -} - - - - - - - static LRESULT -TREEVIEW_GetImageList (HWND hwnd, WPARAM wParam, LPARAM lParam) +TREEVIEW_InsertItemW(TREEVIEW_INFO *infoPtr, LPARAM lParam) { - TREEVIEW_INFO *infoPtr = TREEVIEW_GetInfoPtr(hwnd); + TVINSERTSTRUCTW *tvisW; + TVINSERTSTRUCTA tvisA; + LRESULT lRes; - TRACE("\n"); + tvisW = (LPTVINSERTSTRUCTW) lParam; - if ((INT)wParam == TVSIL_NORMAL) - return (LRESULT) infoPtr->himlNormal; - if ((INT)wParam == TVSIL_STATE) - return (LRESULT) infoPtr->himlState; + tvisA.hParent = tvisW->hParent; + tvisA.hInsertAfter = tvisW->hInsertAfter; + + tvisA.DUMMYUNIONNAME.item.mask = tvisW->DUMMYUNIONNAME.item.mask; + tvisA.DUMMYUNIONNAME.item.hItem = tvisW->DUMMYUNIONNAME.item.hItem; + tvisA.DUMMYUNIONNAME.item.state = tvisW->DUMMYUNIONNAME.item.state; + tvisA.DUMMYUNIONNAME.item.stateMask = tvisW->DUMMYUNIONNAME.item.stateMask; + tvisA.DUMMYUNIONNAME.item.cchTextMax = + tvisW->DUMMYUNIONNAME.item.cchTextMax; + + if (tvisW->DUMMYUNIONNAME.item.pszText) + { + if (tvisW->DUMMYUNIONNAME.item.pszText != LPSTR_TEXTCALLBACKW) + { + int len = lstrlenW(tvisW->DUMMYUNIONNAME.item.pszText) + 1; + + tvisA.DUMMYUNIONNAME.item.pszText = COMCTL32_Alloc(len); + lstrcpyWtoA(tvisA.DUMMYUNIONNAME.item.pszText, + tvisW->DUMMYUNIONNAME.item.pszText); + } + else + { + tvisA.DUMMYUNIONNAME.item.pszText = LPSTR_TEXTCALLBACKA; + tvisA.DUMMYUNIONNAME.item.cchTextMax = 0; + } + } + + tvisA.DUMMYUNIONNAME.item.iImage = tvisW->DUMMYUNIONNAME.item.iImage; + tvisA.DUMMYUNIONNAME.item.iSelectedImage = + tvisW->DUMMYUNIONNAME.item.iSelectedImage; + tvisA.DUMMYUNIONNAME.item.cChildren = tvisW->DUMMYUNIONNAME.item.cChildren; + tvisA.DUMMYUNIONNAME.item.lParam = tvisW->DUMMYUNIONNAME.item.lParam; + + lRes = TREEVIEW_InsertItemA(infoPtr, (LPARAM)&tvisA); + + if (tvisA.DUMMYUNIONNAME.item.pszText != LPSTR_TEXTCALLBACKA) + { + COMCTL32_Free(tvisA.DUMMYUNIONNAME.item.pszText); + } + + return lRes; + +} + + +/* Item Deletion ************************************************************/ +static void +TREEVIEW_RemoveItem(TREEVIEW_INFO *infoPtr, TREEVIEW_ITEM *wineItem); + +static void +TREEVIEW_RemoveAllChildren(TREEVIEW_INFO *infoPtr, TREEVIEW_ITEM *parentItem) +{ + TREEVIEW_ITEM *kill = parentItem->firstChild; + + while (kill != NULL) + { + TREEVIEW_ITEM *next = kill->nextSibling; + + TREEVIEW_RemoveItem(infoPtr, kill); + + kill = next; + } + + assert(parentItem->cChildren <= 0); /* I_CHILDRENCALLBACK or 0 */ + assert(parentItem->firstChild == NULL); + assert(parentItem->lastChild == NULL); +} + +static void +TREEVIEW_UnlinkItem(TREEVIEW_ITEM *item) +{ + TREEVIEW_ITEM *parentItem = item->parent; + + assert(item != NULL); + assert(item->parent != NULL); /* i.e. it must not be the root */ + + if (parentItem->firstChild == item) + parentItem->firstChild = item->nextSibling; + + if (parentItem->lastChild == item) + parentItem->lastChild = item->prevSibling; + + if (parentItem->firstChild == NULL && parentItem->lastChild == NULL + && parentItem->cChildren > 0) + parentItem->cChildren = 0; + + if (item->prevSibling) + item->prevSibling->nextSibling = item->nextSibling; + + if (item->nextSibling) + item->nextSibling->prevSibling = item->prevSibling; +} + +static void +TREEVIEW_RemoveItem(TREEVIEW_INFO *infoPtr, TREEVIEW_ITEM *wineItem) +{ + TRACE("%p, (%s)\n", wineItem, TREEVIEW_ItemName(wineItem)); + + TREEVIEW_SendTreeviewNotify(infoPtr, TVN_DELETEITEMA, + TVIF_HANDLE | TVIF_PARAM, 0, wineItem, 0); + + if (wineItem->firstChild) + TREEVIEW_RemoveAllChildren(infoPtr, wineItem); + + TREEVIEW_UnlinkItem(wineItem); + + infoPtr->uNumItems--; + + if (wineItem->pszText != LPSTR_TEXTCALLBACKA) + COMCTL32_Free(wineItem->pszText); + + TREEVIEW_FreeItem(infoPtr, wineItem); +} + + +/* Empty out the tree. */ +static void +TREEVIEW_RemoveTree(TREEVIEW_INFO *infoPtr) +{ + TREEVIEW_RemoveAllChildren(infoPtr, infoPtr->root); + + assert(infoPtr->uNumItems == 0); /* root isn't counted in uNumItems */ +} + +static LRESULT +TREEVIEW_DeleteItem(TREEVIEW_INFO *infoPtr, HTREEITEM wineItem) +{ + TREEVIEW_ITEM *oldSelection = infoPtr->selectedItem; + TREEVIEW_ITEM *newSelection = oldSelection; + TREEVIEW_ITEM *newFirstVisible = NULL; + TREEVIEW_ITEM *parent, *prev = NULL; + BOOL visible = FALSE; + + if (wineItem == TVI_ROOT) + { + TRACE("TVI_ROOT\n"); + parent = infoPtr->root; + newSelection = NULL; + visible = TRUE; + TREEVIEW_RemoveTree(infoPtr); + } + else + { + if (!TREEVIEW_ValidItem(infoPtr, wineItem)) + return FALSE; + + TRACE("%p (%s)\n", wineItem, TREEVIEW_ItemName(wineItem)); + parent = wineItem->parent; + + if (ISVISIBLE(wineItem)) + { + prev = TREEVIEW_GetPrevListItem(infoPtr, wineItem); + visible = TRUE; + } + + if (infoPtr->selectedItem != NULL + && (wineItem == infoPtr->selectedItem + || TREEVIEW_IsChildOf(wineItem, infoPtr->selectedItem))) + { + if (wineItem->nextSibling) + newSelection = wineItem->nextSibling; + else if (wineItem->parent != infoPtr->root) + newSelection = wineItem->parent; + } + + if (infoPtr->firstVisible == wineItem) + { + if (wineItem->nextSibling) + newFirstVisible = wineItem->nextSibling; + else if (wineItem->prevSibling) + newFirstVisible = wineItem->prevSibling; + else if (wineItem->parent != infoPtr->root) + newFirstVisible = wineItem->parent; + } + else + newFirstVisible = infoPtr->firstVisible; + + TREEVIEW_RemoveItem(infoPtr, wineItem); + } + + /* Don't change if somebody else already has. */ + if (oldSelection == infoPtr->selectedItem) + { + if (TREEVIEW_ValidItem(infoPtr, newSelection)) + TREEVIEW_DoSelectItem(infoPtr, TVGN_CARET, newSelection, TVC_UNKNOWN); + else + infoPtr->selectedItem = 0; + } + + /* Validate insertMark dropItem. + * hotItem ??? - used for comparision only. + */ + if (!TREEVIEW_ValidItem(infoPtr, infoPtr->insertMarkItem)) + infoPtr->insertMarkItem = 0; + + if (!TREEVIEW_ValidItem(infoPtr, infoPtr->dropItem)) + infoPtr->dropItem = 0; + + if (!TREEVIEW_ValidItem(infoPtr, newFirstVisible)) + newFirstVisible = infoPtr->root->firstChild; + + TREEVIEW_VerifyTree(infoPtr); + + + if (visible) + { + TREEVIEW_RecalculateVisibleOrder(infoPtr, prev); + TREEVIEW_SetFirstVisible(infoPtr, newFirstVisible, TRUE); + TREEVIEW_UpdateScrollBars(infoPtr); + TREEVIEW_QueueRefresh(infoPtr); + } + else if (ISVISIBLE(parent) && !TREEVIEW_HasChildren(infoPtr, parent)) + { + /* parent lost '+/-' - update it */ + TREEVIEW_QueueItemRefresh(infoPtr, parent); + } + + return TRUE; +} + + +/* Get/Set Messages *********************************************************/ +static LRESULT +TREEVIEW_SetRedraw(TREEVIEW_INFO* infoPtr, WPARAM wParam, LPARAM lParam) +{ + if(wParam) + infoPtr->bRedraw = TRUE; + else + infoPtr->bRedraw = FALSE; return 0; } static LRESULT -TREEVIEW_SetImageList (HWND hwnd, WPARAM wParam, LPARAM lParam) +TREEVIEW_GetIndent(TREEVIEW_INFO *infoPtr) { - TREEVIEW_INFO *infoPtr = TREEVIEW_GetInfoPtr(hwnd); - HIMAGELIST himlTemp; + TRACE("\n"); + return infoPtr->uIndent; +} - TRACE("%x,%lx\n", wParam, lParam); - switch ((INT)wParam) { - case TVSIL_NORMAL: - himlTemp = infoPtr->himlNormal; - infoPtr->himlNormal = (HIMAGELIST)lParam; - return (LRESULT)himlTemp; +static LRESULT +TREEVIEW_SetIndent(TREEVIEW_INFO *infoPtr, UINT newIndent) +{ + TRACE("\n"); - case TVSIL_STATE: - himlTemp = infoPtr->himlState; - infoPtr->himlState = (HIMAGELIST)lParam; - return (LRESULT)himlTemp; + if (newIndent < MINIMUM_INDENT) + newIndent = MINIMUM_INDENT; + + if (infoPtr->uIndent != newIndent) + { + infoPtr->uIndent = newIndent; + TREEVIEW_UpdateSubTree(infoPtr, infoPtr->root); + TREEVIEW_UpdateScrollBars(infoPtr); + TREEVIEW_QueueRefresh(infoPtr); } - return (LRESULT)NULL; + return 0; } - static LRESULT -TREEVIEW_SetItemHeight (HWND hwnd, WPARAM wParam) +TREEVIEW_GetToolTips(TREEVIEW_INFO *infoPtr) { - TREEVIEW_INFO *infoPtr = TREEVIEW_GetInfoPtr(hwnd); - INT cx,cy,prevHeight=infoPtr->uItemHeight; - - TRACE("\n"); - if (wParam==-1) { - infoPtr->uItemHeight=-1; - return prevHeight; - } - - ImageList_GetIconSize (infoPtr->himlNormal, &cx, &cy); - - if (wParam>cy) cy=wParam; - infoPtr->uItemHeight=cy; - - if (!( GetWindowLongA( hwnd, GWL_STYLE) & TVS_NONEVENHEIGHT)) - infoPtr->uItemHeight = (INT) wParam & 0xfffffffe; - return prevHeight; + TRACE("\n"); + return infoPtr->hwndToolTip; } static LRESULT -TREEVIEW_GetItemHeight (HWND hwnd) +TREEVIEW_SetToolTips(TREEVIEW_INFO *infoPtr, HWND hwndTT) { - TREEVIEW_INFO *infoPtr = TREEVIEW_GetInfoPtr(hwnd); - - TRACE("\n"); - return infoPtr->uItemHeight; -} - -static LRESULT -TREEVIEW_GetLineColor (HWND hwnd, WPARAM wParam, LPARAM lParam) -{ - TREEVIEW_INFO *infoPtr = TREEVIEW_GetInfoPtr(hwnd); + HWND prevToolTip; - TRACE("\n"); - return (LRESULT) infoPtr->clrLine; + TRACE("\n"); + prevToolTip = infoPtr->hwndToolTip; + infoPtr->hwndToolTip = hwndTT; + + return prevToolTip; +} + + +static LRESULT +TREEVIEW_GetScrollTime(TREEVIEW_INFO *infoPtr) +{ + return infoPtr->uScrollTime; } static LRESULT -TREEVIEW_SetLineColor (HWND hwnd, WPARAM wParam, LPARAM lParam) +TREEVIEW_SetScrollTime(TREEVIEW_INFO *infoPtr, UINT uScrollTime) { - TREEVIEW_INFO *infoPtr = TREEVIEW_GetInfoPtr(hwnd); - COLORREF prevColor=infoPtr->clrLine; + UINT uOldScrollTime = infoPtr->uScrollTime; - TRACE("\n"); - infoPtr->clrLine=(COLORREF) lParam; - return (LRESULT) prevColor; + infoPtr->uScrollTime = min(uScrollTime, 100); + + return uOldScrollTime; +} + + +static LRESULT +TREEVIEW_GetImageList(TREEVIEW_INFO *infoPtr, WPARAM wParam) +{ + TRACE("\n"); + + switch (wParam) + { + case (WPARAM)TVSIL_NORMAL: + return (LRESULT)infoPtr->himlNormal; + + case (WPARAM)TVSIL_STATE: + return (LRESULT)infoPtr->himlState; + + default: + return 0; + } } static LRESULT -TREEVIEW_GetInsertMarkColor (HWND hwnd, WPARAM wParam, LPARAM lParam) +TREEVIEW_SetImageList(TREEVIEW_INFO *infoPtr, WPARAM wParam, HIMAGELIST himlNew) { - TREEVIEW_INFO *infoPtr = TREEVIEW_GetInfoPtr(hwnd); - - TRACE("\n"); - return (LRESULT) infoPtr->clrInsertMark; -} - -static LRESULT -TREEVIEW_SetInsertMarkColor (HWND hwnd, WPARAM wParam, LPARAM lParam) -{ - TREEVIEW_INFO *infoPtr = TREEVIEW_GetInfoPtr(hwnd); - COLORREF prevColor=infoPtr->clrInsertMark; - - TRACE("%d %ld\n",wParam,lParam); - infoPtr->clrInsertMark=(COLORREF) lParam; - return (LRESULT) prevColor; -} - -static LRESULT -TREEVIEW_SetInsertMark (HWND hwnd, WPARAM wParam, LPARAM lParam) -{ - TREEVIEW_INFO *infoPtr = TREEVIEW_GetInfoPtr(hwnd); - - FIXME("%d %ld\n",wParam,lParam); - if (!TREEVIEW_ValidItem (infoPtr, (HTREEITEM)lParam)) return 0; - FIXME("%d %ld\n",wParam,lParam); - - infoPtr->insertBeforeorAfter=(BOOL) wParam; - infoPtr->insertMarkItem=(HTREEITEM) lParam; - - InvalidateRect(hwnd, NULL, FALSE); - - return 1; -} - -static LRESULT -TREEVIEW_SetTextColor (HWND hwnd, WPARAM wParam, LPARAM lParam) -{ - TREEVIEW_INFO *infoPtr = TREEVIEW_GetInfoPtr(hwnd); - COLORREF prevColor=infoPtr->clrText; - - TRACE("\n"); - infoPtr->clrText=(COLORREF) lParam; - return (LRESULT) prevColor; -} - -static LRESULT -TREEVIEW_GetBkColor (HWND hwnd) -{ - TREEVIEW_INFO *infoPtr = TREEVIEW_GetInfoPtr(hwnd); - - TRACE("\n"); - return (LRESULT) infoPtr->clrBk; -} - -static LRESULT -TREEVIEW_SetBkColor (HWND hwnd, WPARAM wParam, LPARAM lParam) -{ - TREEVIEW_INFO *infoPtr = TREEVIEW_GetInfoPtr(hwnd); - COLORREF prevColor=infoPtr->clrBk; - - TRACE("\n"); - infoPtr->clrBk=(COLORREF) lParam; - return (LRESULT) prevColor; -} - -static LRESULT -TREEVIEW_GetTextColor (HWND hwnd) -{ - TREEVIEW_INFO *infoPtr = TREEVIEW_GetInfoPtr(hwnd); - - TRACE("\n"); - return (LRESULT) infoPtr->clrText; -} + HIMAGELIST himlOld = 0; + int oldWidth = infoPtr->normalImageWidth; + int oldHeight = infoPtr->normalImageHeight; -/* cdmode: custom draw mode as received from app. in first NMCUSTOMDRAW - notification */ + TRACE("%x,%p\n", wParam, himlNew); -#define TREEVIEW_LEFT_MARGIN 8 + switch (wParam) + { + case (WPARAM)TVSIL_NORMAL: + himlOld = infoPtr->himlNormal; + infoPtr->himlNormal = himlNew; - -static void -TREEVIEW_DrawItem (HWND hwnd, HDC hdc, TREEVIEW_ITEM *wineItem) -{ - TREEVIEW_INFO *infoPtr = TREEVIEW_GetInfoPtr(hwnd); - DWORD dwStyle = GetWindowLongA (hwnd, GWL_STYLE); - INT center,xpos,cx,cy, cditem; - HFONT hOldFont; - UINT uTextJustify = DT_LEFT; - RECT r; - - - if (wineItem->state & TVIS_BOLD) - hOldFont = SelectObject (hdc, infoPtr->hBoldFont); - else - hOldFont = SelectObject (hdc, infoPtr->hFont); - - cditem=0; - TRACE ("cdmode:%x\n",infoPtr->cdmode); - if (infoPtr->cdmode & CDRF_NOTIFYITEMDRAW) { - cditem=TREEVIEW_SendCustomDrawItemNotify - (hwnd, hdc, wineItem, CDDS_ITEMPREPAINT); - TRACE("prepaint:cditem-app returns 0x%x\n",cditem); - - if (cditem & CDRF_SKIPDEFAULT) - return; + if (himlNew != NULL) + ImageList_GetIconSize(himlNew, &infoPtr->normalImageWidth, + &infoPtr->normalImageHeight); + else + { + infoPtr->normalImageWidth = 0; + infoPtr->normalImageHeight = 0; } - /* - * Set drawing starting points - */ - r = wineItem->rect; /* this item rectangle */ - center = (r.top+r.bottom)/2; /* this item vertical center */ - xpos = r.left + TREEVIEW_LEFT_MARGIN;/* horizontal starting point */ + break; - /* - * Display the tree hierarchy - */ - if ( dwStyle & TVS_HASLINES) - { - /* - * Write links to parent node - * we draw the L starting from the child to the parent - * - * points[0] is attached to the current item - * points[1] is the L corner - * points[2] is attached to the parent or the up sibling - */ - if ( dwStyle & TVS_LINESATROOT) - { - TREEVIEW_ITEM *upNode = NULL; - BOOL hasParentOrSibling = TRUE; - RECT upRect = {0,0,0,0}; - HPEN hOldPen, hNewPen; - POINT points[3]; - /* - * determine the target location of the line at root, either be linked - * to the up sibling or to the parent node. - */ - if (wineItem->upsibling) - upNode = TREEVIEW_ValidItem (infoPtr, wineItem->upsibling); - else if (wineItem->parent) - upNode = TREEVIEW_ValidItem (infoPtr, wineItem->parent); - else - hasParentOrSibling = FALSE; + case (WPARAM)TVSIL_STATE: + himlOld = infoPtr->himlState; + infoPtr->himlState = himlNew; - if (upNode) - upRect = upNode->rect; - - if ( wineItem->iLevel == 0 ) - { - points[2].x = points[1].x = upRect.left+8; - points[0].x = points[2].x + 10; - points[2].y = upRect.bottom-3; - points[1].y = points[0].y = center; - } - else - { - points[2].x = points[1].x = 8 + (20*wineItem->iLevel); - points[2].y = ( upNode->cChildren == 0) ? - upRect.top : /* is linked to the "L" above */ - ( wineItem->upsibling != NULL) ? - upRect.bottom-3: /* is linked to an icon */ - upRect.bottom+1; /* is linked to a +/- box */ - points[1].y = points[0].y = center; - points[0].x = points[1].x + 10; - } - - /* - * Get a dotted pen - */ - hNewPen = CreatePen(PS_DOT, 0, infoPtr->clrLine); - hOldPen = SelectObject( hdc, hNewPen ); - - if (hasParentOrSibling) - Polyline (hdc,points,3); - else - Polyline (hdc,points,2); - - DeleteObject(hNewPen); - SelectObject(hdc, hOldPen); - } - } - - /* - * Display the (+/-) signs - */ - if (wineItem->iLevel != 0)/* update position only for non root node */ - xpos+=(5*wineItem->iLevel); - - if (( dwStyle & TVS_HASBUTTONS) && ( dwStyle & TVS_HASLINES)) - { - if ( (wineItem->cChildren) || - (wineItem->cChildren == I_CHILDRENCALLBACK)) - { - /* Setup expand box coordinate to facilitate the LMBClick handling */ - wineItem->expandBox.left = xpos-4; - wineItem->expandBox.top = center-4; - wineItem->expandBox.right = xpos+5; - wineItem->expandBox.bottom = center+5; - - Rectangle ( - hdc, - wineItem->expandBox.left, - wineItem->expandBox.top , - wineItem->expandBox.right, - wineItem->expandBox.bottom); - - MoveToEx (hdc, xpos-2, center, NULL); - LineTo (hdc, xpos+3, center); - - if (!(wineItem->state & TVIS_EXPANDED)) { - MoveToEx (hdc, xpos, center-2, NULL); - LineTo (hdc, xpos, center+3); - } - } - } - - /* - * Display the image associated with this item - */ - xpos += 13; /* update position */ - if (wineItem->mask & (TVIF_IMAGE|TVIF_SELECTEDIMAGE)) { - INT imageIndex; - HIMAGELIST *himlp = NULL; - - /* State images are displayed to the left of the Normal image - * image number is in state; zero should be `display no image'. - * FIXME: that last sentence looks like it needs some checking. - */ - if (infoPtr->himlState) - himlp=&infoPtr->himlState; - imageIndex=wineItem->state>>12; - imageIndex++; /* yeah, right */ - TRACE ("imindex:%d\n",imageIndex); - if ((himlp) && (imageIndex)) - { - imageIndex--; /* see FIXME */ - ImageList_Draw ( *himlp, imageIndex, hdc, xpos-2, r.top+1, ILD_NORMAL); - ImageList_GetIconSize (*himlp, &cx, &cy); - wineItem->statebitmap.left=xpos-2; - wineItem->statebitmap.right=xpos-2+cx; - wineItem->statebitmap.top=r.top+1; - wineItem->statebitmap.bottom=r.top+1+cy; - xpos+=cx; - } - - /* Now, draw the normal image; can be either selected or - * non-selected image. - */ - - himlp=NULL; - if (infoPtr->himlNormal) - himlp=&infoPtr->himlNormal; /* get the image list */ - - imageIndex = wineItem->iImage; - if ( (wineItem->state & TVIS_SELECTED) && - (wineItem->iSelectedImage)) { - - /* The item is curently selected */ - if (wineItem->iSelectedImage == I_IMAGECALLBACK) - TREEVIEW_SendDispInfoNotify - (hwnd, wineItem, TVN_GETDISPINFOA, TVIF_SELECTEDIMAGE); - - imageIndex = wineItem->iSelectedImage; - } else { - /* The item is not selected */ - if (wineItem->iImage == I_IMAGECALLBACK) - TREEVIEW_SendDispInfoNotify - (hwnd, wineItem, TVN_GETDISPINFOA, TVIF_IMAGE); - - imageIndex = wineItem->iImage; - } - - if (himlp) - { - int ovlIdx = 0; - - if(wineItem->stateMask & TVIS_OVERLAYMASK) - ovlIdx = wineItem->state & TVIS_OVERLAYMASK; - - ImageList_Draw ( *himlp, imageIndex, hdc, xpos-2, r.top+1, ILD_NORMAL|ovlIdx); - ImageList_GetIconSize (*himlp, &cx, &cy); - wineItem->bitmap.left=xpos-2; - wineItem->bitmap.right=xpos-2+cx; - wineItem->bitmap.top=r.top+1; - wineItem->bitmap.bottom=r.top+1+cy; - xpos+=cx; - } - } - - - /* - * Display the text associated with this item - */ - r.left=xpos; - if ((wineItem->mask & TVIF_TEXT) && (wineItem->pszText)) - { - COLORREF oldTextColor = 0; - INT oldBkMode; - HBRUSH hbrBk = 0; - BOOL inFocus = GetFocus() == hwnd; - - TREEVIEW_ITEM tmpItem; - char buf[128]; - - if (wineItem->pszText == LPSTR_TEXTCALLBACKA) - { - tmpItem.hItem = wineItem->hItem; - tmpItem.state = wineItem->state; - tmpItem.lParam = wineItem->lParam; - tmpItem.pszText = buf; - tmpItem.cchTextMax = sizeof(buf); - - TREEVIEW_SendDispInfoNotify(hwnd, &tmpItem, TVN_GETDISPINFOA, TVIF_TEXT); - } - - r.left += 3; - r.right -= 3; - - wineItem->text.left = r.left; - wineItem->text.right = r.right; - wineItem->text.top = r.top; - wineItem->text.bottom= r.bottom; - - oldBkMode = SetBkMode(hdc, TRANSPARENT); - - /* - If item is drop target or it is selected and window is in focus - - * use blue background (COLOR_HIGHLIGHT). - * - If item is selected, window is not in focus, but it has style - * TVS_SHOWSELALWAYS - use grey background (COLOR_BTNFACE) - * - Otherwise - don't fill background - */ - if ((wineItem->state & TVIS_DROPHILITED) || - ((wineItem->state & TVIS_SELECTED) && - (inFocus || (GetWindowLongA( hwnd, GWL_STYLE) & TVS_SHOWSELALWAYS)))) - { - if ((wineItem->state & TVIS_DROPHILITED) || inFocus) - { - hbrBk = CreateSolidBrush(GetSysColor( COLOR_HIGHLIGHT)); - oldTextColor = SetTextColor(hdc, GetSysColor( COLOR_HIGHLIGHTTEXT)); - } - else - { - hbrBk = CreateSolidBrush(GetSysColor( COLOR_BTNFACE)); - - if (infoPtr->clrText == -1) - oldTextColor = SetTextColor(hdc, GetSysColor( COLOR_WINDOWTEXT)); - else - oldTextColor = SetTextColor(hdc, infoPtr->clrText); - } - } - else - { - if (infoPtr->clrText == -1) - oldTextColor = SetTextColor(hdc, GetSysColor( COLOR_WINDOWTEXT)); - else - oldTextColor = SetTextColor(hdc, infoPtr->clrText); - } - - if (wineItem->pszText != LPSTR_TEXTCALLBACKA) - tmpItem.pszText = wineItem->pszText; - - /* Obtain the text coordinate */ - DrawTextA ( - hdc, - tmpItem.pszText, - lstrlenA(tmpItem.pszText), - &wineItem->text, - uTextJustify | DT_VCENTER | DT_SINGLELINE | DT_CALCRECT | DT_NOPREFIX); - - /* We need to reset it to items height */ - wineItem->text.top = r.top; - wineItem->text.bottom = r.bottom; - wineItem->text.right += 4; /* This is extra for focus rectangle */ - - if (hbrBk) - { - FillRect(hdc, &wineItem->text, hbrBk); - DeleteObject(hbrBk); - } - - wineItem->text.left += 2; - - /* Draw it */ - DrawTextA ( hdc, - tmpItem.pszText, - lstrlenA(tmpItem.pszText), - &wineItem->text, - uTextJustify | DT_VCENTER | DT_SINGLELINE | DT_NOPREFIX); - - wineItem->text.left -=2; - - /* Restore the hdc state */ - SetTextColor( hdc, oldTextColor); - - /* Draw the box arround the selected item */ - if (wineItem->state & TVIS_SELECTED && inFocus) - { - HPEN hNewPen = CreatePen(PS_DOT, 0, GetSysColor(COLOR_WINDOWTEXT) ); - HPEN hOldPen = SelectObject( hdc, hNewPen ); - INT rop = SetROP2(hdc, R2_XORPEN); - POINT points[5]; - - points[4].x = points[0].x = wineItem->text.left; - points[4].y = points[0].y = wineItem->text.top; - points[1].x = wineItem->text.right-1 ; - points[1].y = wineItem->text.top; - points[2].x = wineItem->text.right-1; - points[2].y = wineItem->text.bottom-1; - points[3].x = wineItem->text.left; - points[3].y = wineItem->text.bottom-1; - - Polyline (hdc,points,5); - - SetROP2(hdc, rop); - DeleteObject(hNewPen); - SelectObject(hdc, hOldPen); - } - - if (oldBkMode != TRANSPARENT) - SetBkMode(hdc, oldBkMode); - } - - /* Draw insertion mark if necessary */ - - if (infoPtr->insertMarkItem) - TRACE ("item:%d,mark:%d\n", (int)wineItem->hItem, - (int) infoPtr->insertMarkItem); - if (wineItem->hItem==infoPtr->insertMarkItem) { - HPEN hNewPen, hOldPen; - int offset; - - hNewPen = CreatePen(PS_SOLID, 2, infoPtr->clrInsertMark); - hOldPen = SelectObject( hdc, hNewPen ); - - if (infoPtr->insertBeforeorAfter) - offset=wineItem->text.top+1; - else - offset=wineItem->text.bottom-1; - - MoveToEx (hdc, wineItem->text.left, offset-3, NULL); - LineTo (hdc, wineItem->text.left, offset+3); - - MoveToEx (hdc, wineItem->text.left, offset, NULL); - LineTo (hdc, r.right-2, offset); - - MoveToEx (hdc, r.right-2, offset+3, NULL); - LineTo (hdc, r.right-2, offset-3); - - DeleteObject(hNewPen); - - SelectObject(hdc, hOldPen); + if (himlNew != NULL) + ImageList_GetIconSize(himlNew, &infoPtr->stateImageWidth, + &infoPtr->stateImageHeight); + else + { + infoPtr->stateImageWidth = 0; + infoPtr->stateImageHeight = 0; } - if (cditem & CDRF_NOTIFYPOSTPAINT) { - cditem=TREEVIEW_SendCustomDrawItemNotify - (hwnd, hdc, wineItem, CDDS_ITEMPOSTPAINT); - TRACE("postpaint:cditem-app returns 0x%x\n",cditem); - } + break; + } - SelectObject (hdc, hOldFont); -} - -static LRESULT -TREEVIEW_GetItemRect (HWND hwnd, WPARAM wParam, LPARAM lParam) -{ - TREEVIEW_INFO *infoPtr = TREEVIEW_GetInfoPtr(hwnd); - TREEVIEW_ITEM *wineItem; - HTREEITEM *iItem; - LPRECT lpRect = (LPRECT)lParam; - - TRACE("\n"); - /* - * validate parameters - */ - if (lpRect == NULL) - return FALSE; - - if (infoPtr->Timer & TV_REFRESH_TIMER_SET) { - InvalidateRect(hwnd, NULL, FALSE); - } - - - /* - * retrieve the item ptr - */ - iItem = (HTREEITEM *) lParam; - wineItem = TREEVIEW_ValidItem (infoPtr, *iItem); - if ((!wineItem) || (!wineItem->visible)) - return FALSE; - - /* - * If wParam is TRUE return the text size otherwise return - * the whole item size - */ - if ((INT) wParam) { - lpRect->left = wineItem->text.left; - lpRect->right = wineItem->text.right; - lpRect->bottom = wineItem->text.bottom; - lpRect->top = wineItem->text.top; - } else { - lpRect->left = wineItem->rect.left; - lpRect->right = wineItem->rect.right; - lpRect->bottom = wineItem->rect.bottom; - lpRect->top = wineItem->rect.top; - } - - TRACE("[L:%d R:%d T:%d B:%d]\n", - lpRect->left,lpRect->right, - lpRect->top,lpRect->bottom); - - return TRUE; -} - -static LRESULT -TREEVIEW_GetVisibleCount (HWND hwnd, WPARAM wParam, LPARAM lParam) - -{ - TREEVIEW_INFO *infoPtr = TREEVIEW_GetInfoPtr(hwnd); - - return (LRESULT) infoPtr->uVisibleHeight / infoPtr->uRealItemHeight; -} - - - -static LRESULT -TREEVIEW_SetItemA (HWND hwnd, WPARAM wParam, LPARAM lParam) -{ - TREEVIEW_INFO *infoPtr = TREEVIEW_GetInfoPtr(hwnd); - TREEVIEW_ITEM *wineItem; - TVITEMEXA *tvItem; - INT iItem,len; - - tvItem=(LPTVITEMEXA) lParam; - iItem=(INT)tvItem->hItem; - TRACE("item %d,mask %x\n",iItem,tvItem->mask); - - wineItem = TREEVIEW_ValidItem (infoPtr, (HTREEITEM)iItem); - if (!wineItem) return FALSE; - - if (tvItem->mask & TVIF_CHILDREN) { - wineItem->cChildren=tvItem->cChildren; - } - - if (tvItem->mask & TVIF_IMAGE) { - wineItem->iImage=tvItem->iImage; - } - - if (tvItem->mask & TVIF_INTEGRAL) { - wineItem->iIntegral=tvItem->iIntegral; - } - - if (tvItem->mask & TVIF_PARAM) { - wineItem->lParam=tvItem->lParam; - } - - if (tvItem->mask & TVIF_SELECTEDIMAGE) { - wineItem->iSelectedImage=tvItem->iSelectedImage; - } - - if (tvItem->mask & TVIF_STATE) { - TRACE ("prevstate,state,mask:%x,%x,%x\n",wineItem->state,tvItem->state, -tvItem->stateMask); - wineItem->state&= ~tvItem->stateMask; - wineItem->state|= (tvItem->state & tvItem->stateMask); - wineItem->stateMask|= tvItem->stateMask; - } - - if (tvItem->mask & TVIF_TEXT) - { - if (tvItem->pszText!=LPSTR_TEXTCALLBACKA) + if (oldWidth != infoPtr->normalImageWidth || + oldHeight != infoPtr->normalImageHeight) { - len=lstrlenA (tvItem->pszText) + 1; - if (len>wineItem->cchTextMax) - { - wineItem->pszText= COMCTL32_ReAlloc (wineItem->pszText, len); - wineItem->cchTextMax = len; - } + TREEVIEW_UpdateSubTree(infoPtr, infoPtr->root); + TREEVIEW_UpdateScrollBars(infoPtr); + } - lstrcpynA (wineItem->pszText, tvItem->pszText,len); + TREEVIEW_QueueRefresh(infoPtr); + + return (LRESULT)himlOld; +} + +/* Compute the natural height (based on the font size) for items. */ +static UINT +TREEVIEW_NaturalHeight(TREEVIEW_INFO *infoPtr) +{ + TEXTMETRICA tm; + HDC hdc = GetDC(0); + HFONT hOldFont = SelectObject(hdc, infoPtr->hFont); + + GetTextMetricsA(hdc, &tm); + + SelectObject(hdc, hOldFont); + ReleaseDC(0, hdc); + + /* The 16 is a hack because our fonts are tiny. */ + return max(16, tm.tmHeight + tm.tmExternalLeading); +} + +static LRESULT +TREEVIEW_SetItemHeight(TREEVIEW_INFO *infoPtr, INT newHeight) +{ + INT prevHeight = infoPtr->uItemHeight; + + TRACE("%d \n", newHeight); + if (newHeight == -1) + { + infoPtr->uItemHeight = TREEVIEW_NaturalHeight(infoPtr); + infoPtr->bHeightSet = FALSE; } else { - if (wineItem->cchTextMax) - { - COMCTL32_Free (wineItem->pszText); - wineItem->cchTextMax=0; - } - wineItem->pszText=LPSTR_TEXTCALLBACKA; + infoPtr->uItemHeight = newHeight; + infoPtr->bHeightSet = TRUE; } - } - wineItem->mask |= tvItem->mask; + /* Round down, unless we support odd ("non even") heights. */ + if (!(infoPtr->dwStyle) & TVS_NONEVENHEIGHT) + infoPtr->uItemHeight &= ~1; - return TRUE; + if (infoPtr->uItemHeight != prevHeight) + { + TREEVIEW_RecalculateVisibleOrder(infoPtr, NULL); + TREEVIEW_UpdateScrollBars(infoPtr); + TREEVIEW_QueueRefresh(infoPtr); + } + + return prevHeight; } static LRESULT -TREEVIEW_GetItemState (HWND hwnd, WPARAM wParam, LPARAM lParam) - +TREEVIEW_GetItemHeight(TREEVIEW_INFO *infoPtr) { - TREEVIEW_INFO *infoPtr = TREEVIEW_GetInfoPtr(hwnd); - TREEVIEW_ITEM *wineItem; - TRACE("\n"); - wineItem = TREEVIEW_ValidItem (infoPtr, (HTREEITEM)wParam); - if (!wineItem) return 0; - - return (wineItem->state & lParam); + return infoPtr->uItemHeight; } - - -static void -TREEVIEW_Refresh (HWND hwnd, HDC hdc) +static LRESULT +TREEVIEW_GetFont(TREEVIEW_INFO *infoPtr) { - TREEVIEW_INFO *infoPtr = TREEVIEW_GetInfoPtr(hwnd); - TEXTMETRICA tm; - HBRUSH hbrBk; - HFONT hOldFont; + TRACE("%x\n", infoPtr->hFont); + return infoPtr->hFont; +} - RECT rect; - INT iItem, indent, x, y, height, itemHeight; - INT viewtop,viewbottom,viewleft,viewright; - TREEVIEW_ITEM *wineItem, *prevItem; + +static INT CALLBACK +TREEVIEW_ResetTextWidth(LPVOID pItem, DWORD unused) +{ + (void)unused; + + ((TREEVIEW_ITEM *)pItem)->textWidth = 0; + + return 1; +} + +static LRESULT +TREEVIEW_SetFont(TREEVIEW_INFO *infoPtr, HFONT hFont, BOOL bRedraw) +{ + UINT uHeight = infoPtr->uItemHeight; + + TRACE("%x %i\n", hFont, bRedraw); + + infoPtr->hFont = hFont ? hFont : GetStockObject(SYSTEM_FONT); + + DeleteObject(infoPtr->hBoldFont); + infoPtr->hBoldFont = TREEVIEW_CreateBoldFont(infoPtr->hFont); + + if (!infoPtr->bHeightSet) + infoPtr->uItemHeight = TREEVIEW_NaturalHeight(infoPtr); + + if (uHeight != infoPtr->uItemHeight) + TREEVIEW_RecalculateVisibleOrder(infoPtr, NULL); + + DPA_EnumCallback(infoPtr->items, TREEVIEW_ResetTextWidth, 0); + + TREEVIEW_UpdateSubTree(infoPtr, infoPtr->root); + TREEVIEW_UpdateScrollBars(infoPtr); + + if (bRedraw) + TREEVIEW_QueueRefresh(infoPtr); + + return 0; +} + + +static LRESULT +TREEVIEW_GetLineColor(TREEVIEW_INFO *infoPtr) +{ + TRACE("\n"); + return (LRESULT)infoPtr->clrLine; +} + +static LRESULT +TREEVIEW_SetLineColor(TREEVIEW_INFO *infoPtr, COLORREF color) +{ + COLORREF prevColor = infoPtr->clrLine; TRACE("\n"); - - - if (infoPtr->Timer & TV_REFRESH_TIMER_SET) { - KillTimer (hwnd, TV_REFRESH_TIMER); - infoPtr->Timer &= ~TV_REFRESH_TIMER_SET; - } - - - GetClientRect (hwnd, &rect); - if ((rect.left >= rect.right) || (rect.top >= rect.bottom)) return; - - infoPtr->cdmode=TREEVIEW_SendCustomDrawNotify(hwnd,CDDS_PREPAINT,hdc,rect); - - if (infoPtr->cdmode==CDRF_SKIPDEFAULT) return; - - infoPtr->uVisibleHeight= rect.bottom-rect.top + 1; - infoPtr->uVisibleWidth= rect.right-rect.left + 1; - - viewtop=infoPtr->cy; - viewbottom=infoPtr->cy + rect.bottom-rect.top; - viewleft=infoPtr->cx; - viewright=infoPtr->cx + rect.right-rect.left; - - TRACE("[%d %d %d %d]\n",viewtop,viewbottom,viewleft,viewright); - - /* draw background */ - - hbrBk = CreateSolidBrush (infoPtr->clrBk); - FillRect(hdc, &rect, hbrBk); - DeleteObject(hbrBk); - - ImageList_GetIconSize (infoPtr->himlNormal, &x, &itemHeight); - if (infoPtr->uItemHeight>itemHeight) - itemHeight=infoPtr->uItemHeight; - - // assume that bold and normal fonts have same height - hOldFont = SelectObject (hdc, infoPtr->hBoldFont); - GetTextMetricsA (hdc, &tm); - if ((tm.tmHeight + tm.tmExternalLeading + FOCUS_BORDER) > itemHeight) - itemHeight=tm.tmHeight + tm.tmExternalLeading + FOCUS_BORDER; - SelectObject (hdc, hOldFont); - - infoPtr->uRealItemHeight=itemHeight; - - iItem=(INT)infoPtr->TopRootItem; - infoPtr->firstVisible=0; - wineItem=NULL; - indent=0; - x=y=0; - - while (iItem) { - prevItem=wineItem; - wineItem= & infoPtr->items[iItem]; - wineItem->iLevel=indent; - -/* FIXME: remove this in later stage */ -/* - if (wineItem->pszText!=LPSTR_TEXTCALLBACK32A) - TRACE (treeview, "%d %d [%d %d %d %d] (%s)\n",y,x, - wineItem->rect.top, wineItem->rect.bottom, - wineItem->rect.left, wineItem->rect.right, - wineItem->pszText); - else - TRACE (treeview, "%d [%d %d %d %d] (CALLBACK)\n", - wineItem->hItem, - wineItem->rect.top, wineItem->rect.bottom, - wineItem->rect.left, wineItem->rect.right); -*/ - - height=itemHeight * wineItem->iIntegral; - if ((y >= viewtop) && (y <= viewbottom) && - (x >= viewleft ) && (x <= viewright)) { - wineItem->visible = TRUE; - wineItem->rect.top = y - infoPtr->cy + rect.top; - wineItem->rect.bottom = wineItem->rect.top + height-1; - wineItem->rect.left = x - infoPtr->cx + rect.left; - wineItem->rect.right = rect.right; - if (!infoPtr->firstVisible) - infoPtr->firstVisible=wineItem->hItem; - TREEVIEW_DrawItem (hwnd, hdc, wineItem); - } - else { - wineItem->visible = FALSE; - wineItem->rect.left = wineItem->rect.top = 0; - wineItem->rect.right= wineItem->rect.bottom = 0; - wineItem->text.left = wineItem->text.top = 0; - wineItem->text.right= wineItem->text.bottom = 0; - } - - /* look up next item */ - - if ((wineItem->firstChild) && (wineItem->state & TVIS_EXPANDED)) { - iItem=(INT)wineItem->firstChild; - indent++; - x+=infoPtr->uIndent; - if (x>infoPtr->uTotalWidth) - infoPtr->uTotalWidth=x; - } - else { - iItem=(INT)wineItem->sibling; - while ((!iItem) && (indent>0)) { - indent--; - x-=infoPtr->uIndent; - wineItem=&infoPtr->items[(INT)wineItem->parent]; - iItem=(INT)wineItem->sibling; - } - } - y +=height; - } /* while */ - -/* FIXME: infoPtr->uTotalWidth should also take item label into account */ -/* FIXME: or should query item sizes (ie check CDRF_NEWFONT) */ - - infoPtr->uTotalHeight=y; - if (y >= (viewbottom-viewtop)) { - if (!(infoPtr->uInternalStatus & TV_VSCROLL)) - ShowScrollBar (hwnd, SB_VERT, TRUE); - infoPtr->uInternalStatus |=TV_VSCROLL; - SetScrollRange (hwnd, SB_VERT, 0, - y - infoPtr->uVisibleHeight, FALSE); - SetScrollPos (hwnd, SB_VERT, infoPtr->cy, TRUE); - } - else { - if (infoPtr->uInternalStatus & TV_VSCROLL) - ShowScrollBar (hwnd, SB_VERT, FALSE); - infoPtr->uInternalStatus &= ~TV_VSCROLL; - } - - - if (infoPtr->cdmode & CDRF_NOTIFYPOSTPAINT) - infoPtr->cdmode=TREEVIEW_SendCustomDrawNotify - (hwnd, CDDS_POSTPAINT, hdc, rect); - - TRACE("done\n"); -} - - -static LRESULT -TREEVIEW_HandleTimer (HWND hwnd, WPARAM wParam, LPARAM lParam) -{ - TREEVIEW_INFO *infoPtr = TREEVIEW_GetInfoPtr(hwnd); - - TRACE(" %d\n",wParam); - - switch (wParam) { - case TV_REFRESH_TIMER: - KillTimer (hwnd, TV_REFRESH_TIMER); - infoPtr->Timer &= ~TV_REFRESH_TIMER_SET; - InvalidateRect(hwnd, NULL, FALSE); - return 0; - case TV_EDIT_TIMER: - KillTimer (hwnd, TV_EDIT_TIMER); - infoPtr->Timer &= ~TV_EDIT_TIMER_SET; - return 0; - default: - ERR("got unknown timer\n"); - } - - return 1; -} - - -static void -TREEVIEW_QueueRefresh (HWND hwnd) - -{ - TREEVIEW_INFO *infoPtr = TREEVIEW_GetInfoPtr(hwnd); - - TRACE("\n"); - if (infoPtr->Timer & TV_REFRESH_TIMER_SET) { - KillTimer (hwnd, TV_REFRESH_TIMER); - } - - SetTimer (hwnd, TV_REFRESH_TIMER, TV_REFRESH_DELAY, 0); - infoPtr->Timer|=TV_REFRESH_TIMER_SET; -} - - - -static LRESULT -TREEVIEW_GetItemA (HWND hwnd, WPARAM wParam, LPARAM lParam) -{ - TREEVIEW_INFO *infoPtr = TREEVIEW_GetInfoPtr(hwnd); - LPTVITEMEXA tvItem; - TREEVIEW_ITEM *wineItem; - INT iItem; - - tvItem=(LPTVITEMEXA) lParam; - iItem=(INT)tvItem->hItem; - - wineItem = TREEVIEW_ValidItem (infoPtr, (HTREEITEM)iItem); - if (!wineItem) return FALSE; - - if (tvItem->mask & TVIF_CHILDREN) { - if (TVIF_CHILDREN==I_CHILDRENCALLBACK) - FIXME("I_CHILDRENCALLBACK not supported\n"); - tvItem->cChildren=wineItem->cChildren; - } - - if (tvItem->mask & TVIF_HANDLE) { - tvItem->hItem=wineItem->hItem; - } - - if (tvItem->mask & TVIF_IMAGE) { - tvItem->iImage=wineItem->iImage; - } - - if (tvItem->mask & TVIF_INTEGRAL) { - tvItem->iIntegral=wineItem->iIntegral; - } - - /* undocumented: windows ignores TVIF_PARAM and - * always sets lParam - */ - tvItem->lParam=wineItem->lParam; - - if (tvItem->mask & TVIF_SELECTEDIMAGE) { - tvItem->iSelectedImage=wineItem->iSelectedImage; - } - - if (tvItem->mask & TVIF_STATE) { - tvItem->state=wineItem->state & tvItem->stateMask; - } - - if (tvItem->mask & TVIF_TEXT) { - if (wineItem->pszText == LPSTR_TEXTCALLBACKA) { - tvItem->pszText = LPSTR_TEXTCALLBACKA; /* FIXME:send notification? */ - ERR(" GetItem called with LPSTR_TEXTCALLBACK\n"); - } - else if (wineItem->pszText) { - lstrcpynA (tvItem->pszText, wineItem->pszText, tvItem->cchTextMax); - } - } - - TRACE("item %d<%p>, txt %p, img %p, action %x\n", - iItem, tvItem, tvItem->pszText, &tvItem->iImage, tvItem->mask); - - return TRUE; + infoPtr->clrLine = color; + return (LRESULT)prevColor; } static LRESULT -TREEVIEW_GetItemW (HWND hwnd, WPARAM wParam, LPARAM lParam) +TREEVIEW_GetTextColor(TREEVIEW_INFO *infoPtr) { - TREEVIEW_INFO *infoPtr = TREEVIEW_GetInfoPtr(hwnd); - LPTVITEMEXA tvItem; - TREEVIEW_ITEM *wineItem; - INT iItem; - - tvItem=(LPTVITEMEXA) lParam; - iItem=(INT)tvItem->hItem; - - wineItem = TREEVIEW_ValidItem (infoPtr, (HTREEITEM)iItem); - if (!wineItem) return FALSE; - - if (tvItem->mask & TVIF_CHILDREN) { - if (TVIF_CHILDREN==I_CHILDRENCALLBACK) - FIXME("I_CHILDRENCALLBACK not supported\n"); - tvItem->cChildren=wineItem->cChildren; - } - - if (tvItem->mask & TVIF_HANDLE) { - tvItem->hItem=wineItem->hItem; - } - - if (tvItem->mask & TVIF_IMAGE) { - tvItem->iImage=wineItem->iImage; - } - - if (tvItem->mask & TVIF_INTEGRAL) { - tvItem->iIntegral=wineItem->iIntegral; - } - - /* undocumented: windows ignores TVIF_PARAM and - * always sets lParam - */ - tvItem->lParam=wineItem->lParam; - - if (tvItem->mask & TVIF_SELECTEDIMAGE) { - tvItem->iSelectedImage=wineItem->iSelectedImage; - } - - if (tvItem->mask & TVIF_STATE) { - tvItem->state=wineItem->state & tvItem->stateMask; - } - -#if 0 - if (tvItem->mask & TVIF_TEXT) { - if (wineItem->pszText == LPSTR_TEXTCALLBACKW) { - tvItem->pszText = LPSTR_TEXTCALLBACKW; /* FIXME:send notification? */ - ERR(" GetItem called with LPSTR_TEXTCALLBACK\n"); - } - else if (wineItem->pszText) { - lstrcpynAtoW (tvItem->pszText, wineItem->pszText, tvItem->cchTextMax); - } - } -#endif - wineItem->pszText = NULL; - - TRACE("item %d<%p>, txt %p, img %p, action %x\n", - iItem, tvItem, tvItem->pszText, &tvItem->iImage, tvItem->mask); - - return TRUE; + TRACE("\n"); + return (LRESULT)infoPtr->clrText; } - - -/* FIXME: check implementation of TVGN_NEXT/TVGN_NEXTVISIBLE */ - static LRESULT -TREEVIEW_GetNextItem (HWND hwnd, WPARAM wParam, LPARAM lParam) - +TREEVIEW_SetTextColor(TREEVIEW_INFO *infoPtr, COLORREF color) { - TREEVIEW_INFO *infoPtr = TREEVIEW_GetInfoPtr(hwnd); - TREEVIEW_ITEM *wineItem, *returnItem; - INT iItem = (INT)lParam, retval = 0, flag = (INT)wParam; + COLORREF prevColor = infoPtr->clrText; - switch (flag) { - case TVGN_ROOT: - retval = (INT)infoPtr->TopRootItem; - break; + TRACE("\n"); + infoPtr->clrText = color; - case TVGN_CARET: - retval = (INT)infoPtr->selectedItem; - break; + if (infoPtr->clrText != prevColor) + TREEVIEW_QueueRefresh(infoPtr); - case TVGN_FIRSTVISIBLE: /* FIXME:we should only recalculate, not redraw */ - InvalidateRect(hwnd, NULL, FALSE); - retval = (INT)infoPtr->firstVisible; - break; - - case TVGN_DROPHILITE: - retval = (INT)infoPtr->dropItem; - break; - - case TVGN_NEXT: - wineItem = TREEVIEW_ValidItem (infoPtr, (HTREEITEM)iItem); - retval = wineItem ? (INT)wineItem->sibling : 0; - break; - - case TVGN_PREVIOUS: - wineItem = TREEVIEW_ValidItem (infoPtr, (HTREEITEM)iItem); - retval = wineItem ? (INT)wineItem->upsibling : 0; - break; - - case TVGN_PARENT: - wineItem = TREEVIEW_ValidItem (infoPtr, (HTREEITEM)iItem); - retval = wineItem ? (INT)wineItem->parent : 0; - break; - - case TVGN_CHILD: - wineItem = TREEVIEW_ValidItem (infoPtr, (HTREEITEM)iItem); - retval = wineItem ? (INT)wineItem->firstChild : 0; - break; - - case TVGN_LASTVISIBLE: - if((wineItem = TREEVIEW_ValidItem (infoPtr, (HTREEITEM)iItem))) { - returnItem = TREEVIEW_GetLastListItem (infoPtr,wineItem); - retval = returnItem ? (INT)returnItem->hItem : 0; - } - break; - - case TVGN_NEXTVISIBLE: - if((wineItem = TREEVIEW_ValidItem (infoPtr, (HTREEITEM)iItem))) { - returnItem = TREEVIEW_GetNextListItem (infoPtr,wineItem); - retval = returnItem ? (INT)returnItem->hItem : 0; - } - break; - - case TVGN_PREVIOUSVISIBLE: - if((wineItem = TREEVIEW_ValidItem (infoPtr, (HTREEITEM)iItem))) { - returnItem = TREEVIEW_GetPrevListItem (infoPtr, wineItem); - retval = returnItem ? (INT)returnItem->hItem : 0; - } - break; - - default: - FIXME("Unknown msg %x,item %x\n", flag,iItem); - break; - } - - TRACE("flags %x, item %d returns %d\n", flag, iItem, retval); - return retval; + return (LRESULT)prevColor; } static LRESULT -TREEVIEW_GetCount (HWND hwnd, WPARAM wParam, LPARAM lParam) +TREEVIEW_GetBkColor(TREEVIEW_INFO *infoPtr) { - TREEVIEW_INFO *infoPtr = TREEVIEW_GetInfoPtr(hwnd); - - TRACE(" %d\n",infoPtr->uNumItems); - return (LRESULT) infoPtr->uNumItems; + TRACE("\n"); + return (LRESULT)infoPtr->clrBk; } -/*************************************************************************** - * This method does the chaining of the insertion of a treeview item - * before an item. - * If parent is NULL, we're inserting at the root of the list. +static LRESULT +TREEVIEW_SetBkColor(TREEVIEW_INFO *infoPtr, COLORREF newColor) +{ + COLORREF prevColor = infoPtr->clrBk; + + TRACE("\n"); + infoPtr->clrBk = newColor; + + if (newColor != prevColor) + TREEVIEW_QueueRefresh(infoPtr); + + return (LRESULT)prevColor; +} + + +static LRESULT +TREEVIEW_GetInsertMarkColor(TREEVIEW_INFO *infoPtr) +{ + TRACE("\n"); + return (LRESULT)infoPtr->clrInsertMark; +} + +static LRESULT +TREEVIEW_SetInsertMarkColor(TREEVIEW_INFO *infoPtr, COLORREF color) +{ + COLORREF prevColor = infoPtr->clrInsertMark; + + TRACE("%lx\n", color); + infoPtr->clrInsertMark = color; + + return (LRESULT)prevColor; +} + + +static LRESULT +TREEVIEW_SetInsertMark(TREEVIEW_INFO *infoPtr, BOOL wParam, HTREEITEM item) +{ + TRACE("%d %p\n", wParam, item); + + if (!TREEVIEW_ValidItem(infoPtr, item)) + return 0; + + infoPtr->insertBeforeorAfter = wParam; + infoPtr->insertMarkItem = item; + + TREEVIEW_QueueRefresh(infoPtr); + + return 1; +} + + +/************************************************************************ + * Some serious braindamage here. lParam is a pointer to both the + * input HTREEITEM and the output RECT. */ -static void TREEVIEW_InsertBefore( - TREEVIEW_INFO *infoPtr, - TREEVIEW_ITEM *newItem, - TREEVIEW_ITEM *sibling, - TREEVIEW_ITEM *parent) +static LRESULT +TREEVIEW_GetItemRect(TREEVIEW_INFO *infoPtr, BOOL fTextRect, LPRECT lpRect) { - HTREEITEM siblingHandle = 0; - HTREEITEM upSiblingHandle = 0; - TREEVIEW_ITEM *upSibling = NULL; + TREEVIEW_ITEM *wineItem; + const HTREEITEM *pItem = (HTREEITEM *)lpRect; - if (newItem == NULL) - ERR("NULL newItem, impossible condition\n"); - - if (sibling != NULL) /* Insert before this sibling for this parent */ - { - /* Store the new item sibling up sibling and sibling tem handle */ - siblingHandle = sibling->hItem; - upSiblingHandle = sibling->upsibling; - /* As well as a pointer to the upsibling sibling object */ - if ( (INT)sibling->upsibling != 0 ) - upSibling = &infoPtr->items[(INT)sibling->upsibling]; - - /* Adjust the sibling pointer */ - sibling->upsibling = newItem->hItem; - - /* Adjust the new item pointers */ - newItem->upsibling = upSiblingHandle; - newItem->sibling = siblingHandle; - - /* Adjust the up sibling pointer */ - if ( upSibling != NULL ) - upSibling->sibling = newItem->hItem; - else - /* this item is the first child of this parent, adjust parent pointers */ - if (parent) - parent->firstChild = newItem->hItem; - else - infoPtr->TopRootItem= newItem->hItem; - } - else /* Insert as first child of this parent */ - if (parent) - parent->firstChild = newItem->hItem; -} - -/*************************************************************************** - * This method does the chaining of the insertion of a treeview item - * after an item. - * If parent is NULL, we're inserting at the root of the list. - */ -static void TREEVIEW_InsertAfter( - TREEVIEW_INFO *infoPtr, - TREEVIEW_ITEM *newItem, - TREEVIEW_ITEM *upSibling, - TREEVIEW_ITEM *parent) -{ - HTREEITEM upSiblingHandle = 0; - HTREEITEM siblingHandle = 0; - TREEVIEW_ITEM *sibling = NULL; - - - if (newItem == NULL) - ERR("NULL newItem, impossible condition\n"); - - if (upSibling != NULL) /* Insert after this upsibling for this parent */ - { - /* Store the new item up sibling and sibling item handle */ - upSiblingHandle = upSibling->hItem; - siblingHandle = upSibling->sibling; - /* As well as a pointer to the upsibling sibling object */ - if ( (INT)upSibling->sibling != 0 ) - sibling = &infoPtr->items[(INT)upSibling->sibling]; - - /* Adjust the up sibling pointer */ - upSibling->sibling = newItem->hItem; - - /* Adjust the new item pointers */ - newItem->upsibling = upSiblingHandle; - newItem->sibling = siblingHandle; - - /* Adjust the sibling pointer */ - if ( sibling != NULL ) - sibling->upsibling = newItem->hItem; + TRACE("\n"); /* - else - newItem is the last of the level, nothing else to do - */ - } - else /* Insert as first child of this parent */ - if (parent) - parent->firstChild = newItem->hItem; + * validate parameters + */ + if (pItem == NULL) + return FALSE; + + wineItem = *pItem; + if (!TREEVIEW_ValidItem(infoPtr, wineItem) || !ISVISIBLE(wineItem)) + return FALSE; + + /* + * If wParam is TRUE return the text size otherwise return + * the whole item size + */ + if (fTextRect) + { + /* Windows does not send TVN_GETDISPINFO here. */ + + lpRect->top = wineItem->rect.top; + lpRect->bottom = wineItem->rect.bottom; + + lpRect->left = wineItem->textOffset; + lpRect->right = wineItem->textOffset + wineItem->textWidth; + } + else + { + *lpRect = wineItem->rect; + } + + TRACE("%s [L:%d R:%d T:%d B:%d]\n", fTextRect ? "text" : "item", + lpRect->left, lpRect->right, lpRect->top, lpRect->bottom); + + return TRUE; } +static inline LRESULT +TREEVIEW_GetVisibleCount(TREEVIEW_INFO *infoPtr) +{ + /* Suprise! This does not take integral height into account. */ + return infoPtr->clientHeight / infoPtr->uItemHeight; +} + + +static LRESULT +TREEVIEW_GetItemA(TREEVIEW_INFO *infoPtr, LPTVITEMEXA tvItem) +{ + TREEVIEW_ITEM *wineItem; + + wineItem = tvItem->hItem; + if (!TREEVIEW_ValidItem(infoPtr, wineItem)) + return FALSE; + + TREEVIEW_UpdateDispInfo(infoPtr, wineItem, tvItem->mask); + + if (tvItem->mask & TVIF_CHILDREN) + tvItem->cChildren = wineItem->cChildren; + + if (tvItem->mask & TVIF_HANDLE) + tvItem->hItem = wineItem; + + if (tvItem->mask & TVIF_IMAGE) + tvItem->iImage = wineItem->iImage; + + if (tvItem->mask & TVIF_INTEGRAL) + tvItem->iIntegral = wineItem->iIntegral; + + /* undocumented: windows ignores TVIF_PARAM and + * * always sets lParam + */ + tvItem->lParam = wineItem->lParam; + + if (tvItem->mask & TVIF_SELECTEDIMAGE) + tvItem->iSelectedImage = wineItem->iSelectedImage; + + if (tvItem->mask & TVIF_STATE) + tvItem->state = wineItem->state & tvItem->stateMask; + + if (tvItem->mask & TVIF_TEXT) + lstrcpynA(tvItem->pszText, wineItem->pszText, tvItem->cchTextMax); + + TRACE("item <%p>, txt %p, img %p, mask %x\n", + wineItem, tvItem->pszText, &tvItem->iImage, tvItem->mask); + + return TRUE; +} + +/* Beware MSDN Library Visual Studio 6.0. It says -1 on failure, 0 on success, + * which is wrong. */ +static LRESULT +TREEVIEW_SetItemA(TREEVIEW_INFO *infoPtr, LPTVITEMEXA tvItem) +{ + TREEVIEW_ITEM *wineItem; + + wineItem = tvItem->hItem; + TRACE("item %d,mask %x\n", TREEVIEW_GetItemIndex(infoPtr, wineItem), + tvItem->mask); + + if (!TREEVIEW_ValidItem(infoPtr, wineItem)) + return FALSE; + + if (!TREEVIEW_DoSetItem(infoPtr, wineItem, tvItem)) + return FALSE; + + /* If the text or TVIS_BOLD was changed, and it is visible, recalculate. */ + if ((tvItem->mask & TVIF_TEXT + || (tvItem->mask & TVIF_STATE && tvItem->stateMask & TVIS_BOLD)) + && ISVISIBLE(wineItem)) + { + TREEVIEW_UpdateDispInfo(infoPtr, wineItem, TVIF_TEXT); + TREEVIEW_ComputeTextWidth(infoPtr, wineItem, 0); + } + + if (tvItem->mask != 0 && ISVISIBLE(wineItem)) + { + /* The refresh updates everything, but we can't wait until then. */ + TREEVIEW_ComputeItemInternalMetrics(infoPtr, wineItem); + + if (tvItem->mask & TVIF_INTEGRAL) + { + TREEVIEW_RecalculateVisibleOrder(infoPtr, wineItem); + TREEVIEW_UpdateScrollBars(infoPtr); + + TREEVIEW_QueueRefresh(infoPtr); + } + else + { + TREEVIEW_UpdateScrollBars(infoPtr); + TREEVIEW_QueueItemRefresh(infoPtr, wineItem); + } + } + + return TRUE; +} + +static LRESULT +TREEVIEW_GetItemW(TREEVIEW_INFO *infoPtr, LPTVITEMEXA tvItem) +{ + TREEVIEW_ITEM *wineItem; + INT iItem; + iItem = (INT)tvItem->hItem; + + wineItem = tvItem->hItem; + if(!TREEVIEW_ValidItem (infoPtr, wineItem)) + return FALSE; + + TREEVIEW_UpdateDispInfo(infoPtr, wineItem, tvItem->mask); + + if (tvItem->mask & TVIF_CHILDREN) { + if (TVIF_CHILDREN==I_CHILDRENCALLBACK) + FIXME("I_CHILDRENCALLBACK not supported\n"); + tvItem->cChildren = wineItem->cChildren; + } + + if (tvItem->mask & TVIF_HANDLE) { + tvItem->hItem = wineItem; + } + if (tvItem->mask & TVIF_IMAGE) { + tvItem->iImage = wineItem->iImage; + } + if (tvItem->mask & TVIF_INTEGRAL) { + tvItem->iIntegral = wineItem->iIntegral; + } + /* undocumented: windows ignores TVIF_PARAM and + * always sets lParam */ + tvItem->lParam = wineItem->lParam; + if (tvItem->mask & TVIF_SELECTEDIMAGE) { + tvItem->iSelectedImage = wineItem->iSelectedImage; + } + if (tvItem->mask & TVIF_STATE) { + tvItem->state = wineItem->state & tvItem->stateMask; + } +#if 0 + if (tvItem->mask & TVIF_TEXT) { + if (wineItem->pszText == LPSTR_TEXTCALLBACKW) { + tvItem->pszText = LPSTR_TEXTCALLBACKW; /* FIXME:send notification? */ + ERR(" GetItem called with LPSTR_TEXTCALLBACK\n"); + } + else if (wineItem->pszText) { + lstrcpynAtoW(tvItem->pszText, wineItem->pszText, tvItem->cchTextMax); + } + } +#endif + wineItem->pszText = NULL; + TRACE("item %d<%p>, txt %p, img %p, action %x\n", + iItem, tvItem, tvItem->pszText, &tvItem->iImage, tvItem->mask); + return TRUE; +} + +static LRESULT +TREEVIEW_GetItemState(TREEVIEW_INFO *infoPtr, HTREEITEM wineItem, UINT mask) +{ + TRACE("\n"); + + if (!wineItem || !TREEVIEW_ValidItem(infoPtr, wineItem)) + return 0; + + return (wineItem->state & mask); +} + +static LRESULT +TREEVIEW_GetNextItem(TREEVIEW_INFO *infoPtr, UINT which, HTREEITEM wineItem) +{ + TREEVIEW_ITEM *retval; + + retval = 0; + + /* handle all the global data here */ + switch (which) + { + case TVGN_CHILD: /* Special case: child of 0 is root */ + if (wineItem) + break; + /* fall through */ + case TVGN_ROOT: + retval = infoPtr->root->firstChild; + break; + + case TVGN_CARET: + retval = infoPtr->selectedItem; + break; + + case TVGN_FIRSTVISIBLE: + retval = infoPtr->firstVisible; + break; + + case TVGN_DROPHILITE: + retval = infoPtr->dropItem; + break; + + case TVGN_LASTVISIBLE: + retval = TREEVIEW_GetLastListItem(infoPtr, infoPtr->root); + break; + } + + if (retval) + { + TRACE("flags:%x, returns %p\n", which, retval); + return (LRESULT)retval; + } + + if (!TREEVIEW_ValidItem(infoPtr, wineItem)) + return FALSE; + + switch (which) + { + case TVGN_NEXT: + retval = wineItem->nextSibling; + break; + case TVGN_PREVIOUS: + retval = wineItem->prevSibling; + break; + case TVGN_PARENT: + retval = (wineItem->parent != infoPtr->root) ? wineItem->parent : NULL; + break; + case TVGN_CHILD: + retval = wineItem->firstChild; + break; + case TVGN_NEXTVISIBLE: + retval = TREEVIEW_GetNextListItem(infoPtr, wineItem); + break; + case TVGN_PREVIOUSVISIBLE: + retval = TREEVIEW_GetPrevListItem(infoPtr, wineItem); + break; + default: + TRACE("Unknown msg %x,item %p\n", which, wineItem); + break; + } + + TRACE("flags:%x, item %p;returns %p\n", which, wineItem, retval); + return (LRESULT)retval; +} + + +static LRESULT +TREEVIEW_GetCount(TREEVIEW_INFO *infoPtr) +{ + TRACE(" %d\n", infoPtr->uNumItems); + return (LRESULT)infoPtr->uNumItems; +} + +static VOID +TREEVIEW_ToggleItemState(TREEVIEW_INFO *infoPtr, TREEVIEW_ITEM *item) +{ + if (infoPtr->dwStyle & TVS_CHECKBOXES) + { + static const unsigned int state_table[] = { 0, 2, 1 }; + + unsigned int state; + + state = STATEIMAGEINDEX(item->state); + TRACE("state:%x\n", state); + item->state &= ~TVIS_STATEIMAGEMASK; + + if (state < 3) + state = state_table[state]; + + item->state |= INDEXTOSTATEIMAGEMASK(state); + + TRACE("state:%x\n", state); + TREEVIEW_QueueItemRefresh(infoPtr, item); + } +} + + +/* Painting *************************************************************/ + +/* Draw the lines and expand button for an item. Also draws one section + * of the line from item's parent to item's parent's next sibling. */ +static void +TREEVIEW_DrawItemLines(TREEVIEW_INFO *infoPtr, HDC hdc, TREEVIEW_ITEM *item) +{ + LONG centerx, centery; + BOOL lar = ((infoPtr->dwStyle + & (TVS_LINESATROOT|TVS_HASLINES|TVS_HASBUTTONS)) + > TVS_LINESATROOT); + + if (!lar && item->iLevel == 0) + return; + + centerx = (item->linesOffset + item->stateOffset) / 2; + centery = (item->rect.top + item->rect.bottom) / 2; + + if (infoPtr->dwStyle & TVS_HASLINES) + { + HPEN hOldPen, hNewPen; + HTREEITEM parent; + + /* + * Get a dotted grey pen + */ + hNewPen = CreatePen(PS_DOT, 0, infoPtr->clrLine); + hOldPen = SelectObject(hdc, hNewPen); + + MoveToEx(hdc, item->stateOffset, centery, NULL); + LineTo(hdc, centerx - 1, centery); + + if (item->prevSibling || item->parent != infoPtr->root) + { + MoveToEx(hdc, centerx, item->rect.top, NULL); + LineTo(hdc, centerx, centery); + } + + if (item->nextSibling) + { + MoveToEx(hdc, centerx, centery, NULL); + LineTo(hdc, centerx, item->rect.bottom + 1); + } + + /* Draw the line from our parent to its next sibling. */ + parent = item->parent; + while (parent != infoPtr->root) + { + int pcenterx = (parent->linesOffset + parent->stateOffset) / 2; + + if (parent->nextSibling + /* skip top-levels unless TVS_LINESATROOT */ + && parent->stateOffset > parent->linesOffset) + { + MoveToEx(hdc, pcenterx, item->rect.top, NULL); + LineTo(hdc, pcenterx, item->rect.bottom + 1); + } + + parent = parent->parent; + } + + SelectObject(hdc, hOldPen); + DeleteObject(hNewPen); + } + + /* + * Display the (+/-) signs + */ + + if (infoPtr->dwStyle & TVS_HASBUTTONS) + { + if (item->cChildren) + { + LONG height = item->rect.bottom - item->rect.top; + LONG width = item->stateOffset - item->linesOffset; + LONG rectsize = min(height, width) / 4; + /* plussize = ceil(rectsize * 3/4) */ + LONG plussize = (rectsize + 1) * 3 / 4; + + HPEN hNewPen = CreatePen(PS_SOLID, 0, infoPtr->clrLine); + HPEN hOldPen = SelectObject(hdc, hNewPen); + HBRUSH hbr = CreateSolidBrush(infoPtr->clrBk); + HBRUSH hbrOld = SelectObject(hdc, hbr); + + Rectangle(hdc, centerx - rectsize, centery - rectsize, + centerx + rectsize + 1, centery + rectsize + 1); + + SelectObject(hdc, hbrOld); + DeleteObject(hbr); + + SelectObject(hdc, hOldPen); + DeleteObject(hNewPen); + + MoveToEx(hdc, centerx - plussize + 1, centery, NULL); + LineTo(hdc, centerx + plussize, centery); + + if (!(item->state & TVIS_EXPANDED)) + { + MoveToEx(hdc, centerx, centery - plussize + 1, NULL); + LineTo(hdc, centerx, centery + plussize); + } + } + } +} + +static void +TREEVIEW_DrawItem(TREEVIEW_INFO *infoPtr, HDC hdc, TREEVIEW_ITEM *wineItem) +{ + INT cditem; + HFONT hOldFont; + int centery; + + hOldFont = SelectObject(hdc, TREEVIEW_FontForItem(infoPtr, wineItem)); + + TREEVIEW_UpdateDispInfo(infoPtr, wineItem, CALLBACK_MASK_ALL); + + /* The custom draw handler can query the text rectangle, + * so get ready. */ + TREEVIEW_ComputeTextWidth(infoPtr, wineItem, hdc); + + cditem = 0; + + if (infoPtr->cdmode & CDRF_NOTIFYITEMDRAW) + { + cditem = TREEVIEW_SendCustomDrawItemNotify + (infoPtr, hdc, wineItem, CDDS_ITEMPREPAINT); + TRACE("prepaint:cditem-app returns 0x%x\n", cditem); + + if (cditem & CDRF_SKIPDEFAULT) + { + SelectObject(hdc, hOldFont); + return; + } + } + + if (cditem & CDRF_NEWFONT) + TREEVIEW_ComputeTextWidth(infoPtr, wineItem, hdc); + + TREEVIEW_DrawItemLines(infoPtr, hdc, wineItem); + + centery = (wineItem->rect.top + wineItem->rect.bottom) / 2; + + /* + * Display the images associated with this item + */ + { + INT imageIndex; + + /* State images are displayed to the left of the Normal image + * image number is in state; zero should be `display no image'. + */ + imageIndex = STATEIMAGEINDEX(wineItem->state); + + if (infoPtr->himlState && imageIndex) + { + ImageList_Draw(infoPtr->himlState, imageIndex, hdc, + wineItem->stateOffset, + centery - infoPtr->stateImageHeight / 2, + ILD_NORMAL); + } + + /* Now, draw the normal image; can be either selected or + * non-selected image. + */ + + if ((wineItem->state & TVIS_SELECTED) && (wineItem->iSelectedImage)) + { + /* The item is curently selected */ + imageIndex = wineItem->iSelectedImage; + } + else + { + /* The item is not selected */ + imageIndex = wineItem->iImage; + } + + if (infoPtr->himlNormal) + { + int ovlIdx = wineItem->state & TVIS_OVERLAYMASK; + + ImageList_Draw(infoPtr->himlNormal, imageIndex, hdc, + wineItem->imageOffset, + centery - infoPtr->normalImageHeight / 2, + ILD_NORMAL | ovlIdx); + } + } + + + /* + * Display the text associated with this item + */ + + /* Don't paint item's text if it's being edited */ + if (!infoPtr->hwndEdit || (infoPtr->selectedItem != wineItem)) + { + if (wineItem->pszText) + { + COLORREF oldTextColor = 0; + INT oldBkMode; + HBRUSH hbrBk = 0; + BOOL inFocus = (GetFocus() == infoPtr->hwnd); + RECT rcText; + + oldBkMode = SetBkMode(hdc, TRANSPARENT); + + /* - If item is drop target or it is selected and window is in focus - + * use blue background (COLOR_HIGHLIGHT). + * - If item is selected, window is not in focus, but it has style + * TVS_SHOWSELALWAYS - use grey background (COLOR_BTNFACE) + * - Otherwise - don't fill background + */ + if ((wineItem->state & TVIS_DROPHILITED) || ((wineItem == infoPtr->focusedItem) && !(wineItem->state & TVIS_SELECTED)) || + ((wineItem->state & TVIS_SELECTED) && (!infoPtr->focusedItem) && + (inFocus || (infoPtr->dwStyle & TVS_SHOWSELALWAYS)))) + { + if ((wineItem->state & TVIS_DROPHILITED) || inFocus) + { + hbrBk = CreateSolidBrush(GetSysColor(COLOR_HIGHLIGHT)); + oldTextColor = + SetTextColor(hdc, GetSysColor(COLOR_HIGHLIGHTTEXT)); + } + else + { + hbrBk = CreateSolidBrush(GetSysColor(COLOR_BTNFACE)); + + if (infoPtr->clrText == -1) + oldTextColor = + SetTextColor(hdc, GetSysColor(COLOR_WINDOWTEXT)); + else + oldTextColor = SetTextColor(hdc, infoPtr->clrText); + } + } + else + { + if (infoPtr->clrText == -1) + oldTextColor = + SetTextColor(hdc, GetSysColor(COLOR_WINDOWTEXT)); + else + oldTextColor = SetTextColor(hdc, infoPtr->clrText); + } + + rcText.top = wineItem->rect.top; + rcText.bottom = wineItem->rect.bottom; + rcText.left = wineItem->textOffset; + rcText.right = rcText.left + wineItem->textWidth + 4; + + if (hbrBk) + { + FillRect(hdc, &rcText, hbrBk); + DeleteObject(hbrBk); + } + + /* Draw the box arround the selected item */ + if ((wineItem == infoPtr->selectedItem) && inFocus) + { + HPEN hNewPen = CreatePen(PS_DOT, 0, + GetSysColor(COLOR_WINDOWTEXT)); + HPEN hOldPen = SelectObject(hdc, hNewPen); + INT rop = SetROP2(hdc, R2_XORPEN); + POINT points[5]; + + points[4].x = points[0].x = rcText.left; + points[4].y = points[0].y = rcText.top; + points[1].x = rcText.right - 1; + points[1].y = rcText.top; + points[2].x = rcText.right - 1; + points[2].y = rcText.bottom - 1; + points[3].x = rcText.left; + points[3].y = rcText.bottom - 1; + + Polyline(hdc, points, 5); + + SetROP2(hdc, rop); + SelectObject(hdc, hOldPen); + DeleteObject(hNewPen); + } + + rcText.left += 2; + rcText.right -= 2; + + /* Draw it */ + DrawTextA(hdc, + wineItem->pszText, + lstrlenA(wineItem->pszText), + &rcText, + DT_CENTER | DT_VCENTER | DT_SINGLELINE | DT_NOPREFIX); + + /* Restore the hdc state */ + SetTextColor(hdc, oldTextColor); + + if (oldBkMode != TRANSPARENT) + SetBkMode(hdc, oldBkMode); + } + } + + /* Draw insertion mark if necessary */ + + if (infoPtr->insertMarkItem) + TRACE("item:%d,mark:%d\n", + TREEVIEW_GetItemIndex(infoPtr, wineItem), + (int)infoPtr->insertMarkItem); + + if (wineItem == infoPtr->insertMarkItem) + { + HPEN hNewPen, hOldPen; + int offset; + int left, right; + + hNewPen = CreatePen(PS_SOLID, 2, infoPtr->clrInsertMark); + hOldPen = SelectObject(hdc, hNewPen); + + if (infoPtr->insertBeforeorAfter) + offset = wineItem->rect.bottom - 1; + else + offset = wineItem->rect.top + 1; + + left = wineItem->textOffset - 2; + right = wineItem->textOffset + wineItem->textWidth + 2; + + MoveToEx(hdc, left, offset - 3, NULL); + LineTo(hdc, left, offset + 4); + + MoveToEx(hdc, left, offset, NULL); + LineTo(hdc, right + 1, offset); + + MoveToEx(hdc, right, offset + 3, NULL); + LineTo(hdc, right, offset - 4); + + SelectObject(hdc, hOldPen); + DeleteObject(hNewPen); + } + + if (cditem & CDRF_NOTIFYPOSTPAINT) + { + cditem = TREEVIEW_SendCustomDrawItemNotify + (infoPtr, hdc, wineItem, CDDS_ITEMPOSTPAINT); + TRACE("postpaint:cditem-app returns 0x%x\n", cditem); + } + + SelectObject(hdc, hOldFont); +} + +/* Computes treeHeight and treeWidth and updates the scroll bars. + */ +static void +TREEVIEW_UpdateScrollBars(TREEVIEW_INFO *infoPtr) +{ + TREEVIEW_ITEM *wineItem; + HWND hwnd = infoPtr->hwnd; + BOOL vert = FALSE; + BOOL horz = FALSE; + SCROLLINFO si; + LONG scrollX = infoPtr->scrollX; + + infoPtr->treeWidth = 0; + infoPtr->treeHeight = 0; + + /* We iterate through all visible items in order to get the tree height + * and width */ + wineItem = infoPtr->root->firstChild; + + while (wineItem != NULL) + { + if (ISVISIBLE(wineItem)) + { + /* actually we draw text at textOffset + 2 */ + if (2+wineItem->textOffset+wineItem->textWidth > infoPtr->treeWidth) + infoPtr->treeWidth = wineItem->textOffset+wineItem->textWidth+2; + + /* This is scroll-adjusted, but we fix this below. */ + infoPtr->treeHeight = wineItem->rect.bottom; + } + + wineItem = TREEVIEW_GetNextListItem(infoPtr, wineItem); + } + + /* Fix the scroll adjusted treeHeight and treeWidth. */ + if (infoPtr->root->firstChild) + infoPtr->treeHeight -= infoPtr->root->firstChild->rect.top; + + infoPtr->treeWidth += infoPtr->scrollX; + + /* Adding one scroll bar may take up enough space that it forces us + * to add the other as well. */ + if (infoPtr->treeHeight > infoPtr->clientHeight) + { + vert = TRUE; + + if (infoPtr->treeWidth + > infoPtr->clientWidth - GetSystemMetrics(SM_CXVSCROLL)) + horz = TRUE; + } + else if (infoPtr->treeWidth > infoPtr->clientWidth) + horz = TRUE; + + if (!vert && horz && infoPtr->treeHeight + > infoPtr->clientHeight - GetSystemMetrics(SM_CYVSCROLL)) + vert = TRUE; + + si.cbSize = sizeof(SCROLLINFO); + si.fMask = SIF_POS|SIF_RANGE|SIF_PAGE; + si.nMin = 0; + + if (vert) + { + infoPtr->uInternalStatus |= TV_VSCROLL; + + si.nPage = TREEVIEW_GetVisibleCount(infoPtr); + si.nPos = infoPtr->firstVisible->visibleOrder; + si.nMax = infoPtr->maxVisibleOrder - 1; + + SetScrollInfo(hwnd, SB_VERT, &si, TRUE); + } + else + { + if (infoPtr->uInternalStatus & TV_VSCROLL) + ShowScrollBar(hwnd, SB_VERT, FALSE); + + infoPtr->uInternalStatus &= ~TV_VSCROLL; + } + + if (horz) + { + infoPtr->uInternalStatus |= TV_HSCROLL; + + si.nPage = infoPtr->clientWidth; + si.nPos = infoPtr->scrollX; + si.nMax = infoPtr->treeWidth - 1; + + if (si.nPos > si.nMax - max( si.nPage-1, 0 )) + { + si.nPos = si.nMax - max( si.nPage-1, 0 ); + scrollX = si.nPos; + } + + SetScrollInfo(hwnd, SB_HORZ, &si, TRUE); + } + else + { + if (infoPtr->uInternalStatus & TV_HSCROLL) + ShowScrollBar(hwnd, SB_HORZ, FALSE); + + scrollX = 0; + } + + if (infoPtr->scrollX != scrollX) + { + TREEVIEW_HScroll(infoPtr, + MAKEWPARAM(SB_THUMBPOSITION, scrollX)); + } + + if (!horz) + infoPtr->uInternalStatus &= ~TV_HSCROLL; +} + +/* CtrlSpy doesn't mention this, but CorelDRAW's object manager needs it. */ +static LRESULT +TREEVIEW_EraseBackground(TREEVIEW_INFO *infoPtr, HDC hDC) +{ + HBRUSH hBrush = CreateSolidBrush(infoPtr->clrBk); + RECT rect; + + GetClientRect(infoPtr->hwnd, &rect); + FillRect(hDC, &rect, hBrush); + DeleteObject(hBrush); + + return 1; +} + +static void +TREEVIEW_Refresh(TREEVIEW_INFO *infoPtr, HDC hdc, RECT *rc) +{ + HWND hwnd = infoPtr->hwnd; + RECT rect = *rc; + TREEVIEW_ITEM *wineItem; + + if (infoPtr->clientHeight == 0 || infoPtr->clientWidth == 0) + { + TRACE("empty window\n"); + return; + } + + infoPtr->cdmode = TREEVIEW_SendCustomDrawNotify(infoPtr, CDDS_PREPAINT, + hdc, rect); + + if (infoPtr->cdmode == CDRF_SKIPDEFAULT) + { + ReleaseDC(hwnd, hdc); + return; + } + + for (wineItem = infoPtr->root->firstChild; + wineItem != NULL; + wineItem = TREEVIEW_GetNextListItem(infoPtr, wineItem)) + { + if (ISVISIBLE(wineItem)) + { + /* Avoid unneeded calculations */ + if (wineItem->rect.top > rect.bottom) + break; + if (wineItem->rect.bottom < rect.top) + continue; + + TREEVIEW_DrawItem(infoPtr, hdc, wineItem); + } + } + + TREEVIEW_UpdateScrollBars(infoPtr); + + if (infoPtr->cdmode & CDRF_NOTIFYPOSTPAINT) + infoPtr->cdmode = + TREEVIEW_SendCustomDrawNotify(infoPtr, CDDS_POSTPAINT, hdc, rect); +} + +static void +TREEVIEW_QueueRefresh(TREEVIEW_INFO *infoPtr) +{ + InvalidateRect(infoPtr->hwnd, NULL, TRUE); +} + +/* It be that item->rect is out of date. If so, we invalidate the wrong area, + * but then whoever updates item->rect knows that they must invalidate after + * correcting it. */ +static void +TREEVIEW_QueueItemRefresh(TREEVIEW_INFO *infoPtr, TREEVIEW_ITEM *item) +{ + if (item != NULL) + InvalidateRect(infoPtr->hwnd, &item->rect, TRUE); +} + +static LRESULT +TREEVIEW_Paint(TREEVIEW_INFO *infoPtr, WPARAM wParam) +{ + HDC hdc; + PAINTSTRUCT ps; + RECT rc; + + TRACE("\n"); + + if (wParam) + { + hdc = (HDC)wParam; + GetUpdateRect(infoPtr->hwnd, &rc, TRUE); + } + else + { + hdc = BeginPaint(infoPtr->hwnd, &ps); + rc = ps.rcPaint; + } + + if(infoPtr->bRedraw) /* WM_SETREDRAW sets bRedraw */ + TREEVIEW_Refresh(infoPtr, hdc, &rc); + + if (!wParam) + EndPaint(infoPtr->hwnd, &ps); + + return 0; +} + + +/* Sorting **************************************************************/ + /*************************************************************************** * Forward the DPA local callback to the treeview owner callback */ -static INT WINAPI TREEVIEW_CallBackCompare( - LPVOID first, - LPVOID second, - LPARAM tvInfoPtr) +static INT WINAPI +TREEVIEW_CallBackCompare(TREEVIEW_ITEM *first, TREEVIEW_ITEM *second, LPTVSORTCB pCallBackSort) { - /* Forward the call to the client define callback */ - TREEVIEW_INFO *infoPtr = TREEVIEW_GetInfoPtr((HWND)tvInfoPtr); - return (infoPtr->pCallBackSort->lpfnCompare)( - ((TREEVIEW_ITEM*)first)->lParam, - ((TREEVIEW_ITEM*)second)->lParam, - infoPtr->pCallBackSort->lParam); + /* Forward the call to the client-defined callback */ + return pCallBackSort->lpfnCompare(first->lParam, + second->lParam, + pCallBackSort->lParam); } /*************************************************************************** * Treeview native sort routine: sort on item text. */ -static INT WINAPI TREEVIEW_SortOnName ( - LPVOID first, - LPVOID second, - LPARAM tvInfoPtr) +static INT WINAPI +TREEVIEW_SortOnName(TREEVIEW_ITEM *first, TREEVIEW_ITEM *second, + TREEVIEW_INFO *infoPtr) { - HWND hwnd=(HWND) tvInfoPtr; - char *txt1, *txt2; - TREEVIEW_ITEM *item; + TREEVIEW_UpdateDispInfo(infoPtr, first, TVIF_TEXT); + TREEVIEW_UpdateDispInfo(infoPtr, second, TVIF_TEXT); - - item=(TREEVIEW_ITEM *) first; - if (item->pszText==LPSTR_TEXTCALLBACKA) { - TREEVIEW_SendDispInfoNotify (hwnd, item, TVN_GETDISPINFOA, TVIF_TEXT); + return strcasecmp(first->pszText, second->pszText); +} + +/* Returns the number of physical children belonging to item. */ +static INT +TREEVIEW_CountChildren(TREEVIEW_INFO *infoPtr, TREEVIEW_ITEM *item) +{ + INT cChildren = 0; + HTREEITEM hti; + + for (hti = item->firstChild; hti != NULL; hti = hti->nextSibling) + cChildren++; + + return cChildren; +} + +/* Returns a DPA containing a pointer to each physical child of item in + * sibling order. If item has no children, an empty DPA is returned. */ +static HDPA +TREEVIEW_BuildChildDPA(TREEVIEW_INFO *infoPtr, TREEVIEW_ITEM *item) +{ + HTREEITEM child = item->firstChild; + + HDPA list = DPA_Create(8); + if (list == 0) return NULL; + + for (child = item->firstChild; child != NULL; child = child->nextSibling) + { + if (DPA_InsertPtr(list, INT_MAX, child) == -1) + { + DPA_Destroy(list); + return NULL; } - txt1=item->pszText; + } - item=(TREEVIEW_ITEM *) second; - if (item->pszText==LPSTR_TEXTCALLBACKA) { - TREEVIEW_SendDispInfoNotify (hwnd, item, TVN_GETDISPINFOA, TVIF_TEXT); - } - txt2=item->pszText; - - return -strcmp (txt1,txt2); + return list; } /*************************************************************************** @@ -1755,109 +2705,116 @@ static INT WINAPI TREEVIEW_SortOnName ( * application decide what that means. See also TVM_SORTCHILDRENCB. */ -static LRESULT WINAPI TREEVIEW_Sort ( - HWND hwnd, - BOOL fRecurse, - HTREEITEM parent, - LPTVSORTCB pSort - ) +static LRESULT +TREEVIEW_Sort(TREEVIEW_INFO *infoPtr, BOOL fRecurse, HTREEITEM parent, + LPTVSORTCB pSort) { - TREEVIEW_INFO *infoPtr = TREEVIEW_GetInfoPtr(hwnd); - TREEVIEW_ITEM *sortMe = NULL; /* Node for which we sort the children */ + INT cChildren; + PFNDPACOMPARE pfnCompare; + LPARAM lpCompare; - /* Obtain the TVSORTBC struct */ - infoPtr->pCallBackSort = pSort; + /* undocumented feature: TVI_ROOT means `sort the whole tree' */ + if (parent == TVI_ROOT) + parent = infoPtr->root; - /* undocumented feature: TVI_ROOT means `sort the whole tree' */ - - if (parent==TVI_ROOT) - parent=infoPtr->TopRootItem; - - /* Check for a valid handle to the parent item */ - if (!TREEVIEW_ValidItem(infoPtr, parent)) - { - ERR ("invalid item hParent=%x\n", (INT)parent); - return FALSE; - } - - /* Obtain the parent node to sort */ - sortMe = &infoPtr->items[ (INT)parent ]; - - /* Make sure there is something to sort */ - if ( sortMe->cChildren > 1 ) - { - /* pointer organization */ - HDPA sortList = DPA_Create(sortMe->cChildren); - HTREEITEM itemHandle = sortMe->firstChild; - TREEVIEW_ITEM *itemPtr = & infoPtr->items[ (INT)itemHandle ]; - - /* TREEVIEW_ITEM rechaining */ - INT count = 0; - VOID *item = 0; - VOID *nextItem = 0; - VOID *prevItem = 0; - - /* Build the list of item to sort */ - do + /* Check for a valid handle to the parent item */ + if (!TREEVIEW_ValidItem(infoPtr, parent)) { - DPA_InsertPtr( - sortList, /* the list */ - sortMe->cChildren+1, /* force the insertion to be an append */ - itemPtr); /* the ptr to store */ - - /* Get the next sibling */ - itemHandle = itemPtr->sibling; - itemPtr = & infoPtr->items[ (INT)itemHandle ]; - } while ( itemHandle != NULL ); - - /* let DPA perform the sort activity */ - if (pSort) - DPA_Sort( - sortList, /* what */ - TREEVIEW_CallBackCompare, /* how */ - hwnd); /* owner */ - else - DPA_Sort ( - sortList, /* what */ - TREEVIEW_SortOnName, /* how */ - hwnd); /* owner */ - - /* - * Reorganized TREEVIEW_ITEM structures. - * Note that we know we have at least two elements. - */ - - /* Get the first item and get ready to start... */ - item = DPA_GetPtr(sortList, count++); - while ( (nextItem = DPA_GetPtr(sortList, count++)) != NULL ) - { - /* link the two current item toghether */ - ((TREEVIEW_ITEM*)item)->sibling = ((TREEVIEW_ITEM*)nextItem)->hItem; - ((TREEVIEW_ITEM*)nextItem)->upsibling = ((TREEVIEW_ITEM*)item)->hItem; - - if (prevItem == NULL) /* this is the first item, update the parent */ - { - sortMe->firstChild = ((TREEVIEW_ITEM*)item)->hItem; - ((TREEVIEW_ITEM*)item)->upsibling = NULL; - } - else /* fix the back chaining */ - { - ((TREEVIEW_ITEM*)item)->upsibling = ((TREEVIEW_ITEM*)prevItem)->hItem; - } - - /* get ready for the next one */ - prevItem = item; - item = nextItem; + ERR("invalid item hParent=%x\n", (INT)parent); + return FALSE; } - /* the last item is pointed to by item and never has a sibling */ - ((TREEVIEW_ITEM*)item)->sibling = NULL; + if (pSort) + { + pfnCompare = (PFNDPACOMPARE)TREEVIEW_CallBackCompare; + lpCompare = (LPARAM)pSort; + } + else + { + pfnCompare = (PFNDPACOMPARE)TREEVIEW_SortOnName; + lpCompare = (LPARAM)infoPtr; + } - DPA_Destroy(sortList); + cChildren = TREEVIEW_CountChildren(infoPtr, parent); - return TRUE; - } - return FALSE; + /* Make sure there is something to sort */ + if (cChildren > 1) + { + /* TREEVIEW_ITEM rechaining */ + INT count = 0; + HTREEITEM item = 0; + HTREEITEM nextItem = 0; + HTREEITEM prevItem = 0; + + HDPA sortList = TREEVIEW_BuildChildDPA(infoPtr, parent); + + if (sortList == NULL) + return FALSE; + + /* let DPA sort the list */ + DPA_Sort(sortList, pfnCompare, lpCompare); + + /* The order of DPA entries has been changed, so fixup the + * nextSibling and prevSibling pointers. */ + + item = (HTREEITEM)DPA_GetPtr(sortList, count++); + while ((nextItem = (HTREEITEM)DPA_GetPtr(sortList, count++)) != NULL) + { + /* link the two current item toghether */ + item->nextSibling = nextItem; + nextItem->prevSibling = item; + + if (prevItem == NULL) + { + /* this is the first item, update the parent */ + parent->firstChild = item; + item->prevSibling = NULL; + } + else + { + /* fix the back chaining */ + item->prevSibling = prevItem; + } + + /* get ready for the next one */ + prevItem = item; + item = nextItem; + } + + /* the last item is pointed to by item and never has a sibling */ + item->nextSibling = NULL; + parent->lastChild = item; + + DPA_Destroy(sortList); + + TREEVIEW_VerifyTree(infoPtr); + + if (parent->state & TVIS_EXPANDED) + { + int visOrder = infoPtr->firstVisible->visibleOrder; + + TREEVIEW_RecalculateVisibleOrder(infoPtr, parent); + + if (TREEVIEW_IsChildOf(parent, infoPtr->firstVisible)) + { + TREEVIEW_ITEM *item; + + for (item = infoPtr->root->firstChild; item != NULL; + item = TREEVIEW_GetNextListItem(infoPtr, item)) + { + if (item->visibleOrder == visOrder) + break; + } + + TREEVIEW_SetFirstVisible(infoPtr, item, FALSE); + } + + TREEVIEW_QueueRefresh(infoPtr); + } + + return TRUE; + } + return FALSE; } @@ -1865,1058 +2822,213 @@ static LRESULT WINAPI TREEVIEW_Sort ( * Setup the treeview structure with regards of the sort method * and sort the children of the TV item specified in lParam */ -static LRESULT WINAPI TREEVIEW_SortChildrenCB( - HWND hwnd, - WPARAM wParam, - LPARAM lParam - ) +static LRESULT +TREEVIEW_SortChildrenCB(TREEVIEW_INFO *infoPtr, WPARAM wParam, LPTVSORTCB pSort) { - LPTVSORTCB pSort=(LPTVSORTCB) lParam; - - return TREEVIEW_Sort (hwnd, wParam, pSort->hParent, pSort); + return TREEVIEW_Sort(infoPtr, wParam, pSort->hParent, pSort); } /*************************************************************************** * Sort the children of the TV item specified in lParam. */ -static LRESULT WINAPI TREEVIEW_SortChildren ( - HWND hwnd, - WPARAM wParam, - LPARAM lParam) +static LRESULT +TREEVIEW_SortChildren(TREEVIEW_INFO *infoPtr, WPARAM wParam, LPARAM lParam) { - return TREEVIEW_Sort (hwnd, (BOOL) wParam, (HTREEITEM) lParam, NULL); + return TREEVIEW_Sort(infoPtr, (BOOL)wParam, (HTREEITEM)lParam, NULL); } +/* Expansion/Collapse ***************************************************/ -/* the method used below isn't the most memory-friendly, but it avoids - a lot of memory reallocations */ - -/* BTW: we waste handle 0; 0 is not an allowed handle. */ - -static LRESULT -TREEVIEW_InsertItemA (HWND hwnd, WPARAM wParam, LPARAM lParam) - +static BOOL +TREEVIEW_SendExpanding(TREEVIEW_INFO *infoPtr, TREEVIEW_ITEM *wineItem, + UINT action) { - TREEVIEW_INFO *infoPtr = TREEVIEW_GetInfoPtr(hwnd); - TVINSERTSTRUCTA *ptdi; - TVITEMEXA *tvItem; - TREEVIEW_ITEM *wineItem, *parentItem, *prevsib, *sibItem; - INT iItem,listItems,i,len; - - /* Item to insert */ - ptdi = (LPTVINSERTSTRUCTA) lParam; + return !TREEVIEW_SendTreeviewNotify(infoPtr, TVN_ITEMEXPANDINGA, action, + TVIF_HANDLE | TVIF_STATE | TVIF_PARAM + | TVIF_IMAGE | TVIF_SELECTEDIMAGE, + 0, wineItem); +} - /* check if memory is available */ - - if (infoPtr->uNumPtrsAlloced==0) { - infoPtr->items = COMCTL32_Alloc (TVITEM_ALLOC*sizeof (TREEVIEW_ITEM)); - infoPtr->freeList= COMCTL32_Alloc ((1+(TVITEM_ALLOC>>5)) * sizeof (INT)); - infoPtr->uNumPtrsAlloced=TVITEM_ALLOC; - infoPtr->TopRootItem=(HTREEITEM)1; - } - - /* - * Reallocate contiguous space for items - */ - if (infoPtr->uNumItems == (infoPtr->uNumPtrsAlloced-1) ) { - TREEVIEW_ITEM *oldItems = infoPtr->items; - INT *oldfreeList = infoPtr->freeList; - - infoPtr->uNumPtrsAlloced*=2; - infoPtr->items = COMCTL32_Alloc (infoPtr->uNumPtrsAlloced*sizeof (TREEVIEW_ITEM)); - infoPtr->freeList= COMCTL32_Alloc ((1+(infoPtr->uNumPtrsAlloced>>5))*sizeof (INT)); - - memcpy (&infoPtr->items[0], &oldItems[0], - infoPtr->uNumPtrsAlloced/2 * sizeof(TREEVIEW_ITEM)); - memcpy (&infoPtr->freeList[0], &oldfreeList[0], - (infoPtr->uNumPtrsAlloced>>6) * sizeof(INT)); - - COMCTL32_Free (oldItems); - COMCTL32_Free (oldfreeList); - } - - /* - * Reset infoPtr structure with new stat according to current TV picture - */ - iItem=0; - infoPtr->uNumItems++; - if ((INT)infoPtr->uMaxHandle==(infoPtr->uNumItems-1)) { - iItem=infoPtr->uNumItems; - infoPtr->uMaxHandle = (HTREEITEM)((INT)infoPtr->uMaxHandle + 1); - } else { /* check freelist */ - for (i=0; i<=infoPtr->uNumPtrsAlloced>>5; i++) { - if (infoPtr->freeList[i]) { - iItem=ffs (infoPtr->freeList[i])-1; - tv_clear_bit(iItem,&infoPtr->freeList[i]); - iItem+=i<<5; - break; - } - } - } - - if (TRACE_ON(treeview)) { - for (i=0; i<=infoPtr->uNumPtrsAlloced>>5; i++) - TRACE("%8x\n",infoPtr->freeList[i]); - } - - if (!iItem) ERR("Argh -- can't find free item.\n"); - - /* - * Find the parent item of the new item - */ - tvItem= & ptdi->DUMMYUNIONNAME.itemex; - wineItem=& infoPtr->items[iItem]; - - if ((ptdi->hParent==TVI_ROOT) || (ptdi->hParent==0)) { - parentItem = NULL; - wineItem->parent = 0; - sibItem = &infoPtr->items [(INT)infoPtr->TopRootItem]; - listItems = infoPtr->uNumItems; - } - else { - parentItem = &infoPtr->items[(INT)ptdi->hParent]; - - /* Do the insertion here it if it's the only item of this parent */ - if (!parentItem->firstChild) - parentItem->firstChild=(HTREEITEM)iItem; - - wineItem->parent = ptdi->hParent; - sibItem = &infoPtr->items [(INT)parentItem->firstChild]; - listItems = parentItem->cChildren; - parentItem->cChildren++; - } - - - /* NOTE: I am moving some setup of the wineItem object that was initialy - * done at the end of the function since some of the values are - * required by the Callback sorting - */ - - if (tvItem->mask & TVIF_TEXT) - { - /* - * Setup the item text stuff here since it's required by the Sort method - * when the insertion are ordered - */ - if (tvItem->pszText!=LPSTR_TEXTCALLBACKA) - { - TRACE("(%p,%s)\n", &tvItem->pszText, tvItem->pszText); - len = lstrlenA (tvItem->pszText)+1; - wineItem->pszText= COMCTL32_Alloc (len+1); - strcpy (wineItem->pszText, tvItem->pszText); - wineItem->cchTextMax=len; - } - else - { - TRACE("LPSTR_TEXTCALLBACK\n"); - wineItem->pszText = LPSTR_TEXTCALLBACKA; - wineItem->cchTextMax = 0; - } - } - - if (tvItem->mask & TVIF_PARAM) - wineItem->lParam=tvItem->lParam; - - - wineItem->upsibling=0; /* needed in case we're the first item in a list */ - wineItem->sibling=0; - wineItem->firstChild=0; - wineItem->hItem=(HTREEITEM)iItem; - - if (listItems!=0) { - prevsib=NULL; - - switch ((DWORD) ptdi->hInsertAfter) { - case (DWORD) TVI_FIRST: - if (sibItem==wineItem) break; - if (wineItem->parent) { - wineItem->sibling=parentItem->firstChild; - parentItem->firstChild=(HTREEITEM)iItem; - } else { - wineItem->sibling=infoPtr->TopRootItem; - infoPtr->TopRootItem=(HTREEITEM)iItem; - } - sibItem->upsibling=(HTREEITEM)iItem; - break; - - case (DWORD) TVI_SORT: - if (sibItem==wineItem) - /* - * This item is the first child of the level and it - * has already been inserted - */ - break; - else - { - TREEVIEW_ITEM *aChild; - - - TREEVIEW_ITEM *previousChild = NULL; - BOOL bItemInserted = FALSE; - - if (parentItem) - aChild = &infoPtr->items[(INT)parentItem->firstChild]; - else - aChild = &infoPtr->items[(INT)infoPtr->TopRootItem]; - - /* lookup the text if using LPSTR_TEXTCALLBACKs */ - if (wineItem->pszText==LPSTR_TEXTCALLBACKA) { - TREEVIEW_SendDispInfoNotify (hwnd, wineItem, TVN_GETDISPINFOA, TVIF_TEXT); - } - - /* Iterate the parent children to see where we fit in */ - while ( aChild != NULL ) - { - INT comp; - - /* lookup the text if using LPSTR_TEXTCALLBACKs */ - if (aChild->pszText==LPSTR_TEXTCALLBACKA) { - TREEVIEW_SendDispInfoNotify (hwnd, aChild, TVN_GETDISPINFOA, TVIF_TEXT); - } - - comp = strcmp(wineItem->pszText, aChild->pszText); - if ( comp < 0 ) /* we are smaller than the current one */ - { - TREEVIEW_InsertBefore(infoPtr, wineItem, aChild, parentItem); - bItemInserted = TRUE; - break; - } - else if ( comp > 0 ) /* we are bigger than the current one */ - { - previousChild = aChild; - aChild = (aChild->sibling == 0) /* This will help us to exit */ - ? NULL /* if there is no more sibling */ - : &infoPtr->items[(INT)aChild->sibling]; - - /* Look at the next item */ - continue; - } - else if ( comp == 0 ) - { - /* - * An item with this name is already existing, therefore, - * we add after the one we found - */ - TREEVIEW_InsertAfter(infoPtr, wineItem, aChild, parentItem); - bItemInserted = TRUE; - break; - } - } - - /* - * we reach the end of the child list and the item as not - * yet been inserted, therefore, insert it after the last child. - */ - if ( (! bItemInserted ) && (aChild == NULL) ) - TREEVIEW_InsertAfter(infoPtr, wineItem, previousChild, parentItem); - - break; - } - - - case (DWORD) TVI_LAST: - if (sibItem==wineItem) break; - while (sibItem->sibling) { - prevsib=sibItem; - sibItem=&infoPtr->items [(INT)sibItem->sibling]; - } - sibItem->sibling=(HTREEITEM)iItem; - wineItem->upsibling=sibItem->hItem; - break; - default: - { - TREEVIEW_ITEM *localsibItem = sibItem; - while ((localsibItem->sibling) && - (localsibItem->hItem!=ptdi->hInsertAfter)) - { - prevsib=localsibItem; - localsibItem=&infoPtr->items [(INT)localsibItem->sibling]; - } - if (localsibItem->hItem!=ptdi->hInsertAfter) { - WARN("tried to insert item after nonexisting handle %d treating as TVI_LAST.\n", - (INT) ptdi->hInsertAfter); - /* - * retry placing it last - */ - if (sibItem==wineItem) break; - while (sibItem->sibling) { - prevsib=sibItem; - sibItem=&infoPtr->items [(INT)sibItem->sibling]; - } - sibItem->sibling=(HTREEITEM)iItem; - wineItem->upsibling=sibItem->hItem; - break; - } - prevsib=localsibItem; - if (localsibItem->sibling) { - localsibItem=&infoPtr->items [(INT)localsibItem->sibling]; - localsibItem->upsibling=(HTREEITEM)iItem; - wineItem->sibling=localsibItem->hItem; - } - prevsib->sibling=(HTREEITEM)iItem; - wineItem->upsibling=prevsib->hItem; - break; - } - } - } - - -/* Fill in info structure */ - - TRACE("new item %d; parent %d, mask %x\n", iItem, - (INT)wineItem->parent,tvItem->mask); - - wineItem->mask=tvItem->mask; - wineItem->iIntegral=1; - - if (tvItem->mask & TVIF_CHILDREN) { - wineItem->cChildren=tvItem->cChildren; - if (tvItem->cChildren==I_CHILDRENCALLBACK) - FIXME(" I_CHILDRENCALLBACK not supported\n"); - } - - wineItem->expandBox.left = 0; /* Initialize the expandBox */ - wineItem->expandBox.top = 0; - wineItem->expandBox.right = 0; - wineItem->expandBox.bottom = 0; - - if (tvItem->mask & TVIF_IMAGE) - wineItem->iImage=tvItem->iImage; - - /* If the application sets TVIF_INTEGRAL without - supplying a TVITEMEX structure, it's toast */ - - if (tvItem->mask & TVIF_INTEGRAL) - wineItem->iIntegral=tvItem->iIntegral; - - if (tvItem->mask & TVIF_SELECTEDIMAGE) - wineItem->iSelectedImage=tvItem->iSelectedImage; - - if (tvItem->mask & TVIF_STATE) { - TRACE("item state: %x ->%x\n", wineItem->state, tvItem->state); - TRACE("statemask: %x ->%x\n", wineItem->stateMask, tvItem->stateMask); - wineItem->state=tvItem->state; - wineItem->stateMask=tvItem->stateMask; - } - - TREEVIEW_QueueRefresh (hwnd); - - return (LRESULT) iItem; +static VOID +TREEVIEW_SendExpanded(TREEVIEW_INFO *infoPtr, TREEVIEW_ITEM *wineItem, + UINT action) +{ + TREEVIEW_SendTreeviewNotify(infoPtr, TVN_ITEMEXPANDEDA, action, + TVIF_HANDLE | TVIF_STATE | TVIF_PARAM + | TVIF_IMAGE | TVIF_SELECTEDIMAGE, + 0, wineItem); } -static LRESULT -TREEVIEW_InsertItemW(HWND hwnd, WPARAM wParam, LPARAM lParam) +/* This corresponds to TVM_EXPAND with TVE_COLLAPSE. + * bRemoveChildren corresponds to TVE_COLLAPSERESET. */ +static BOOL +TREEVIEW_Collapse(TREEVIEW_INFO *infoPtr, TREEVIEW_ITEM *wineItem, + BOOL bRemoveChildren, BOOL bUser) { - TVINSERTSTRUCTW *tvisW; - TVINSERTSTRUCTA tvisA; - LRESULT lRes; + UINT action = TVE_COLLAPSE | (bRemoveChildren ? TVE_COLLAPSERESET : 0); + BOOL bSetSelection, bSetFirstVisible; - tvisW = (LPTVINSERTSTRUCTW)lParam; + TRACE("TVE_COLLAPSE %p %s\n", wineItem, TREEVIEW_ItemName(wineItem)); - tvisA.hParent = tvisW->hParent; - tvisA.hInsertAfter = tvisW->hInsertAfter; + if (!(wineItem->state & TVIS_EXPANDED) || wineItem->firstChild == NULL) + return FALSE; - tvisA.DUMMYUNIONNAME.item.mask = tvisW->DUMMYUNIONNAME.item.mask; - tvisA.DUMMYUNIONNAME.item.hItem = tvisW->DUMMYUNIONNAME.item.hItem; - tvisA.DUMMYUNIONNAME.item.state = tvisW->DUMMYUNIONNAME.item.state; - tvisA.DUMMYUNIONNAME.item.stateMask = tvisW->DUMMYUNIONNAME.item.stateMask; - tvisA.DUMMYUNIONNAME.item.cchTextMax = tvisW->DUMMYUNIONNAME.item.cchTextMax; + if (bUser) + TREEVIEW_SendExpanding(infoPtr, wineItem, action); - if(tvisW->DUMMYUNIONNAME.item.pszText) + wineItem->state &= ~TVIS_EXPANDED; + + if (bUser) + TREEVIEW_SendExpanded(infoPtr, wineItem, action); + + bSetSelection = (infoPtr->selectedItem != NULL + && TREEVIEW_IsChildOf(wineItem, infoPtr->selectedItem)); + + bSetFirstVisible = (infoPtr->firstVisible != NULL + && TREEVIEW_IsChildOf(wineItem, infoPtr->firstVisible)); + + if (bRemoveChildren) { - if (tvisW->DUMMYUNIONNAME.item.pszText!=LPSTR_TEXTCALLBACKW) - { - int len = lstrlenW (tvisW->DUMMYUNIONNAME.item.pszText)+1; - tvisA.DUMMYUNIONNAME.item.pszText = COMCTL32_Alloc (len); - lstrcpyWtoA (tvisA.DUMMYUNIONNAME.item.pszText, - tvisW->DUMMYUNIONNAME.item.pszText ); - } - else + TRACE("TVE_COLLAPSERESET\n"); + wineItem->state &= ~TVIS_EXPANDEDONCE; + TREEVIEW_RemoveAllChildren(infoPtr, wineItem); + } + + if (wineItem->firstChild) + { + TREEVIEW_ITEM *item, *sibling; + + sibling = TREEVIEW_GetNextListItem(infoPtr, wineItem); + + for (item = wineItem->firstChild; item != sibling; + item = TREEVIEW_GetNextListItem(infoPtr, item)) { - tvisA.DUMMYUNIONNAME.item.pszText = LPSTR_TEXTCALLBACKA; - tvisA.DUMMYUNIONNAME.item.cchTextMax = 0; + item->visibleOrder = -1; } } - tvisA.DUMMYUNIONNAME.item.iImage = tvisW->DUMMYUNIONNAME.item.iImage; - tvisA.DUMMYUNIONNAME.item.iSelectedImage = tvisW->DUMMYUNIONNAME.item.iSelectedImage; - tvisA.DUMMYUNIONNAME.item.cChildren = tvisW->DUMMYUNIONNAME.item.cChildren; - tvisA.DUMMYUNIONNAME.item.lParam = tvisW->DUMMYUNIONNAME.item.lParam; + TREEVIEW_RecalculateVisibleOrder(infoPtr, wineItem); - lRes = TREEVIEW_InsertItemA(hwnd,wParam,(LPARAM)&tvisA); + TREEVIEW_SetFirstVisible(infoPtr, bSetFirstVisible ? wineItem + : infoPtr->firstVisible, TRUE); - if (tvisA.DUMMYUNIONNAME.item.pszText!=LPSTR_TEXTCALLBACKA) + if (bSetSelection) { - COMCTL32_Free(tvisA.DUMMYUNIONNAME.item.pszText); + /* Don't call DoSelectItem, it sends notifications. */ + if (TREEVIEW_ValidItem(infoPtr, infoPtr->selectedItem)) + infoPtr->selectedItem->state &= ~TVIS_SELECTED; + wineItem->state |= TVIS_SELECTED; + infoPtr->selectedItem = wineItem; + + TREEVIEW_EnsureVisible(infoPtr, wineItem, FALSE); } - return lRes; + TREEVIEW_UpdateScrollBars(infoPtr); + TREEVIEW_QueueRefresh(infoPtr); -} - - -static LRESULT -TREEVIEW_DeleteItem (HWND hwnd, WPARAM wParam, LPARAM lParam) -{ - TREEVIEW_INFO *infoPtr = TREEVIEW_GetInfoPtr(hwnd); - INT iItem; - TREEVIEW_ITEM *wineItem; - - TRACE("item = %08lx\n", lParam); - - if (lParam == (INT)TVI_ROOT) { - TREEVIEW_RemoveTree (hwnd); - } else { - iItem= (INT) lParam; - wineItem = TREEVIEW_ValidItem (infoPtr, (HTREEITEM)iItem); - if (!wineItem) return FALSE; - - if (wineItem->pszText==LPSTR_TEXTCALLBACKA) - TRACE("LPSTR_TEXTCALLBACK\n"); - else - TRACE("%s\n",wineItem->pszText); - TREEVIEW_RemoveItem (hwnd, wineItem); - } - - TREEVIEW_QueueRefresh (hwnd); - - return TRUE; -} - - - -static LRESULT -TREEVIEW_GetIndent (HWND hwnd) -{ - TREEVIEW_INFO *infoPtr = TREEVIEW_GetInfoPtr(hwnd); - - TRACE("\n"); - return infoPtr->uIndent; -} - -static LRESULT -TREEVIEW_SetIndent (HWND hwnd, WPARAM wParam) -{ - TREEVIEW_INFO *infoPtr = TREEVIEW_GetInfoPtr(hwnd); - INT newIndent; - - TRACE("\n"); - newIndent=(INT) wParam; - if (newIndent < MINIMUM_INDENT) newIndent=MINIMUM_INDENT; - infoPtr->uIndent=newIndent; - - return 0; -} - -static LRESULT -TREEVIEW_GetToolTips (HWND hwnd) - -{ - TREEVIEW_INFO *infoPtr = TREEVIEW_GetInfoPtr(hwnd); - - TRACE("\n"); - return infoPtr->hwndToolTip; -} - - -static LRESULT -TREEVIEW_SetToolTips (HWND hwnd, WPARAM wParam) - -{ - TREEVIEW_INFO *infoPtr = TREEVIEW_GetInfoPtr(hwnd); - HWND prevToolTip; - - TRACE("\n"); - prevToolTip=infoPtr->hwndToolTip; - infoPtr->hwndToolTip= (HWND) wParam; - - return prevToolTip; -} - - -static LRESULT CALLBACK -TREEVIEW_GetEditControl (HWND hwnd) - -{ - TREEVIEW_INFO *infoPtr = TREEVIEW_GetInfoPtr(hwnd); - - return infoPtr->hwndEdit; -} - -LRESULT CALLBACK -TREEVIEW_Edit_SubclassProc (HWND hwnd, UINT uMsg, WPARAM wParam, - LPARAM lParam) -{ - switch (uMsg) - { - case WM_ERASEBKGND: - { - RECT rc; - HDC hdc = (HDC) wParam; - GetClientRect (hwnd, &rc); - Rectangle (hdc, rc.left, rc.top, rc.right, rc.bottom); - return -1; - } - - case WM_GETDLGCODE: - { - return DLGC_WANTARROWS | DLGC_WANTALLKEYS; - } - - case WM_KEYDOWN: - if (wParam == VK_ESCAPE) -{ - TREEVIEW_EndEditLabelNow(GetParent(hwnd), (WPARAM)TRUE, 0); - return 1; -} - else if (wParam == VK_RETURN) - TREEVIEW_EndEditLabelNow(GetParent(hwnd), (WPARAM)FALSE, 0); - break; - - - - default: - { - TREEVIEW_INFO *infoPtr = TREEVIEW_GetInfoPtr(GetParent(hwnd)); - if (infoPtr!=NULL) - return CallWindowProcA (infoPtr->wpEditOrig, hwnd, uMsg, wParam, lParam); - else - break; - - } - } - - return 0; -} - - -/* should handle edit control messages here */ - -static LRESULT -TREEVIEW_Command (HWND hwnd, WPARAM wParam, LPARAM lParam) - -{ - TRACE("%x %ld\n",wParam, lParam); - - switch (HIWORD(wParam)) - { - case EN_UPDATE: - { - /* - * Adjust the edit window size - */ - TREEVIEW_INFO *infoPtr = TREEVIEW_GetInfoPtr(hwnd); - TREEVIEW_ITEM *editItem = TREEVIEW_ValidItem(infoPtr, infoPtr->editItem); - INT iLength = GetWindowTextLengthA(infoPtr->hwndEdit); - HDC hdc = GetDC(infoPtr->hwndEdit); - TEXTMETRICA tm; - - if ( GetTextMetricsA(hdc, &tm) ) - { - LONG newWidth = (iLength * tm.tmAveCharWidth) + 15; - - SetWindowPos ( - infoPtr->hwndEdit, - HWND_TOP, - editItem->text.left - 2, - editItem->text.top - 1, - newWidth, - editItem->text.bottom - editItem->text.top + 3, - SWP_DRAWFRAME ); - } - ReleaseDC(hwnd, hdc); - - break; - } - - case EN_KILLFOCUS: -/* TREEVIEW_EndEditLabelNow(hwnd, (WPARAM)FALSE, 0); -*/ - break; - - default: - return SendMessageA (GetParent (hwnd), WM_COMMAND, wParam, lParam); - } - - return 0; -} - -static LRESULT -TREEVIEW_Size (HWND hwnd, WPARAM wParam, LPARAM lParam) - -{ - TREEVIEW_INFO *infoPtr = TREEVIEW_GetInfoPtr(hwnd); - - if (infoPtr->bAutoSize) - { - infoPtr->bAutoSize = FALSE; - return 0; - } - infoPtr->bAutoSize = TRUE; - - if (wParam == SIZE_RESTORED) - { - infoPtr->uTotalWidth = LOWORD (lParam); - infoPtr->uTotalHeight = HIWORD (lParam); - } else { - FIXME("WM_SIZE flag %x %lx not handled\n", wParam, lParam); - } - - TREEVIEW_QueueRefresh (hwnd); - return 0; -} - - - -static LRESULT -TREEVIEW_StyleChanged (HWND hwnd, WPARAM wParam, LPARAM lParam) -{ - TRACE("(%x %lx)\n",wParam,lParam); - - InvalidateRect(hwnd, NULL, FALSE); - - return 0; -} - -static LRESULT -TREEVIEW_Create (HWND hwnd, WPARAM wParam, LPARAM lParam) -{ - TREEVIEW_INFO *infoPtr; - DWORD dwStyle = GetWindowLongA (hwnd, GWL_STYLE); - LOGFONTA logFont; - TEXTMETRICA tm; - HDC hdc; - - TRACE("wnd %x, style %lx\n",hwnd,dwStyle); - /* allocate memory for info structure */ - infoPtr = (TREEVIEW_INFO *) COMCTL32_Alloc (sizeof(TREEVIEW_INFO)); - - SetWindowLongA( hwnd, 0, (DWORD)infoPtr); - - if (infoPtr == NULL) { - ERR("could not allocate info memory!\n"); - return 0; - } - - if ((TREEVIEW_INFO*) GetWindowLongA( hwnd, 0) != infoPtr) { - ERR("pointer assignment error!\n"); - return 0; - } - - hdc=GetDC (hwnd); - - /* set default settings */ - infoPtr->uInternalStatus=0; - infoPtr->uNumItems=0; - infoPtr->clrBk = GetSysColor (COLOR_WINDOW); - infoPtr->clrText = GetSysColor (COLOR_WINDOWTEXT); - infoPtr->clrLine = GetSysColor (COLOR_WINDOWTEXT); - infoPtr->clrInsertMark = GetSysColor (COLOR_BTNTEXT); - infoPtr->cy = 0; - infoPtr->cx = 0; - infoPtr->uIndent = 15; - infoPtr->himlNormal = NULL; - infoPtr->himlState = NULL; - infoPtr->uItemHeight = -1; - GetTextMetricsA (hdc, &tm); - infoPtr->hFont = GetStockObject (DEFAULT_GUI_FONT); - GetObjectA (infoPtr->hFont, sizeof (LOGFONTA), &logFont); - logFont.lfWeight=FW_BOLD; - infoPtr->hBoldFont = CreateFontIndirectA (&logFont); - - infoPtr->items = NULL; - infoPtr->selectedItem=0; - infoPtr->clrText=-1; /* use system color */ - infoPtr->dropItem=0; - infoPtr->insertMarkItem=0; - infoPtr->insertBeforeorAfter=0; - infoPtr->pCallBackSort=NULL; - infoPtr->uScrollTime = 300; /* milliseconds */ - infoPtr->wpEditOrig = NULL; /* we haven't subclassed anything yet */ - - infoPtr->hwndToolTip=0; - if (!(dwStyle & TVS_NOTOOLTIPS)) { /* Create tooltip control */ - TTTOOLINFOA ti; - - infoPtr->hwndToolTip = - CreateWindowExA (0, TOOLTIPS_CLASSA, NULL, 0, - CW_USEDEFAULT, CW_USEDEFAULT, - CW_USEDEFAULT, CW_USEDEFAULT, - hwnd, 0, 0, 0); - - /* Send NM_TOOLTIPSCREATED notification */ - if (infoPtr->hwndToolTip) { - NMTOOLTIPSCREATED nmttc; - - nmttc.hdr.hwndFrom = hwnd; - nmttc.hdr.idFrom = GetWindowLongA( hwnd, GWL_ID); - nmttc.hdr.code = NM_TOOLTIPSCREATED; - nmttc.hwndToolTips = infoPtr->hwndToolTip; - - SendMessageA (GetParent (hwnd), WM_NOTIFY, - (WPARAM) GetWindowLongA( hwnd, GWL_ID), (LPARAM)&nmttc); - } - - ZeroMemory (&ti, sizeof(TTTOOLINFOA)); - ti.cbSize = sizeof(TTTOOLINFOA); - ti.uFlags = TTF_IDISHWND | TTF_TRACK | TTF_TRANSPARENT ; - ti.hwnd = hwnd; - ti.uId = 0; - ti.lpszText = "Test"; /* LPSTR_TEXTCALLBACK; */ - SetRectEmpty (&ti.rect); - - SendMessageA (infoPtr->hwndToolTip, TTM_ADDTOOLA, 0, (LPARAM)&ti); - } - - infoPtr->hwndEdit = CreateWindowExA ( - WS_EX_LEFT, - "EDIT", - 0, - WS_CHILD | WS_BORDER | ES_AUTOHSCROLL | - ES_WANTRETURN | ES_LEFT, - 0, 0, 0, 0, - hwnd, - 0,0,0); /* FIXME: (HMENU)IDTVEDIT,pcs->hInstance,0);*/ - - SendMessageA ( infoPtr->hwndEdit, WM_SETFONT, infoPtr->hFont, FALSE); - infoPtr->wpEditOrig = (WNDPROC)SetWindowLongA ( - infoPtr->hwndEdit, - GWL_WNDPROC, - (LONG) TREEVIEW_Edit_SubclassProc); - - if (dwStyle & TVS_CHECKBOXES) { - HBITMAP hbmLoad; - int nIndex; - - infoPtr->himlState = - ImageList_Create (16, 16,ILC_COLOR|ILC_MASK, 15, 1); - - hbmLoad = LoadBitmapA (COMCTL32_hModule, MAKEINTRESOURCEA(IDT_CHECK)); - TRACE ("%x\n",hbmLoad); - nIndex = ImageList_AddMasked (infoPtr->himlState, hbmLoad, CLR_DEFAULT); - TRACE ("%d\n",nIndex); - DeleteObject (hbmLoad); - } - ReleaseDC (hwnd, hdc); - return 0; -} - - - -static LRESULT -TREEVIEW_Destroy (HWND hwnd) -{ - TREEVIEW_INFO *infoPtr = TREEVIEW_GetInfoPtr(hwnd); - - TRACE("\n"); - TREEVIEW_RemoveTree (hwnd); - SetWindowLongA (hwnd, 0, (DWORD)NULL); - - if (infoPtr->Timer & TV_REFRESH_TIMER_SET) - KillTimer (hwnd, TV_REFRESH_TIMER); - if (infoPtr->hwndToolTip) - DestroyWindow (infoPtr->hwndToolTip); - - COMCTL32_Free (infoPtr); - return 0; -} - - -static LRESULT -TREEVIEW_Paint (HWND hwnd, WPARAM wParam, LPARAM lParam) -{ - HDC hdc; - PAINTSTRUCT ps; - - TRACE("\n"); - hdc = wParam==0 ? BeginPaint (hwnd, &ps) : (HDC)wParam; - TREEVIEW_Refresh (hwnd, hdc); - if(!wParam) EndPaint (hwnd, &ps); - TRACE("done\n"); - - return 0; -} - -static LRESULT -TREEVIEW_SetFocus (HWND hwnd, WPARAM wParam, LPARAM lParam) -{ - TREEVIEW_SendSimpleNotify (hwnd, NM_SETFOCUS); - InvalidateRect(hwnd, NULL, FALSE); - return 0; -} - -static LRESULT -TREEVIEW_KillFocus (HWND hwnd, WPARAM wParam, LPARAM lParam) -{ - TREEVIEW_SendSimpleNotify (hwnd, NM_KILLFOCUS); - InvalidateRect(hwnd, NULL, FALSE); - return 0; -} - -static LRESULT -TREEVIEW_EraseBackground (HWND hwnd, WPARAM wParam, LPARAM lParam) -{ - TREEVIEW_INFO *infoPtr = TREEVIEW_GetInfoPtr(hwnd); - HBRUSH hBrush = CreateSolidBrush (infoPtr->clrBk); - RECT rect; - - TRACE("\n"); - GetClientRect (hwnd, &rect); - FillRect ((HDC)wParam, &rect, hBrush); - DeleteObject (hBrush); return TRUE; } - - - - - -/* Notifications */ - - - - - static BOOL -TREEVIEW_SendSimpleNotify (HWND hwnd, UINT code) +TREEVIEW_Expand(TREEVIEW_INFO *infoPtr, TREEVIEW_ITEM *wineItem, + BOOL bExpandPartial, BOOL bUser) { - NMHDR nmhdr; + TRACE("\n"); - TRACE("%x\n",code); - nmhdr.hwndFrom = hwnd; - nmhdr.idFrom = GetWindowLongA( hwnd, GWL_ID); - nmhdr.code = code; + if (!TREEVIEW_HasChildren(infoPtr, wineItem) + || wineItem->state & TVIS_EXPANDED) + return FALSE; - return (BOOL) SendMessageA (GetParent (hwnd), WM_NOTIFY, - (WPARAM)nmhdr.idFrom, (LPARAM)&nmhdr); -} + TRACE("TVE_EXPAND %p %s\n", wineItem, TREEVIEW_ItemName(wineItem)); - - -static BOOL -TREEVIEW_SendTreeviewNotify (HWND hwnd, UINT code, UINT action, - HTREEITEM oldItem, HTREEITEM newItem) - -{ - TREEVIEW_INFO *infoPtr = TREEVIEW_GetInfoPtr(hwnd); - NMTREEVIEWA nmhdr; - TREEVIEW_ITEM *wineItem; - - TRACE("code:%x action:%x olditem:%x newitem:%x\n", - code,action,(INT)oldItem,(INT)newItem); - nmhdr.hdr.hwndFrom = hwnd; - nmhdr.hdr.idFrom = GetWindowLongA( hwnd, GWL_ID); - nmhdr.hdr.code = code; - nmhdr.action = action; - if (oldItem) { - wineItem=& infoPtr->items[(INT)oldItem]; - nmhdr.itemOld.mask = wineItem->mask; - nmhdr.itemOld.hItem = wineItem->hItem; - nmhdr.itemOld.state = wineItem->state; - nmhdr.itemOld.stateMask = wineItem->stateMask; - nmhdr.itemOld.iImage = wineItem->iImage; - nmhdr.itemOld.pszText = wineItem->pszText; - nmhdr.itemOld.cchTextMax= wineItem->cchTextMax; - nmhdr.itemOld.iImage = wineItem->iImage; - nmhdr.itemOld.iSelectedImage = wineItem->iSelectedImage; - nmhdr.itemOld.cChildren = wineItem->cChildren; - nmhdr.itemOld.lParam = wineItem->lParam; - } - - if (newItem) { - wineItem=& infoPtr->items[(INT)newItem]; - nmhdr.itemNew.mask = wineItem->mask; - nmhdr.itemNew.hItem = wineItem->hItem; - nmhdr.itemNew.state = wineItem->state; - nmhdr.itemNew.stateMask = wineItem->stateMask; - nmhdr.itemNew.iImage = wineItem->iImage; - nmhdr.itemNew.pszText = wineItem->pszText; - nmhdr.itemNew.cchTextMax= wineItem->cchTextMax; - nmhdr.itemNew.iImage = wineItem->iImage; - nmhdr.itemNew.iSelectedImage = wineItem->iSelectedImage; - nmhdr.itemNew.cChildren = wineItem->cChildren; - nmhdr.itemNew.lParam = wineItem->lParam; - } - - nmhdr.ptDrag.x = 0; - nmhdr.ptDrag.y = 0; - - return (BOOL)SendMessageA (GetParent (hwnd), WM_NOTIFY, - (WPARAM) GetWindowLongA( hwnd, GWL_ID), (LPARAM)&nmhdr); - -} - -static BOOL -TREEVIEW_SendTreeviewDnDNotify (HWND hwnd, UINT code, HTREEITEM dragItem, - POINT pt) -{ - TREEVIEW_INFO *infoPtr = TREEVIEW_GetInfoPtr(hwnd); - NMTREEVIEWA nmhdr; - TREEVIEW_ITEM *wineItem; - - TRACE("code:%x dragitem:%x\n", code,(INT)dragItem); - - nmhdr.hdr.hwndFrom = hwnd; - nmhdr.hdr.idFrom = GetWindowLongA( hwnd, GWL_ID); - nmhdr.hdr.code = code; - nmhdr.action = 0; - wineItem=& infoPtr->items[(INT)dragItem]; - nmhdr.itemNew.mask = wineItem->mask; - nmhdr.itemNew.hItem = wineItem->hItem; - nmhdr.itemNew.state = wineItem->state; - nmhdr.itemNew.lParam = wineItem->lParam; - - nmhdr.ptDrag.x = pt.x; - nmhdr.ptDrag.y = pt.y; - - return (BOOL)SendMessageA (GetParent (hwnd), WM_NOTIFY, - (WPARAM) GetWindowLongA( hwnd, GWL_ID), (LPARAM)&nmhdr); - -} - - - -static BOOL -TREEVIEW_SendDispInfoNotify (HWND hwnd, TREEVIEW_ITEM *wineItem, - UINT code, UINT what) -{ - NMTVDISPINFOA tvdi; - BOOL retval; - char *buf; - - TRACE("item %d, action %x, state %d\n", - (INT)wineItem->hItem, - what, - (INT)wineItem->state); - - tvdi.hdr.hwndFrom = hwnd; - tvdi.hdr.idFrom = GetWindowLongA( hwnd, GWL_ID); - tvdi.hdr.code = code; - tvdi.item.mask = what; - tvdi.item.hItem = wineItem->hItem; - tvdi.item.state = wineItem->state; - tvdi.item.lParam = wineItem->lParam; - tvdi.item.pszText = COMCTL32_Alloc (128*sizeof(char)); - tvdi.item.cchTextMax = 128; - buf = tvdi.item.pszText; - - retval=(BOOL)SendMessageA ( - GetParent(hwnd), - WM_NOTIFY, - (WPARAM)tvdi.hdr.idFrom, - (LPARAM)&tvdi); - - if (what & TVIF_TEXT) { - wineItem->pszText = tvdi.item.pszText; - if (buf==tvdi.item.pszText) { - wineItem->cchTextMax = 128; - } else { - TRACE("user-supplied buffer\n"); - COMCTL32_Free (buf); - wineItem->cchTextMax = 0; - } + if (bUser || !(wineItem->state & TVIS_EXPANDEDONCE)) + { + if (!TREEVIEW_SendExpanding(infoPtr, wineItem, TVE_EXPAND)) + { + TRACE(" TVN_ITEMEXPANDING returned TRUE, exiting...\n"); + return FALSE; } - if (what & TVIF_SELECTEDIMAGE) - wineItem->iSelectedImage = tvdi.item.iSelectedImage; - if (what & TVIF_IMAGE) - wineItem->iImage = tvdi.item.iImage; - if (what & TVIF_CHILDREN) - wineItem->cChildren = tvdi.item.cChildren; - return retval; + wineItem->state |= TVIS_EXPANDED; + TREEVIEW_SendExpanded(infoPtr, wineItem, TVE_EXPAND); + wineItem->state |= TVIS_EXPANDEDONCE; + } + else + { + /* this item has already been expanded */ + wineItem->state |= TVIS_EXPANDED; + } + + if (bExpandPartial) + FIXME("TVE_EXPANDPARTIAL not implemented\n"); + + TREEVIEW_RecalculateVisibleOrder(infoPtr, wineItem); + TREEVIEW_UpdateSubTree(infoPtr, wineItem); + TREEVIEW_UpdateScrollBars(infoPtr); + + /* Scroll up so that as many children as possible are visible. + * This looses when expanding causes an HScroll bar to appear, but we + * don't know that yet, so the last item is obscured. */ + if (wineItem->firstChild != NULL) + { + int nChildren = wineItem->lastChild->visibleOrder + - wineItem->firstChild->visibleOrder + 1; + + int visible_pos = wineItem->visibleOrder + - infoPtr->firstVisible->visibleOrder; + + int rows_below = TREEVIEW_GetVisibleCount(infoPtr) - visible_pos - 1; + + if (visible_pos > 0 && nChildren > rows_below) + { + int scroll = nChildren - rows_below; + + if (scroll > visible_pos) + scroll = visible_pos; + + if (scroll > 0) + { + TREEVIEW_ITEM *newFirstVisible + = TREEVIEW_GetListItem(infoPtr, infoPtr->firstVisible, + scroll); + + + TREEVIEW_SetFirstVisible(infoPtr, newFirstVisible, TRUE); + } + } + } + + TREEVIEW_QueueRefresh(infoPtr); + + return TRUE; } - - static BOOL -TREEVIEW_SendCustomDrawNotify (HWND hwnd, DWORD dwDrawStage, HDC hdc, - RECT rc) +TREEVIEW_Toggle(TREEVIEW_INFO *infoPtr, TREEVIEW_ITEM *wineItem, BOOL bUser) { - TREEVIEW_INFO *infoPtr = TREEVIEW_GetInfoPtr(hwnd); - NMTVCUSTOMDRAW nmcdhdr; - LPNMCUSTOMDRAW nmcd; - - TRACE("drawstage:%lx hdc:%x\n", dwDrawStage, hdc); - - nmcd= & nmcdhdr.nmcd; - nmcd->hdr.hwndFrom = hwnd; - nmcd->hdr.idFrom = GetWindowLongA( hwnd, GWL_ID); - nmcd->hdr.code = NM_CUSTOMDRAW; - nmcd->dwDrawStage= dwDrawStage; - nmcd->hdc = hdc; - nmcd->rc.left = rc.left; - nmcd->rc.right = rc.right; - nmcd->rc.bottom = rc.bottom; - nmcd->rc.top = rc.top; - nmcd->dwItemSpec = 0; - nmcd->uItemState = 0; - nmcd->lItemlParam= 0; - nmcdhdr.clrText = infoPtr->clrText; - nmcdhdr.clrTextBk= infoPtr->clrBk; - nmcdhdr.iLevel = 0; - - return (BOOL)SendMessageA (GetParent (hwnd), WM_NOTIFY, - (WPARAM) GetWindowLongA( hwnd, GWL_ID), (LPARAM)&nmcdhdr); + TRACE("\n"); + if (wineItem->state & TVIS_EXPANDED) + return TREEVIEW_Collapse(infoPtr, wineItem, FALSE, bUser); + else + return TREEVIEW_Expand(infoPtr, wineItem, FALSE, bUser); } - - -/* FIXME: need to find out when the flags in uItemState need to be set */ - -static BOOL -TREEVIEW_SendCustomDrawItemNotify (HWND hwnd, HDC hdc, - TREEVIEW_ITEM *wineItem, UINT uItemDrawState) +static VOID +TREEVIEW_ExpandAll(TREEVIEW_INFO *infoPtr, TREEVIEW_ITEM *item) { - TREEVIEW_INFO *infoPtr = TREEVIEW_GetInfoPtr(hwnd); - NMTVCUSTOMDRAW nmcdhdr; - LPNMCUSTOMDRAW nmcd; - DWORD dwDrawStage,dwItemSpec; - UINT uItemState; - INT retval; - - dwDrawStage=CDDS_ITEM | uItemDrawState; - dwItemSpec=(DWORD)wineItem->hItem; - uItemState=0; - if (wineItem->hItem==infoPtr->selectedItem) uItemState|=CDIS_SELECTED; - if (wineItem->hItem==infoPtr->focusItem) uItemState|=CDIS_FOCUS; - if (wineItem->hItem==infoPtr->hotItem) uItemState|=CDIS_HOT; + TREEVIEW_Expand(infoPtr, item, FALSE, TRUE); - nmcd= & nmcdhdr.nmcd; - nmcd->hdr.hwndFrom = hwnd; - nmcd->hdr.idFrom = GetWindowLongA( hwnd, GWL_ID); - nmcd->hdr.code = NM_CUSTOMDRAW; - nmcd->dwDrawStage= dwDrawStage; - nmcd->hdc = hdc; - nmcd->rc.left = wineItem->rect.left; - nmcd->rc.right = wineItem->rect.right; - nmcd->rc.bottom = wineItem->rect.bottom; - nmcd->rc.top = wineItem->rect.top; - nmcd->dwItemSpec = dwItemSpec; - nmcd->uItemState = uItemState; - nmcd->lItemlParam= wineItem->lParam; - nmcdhdr.clrText = infoPtr->clrText; - nmcdhdr.clrTextBk= infoPtr->clrBk; - nmcdhdr.iLevel = wineItem->iLevel; - - TRACE("drawstage:%lx hdc:%x item:%lx, itemstate:%x, lItemlParam:%lx\n", - nmcd->dwDrawStage, nmcd->hdc, nmcd->dwItemSpec, - nmcd->uItemState, nmcd->lItemlParam); - - retval=SendMessageA (GetParent (hwnd), WM_NOTIFY, - (WPARAM) GetWindowLongA( hwnd, GWL_ID), (LPARAM)&nmcdhdr); - - infoPtr->clrText=nmcdhdr.clrText; - infoPtr->clrBk =nmcdhdr.clrTextBk; - return (BOOL) retval; + for (item = item->firstChild; item != NULL; item = item->nextSibling) + { + if (TREEVIEW_HasChildren(infoPtr, item)) + TREEVIEW_ExpandAll(infoPtr, item); + } } - - /* Note:If the specified item is the child of a collapsed parent item, the parent's list of child items is (recursively) expanded to reveal the specified item. This is mentioned for TREEVIEW_SelectItem; don't @@ -2924,716 +3036,832 @@ TREEVIEW_SendCustomDrawItemNotify (HWND hwnd, HDC hdc, */ static LRESULT -TREEVIEW_Expand (HWND hwnd, WPARAM wParam, LPARAM lParam) +TREEVIEW_ExpandMsg(TREEVIEW_INFO *infoPtr, UINT flag, HTREEITEM wineItem) { - TREEVIEW_INFO *infoPtr = TREEVIEW_GetInfoPtr(hwnd); - TREEVIEW_ITEM *wineItem; - UINT flag; - INT expand; - - flag = (UINT) wParam; - expand = (INT) lParam; + if (!TREEVIEW_ValidItem(infoPtr, wineItem)) + return 0; - wineItem = TREEVIEW_ValidItem (infoPtr, (HTREEITEM)expand); + TRACE("For (%s) item:%d, flags %x, state:%d\n", + TREEVIEW_ItemName(wineItem), flag, + TREEVIEW_GetItemIndex(infoPtr, wineItem), wineItem->state); - if (!wineItem) - return 0; - if (!wineItem->cChildren) - return 0; + switch (flag & TVE_TOGGLE) + { + case TVE_COLLAPSE: + return TREEVIEW_Collapse(infoPtr, wineItem, flag & TVE_COLLAPSERESET, + FALSE); - if (wineItem->pszText==LPSTR_TEXTCALLBACKA) - TRACE ("For item %d, flags %d, state %d\n", - expand, flag, wineItem->state); - else - TRACE("For (%s) item:%d, flags %x, state:%d\n", - wineItem->pszText, flag, expand, wineItem->state); + case TVE_EXPAND: + return TREEVIEW_Expand(infoPtr, wineItem, flag & TVE_EXPANDPARTIAL, + FALSE); - if (wineItem->cChildren==I_CHILDRENCALLBACK) { - FIXME("we don't handle I_CHILDRENCALLBACK yet\n"); - return 0; - } + case TVE_TOGGLE: + return TREEVIEW_Toggle(infoPtr, wineItem, TRUE); - if (flag == TVE_TOGGLE) { /* FIXME: check exact behaviour here */ - flag &= ~TVE_TOGGLE; /* ie: bitwise ops or 'case' ops */ - if (wineItem->state & TVIS_EXPANDED) - flag |= TVE_COLLAPSE; - else - flag |= TVE_EXPAND; - } + default: + return 0; + } - switch (flag) - { - case TVE_COLLAPSERESET: - TRACE(" case TVE_COLLAPSERESET\n"); - if (!wineItem->state & TVIS_EXPANDED) - return 0; - - wineItem->state &= ~(TVIS_EXPANDEDONCE | TVIS_EXPANDED); - TREEVIEW_RemoveAllChildren (hwnd, wineItem); - break; - - case TVE_COLLAPSE: - TRACE(" case TVE_COLLAPSE\n"); - if (!wineItem->state & TVIS_EXPANDED) - return 0; - - wineItem->state &= ~TVIS_EXPANDED; - break; - - case TVE_EXPAND: - TRACE(" case TVE_EXPAND\n"); - if (wineItem->state & TVIS_EXPANDED) - return 0; - - TRACE(" is not expanded...\n"); - - if (!(wineItem->state & TVIS_EXPANDEDONCE)) - { - TRACE(" and has never been expanded...\n"); - wineItem->state |= TVIS_EXPANDED; - - /* this item has never been expanded */ - if (TREEVIEW_SendTreeviewNotify ( - hwnd, - TVN_ITEMEXPANDINGA, - TVE_EXPAND, - 0, - (HTREEITEM)expand)) - { - TRACE(" TVN_ITEMEXPANDINGA returned TRUE, exiting...\n"); - return FALSE; - } - - /* FIXME - * Since the TVN_ITEMEXPANDINGA message may has caused the parent to - * insert new items which in turn may have cause items placeholder - * reallocation, I reassign the current item pointer so we have - * something valid to work with... - * However, this should not be necessary, - * investigation required in TREEVIEW_InsertItemA - */ - wineItem = TREEVIEW_ValidItem (infoPtr, (HTREEITEM)expand); - if (! wineItem) - { - ERR( - "Catastrophic situation, cannot retrieve item #%d\n", - expand); - return FALSE; - } - - wineItem->state |= TVIS_EXPANDEDONCE; - TRACE(" TVN_ITEMEXPANDINGA sent...\n"); - - TREEVIEW_SendTreeviewNotify ( - hwnd, - TVN_ITEMEXPANDEDA, - TVE_EXPAND, - 0, - (HTREEITEM)expand); - - TRACE(" TVN_ITEMEXPANDEDA sent...\n"); - - } - else - { - /* this item has already been expanded */ - wineItem->state |= TVIS_EXPANDED; - } - break; - - case TVE_EXPANDPARTIAL: - TRACE(" case TVE_EXPANDPARTIAL\n"); - FIXME("TVE_EXPANDPARTIAL not implemented\n"); - wineItem->state ^=TVIS_EXPANDED; - wineItem->state |=TVIS_EXPANDEDONCE; - break; - } - - TRACE("Exiting, Item %d state is now %d...\n", - expand, - wineItem->state); - - TREEVIEW_QueueRefresh (hwnd); - return TRUE; +#if 0 + TRACE("Exiting, Item %p state is now %d...\n", wineItem, wineItem->state); +#endif } - +/* Hit-Testing **********************************************************/ static TREEVIEW_ITEM * -TREEVIEW_HitTestPoint (HWND hwnd, POINT pt) +TREEVIEW_HitTestPoint(TREEVIEW_INFO *infoPtr, POINT pt) { - TREEVIEW_INFO *infoPtr = TREEVIEW_GetInfoPtr(hwnd); - TREEVIEW_ITEM *wineItem; - RECT rect; + TREEVIEW_ITEM *wineItem; + LONG row; - GetClientRect (hwnd, &rect); - - if (!infoPtr->firstVisible) return NULL; - - wineItem=&infoPtr->items [(INT)infoPtr->firstVisible]; - - while ((wineItem!=NULL) && (pt.y > wineItem->rect.bottom)) - wineItem=TREEVIEW_GetNextListItem (infoPtr,wineItem); - - if (!wineItem) + if (!infoPtr->firstVisible) return NULL; - return wineItem; -} + row = pt.y / infoPtr->uItemHeight + infoPtr->firstVisible->visibleOrder; - - - -static LRESULT -TREEVIEW_HitTest (HWND hwnd, LPARAM lParam) -{ - LPTVHITTESTINFO lpht=(LPTVHITTESTINFO) lParam; - TREEVIEW_ITEM *wineItem; - RECT rect; - UINT status,x,y; - - GetClientRect (hwnd, &rect); - status=0; - x=lpht->pt.x; - y=lpht->pt.y; - if (x < rect.left) status|=TVHT_TOLEFT; - if (x > rect.right) status|=TVHT_TORIGHT; - if (y < rect.top ) status|=TVHT_ABOVE; - if (y > rect.bottom) status|=TVHT_BELOW; - - if (status) { - lpht->flags=status; - return 0; - } - - wineItem=TREEVIEW_HitTestPoint (hwnd, lpht->pt); - if (!wineItem) { - lpht->flags=TVHT_NOWHERE; - return 0; - } - - lpht->flags=0; - - if (x < wineItem->expandBox.left) { - lpht->flags |= TVHT_ONITEMINDENT; - goto done; - } - if ( PtInRect ( &wineItem->expandBox, lpht->pt)) { - lpht->flags |= TVHT_ONITEMBUTTON; - goto done; - } - if ( PtInRect ( &wineItem->bitmap, lpht->pt)) { - lpht->flags |= TVHT_ONITEMICON; - goto done; - } - if ( PtInRect ( &wineItem->statebitmap, lpht->pt)) { - lpht->flags |= TVHT_ONITEMSTATEICON; - goto done; - } - if ( PtInRect ( &wineItem->text, lpht->pt)) { - lpht->flags |= TVHT_ONITEMLABEL; - goto done; - } - - lpht->flags|=TVHT_ONITEMRIGHT; - - -done: - lpht->hItem=wineItem->hItem; - TRACE ("(%ld,%ld):result %x\n",lpht->pt.x,lpht->pt.y,lpht->flags); - - return (LRESULT) wineItem->hItem; -} - -static LRESULT -TREEVIEW_EditLabelA (HWND hwnd, WPARAM wParam, LPARAM lParam) -{ - TREEVIEW_INFO *infoPtr = TREEVIEW_GetInfoPtr(hwnd); - TREEVIEW_ITEM *wineItem; - - /* - * If the style allow editing... - */ - if ( GetWindowLongA( hwnd, GWL_STYLE) & TVS_EDITLABELS ) - { - - if ( infoPtr->editItem == 0 ) /* If we are not curently editing */ + for (wineItem = infoPtr->firstVisible; wineItem != NULL; + wineItem = TREEVIEW_GetNextListItem(infoPtr, wineItem)) { - wineItem = TREEVIEW_ValidItem(infoPtr,(HTREEITEM) lParam); - if ( wineItem == NULL ) - { - ERR("Cannot get valid TREEVIEW_ITEM for lParam\n"); - return 0; - } - - TRACE("Edit started for %s.\n", wineItem->pszText); - infoPtr->editItem = wineItem->hItem; - - - /* - * It is common practice for a windows program to get this - * edit control and then subclass it. It is assumed that a - * new edit control is given every time. - * - * As a result some programs really mess up the edit control - * so we need to destory our old edit control and create a new - * one. Recycling would be nice but we would need to reset - * everything. So recreating may just be easyier - * - */ - DestroyWindow(infoPtr->hwndEdit); - infoPtr->hwndEdit = CreateWindowExA ( - WS_EX_LEFT, - "EDIT", - 0, - WS_CHILD | WS_BORDER | ES_AUTOHSCROLL | - ES_WANTRETURN | ES_LEFT, - 0, 0, 0, 0, - hwnd, - 0,0,0); /* FIXME: (HMENU)IDTVEDIT,pcs->hInstance,0);*/ - - SetWindowLongA ( - infoPtr->hwndEdit, - GWL_WNDPROC, - (LONG) TREEVIEW_Edit_SubclassProc); - - - SendMessageA ( infoPtr->hwndEdit, WM_SETFONT, infoPtr->hFont, FALSE); - - SetWindowTextA( infoPtr->hwndEdit, wineItem->pszText ); - SendMessageA ( infoPtr->hwndEdit, EM_SETSEL, 0, -1 ); - - /* - ** NOTE: this must be after the edit control is created - ** (according to TVN_BEGINLABELEDITA docs), before position is set. - */ - if ( TREEVIEW_SendDispInfoNotify( /* Return true to cancel edition */ - hwnd, - wineItem, - TVN_BEGINLABELEDITA, - 0)) - { - /* - ** FIXME: Is this right, should we return a handle even though edit was cancelled? - */ - TRACE("Edit cancelled by TVN_BEGINLABELEDITA for %s.\n", wineItem->pszText); - - TREEVIEW_EndEditLabelNow(hwnd, (WPARAM)TRUE, 0); - - return 0; - } - - SetWindowPos ( - infoPtr->hwndEdit, - HWND_TOP, - wineItem->text.left - 2, - wineItem->text.top - 1, - wineItem->text.right - wineItem->text.left + 20 , - wineItem->text.bottom - wineItem->text.top + 3, - SWP_DRAWFRAME ); - - SetFocus ( infoPtr->hwndEdit); - ShowWindow ( infoPtr->hwndEdit, SW_SHOW); - } - } - else - { - /* - ** return NULL since we cannot edit this. - */ - - return 0; - } - - return infoPtr->hwndEdit; -} - -static LRESULT -TREEVIEW_EndEditLabelNow (HWND hwnd, WPARAM wParam, LPARAM lParam) -{ - TREEVIEW_INFO *infoPtr = TREEVIEW_GetInfoPtr(hwnd); - TREEVIEW_ITEM *editedItem = TREEVIEW_ValidItem (infoPtr, infoPtr->editItem); - BOOL bRevert = (BOOL)wParam; - NMTVDISPINFOA tvdi; - RECT itemRect; - LPSTR oldText; - LPSTR newText; - int iLength; - - /* - ** NOTE: we have to get the new text before calling TVN_ENDLABELEDITA - ** since some apps(eg regedit, win98) validate the text in TVN_ENDLABELEDITA. - */ - - oldText = editedItem->pszText; - - if ( !bRevert ) /* wParam is set to true to cancel the edition */ - { - - iLength = GetWindowTextLengthA( infoPtr->hwndEdit ); - if (iLength == 0) - { - WARN("Zero length string for new label(not changing)."); - bRevert=TRUE; + if (row >= wineItem->visibleOrder + && row < wineItem->visibleOrder + wineItem->iIntegral) + break; } - newText = COMCTL32_Alloc( iLength+1 ); - if( newText == NULL ) - { - ERR("OutOfMemory, cannot allocate space for label"); - return FALSE; - } - GetWindowTextA( infoPtr->hwndEdit, newText, iLength+1); - } - else - { - newText=NULL; - } + return wineItem; +} +static LRESULT +TREEVIEW_HitTest(TREEVIEW_INFO *infoPtr, LPTVHITTESTINFO lpht) +{ + TREEVIEW_ITEM *wineItem; + RECT rect; + UINT status; + LONG x, y; - /* - * notify our parent with the new string(or NULL if wParam==TRUE) - */ - tvdi.hdr.hwndFrom = hwnd; - tvdi.hdr.idFrom = GetWindowLongA( hwnd, GWL_ID); - tvdi.hdr.code = TVN_ENDLABELEDITA; - tvdi.item.hItem = editedItem->hItem; - tvdi.item.lParam = editedItem->lParam; - tvdi.item.mask = TVIF_TEXT|TVIF_HANDLE|TVIF_PARAM; - tvdi.item.pszText = newText; + lpht->hItem = 0; + GetClientRect(infoPtr->hwnd, &rect); + status = 0; + x = lpht->pt.x; + y = lpht->pt.y; - if(!SendMessageA ( /* return false to cancel edition */ - GetParent(hwnd), - WM_NOTIFY, - (WPARAM)tvdi.hdr.idFrom, - (LPARAM)&tvdi)) - { - if( newText == NULL ) /*we are supposed to ignore the return if (and pszText==NULL), MSDOCs */ - bRevert=TRUE; - } - - if (oldText != LPSTR_TEXTCALLBACKA) - { - - if( bRevert ) + if (x < rect.left) { - if( newText != NULL ) - COMCTL32_Free(newText); + status |= TVHT_TOLEFT; + } + else if (x > rect.right) + { + status |= TVHT_TORIGHT; + } - editedItem->pszText=oldText; /* revert back to the old label */ + if (y < rect.top) + { + status |= TVHT_ABOVE; + } + else if (y > rect.bottom) + { + status |= TVHT_BELOW; + } + + if (status) + { + lpht->flags = status; + return (LRESULT)(HTREEITEM)NULL; + } + + wineItem = TREEVIEW_HitTestPoint(infoPtr, lpht->pt); + if (!wineItem) + { + lpht->flags = TVHT_NOWHERE; + return (LRESULT)(HTREEITEM)NULL; + } + + if (x >= wineItem->textOffset + wineItem->textWidth) + { + lpht->flags = TVHT_ONITEMRIGHT; + } + else if (x >= wineItem->textOffset) + { + lpht->flags = TVHT_ONITEMLABEL; + } + else if (x >= wineItem->imageOffset) + { + lpht->flags = TVHT_ONITEMICON; + } + else if (x >= wineItem->stateOffset) + { + lpht->flags = TVHT_ONITEMSTATEICON; + } + else if (x >= wineItem->linesOffset && infoPtr->dwStyle & TVS_HASBUTTONS) + { + lpht->flags = TVHT_ONITEMBUTTON; } else { - COMCTL32_Free(oldText); - - editedItem->pszText=newText; /* use the new label */ + lpht->flags = TVHT_ONITEMINDENT; } - } - else if( newText!=NULL ) - { - /* - ** Is really this necessary? shouldnt an app update its internal data in TVN_ENDLABELEDITA? - */ - if( !bRevert ) + + lpht->hItem = wineItem; + TRACE("(%ld,%ld):result %x\n", lpht->pt.x, lpht->pt.y, lpht->flags); + + return (LRESULT)wineItem; +} + +/* Item Label Editing ***************************************************/ + +static LRESULT +TREEVIEW_GetEditControl(TREEVIEW_INFO *infoPtr) +{ + return infoPtr->hwndEdit; +} + +static LRESULT CALLBACK +TREEVIEW_Edit_SubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + TREEVIEW_INFO *infoPtr; + BOOL bCancel = FALSE; + + switch (uMsg) { - /* - * This is a callback string so we need - * to inform the parent that the string - * has changed - * - */ - tvdi.hdr.hwndFrom = hwnd; - tvdi.hdr.idFrom = GetWindowLongA( hwnd, GWL_ID); - tvdi.hdr.code = TVN_SETDISPINFOA; - tvdi.item.mask = TVIF_TEXT; - tvdi.item.pszText = newText; + case WM_PAINT: + { + LRESULT rc; + TREEVIEW_INFO *infoPtr = TREEVIEW_GetInfoPtr(GetParent(hwnd)); - SendMessageA ( - GetParent(hwnd), - WM_NOTIFY, - (WPARAM)tvdi.hdr.idFrom, - (LPARAM)&tvdi); + TRACE("WM_PAINT start\n"); + rc = CallWindowProcA(infoPtr->wpEditOrig, hwnd, uMsg, wParam, + lParam); + TRACE("WM_PAINT done\n"); + return rc; + } + case WM_KILLFOCUS: + { + TREEVIEW_INFO *infoPtr = TREEVIEW_GetInfoPtr(GetParent(hwnd)); + if (infoPtr->bIgnoreEditKillFocus) + return TRUE; + + break; } - COMCTL32_Free(newText); - } - + case WM_GETDLGCODE: + return DLGC_WANTARROWS | DLGC_WANTALLKEYS; - ShowWindow(infoPtr->hwndEdit, SW_HIDE); - EnableWindow(infoPtr->hwndEdit, FALSE); + case WM_KEYDOWN: + if (wParam == (WPARAM)VK_ESCAPE) + { + bCancel = TRUE; + break; + } + else if (wParam == (WPARAM)VK_RETURN) + { + break; + } - /* update the window to eliminate fragments and the like */ - TreeView_GetItemRect(hwnd,infoPtr->editItem,&itemRect,FALSE); - RedrawWindow(hwnd,&itemRect,0,RDW_ERASE|RDW_INVALIDATE|RDW_UPDATENOW); + /* fall through */ + default: + { + TREEVIEW_INFO *infoPtr = TREEVIEW_GetInfoPtr(GetParent(hwnd)); - infoPtr->editItem = 0; + return CallWindowProcA(infoPtr->wpEditOrig, hwnd, uMsg, wParam, + lParam); + } + } - return !bRevert; /* return true if label edit succesful, otherwise false */ -} + /* Processing TVN_ENDLABELEDIT message could kill the focus */ + /* eg. Using a messagebox */ + infoPtr = TREEVIEW_GetInfoPtr(GetParent(hwnd)); + infoPtr->bIgnoreEditKillFocus = TRUE; + TREEVIEW_EndEditLabelNow(infoPtr, bCancel || !infoPtr->bLabelChanged); + infoPtr->bIgnoreEditKillFocus = FALSE; - -static LRESULT -TREEVIEW_LButtonDoubleClick (HWND hwnd, WPARAM wParam, LPARAM lParam) -{ - TREEVIEW_ITEM *wineItem; - POINT pt; - - TRACE("\n"); - pt.x = (INT)LOWORD(lParam); - pt.y = (INT)HIWORD(lParam); - SetFocus (hwnd); - - wineItem=TREEVIEW_HitTestPoint (hwnd, pt); - if (!wineItem) return 0; - TRACE("item %d \n",(INT)wineItem->hItem); - - if (TREEVIEW_SendSimpleNotify (hwnd, NM_DBLCLK)!=TRUE) { /* FIXME!*/ - TREEVIEW_Expand (hwnd, (WPARAM) TVE_TOGGLE, (LPARAM) wineItem->hItem); - } - return TRUE; -} - - -static LRESULT -TREEVIEW_LButtonDown (HWND hwnd, WPARAM wParam, LPARAM lParam) -{ - TREEVIEW_INFO *infoPtr = TREEVIEW_GetInfoPtr(hwnd); - INT iItem; - TVHITTESTINFO ht; - - ht.pt.x = (INT)LOWORD(lParam); - ht.pt.y = (INT)HIWORD(lParam); - - SetFocus (hwnd); - iItem=TREEVIEW_HitTest (hwnd, (LPARAM) &ht); - TRACE("item %d \n",iItem); - - if (ht.flags & TVHT_ONITEMBUTTON) { - TREEVIEW_Expand (hwnd, (WPARAM) TVE_TOGGLE, (LPARAM) iItem); - } - else - { - infoPtr->uInternalStatus|=TV_LDRAG; - } - - return 0; -} - -static LRESULT -TREEVIEW_LButtonUp (HWND hwnd, WPARAM wParam, LPARAM lParam) -{ - TREEVIEW_INFO *infoPtr = TREEVIEW_GetInfoPtr(hwnd); - INT iItem; - TREEVIEW_ITEM *wineItem; - TVHITTESTINFO ht; - DWORD dwStyle = GetWindowLongA (hwnd, GWL_STYLE); - - ht.pt.x = (INT)LOWORD(lParam); - ht.pt.y = (INT)HIWORD(lParam); - - TRACE("\n"); - - /* Return true to cancel default behaviour */ - if ( TREEVIEW_SendSimpleNotify (hwnd, NM_CLICK) ) return 0; +} + + +/* should handle edit control messages here */ + +static LRESULT +TREEVIEW_Command(TREEVIEW_INFO *infoPtr, WPARAM wParam, LPARAM lParam) +{ + TRACE("%x %ld\n", wParam, lParam); + + switch (HIWORD(wParam)) + { + case EN_UPDATE: + { + /* + * Adjust the edit window size + */ + char buffer[1024]; + TREEVIEW_ITEM *editItem = infoPtr->selectedItem; + HDC hdc = GetDC(infoPtr->hwndEdit); + SIZE sz; + int len; + HFONT hFont, hOldFont = 0; + + infoPtr->bLabelChanged = TRUE; + + len = GetWindowTextA(infoPtr->hwndEdit, buffer, sizeof(buffer)); + + /* Select font to get the right dimension of the string */ + hFont = SendMessageA(infoPtr->hwndEdit, WM_GETFONT, 0, 0); + if (hFont != 0) + { + hOldFont = SelectObject(hdc, hFont); + } + + if (GetTextExtentPoint32A(hdc, buffer, strlen(buffer), &sz)) + { + TEXTMETRICA textMetric; + + /* Add Extra spacing for the next character */ + GetTextMetricsA(hdc, &textMetric); + sz.cx += (textMetric.tmMaxCharWidth * 2); + + sz.cx = max(sz.cx, textMetric.tmMaxCharWidth * 3); + sz.cx = min(sz.cx, + infoPtr->clientWidth - editItem->textOffset + 2); + + SetWindowPos(infoPtr->hwndEdit, + HWND_TOP, + 0, + 0, + sz.cx, + editItem->rect.bottom - editItem->rect.top + 3, + SWP_NOMOVE | SWP_DRAWFRAME); + } + + if (hFont != 0) + { + SelectObject(hdc, hOldFont); + } + + ReleaseDC(infoPtr->hwnd, hdc); + break; + } + + default: + return SendMessageA(GetParent(infoPtr->hwnd), WM_COMMAND, wParam, lParam); + } - /* Get the item */ - iItem = TREEVIEW_HitTest (hwnd, (LPARAM) &ht); - TRACE ("%d\n",iItem); - if (!iItem) return 0; +} - wineItem = TREEVIEW_ValidItem(infoPtr, (HTREEITEM)iItem); - - /* - * if we are TVS_SINGLEEXPAND then we want this single click to - * do a bunch of things. - */ - if ((dwStyle & TVS_SINGLEEXPAND)&& - ( ht.flags & (TVHT_ONITEMLABEL | TVHT_ONITEMICON))&& - ( infoPtr->editItem == 0 )) - { - TREEVIEW_ITEM *SelItem; - /* - * Send the notification - */ - TREEVIEW_SendTreeviewNotify (hwnd,TVN_SINGLEEXPAND,0, - (HTREEITEM)iItem,0); - /* - * Close the previous selection all the way to the root - * as long as the new selection is not a child - */ - - if ((infoPtr->selectedItem)&&(infoPtr->selectedItem != (HTREEITEM)iItem)) +static HWND +TREEVIEW_EditLabelA(TREEVIEW_INFO *infoPtr, HTREEITEM hItem) +{ + HWND hwnd = infoPtr->hwnd; + HWND hwndEdit; + SIZE sz; + TREEVIEW_ITEM *editItem = hItem; + HINSTANCE hinst = GetWindowLongA(hwnd, GWL_HINSTANCE); + HDC hdc; + HFONT hOldFont=0; + TEXTMETRICA textMetric; + + TRACE("%x %p\n", (unsigned)hwnd, hItem); + if (!TREEVIEW_ValidItem(infoPtr, editItem)) + return (HWND)NULL; + + if (infoPtr->hwndEdit) + return infoPtr->hwndEdit; + + infoPtr->bLabelChanged = FALSE; + + /* Make sure that edit item is selected */ + TREEVIEW_DoSelectItem(infoPtr, TVGN_CARET, hItem, TVC_UNKNOWN); + TREEVIEW_EnsureVisible(infoPtr, hItem, TRUE); + + TREEVIEW_UpdateDispInfo(infoPtr, editItem, TVIF_TEXT); + + hdc = GetDC(hwnd); + /* Select the font to get appropriate metric dimensions */ + if (infoPtr->hFont != 0) { - BOOL closeit = TRUE; - SelItem = wineItem; - - while (closeit && SelItem) - { - closeit = (SelItem->hItem != infoPtr->selectedItem); - SelItem = TREEVIEW_ValidItem(infoPtr,SelItem->parent); - } - - if (closeit) - { - SelItem = TREEVIEW_ValidItem(infoPtr,infoPtr->selectedItem); - while ((SelItem)&&(SelItem->hItem != wineItem->hItem)) - { - TREEVIEW_Expand (hwnd,(WPARAM)TVE_COLLAPSE,(LPARAM)SelItem->hItem); - SelItem = TREEVIEW_ValidItem(infoPtr,SelItem->parent); - } - } + hOldFont = SelectObject(hdc, infoPtr->hFont); } - - /* - * Expand the current item - */ - TREEVIEW_Expand (hwnd, (WPARAM) TVE_TOGGLE, (LPARAM) wineItem->hItem); - } - infoPtr->uInternalStatus &= ~(TV_LDRAG | TV_LDRAGGING); + /*Get String Lenght in pixels */ + GetTextExtentPoint32A(hdc, editItem->pszText, strlen(editItem->pszText), + &sz); - /* - * If the style allow editing and the node is already selected - * and the click occured on the item label... - */ - if ( ( GetWindowLongA( hwnd, GWL_STYLE) & TVS_EDITLABELS ) && - ( wineItem->state & TVIS_SELECTED ) && - ( ht.flags & TVHT_ONITEMLABEL )) - { - if ( infoPtr->editItem == 0 ) /* If we are not curently editing */ + /*Add Extra spacing for the next character */ + GetTextMetricsA(hdc, &textMetric); + sz.cx += (textMetric.tmMaxCharWidth * 2); + + sz.cx = max(sz.cx, textMetric.tmMaxCharWidth * 3); + sz.cx = min(sz.cx, infoPtr->clientWidth - editItem->textOffset + 2); + + if (infoPtr->hFont != 0) { - if( SendMessageA(hwnd, TVM_EDITLABELA, 0, (LPARAM)iItem) == 0) - return 0; + SelectObject(hdc, hOldFont); } - } - else if ( infoPtr->editItem != 0 ) /* If we are curently editing */ - { - TREEVIEW_EndEditLabelNow(hwnd, (WPARAM)FALSE, 0); - } - else if ( ht.flags & (TVHT_ONITEMLABEL | TVHT_ONITEMICON)) - { - TREEVIEW_DoSelectItem ( hwnd, TVGN_CARET, (HTREEITEM)iItem, TVC_BYMOUSE); - } - if (ht.flags & TVHT_ONITEMSTATEICON) { + ReleaseDC(hwnd, hdc); + hwndEdit = CreateWindowExA(WS_EX_LEFT, + "EDIT", + 0, + WS_CHILD | WS_BORDER | ES_AUTOHSCROLL | + WS_CLIPSIBLINGS | ES_WANTRETURN | + ES_LEFT, editItem->textOffset - 2, + editItem->rect.top - 1, sz.cx + 3, + editItem->rect.bottom - + editItem->rect.top + 3, hwnd, 0, hinst, 0); +/* FIXME: (HMENU)IDTVEDIT,pcs->hInstance,0); */ - - if (dwStyle & TVS_CHECKBOXES) { /* TVS_CHECKBOXES requires _us_ */ - int state; /* to toggle the current state */ - state=1-(wineItem->state>>12); - TRACE ("state:%x\n", state); - wineItem->state&= ~TVIS_STATEIMAGEMASK; - wineItem->state|=state<<12; - TRACE ("state:%x\n", wineItem->state); - TREEVIEW_QueueRefresh (hwnd); + infoPtr->hwndEdit = hwndEdit; + + /* Get a 2D border. */ + SetWindowLongA(hwndEdit, GWL_EXSTYLE, + GetWindowLongA(hwndEdit, GWL_EXSTYLE) & ~WS_EX_CLIENTEDGE); + SetWindowLongA(hwndEdit, GWL_STYLE, + GetWindowLongA(hwndEdit, GWL_STYLE) | WS_BORDER); + + SendMessageA(hwndEdit, WM_SETFONT, TREEVIEW_FontForItem(infoPtr, editItem), + FALSE); + + infoPtr->wpEditOrig = (WNDPROC)SetWindowLongA(hwndEdit, GWL_WNDPROC, + (DWORD) + TREEVIEW_Edit_SubclassProc); + + if (TREEVIEW_BeginLabelEditNotify(infoPtr, editItem)) + { + DestroyWindow(hwndEdit); + infoPtr->hwndEdit = 0; + return (HWND)NULL; + } + + infoPtr->selectedItem = hItem; + SetWindowTextA(hwndEdit, editItem->pszText); + SetFocus(hwndEdit); + SendMessageA(hwndEdit, EM_SETSEL, 0, -1); + ShowWindow(hwndEdit, SW_SHOW); + + return hwndEdit; +} + + +static LRESULT +TREEVIEW_EndEditLabelNow(TREEVIEW_INFO *infoPtr, BOOL bCancel) +{ + HWND hwnd = infoPtr->hwnd; + TREEVIEW_ITEM *editedItem = infoPtr->selectedItem; + NMTVDISPINFOA tvdi; + BOOL bCommit; + char tmpText[1024] = { '\0' }; + int iLength = 0; + + if (!infoPtr->hwndEdit) + return FALSE; + + tvdi.hdr.hwndFrom = hwnd; + tvdi.hdr.idFrom = GetWindowLongA(hwnd, GWL_ID); + tvdi.hdr.code = TVN_ENDLABELEDITA; + tvdi.item.mask = 0; + tvdi.item.hItem = editedItem; + tvdi.item.state = editedItem->state; + tvdi.item.lParam = editedItem->lParam; + + if (!bCancel) + { + iLength = GetWindowTextA(infoPtr->hwndEdit, tmpText, 1023); + + if (iLength >= 1023) + { + ERR("Insuficient space to retrieve new item label."); + } + + tvdi.item.pszText = tmpText; + tvdi.item.cchTextMax = iLength + 1; + } + else + { + tvdi.item.pszText = NULL; + tvdi.item.cchTextMax = 0; + } + + bCommit = (BOOL)SendMessageA(GetParent(hwnd), + WM_NOTIFY, + (WPARAM)tvdi.hdr.idFrom, (LPARAM)&tvdi); + + if (!bCancel && bCommit) /* Apply the changes */ + { + if (strcmp(tmpText, editedItem->pszText) != 0) + { + if (NULL == COMCTL32_ReAlloc(editedItem->pszText, iLength + 1)) + { + ERR("OutOfMemory, cannot allocate space for label"); + DestroyWindow(infoPtr->hwndEdit); + infoPtr->hwndEdit = 0; + return FALSE; + } + else + { + editedItem->cchTextMax = iLength + 1; + lstrcpyA(editedItem->pszText, tmpText); + } + } + } + + ShowWindow(infoPtr->hwndEdit, SW_HIDE); + DestroyWindow(infoPtr->hwndEdit); + infoPtr->hwndEdit = 0; + return TRUE; +} + +static LRESULT +TREEVIEW_HandleTimer(TREEVIEW_INFO *infoPtr, WPARAM wParam) +{ + if (wParam != TV_EDIT_TIMER) + { + ERR("got unknown timer\n"); + return 1; + } + + KillTimer(infoPtr->hwnd, TV_EDIT_TIMER); + infoPtr->Timer &= ~TV_EDIT_TIMER_SET; + + TREEVIEW_EditLabelA(infoPtr, infoPtr->selectedItem); + + return 0; +} + + +/* Mouse Tracking/Drag **************************************************/ + +/*************************************************************************** + * This is quite unusual piece of code, but that's how it's implemented in + * Windows. + */ +static LRESULT +TREEVIEW_TrackMouse(TREEVIEW_INFO *infoPtr, POINT pt) +{ + INT cxDrag = GetSystemMetrics(SM_CXDRAG); + INT cyDrag = GetSystemMetrics(SM_CYDRAG); + RECT r; + MSG msg; + + r.top = pt.y - cyDrag; + r.left = pt.x - cxDrag; + r.bottom = pt.y + cyDrag; + r.right = pt.x + cxDrag; + + SetCapture(infoPtr->hwnd); + + while (1) + { + if (PeekMessageA(&msg, 0, 0, 0, PM_REMOVE | PM_NOYIELD)) + { + if (msg.message == WM_MOUSEMOVE) + { + pt.x = (LONG)(INT16)LOWORD(msg.lParam); + pt.y = (LONG)(INT16)HIWORD(msg.lParam); + if (PtInRect(&r, pt)) + continue; + else + { + ReleaseCapture(); + return 1; } - } - return 0; + } + else if (msg.message >= WM_LBUTTONDOWN && + msg.message <= WM_RBUTTONDBLCLK) + { + if (msg.message == WM_RBUTTONUP) + TREEVIEW_RButtonUp(infoPtr, &pt); + break; + } + + DispatchMessageA(&msg); + } + + if (GetCapture() != infoPtr->hwnd) + return 0; + } + + ReleaseCapture(); + return 0; } static LRESULT -TREEVIEW_RButtonDown (HWND hwnd, WPARAM wParam, LPARAM lParam) +TREEVIEW_LButtonDoubleClick(TREEVIEW_INFO *infoPtr, LPARAM lParam) { - TREEVIEW_INFO *infoPtr = TREEVIEW_GetInfoPtr(hwnd); + TREEVIEW_ITEM *wineItem; + TVHITTESTINFO hit; - TRACE("\n"); - infoPtr->uInternalStatus|=TV_RDRAG; - return 0; -} + TRACE("\n"); + SetFocus(infoPtr->hwnd); -static LRESULT -TREEVIEW_RButtonUp (HWND hwnd, WPARAM wParam, LPARAM lParam) -{ - TREEVIEW_INFO *infoPtr = TREEVIEW_GetInfoPtr(hwnd); + if (infoPtr->Timer & TV_EDIT_TIMER_SET) + { + /* If there is pending 'edit label' event - kill it now */ + KillTimer(infoPtr->hwnd, TV_EDIT_TIMER); + } - TRACE("\n"); - if (TREEVIEW_SendSimpleNotify (hwnd, NM_RCLICK)) return 0; - infoPtr->uInternalStatus&= ~(TV_RDRAG | TV_RDRAGGING); - return 0; -} + hit.pt.x = (LONG)(INT16)LOWORD(lParam); + hit.pt.y = (LONG)(INT16)HIWORD(lParam); - -static LRESULT -TREEVIEW_MouseMove (HWND hwnd, WPARAM wParam, LPARAM lParam) -{ - TREEVIEW_INFO *infoPtr = TREEVIEW_GetInfoPtr(hwnd); - TREEVIEW_ITEM *hotItem; - POINT pt; - - pt.x=(INT) LOWORD (lParam); - pt.y=(INT) HIWORD (lParam); - hotItem=TREEVIEW_HitTestPoint (hwnd, pt); - if (!hotItem) return 0; - infoPtr->focusItem=hotItem->hItem; - - if ( GetWindowLongA( hwnd, GWL_STYLE) & TVS_DISABLEDRAGDROP) return 0; - - if (infoPtr->uInternalStatus & TV_LDRAG) { - TREEVIEW_SendTreeviewDnDNotify (hwnd, TVN_BEGINDRAGA, hotItem->hItem, pt); - infoPtr->uInternalStatus &= ~TV_LDRAG; - infoPtr->uInternalStatus |= TV_LDRAGGING; - infoPtr->dropItem=hotItem->hItem; + wineItem = (TREEVIEW_ITEM *)TREEVIEW_HitTest(infoPtr, &hit); + if (!wineItem) return 0; - } + TRACE("item %d \n", TREEVIEW_GetItemIndex(infoPtr, wineItem)); - if (infoPtr->uInternalStatus & TV_RDRAG) { - TREEVIEW_SendTreeviewDnDNotify (hwnd, TVN_BEGINRDRAGA, hotItem->hItem, pt); - infoPtr->uInternalStatus &= ~TV_RDRAG; - infoPtr->uInternalStatus |= TV_RDRAGGING; - infoPtr->dropItem=hotItem->hItem; - return 0; - } - - return 0; + if (TREEVIEW_SendSimpleNotify(infoPtr, NM_DBLCLK) == FALSE) + { /* FIXME! */ + switch (hit.flags) + { + case TVHT_ONITEMRIGHT: + /* FIXME: we should not have send NM_DBLCLK in this case. */ + break; + + case TVHT_ONITEMINDENT: + if (!(infoPtr->dwStyle & TVS_HASLINES)) + { + break; + } + else + { + int level = hit.pt.x / infoPtr->uIndent; + if (!(infoPtr->dwStyle & TVS_LINESATROOT)) level++; + + while (wineItem->iLevel > level) + { + wineItem = wineItem->parent; + } + + /* fall through */ + } + + case TVHT_ONITEMLABEL: + case TVHT_ONITEMICON: + case TVHT_ONITEMBUTTON: + TREEVIEW_Toggle(infoPtr, wineItem, TRUE); + break; + + case TVHT_ONITEMSTATEICON: + if (infoPtr->dwStyle & TVS_CHECKBOXES) + TREEVIEW_ToggleItemState(infoPtr, wineItem); + else + TREEVIEW_Toggle(infoPtr, wineItem, TRUE); + break; + } + } + return TRUE; } static LRESULT -TREEVIEW_CreateDragImage (HWND hwnd, WPARAM wParam, LPARAM lParam) +TREEVIEW_LButtonDown(TREEVIEW_INFO *infoPtr, LPARAM lParam) { - TREEVIEW_INFO *infoPtr = TREEVIEW_GetInfoPtr(hwnd); - TREEVIEW_ITEM *dragItem; - INT cx,cy; - HDC hdc,htopdc; - HWND hwtop; - HBITMAP hbmp,hOldbmp; - SIZE size; - RECT rc; - HFONT hOldFont; - char *itemtxt; - - TRACE("\n"); - if (!(infoPtr->himlNormal)) return 0; - dragItem=TREEVIEW_ValidItem (infoPtr, (HTREEITEM) lParam); - - if (!dragItem) return 0; + HWND hwnd = infoPtr->hwnd; + TVHITTESTINFO ht; + BOOL bTrack; + HTREEITEM tempItem; - if (dragItem->pszText==LPSTR_TEXTCALLBACKA) { - TREEVIEW_SendDispInfoNotify (hwnd, dragItem, TVN_GETDISPINFOA, TVIF_TEXT); - } - itemtxt=dragItem->pszText; + /* If Edit control is active - kill it and return. + * The best way to do it is to set focus to itself. + * Edit control subclassed procedure will automatically call + * EndEditLabelNow. + */ + if (infoPtr->hwndEdit) + { + SetFocus(hwnd); + return 0; + } - hwtop=GetDesktopWindow (); - htopdc= GetDC (hwtop); - hdc=CreateCompatibleDC (htopdc); - - hOldFont=SelectObject (hdc, infoPtr->hFont); - GetTextExtentPoint32A (hdc, itemtxt, lstrlenA (itemtxt), &size); - TRACE("%d %d %s %d\n",size.cx,size.cy,itemtxt,lstrlenA(itemtxt)); - hbmp=CreateCompatibleBitmap (htopdc, size.cx, size.cy); - hOldbmp=SelectObject (hdc, hbmp); + ht.pt.x = (LONG)(INT16)LOWORD(lParam); + ht.pt.y = (LONG)(INT16)HIWORD(lParam); - ImageList_GetIconSize (infoPtr->himlNormal, &cx, &cy); - size.cx+=cx; - if (cy>size.cy) size.cy=cy; + TREEVIEW_HitTest(infoPtr, &ht); + TRACE("item %d \n", TREEVIEW_GetItemIndex(infoPtr, ht.hItem)); - infoPtr->dragList=ImageList_Create (size.cx, size.cy, ILC_COLOR, 10, 10); - ImageList_Draw (infoPtr->himlNormal, dragItem->iImage, hdc, 0, 0, ILD_NORMAL); + /* update focusedItem and redraw both items */ + if(ht.hItem && (ht.flags & TVHT_ONITEM)) + { + infoPtr->focusedItem = ht.hItem; + InvalidateRect(hwnd, &(((HTREEITEM)(ht.hItem))->rect), TRUE); + + if(infoPtr->selectedItem) + InvalidateRect(hwnd, &(infoPtr->selectedItem->rect), TRUE); + } + + bTrack = (ht.flags & TVHT_ONITEM) + && !(infoPtr->dwStyle & TVS_DISABLEDRAGDROP); + + /* Send NM_CLICK right away */ + if (!bTrack) + if (TREEVIEW_SendSimpleNotify(infoPtr, NM_CLICK)) + goto setfocus; + + if (ht.flags & TVHT_ONITEMBUTTON) + { + TREEVIEW_Toggle(infoPtr, ht.hItem, TRUE); + goto setfocus; + } + else if (bTrack) + { /* if TREEVIEW_TrackMouse == 1 dragging occured and the cursor left the dragged item's rectangle */ + if (TREEVIEW_TrackMouse(infoPtr, ht.pt)) + { + TREEVIEW_SendTreeviewDnDNotify(infoPtr, TVN_BEGINDRAGA, ht.hItem, + ht.pt); + infoPtr->dropItem = ht.hItem; + + /* clean up focuedItem as we dragged and won't select this item */ + if(infoPtr->focusedItem) + { + /* refresh the item that was focused */ + tempItem = infoPtr->focusedItem; + infoPtr->focusedItem = 0; + InvalidateRect(infoPtr->hwnd, &tempItem->rect, TRUE); + + /* refresh the selected item to return the filled background */ + InvalidateRect(infoPtr->hwnd, &(infoPtr->selectedItem->rect), TRUE); + } + + return 0; + } + } + + if (TREEVIEW_SendSimpleNotify(infoPtr, NM_CLICK)) + goto setfocus; + + /* + * If the style allow editing and the node is already selected + * and the click occured on the item label... + */ + if ((infoPtr->dwStyle & TVS_EDITLABELS) && + (ht.flags & TVHT_ONITEMLABEL) && (infoPtr->selectedItem == ht.hItem)) + { + if (infoPtr->Timer & TV_EDIT_TIMER_SET) + KillTimer(hwnd, TV_EDIT_TIMER); + + SetTimer(hwnd, TV_EDIT_TIMER, GetDoubleClickTime(), 0); + infoPtr->Timer |= TV_EDIT_TIMER_SET; + } + else if (ht.flags & TVHT_ONITEM) /* select the item if the hit was inside of the icon or text */ + { + /* + * if we are TVS_SINGLEEXPAND then we want this single click to + * do a bunch of things. + */ + if((infoPtr->dwStyle & TVS_SINGLEEXPAND) && + (infoPtr->hwndEdit == 0)) + { + TREEVIEW_ITEM *SelItem; + + /* + * Send the notification + */ + TREEVIEW_SendTreeviewNotify(infoPtr, TVN_SINGLEEXPAND, TVIF_HANDLE | TVIF_PARAM, + 0, ht.hItem, 0); + /* + * Close the previous selection all the way to the root + * as long as the new selection is not a child + */ + + if((infoPtr->selectedItem) + && (infoPtr->selectedItem != ht.hItem)) + { + BOOL closeit = TRUE; + SelItem = ht.hItem; + + while(closeit && SelItem) + { + closeit = (SelItem != infoPtr->selectedItem); + + if(TREEVIEW_ValidItem(infoPtr, SelItem->parent)) + SelItem = SelItem->parent; + } + + if(closeit) + { + if(TREEVIEW_ValidItem(infoPtr, infoPtr->selectedItem)) + SelItem = infoPtr->selectedItem; + + while((SelItem) && (SelItem != ht.hItem)) + { + TREEVIEW_Expand(infoPtr, SelItem, (WPARAM)TVE_COLLAPSE, + FALSE); + if(TREEVIEW_ValidItem(infoPtr, SelItem->parent)) + SelItem = SelItem->parent; + } + } + } + + /* + * Expand the current item + */ + TREEVIEW_Expand(infoPtr, ht.hItem, TVE_TOGGLE, FALSE); + } + else + TREEVIEW_DoSelectItem(infoPtr, TVGN_CARET, ht.hItem, TVC_BYMOUSE); + } + else if (ht.flags & TVHT_ONITEMSTATEICON) + { + /* TVS_CHECKBOXES requires us to toggle the current state */ + if (infoPtr->dwStyle & TVS_CHECKBOXES) + TREEVIEW_ToggleItemState(infoPtr, ht.hItem); + } + + setfocus: + SetFocus(hwnd); + return 0; +} + + +static LRESULT +TREEVIEW_RButtonDown(TREEVIEW_INFO *infoPtr, LPARAM lParam) +{ + TVHITTESTINFO ht; + + if (infoPtr->hwndEdit) + { + SetFocus(infoPtr->hwnd); + return 0; + } + + ht.pt.x = (LONG)(INT16)LOWORD(lParam); + ht.pt.y = (LONG)(INT16)HIWORD(lParam); + + TREEVIEW_HitTest(infoPtr, &ht); + + if (TREEVIEW_TrackMouse(infoPtr, ht.pt)) + { + if (ht.hItem) + { + TREEVIEW_SendTreeviewDnDNotify(infoPtr, TVN_BEGINRDRAGA, ht.hItem, + ht.pt); + infoPtr->dropItem = ht.hItem; + } + } + else + { + SetFocus(infoPtr->hwnd); + TREEVIEW_SendSimpleNotify(infoPtr, NM_RCLICK); + } + + return 0; +} + +static LRESULT +TREEVIEW_RButtonUp(TREEVIEW_INFO *infoPtr, LPPOINT pPt) +{ + HWND hwnd = infoPtr->hwnd; + POINT pt = *pPt; + + /* Change to screen coordinate for WM_CONTEXTMENU */ + ClientToScreen(hwnd, &pt); + + SendMessageA(hwnd, WM_CONTEXTMENU, (WPARAM)hwnd, MAKELPARAM(pt.x, pt.y)); + return 0; +} + + +static LRESULT +TREEVIEW_CreateDragImage(TREEVIEW_INFO *infoPtr, WPARAM wParam, LPARAM lParam) +{ + TREEVIEW_ITEM *dragItem = (HTREEITEM)lParam; + INT cx, cy; + HDC hdc, htopdc; + HWND hwtop; + HBITMAP hbmp, hOldbmp; + SIZE size; + RECT rc; + HFONT hOldFont; + + TRACE("\n"); + + if (!(infoPtr->himlNormal)) + return 0; + + if (!dragItem || !TREEVIEW_ValidItem(infoPtr, dragItem)) + return 0; + + TREEVIEW_UpdateDispInfo(infoPtr, dragItem, TVIF_TEXT); + + hwtop = GetDesktopWindow(); + htopdc = GetDC(hwtop); + hdc = CreateCompatibleDC(htopdc); + + hOldFont = SelectObject(hdc, infoPtr->hFont); + GetTextExtentPoint32A(hdc, dragItem->pszText, lstrlenA(dragItem->pszText), + &size); + TRACE("%d %d %s %d\n", size.cx, size.cy, dragItem->pszText, + lstrlenA(dragItem->pszText)); + hbmp = CreateCompatibleBitmap(htopdc, size.cx, size.cy); + hOldbmp = SelectObject(hdc, hbmp); + + ImageList_GetIconSize(infoPtr->himlNormal, &cx, &cy); + size.cx += cx; + if (cy > size.cy) + size.cy = cy; + + infoPtr->dragList = ImageList_Create(size.cx, size.cy, ILC_COLOR, 10, 10); + ImageList_Draw(infoPtr->himlNormal, dragItem->iImage, hdc, 0, 0, + ILD_NORMAL); /* ImageList_GetImageInfo (infoPtr->himlNormal, dragItem->hItem, &iminfo); @@ -3642,759 +3870,1231 @@ TREEVIEW_CreateDragImage (HWND hwnd, WPARAM wParam, LPARAM lParam) /* draw item text */ - SetRect (&rc, cx, 0, size.cx,size.cy); - DrawTextA (hdc, itemtxt, lstrlenA (itemtxt), &rc, DT_LEFT); - SelectObject (hdc, hOldFont); - SelectObject (hdc, hOldbmp); + SetRect(&rc, cx, 0, size.cx, size.cy); + DrawTextA(hdc, dragItem->pszText, lstrlenA(dragItem->pszText), &rc, + DT_LEFT); + SelectObject(hdc, hOldFont); + SelectObject(hdc, hOldbmp); - ImageList_Add (infoPtr->dragList, hbmp, 0); + ImageList_Add(infoPtr->dragList, hbmp, 0); - DeleteDC (hdc); - DeleteObject (hbmp); - ReleaseDC (hwtop, htopdc); + DeleteDC(hdc); + DeleteObject(hbmp); + ReleaseDC(hwtop, htopdc); - return (LRESULT)infoPtr->dragList; + return (LRESULT)infoPtr->dragList; } +/* Selection ************************************************************/ static LRESULT -TREEVIEW_DoSelectItem (HWND hwnd, INT action, HTREEITEM newSelect, INT cause) - +TREEVIEW_DoSelectItem(TREEVIEW_INFO *infoPtr, INT action, HTREEITEM newSelect, + INT cause) { - TREEVIEW_INFO *infoPtr = TREEVIEW_GetInfoPtr(hwnd); - TREEVIEW_ITEM *prevItem,*wineItem; - INT prevSelect; + TREEVIEW_ITEM *prevSelect; + RECT rcFocused; - wineItem = TREEVIEW_ValidItem (infoPtr, (HTREEITEM)newSelect); + assert(newSelect == NULL || TREEVIEW_ValidItem(infoPtr, newSelect)); - TRACE("Entering item %d, flag %x, cause %x, state %d\n", - (INT)newSelect, - action, - cause, - wineItem->state); + TRACE("Entering item %p (%s), flag %x, cause %x, state %d\n", + newSelect, TREEVIEW_ItemName(newSelect), action, cause, + newSelect ? newSelect->state : 0); - if ( (wineItem) && (wineItem->parent)) - { - /* - * If the item has a collapse parent expand the parent so he - * can expose the item - */ - TREEVIEW_ITEM *parentItem = TREEVIEW_ValidItem (infoPtr, wineItem->parent); - if ( !(parentItem->state & TVIS_EXPANDED)) - TREEVIEW_Expand (hwnd, TVE_EXPAND, (LPARAM) wineItem->parent); - } + /* reset and redraw focusedItem if focusedItem was set so we don't */ + /* have to worry about the previously focused item when we set a new one */ + if(infoPtr->focusedItem) + { + rcFocused = (infoPtr->focusedItem)->rect; + infoPtr->focusedItem = 0; + InvalidateRect(infoPtr->hwnd, &rcFocused, TRUE); + } - switch (action) - { - case TVGN_CARET: - prevSelect=(INT)infoPtr->selectedItem; + switch (action) + { + case TVGN_CARET: + prevSelect = infoPtr->selectedItem; - if ((HTREEITEM)prevSelect==newSelect) - return FALSE; + if (prevSelect == newSelect) + return FALSE; - prevItem= TREEVIEW_ValidItem (infoPtr, (HTREEITEM)prevSelect); + if (TREEVIEW_SendTreeviewNotify(infoPtr, + TVN_SELCHANGINGA, + cause, + TVIF_HANDLE | TVIF_STATE | TVIF_PARAM, + prevSelect, + newSelect)) + return FALSE; - if (newSelect) - if (TREEVIEW_SendTreeviewNotify( - hwnd, - TVN_SELCHANGINGA, - cause, - (HTREEITEM)prevSelect, - (HTREEITEM)newSelect)) - return FALSE; /* FIXME: OK? */ - - if (prevItem) - prevItem->state &= ~TVIS_SELECTED; - if (wineItem) - wineItem->state |= TVIS_SELECTED; + if (prevSelect) + prevSelect->state &= ~TVIS_SELECTED; + if (newSelect) + newSelect->state |= TVIS_SELECTED; - infoPtr->selectedItem=(HTREEITEM)newSelect; + infoPtr->selectedItem = newSelect; - TREEVIEW_SendTreeviewNotify( - hwnd, - TVN_SELCHANGEDA, - cause, - (HTREEITEM)prevSelect, - (HTREEITEM)newSelect); + TREEVIEW_EnsureVisible(infoPtr, infoPtr->selectedItem, FALSE); - break; + TREEVIEW_SendTreeviewNotify(infoPtr, + TVN_SELCHANGEDA, + cause, + TVIF_HANDLE | TVIF_STATE | TVIF_PARAM, + prevSelect, + newSelect); + TREEVIEW_QueueItemRefresh(infoPtr, prevSelect); + TREEVIEW_QueueItemRefresh(infoPtr, newSelect); + break; - case TVGN_DROPHILITE: - prevItem= TREEVIEW_ValidItem (infoPtr, infoPtr->dropItem); + case TVGN_DROPHILITE: + prevSelect = infoPtr->dropItem; - if (prevItem) - prevItem->state &= ~TVIS_DROPHILITED; + if (prevSelect) + prevSelect->state &= ~TVIS_DROPHILITED; - infoPtr->dropItem=(HTREEITEM)newSelect; + infoPtr->dropItem = newSelect; - if (wineItem) - wineItem->state |=TVIS_DROPHILITED; + if (newSelect) + newSelect->state |= TVIS_DROPHILITED; - break; + TREEVIEW_QueueItemRefresh(infoPtr, prevSelect); + TREEVIEW_QueueItemRefresh(infoPtr, newSelect); + break; - case TVGN_FIRSTVISIBLE: - FIXME("FIRSTVISIBLE not implemented\n"); - break; - } - - TREEVIEW_QueueRefresh (hwnd); + case TVGN_FIRSTVISIBLE: + TREEVIEW_EnsureVisible(infoPtr, newSelect, FALSE); + TREEVIEW_SetFirstVisible(infoPtr, newSelect, TRUE); + TREEVIEW_QueueRefresh(infoPtr); + break; + } - TRACE("Leaving state %d\n", wineItem->state); - return TRUE; + TRACE("Leaving state %d\n", newSelect ? newSelect->state : 0); + return TRUE; } -/* FIXME: handle NM_KILLFocus etc */ +/* FIXME: handle NM_KILLFOCUS etc */ static LRESULT -TREEVIEW_SelectItem (HWND hwnd, WPARAM wParam, LPARAM lParam) - +TREEVIEW_SelectItem(TREEVIEW_INFO *infoPtr, INT wParam, HTREEITEM item) { - return TREEVIEW_DoSelectItem (hwnd, wParam, (HTREEITEM) lParam, TVC_UNKNOWN); + if (item != NULL && !TREEVIEW_ValidItem(infoPtr, item)) + return FALSE; + + TRACE("%p (%s) %d\n", item, TREEVIEW_ItemName(item), wParam); + + if (!TREEVIEW_DoSelectItem(infoPtr, wParam, item, TVC_UNKNOWN)) + return FALSE; + + return TRUE; } +/* Scrolling ************************************************************/ - - static LRESULT -TREEVIEW_GetFont (HWND hwnd, WPARAM wParam, LPARAM lParam) - +TREEVIEW_EnsureVisible(TREEVIEW_INFO *infoPtr, HTREEITEM item, BOOL bHScroll) { - TREEVIEW_INFO *infoPtr = TREEVIEW_GetInfoPtr(hwnd); + HTREEITEM newFirstVisible = NULL; + int visible_pos; - TRACE("%x\n",infoPtr->hFont); - return infoPtr->hFont; + if (!TREEVIEW_ValidItem(infoPtr, item)) + return FALSE; + + if (!ISVISIBLE(item)) + { + /* Expand parents as necessary. */ + HTREEITEM parent = item->parent; + + while (parent != infoPtr->root) + { + if (!(parent->state & TVIS_EXPANDED)) + TREEVIEW_Expand(infoPtr, parent, FALSE, FALSE); + + parent = parent->parent; + } + } + + TRACE("%p (%s) %ld - %ld\n", item, TREEVIEW_ItemName(item), item->visibleOrder, + infoPtr->firstVisible->visibleOrder); + + visible_pos = item->visibleOrder - infoPtr->firstVisible->visibleOrder; + + if (visible_pos < 0) + { + /* item is before the start of the list: put it at the top. */ + newFirstVisible = item; + } + else if (visible_pos >= TREEVIEW_GetVisibleCount(infoPtr) + /* Sometimes, before we are displayed, GVC is 0, causing us to + * spuriously scroll up. */ + && visible_pos > 0) + { + /* item is past the end of the list. */ + int scroll = visible_pos - TREEVIEW_GetVisibleCount(infoPtr); + + newFirstVisible = TREEVIEW_GetListItem(infoPtr, infoPtr->firstVisible, + scroll + 1); + } + + if (bHScroll) + { + /* Scroll window so item's text is visible as much as possible */ + /* Calculation of amount of extra space is taken from EditLabel code */ + INT pos, x; + TEXTMETRICA textMetric; + HDC hdc = GetWindowDC(infoPtr->hwnd); + + x = item->textWidth; + + GetTextMetricsA(hdc, &textMetric); + ReleaseDC(infoPtr->hwnd, hdc); + + x += (textMetric.tmMaxCharWidth * 2); + x = max(x, textMetric.tmMaxCharWidth * 3); + + if (item->textOffset < 0) + pos = item->textOffset; + else if (item->textOffset + x > infoPtr->clientWidth) + { + if (x > infoPtr->clientWidth) + pos = item->textOffset; + else + pos = item->textOffset + x - infoPtr->clientWidth; + } + else + pos = 0; + + TREEVIEW_HScroll(infoPtr, MAKEWPARAM(SB_THUMBPOSITION, infoPtr->scrollX + pos)); + } + + if (newFirstVisible != NULL && newFirstVisible != infoPtr->firstVisible) + { + TREEVIEW_SetFirstVisible(infoPtr, newFirstVisible, TRUE); + + return TRUE; + } + + return FALSE; +} + +static VOID +TREEVIEW_SetFirstVisible(TREEVIEW_INFO *infoPtr, + TREEVIEW_ITEM *newFirstVisible, + BOOL bUpdateScrollPos) +{ + int gap_size; + + TRACE("%p: %s\n", newFirstVisible, TREEVIEW_ItemName(newFirstVisible)); + + if (newFirstVisible != NULL) + { + /* Prevent an empty gap from appearing at the bottom... */ + gap_size = TREEVIEW_GetVisibleCount(infoPtr) + - infoPtr->maxVisibleOrder + newFirstVisible->visibleOrder; + + if (gap_size > 0) + { + newFirstVisible = TREEVIEW_GetListItem(infoPtr, newFirstVisible, + -gap_size); + + /* ... unless we just don't have enough items. */ + if (newFirstVisible == NULL) + newFirstVisible = infoPtr->root->firstChild; + } + } + + if (infoPtr->firstVisible != newFirstVisible) + { + if (infoPtr->firstVisible == NULL || newFirstVisible == NULL) + { + infoPtr->firstVisible = newFirstVisible; + TREEVIEW_QueueRefresh(infoPtr); + } + else + { + TREEVIEW_ITEM *item; + int scroll = infoPtr->uItemHeight * + (infoPtr->firstVisible->visibleOrder + - newFirstVisible->visibleOrder); + + infoPtr->firstVisible = newFirstVisible; + + for (item = infoPtr->root->firstChild; item != NULL; + item = TREEVIEW_GetNextListItem(infoPtr, item)) + { + item->rect.top += scroll; + item->rect.bottom += scroll; + } + + if (bUpdateScrollPos) + SetScrollPos(infoPtr->hwnd, SB_VERT, + newFirstVisible->visibleOrder, TRUE); + + ScrollWindow(infoPtr->hwnd, 0, scroll, NULL, NULL); + UpdateWindow(infoPtr->hwnd); + } + } +} + +/************************************************************************ + * VScroll is always in units of visible items. i.e. we always have a + * visible item aligned to the top of the control. (Unless we have no + * items at all.) + */ +static LRESULT +TREEVIEW_VScroll(TREEVIEW_INFO *infoPtr, WPARAM wParam) +{ + TREEVIEW_ITEM *oldFirstVisible = infoPtr->firstVisible; + TREEVIEW_ITEM *newFirstVisible = NULL; + + int nScrollCode = LOWORD(wParam); + + TRACE("wp %x\n", wParam); + + if (!(infoPtr->uInternalStatus & TV_VSCROLL)) + return 0; + + if (infoPtr->hwndEdit) + SetFocus(infoPtr->hwnd); + + if (!oldFirstVisible) + { + assert(infoPtr->root->firstChild == NULL); + return 0; + } + + switch (nScrollCode) + { + case SB_TOP: + newFirstVisible = infoPtr->root->firstChild; + break; + + case SB_BOTTOM: + newFirstVisible = TREEVIEW_GetLastListItem(infoPtr, infoPtr->root); + break; + + case SB_LINEUP: + newFirstVisible = TREEVIEW_GetPrevListItem(infoPtr, oldFirstVisible); + break; + + case SB_LINEDOWN: + newFirstVisible = TREEVIEW_GetNextListItem(infoPtr, oldFirstVisible); + break; + + case SB_PAGEUP: + newFirstVisible = TREEVIEW_GetListItem(infoPtr, oldFirstVisible, + -max(1, TREEVIEW_GetVisibleCount(infoPtr))); + break; + + case SB_PAGEDOWN: + newFirstVisible = TREEVIEW_GetListItem(infoPtr, oldFirstVisible, + max(1, TREEVIEW_GetVisibleCount(infoPtr))); + break; + + case SB_THUMBTRACK: + case SB_THUMBPOSITION: + newFirstVisible = TREEVIEW_GetListItem(infoPtr, + infoPtr->root->firstChild, + (LONG)(INT16)HIWORD(wParam)); + break; + + case SB_ENDSCROLL: + return 0; + } + + if (newFirstVisible != NULL) + { + if (newFirstVisible != oldFirstVisible) + TREEVIEW_SetFirstVisible(infoPtr, newFirstVisible, + nScrollCode != SB_THUMBTRACK); + else if (nScrollCode == SB_THUMBPOSITION) + SetScrollPos(infoPtr->hwnd, SB_VERT, + newFirstVisible->visibleOrder, TRUE); + } + + return 0; } static LRESULT -TREEVIEW_SetFont (HWND hwnd, WPARAM wParam, LPARAM lParam) - +TREEVIEW_HScroll(TREEVIEW_INFO *infoPtr, WPARAM wParam) { - TREEVIEW_INFO *infoPtr = TREEVIEW_GetInfoPtr(hwnd); - TEXTMETRICA tm; - LOGFONTA logFont; - HFONT hFont, hOldFont; - INT height; - HDC hdc; + int maxWidth; + int scrollX = infoPtr->scrollX; + int nScrollCode = LOWORD(wParam); - TRACE("%x %lx\n",wParam, lParam); - - infoPtr->hFont = (HFONT)wParam; + TRACE("wp %x\n", wParam); - hFont = infoPtr->hFont ? infoPtr->hFont : GetStockObject (SYSTEM_FONT); + if (!(infoPtr->uInternalStatus & TV_HSCROLL)) + return FALSE; - GetObjectA (infoPtr->hFont, sizeof (LOGFONTA), &logFont); - logFont.lfWeight=FW_BOLD; - infoPtr->hBoldFont = CreateFontIndirectA (&logFont); + if (infoPtr->hwndEdit) + SetFocus(infoPtr->hwnd); - hdc = GetDC (0); - hOldFont = SelectObject (hdc, hFont); - GetTextMetricsA (hdc, &tm); - height= tm.tmHeight + tm.tmExternalLeading + FOCUS_BORDER; - if (height>infoPtr->uRealItemHeight) - infoPtr->uRealItemHeight=height; - SelectObject (hdc, hOldFont); - ReleaseDC (0, hdc); + maxWidth = infoPtr->treeWidth - infoPtr->clientWidth; + /* shall never occure */ + if (maxWidth <= 0) + { + scrollX = 0; + goto scroll; + } - if (lParam) - TREEVIEW_QueueRefresh (hwnd); - - return 0; -} + switch (nScrollCode) + { + case SB_LINELEFT: + scrollX -= infoPtr->uItemHeight; + break; + case SB_LINERIGHT: + scrollX += infoPtr->uItemHeight; + break; + case SB_PAGELEFT: + scrollX -= infoPtr->clientWidth; + break; + case SB_PAGERIGHT: + scrollX += infoPtr->clientWidth; + break; + case SB_THUMBTRACK: + case SB_THUMBPOSITION: + scrollX = (int)(INT16)HIWORD(wParam); + break; + case SB_ENDSCROLL: + return 0; + } -static LRESULT -TREEVIEW_VScroll (HWND hwnd, WPARAM wParam, LPARAM lParam) + if (scrollX > maxWidth) + scrollX = maxWidth; + else if (scrollX < 0) + scrollX = 0; -{ - TREEVIEW_INFO *infoPtr = TREEVIEW_GetInfoPtr(hwnd); - WORD nVisibleItems; - int maxHeight; +scroll: + if (scrollX != infoPtr->scrollX) + { + TREEVIEW_ITEM *item; + LONG scroll_pixels = infoPtr->scrollX - scrollX; + + for (item = infoPtr->root->firstChild; item != NULL; + item = TREEVIEW_GetNextListItem(infoPtr, item)) + { + item->linesOffset += scroll_pixels; + item->stateOffset += scroll_pixels; + item->imageOffset += scroll_pixels; + item->textOffset += scroll_pixels; + } - TRACE("wp %x, lp %lx\n", wParam, lParam); - if (!infoPtr->uInternalStatus & TV_VSCROLL) return FALSE; + ScrollWindow(infoPtr->hwnd, scroll_pixels, 0, NULL, NULL); + infoPtr->scrollX = scrollX; + UpdateWindow(infoPtr->hwnd); + } - switch (LOWORD (wParam)) { - case SB_LINEUP: - if (!infoPtr->cy) return FALSE; - infoPtr->cy -= infoPtr->uRealItemHeight; - if (infoPtr->cy < 0) infoPtr->cy=0; - break; - case SB_LINEDOWN: - nVisibleItems = infoPtr->uVisibleHeight / infoPtr->uRealItemHeight; - maxHeight=infoPtr->uTotalHeight - nVisibleItems * infoPtr->uRealItemHeight; - if (infoPtr->cy >= maxHeight) return FALSE; - infoPtr->cy += infoPtr->uRealItemHeight; - if (infoPtr->cy >= maxHeight) - infoPtr->cy = maxHeight; - break; - case SB_PAGEUP: - if (!infoPtr->cy) return FALSE; - infoPtr->cy -= infoPtr->uVisibleHeight; - if (infoPtr->cy < 0) infoPtr->cy=0; - break; - case SB_PAGEDOWN: - nVisibleItems = infoPtr->uVisibleHeight / infoPtr->uRealItemHeight; - maxHeight=infoPtr->uTotalHeight - nVisibleItems * infoPtr->uRealItemHeight; - if (infoPtr->cy == maxHeight) return FALSE; - infoPtr->cy += infoPtr->uVisibleHeight; - if (infoPtr->cy >= maxHeight) - infoPtr->cy = maxHeight; - break; - case SB_THUMBTRACK: - infoPtr->cy = HIWORD (wParam); - break; - - } - - TREEVIEW_QueueRefresh (hwnd); - return TRUE; + if (nScrollCode != SB_THUMBTRACK) + SetScrollPos(infoPtr->hwnd, SB_HORZ, scrollX, TRUE); + + return 0; } static LRESULT -TREEVIEW_HScroll (HWND hwnd, WPARAM wParam, LPARAM lParam) +TREEVIEW_MouseWheel(TREEVIEW_INFO *infoPtr, WPARAM wParam) { - TREEVIEW_INFO *infoPtr = TREEVIEW_GetInfoPtr(hwnd); - int maxWidth; - - TRACE("wp %lx, lp %x\n", lParam, wParam); - - if (!infoPtr->uInternalStatus & TV_HSCROLL) return FALSE; - - switch (LOWORD (wParam)) { - case SB_LINEUP: - if (!infoPtr->cx) return FALSE; - infoPtr->cx -= infoPtr->uRealItemHeight; - if (infoPtr->cx < 0) infoPtr->cx=0; - break; - case SB_LINEDOWN: - maxWidth=infoPtr->uTotalWidth-infoPtr->uVisibleWidth; - if (infoPtr->cx == maxWidth) return FALSE; - infoPtr->cx += infoPtr->uRealItemHeight; /*FIXME */ - if (infoPtr->cx > maxWidth) - infoPtr->cx = maxWidth; - break; - case SB_PAGEUP: - if (!infoPtr->cx) return FALSE; - infoPtr->cx -= infoPtr->uVisibleWidth; - if (infoPtr->cx < 0) infoPtr->cx=0; - break; - case SB_PAGEDOWN: - maxWidth=infoPtr->uTotalWidth-infoPtr->uVisibleWidth; - if (infoPtr->cx == maxWidth) return FALSE; - infoPtr->cx += infoPtr->uVisibleWidth; - if (infoPtr->cx > maxWidth) - infoPtr->cx = maxWidth; - break; - case SB_THUMBTRACK: - infoPtr->cx = HIWORD (wParam); - break; - - } - - TREEVIEW_QueueRefresh (hwnd); - return TRUE; -} - -static LRESULT TREEVIEW_MouseWheel (HWND hwnd, WPARAM wParam, LPARAM lParam) -{ - - TREEVIEW_INFO *infoPtr = TREEVIEW_GetInfoPtr(hwnd); - short gcWheelDelta = 0; + short gcWheelDelta; UINT pulScrollLines = 3; - SystemParametersInfoW(SPI_GETWHEELSCROLLLINES,0, &pulScrollLines, 0); + if (infoPtr->firstVisible == NULL) + return TRUE; - gcWheelDelta -= (short) HIWORD(wParam); + SystemParametersInfoW(SPI_GETWHEELSCROLLLINES, 0, &pulScrollLines, 0); + + gcWheelDelta = -(short)HIWORD(wParam); pulScrollLines *= (gcWheelDelta / WHEEL_DELTA); if (abs(gcWheelDelta) >= WHEEL_DELTA && pulScrollLines) { - int wheelDy = pulScrollLines * infoPtr->uRealItemHeight; - int newDy = infoPtr->cy + wheelDy; - int maxDy = infoPtr->uTotalHeight - infoPtr->uVisibleHeight; + int newDy = infoPtr->firstVisible->visibleOrder + pulScrollLines; + int maxDy = infoPtr->maxVisibleOrder; - if (newDy > maxDy) newDy = maxDy; - if (newDy < 0) newDy = 0; + if (newDy > maxDy) + newDy = maxDy; - if (newDy == infoPtr->cy) return TRUE; + if (newDy < 0) + newDy = 0; - TREEVIEW_VScroll(hwnd, MAKEWPARAM(SB_THUMBTRACK,newDy),0); + TREEVIEW_VScroll(infoPtr, MAKEWPARAM(SB_THUMBPOSITION, newDy)); } - return TRUE; + return TRUE; } +/* Create/Destroy *******************************************************/ + static LRESULT -TREEVIEW_KeyDown (HWND hwnd, WPARAM wParam, LPARAM lParam) -{ - TREEVIEW_INFO *infoPtr = TREEVIEW_GetInfoPtr(hwnd); - HTREEITEM hNewSelection = 0; - INT scrollNeeds = -1; - INT cyChangeNeeds = -1; - INT prevSelect = (INT)infoPtr->selectedItem; +TREEVIEW_Create(HWND hwnd) +{ + RECT rcClient; + TREEVIEW_INFO *infoPtr; - TREEVIEW_ITEM *prevItem = - (prevSelect != 0 ) ? - TREEVIEW_ValidItem (infoPtr, (HTREEITEM)prevSelect) : - NULL; + TRACE("wnd %x, style %lx\n", hwnd, GetWindowLongA(hwnd, GWL_STYLE)); - TREEVIEW_ITEM *newItem = NULL; - - TRACE("%x %lx\n",wParam, lParam); - - if (prevSelect == 0) - return FALSE; - - switch (wParam) { - case VK_UP: - newItem=TREEVIEW_GetPrevListItem (infoPtr, prevItem); - - if (!newItem) - newItem=& infoPtr->items[(INT)infoPtr->TopRootItem]; - - hNewSelection = newItem->hItem; - - if (! newItem->visible) - scrollNeeds = SB_LINEUP; - break; - - case VK_DOWN: - newItem=TREEVIEW_GetNextListItem (infoPtr, prevItem); - - if (!newItem) - newItem=prevItem; - - hNewSelection = newItem->hItem; - - if (! newItem->visible) - scrollNeeds = SB_LINEDOWN; - - break; - - case VK_HOME: - newItem = &infoPtr->items[(INT)infoPtr->TopRootItem]; - hNewSelection = newItem->hItem; - cyChangeNeeds = 0; - break; - - case VK_END: - newItem = &infoPtr->items[(INT)infoPtr->TopRootItem]; - newItem = TREEVIEW_GetLastListItem (infoPtr, newItem); - hNewSelection = newItem->hItem; - - if (! newItem->visible) - cyChangeNeeds = infoPtr->uTotalHeight-infoPtr->uVisibleHeight; - - break; - - case VK_LEFT: - if ( (prevItem->cChildren > 0) && (prevItem->state & TVIS_EXPANDED) ) - { - TREEVIEW_Expand(hwnd, TVE_COLLAPSE, prevSelect ); - } - else if ((INT)prevItem->parent) - { - newItem = (& infoPtr->items[(INT)prevItem->parent]); - if (! newItem->visible) - /* FIXME find a way to make this item the first visible... */ - newItem = NULL; - - hNewSelection = newItem->hItem; - } - - break; - - case VK_RIGHT: - if ( ( prevItem->cChildren > 0) || - ( prevItem->cChildren == I_CHILDRENCALLBACK)) - { - if (! (prevItem->state & TVIS_EXPANDED)) - TREEVIEW_Expand(hwnd, TVE_EXPAND, prevSelect ); - else - { - newItem = (& infoPtr->items[(INT)prevItem->firstChild]); - hNewSelection = newItem->hItem; - } - } - - break; - - case VK_ADD: - if (! (prevItem->state & TVIS_EXPANDED)) - TREEVIEW_Expand(hwnd, TVE_EXPAND, prevSelect ); - break; - - case VK_SUBTRACT: - if (prevItem->state & TVIS_EXPANDED) - TREEVIEW_Expand(hwnd, TVE_COLLAPSE, prevSelect ); - break; - - case VK_PRIOR: - - newItem=TREEVIEW_GetListItem( - infoPtr, - prevItem, - -1*(TREEVIEW_GetVisibleCount(hwnd,0,0)-3)); - if (!newItem) - newItem=prevItem; - - hNewSelection = newItem->hItem; - - if (! newItem->visible) - scrollNeeds = SB_PAGEUP; - - break; - - case VK_NEXT: - newItem=TREEVIEW_GetListItem( - infoPtr, - prevItem, - TREEVIEW_GetVisibleCount(hwnd,0,0)-3); - - if (!newItem) - newItem=prevItem; - - hNewSelection = newItem->hItem; - - if (! newItem->visible) - scrollNeeds = SB_PAGEDOWN; - - break; - - case VK_BACK: - - case VK_RETURN: - - default: - FIXME("%x not implemented\n", wParam); - break; - } - - if (hNewSelection) - { -/* - This works but does not send notification... - - prevItem->state &= ~TVIS_SELECTED; - newItem->state |= TVIS_SELECTED; - infoPtr->selectedItem = hNewSelection; - TREEVIEW_QueueRefresh (hwnd); -*/ - - if ( TREEVIEW_DoSelectItem( - hwnd, - TVGN_CARET, - (HTREEITEM)hNewSelection, - TVC_BYKEYBOARD)) - { - /* If selection change is allowed for the new item, perform scrolling */ - if (scrollNeeds != -1) - TREEVIEW_VScroll(hwnd, scrollNeeds, 0); - - if (cyChangeNeeds != -1) - infoPtr->cy = cyChangeNeeds; - - /* FIXME: Something happen in the load the in the two weeks before - april 1st 1999 which makes this SetFocus mandatory otherwise, the focus - is lost... However the SetFocus should not be required...*/ - - SetFocus(hwnd); - } - } - - return FALSE; -} - - -static LRESULT -TREEVIEW_GetScrollTime (HWND hwnd) -{ - TREEVIEW_INFO *infoPtr = TREEVIEW_GetInfoPtr(hwnd); - - return infoPtr->uScrollTime; -} - - -static LRESULT -TREEVIEW_SetScrollTime (HWND hwnd, UINT uScrollTime) -{ - TREEVIEW_INFO *infoPtr = TREEVIEW_GetInfoPtr(hwnd); - UINT uOldScrollTime = infoPtr->uScrollTime; - - infoPtr->uScrollTime = min (uScrollTime, 100); - - return uOldScrollTime; -} - - -static LRESULT WINAPI -TREEVIEW_WindowProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) -{ - TREEVIEW_INFO *infoPtr; - if (uMsg==WM_CREATE) - return TREEVIEW_Create (hwnd, wParam, lParam); - - if (!(infoPtr = TREEVIEW_GetInfoPtr(hwnd))) - return DefWindowProcA (hwnd, uMsg, wParam, lParam); - - switch (uMsg) { - - case TVM_INSERTITEMA: - return TREEVIEW_InsertItemA (hwnd, wParam, lParam); - - case TVM_INSERTITEMW: - return TREEVIEW_InsertItemW(hwnd,wParam,lParam);; - - case TVM_DELETEITEM: - return TREEVIEW_DeleteItem (hwnd, wParam, lParam); - - case TVM_EXPAND: - return TREEVIEW_Expand (hwnd, wParam, lParam); - - case TVM_GETITEMRECT: - return TREEVIEW_GetItemRect (hwnd, wParam, lParam); - - case TVM_GETCOUNT: - return TREEVIEW_GetCount (hwnd, wParam, lParam); - - case TVM_GETINDENT: - return TREEVIEW_GetIndent (hwnd); - - case TVM_SETINDENT: - return TREEVIEW_SetIndent (hwnd, wParam); - - case TVM_GETIMAGELIST: - return TREEVIEW_GetImageList (hwnd, wParam, lParam); - - case TVM_SETIMAGELIST: - return TREEVIEW_SetImageList (hwnd, wParam, lParam); - - case TVM_GETNEXTITEM: - return TREEVIEW_GetNextItem (hwnd, wParam, lParam); - - case TVM_SELECTITEM: - return TREEVIEW_SelectItem (hwnd, wParam, lParam); - - case TVM_GETITEMA: - return TREEVIEW_GetItemA (hwnd, wParam, lParam); - - case TVM_GETITEMW: - return TREEVIEW_GetItemW (hwnd, wParam, lParam); - - case TVM_SETITEMA: - return TREEVIEW_SetItemA (hwnd, wParam, lParam); - - case TVM_SETITEMW: - FIXME("Unimplemented msg TVM_SETITEMW\n"); - return 0; - - case TVM_EDITLABELA: - return TREEVIEW_EditLabelA(hwnd, wParam, lParam); - - case TVM_EDITLABELW: - FIXME("Unimplemented msg TVM_EDITLABELW \n"); - return 0; - - case TVM_GETEDITCONTROL: - return TREEVIEW_GetEditControl (hwnd); - - case TVM_GETVISIBLECOUNT: - return TREEVIEW_GetVisibleCount (hwnd, wParam, lParam); - - case TVM_HITTEST: - return TREEVIEW_HitTest (hwnd, lParam); - - case TVM_CREATEDRAGIMAGE: - return TREEVIEW_CreateDragImage (hwnd, wParam, lParam); - - case TVM_SORTCHILDREN: - return TREEVIEW_SortChildren (hwnd, wParam, lParam); - - case TVM_ENSUREVISIBLE: - FIXME("Unimplemented msg TVM_ENSUREVISIBLE\n"); - return 0; - - case TVM_SORTCHILDRENCB: - return TREEVIEW_SortChildrenCB(hwnd, wParam, lParam); - - case TVM_ENDEDITLABELNOW: - if (infoPtr->editItem) - return TREEVIEW_EndEditLabelNow (hwnd, wParam, lParam); - - case TVM_GETISEARCHSTRINGA: - FIXME("Unimplemented msg TVM_GETISEARCHSTRINGA\n"); - return 0; - - case TVM_GETISEARCHSTRINGW: - FIXME("Unimplemented msg TVM_GETISEARCHSTRINGW\n"); - return 0; - - case TVM_GETTOOLTIPS: - return TREEVIEW_GetToolTips (hwnd); - - case TVM_SETTOOLTIPS: - return TREEVIEW_SetToolTips (hwnd, wParam); - - case TVM_SETINSERTMARK: - return TREEVIEW_SetInsertMark (hwnd,wParam, lParam); - - case TVM_SETITEMHEIGHT: - return TREEVIEW_SetItemHeight (hwnd, wParam); - - case TVM_GETITEMHEIGHT: - return TREEVIEW_GetItemHeight (hwnd); - - case TVM_SETBKCOLOR: - return TREEVIEW_SetBkColor (hwnd, wParam, lParam); - - case TVM_SETTEXTCOLOR: - return TREEVIEW_SetTextColor (hwnd, wParam, lParam); - - case TVM_GETBKCOLOR: - return TREEVIEW_GetBkColor (hwnd); - - case TVM_GETTEXTCOLOR: - return TREEVIEW_GetTextColor (hwnd); - - case TVM_SETSCROLLTIME: - return TREEVIEW_SetScrollTime (hwnd, (UINT)wParam); - - case TVM_GETSCROLLTIME: - return TREEVIEW_GetScrollTime (hwnd); - - case TVM_GETITEMSTATE: - return TREEVIEW_GetItemState (hwnd,wParam, lParam); - - case TVM_GETLINECOLOR: - return TREEVIEW_GetLineColor (hwnd,wParam, lParam); - - case TVM_SETLINECOLOR: - return TREEVIEW_SetLineColor (hwnd,wParam, lParam); - - case TVM_SETINSERTMARKCOLOR: - return TREEVIEW_SetInsertMarkColor (hwnd,wParam, lParam); - - case TVM_GETINSERTMARKCOLOR: - return TREEVIEW_GetInsertMarkColor (hwnd,wParam, lParam); - - case TVM_SETUNICODEFORMAT: - FIXME("Unimplemented msg TVM_SETUNICODEFORMAT\n"); - return 0; - - case TVM_GETUNICODEFORMAT: - FIXME("Unimplemented msg TVM_GETUNICODEFORMAT\n"); - return 0; - - case WM_COMMAND: - return TREEVIEW_Command (hwnd, wParam, lParam); - - case WM_DESTROY: - return TREEVIEW_Destroy (hwnd); - -/* case WM_ENABLE: */ - - case WM_ERASEBKGND: - return TREEVIEW_EraseBackground (hwnd, wParam, lParam); - - case WM_GETDLGCODE: - return DLGC_WANTARROWS | DLGC_WANTCHARS; - - case WM_PAINT: - return TREEVIEW_Paint (hwnd, wParam, lParam); - - case WM_GETFONT: - return TREEVIEW_GetFont (hwnd, wParam, lParam); - - case WM_SETFONT: - return TREEVIEW_SetFont (hwnd, wParam, lParam); - - case WM_KEYDOWN: - return TREEVIEW_KeyDown (hwnd, wParam, lParam); - - case WM_SETFOCUS: - return TREEVIEW_SetFocus (hwnd, wParam, lParam); - - case WM_KILLFOCUS: - return TREEVIEW_KillFocus (hwnd, wParam, lParam); - - case WM_LBUTTONDOWN: - return TREEVIEW_LButtonDown (hwnd, wParam, lParam); - - case WM_LBUTTONUP: - return TREEVIEW_LButtonUp (hwnd, wParam, lParam); - - case WM_LBUTTONDBLCLK: - return TREEVIEW_LButtonDoubleClick (hwnd, wParam, lParam); - - case WM_RBUTTONDOWN: - return TREEVIEW_RButtonDown (hwnd, wParam, lParam); - - case WM_RBUTTONUP: - return TREEVIEW_RButtonUp (hwnd, wParam, lParam); - - case WM_MOUSEMOVE: - return TREEVIEW_MouseMove (hwnd, wParam, lParam); - - case WM_STYLECHANGED: - return TREEVIEW_StyleChanged (hwnd, wParam, lParam); - -/* case WM_SYSCOLORCHANGE: */ -/* case WM_SETREDRAW: */ - - case WM_TIMER: - return TREEVIEW_HandleTimer (hwnd, wParam, lParam); + infoPtr = (TREEVIEW_INFO *)COMCTL32_Alloc(sizeof(TREEVIEW_INFO)); - case WM_SIZE: - return TREEVIEW_Size (hwnd, wParam,lParam); + if (infoPtr == NULL) + { + ERR("could not allocate info memory!\n"); + return 0; + } - case WM_HSCROLL: - return TREEVIEW_HScroll (hwnd, wParam, lParam); - case WM_VSCROLL: - return TREEVIEW_VScroll (hwnd, wParam, lParam); - - case WM_MOUSEWHEEL: - if (wParam & (MK_SHIFT | MK_CONTROL)) - return DefWindowProcA( hwnd, uMsg, wParam, lParam ); - return TREEVIEW_MouseWheel (hwnd, wParam, lParam); + SetWindowLongA(hwnd, 0, (DWORD)infoPtr); - case WM_DRAWITEM: - TRACE ("drawItem\n"); - return DefWindowProcA (hwnd, uMsg, wParam, lParam); - - default: - if (uMsg >= WM_USER) - FIXME("Unknown msg %04x wp=%08x lp=%08lx\n", - uMsg, wParam, lParam); - return DefWindowProcA (hwnd, uMsg, wParam, lParam); - } + infoPtr->hwnd = hwnd; + infoPtr->dwStyle = GetWindowLongA(hwnd, GWL_STYLE); + infoPtr->uInternalStatus = 0; + infoPtr->Timer = 0; + infoPtr->uNumItems = 0; + infoPtr->cdmode = 0; + infoPtr->uScrollTime = 300; /* milliseconds */ + infoPtr->bRedraw = TRUE; + + GetClientRect(hwnd, &rcClient); + + /* No scroll bars yet. */ + infoPtr->clientWidth = rcClient.right; + infoPtr->clientHeight = rcClient.bottom; + + infoPtr->treeWidth = 0; + infoPtr->treeHeight = 0; + + infoPtr->uIndent = 19; + infoPtr->selectedItem = 0; + infoPtr->focusedItem = 0; + /* hotItem? */ + infoPtr->firstVisible = 0; + infoPtr->maxVisibleOrder = 0; + infoPtr->dropItem = 0; + infoPtr->insertMarkItem = 0; + infoPtr->insertBeforeorAfter = 0; + /* dragList */ + + infoPtr->scrollX = 0; + + infoPtr->clrBk = GetSysColor(COLOR_WINDOW); + infoPtr->clrText = -1; /* use system color */ + infoPtr->clrLine = RGB(128, 128, 128); + infoPtr->clrInsertMark = GetSysColor(COLOR_BTNTEXT); + + /* hwndToolTip */ + + infoPtr->hwndEdit = 0; + infoPtr->wpEditOrig = NULL; + infoPtr->bIgnoreEditKillFocus = FALSE; + infoPtr->bLabelChanged = FALSE; + + infoPtr->himlNormal = NULL; + infoPtr->himlState = NULL; + infoPtr->normalImageWidth = 0; + infoPtr->normalImageHeight = 0; + infoPtr->stateImageWidth = 0; + infoPtr->stateImageHeight = 0; + + infoPtr->items = DPA_Create(16); + + infoPtr->hFont = GetStockObject(DEFAULT_GUI_FONT); + infoPtr->hBoldFont = TREEVIEW_CreateBoldFont(infoPtr->hFont); + + infoPtr->uItemHeight = TREEVIEW_NaturalHeight(infoPtr); + + infoPtr->root = TREEVIEW_AllocateItem(infoPtr); + infoPtr->root->state = TVIS_EXPANDED; + infoPtr->root->iLevel = -1; + infoPtr->root->visibleOrder = -1; + +#if 0 + infoPtr->hwndNotify = GetParent32 (hwnd); + infoPtr->bTransparent = ( GetWindowLongA( hwnd, GWL_STYLE) & TBSTYLE_FLAT); +#endif + + infoPtr->hwndToolTip = 0; + if (!(infoPtr->dwStyle & TVS_NOTOOLTIPS)) + infoPtr->hwndToolTip = COMCTL32_CreateToolTip(hwnd); + + if (infoPtr->dwStyle & TVS_CHECKBOXES) + { + RECT rc; + HBITMAP hbm, hbmOld; + HDC hdc; + int nIndex; + + infoPtr->himlState = + ImageList_Create(16, 16, ILC_COLOR | ILC_MASK, 3, 0); + + hdc = CreateCompatibleDC(0); + hbm = CreateCompatibleBitmap(hdc, 48, 16); + hbmOld = SelectObject(hdc, hbm); + + rc.left = 0; rc.top = 0; + rc.right = 48; rc.bottom = 16; + FillRect(hdc, &rc, (HBRUSH)(COLOR_WINDOW+1)); + + rc.left = 18; rc.top = 2; + rc.right = 30; rc.bottom = 14; + DrawFrameControl(hdc, &rc, DFC_BUTTON, + DFCS_BUTTONCHECK|DFCS_FLAT); + + rc.left = 34; rc.right = 46; + DrawFrameControl(hdc, &rc, DFC_BUTTON, + DFCS_BUTTONCHECK|DFCS_FLAT|DFCS_CHECKED); + + nIndex = ImageList_AddMasked(infoPtr->himlState, hbm, + GetSysColor(COLOR_WINDOW)); + TRACE("chckbox index %d\n", nIndex); + SelectObject(hdc, hbmOld); + DeleteObject(hbm); + DeleteDC(hdc); + + infoPtr->stateImageWidth = 16; + infoPtr->stateImageHeight = 16; + } return 0; } +static LRESULT +TREEVIEW_Destroy(TREEVIEW_INFO *infoPtr) +{ + TRACE("\n"); + + TREEVIEW_RemoveTree(infoPtr); + + /* tool tip is automatically destroyed: we are its owner */ + + /* Restore original windproc. */ + if (infoPtr->hwndEdit) + SetWindowLongA(infoPtr->hwndEdit, GWL_WNDPROC, + (LONG)infoPtr->wpEditOrig); + + /* Deassociate treeview from the window before doing anything drastic. */ + SetWindowLongA(infoPtr->hwnd, 0, (LONG)NULL); + + DeleteObject(infoPtr->hBoldFont); + COMCTL32_Free(infoPtr); + + return 0; +} + +/* Miscellaneous Messages ***********************************************/ + +static LRESULT +TREEVIEW_ScrollKeyDown(TREEVIEW_INFO *infoPtr, WPARAM key) +{ + static const struct + { + unsigned char code; + } + scroll[] = + { +#define SCROLL_ENTRY(dir, code) { ((dir) << 7) | (code) } + SCROLL_ENTRY(SB_VERT, SB_PAGEUP), /* VK_PRIOR */ + SCROLL_ENTRY(SB_VERT, SB_PAGEDOWN), /* VK_NEXT */ + SCROLL_ENTRY(SB_VERT, SB_BOTTOM), /* VK_END */ + SCROLL_ENTRY(SB_VERT, SB_TOP), /* VK_HOME */ + SCROLL_ENTRY(SB_HORZ, SB_LINEUP), /* VK_LEFT */ + SCROLL_ENTRY(SB_VERT, SB_LINEUP), /* VK_UP */ + SCROLL_ENTRY(SB_HORZ, SB_LINEDOWN), /* VK_RIGHT */ + SCROLL_ENTRY(SB_VERT, SB_LINEDOWN) /* VK_DOWN */ +#undef SCROLL_ENTRY + }; + + if (key >= VK_PRIOR && key <= VK_DOWN) + { + unsigned char code = scroll[key - VK_PRIOR].code; + + (((code & (1 << 7)) == (SB_HORZ << 7)) + ? TREEVIEW_HScroll + : TREEVIEW_VScroll)(infoPtr, code & 0x7F); + } + + return 0; +} + +/************************************************************************ + * TREEVIEW_KeyDown + * + * VK_UP Move selection to the previous non-hidden item. + * VK_DOWN Move selection to the next non-hidden item. + * VK_HOME Move selection to the first item. + * VK_END Move selection to the last item. + * VK_LEFT If expanded then collapse, otherwise move to parent. + * VK_RIGHT If collapsed then expand, otherwise move to first child. + * VK_ADD Expand. + * VK_SUBTRACT Collapse. + * VK_MULTIPLY Expand all. + * VK_PRIOR Move up GetVisibleCount items. + * VK_NEXT Move down GetVisibleCount items. + * VK_BACK Move to parent. + * CTRL-Left,Right,Up,Down,PgUp,PgDown,Home,End: Scroll without changing selection + */ +static LRESULT +TREEVIEW_KeyDown(TREEVIEW_INFO *infoPtr, WPARAM wParam) +{ + /* If it is non-NULL and different, it will be selected and visible. */ + TREEVIEW_ITEM *newSelection = NULL; + + TREEVIEW_ITEM *prevItem = infoPtr->selectedItem; + + TRACE("%x\n", wParam); + + if (prevItem == NULL) + return FALSE; + + if (GetAsyncKeyState(VK_CONTROL) & 0x8000) + return TREEVIEW_ScrollKeyDown(infoPtr, wParam); + + switch (wParam) + { + case VK_UP: + newSelection = TREEVIEW_GetPrevListItem(infoPtr, prevItem); + if (!newSelection) + newSelection = infoPtr->root->firstChild; + break; + + case VK_DOWN: + newSelection = TREEVIEW_GetNextListItem(infoPtr, prevItem); + break; + + case VK_HOME: + newSelection = infoPtr->root->firstChild; + break; + + case VK_END: + newSelection = TREEVIEW_GetLastListItem(infoPtr, infoPtr->root); + break; + + case VK_LEFT: + if (prevItem->state & TVIS_EXPANDED) + { + TREEVIEW_Collapse(infoPtr, prevItem, FALSE, TRUE); + } + else if (prevItem->parent != infoPtr->root) + { + newSelection = prevItem->parent; + } + break; + + case VK_RIGHT: + if (TREEVIEW_HasChildren(infoPtr, prevItem)) + { + if (!(prevItem->state & TVIS_EXPANDED)) + TREEVIEW_Expand(infoPtr, prevItem, FALSE, TRUE); + else + { + newSelection = prevItem->firstChild; + } + } + + break; + + case VK_MULTIPLY: + TREEVIEW_ExpandAll(infoPtr, prevItem); + break; + + case VK_ADD: + if (!(prevItem->state & TVIS_EXPANDED)) + TREEVIEW_Expand(infoPtr, prevItem, FALSE, TRUE); + break; + + case VK_SUBTRACT: + if (prevItem->state & TVIS_EXPANDED) + TREEVIEW_Collapse(infoPtr, prevItem, FALSE, TRUE); + break; + + case VK_PRIOR: + newSelection + = TREEVIEW_GetListItem(infoPtr, prevItem, + -TREEVIEW_GetVisibleCount(infoPtr)); + break; + + case VK_NEXT: + newSelection + = TREEVIEW_GetListItem(infoPtr, prevItem, + TREEVIEW_GetVisibleCount(infoPtr)); + break; + + case VK_BACK: + newSelection = prevItem->parent; + if (newSelection == infoPtr->root) + newSelection = NULL; + break; + + case VK_SPACE: + if (infoPtr->dwStyle & TVS_CHECKBOXES) + TREEVIEW_ToggleItemState(infoPtr, prevItem); + break; + } + + if (newSelection && newSelection != prevItem) + { + if (TREEVIEW_DoSelectItem(infoPtr, TVGN_CARET, newSelection, + TVC_BYKEYBOARD)) + { + TREEVIEW_EnsureVisible(infoPtr, newSelection, FALSE); + } + } + + return FALSE; +} + +static LRESULT +TREEVIEW_Size(TREEVIEW_INFO *infoPtr, WPARAM wParam, LPARAM lParam) +{ + if (wParam == SIZE_RESTORED) + { + infoPtr->clientWidth = (LONG)(INT16)LOWORD(lParam); + infoPtr->clientHeight = (LONG)(INT16)HIWORD(lParam); + + TREEVIEW_RecalculateVisibleOrder(infoPtr, NULL); + TREEVIEW_SetFirstVisible(infoPtr, infoPtr->firstVisible, TRUE); + TREEVIEW_UpdateScrollBars(infoPtr); + } + else + { + FIXME("WM_SIZE flag %x %lx not handled\n", wParam, lParam); + } + + TREEVIEW_QueueRefresh(infoPtr); + return 0; +} + +static LRESULT +TREEVIEW_StyleChanged(TREEVIEW_INFO *infoPtr, WPARAM wParam, LPARAM lParam) +{ + TRACE("(%x %lx)\n", wParam, lParam); + + if (wParam == GWL_STYLE) + { + DWORD dwNewStyle = ((LPSTYLESTRUCT)lParam)->styleNew; + + /* we have to take special care about tooltips */ + if ((infoPtr->dwStyle ^ dwNewStyle) & TVS_NOTOOLTIPS) + { + if (infoPtr->dwStyle & TVS_NOTOOLTIPS) + { + infoPtr->hwndToolTip = COMCTL32_CreateToolTip(infoPtr->hwnd); + TRACE("\n"); + } + else + { + DestroyWindow(infoPtr->hwndToolTip); + infoPtr->hwndToolTip = 0; + } + } + + infoPtr->dwStyle = dwNewStyle; + } + + TREEVIEW_UpdateSubTree(infoPtr, infoPtr->root); + TREEVIEW_UpdateScrollBars(infoPtr); + TREEVIEW_QueueRefresh(infoPtr); + + return 0; +} + +static LRESULT +TREEVIEW_SetFocus(TREEVIEW_INFO *infoPtr) +{ + TRACE("\n"); + + if (!infoPtr->selectedItem) + { + TREEVIEW_DoSelectItem(infoPtr, TVGN_CARET, infoPtr->firstVisible, + TVC_UNKNOWN); + } + + TREEVIEW_SendSimpleNotify(infoPtr, NM_SETFOCUS); + TREEVIEW_QueueItemRefresh(infoPtr, infoPtr->selectedItem); + return 0; +} + +static LRESULT +TREEVIEW_KillFocus(TREEVIEW_INFO *infoPtr) +{ + TRACE("\n"); + + TREEVIEW_SendSimpleNotify(infoPtr, NM_KILLFOCUS); + TREEVIEW_QueueItemRefresh(infoPtr, infoPtr->selectedItem); + return 0; +} + + +static LRESULT WINAPI +TREEVIEW_WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + TREEVIEW_INFO *infoPtr = TREEVIEW_GetInfoPtr(hwnd); + if (infoPtr) TREEVIEW_VerifyTree(infoPtr); + else + { + if (uMsg == WM_CREATE) + TREEVIEW_Create(hwnd); + else + goto def; + } + + switch (uMsg) + { + case TVM_CREATEDRAGIMAGE: + return TREEVIEW_CreateDragImage(infoPtr, wParam, lParam); + + case TVM_DELETEITEM: + return TREEVIEW_DeleteItem(infoPtr, (HTREEITEM)lParam); + + case TVM_EDITLABELA: + return TREEVIEW_EditLabelA(infoPtr, (HTREEITEM)lParam); + + case TVM_EDITLABELW: + FIXME("Unimplemented msg TVM_EDITLABELW\n"); + return 0; + + case TVM_ENDEDITLABELNOW: + return TREEVIEW_EndEditLabelNow(infoPtr, (BOOL)wParam); + + case TVM_ENSUREVISIBLE: + return TREEVIEW_EnsureVisible(infoPtr, (HTREEITEM)lParam, TRUE); + + case TVM_EXPAND: + return TREEVIEW_ExpandMsg(infoPtr, (UINT)wParam, (HTREEITEM)lParam); + + case TVM_GETBKCOLOR: + return TREEVIEW_GetBkColor(infoPtr); + + case TVM_GETCOUNT: + return TREEVIEW_GetCount(infoPtr); + + case TVM_GETEDITCONTROL: + return TREEVIEW_GetEditControl(infoPtr); + + case TVM_GETIMAGELIST: + return TREEVIEW_GetImageList(infoPtr, wParam); + + case TVM_GETINDENT: + return TREEVIEW_GetIndent(infoPtr); + + case TVM_GETINSERTMARKCOLOR: + return TREEVIEW_GetInsertMarkColor(infoPtr); + + case TVM_GETISEARCHSTRINGA: + FIXME("Unimplemented msg TVM_GETISEARCHSTRINGA\n"); + return 0; + + case TVM_GETISEARCHSTRINGW: + FIXME("Unimplemented msg TVM_GETISEARCHSTRINGW\n"); + return 0; + + case TVM_GETITEMA: + return TREEVIEW_GetItemA(infoPtr, (LPTVITEMEXA)lParam); + + case TVM_GETITEMW: + return TREEVIEW_GetItemW(infoPtr, (LPTVITEMEXA)lParam); + + case TVM_GETITEMHEIGHT: + return TREEVIEW_GetItemHeight(infoPtr); + + case TVM_GETITEMRECT: + return TREEVIEW_GetItemRect(infoPtr, (BOOL)wParam, (LPRECT)lParam); + + case TVM_GETITEMSTATE: + return TREEVIEW_GetItemState(infoPtr, (HTREEITEM)wParam, (UINT)lParam); + + case TVM_GETLINECOLOR: + return TREEVIEW_GetLineColor(infoPtr); + + case TVM_GETNEXTITEM: + return TREEVIEW_GetNextItem(infoPtr, (UINT)wParam, (HTREEITEM)lParam); + + case TVM_GETSCROLLTIME: + return TREEVIEW_GetScrollTime(infoPtr); + + case TVM_GETTEXTCOLOR: + return TREEVIEW_GetTextColor(infoPtr); + + case TVM_GETTOOLTIPS: + return TREEVIEW_GetToolTips(infoPtr); + + case TVM_GETUNICODEFORMAT: + FIXME("Unimplemented msg TVM_GETUNICODEFORMAT\n"); + return 0; + + case TVM_GETVISIBLECOUNT: + return TREEVIEW_GetVisibleCount(infoPtr); + + case TVM_HITTEST: + return TREEVIEW_HitTest(infoPtr, (LPTVHITTESTINFO)lParam); + + case TVM_INSERTITEMA: + return TREEVIEW_InsertItemA(infoPtr, lParam); + + case TVM_INSERTITEMW: + return TREEVIEW_InsertItemW(infoPtr, lParam); + + case TVM_SELECTITEM: + return TREEVIEW_SelectItem(infoPtr, (INT)wParam, (HTREEITEM)lParam); + + case TVM_SETBKCOLOR: + return TREEVIEW_SetBkColor(infoPtr, (COLORREF)lParam); + + case TVM_SETIMAGELIST: + return TREEVIEW_SetImageList(infoPtr, wParam, (HIMAGELIST)lParam); + + case TVM_SETINDENT: + return TREEVIEW_SetIndent(infoPtr, (UINT)wParam); + + case TVM_SETINSERTMARK: + return TREEVIEW_SetInsertMark(infoPtr, (BOOL)wParam, (HTREEITEM)lParam); + + case TVM_SETINSERTMARKCOLOR: + return TREEVIEW_SetInsertMarkColor(infoPtr, (COLORREF)lParam); + + case TVM_SETITEMA: + return TREEVIEW_SetItemA(infoPtr, (LPTVITEMEXA)lParam); + + case TVM_SETITEMW: + FIXME("Unimplemented msg TVM_SETITEMW\n"); + return 0; + + case TVM_SETLINECOLOR: + return TREEVIEW_SetLineColor(infoPtr, (COLORREF)lParam); + + case TVM_SETITEMHEIGHT: + return TREEVIEW_SetItemHeight(infoPtr, (INT)(SHORT)wParam); + + case TVM_SETSCROLLTIME: + return TREEVIEW_SetScrollTime(infoPtr, (UINT)wParam); + + case TVM_SETTEXTCOLOR: + return TREEVIEW_SetTextColor(infoPtr, (COLORREF)lParam); + + case TVM_SETTOOLTIPS: + return TREEVIEW_SetToolTips(infoPtr, (HWND)wParam); + + case TVM_SETUNICODEFORMAT: + FIXME("Unimplemented msg TVM_SETUNICODEFORMAT\n"); + return 0; + + case TVM_SORTCHILDREN: + return TREEVIEW_SortChildren(infoPtr, wParam, lParam); + + case TVM_SORTCHILDRENCB: + return TREEVIEW_SortChildrenCB(infoPtr, wParam, (LPTVSORTCB)lParam); + + /* WM_CHAR */ + + case WM_COMMAND: + return TREEVIEW_Command(infoPtr, wParam, lParam); + + case WM_DESTROY: + return TREEVIEW_Destroy(infoPtr); + + /* WM_ENABLE */ + + case WM_ERASEBKGND: + return TREEVIEW_EraseBackground(infoPtr, (HDC)wParam); + + case WM_GETDLGCODE: + return DLGC_WANTARROWS | DLGC_WANTCHARS; + + case WM_GETFONT: + return TREEVIEW_GetFont(infoPtr); + + case WM_HSCROLL: + return TREEVIEW_HScroll(infoPtr, wParam); + + case WM_KEYDOWN: + return TREEVIEW_KeyDown(infoPtr, wParam); + + case WM_KILLFOCUS: + return TREEVIEW_KillFocus(infoPtr); + + case WM_LBUTTONDBLCLK: + return TREEVIEW_LButtonDoubleClick(infoPtr, lParam); + + case WM_LBUTTONDOWN: + return TREEVIEW_LButtonDown(infoPtr, lParam); + + /* WM_MBUTTONDOWN */ + + /* WM_MOUSEMOVE */ + + /* WM_NOTIFY */ + + /* WM_NOTIFYFORMAT */ + + case WM_PAINT: + return TREEVIEW_Paint(infoPtr, wParam); + + /* WM_PRINTCLIENT */ + + case WM_RBUTTONDOWN: + return TREEVIEW_RButtonDown(infoPtr, lParam); + + case WM_SETFOCUS: + return TREEVIEW_SetFocus(infoPtr); + + case WM_SETFONT: + return TREEVIEW_SetFont(infoPtr, (HFONT)wParam, (BOOL)lParam); + + case WM_SETREDRAW: + return TREEVIEW_SetRedraw(infoPtr, wParam, lParam); + + case WM_SIZE: + return TREEVIEW_Size(infoPtr, wParam, lParam); + + case WM_STYLECHANGED: + return TREEVIEW_StyleChanged(infoPtr, wParam, lParam); + + /* WM_SYSCOLORCHANGE */ + + /* WM_SYSKEYDOWN */ + + case WM_TIMER: + return TREEVIEW_HandleTimer(infoPtr, wParam); + + case WM_VSCROLL: + return TREEVIEW_VScroll(infoPtr, wParam); + + /* WM_WININICHANGE */ + + case WM_MOUSEWHEEL: + if (wParam & (MK_SHIFT | MK_CONTROL)) + goto def; + return TREEVIEW_MouseWheel(infoPtr, wParam); + + case WM_DRAWITEM: + TRACE("drawItem\n"); + goto def; + + default: + /* This mostly catches MFC and Delphi messages. :( */ + if (uMsg >= WM_USER) + TRACE("Unknown msg %04x wp=%08x lp=%08lx\n", uMsg, wParam, lParam); +def: + return DefWindowProcA(hwnd, uMsg, wParam, lParam); + } + return 0; +} + + +/* Class Registration ***************************************************/ + VOID -TREEVIEW_Register (void) +TREEVIEW_Register(void) { WNDCLASSA wndClass; TRACE("\n"); - ZeroMemory (&wndClass, sizeof(WNDCLASSA)); - wndClass.style = CS_GLOBALCLASS | CS_DBLCLKS; - wndClass.lpfnWndProc = (WNDPROC)TREEVIEW_WindowProc; - wndClass.cbClsExtra = 0; - wndClass.cbWndExtra = sizeof(TREEVIEW_INFO *); - wndClass.hCursor = LoadCursorA (0, IDC_ARROWA); + ZeroMemory(&wndClass, sizeof(WNDCLASSA)); + wndClass.style = CS_GLOBALCLASS | CS_DBLCLKS; + wndClass.lpfnWndProc = (WNDPROC)TREEVIEW_WindowProc; + wndClass.cbClsExtra = 0; + wndClass.cbWndExtra = sizeof(TREEVIEW_INFO *); + + wndClass.hCursor = LoadCursorA(0, IDC_ARROWA); wndClass.hbrBackground = 0; wndClass.lpszClassName = WC_TREEVIEWA; - - RegisterClassA (&wndClass); + + RegisterClassA(&wndClass); } VOID -TREEVIEW_Unregister (void) +TREEVIEW_Unregister(void) { - UnregisterClassA (WC_TREEVIEWA, (HINSTANCE)NULL); + UnregisterClassA(WC_TREEVIEWA, (HINSTANCE) NULL); } +/* Tree Verification ****************************************************/ +#ifndef NDEBUG +static inline void +TREEVIEW_VerifyChildren(TREEVIEW_INFO *infoPtr, TREEVIEW_ITEM *item); + +static inline void TREEVIEW_VerifyItemCommon(TREEVIEW_INFO *infoPtr, + TREEVIEW_ITEM *item) +{ + assert(infoPtr != NULL); + assert(item != NULL); + + /* both NULL, or both non-null */ + assert((item->firstChild == NULL) == (item->lastChild == NULL)); + + assert(item->firstChild != item); + assert(item->lastChild != item); + + if (item->firstChild) + { + assert(item->firstChild->parent == item); + assert(item->firstChild->prevSibling == NULL); + } + + if (item->lastChild) + { + assert(item->lastChild->parent == item); + assert(item->lastChild->nextSibling == NULL); + } + + assert(item->nextSibling != item); + if (item->nextSibling) + { + assert(item->nextSibling->parent == item->parent); + assert(item->nextSibling->prevSibling == item); + } + + assert(item->prevSibling != item); + if (item->prevSibling) + { + assert(item->prevSibling->parent == item->parent); + assert(item->prevSibling->nextSibling == item); + } +} + +static inline void +TREEVIEW_VerifyItem(TREEVIEW_INFO *infoPtr, TREEVIEW_ITEM *item) +{ + assert(item != NULL); + + assert(item->parent != NULL); + assert(item->parent != item); + assert(item->iLevel == item->parent->iLevel + 1); + + assert(DPA_GetPtrIndex(infoPtr->items, item) != -1); + + TREEVIEW_VerifyItemCommon(infoPtr, item); + + TREEVIEW_VerifyChildren(infoPtr, item); +} + +static inline void +TREEVIEW_VerifyChildren(TREEVIEW_INFO *infoPtr, TREEVIEW_ITEM *item) +{ + TREEVIEW_ITEM *child; + assert(item != NULL); + + for (child = item->firstChild; child != NULL; child = child->nextSibling) + TREEVIEW_VerifyItem(infoPtr, child); +} + +static inline void +TREEVIEW_VerifyRoot(TREEVIEW_INFO *infoPtr) +{ + TREEVIEW_ITEM *root = infoPtr->root; + + assert(root != NULL); + assert(root->iLevel == -1); + assert(root->parent == NULL); + assert(root->prevSibling == NULL); + + TREEVIEW_VerifyItemCommon(infoPtr, root); + + TREEVIEW_VerifyChildren(infoPtr, root); +} + +static void +TREEVIEW_VerifyTree(TREEVIEW_INFO *infoPtr) +{ + assert(infoPtr != NULL); + + TREEVIEW_VerifyRoot(infoPtr); +} +#endif