Implemented VK_PRIOR and VK_NEXT processing (merged from Corel tree).
[wine] / dlls / comctl32 / treeview.c
1 /* Treeview control
2  *
3  * Copyright 1998 Eric Kohl <ekohl@abo.rhein-zeitung.de>
4  * Copyright 1998,1999 Alex Priem <alexp@sci.kun.nl>
5  * Copyright 1999 Sylvain St-Germain
6  *
7  * Note that TREEVIEW_INFO * and HTREEITEM are the same thing.
8  *
9  * Note2: All items always! have valid (allocated) pszText field.
10  *      If item's text == LPSTR_TEXTCALLBACKA we allocate buffer
11  *      of size TEXT_CALLBACK_SIZE in DoSetItem.
12  *      We use callbackMask to keep track of fields to be updated.
13  *
14  * TODO:
15  *   missing notifications: NM_SETCURSOR, TVN_GETINFOTIP, TVN_KEYDOWN,
16  *      TVN_SETDISPINFO, TVN_SINGLEEXPAND
17  *
18  *   missing styles: TVS_FULLROWSELECT, TVS_INFOTIP, TVS_NOSCROLL,
19  *      TVS_RTLREADING, TVS_TRACKSELECT
20  *
21  *   missing item styles: TVIS_CUT, TVIS_EXPANDPARTIAL
22  *
23  *   Make the insertion mark look right.
24  *   Scroll (instead of repaint) as much as possible.
25  */
26
27 #include <assert.h>
28 #include <string.h>
29 #include <limits.h>
30 #include <stdlib.h>
31
32 #include "winbase.h"
33 #include "wingdi.h"
34 #include "wine/winestring.h"
35 #include "commctrl.h"
36 #include "comctl32.h"
37 #include "debugtools.h"
38
39 /* internal structures */
40
41 typedef struct _TREEITEM    /* HTREEITEM is a _TREEINFO *. */
42 {
43   UINT      callbackMask;
44   UINT      state;
45   UINT      stateMask;
46   LPSTR     pszText;
47   int       cchTextMax;
48   int       iImage;
49   int       iSelectedImage;
50   int       cChildren;
51   LPARAM    lParam;
52   int       iIntegral;      /* item height multiplier (1 is normal) */
53   int       iLevel;         /* indentation level:0=root level */
54   HTREEITEM parent;         /* handle to parent or 0 if at root*/
55   HTREEITEM firstChild;     /* handle to first child or 0 if no child*/
56   HTREEITEM lastChild;
57   HTREEITEM prevSibling;    /* handle to prev item in list, 0 if first */
58   HTREEITEM nextSibling;    /* handle to next item in list, 0 if last */
59   RECT      rect;
60   LONG      linesOffset;
61   LONG      stateOffset;
62   LONG      imageOffset;
63   LONG      textOffset;
64   LONG      textWidth;      /* horizontal text extent for pszText */
65   LONG      visibleOrder;   /* visible ordering, 0 is first visible item */
66 } TREEVIEW_ITEM;
67
68
69 typedef struct tagTREEVIEW_INFO
70 {
71   HWND          hwnd;
72   HWND          hwndNotify;     /* Owner window to send notifications to */
73   DWORD         dwStyle;
74   HTREEITEM     root;
75   UINT          uInternalStatus;
76   INT           Timer;
77   UINT          uNumItems;      /* number of valid TREEVIEW_ITEMs */
78   INT           cdmode;         /* last custom draw setting */
79   UINT          uScrollTime;    /* max. time for scrolling in milliseconds */
80   BOOL          bRedraw;        /* if FALSE we validate but don't redraw in TREEVIEW_Paint() */
81
82   UINT          uItemHeight;    /* item height */
83   BOOL          bHeightSet;
84
85   LONG          clientWidth;    /* width of control window */
86   LONG          clientHeight;   /* height of control window */
87
88   LONG          treeWidth;      /* width of visible tree items */
89   LONG          treeHeight;     /* height of visible tree items */
90
91   UINT          uIndent;        /* indentation in pixels */
92   HTREEITEM     selectedItem;   /* handle to selected item or 0 if none */
93   HTREEITEM     hotItem;        /* handle currently under cursor, 0 if none */
94   HTREEITEM     focusedItem;    /* item that was under the cursor when WM_LBUTTONDOWN was received */
95
96   HTREEITEM     firstVisible;   /* handle to first visible item */
97   LONG          maxVisibleOrder;
98   HTREEITEM     dropItem;       /* handle to item selected by drag cursor */
99   HTREEITEM     insertMarkItem; /* item after which insertion mark is placed */
100   BOOL          insertBeforeorAfter; /* flag used by TVM_SETINSERTMARK */
101   HIMAGELIST    dragList;       /* Bitmap of dragged item */
102   LONG          scrollX;
103   COLORREF      clrBk;
104   COLORREF      clrText;
105   COLORREF      clrLine;
106   COLORREF      clrInsertMark;
107   HFONT         hFont;
108   HFONT         hBoldFont;
109   HWND          hwndToolTip;
110
111   HWND          hwndEdit;
112   WNDPROC       wpEditOrig;     /* orig window proc for subclassing edit */
113   BOOL          bIgnoreEditKillFocus;
114   BOOL          bLabelChanged;
115
116   HIMAGELIST    himlNormal;
117   int           normalImageHeight;
118   int           normalImageWidth;
119   HIMAGELIST    himlState;
120   int           stateImageHeight;
121   int           stateImageWidth;
122   HDPA          items;
123 } TREEVIEW_INFO;
124
125
126
127 /* bitflags for infoPtr->uInternalStatus */
128
129 #define TV_HSCROLL      0x01    /* treeview too large to fit in window */
130 #define TV_VSCROLL      0x02    /* (horizontal/vertical) */
131 #define TV_LDRAG                0x04    /* Lbutton pushed to start drag */
132 #define TV_LDRAGGING    0x08    /* Lbutton pushed, mouse moved.  */
133 #define TV_RDRAG                0x10    /* dito Rbutton */
134 #define TV_RDRAGGING    0x20    
135
136 /* bitflags for infoPtr->timer */
137
138 #define TV_EDIT_TIMER    2
139 #define TV_EDIT_TIMER_SET 2
140
141
142 VOID TREEVIEW_Register (VOID);
143 VOID TREEVIEW_Unregister (VOID);
144
145
146 DEFAULT_DEBUG_CHANNEL(treeview);
147
148
149 #define TEXT_CALLBACK_SIZE 260
150
151 #define TREEVIEW_LEFT_MARGIN 8
152
153 #define MINIMUM_INDENT 19
154
155 #define CALLBACK_MASK_ALL (TVIF_TEXT|TVIF_CHILDREN|TVIF_IMAGE|TVIF_SELECTEDIMAGE)
156
157 #define STATEIMAGEINDEX(x) (((x) >> 12) & 0x0f)
158 #define OVERLAYIMAGEINDEX(x) (((x) >> 8) & 0x0f)
159 #define ISVISIBLE(x)         ((x)->visibleOrder >= 0)
160
161
162 typedef VOID (*TREEVIEW_ItemEnumFunc)(TREEVIEW_INFO *, TREEVIEW_ITEM *,LPVOID);
163
164
165 static VOID TREEVIEW_Invalidate(TREEVIEW_INFO *, TREEVIEW_ITEM *);
166
167 static LRESULT TREEVIEW_DoSelectItem(TREEVIEW_INFO *, INT, HTREEITEM, INT);
168 static VOID TREEVIEW_SetFirstVisible(TREEVIEW_INFO *, TREEVIEW_ITEM *, BOOL);
169 static LRESULT TREEVIEW_EnsureVisible(TREEVIEW_INFO *, HTREEITEM, BOOL);
170 static LRESULT TREEVIEW_RButtonUp(TREEVIEW_INFO *, LPPOINT);
171 static LRESULT TREEVIEW_EndEditLabelNow(TREEVIEW_INFO *infoPtr, BOOL bCancel);
172 static VOID TREEVIEW_UpdateScrollBars(TREEVIEW_INFO *infoPtr);
173 static LRESULT TREEVIEW_HScroll(TREEVIEW_INFO *, WPARAM);
174
175
176 /* Random Utilities *****************************************************/
177
178 #ifndef NDEBUG
179 static inline void
180 TREEVIEW_VerifyTree(TREEVIEW_INFO *infoPtr)
181 {
182     (void)infoPtr;
183 }
184 #else
185 /* The definition is at the end of the file. */
186 static void TREEVIEW_VerifyTree(TREEVIEW_INFO *infoPtr);
187 #endif
188
189 /* Returns the treeview private data if hwnd is a treeview.
190  * Otherwise returns an undefined value. */
191 static TREEVIEW_INFO *
192 TREEVIEW_GetInfoPtr(HWND hwnd)
193 {
194     return (TREEVIEW_INFO *)GetWindowLongA(hwnd, 0);
195 }
196
197 /* Don't call this. Nothing wants an item index. */
198 static inline int
199 TREEVIEW_GetItemIndex(TREEVIEW_INFO *infoPtr, HTREEITEM handle)
200 {
201     assert(infoPtr != NULL);
202
203     return DPA_GetPtrIndex(infoPtr->items, handle);
204 }
205
206 /***************************************************************************
207  * This method checks that handle is an item for this tree.
208  */
209 static BOOL
210 TREEVIEW_ValidItem(TREEVIEW_INFO *infoPtr, HTREEITEM handle)
211 {
212     if (TREEVIEW_GetItemIndex(infoPtr, handle) == -1)
213     {
214         TRACE("invalid item %p\n", handle);
215         return FALSE;
216     }
217     else
218         return TRUE;
219 }
220
221 static HFONT
222 TREEVIEW_CreateBoldFont(HFONT hOrigFont)
223 {
224     LOGFONTA font;
225
226     GetObjectA(hOrigFont, sizeof(font), &font);
227     font.lfWeight = FW_BOLD;
228     return CreateFontIndirectA(&font);
229 }
230
231 static inline HFONT
232 TREEVIEW_FontForItem(TREEVIEW_INFO *infoPtr, TREEVIEW_ITEM *item)
233 {
234     return (item->state & TVIS_BOLD) ? infoPtr->hBoldFont : infoPtr->hFont;
235 }
236
237 /* for trace/debugging purposes only */
238 static const char *
239 TREEVIEW_ItemName(TREEVIEW_ITEM *item)
240 {
241     if (item == NULL) return "<null item>";
242     if (item->pszText == LPSTR_TEXTCALLBACKA) return "<callback>";
243     if (item->pszText == NULL) return "<null>";
244     return item->pszText;
245 }
246
247 /* An item is not a child of itself. */
248 static BOOL
249 TREEVIEW_IsChildOf(TREEVIEW_ITEM *parent, TREEVIEW_ITEM *child)
250 {
251     do
252     {
253         child = child->parent;
254         if (child == parent) return TRUE;
255     } while (child != NULL);
256
257     return FALSE;
258 }
259
260
261 /* Tree Traversal *******************************************************/
262
263 /***************************************************************************
264  * This method returns the last expanded sibling or child child item
265  * of a tree node
266  */
267 static TREEVIEW_ITEM *
268 TREEVIEW_GetLastListItem(TREEVIEW_INFO *infoPtr, TREEVIEW_ITEM *wineItem)
269 {
270     if (!wineItem)
271        return NULL;
272
273     while (wineItem->lastChild)
274     {
275        if (wineItem->state & TVIS_EXPANDED)
276           wineItem = wineItem->lastChild;
277        else
278           break;
279     }
280
281     if (wineItem == infoPtr->root)
282         return NULL;
283
284     return wineItem;
285 }
286
287 /***************************************************************************
288  * This method returns the previous non-hidden item in the list not 
289  * considering the tree hierarchy.
290  */
291 static TREEVIEW_ITEM *
292 TREEVIEW_GetPrevListItem(TREEVIEW_INFO *infoPtr, TREEVIEW_ITEM *tvItem)
293 {
294     if (tvItem->prevSibling)
295     {
296         /* This item has a prevSibling, get the last item in the sibling's tree. */
297         TREEVIEW_ITEM *upItem = tvItem->prevSibling;
298
299         if ((upItem->state & TVIS_EXPANDED) && upItem->lastChild != NULL)
300             return TREEVIEW_GetLastListItem(infoPtr, upItem->lastChild);
301         else
302             return upItem;
303     }
304     else
305     {
306         /* this item does not have a prevSibling, get the parent */
307         return (tvItem->parent != infoPtr->root) ? tvItem->parent : NULL;
308     }
309 }
310
311
312 /***************************************************************************
313  * This method returns the next physical item in the treeview not 
314  * considering the tree hierarchy.
315  */
316 static TREEVIEW_ITEM *
317 TREEVIEW_GetNextListItem(TREEVIEW_INFO *infoPtr, TREEVIEW_ITEM *tvItem)
318 {
319     assert(tvItem != NULL);
320
321     /* 
322      * If this item has children and is expanded, return the first child
323      */
324     if ((tvItem->state & TVIS_EXPANDED) && tvItem->firstChild != NULL)
325     {
326         return tvItem->firstChild;
327     }
328
329
330     /*
331      * try to get the sibling
332      */
333     if (tvItem->nextSibling)
334         return tvItem->nextSibling;
335
336     /*
337      * Otherwise, get the parent's sibling.
338      */
339     while (tvItem->parent)
340     {
341         tvItem = tvItem->parent;
342
343         if (tvItem->nextSibling)
344             return tvItem->nextSibling;
345     }
346
347     return NULL;
348 }
349
350 /***************************************************************************
351  * This method returns the nth item starting at the given item.  It returns 
352  * the last item (or first) we we run out of items.
353  *
354  * Will scroll backward if count is <0.
355  *             forward if count is >0.
356  */
357 static TREEVIEW_ITEM *
358 TREEVIEW_GetListItem(TREEVIEW_INFO *infoPtr, TREEVIEW_ITEM *wineItem,
359                      LONG count)
360 {
361     TREEVIEW_ITEM *(*next_item)(TREEVIEW_INFO *, TREEVIEW_ITEM *);
362     TREEVIEW_ITEM *previousItem;
363
364     assert(wineItem != NULL);
365
366     if (count > 0)
367     {
368         next_item = TREEVIEW_GetNextListItem;
369     }
370     else if (count < 0)
371     {
372         count = -count;
373         next_item = TREEVIEW_GetPrevListItem;
374     }
375     else
376         return wineItem;
377
378     do
379     {
380         previousItem = wineItem;
381         wineItem = next_item(infoPtr, wineItem);
382
383     } while (--count && wineItem != NULL);
384
385
386     return wineItem ? wineItem : previousItem;
387 }
388
389 /* Notifications ************************************************************/
390
391 static BOOL
392 TREEVIEW_SendSimpleNotify(TREEVIEW_INFO *infoPtr, UINT code)
393 {
394     NMHDR nmhdr;
395     HWND hwnd = infoPtr->hwnd;
396
397     TRACE("%x\n", code);
398     nmhdr.hwndFrom = hwnd;
399     nmhdr.idFrom = GetWindowLongA(hwnd, GWL_ID);
400     nmhdr.code = code;
401
402     return (BOOL)SendMessageA(infoPtr->hwndNotify, WM_NOTIFY,
403                               (WPARAM)nmhdr.idFrom, (LPARAM)&nmhdr);
404 }
405
406 static VOID
407 TREEVIEW_TVItemFromItem(UINT mask, TVITEMA *tvItem, TREEVIEW_ITEM *item)
408 {
409     tvItem->mask = mask;
410     tvItem->hItem = item;
411     tvItem->state = item->state;
412     tvItem->stateMask = 0;
413     tvItem->iImage = item->iImage;
414     tvItem->pszText = item->pszText;
415     tvItem->cchTextMax = item->cchTextMax;
416     tvItem->iImage = item->iImage;
417     tvItem->iSelectedImage = item->iSelectedImage;
418     tvItem->cChildren = item->cChildren;
419     tvItem->lParam = item->lParam;
420 }
421
422 static BOOL
423 TREEVIEW_SendTreeviewNotify(TREEVIEW_INFO *infoPtr, UINT code, UINT action,
424                             UINT mask, HTREEITEM oldItem, HTREEITEM newItem)
425 {
426     HWND hwnd = infoPtr->hwnd;
427     NMTREEVIEWA nmhdr;
428
429     TRACE("code:%x action:%x olditem:%p newitem:%p\n",
430           code, action, oldItem, newItem);
431
432     ZeroMemory(&nmhdr, sizeof(NMTREEVIEWA));
433
434     nmhdr.hdr.hwndFrom = hwnd;
435     nmhdr.hdr.idFrom = GetWindowLongA(hwnd, GWL_ID);
436     nmhdr.hdr.code = code;
437     nmhdr.action = action;
438
439     if (oldItem)
440         TREEVIEW_TVItemFromItem(mask, &nmhdr.itemOld, oldItem);
441
442     if (newItem)
443         TREEVIEW_TVItemFromItem(mask, &nmhdr.itemNew, newItem);
444
445     nmhdr.ptDrag.x = 0;
446     nmhdr.ptDrag.y = 0;
447
448     return (BOOL)SendMessageA(infoPtr->hwndNotify, WM_NOTIFY,
449                               (WPARAM)GetWindowLongA(hwnd, GWL_ID),
450                               (LPARAM)&nmhdr);
451 }
452
453 static BOOL
454 TREEVIEW_SendTreeviewDnDNotify(TREEVIEW_INFO *infoPtr, UINT code,
455                                HTREEITEM dragItem, POINT pt)
456 {
457     HWND hwnd = infoPtr->hwnd;
458     NMTREEVIEWA nmhdr;
459
460     TRACE("code:%x dragitem:%p\n", code, dragItem);
461
462     nmhdr.hdr.hwndFrom = hwnd;
463     nmhdr.hdr.idFrom = GetWindowLongA(hwnd, GWL_ID);
464     nmhdr.hdr.code = code;
465     nmhdr.action = 0;
466     nmhdr.itemNew.mask = TVIF_STATE | TVIF_PARAM | TVIF_HANDLE;
467     nmhdr.itemNew.hItem = dragItem;
468     nmhdr.itemNew.state = dragItem->state;
469     nmhdr.itemNew.lParam = dragItem->lParam;
470
471     nmhdr.ptDrag.x = pt.x;
472     nmhdr.ptDrag.y = pt.y;
473
474     return (BOOL)SendMessageA(infoPtr->hwndNotify, WM_NOTIFY,
475                               (WPARAM)GetWindowLongA(hwnd, GWL_ID),
476                               (LPARAM)&nmhdr);
477 }
478
479
480 static BOOL
481 TREEVIEW_SendCustomDrawNotify(TREEVIEW_INFO *infoPtr, DWORD dwDrawStage,
482                               HDC hdc, RECT rc)
483 {
484     HWND hwnd = infoPtr->hwnd;
485     NMTVCUSTOMDRAW nmcdhdr;
486     LPNMCUSTOMDRAW nmcd;
487
488     TRACE("drawstage:%lx hdc:%x\n", dwDrawStage, hdc);
489
490     nmcd = &nmcdhdr.nmcd;
491     nmcd->hdr.hwndFrom = hwnd;
492     nmcd->hdr.idFrom = GetWindowLongA(hwnd, GWL_ID);
493     nmcd->hdr.code = NM_CUSTOMDRAW;
494     nmcd->dwDrawStage = dwDrawStage;
495     nmcd->hdc = hdc;
496     nmcd->rc = rc;
497     nmcd->dwItemSpec = 0;
498     nmcd->uItemState = 0;
499     nmcd->lItemlParam = 0;
500     nmcdhdr.clrText = infoPtr->clrText;
501     nmcdhdr.clrTextBk = infoPtr->clrBk;
502     nmcdhdr.iLevel = 0;
503
504     return (BOOL)SendMessageA(infoPtr->hwndNotify, WM_NOTIFY,
505                               (WPARAM)GetWindowLongA(hwnd, GWL_ID),
506                               (LPARAM)&nmcdhdr);
507 }
508
509
510
511 /* FIXME: need to find out when the flags in uItemState need to be set */
512
513 static BOOL
514 TREEVIEW_SendCustomDrawItemNotify(TREEVIEW_INFO *infoPtr, HDC hdc,
515                                   TREEVIEW_ITEM *wineItem, UINT uItemDrawState)
516 {
517     HWND hwnd = infoPtr->hwnd;
518     NMTVCUSTOMDRAW nmcdhdr;
519     LPNMCUSTOMDRAW nmcd;
520     DWORD dwDrawStage, dwItemSpec;
521     UINT uItemState;
522     INT retval;
523
524     dwDrawStage = CDDS_ITEM | uItemDrawState;
525     dwItemSpec = (DWORD)wineItem;
526     uItemState = 0;
527     if (wineItem->state & TVIS_SELECTED)
528         uItemState |= CDIS_SELECTED;
529     if (wineItem == infoPtr->selectedItem)
530         uItemState |= CDIS_FOCUS;
531     if (wineItem == infoPtr->hotItem)
532         uItemState |= CDIS_HOT;
533
534     nmcd = &nmcdhdr.nmcd;
535     nmcd->hdr.hwndFrom = hwnd;
536     nmcd->hdr.idFrom = GetWindowLongA(hwnd, GWL_ID);
537     nmcd->hdr.code = NM_CUSTOMDRAW;
538     nmcd->dwDrawStage = dwDrawStage;
539     nmcd->hdc = hdc;
540     nmcd->rc = wineItem->rect;
541     nmcd->dwItemSpec = dwItemSpec;
542     nmcd->uItemState = uItemState;
543     nmcd->lItemlParam = wineItem->lParam;
544     nmcdhdr.clrText = infoPtr->clrText;
545     nmcdhdr.clrTextBk = infoPtr->clrBk;
546     nmcdhdr.iLevel = wineItem->iLevel;
547
548     TRACE("drawstage:%lx hdc:%x item:%lx, itemstate:%x, lItemlParam:%lx\n",
549           nmcd->dwDrawStage, nmcd->hdc, nmcd->dwItemSpec,
550           nmcd->uItemState, nmcd->lItemlParam);
551
552     retval = SendMessageA(infoPtr->hwndNotify, WM_NOTIFY,
553                           (WPARAM)GetWindowLongA(hwnd, GWL_ID),
554                           (LPARAM)&nmcdhdr);
555
556     infoPtr->clrText = nmcdhdr.clrText;
557     infoPtr->clrBk = nmcdhdr.clrTextBk;
558     return (BOOL)retval;
559 }
560
561 static BOOL
562 TREEVIEW_BeginLabelEditNotify(TREEVIEW_INFO *infoPtr, TREEVIEW_ITEM *editItem)
563 {
564     HWND hwnd = infoPtr->hwnd;
565     NMTVDISPINFOA tvdi;
566
567     tvdi.hdr.hwndFrom = hwnd;
568     tvdi.hdr.idFrom = GetWindowLongA(hwnd, GWL_ID);
569     tvdi.hdr.code = TVN_BEGINLABELEDITA;
570
571     tvdi.item.mask = TVIF_HANDLE | TVIF_STATE | TVIF_PARAM | TVIF_TEXT;
572     tvdi.item.hItem = editItem;
573     tvdi.item.state = editItem->state;
574     tvdi.item.lParam = editItem->lParam;
575     tvdi.item.pszText = editItem->pszText;
576     tvdi.item.cchTextMax = editItem->cchTextMax;
577
578     return SendMessageA(infoPtr->hwndNotify, WM_NOTIFY, tvdi.hdr.idFrom,
579                         (LPARAM)&tvdi);
580 }
581
582 static void
583 TREEVIEW_UpdateDispInfo(TREEVIEW_INFO *infoPtr, TREEVIEW_ITEM *wineItem,
584                         UINT mask)
585 {
586     NMTVDISPINFOA callback;
587     HWND hwnd = infoPtr->hwnd;
588
589     mask &= wineItem->callbackMask;
590
591     if (mask == 0) return;
592
593     callback.hdr.hwndFrom         = hwnd;
594     callback.hdr.idFrom           = GetWindowLongA(hwnd, GWL_ID);
595     callback.hdr.code             = TVN_GETDISPINFOA;
596
597     /* 'state' always contains valid value, as well as 'lParam'.
598      * All other parameters are uninitialized.
599      */
600     callback.item.pszText         = wineItem->pszText;
601     callback.item.cchTextMax      = wineItem->cchTextMax;
602     callback.item.mask            = mask;
603     callback.item.hItem           = wineItem;
604     callback.item.state           = wineItem->state;
605     callback.item.lParam          = wineItem->lParam;
606
607     /* If text is changed we need to recalculate textWidth */
608     if (mask & TVIF_TEXT)
609        wineItem->textWidth = 0;
610
611     SendMessageA(infoPtr->hwndNotify, WM_NOTIFY, callback.hdr.idFrom, (LPARAM)&callback);
612
613     /* It may have changed due to a call to SetItem. */ 
614     mask &= wineItem->callbackMask;
615
616     if ((mask & TVIF_TEXT) && callback.item.pszText != wineItem->pszText)
617     {
618         /* Instead of copying text into our buffer user specified its own */
619         int len = max(lstrlenA(callback.item.pszText) + 1, TEXT_CALLBACK_SIZE);
620         LPSTR newText = COMCTL32_ReAlloc(wineItem->pszText, len);
621
622         if (newText)
623         {
624            wineItem->pszText = newText;
625            strcpy(wineItem->pszText, callback.item.pszText);
626            wineItem->cchTextMax = len;
627         }
628         /* If ReAlloc fails we have nothing to do, but keep original text */
629     }
630
631     if (mask & TVIF_IMAGE)
632         wineItem->iImage = callback.item.iImage;
633
634     if (mask & TVIF_SELECTEDIMAGE)
635         wineItem->iSelectedImage = callback.item.iSelectedImage;
636
637     if (mask & TVIF_CHILDREN)
638         wineItem->cChildren = callback.item.cChildren;
639
640     /* These members are now permanently set. */
641     if (callback.item.mask & TVIF_DI_SETITEM)
642         wineItem->callbackMask &= ~callback.item.mask;
643 }
644
645 /***************************************************************************
646  * This function uses cChildren field to decide whether the item has
647  * children or not.
648  * Note: if this returns TRUE, the child items may not actually exist,
649  * they could be virtual.
650  *
651  * Just use wineItem->firstChild to check for physical children.
652  */
653 static BOOL
654 TREEVIEW_HasChildren(TREEVIEW_INFO *infoPtr, TREEVIEW_ITEM *wineItem)
655 {
656     TREEVIEW_UpdateDispInfo(infoPtr, wineItem, TVIF_CHILDREN);
657
658     return wineItem->cChildren > 0;
659 }
660
661
662 /* Item Position ********************************************************/
663
664 /* Compute linesOffset, stateOffset, imageOffset, textOffset of an item. */
665 static VOID
666 TREEVIEW_ComputeItemInternalMetrics(TREEVIEW_INFO *infoPtr,
667                                     TREEVIEW_ITEM *item)
668 {
669     /* Same effect, different optimisation. */
670 #if 0
671     BOOL lar = ((infoPtr->dwStyle & TVS_LINESATROOT)
672                 && (infoPtr->dwStyle & (TVS_HASLINES|TVS_HASBUTTONS)));
673 #else
674     BOOL lar = ((infoPtr->dwStyle
675                  & (TVS_LINESATROOT|TVS_HASLINES|TVS_HASBUTTONS))
676                 > TVS_LINESATROOT);
677 #endif
678
679     item->linesOffset = infoPtr->uIndent * (item->iLevel + lar - 1)
680         - infoPtr->scrollX;
681     item->stateOffset = item->linesOffset + infoPtr->uIndent;
682     item->imageOffset = item->stateOffset
683         + (STATEIMAGEINDEX(item->state) ? infoPtr->stateImageWidth : 0);
684     item->textOffset  = item->imageOffset + infoPtr->normalImageWidth;
685 }
686
687 static VOID
688 TREEVIEW_ComputeTextWidth(TREEVIEW_INFO *infoPtr, TREEVIEW_ITEM *item, HDC hDC)
689 {
690     HDC hdc;
691     HFONT hOldFont=0;
692     SIZE sz;
693
694     /* DRAW's OM docker creates items like this */
695     if (item->pszText == NULL)
696     {
697         item->textWidth = 0;
698         return;
699     }
700
701     if (item->textWidth != 0 && !(item->callbackMask & TVIF_TEXT))
702        return;
703
704     if (hDC != 0)
705     {
706         hdc = hDC;
707     }
708     else
709     {
710         hdc = GetDC(infoPtr->hwnd);
711         hOldFont = SelectObject(hdc, TREEVIEW_FontForItem(infoPtr, item));
712     }
713
714     GetTextExtentPoint32A(hdc, item->pszText, strlen(item->pszText), &sz);
715     item->textWidth = sz.cx;
716
717     if (hDC == 0)
718     {
719         SelectObject(hdc, hOldFont);
720         ReleaseDC(0, hdc);
721     }
722 }
723
724 static VOID
725 TREEVIEW_ComputeItemRect(TREEVIEW_INFO *infoPtr, TREEVIEW_ITEM *item)
726 {
727     item->rect.top = infoPtr->uItemHeight *
728         (item->visibleOrder - infoPtr->firstVisible->visibleOrder);
729
730     item->rect.bottom = item->rect.top
731         + infoPtr->uItemHeight * item->iIntegral - 1;
732
733     item->rect.left = 0;
734     item->rect.right = infoPtr->clientWidth;
735 }
736
737 /* We know that only items after start need their order updated. */
738 static void
739 TREEVIEW_RecalculateVisibleOrder(TREEVIEW_INFO *infoPtr, TREEVIEW_ITEM *start)
740 {
741     TREEVIEW_ITEM *item;
742     int order;
743
744     if (!start)
745     {
746         start = infoPtr->root->firstChild;
747         order = 0;
748     }
749     else
750         order = start->visibleOrder;
751
752     for (item = start; item != NULL;
753          item = TREEVIEW_GetNextListItem(infoPtr, item))
754     {
755         item->visibleOrder = order;
756         order += item->iIntegral;
757     }
758
759     infoPtr->maxVisibleOrder = order;
760
761     for (item = start; item != NULL;
762          item = TREEVIEW_GetNextListItem(infoPtr, item))
763     {
764         TREEVIEW_ComputeItemRect(infoPtr, item);
765     }
766 }
767
768
769 /* Update metrics of all items in selected subtree.
770  * root must be expanded
771  */
772 static VOID
773 TREEVIEW_UpdateSubTree(TREEVIEW_INFO *infoPtr, TREEVIEW_ITEM *root)
774 {
775    TREEVIEW_ITEM *sibling;
776    HDC hdc;
777    HFONT hOldFont;
778
779    if (!root->firstChild || !(root->state & TVIS_EXPANDED))
780       return;
781
782    root->state &= ~TVIS_EXPANDED;
783    sibling = TREEVIEW_GetNextListItem(infoPtr, root);
784    root->state |= TVIS_EXPANDED;
785
786    hdc = GetDC(infoPtr->hwnd);
787    hOldFont = SelectObject(hdc, infoPtr->hFont);
788
789    for (; root != sibling;
790         root = TREEVIEW_GetNextListItem(infoPtr, root))
791    {
792       TREEVIEW_ComputeItemInternalMetrics(infoPtr, root);
793
794       if (root->callbackMask & TVIF_TEXT)
795          TREEVIEW_UpdateDispInfo(infoPtr, root, TVIF_TEXT);
796
797       if (root->textWidth == 0)
798       {
799          SelectObject(hdc, TREEVIEW_FontForItem(infoPtr, root));
800          TREEVIEW_ComputeTextWidth(infoPtr, root, hdc);
801       }
802    }
803
804    SelectObject(hdc, hOldFont);
805    ReleaseDC(infoPtr->hwnd, hdc);
806 }
807
808 /* Item Allocation **********************************************************/
809
810 static TREEVIEW_ITEM *
811 TREEVIEW_AllocateItem(TREEVIEW_INFO *infoPtr)
812 {
813     TREEVIEW_ITEM *newItem = COMCTL32_Alloc(sizeof(TREEVIEW_ITEM));
814
815     if (!newItem)
816         return NULL;
817
818     if (DPA_InsertPtr(infoPtr->items, INT_MAX, newItem) == -1)
819     {
820         COMCTL32_Free(newItem);
821         return NULL;
822     }
823
824     return newItem;
825 }
826
827 /* Exact opposite of TREEVIEW_AllocateItem. In particular, it does not
828  * free item->pszText. */
829 static void
830 TREEVIEW_FreeItem(TREEVIEW_INFO *infoPtr, TREEVIEW_ITEM *item)
831 {
832     DPA_DeletePtr(infoPtr->items, DPA_GetPtrIndex(infoPtr->items, item));
833     COMCTL32_Free(item);
834 }
835
836
837 /* Item Insertion *******************************************************/
838
839 /***************************************************************************
840  * This method inserts newItem before sibling as a child of parent.
841  * sibling can be NULL, but only if parent has no children.
842  */
843 static void
844 TREEVIEW_InsertBefore(TREEVIEW_ITEM *newItem, TREEVIEW_ITEM *sibling,
845                       TREEVIEW_ITEM *parent)
846 {
847     assert(newItem != NULL);
848     assert(parent != NULL);
849
850     if (sibling != NULL)
851     {
852         assert(sibling->parent == parent);
853
854         if (sibling->prevSibling != NULL)
855             sibling->prevSibling->nextSibling = newItem;
856
857         newItem->prevSibling = sibling->prevSibling;
858         sibling->prevSibling = newItem;
859     }
860     else
861        newItem->prevSibling = NULL;
862
863     newItem->nextSibling = sibling;
864
865     if (parent->firstChild == sibling)
866         parent->firstChild = newItem;
867
868     if (parent->lastChild == NULL)
869         parent->lastChild = newItem;
870 }
871
872 /***************************************************************************
873  * This method inserts newItem after sibling as a child of parent.
874  * sibling can be NULL, but only if parent has no children.
875  */
876 static void
877 TREEVIEW_InsertAfter(TREEVIEW_ITEM *newItem, TREEVIEW_ITEM *sibling,
878                      TREEVIEW_ITEM *parent)
879 {
880     assert(newItem != NULL);
881     assert(parent != NULL);
882
883     if (sibling != NULL)
884     {
885         assert(sibling->parent == parent);
886
887         if (sibling->nextSibling != NULL)
888             sibling->nextSibling->prevSibling = newItem;
889
890         newItem->nextSibling = sibling->nextSibling;
891         sibling->nextSibling = newItem;
892     }
893     else
894        newItem->nextSibling = NULL;
895
896     newItem->prevSibling = sibling;
897
898     if (parent->lastChild == sibling)
899         parent->lastChild = newItem;
900
901     if (parent->firstChild == NULL)
902         parent->firstChild = newItem;
903 }
904
905 static BOOL
906 TREEVIEW_DoSetItem(TREEVIEW_INFO *infoPtr, TREEVIEW_ITEM *wineItem,
907                    const TVITEMEXA *tvItem)
908 {
909     UINT callbackClear = 0;
910     UINT callbackSet = 0;
911
912     /* Do this first in case it fails. */
913     if (tvItem->mask & TVIF_TEXT)
914     {
915         if (tvItem->pszText != LPSTR_TEXTCALLBACKA)
916         {
917             int len = lstrlenA(tvItem->pszText) + 1;
918             LPSTR newText = COMCTL32_ReAlloc(wineItem->pszText, len);
919
920             if (newText == NULL) return FALSE;
921
922             callbackClear |= TVIF_TEXT;
923
924             wineItem->pszText = newText;
925             wineItem->cchTextMax = len;
926             lstrcpynA(wineItem->pszText, tvItem->pszText, len);
927         }
928         else
929         {
930             callbackSet |= TVIF_TEXT;
931
932             wineItem->pszText = COMCTL32_ReAlloc(wineItem->pszText,
933                                                  TEXT_CALLBACK_SIZE);
934             wineItem->cchTextMax = TEXT_CALLBACK_SIZE;
935         }
936     }
937
938     if (tvItem->mask & TVIF_CHILDREN)
939     {
940         wineItem->cChildren = tvItem->cChildren;
941
942         if (wineItem->cChildren == I_CHILDRENCALLBACK)
943             callbackSet |= TVIF_CHILDREN;
944         else
945             callbackClear |= TVIF_CHILDREN;
946     }
947
948     if (tvItem->mask & TVIF_IMAGE)
949     {
950         wineItem->iImage = tvItem->iImage;
951
952         if (wineItem->iImage == I_IMAGECALLBACK)
953             callbackSet |= TVIF_IMAGE;
954         else
955             callbackClear |= TVIF_IMAGE;
956     }
957
958     if (tvItem->mask & TVIF_SELECTEDIMAGE)
959     {
960         wineItem->iSelectedImage = tvItem->iSelectedImage;
961
962         if (wineItem->iSelectedImage == I_IMAGECALLBACK)
963             callbackSet |= TVIF_SELECTEDIMAGE;
964         else
965             callbackClear |= TVIF_SELECTEDIMAGE;
966     }
967
968     if (tvItem->mask & TVIF_PARAM)
969         wineItem->lParam = tvItem->lParam;
970
971     /* If the application sets TVIF_INTEGRAL without
972      * supplying a TVITEMEX structure, it's toast. */
973     if (tvItem->mask & TVIF_INTEGRAL)
974         wineItem->iIntegral = tvItem->iIntegral;
975
976     if (tvItem->mask & TVIF_STATE)
977     {
978         TRACE("prevstate,state,mask:%x,%x,%x\n", wineItem->state, tvItem->state,
979               tvItem->stateMask);
980         wineItem->state &= ~tvItem->stateMask;
981         wineItem->state |= (tvItem->state & tvItem->stateMask);
982     }
983
984     wineItem->callbackMask |= callbackSet;
985     wineItem->callbackMask &= ~callbackClear;
986
987     return TRUE;
988 }
989
990 /* Note that the new item is pre-zeroed. */
991 static LRESULT
992 TREEVIEW_InsertItemA(TREEVIEW_INFO *infoPtr, LPARAM lParam)
993 {
994     const TVINSERTSTRUCTA *ptdi = (LPTVINSERTSTRUCTA) lParam;
995     const TVITEMEXA *tvItem = &ptdi->DUMMYUNIONNAME.itemex;
996     HTREEITEM insertAfter;
997     TREEVIEW_ITEM *newItem, *parentItem;
998     BOOL bTextUpdated = FALSE;
999
1000     if (ptdi->hParent == TVI_ROOT || ptdi->hParent == 0)
1001     {
1002         parentItem = infoPtr->root;
1003     }
1004     else
1005     {
1006         parentItem = ptdi->hParent;
1007
1008         if (!TREEVIEW_ValidItem(infoPtr, parentItem))
1009         {
1010             WARN("invalid parent %p\n", parentItem);
1011             return (LRESULT)(HTREEITEM)NULL;
1012         }
1013     }
1014
1015     insertAfter = ptdi->hInsertAfter;
1016
1017     /* Validate this now for convenience. */
1018     switch ((DWORD)insertAfter)
1019     {
1020     case (DWORD)TVI_FIRST:
1021     case (DWORD)TVI_LAST:
1022     case (DWORD)TVI_SORT:
1023         break;
1024
1025     default:
1026         if (!TREEVIEW_ValidItem(infoPtr, insertAfter) ||
1027             insertAfter->parent != parentItem)
1028         {
1029             WARN("invalid insert after %p\n", insertAfter);
1030             insertAfter = TVI_LAST;
1031         }
1032     }
1033
1034     TRACE("parent %p position %p: '%s'\n", parentItem, insertAfter,
1035           (tvItem->mask & TVIF_TEXT)
1036           ? ((tvItem->pszText == LPSTR_TEXTCALLBACKA) ? "<callback>"
1037              : tvItem->pszText)
1038           : "<no label>");
1039
1040     newItem = TREEVIEW_AllocateItem(infoPtr);
1041     if (newItem == NULL)
1042         return (LRESULT)(HTREEITEM)NULL;
1043
1044     newItem->parent = parentItem;
1045     newItem->iIntegral = 1;
1046
1047     if (!TREEVIEW_DoSetItem(infoPtr, newItem, tvItem))
1048         return (LRESULT)(HTREEITEM)NULL;
1049
1050     /* After this point, nothing can fail. (Except for TVI_SORT.) */
1051
1052     infoPtr->uNumItems++;
1053
1054     switch ((DWORD)insertAfter)
1055     {
1056     case (DWORD)TVI_FIRST:
1057         if (infoPtr->firstVisible == parentItem->firstChild)
1058             TREEVIEW_SetFirstVisible(infoPtr, newItem, TRUE);
1059         TREEVIEW_InsertBefore(newItem, parentItem->firstChild, parentItem);
1060         break;
1061
1062     case (DWORD)TVI_LAST:
1063         TREEVIEW_InsertAfter(newItem, parentItem->lastChild, parentItem);
1064         break;
1065
1066         /* hInsertAfter names a specific item we want to insert after */
1067     default:
1068         TREEVIEW_InsertAfter(newItem, insertAfter, insertAfter->parent);
1069         break;
1070
1071     case (DWORD)TVI_SORT:
1072         {
1073             TREEVIEW_ITEM *aChild;
1074             TREEVIEW_ITEM *previousChild = NULL;
1075             BOOL bItemInserted = FALSE;
1076
1077             aChild = parentItem->firstChild;
1078
1079             bTextUpdated = TRUE;
1080             TREEVIEW_UpdateDispInfo(infoPtr, newItem, TVIF_TEXT);
1081
1082             /* Iterate the parent children to see where we fit in */
1083             while (aChild != NULL)
1084             {
1085                 INT comp;
1086
1087                 TREEVIEW_UpdateDispInfo(infoPtr, aChild, TVIF_TEXT);
1088                 comp = lstrcmpA(newItem->pszText, aChild->pszText);
1089
1090                 if (comp < 0)   /* we are smaller than the current one */
1091                 {
1092                     TREEVIEW_InsertBefore(newItem, aChild, parentItem);
1093                     bItemInserted = TRUE;
1094                     break;
1095                 }
1096                 else if (comp > 0)      /* we are bigger than the current one */
1097                 {
1098                     previousChild = aChild;
1099
1100                     /* This will help us to exit if there is no more sibling */
1101                     aChild = (aChild->nextSibling == 0)
1102                         ? NULL  
1103                         : aChild->nextSibling;
1104
1105                     /* Look at the next item */
1106                     continue;
1107                 }
1108                 else if (comp == 0)
1109                 {
1110                     /* 
1111                      * An item with this name is already existing, therefore,  
1112                      * we add after the one we found 
1113                      */
1114                     TREEVIEW_InsertAfter(newItem, aChild, parentItem);
1115                     bItemInserted = TRUE;
1116                     break;
1117                 }
1118             }
1119
1120             /* 
1121              * we reach the end of the child list and the item has not
1122              * yet been inserted, therefore, insert it after the last child.
1123              */
1124             if ((!bItemInserted) && (aChild == NULL))
1125                 TREEVIEW_InsertAfter(newItem, previousChild, parentItem);
1126
1127             break;
1128         }
1129     }
1130
1131
1132     TRACE("new item %p; parent %p, mask %x\n", newItem,
1133           newItem->parent, tvItem->mask);
1134
1135     newItem->iLevel = newItem->parent->iLevel + 1;
1136
1137     if (newItem->parent->cChildren == 0)
1138         newItem->parent->cChildren = 1;
1139
1140     if (infoPtr->dwStyle & TVS_CHECKBOXES)
1141     {
1142         if (STATEIMAGEINDEX(newItem->state) == 0)
1143             newItem->state |= INDEXTOSTATEIMAGEMASK(1);
1144     }
1145
1146     if (infoPtr->firstVisible == NULL)
1147         infoPtr->firstVisible = newItem;
1148
1149     TREEVIEW_VerifyTree(infoPtr);
1150
1151     if (parentItem == infoPtr->root ||
1152         (ISVISIBLE(parentItem) && parentItem->state & TVIS_EXPANDED))
1153     {
1154        TREEVIEW_ITEM *item;
1155        TREEVIEW_ITEM *prev = TREEVIEW_GetPrevListItem(infoPtr, newItem);
1156
1157        TREEVIEW_RecalculateVisibleOrder(infoPtr, prev);
1158        TREEVIEW_ComputeItemInternalMetrics(infoPtr, newItem);
1159
1160        if (!bTextUpdated)
1161           TREEVIEW_UpdateDispInfo(infoPtr, newItem, TVIF_TEXT);
1162
1163        TREEVIEW_ComputeTextWidth(infoPtr, newItem, 0);
1164        TREEVIEW_UpdateScrollBars(infoPtr);
1165     /*
1166      * if the item was inserted in a visible part of the tree, 
1167      * invalidate it, as well as those after it
1168      */
1169        for (item = newItem;
1170             item != NULL;
1171             item = TREEVIEW_GetNextListItem(infoPtr, item))
1172           TREEVIEW_Invalidate(infoPtr, item);
1173     }
1174     else
1175     {
1176        newItem->visibleOrder = -1;
1177
1178        /* refresh treeview if newItem is the first item inserted under parentItem */
1179        if (ISVISIBLE(parentItem) && newItem->prevSibling == newItem->nextSibling)
1180        {
1181           /* parent got '+' - update it */
1182           TREEVIEW_Invalidate(infoPtr, parentItem);
1183        }
1184     }
1185
1186     return (LRESULT)newItem;
1187 }
1188
1189
1190 static LRESULT
1191 TREEVIEW_InsertItemW(TREEVIEW_INFO *infoPtr, LPARAM lParam)
1192 {
1193     TVINSERTSTRUCTW *tvisW;
1194     TVINSERTSTRUCTA tvisA;
1195     LRESULT lRes;
1196
1197     tvisW = (LPTVINSERTSTRUCTW) lParam;
1198
1199     tvisA.hParent = tvisW->hParent;
1200     tvisA.hInsertAfter = tvisW->hInsertAfter;
1201
1202     tvisA.DUMMYUNIONNAME.item.mask = tvisW->DUMMYUNIONNAME.item.mask;
1203     tvisA.DUMMYUNIONNAME.item.hItem = tvisW->DUMMYUNIONNAME.item.hItem;
1204     tvisA.DUMMYUNIONNAME.item.state = tvisW->DUMMYUNIONNAME.item.state;
1205     tvisA.DUMMYUNIONNAME.item.stateMask = tvisW->DUMMYUNIONNAME.item.stateMask;
1206     tvisA.DUMMYUNIONNAME.item.cchTextMax =
1207         tvisW->DUMMYUNIONNAME.item.cchTextMax;
1208
1209     if (tvisW->DUMMYUNIONNAME.item.pszText)
1210     {
1211         if (tvisW->DUMMYUNIONNAME.item.pszText != LPSTR_TEXTCALLBACKW)
1212         {
1213             int len = lstrlenW(tvisW->DUMMYUNIONNAME.item.pszText) + 1;
1214
1215             tvisA.DUMMYUNIONNAME.item.pszText = COMCTL32_Alloc(len);
1216             lstrcpyWtoA(tvisA.DUMMYUNIONNAME.item.pszText,
1217                         tvisW->DUMMYUNIONNAME.item.pszText);
1218         }
1219         else
1220         {
1221             tvisA.DUMMYUNIONNAME.item.pszText = LPSTR_TEXTCALLBACKA;
1222             tvisA.DUMMYUNIONNAME.item.cchTextMax = 0;
1223         }
1224     }
1225
1226     tvisA.DUMMYUNIONNAME.item.iImage = tvisW->DUMMYUNIONNAME.item.iImage;
1227     tvisA.DUMMYUNIONNAME.item.iSelectedImage =
1228         tvisW->DUMMYUNIONNAME.item.iSelectedImage;
1229     tvisA.DUMMYUNIONNAME.item.cChildren = tvisW->DUMMYUNIONNAME.item.cChildren;
1230     tvisA.DUMMYUNIONNAME.item.lParam = tvisW->DUMMYUNIONNAME.item.lParam;
1231
1232     lRes = TREEVIEW_InsertItemA(infoPtr, (LPARAM)&tvisA);
1233
1234     if (tvisA.DUMMYUNIONNAME.item.pszText != LPSTR_TEXTCALLBACKA)
1235     {
1236         COMCTL32_Free(tvisA.DUMMYUNIONNAME.item.pszText);
1237     }
1238
1239     return lRes;
1240
1241 }
1242
1243
1244 /* Item Deletion ************************************************************/
1245 static void
1246 TREEVIEW_RemoveItem(TREEVIEW_INFO *infoPtr, TREEVIEW_ITEM *wineItem);
1247
1248 static void
1249 TREEVIEW_RemoveAllChildren(TREEVIEW_INFO *infoPtr, TREEVIEW_ITEM *parentItem)
1250 {
1251     TREEVIEW_ITEM *kill = parentItem->firstChild;
1252
1253     while (kill != NULL)
1254     {
1255         TREEVIEW_ITEM *next = kill->nextSibling;
1256
1257         TREEVIEW_RemoveItem(infoPtr, kill);
1258
1259         kill = next;
1260     }
1261
1262     assert(parentItem->cChildren <= 0); /* I_CHILDRENCALLBACK or 0 */
1263     assert(parentItem->firstChild == NULL);
1264     assert(parentItem->lastChild == NULL);
1265 }
1266
1267 static void
1268 TREEVIEW_UnlinkItem(TREEVIEW_ITEM *item)
1269 {
1270     TREEVIEW_ITEM *parentItem = item->parent;
1271
1272     assert(item != NULL);
1273     assert(item->parent != NULL); /* i.e. it must not be the root */
1274
1275     if (parentItem->firstChild == item)
1276         parentItem->firstChild = item->nextSibling;
1277
1278     if (parentItem->lastChild == item)
1279         parentItem->lastChild = item->prevSibling;
1280
1281     if (parentItem->firstChild == NULL && parentItem->lastChild == NULL
1282         && parentItem->cChildren > 0)
1283         parentItem->cChildren = 0;
1284
1285     if (item->prevSibling)
1286         item->prevSibling->nextSibling = item->nextSibling;
1287
1288     if (item->nextSibling)
1289         item->nextSibling->prevSibling = item->prevSibling;
1290 }
1291
1292 static void
1293 TREEVIEW_RemoveItem(TREEVIEW_INFO *infoPtr, TREEVIEW_ITEM *wineItem)
1294 {
1295     TRACE("%p, (%s)\n", wineItem, TREEVIEW_ItemName(wineItem));
1296
1297     TREEVIEW_SendTreeviewNotify(infoPtr, TVN_DELETEITEMA,
1298                                 TVIF_HANDLE | TVIF_PARAM, 0, wineItem, 0);
1299
1300     if (wineItem->firstChild)
1301         TREEVIEW_RemoveAllChildren(infoPtr, wineItem);
1302
1303     TREEVIEW_UnlinkItem(wineItem);
1304
1305     infoPtr->uNumItems--;
1306
1307     if (wineItem->pszText != LPSTR_TEXTCALLBACKA)
1308         COMCTL32_Free(wineItem->pszText);
1309
1310     TREEVIEW_FreeItem(infoPtr, wineItem);
1311 }
1312
1313
1314 /* Empty out the tree. */
1315 static void
1316 TREEVIEW_RemoveTree(TREEVIEW_INFO *infoPtr)
1317 {
1318     TREEVIEW_RemoveAllChildren(infoPtr, infoPtr->root);
1319
1320     assert(infoPtr->uNumItems == 0);    /* root isn't counted in uNumItems */
1321 }
1322
1323 static LRESULT
1324 TREEVIEW_DeleteItem(TREEVIEW_INFO *infoPtr, HTREEITEM wineItem)
1325 {
1326     TREEVIEW_ITEM *oldSelection = infoPtr->selectedItem;
1327     TREEVIEW_ITEM *newSelection = oldSelection;
1328     TREEVIEW_ITEM *newFirstVisible = NULL;
1329     TREEVIEW_ITEM *parent, *prev = NULL;
1330     BOOL visible = FALSE;
1331
1332     if (wineItem == TVI_ROOT)
1333     {
1334         TRACE("TVI_ROOT\n");
1335         parent = infoPtr->root;
1336         newSelection = NULL;
1337         visible = TRUE;
1338         TREEVIEW_RemoveTree(infoPtr);
1339     }
1340     else
1341     {
1342         if (!TREEVIEW_ValidItem(infoPtr, wineItem))
1343             return FALSE;
1344
1345         TRACE("%p (%s)\n", wineItem, TREEVIEW_ItemName(wineItem));
1346         parent = wineItem->parent;
1347
1348         if (ISVISIBLE(wineItem))
1349         {
1350             prev = TREEVIEW_GetPrevListItem(infoPtr, wineItem);
1351             visible = TRUE;
1352         }
1353
1354         if (infoPtr->selectedItem != NULL
1355             && (wineItem == infoPtr->selectedItem
1356                 || TREEVIEW_IsChildOf(wineItem, infoPtr->selectedItem)))
1357         {
1358             if (wineItem->nextSibling)
1359                 newSelection = wineItem->nextSibling;
1360             else if (wineItem->parent != infoPtr->root)
1361                 newSelection = wineItem->parent;
1362         }
1363
1364         if (infoPtr->firstVisible == wineItem)
1365         {
1366             if (wineItem->nextSibling)
1367                newFirstVisible = wineItem->nextSibling;
1368             else if (wineItem->prevSibling)
1369                newFirstVisible = wineItem->prevSibling;
1370             else if (wineItem->parent != infoPtr->root)
1371                newFirstVisible = wineItem->parent;
1372         }
1373         else
1374             newFirstVisible = infoPtr->firstVisible;
1375
1376         TREEVIEW_RemoveItem(infoPtr, wineItem);
1377     }
1378
1379     /* Don't change if somebody else already has. */
1380     if (oldSelection == infoPtr->selectedItem)
1381     {
1382         if (TREEVIEW_ValidItem(infoPtr, newSelection))
1383             TREEVIEW_DoSelectItem(infoPtr, TVGN_CARET, newSelection, TVC_UNKNOWN);
1384         else
1385             infoPtr->selectedItem = 0;
1386     }
1387
1388     /* Validate insertMark dropItem.
1389      * hotItem ??? - used for comparison only.
1390      */
1391     if (!TREEVIEW_ValidItem(infoPtr, infoPtr->insertMarkItem))
1392         infoPtr->insertMarkItem = 0;
1393
1394     if (!TREEVIEW_ValidItem(infoPtr, infoPtr->dropItem))
1395         infoPtr->dropItem = 0;
1396
1397     if (!TREEVIEW_ValidItem(infoPtr, newFirstVisible))
1398         newFirstVisible = infoPtr->root->firstChild;
1399
1400     TREEVIEW_VerifyTree(infoPtr);
1401
1402
1403     if (visible)
1404     {
1405        TREEVIEW_RecalculateVisibleOrder(infoPtr, prev);
1406        TREEVIEW_SetFirstVisible(infoPtr, newFirstVisible, TRUE);
1407        TREEVIEW_UpdateScrollBars(infoPtr);
1408        TREEVIEW_Invalidate(infoPtr, NULL);
1409     }
1410     else if (ISVISIBLE(parent) && !TREEVIEW_HasChildren(infoPtr, parent))
1411     {
1412        /* parent lost '+/-' - update it */
1413        TREEVIEW_Invalidate(infoPtr, parent);
1414     }
1415
1416     return TRUE;
1417 }
1418
1419
1420 /* Get/Set Messages *********************************************************/
1421 static LRESULT
1422 TREEVIEW_SetRedraw(TREEVIEW_INFO* infoPtr, WPARAM wParam, LPARAM lParam)
1423 {
1424   if(wParam)
1425     infoPtr->bRedraw = TRUE;
1426   else
1427     infoPtr->bRedraw = FALSE;
1428
1429   return 0;
1430 }
1431
1432 static LRESULT
1433 TREEVIEW_GetIndent(TREEVIEW_INFO *infoPtr)
1434 {
1435     TRACE("\n");
1436     return infoPtr->uIndent;
1437 }
1438
1439 static LRESULT
1440 TREEVIEW_SetIndent(TREEVIEW_INFO *infoPtr, UINT newIndent)
1441 {
1442     TRACE("\n");
1443
1444     if (newIndent < MINIMUM_INDENT)
1445         newIndent = MINIMUM_INDENT;
1446
1447     if (infoPtr->uIndent != newIndent)
1448     {
1449         infoPtr->uIndent = newIndent;
1450         TREEVIEW_UpdateSubTree(infoPtr, infoPtr->root);
1451         TREEVIEW_UpdateScrollBars(infoPtr);
1452         TREEVIEW_Invalidate(infoPtr, NULL);
1453     }
1454
1455     return 0;
1456 }
1457
1458
1459 static LRESULT
1460 TREEVIEW_GetToolTips(TREEVIEW_INFO *infoPtr)
1461 {
1462     TRACE("\n");
1463     return infoPtr->hwndToolTip;
1464 }
1465
1466 static LRESULT
1467 TREEVIEW_SetToolTips(TREEVIEW_INFO *infoPtr, HWND hwndTT)
1468 {
1469     HWND prevToolTip;
1470
1471     TRACE("\n");
1472     prevToolTip = infoPtr->hwndToolTip;
1473     infoPtr->hwndToolTip = hwndTT;
1474
1475     return prevToolTip;
1476 }
1477
1478
1479 static LRESULT
1480 TREEVIEW_GetScrollTime(TREEVIEW_INFO *infoPtr)
1481 {
1482     return infoPtr->uScrollTime;
1483 }
1484
1485 static LRESULT
1486 TREEVIEW_SetScrollTime(TREEVIEW_INFO *infoPtr, UINT uScrollTime)
1487 {
1488     UINT uOldScrollTime = infoPtr->uScrollTime;
1489
1490     infoPtr->uScrollTime = min(uScrollTime, 100);
1491
1492     return uOldScrollTime;
1493 }
1494
1495
1496 static LRESULT
1497 TREEVIEW_GetImageList(TREEVIEW_INFO *infoPtr, WPARAM wParam)
1498 {
1499     TRACE("\n");
1500
1501     switch (wParam)
1502     {
1503     case (WPARAM)TVSIL_NORMAL:
1504         return (LRESULT)infoPtr->himlNormal;
1505
1506     case (WPARAM)TVSIL_STATE:
1507         return (LRESULT)infoPtr->himlState;
1508
1509     default:
1510         return 0;
1511     }
1512 }
1513
1514 static LRESULT
1515 TREEVIEW_SetImageList(TREEVIEW_INFO *infoPtr, WPARAM wParam, HIMAGELIST himlNew)
1516 {
1517     HIMAGELIST himlOld = 0;
1518     int oldWidth  = infoPtr->normalImageWidth;
1519     int oldHeight = infoPtr->normalImageHeight;
1520
1521
1522     TRACE("%x,%p\n", wParam, himlNew);
1523
1524     switch (wParam)
1525     {
1526     case (WPARAM)TVSIL_NORMAL:
1527         himlOld = infoPtr->himlNormal;
1528         infoPtr->himlNormal = himlNew;
1529
1530         if (himlNew != NULL)
1531             ImageList_GetIconSize(himlNew, &infoPtr->normalImageWidth,
1532                                   &infoPtr->normalImageHeight);
1533         else
1534         {
1535             infoPtr->normalImageWidth = 0;
1536             infoPtr->normalImageHeight = 0;
1537         }
1538
1539         break;
1540
1541     case (WPARAM)TVSIL_STATE:
1542         himlOld = infoPtr->himlState;
1543         infoPtr->himlState = himlNew;
1544
1545         if (himlNew != NULL)
1546             ImageList_GetIconSize(himlNew, &infoPtr->stateImageWidth,
1547                                   &infoPtr->stateImageHeight);
1548         else
1549         {
1550             infoPtr->stateImageWidth = 0;
1551             infoPtr->stateImageHeight = 0;
1552         }
1553
1554         break;
1555     }
1556
1557     if (oldWidth != infoPtr->normalImageWidth ||
1558         oldHeight != infoPtr->normalImageHeight)
1559     {
1560        TREEVIEW_UpdateSubTree(infoPtr, infoPtr->root);
1561        TREEVIEW_UpdateScrollBars(infoPtr);
1562     }
1563
1564     TREEVIEW_Invalidate(infoPtr, NULL);
1565
1566     return (LRESULT)himlOld;
1567 }
1568
1569 /* Compute the natural height (based on the font size) for items. */
1570 static UINT
1571 TREEVIEW_NaturalHeight(TREEVIEW_INFO *infoPtr)
1572 {
1573     TEXTMETRICA tm;
1574     HDC hdc = GetDC(0);
1575     HFONT hOldFont = SelectObject(hdc, infoPtr->hFont);
1576
1577     GetTextMetricsA(hdc, &tm);
1578
1579     SelectObject(hdc, hOldFont);
1580     ReleaseDC(0, hdc);
1581
1582     /* The 16 is a hack because our fonts are tiny. */
1583     /* add 2 for the focus border and 1 more for margin some apps assume */
1584     return max(16, tm.tmHeight + tm.tmExternalLeading + 3);
1585 }
1586
1587 static LRESULT
1588 TREEVIEW_SetItemHeight(TREEVIEW_INFO *infoPtr, INT newHeight)
1589 {
1590     INT prevHeight = infoPtr->uItemHeight;
1591
1592     TRACE("%d \n", newHeight);
1593     if (newHeight == -1)
1594     {
1595         infoPtr->uItemHeight = TREEVIEW_NaturalHeight(infoPtr);
1596         infoPtr->bHeightSet = FALSE;
1597     }
1598     else
1599     {
1600         infoPtr->uItemHeight = newHeight;
1601         infoPtr->bHeightSet = TRUE;
1602     }
1603
1604     /* Round down, unless we support odd ("non even") heights. */
1605     if (!(infoPtr->dwStyle) & TVS_NONEVENHEIGHT)
1606         infoPtr->uItemHeight &= ~1;
1607
1608     if (infoPtr->uItemHeight != prevHeight)
1609     {
1610         TREEVIEW_RecalculateVisibleOrder(infoPtr, NULL);
1611         TREEVIEW_UpdateScrollBars(infoPtr);
1612         TREEVIEW_Invalidate(infoPtr, NULL);
1613     }
1614
1615     return prevHeight;
1616 }
1617
1618 static LRESULT
1619 TREEVIEW_GetItemHeight(TREEVIEW_INFO *infoPtr)
1620 {
1621     TRACE("\n");
1622     return infoPtr->uItemHeight;
1623 }
1624
1625
1626 static LRESULT
1627 TREEVIEW_GetFont(TREEVIEW_INFO *infoPtr)
1628 {
1629     TRACE("%x\n", infoPtr->hFont);
1630     return infoPtr->hFont;
1631 }
1632
1633
1634 static INT CALLBACK
1635 TREEVIEW_ResetTextWidth(LPVOID pItem, DWORD unused)
1636 {
1637     (void)unused;
1638
1639     ((TREEVIEW_ITEM *)pItem)->textWidth = 0;
1640
1641     return 1;
1642 }
1643
1644 static LRESULT
1645 TREEVIEW_SetFont(TREEVIEW_INFO *infoPtr, HFONT hFont, BOOL bRedraw)
1646 {
1647     UINT uHeight = infoPtr->uItemHeight;
1648
1649     TRACE("%x %i\n", hFont, bRedraw);
1650
1651     infoPtr->hFont = hFont ? hFont : GetStockObject(SYSTEM_FONT);
1652
1653     DeleteObject(infoPtr->hBoldFont);
1654     infoPtr->hBoldFont = TREEVIEW_CreateBoldFont(infoPtr->hFont);
1655
1656     if (!infoPtr->bHeightSet)
1657         infoPtr->uItemHeight = TREEVIEW_NaturalHeight(infoPtr);
1658
1659     if (uHeight != infoPtr->uItemHeight)
1660        TREEVIEW_RecalculateVisibleOrder(infoPtr, NULL);
1661
1662     DPA_EnumCallback(infoPtr->items, TREEVIEW_ResetTextWidth, 0);
1663
1664     TREEVIEW_UpdateSubTree(infoPtr, infoPtr->root);
1665     TREEVIEW_UpdateScrollBars(infoPtr);
1666
1667     if (bRedraw)
1668         TREEVIEW_Invalidate(infoPtr, NULL);
1669
1670     return 0;
1671 }
1672
1673
1674 static LRESULT
1675 TREEVIEW_GetLineColor(TREEVIEW_INFO *infoPtr)
1676 {
1677     TRACE("\n");
1678     return (LRESULT)infoPtr->clrLine;
1679 }
1680
1681 static LRESULT
1682 TREEVIEW_SetLineColor(TREEVIEW_INFO *infoPtr, COLORREF color)
1683 {
1684     COLORREF prevColor = infoPtr->clrLine;
1685
1686     TRACE("\n");
1687     infoPtr->clrLine = color;
1688     return (LRESULT)prevColor;
1689 }
1690
1691
1692 static LRESULT
1693 TREEVIEW_GetTextColor(TREEVIEW_INFO *infoPtr)
1694 {
1695     TRACE("\n");
1696     return (LRESULT)infoPtr->clrText;
1697 }
1698
1699 static LRESULT
1700 TREEVIEW_SetTextColor(TREEVIEW_INFO *infoPtr, COLORREF color)
1701 {
1702     COLORREF prevColor = infoPtr->clrText;
1703
1704     TRACE("\n");
1705     infoPtr->clrText = color;
1706
1707     if (infoPtr->clrText != prevColor)
1708         TREEVIEW_Invalidate(infoPtr, NULL);
1709
1710     return (LRESULT)prevColor;
1711 }
1712
1713
1714 static LRESULT
1715 TREEVIEW_GetBkColor(TREEVIEW_INFO *infoPtr)
1716 {
1717     TRACE("\n");
1718     return (LRESULT)infoPtr->clrBk;
1719 }
1720
1721 static LRESULT
1722 TREEVIEW_SetBkColor(TREEVIEW_INFO *infoPtr, COLORREF newColor)
1723 {
1724     COLORREF prevColor = infoPtr->clrBk;
1725
1726     TRACE("\n");
1727     infoPtr->clrBk = newColor;
1728
1729     if (newColor != prevColor)
1730         TREEVIEW_Invalidate(infoPtr, NULL);
1731
1732     return (LRESULT)prevColor;
1733 }
1734
1735
1736 static LRESULT
1737 TREEVIEW_GetInsertMarkColor(TREEVIEW_INFO *infoPtr)
1738 {
1739     TRACE("\n");
1740     return (LRESULT)infoPtr->clrInsertMark;
1741 }
1742
1743 static LRESULT
1744 TREEVIEW_SetInsertMarkColor(TREEVIEW_INFO *infoPtr, COLORREF color)
1745 {
1746     COLORREF prevColor = infoPtr->clrInsertMark;
1747
1748     TRACE("%lx\n", color);
1749     infoPtr->clrInsertMark = color;
1750
1751     return (LRESULT)prevColor;
1752 }
1753
1754
1755 static LRESULT
1756 TREEVIEW_SetInsertMark(TREEVIEW_INFO *infoPtr, BOOL wParam, HTREEITEM item)
1757 {
1758     TRACE("%d %p\n", wParam, item);
1759
1760     if (!TREEVIEW_ValidItem(infoPtr, item))
1761         return 0;
1762
1763     infoPtr->insertBeforeorAfter = wParam;
1764     infoPtr->insertMarkItem = item;
1765
1766     TREEVIEW_Invalidate(infoPtr, NULL);
1767
1768     return 1;
1769 }
1770
1771
1772 /************************************************************************
1773  * Some serious braindamage here. lParam is a pointer to both the
1774  * input HTREEITEM and the output RECT.
1775  */
1776 static LRESULT
1777 TREEVIEW_GetItemRect(TREEVIEW_INFO *infoPtr, BOOL fTextRect, LPRECT lpRect)
1778 {
1779     TREEVIEW_ITEM *wineItem;
1780     const HTREEITEM *pItem = (HTREEITEM *)lpRect;
1781
1782     TRACE("\n");
1783     /*
1784      * validate parameters
1785      */
1786     if (pItem == NULL)
1787         return FALSE;
1788
1789     wineItem = *pItem;
1790     if (!TREEVIEW_ValidItem(infoPtr, wineItem) || !ISVISIBLE(wineItem))
1791         return FALSE;
1792
1793     /* 
1794      * If wParam is TRUE return the text size otherwise return 
1795      * the whole item size        
1796      */
1797     if (fTextRect)
1798     {
1799         /* Windows does not send TVN_GETDISPINFO here. */
1800
1801         lpRect->top = wineItem->rect.top;
1802         lpRect->bottom = wineItem->rect.bottom;
1803
1804         lpRect->left = wineItem->textOffset;
1805         lpRect->right = wineItem->textOffset + wineItem->textWidth;
1806     }
1807     else
1808     {
1809         *lpRect = wineItem->rect;
1810     }
1811
1812     TRACE("%s [L:%d R:%d T:%d B:%d]\n", fTextRect ? "text" : "item",
1813           lpRect->left, lpRect->right, lpRect->top, lpRect->bottom);
1814
1815     return TRUE;
1816 }
1817
1818 static inline LRESULT
1819 TREEVIEW_GetVisibleCount(TREEVIEW_INFO *infoPtr)
1820 {
1821     /* Suprise! This does not take integral height into account. */
1822     return infoPtr->clientHeight / infoPtr->uItemHeight;
1823 }
1824
1825
1826 static LRESULT
1827 TREEVIEW_GetItemA(TREEVIEW_INFO *infoPtr, LPTVITEMEXA tvItem)
1828 {
1829     TREEVIEW_ITEM *wineItem;
1830
1831     wineItem = tvItem->hItem;
1832     if (!TREEVIEW_ValidItem(infoPtr, wineItem))
1833         return FALSE;
1834
1835     TREEVIEW_UpdateDispInfo(infoPtr, wineItem, tvItem->mask);
1836
1837     if (tvItem->mask & TVIF_CHILDREN)
1838         tvItem->cChildren = wineItem->cChildren;
1839
1840     if (tvItem->mask & TVIF_HANDLE)
1841         tvItem->hItem = wineItem;
1842
1843     if (tvItem->mask & TVIF_IMAGE)
1844         tvItem->iImage = wineItem->iImage;
1845
1846     if (tvItem->mask & TVIF_INTEGRAL)
1847         tvItem->iIntegral = wineItem->iIntegral;
1848
1849     /* undocumented: windows ignores TVIF_PARAM and
1850      * * always sets lParam
1851      */
1852     tvItem->lParam = wineItem->lParam;
1853
1854     if (tvItem->mask & TVIF_SELECTEDIMAGE)
1855         tvItem->iSelectedImage = wineItem->iSelectedImage;
1856
1857     if (tvItem->mask & TVIF_STATE)
1858         tvItem->state = wineItem->state & tvItem->stateMask;
1859
1860     if (tvItem->mask & TVIF_TEXT)
1861         lstrcpynA(tvItem->pszText, wineItem->pszText, tvItem->cchTextMax);
1862
1863     TRACE("item <%p>, txt %p, img %p, mask %x\n",
1864           wineItem, tvItem->pszText, &tvItem->iImage, tvItem->mask);
1865
1866     return TRUE;
1867 }
1868
1869 /* Beware MSDN Library Visual Studio 6.0. It says -1 on failure, 0 on success,
1870  * which is wrong. */
1871 static LRESULT
1872 TREEVIEW_SetItemA(TREEVIEW_INFO *infoPtr, LPTVITEMEXA tvItem)
1873 {
1874     TREEVIEW_ITEM *wineItem;
1875     TREEVIEW_ITEM originalItem;
1876
1877     wineItem = tvItem->hItem;
1878
1879     TRACE("item %d,mask %x\n", TREEVIEW_GetItemIndex(infoPtr, wineItem),
1880           tvItem->mask);
1881
1882     if (!TREEVIEW_ValidItem(infoPtr, wineItem))
1883         return FALSE;
1884
1885     if (!TREEVIEW_DoSetItem(infoPtr, wineItem, tvItem))
1886         return FALSE;
1887
1888     /* store the orignal item values */
1889     originalItem = *wineItem;
1890
1891     /* If the text or TVIS_BOLD was changed, and it is visible, recalculate. */
1892     if ((tvItem->mask & TVIF_TEXT
1893          || (tvItem->mask & TVIF_STATE && tvItem->stateMask & TVIS_BOLD))
1894         && ISVISIBLE(wineItem))
1895     {
1896         TREEVIEW_UpdateDispInfo(infoPtr, wineItem, TVIF_TEXT);
1897         TREEVIEW_ComputeTextWidth(infoPtr, wineItem, 0);
1898     }
1899
1900     if (tvItem->mask != 0 && ISVISIBLE(wineItem))
1901     {
1902         /* The refresh updates everything, but we can't wait until then. */
1903         TREEVIEW_ComputeItemInternalMetrics(infoPtr, wineItem);
1904
1905         /* if any of the items values changed, redraw the item */
1906         if(memcmp(&originalItem, wineItem, sizeof(TREEVIEW_ITEM)))
1907         {
1908             if (tvItem->mask & TVIF_INTEGRAL)
1909             {
1910                 TREEVIEW_RecalculateVisibleOrder(infoPtr, wineItem);
1911                 TREEVIEW_UpdateScrollBars(infoPtr);
1912
1913                 TREEVIEW_Invalidate(infoPtr, NULL);
1914             }
1915             else
1916             {
1917                 TREEVIEW_UpdateScrollBars(infoPtr);
1918                 TREEVIEW_Invalidate(infoPtr, wineItem);
1919             }
1920         }
1921     }
1922
1923     return TRUE;
1924 }
1925
1926 static LRESULT
1927 TREEVIEW_GetItemW(TREEVIEW_INFO *infoPtr, LPTVITEMEXA tvItem)
1928 {
1929     TREEVIEW_ITEM *wineItem;
1930     INT         iItem;
1931     iItem = (INT)tvItem->hItem;
1932
1933     wineItem = tvItem->hItem;
1934     if(!TREEVIEW_ValidItem (infoPtr, wineItem))
1935         return FALSE;
1936
1937     TREEVIEW_UpdateDispInfo(infoPtr, wineItem, tvItem->mask);
1938
1939     if (tvItem->mask & TVIF_CHILDREN) {
1940         if (TVIF_CHILDREN==I_CHILDRENCALLBACK)
1941             FIXME("I_CHILDRENCALLBACK not supported\n");
1942         tvItem->cChildren = wineItem->cChildren;
1943     }
1944
1945     if (tvItem->mask & TVIF_HANDLE) {
1946         tvItem->hItem = wineItem;
1947     }
1948     if (tvItem->mask & TVIF_IMAGE) {
1949         tvItem->iImage = wineItem->iImage;
1950     }
1951     if (tvItem->mask & TVIF_INTEGRAL) {
1952         tvItem->iIntegral = wineItem->iIntegral;
1953     }
1954     /* undocumented: windows ignores TVIF_PARAM and
1955      * always sets lParam           */
1956     tvItem->lParam = wineItem->lParam;
1957     if (tvItem->mask & TVIF_SELECTEDIMAGE) {
1958         tvItem->iSelectedImage = wineItem->iSelectedImage;
1959     }
1960     if (tvItem->mask & TVIF_STATE) {
1961         tvItem->state = wineItem->state & tvItem->stateMask;
1962     }
1963 #if 0
1964     if (tvItem->mask & TVIF_TEXT) {
1965         if (wineItem->pszText == LPSTR_TEXTCALLBACKW) {
1966             tvItem->pszText = LPSTR_TEXTCALLBACKW;  /* FIXME:send notification? */
1967             ERR(" GetItem called with LPSTR_TEXTCALLBACK\n");
1968         }
1969         else if (wineItem->pszText) {
1970             lstrcpynAtoW(tvItem->pszText, wineItem->pszText, tvItem->cchTextMax);
1971         }
1972     }
1973 #endif
1974     wineItem->pszText = NULL;
1975     TRACE("item %d<%p>, txt %p, img %p, action %x\n",
1976         iItem, tvItem, tvItem->pszText, &tvItem->iImage, tvItem->mask);
1977     return TRUE;
1978 }
1979
1980 static LRESULT
1981 TREEVIEW_GetItemState(TREEVIEW_INFO *infoPtr, HTREEITEM wineItem, UINT mask)
1982 {
1983     TRACE("\n");
1984
1985     if (!wineItem || !TREEVIEW_ValidItem(infoPtr, wineItem))
1986         return 0;
1987
1988     return (wineItem->state & mask);
1989 }
1990
1991 static LRESULT
1992 TREEVIEW_GetNextItem(TREEVIEW_INFO *infoPtr, UINT which, HTREEITEM wineItem)
1993 {
1994     TREEVIEW_ITEM *retval;
1995
1996     retval = 0;
1997
1998     /* handle all the global data here */
1999     switch (which)
2000     {
2001     case TVGN_CHILD:            /* Special case: child of 0 is root */
2002         if (wineItem)
2003             break;
2004         /* fall through */
2005     case TVGN_ROOT:
2006         retval = infoPtr->root->firstChild;
2007         break;
2008
2009     case TVGN_CARET:
2010         retval = infoPtr->selectedItem;
2011         break;
2012
2013     case TVGN_FIRSTVISIBLE:
2014         retval = infoPtr->firstVisible;
2015         break;
2016
2017     case TVGN_DROPHILITE:
2018         retval = infoPtr->dropItem;
2019         break;
2020
2021     case TVGN_LASTVISIBLE:
2022         retval = TREEVIEW_GetLastListItem(infoPtr, infoPtr->root);
2023         break;
2024     }
2025
2026     if (retval)
2027     {
2028         TRACE("flags:%x, returns %p\n", which, retval);
2029         return (LRESULT)retval;
2030     }
2031
2032     if (!TREEVIEW_ValidItem(infoPtr, wineItem))
2033         return FALSE;
2034
2035     switch (which)
2036     {
2037     case TVGN_NEXT:
2038         retval = wineItem->nextSibling;
2039         break;
2040     case TVGN_PREVIOUS:
2041         retval = wineItem->prevSibling;
2042         break;
2043     case TVGN_PARENT:
2044         retval = (wineItem->parent != infoPtr->root) ? wineItem->parent : NULL;
2045         break;
2046     case TVGN_CHILD:
2047         retval = wineItem->firstChild;
2048         break;
2049     case TVGN_NEXTVISIBLE:
2050         retval = TREEVIEW_GetNextListItem(infoPtr, wineItem);
2051         break;
2052     case TVGN_PREVIOUSVISIBLE:
2053         retval = TREEVIEW_GetPrevListItem(infoPtr, wineItem);
2054         break;
2055     default:
2056         TRACE("Unknown msg %x,item %p\n", which, wineItem);
2057         break;
2058     }
2059
2060     TRACE("flags:%x, item %p;returns %p\n", which, wineItem, retval);
2061     return (LRESULT)retval;
2062 }
2063
2064
2065 static LRESULT
2066 TREEVIEW_GetCount(TREEVIEW_INFO *infoPtr)
2067 {
2068     TRACE(" %d\n", infoPtr->uNumItems);
2069     return (LRESULT)infoPtr->uNumItems;
2070 }
2071
2072 static VOID
2073 TREEVIEW_ToggleItemState(TREEVIEW_INFO *infoPtr, TREEVIEW_ITEM *item)
2074 {
2075     if (infoPtr->dwStyle & TVS_CHECKBOXES)
2076     {
2077         static const unsigned int state_table[] = { 0, 2, 1 };
2078
2079         unsigned int state;
2080
2081         state = STATEIMAGEINDEX(item->state);
2082         TRACE("state:%x\n", state);
2083         item->state &= ~TVIS_STATEIMAGEMASK;
2084
2085         if (state < 3)
2086             state = state_table[state];
2087
2088         item->state |= INDEXTOSTATEIMAGEMASK(state);
2089
2090         TRACE("state:%x\n", state);
2091         TREEVIEW_Invalidate(infoPtr, item);
2092     }
2093 }
2094
2095
2096 /* Painting *************************************************************/
2097
2098 /* Draw the lines and expand button for an item. Also draws one section
2099  * of the line from item's parent to item's parent's next sibling. */
2100 static void
2101 TREEVIEW_DrawItemLines(TREEVIEW_INFO *infoPtr, HDC hdc, TREEVIEW_ITEM *item)
2102 {
2103     LONG centerx, centery;
2104     BOOL lar = ((infoPtr->dwStyle
2105                  & (TVS_LINESATROOT|TVS_HASLINES|TVS_HASBUTTONS))
2106                 > TVS_LINESATROOT);
2107
2108     if (!lar && item->iLevel == 0)
2109         return;
2110
2111     centerx = (item->linesOffset + item->stateOffset) / 2;
2112     centery = (item->rect.top + item->rect.bottom) / 2;
2113
2114     if (infoPtr->dwStyle & TVS_HASLINES)
2115     {
2116         HPEN hOldPen, hNewPen;
2117         HTREEITEM parent;
2118
2119         /* 
2120          * Get a dotted grey pen
2121          */
2122         hNewPen = CreatePen(PS_ALTERNATE, 0, infoPtr->clrLine);
2123         hOldPen = SelectObject(hdc, hNewPen);
2124
2125         MoveToEx(hdc, item->stateOffset, centery, NULL);
2126         LineTo(hdc, centerx - 1, centery);
2127
2128         if (item->prevSibling || item->parent != infoPtr->root)
2129         {
2130             MoveToEx(hdc, centerx, item->rect.top, NULL);
2131             LineTo(hdc, centerx, centery);
2132         }
2133
2134         if (item->nextSibling)
2135         {
2136             MoveToEx(hdc, centerx, centery, NULL);
2137             LineTo(hdc, centerx, item->rect.bottom + 1);
2138         }
2139
2140         /* Draw the line from our parent to its next sibling. */
2141         parent = item->parent;
2142         while (parent != infoPtr->root)
2143         {
2144             int pcenterx = (parent->linesOffset + parent->stateOffset) / 2;
2145
2146             if (parent->nextSibling
2147                 /* skip top-levels unless TVS_LINESATROOT */
2148                 && parent->stateOffset > parent->linesOffset)
2149             {
2150                 MoveToEx(hdc, pcenterx, item->rect.top, NULL);
2151                 LineTo(hdc, pcenterx, item->rect.bottom + 1);
2152             }
2153
2154             parent = parent->parent;
2155         }
2156
2157         SelectObject(hdc, hOldPen);
2158         DeleteObject(hNewPen);
2159     }
2160
2161     /* 
2162      * Display the (+/-) signs
2163      */
2164
2165     if (infoPtr->dwStyle & TVS_HASBUTTONS)
2166     {
2167         if (item->cChildren)
2168         {
2169             LONG height = item->rect.bottom - item->rect.top;
2170             LONG width  = item->stateOffset - item->linesOffset;
2171             LONG rectsize = min(height, width) / 4;
2172             /* plussize = ceil(rectsize * 3/4) */
2173             LONG plussize = (rectsize + 1) * 3 / 4;
2174
2175             HPEN hNewPen  = CreatePen(PS_SOLID, 0, infoPtr->clrLine);
2176             HPEN hOldPen  = SelectObject(hdc, hNewPen);
2177             HBRUSH hbr    = CreateSolidBrush(infoPtr->clrBk);
2178             HBRUSH hbrOld = SelectObject(hdc, hbr);
2179
2180             Rectangle(hdc, centerx - rectsize, centery - rectsize,
2181                       centerx + rectsize + 1, centery + rectsize + 1);
2182
2183             SelectObject(hdc, hbrOld);
2184             DeleteObject(hbr);
2185
2186             SelectObject(hdc, hOldPen);
2187             DeleteObject(hNewPen);
2188
2189             MoveToEx(hdc, centerx - plussize + 1, centery, NULL);
2190             LineTo(hdc, centerx + plussize, centery);
2191
2192             if (!(item->state & TVIS_EXPANDED))
2193             {
2194                 MoveToEx(hdc, centerx, centery - plussize + 1, NULL);
2195                 LineTo(hdc, centerx, centery + plussize);
2196             }
2197         }
2198     }
2199 }
2200
2201 static void
2202 TREEVIEW_DrawItem(TREEVIEW_INFO *infoPtr, HDC hdc, TREEVIEW_ITEM *wineItem)
2203 {
2204     INT cditem;
2205     HFONT hOldFont;
2206     int centery;
2207
2208     hOldFont = SelectObject(hdc, TREEVIEW_FontForItem(infoPtr, wineItem));
2209
2210     TREEVIEW_UpdateDispInfo(infoPtr, wineItem, CALLBACK_MASK_ALL);
2211
2212     /* The custom draw handler can query the text rectangle,
2213      * so get ready. */
2214     TREEVIEW_ComputeTextWidth(infoPtr, wineItem, hdc);
2215
2216     cditem = 0;
2217
2218     if (infoPtr->cdmode & CDRF_NOTIFYITEMDRAW)
2219     {
2220         cditem = TREEVIEW_SendCustomDrawItemNotify
2221             (infoPtr, hdc, wineItem, CDDS_ITEMPREPAINT);
2222         TRACE("prepaint:cditem-app returns 0x%x\n", cditem);
2223
2224         if (cditem & CDRF_SKIPDEFAULT)
2225         {
2226             SelectObject(hdc, hOldFont);
2227             return;
2228         }
2229     }
2230
2231     if (cditem & CDRF_NEWFONT)
2232         TREEVIEW_ComputeTextWidth(infoPtr, wineItem, hdc);
2233
2234     TREEVIEW_DrawItemLines(infoPtr, hdc, wineItem);
2235
2236     centery = (wineItem->rect.top + wineItem->rect.bottom) / 2;
2237
2238     /* 
2239      * Display the images associated with this item
2240      */
2241     {
2242         INT imageIndex;
2243
2244         /* State images are displayed to the left of the Normal image
2245          * image number is in state; zero should be `display no image'.
2246          */
2247         imageIndex = STATEIMAGEINDEX(wineItem->state);
2248
2249         if (infoPtr->himlState && imageIndex)
2250         {
2251             ImageList_Draw(infoPtr->himlState, imageIndex, hdc,
2252                            wineItem->stateOffset,
2253                            centery - infoPtr->stateImageHeight / 2,
2254                            ILD_NORMAL);
2255         }
2256
2257         /* Now, draw the normal image; can be either selected or
2258          * non-selected image. 
2259          */
2260
2261         if ((wineItem->state & TVIS_SELECTED) && (wineItem->iSelectedImage))
2262         {
2263             /* The item is curently selected */
2264             imageIndex = wineItem->iSelectedImage;
2265         }
2266         else
2267         {
2268             /* The item is not selected */
2269             imageIndex = wineItem->iImage;
2270         }
2271
2272         if (infoPtr->himlNormal)
2273         {
2274             int ovlIdx = wineItem->state & TVIS_OVERLAYMASK;
2275
2276             ImageList_Draw(infoPtr->himlNormal, imageIndex, hdc,
2277                            wineItem->imageOffset,
2278                            centery - infoPtr->normalImageHeight / 2,
2279                            ILD_NORMAL | ovlIdx);
2280         }
2281     }
2282
2283
2284     /* 
2285      * Display the text associated with this item
2286      */
2287
2288     /* Don't paint item's text if it's being edited */
2289     if (!infoPtr->hwndEdit || (infoPtr->selectedItem != wineItem))
2290     {
2291         if (wineItem->pszText)
2292         {
2293             COLORREF oldTextColor = 0;
2294             INT oldBkMode;
2295             HBRUSH hbrBk = 0;
2296             BOOL inFocus = (GetFocus() == infoPtr->hwnd);
2297             RECT rcText;
2298
2299             oldBkMode = SetBkMode(hdc, TRANSPARENT);
2300
2301             /* - If item is drop target or it is selected and window is in focus -
2302              * use blue background (COLOR_HIGHLIGHT).
2303              * - If item is selected, window is not in focus, but it has style
2304              * TVS_SHOWSELALWAYS - use grey background (COLOR_BTNFACE)
2305              * - Otherwise - don't fill background
2306              */
2307             if ((wineItem->state & TVIS_DROPHILITED) || ((wineItem == infoPtr->focusedItem) && !(wineItem->state & TVIS_SELECTED)) ||
2308                 ((wineItem->state & TVIS_SELECTED) && (!infoPtr->focusedItem) &&
2309                  (inFocus || (infoPtr->dwStyle & TVS_SHOWSELALWAYS))))
2310             {
2311                 if ((wineItem->state & TVIS_DROPHILITED) || inFocus)
2312                 {
2313                     hbrBk = CreateSolidBrush(GetSysColor(COLOR_HIGHLIGHT));
2314                     oldTextColor =
2315                         SetTextColor(hdc, GetSysColor(COLOR_HIGHLIGHTTEXT));
2316                 }
2317                 else
2318                 {
2319                     hbrBk = CreateSolidBrush(GetSysColor(COLOR_BTNFACE));
2320
2321                     if (infoPtr->clrText == -1)
2322                         oldTextColor =
2323                             SetTextColor(hdc, GetSysColor(COLOR_WINDOWTEXT));
2324                     else
2325                         oldTextColor = SetTextColor(hdc, infoPtr->clrText);
2326                 }
2327             }
2328             else
2329             {
2330                 if (infoPtr->clrText == -1)
2331                     oldTextColor =
2332                         SetTextColor(hdc, GetSysColor(COLOR_WINDOWTEXT));
2333                 else
2334                     oldTextColor = SetTextColor(hdc, infoPtr->clrText);
2335             }
2336
2337             rcText.top = wineItem->rect.top;
2338             rcText.bottom = wineItem->rect.bottom;
2339             rcText.left = wineItem->textOffset;
2340             rcText.right = rcText.left + wineItem->textWidth + 4;
2341
2342             if (hbrBk)
2343             {
2344                 FillRect(hdc, &rcText, hbrBk);
2345                 DeleteObject(hbrBk);
2346             }
2347
2348             /* Draw the box around the selected item */
2349             if ((wineItem == infoPtr->selectedItem) && inFocus)
2350             {
2351                 DrawFocusRect(hdc,&rcText);
2352             }
2353
2354             InflateRect(&rcText, -2, -1); /* allow for the focus rect */
2355
2356             /* Draw it */
2357             DrawTextA(hdc,
2358                       wineItem->pszText,
2359                       lstrlenA(wineItem->pszText),
2360                       &rcText,
2361                       DT_CENTER | DT_VCENTER | DT_SINGLELINE | DT_NOPREFIX);
2362
2363             /* Restore the hdc state */
2364             SetTextColor(hdc, oldTextColor);
2365
2366             if (oldBkMode != TRANSPARENT)
2367                 SetBkMode(hdc, oldBkMode);
2368         }
2369     }
2370
2371     /* Draw insertion mark if necessary */
2372
2373     if (infoPtr->insertMarkItem)
2374         TRACE("item:%d,mark:%d\n",
2375               TREEVIEW_GetItemIndex(infoPtr, wineItem),
2376               (int)infoPtr->insertMarkItem);
2377
2378     if (wineItem == infoPtr->insertMarkItem)
2379     {
2380         HPEN hNewPen, hOldPen;
2381         int offset;
2382         int left, right;
2383
2384         hNewPen = CreatePen(PS_SOLID, 2, infoPtr->clrInsertMark);
2385         hOldPen = SelectObject(hdc, hNewPen);
2386
2387         if (infoPtr->insertBeforeorAfter)
2388             offset = wineItem->rect.bottom - 1;
2389         else
2390             offset = wineItem->rect.top + 1;
2391
2392         left = wineItem->textOffset - 2;
2393         right = wineItem->textOffset + wineItem->textWidth + 2;
2394
2395         MoveToEx(hdc, left, offset - 3, NULL);
2396         LineTo(hdc, left, offset + 4);
2397
2398         MoveToEx(hdc, left, offset, NULL);
2399         LineTo(hdc, right + 1, offset);
2400
2401         MoveToEx(hdc, right, offset + 3, NULL);
2402         LineTo(hdc, right, offset - 4);
2403
2404         SelectObject(hdc, hOldPen);
2405         DeleteObject(hNewPen);
2406     }
2407
2408     if (cditem & CDRF_NOTIFYPOSTPAINT)
2409     {
2410         cditem = TREEVIEW_SendCustomDrawItemNotify
2411             (infoPtr, hdc, wineItem, CDDS_ITEMPOSTPAINT);
2412         TRACE("postpaint:cditem-app returns 0x%x\n", cditem);
2413     }
2414
2415     SelectObject(hdc, hOldFont);
2416 }
2417
2418 /* Computes treeHeight and treeWidth and updates the scroll bars.
2419  */
2420 static void
2421 TREEVIEW_UpdateScrollBars(TREEVIEW_INFO *infoPtr)
2422 {
2423     TREEVIEW_ITEM *wineItem;
2424     HWND hwnd = infoPtr->hwnd;
2425     BOOL vert = FALSE;
2426     BOOL horz = FALSE;
2427     SCROLLINFO si;
2428     LONG scrollX = infoPtr->scrollX;
2429
2430     infoPtr->treeWidth = 0;
2431     infoPtr->treeHeight = 0;
2432
2433     /* We iterate through all visible items in order to get the tree height
2434      * and width */
2435     wineItem = infoPtr->root->firstChild;
2436
2437     while (wineItem != NULL)
2438     {
2439         if (ISVISIBLE(wineItem))
2440         {
2441             /* actually we draw text at textOffset + 2 */
2442             if (2+wineItem->textOffset+wineItem->textWidth > infoPtr->treeWidth)
2443                 infoPtr->treeWidth = wineItem->textOffset+wineItem->textWidth+2;
2444
2445             /* This is scroll-adjusted, but we fix this below. */
2446             infoPtr->treeHeight = wineItem->rect.bottom;
2447         }
2448
2449         wineItem = TREEVIEW_GetNextListItem(infoPtr, wineItem);
2450     }
2451
2452     /* Fix the scroll adjusted treeHeight and treeWidth. */
2453     if (infoPtr->root->firstChild)
2454         infoPtr->treeHeight -= infoPtr->root->firstChild->rect.top;
2455
2456     infoPtr->treeWidth += infoPtr->scrollX;
2457
2458     /* Adding one scroll bar may take up enough space that it forces us
2459      * to add the other as well. */
2460     if (infoPtr->treeHeight > infoPtr->clientHeight)
2461     {
2462         vert = TRUE;
2463
2464         if (infoPtr->treeWidth
2465             > infoPtr->clientWidth - GetSystemMetrics(SM_CXVSCROLL))
2466             horz = TRUE;
2467     }
2468     else if (infoPtr->treeWidth > infoPtr->clientWidth)
2469         horz = TRUE;
2470
2471     if (!vert && horz && infoPtr->treeHeight
2472         > infoPtr->clientHeight - GetSystemMetrics(SM_CYVSCROLL))
2473         vert = TRUE;
2474
2475     si.cbSize = sizeof(SCROLLINFO);
2476     si.fMask  = SIF_POS|SIF_RANGE|SIF_PAGE;
2477     si.nMin   = 0;
2478
2479     if (vert)
2480     {
2481         si.nPage = TREEVIEW_GetVisibleCount(infoPtr);
2482         si.nPos  = infoPtr->firstVisible->visibleOrder;
2483         si.nMax  = infoPtr->maxVisibleOrder - 1;
2484
2485         SetScrollInfo(hwnd, SB_VERT, &si, TRUE);
2486
2487         if (!(infoPtr->uInternalStatus & TV_VSCROLL))
2488             ShowScrollBar(hwnd, SB_VERT, TRUE);
2489         infoPtr->uInternalStatus |= TV_VSCROLL;
2490     }
2491     else
2492     {
2493         if (infoPtr->uInternalStatus & TV_VSCROLL)
2494             ShowScrollBar(hwnd, SB_VERT, FALSE);
2495         infoPtr->uInternalStatus &= ~TV_VSCROLL;
2496     }
2497
2498     if (horz)
2499     {
2500         si.nPage = infoPtr->clientWidth;
2501         si.nPos  = infoPtr->scrollX;
2502         si.nMax  = infoPtr->treeWidth - 1;
2503
2504         if (si.nPos > si.nMax - max( si.nPage-1, 0 ))
2505         {
2506            si.nPos = si.nMax - max( si.nPage-1, 0 );
2507            scrollX = si.nPos;
2508         }
2509
2510         if (!(infoPtr->uInternalStatus & TV_HSCROLL))
2511             ShowScrollBar(hwnd, SB_HORZ, TRUE);
2512         infoPtr->uInternalStatus |= TV_HSCROLL;
2513
2514         SetScrollInfo(hwnd, SB_HORZ, &si, TRUE);
2515     }
2516     else
2517     {
2518         if (infoPtr->uInternalStatus & TV_HSCROLL)
2519             ShowScrollBar(hwnd, SB_HORZ, FALSE);
2520         infoPtr->uInternalStatus &= ~TV_HSCROLL;
2521
2522         scrollX = 0;
2523     }
2524
2525     if (infoPtr->scrollX != scrollX)
2526     {
2527         TREEVIEW_HScroll(infoPtr,
2528                          MAKEWPARAM(SB_THUMBPOSITION, scrollX));
2529     }
2530
2531     if (!horz)
2532         infoPtr->uInternalStatus &= ~TV_HSCROLL;
2533 }
2534
2535 /* CtrlSpy doesn't mention this, but CorelDRAW's object manager needs it. */
2536 static LRESULT
2537 TREEVIEW_EraseBackground(TREEVIEW_INFO *infoPtr, HDC hDC)
2538 {
2539     HBRUSH hBrush = CreateSolidBrush(infoPtr->clrBk);
2540     RECT rect;
2541
2542     GetClientRect(infoPtr->hwnd, &rect);
2543     FillRect(hDC, &rect, hBrush);
2544     DeleteObject(hBrush);
2545
2546     return 1;
2547 }
2548
2549 static void
2550 TREEVIEW_Refresh(TREEVIEW_INFO *infoPtr, HDC hdc, RECT *rc)
2551 {
2552     HWND hwnd = infoPtr->hwnd;
2553     RECT rect = *rc;
2554     TREEVIEW_ITEM *wineItem;
2555
2556     if (infoPtr->clientHeight == 0 || infoPtr->clientWidth == 0)
2557     {
2558         TRACE("empty window\n");
2559         return;
2560     }
2561
2562     infoPtr->cdmode = TREEVIEW_SendCustomDrawNotify(infoPtr, CDDS_PREPAINT,
2563                                                     hdc, rect);
2564
2565     if (infoPtr->cdmode == CDRF_SKIPDEFAULT)
2566     {
2567         ReleaseDC(hwnd, hdc);
2568         return;
2569     }
2570
2571     for (wineItem = infoPtr->root->firstChild;
2572          wineItem != NULL;
2573          wineItem = TREEVIEW_GetNextListItem(infoPtr, wineItem))
2574     {
2575         if (ISVISIBLE(wineItem))
2576         {
2577             /* Avoid unneeded calculations */
2578             if (wineItem->rect.top > rect.bottom)
2579                 break;
2580             if (wineItem->rect.bottom < rect.top)
2581                 continue;
2582
2583             TREEVIEW_DrawItem(infoPtr, hdc, wineItem);
2584         }
2585     }
2586
2587     TREEVIEW_UpdateScrollBars(infoPtr);
2588
2589     if (infoPtr->cdmode & CDRF_NOTIFYPOSTPAINT)
2590         infoPtr->cdmode =
2591             TREEVIEW_SendCustomDrawNotify(infoPtr, CDDS_POSTPAINT, hdc, rect);
2592 }
2593
2594 static void
2595 TREEVIEW_Invalidate(TREEVIEW_INFO *infoPtr, TREEVIEW_ITEM *item)
2596 {
2597     if (item != NULL)
2598         InvalidateRect(infoPtr->hwnd, &item->rect, TRUE);
2599     else
2600         InvalidateRect(infoPtr->hwnd, NULL, TRUE);
2601 }
2602
2603 static LRESULT
2604 TREEVIEW_Paint(TREEVIEW_INFO *infoPtr, WPARAM wParam)
2605 {
2606     HDC hdc;
2607     PAINTSTRUCT ps;
2608     RECT rc;
2609
2610     TRACE("\n");
2611
2612     if (wParam)
2613     {
2614         hdc = (HDC)wParam;
2615         GetUpdateRect(infoPtr->hwnd, &rc, TRUE);
2616     }
2617     else
2618     {
2619         hdc = BeginPaint(infoPtr->hwnd, &ps);
2620         rc = ps.rcPaint;
2621     }
2622
2623     if(infoPtr->bRedraw) /* WM_SETREDRAW sets bRedraw */
2624         TREEVIEW_Refresh(infoPtr, hdc, &rc);
2625
2626     if (!wParam)
2627         EndPaint(infoPtr->hwnd, &ps);
2628
2629     return 0;
2630 }
2631
2632
2633 /* Sorting **************************************************************/
2634
2635 /***************************************************************************
2636  * Forward the DPA local callback to the treeview owner callback
2637  */
2638 static INT WINAPI
2639 TREEVIEW_CallBackCompare(TREEVIEW_ITEM *first, TREEVIEW_ITEM *second, LPTVSORTCB pCallBackSort)
2640 {
2641     /* Forward the call to the client-defined callback */
2642     return pCallBackSort->lpfnCompare(first->lParam,
2643                                       second->lParam,
2644                                       pCallBackSort->lParam);
2645 }
2646
2647 /***************************************************************************
2648  * Treeview native sort routine: sort on item text.
2649  */
2650 static INT WINAPI
2651 TREEVIEW_SortOnName(TREEVIEW_ITEM *first, TREEVIEW_ITEM *second,
2652                      TREEVIEW_INFO *infoPtr)
2653 {
2654     TREEVIEW_UpdateDispInfo(infoPtr, first, TVIF_TEXT);
2655     TREEVIEW_UpdateDispInfo(infoPtr, second, TVIF_TEXT);
2656
2657     return strcasecmp(first->pszText, second->pszText);
2658 }
2659
2660 /* Returns the number of physical children belonging to item. */
2661 static INT
2662 TREEVIEW_CountChildren(TREEVIEW_INFO *infoPtr, TREEVIEW_ITEM *item)
2663 {
2664     INT cChildren = 0;
2665     HTREEITEM hti;
2666
2667     for (hti = item->firstChild; hti != NULL; hti = hti->nextSibling)
2668         cChildren++;
2669
2670     return cChildren;
2671 }
2672
2673 /* Returns a DPA containing a pointer to each physical child of item in
2674  * sibling order. If item has no children, an empty DPA is returned. */
2675 static HDPA
2676 TREEVIEW_BuildChildDPA(TREEVIEW_INFO *infoPtr, TREEVIEW_ITEM *item)
2677 {
2678     HTREEITEM child = item->firstChild;
2679
2680     HDPA list = DPA_Create(8);
2681     if (list == 0) return NULL;
2682
2683     for (child = item->firstChild; child != NULL; child = child->nextSibling)
2684     {
2685         if (DPA_InsertPtr(list, INT_MAX, child) == -1)
2686         {
2687             DPA_Destroy(list);
2688             return NULL;
2689         }
2690     }
2691
2692     return list;
2693 }
2694
2695 /***************************************************************************
2696  * Setup the treeview structure with regards of the sort method
2697  * and sort the children of the TV item specified in lParam
2698  * fRecurse: currently unused. Should be zero.
2699  * parent: if pSort!=NULL, should equal pSort->hParent.
2700  *         otherwise, item which child items are to be sorted.
2701  * pSort:  sort method info. if NULL, sort on item text.
2702  *         if non-NULL, sort on item's lParam content, and let the
2703  *         application decide what that means. See also TVM_SORTCHILDRENCB.
2704  */
2705
2706 static LRESULT
2707 TREEVIEW_Sort(TREEVIEW_INFO *infoPtr, BOOL fRecurse, HTREEITEM parent,
2708               LPTVSORTCB pSort)
2709 {
2710     INT cChildren;
2711     PFNDPACOMPARE pfnCompare;
2712     LPARAM lpCompare;
2713
2714     /* undocumented feature: TVI_ROOT means `sort the whole tree' */
2715     if (parent == TVI_ROOT)
2716         parent = infoPtr->root;
2717
2718     /* Check for a valid handle to the parent item */
2719     if (!TREEVIEW_ValidItem(infoPtr, parent))
2720     {
2721         ERR("invalid item hParent=%x\n", (INT)parent);
2722         return FALSE;
2723     }
2724
2725     if (pSort)
2726     {
2727         pfnCompare = (PFNDPACOMPARE)TREEVIEW_CallBackCompare;
2728         lpCompare = (LPARAM)pSort;
2729     }
2730     else
2731     {
2732         pfnCompare = (PFNDPACOMPARE)TREEVIEW_SortOnName;
2733         lpCompare = (LPARAM)infoPtr;
2734     }
2735
2736     cChildren = TREEVIEW_CountChildren(infoPtr, parent);
2737
2738     /* Make sure there is something to sort */
2739     if (cChildren > 1)
2740     {
2741         /* TREEVIEW_ITEM rechaining */
2742         INT count = 0;
2743         HTREEITEM item = 0;
2744         HTREEITEM nextItem = 0;
2745         HTREEITEM prevItem = 0;
2746
2747         HDPA sortList = TREEVIEW_BuildChildDPA(infoPtr, parent);
2748
2749         if (sortList == NULL)
2750             return FALSE;
2751
2752         /* let DPA sort the list */
2753         DPA_Sort(sortList, pfnCompare, lpCompare);
2754
2755         /* The order of DPA entries has been changed, so fixup the
2756          * nextSibling and prevSibling pointers. */
2757
2758         item = (HTREEITEM)DPA_GetPtr(sortList, count++);
2759         while ((nextItem = (HTREEITEM)DPA_GetPtr(sortList, count++)) != NULL)
2760         {
2761             /* link the two current item toghether */
2762             item->nextSibling = nextItem;
2763             nextItem->prevSibling = item;
2764
2765             if (prevItem == NULL)
2766             {
2767                 /* this is the first item, update the parent */
2768                 parent->firstChild = item;
2769                 item->prevSibling = NULL;
2770             }
2771             else
2772             {
2773                 /* fix the back chaining */
2774                 item->prevSibling = prevItem;
2775             }
2776
2777             /* get ready for the next one */
2778             prevItem = item;
2779             item = nextItem;
2780         }
2781
2782         /* the last item is pointed to by item and never has a sibling */
2783         item->nextSibling = NULL;
2784         parent->lastChild = item;
2785
2786         DPA_Destroy(sortList);
2787
2788         TREEVIEW_VerifyTree(infoPtr);
2789
2790         if (parent->state & TVIS_EXPANDED)
2791         {
2792             int visOrder = infoPtr->firstVisible->visibleOrder;
2793
2794         if (parent == infoPtr->root)
2795             TREEVIEW_RecalculateVisibleOrder(infoPtr, NULL);
2796         else
2797             TREEVIEW_RecalculateVisibleOrder(infoPtr, parent);
2798
2799             if (TREEVIEW_IsChildOf(parent, infoPtr->firstVisible))
2800             {
2801                 TREEVIEW_ITEM *item;
2802
2803                 for (item = infoPtr->root->firstChild; item != NULL;
2804                      item = TREEVIEW_GetNextListItem(infoPtr, item))
2805                 {
2806                     if (item->visibleOrder == visOrder)
2807                         break;
2808                 }
2809
2810                 TREEVIEW_SetFirstVisible(infoPtr, item, FALSE);
2811             }
2812
2813             TREEVIEW_Invalidate(infoPtr, NULL);
2814         }
2815
2816         return TRUE;
2817     }
2818     return FALSE;
2819 }
2820
2821
2822 /***************************************************************************
2823  * Setup the treeview structure with regards of the sort method
2824  * and sort the children of the TV item specified in lParam
2825  */
2826 static LRESULT
2827 TREEVIEW_SortChildrenCB(TREEVIEW_INFO *infoPtr, WPARAM wParam, LPTVSORTCB pSort)
2828 {
2829     return TREEVIEW_Sort(infoPtr, wParam, pSort->hParent, pSort);
2830 }
2831
2832
2833 /***************************************************************************
2834  * Sort the children of the TV item specified in lParam.
2835  */
2836 static LRESULT
2837 TREEVIEW_SortChildren(TREEVIEW_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
2838 {
2839     return TREEVIEW_Sort(infoPtr, (BOOL)wParam, (HTREEITEM)lParam, NULL);
2840 }
2841
2842
2843 /* Expansion/Collapse ***************************************************/
2844
2845 static BOOL
2846 TREEVIEW_SendExpanding(TREEVIEW_INFO *infoPtr, TREEVIEW_ITEM *wineItem,
2847                        UINT action)
2848 {
2849     return !TREEVIEW_SendTreeviewNotify(infoPtr, TVN_ITEMEXPANDINGA, action,
2850                                         TVIF_HANDLE | TVIF_STATE | TVIF_PARAM
2851                                         | TVIF_IMAGE | TVIF_SELECTEDIMAGE,
2852                                         0, wineItem);
2853 }
2854
2855 static VOID
2856 TREEVIEW_SendExpanded(TREEVIEW_INFO *infoPtr, TREEVIEW_ITEM *wineItem,
2857                       UINT action)
2858 {
2859     TREEVIEW_SendTreeviewNotify(infoPtr, TVN_ITEMEXPANDEDA, action,
2860                                 TVIF_HANDLE | TVIF_STATE | TVIF_PARAM
2861                                 | TVIF_IMAGE | TVIF_SELECTEDIMAGE,
2862                                 0, wineItem);
2863 }
2864
2865
2866 /* This corresponds to TVM_EXPAND with TVE_COLLAPSE.
2867  * bRemoveChildren corresponds to TVE_COLLAPSERESET. */
2868 static BOOL
2869 TREEVIEW_Collapse(TREEVIEW_INFO *infoPtr, TREEVIEW_ITEM *wineItem,
2870                   BOOL bRemoveChildren, BOOL bUser)
2871 {
2872     UINT action = TVE_COLLAPSE | (bRemoveChildren ? TVE_COLLAPSERESET : 0);
2873     BOOL bSetSelection, bSetFirstVisible;
2874
2875     TRACE("TVE_COLLAPSE %p %s\n", wineItem, TREEVIEW_ItemName(wineItem));
2876
2877     if (!(wineItem->state & TVIS_EXPANDED) || wineItem->firstChild == NULL)
2878         return FALSE;
2879
2880     if (bUser)
2881         TREEVIEW_SendExpanding(infoPtr, wineItem, action);
2882
2883     wineItem->state &= ~TVIS_EXPANDED;
2884
2885     if (bUser)
2886         TREEVIEW_SendExpanded(infoPtr, wineItem, action);
2887
2888     bSetSelection = (infoPtr->selectedItem != NULL
2889                      && TREEVIEW_IsChildOf(wineItem, infoPtr->selectedItem));
2890
2891     bSetFirstVisible = (infoPtr->firstVisible != NULL
2892                         && TREEVIEW_IsChildOf(wineItem, infoPtr->firstVisible));
2893
2894     if (bRemoveChildren)
2895     {
2896         TRACE("TVE_COLLAPSERESET\n");
2897         wineItem->state &= ~TVIS_EXPANDEDONCE;
2898         TREEVIEW_RemoveAllChildren(infoPtr, wineItem);
2899     }
2900
2901     if (wineItem->firstChild)
2902     {
2903         TREEVIEW_ITEM *item, *sibling;
2904
2905         sibling = TREEVIEW_GetNextListItem(infoPtr, wineItem);
2906
2907         for (item = wineItem->firstChild; item != sibling;
2908              item = TREEVIEW_GetNextListItem(infoPtr, item))
2909         {
2910             item->visibleOrder = -1;
2911         }
2912     }
2913
2914     TREEVIEW_RecalculateVisibleOrder(infoPtr, wineItem);
2915
2916     TREEVIEW_SetFirstVisible(infoPtr, bSetFirstVisible ? wineItem
2917                              : infoPtr->firstVisible, TRUE);
2918
2919     if (bSetSelection)
2920     {
2921         /* Don't call DoSelectItem, it sends notifications. */
2922         if (TREEVIEW_ValidItem(infoPtr, infoPtr->selectedItem))
2923             infoPtr->selectedItem->state &= ~TVIS_SELECTED;
2924         wineItem->state |= TVIS_SELECTED;
2925         infoPtr->selectedItem = wineItem;
2926
2927         TREEVIEW_EnsureVisible(infoPtr, wineItem, FALSE);
2928     }
2929
2930     TREEVIEW_UpdateScrollBars(infoPtr);
2931     TREEVIEW_Invalidate(infoPtr, NULL);
2932
2933     return TRUE;
2934 }
2935
2936 static BOOL
2937 TREEVIEW_Expand(TREEVIEW_INFO *infoPtr, TREEVIEW_ITEM *wineItem,
2938                 BOOL bExpandPartial, BOOL bUser)
2939 {
2940     TRACE("\n");
2941
2942     if (!TREEVIEW_HasChildren(infoPtr, wineItem)
2943         || wineItem->state & TVIS_EXPANDED)
2944         return FALSE;
2945
2946     TRACE("TVE_EXPAND %p %s\n", wineItem, TREEVIEW_ItemName(wineItem));
2947
2948     if (bUser || !(wineItem->state & TVIS_EXPANDEDONCE))
2949     {
2950         if (!TREEVIEW_SendExpanding(infoPtr, wineItem, TVE_EXPAND))
2951         {
2952             TRACE("  TVN_ITEMEXPANDING returned TRUE, exiting...\n");
2953             return FALSE;
2954         }
2955
2956         wineItem->state |= TVIS_EXPANDED;
2957         TREEVIEW_SendExpanded(infoPtr, wineItem, TVE_EXPAND);
2958         wineItem->state |= TVIS_EXPANDEDONCE;
2959     }
2960     else
2961     {
2962         /* this item has already been expanded */
2963         wineItem->state |= TVIS_EXPANDED;
2964     }
2965
2966     if (bExpandPartial)
2967         FIXME("TVE_EXPANDPARTIAL not implemented\n");
2968
2969     TREEVIEW_RecalculateVisibleOrder(infoPtr, wineItem);
2970     TREEVIEW_UpdateSubTree(infoPtr, wineItem);
2971     TREEVIEW_UpdateScrollBars(infoPtr);
2972
2973     /* Scroll up so that as many children as possible are visible.
2974      * This looses when expanding causes an HScroll bar to appear, but we
2975      * don't know that yet, so the last item is obscured. */
2976     if (wineItem->firstChild != NULL)
2977     {
2978         int nChildren = wineItem->lastChild->visibleOrder
2979             - wineItem->firstChild->visibleOrder + 1;
2980
2981         int visible_pos = wineItem->visibleOrder
2982             - infoPtr->firstVisible->visibleOrder;
2983
2984         int rows_below = TREEVIEW_GetVisibleCount(infoPtr) - visible_pos - 1;
2985
2986         if (visible_pos > 0 && nChildren > rows_below)
2987         {
2988             int scroll = nChildren - rows_below;
2989
2990             if (scroll > visible_pos)
2991                 scroll = visible_pos;
2992
2993             if (scroll > 0)
2994             {
2995                 TREEVIEW_ITEM *newFirstVisible
2996                     = TREEVIEW_GetListItem(infoPtr, infoPtr->firstVisible,
2997                                            scroll);
2998
2999
3000                 TREEVIEW_SetFirstVisible(infoPtr, newFirstVisible, TRUE);
3001             }
3002         }
3003     }
3004
3005     TREEVIEW_Invalidate(infoPtr, NULL);
3006
3007     return TRUE;
3008 }
3009
3010 static BOOL
3011 TREEVIEW_Toggle(TREEVIEW_INFO *infoPtr, TREEVIEW_ITEM *wineItem, BOOL bUser)
3012 {
3013     TRACE("\n");
3014
3015     if (wineItem->state & TVIS_EXPANDED)
3016         return TREEVIEW_Collapse(infoPtr, wineItem, FALSE, bUser);
3017     else
3018         return TREEVIEW_Expand(infoPtr, wineItem, FALSE, bUser);
3019 }
3020
3021 static VOID
3022 TREEVIEW_ExpandAll(TREEVIEW_INFO *infoPtr, TREEVIEW_ITEM *item)
3023 {
3024     TREEVIEW_Expand(infoPtr, item, FALSE, TRUE);
3025
3026     for (item = item->firstChild; item != NULL; item = item->nextSibling)
3027     {
3028         if (TREEVIEW_HasChildren(infoPtr, item))
3029             TREEVIEW_ExpandAll(infoPtr, item);
3030     }
3031 }
3032
3033 /* Note:If the specified item is the child of a collapsed parent item,
3034    the parent's list of child items is (recursively) expanded to reveal the 
3035    specified item. This is mentioned for TREEVIEW_SelectItem; don't 
3036    know if it also applies here.
3037 */
3038
3039 static LRESULT
3040 TREEVIEW_ExpandMsg(TREEVIEW_INFO *infoPtr, UINT flag, HTREEITEM wineItem)
3041 {
3042     if (!TREEVIEW_ValidItem(infoPtr, wineItem))
3043         return 0;
3044
3045     TRACE("For (%s) item:%d, flags %x, state:%d\n",
3046               TREEVIEW_ItemName(wineItem), flag,
3047               TREEVIEW_GetItemIndex(infoPtr, wineItem), wineItem->state);
3048
3049     switch (flag & TVE_TOGGLE)
3050     {
3051     case TVE_COLLAPSE:
3052         return TREEVIEW_Collapse(infoPtr, wineItem, flag & TVE_COLLAPSERESET,
3053                                  FALSE);
3054
3055     case TVE_EXPAND:
3056         return TREEVIEW_Expand(infoPtr, wineItem, flag & TVE_EXPANDPARTIAL,
3057                                FALSE);
3058
3059     case TVE_TOGGLE:
3060         return TREEVIEW_Toggle(infoPtr, wineItem, TRUE);
3061
3062     default:
3063         return 0;
3064     }
3065
3066 #if 0
3067     TRACE("Exiting, Item %p state is now %d...\n", wineItem, wineItem->state);
3068 #endif
3069 }
3070
3071 /* Hit-Testing **********************************************************/
3072
3073 static TREEVIEW_ITEM *
3074 TREEVIEW_HitTestPoint(TREEVIEW_INFO *infoPtr, POINT pt)
3075 {
3076     TREEVIEW_ITEM *wineItem;
3077     LONG row;
3078
3079     if (!infoPtr->firstVisible)
3080         return NULL;
3081
3082     row = pt.y / infoPtr->uItemHeight + infoPtr->firstVisible->visibleOrder;
3083
3084     for (wineItem = infoPtr->firstVisible; wineItem != NULL;
3085          wineItem = TREEVIEW_GetNextListItem(infoPtr, wineItem))
3086     {
3087         if (row >= wineItem->visibleOrder
3088             && row < wineItem->visibleOrder + wineItem->iIntegral)
3089             break;
3090     }
3091
3092     return wineItem;
3093 }
3094
3095 static LRESULT
3096 TREEVIEW_HitTest(TREEVIEW_INFO *infoPtr, LPTVHITTESTINFO lpht)
3097 {
3098     TREEVIEW_ITEM *wineItem;
3099     RECT rect;
3100     UINT status;
3101     LONG x, y;
3102
3103     lpht->hItem = 0;
3104     GetClientRect(infoPtr->hwnd, &rect);
3105     status = 0;
3106     x = lpht->pt.x;
3107     y = lpht->pt.y;
3108
3109     if (x < rect.left)
3110     {
3111         status |= TVHT_TOLEFT;
3112     }
3113     else if (x > rect.right)
3114     {
3115         status |= TVHT_TORIGHT;
3116     }
3117
3118     if (y < rect.top)
3119     {
3120         status |= TVHT_ABOVE;
3121     }
3122     else if (y > rect.bottom)
3123     {
3124         status |= TVHT_BELOW;
3125     }
3126
3127     if (status)
3128     {
3129         lpht->flags = status;
3130         return (LRESULT)(HTREEITEM)NULL;
3131     }
3132
3133     wineItem = TREEVIEW_HitTestPoint(infoPtr, lpht->pt);
3134     if (!wineItem)
3135     {
3136         lpht->flags = TVHT_NOWHERE;
3137         return (LRESULT)(HTREEITEM)NULL;
3138     }
3139
3140     if (x >= wineItem->textOffset + wineItem->textWidth)
3141     {
3142         lpht->flags = TVHT_ONITEMRIGHT;
3143     }
3144     else if (x >= wineItem->textOffset)
3145     {
3146         lpht->flags = TVHT_ONITEMLABEL;
3147     }
3148     else if (x >= wineItem->imageOffset)
3149     {
3150         lpht->flags = TVHT_ONITEMICON;
3151     }
3152     else if (x >= wineItem->stateOffset)
3153     {
3154         lpht->flags = TVHT_ONITEMSTATEICON;
3155     }
3156     else if (x >= wineItem->linesOffset && infoPtr->dwStyle & TVS_HASBUTTONS)
3157     {
3158         lpht->flags = TVHT_ONITEMBUTTON;
3159     }
3160     else
3161     {
3162         lpht->flags = TVHT_ONITEMINDENT;
3163     }
3164
3165     lpht->hItem = wineItem;
3166     TRACE("(%ld,%ld):result %x\n", lpht->pt.x, lpht->pt.y, lpht->flags);
3167
3168     return (LRESULT)wineItem;
3169 }
3170
3171 /* Item Label Editing ***************************************************/
3172
3173 static LRESULT
3174 TREEVIEW_GetEditControl(TREEVIEW_INFO *infoPtr)
3175 {
3176     return infoPtr->hwndEdit;
3177 }
3178
3179 static LRESULT CALLBACK
3180 TREEVIEW_Edit_SubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
3181 {
3182     TREEVIEW_INFO *infoPtr;
3183     BOOL bCancel = FALSE;
3184
3185     switch (uMsg)
3186     {
3187     case WM_PAINT:
3188         {
3189             LRESULT rc;
3190             TREEVIEW_INFO *infoPtr = TREEVIEW_GetInfoPtr(GetParent(hwnd));
3191
3192             TRACE("WM_PAINT start\n");
3193             rc = CallWindowProcA(infoPtr->wpEditOrig, hwnd, uMsg, wParam,
3194                                  lParam);
3195             TRACE("WM_PAINT done\n");
3196             return rc;
3197         }
3198
3199     case WM_KILLFOCUS:
3200     {
3201         TREEVIEW_INFO *infoPtr = TREEVIEW_GetInfoPtr(GetParent(hwnd));
3202         if (infoPtr->bIgnoreEditKillFocus)
3203             return TRUE;
3204
3205         break;
3206     }
3207
3208     case WM_GETDLGCODE:
3209         return DLGC_WANTARROWS | DLGC_WANTALLKEYS;
3210
3211     case WM_KEYDOWN:
3212         if (wParam == (WPARAM)VK_ESCAPE)
3213         {
3214             bCancel = TRUE;
3215             break;
3216         }
3217         else if (wParam == (WPARAM)VK_RETURN)
3218         {
3219             break;
3220         }
3221
3222         /* fall through */
3223     default:
3224         {
3225             TREEVIEW_INFO *infoPtr = TREEVIEW_GetInfoPtr(GetParent(hwnd));
3226
3227             return CallWindowProcA(infoPtr->wpEditOrig, hwnd, uMsg, wParam,
3228                                    lParam);
3229         }
3230     }
3231
3232     /* Processing TVN_ENDLABELEDIT message could kill the focus       */
3233     /* eg. Using a messagebox                                         */
3234
3235     infoPtr = TREEVIEW_GetInfoPtr(GetParent(hwnd));
3236     infoPtr->bIgnoreEditKillFocus = TRUE;
3237     TREEVIEW_EndEditLabelNow(infoPtr, bCancel || !infoPtr->bLabelChanged);
3238     infoPtr->bIgnoreEditKillFocus = FALSE;
3239
3240     return 0;
3241 }
3242
3243
3244 /* should handle edit control messages here */
3245
3246 static LRESULT
3247 TREEVIEW_Command(TREEVIEW_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
3248 {
3249     TRACE("%x %ld\n", wParam, lParam);
3250
3251     switch (HIWORD(wParam))
3252     {
3253     case EN_UPDATE:
3254         {
3255             /* 
3256              * Adjust the edit window size 
3257              */
3258             char buffer[1024];
3259             TREEVIEW_ITEM *editItem = infoPtr->selectedItem;
3260             HDC hdc = GetDC(infoPtr->hwndEdit);
3261             SIZE sz;
3262             int len;
3263             HFONT hFont, hOldFont = 0;
3264
3265             infoPtr->bLabelChanged = TRUE;
3266
3267             len = GetWindowTextA(infoPtr->hwndEdit, buffer, sizeof(buffer));
3268
3269             /* Select font to get the right dimension of the string */
3270             hFont = SendMessageA(infoPtr->hwndEdit, WM_GETFONT, 0, 0);
3271             if (hFont != 0)
3272             {
3273                 hOldFont = SelectObject(hdc, hFont);
3274             }
3275
3276             if (GetTextExtentPoint32A(hdc, buffer, strlen(buffer), &sz))
3277             {
3278                 TEXTMETRICA textMetric;
3279
3280                 /* Add Extra spacing for the next character */
3281                 GetTextMetricsA(hdc, &textMetric);
3282                 sz.cx += (textMetric.tmMaxCharWidth * 2);
3283
3284                 sz.cx = max(sz.cx, textMetric.tmMaxCharWidth * 3);
3285                 sz.cx = min(sz.cx,
3286                             infoPtr->clientWidth - editItem->textOffset + 2);
3287
3288                 SetWindowPos(infoPtr->hwndEdit,
3289                              HWND_TOP,
3290                              0,
3291                              0,
3292                              sz.cx,
3293                              editItem->rect.bottom - editItem->rect.top + 3,
3294                              SWP_NOMOVE | SWP_DRAWFRAME);
3295             }
3296
3297             if (hFont != 0)
3298             {
3299                 SelectObject(hdc, hOldFont);
3300             }
3301
3302             ReleaseDC(infoPtr->hwnd, hdc);
3303             break;
3304         }
3305
3306     default:
3307         return SendMessageA(GetParent(infoPtr->hwnd), WM_COMMAND, wParam, lParam);
3308     }
3309
3310     return 0;
3311 }
3312
3313 static HWND
3314 TREEVIEW_EditLabelA(TREEVIEW_INFO *infoPtr, HTREEITEM hItem)
3315 {
3316     HWND hwnd = infoPtr->hwnd;
3317     HWND hwndEdit;
3318     SIZE sz;
3319     TREEVIEW_ITEM *editItem = hItem;
3320     HINSTANCE hinst = GetWindowLongA(hwnd, GWL_HINSTANCE);
3321     HDC hdc;
3322     HFONT hOldFont=0;
3323     TEXTMETRICA textMetric;
3324
3325     TRACE("%x %p\n", (unsigned)hwnd, hItem);
3326     if (!TREEVIEW_ValidItem(infoPtr, editItem))
3327         return (HWND)NULL;
3328
3329     if (infoPtr->hwndEdit)
3330         return infoPtr->hwndEdit;
3331
3332     infoPtr->bLabelChanged = FALSE;
3333
3334     /* Make sure that edit item is selected */
3335     TREEVIEW_DoSelectItem(infoPtr, TVGN_CARET, hItem, TVC_UNKNOWN);
3336     TREEVIEW_EnsureVisible(infoPtr, hItem, TRUE);
3337
3338     TREEVIEW_UpdateDispInfo(infoPtr, editItem, TVIF_TEXT);
3339
3340     hdc = GetDC(hwnd);
3341     /* Select the font to get appropriate metric dimensions */
3342     if (infoPtr->hFont != 0)
3343     {
3344         hOldFont = SelectObject(hdc, infoPtr->hFont);
3345     }
3346
3347     /* Get string length in pixels */
3348     GetTextExtentPoint32A(hdc, editItem->pszText, strlen(editItem->pszText),
3349                           &sz);
3350
3351     /* Add Extra spacing for the next character */
3352     GetTextMetricsA(hdc, &textMetric);
3353     sz.cx += (textMetric.tmMaxCharWidth * 2);
3354
3355     sz.cx = max(sz.cx, textMetric.tmMaxCharWidth * 3);
3356     sz.cx = min(sz.cx, infoPtr->clientWidth - editItem->textOffset + 2);
3357
3358     if (infoPtr->hFont != 0)
3359     {
3360         SelectObject(hdc, hOldFont);
3361     }
3362
3363     ReleaseDC(hwnd, hdc);
3364     hwndEdit = CreateWindowExA(WS_EX_LEFT,
3365                                "EDIT",
3366                                0,
3367                                WS_CHILD | WS_BORDER | ES_AUTOHSCROLL |
3368                                WS_CLIPSIBLINGS | ES_WANTRETURN |
3369                                ES_LEFT, editItem->textOffset - 2,
3370                                editItem->rect.top - 1, sz.cx + 3,
3371                                editItem->rect.bottom -
3372                                editItem->rect.top + 3, hwnd, 0, hinst, 0);
3373 /* FIXME: (HMENU)IDTVEDIT,pcs->hInstance,0); */
3374
3375     infoPtr->hwndEdit = hwndEdit;
3376
3377     /* Get a 2D border. */
3378     SetWindowLongA(hwndEdit, GWL_EXSTYLE,
3379                    GetWindowLongA(hwndEdit, GWL_EXSTYLE) & ~WS_EX_CLIENTEDGE);
3380     SetWindowLongA(hwndEdit, GWL_STYLE,
3381                    GetWindowLongA(hwndEdit, GWL_STYLE) | WS_BORDER);
3382
3383     SendMessageA(hwndEdit, WM_SETFONT, TREEVIEW_FontForItem(infoPtr, editItem),
3384                  FALSE);
3385
3386     infoPtr->wpEditOrig = (WNDPROC)SetWindowLongA(hwndEdit, GWL_WNDPROC,
3387                                                   (DWORD)
3388                                                   TREEVIEW_Edit_SubclassProc);
3389
3390     if (TREEVIEW_BeginLabelEditNotify(infoPtr, editItem))
3391     {
3392         DestroyWindow(hwndEdit);
3393         infoPtr->hwndEdit = 0;
3394         return (HWND)NULL;
3395     }
3396
3397     infoPtr->selectedItem = hItem;
3398     SetWindowTextA(hwndEdit, editItem->pszText);
3399     SetFocus(hwndEdit);
3400     SendMessageA(hwndEdit, EM_SETSEL, 0, -1);
3401     ShowWindow(hwndEdit, SW_SHOW);
3402
3403     return hwndEdit;
3404 }
3405
3406
3407 static LRESULT
3408 TREEVIEW_EndEditLabelNow(TREEVIEW_INFO *infoPtr, BOOL bCancel)
3409 {
3410     HWND hwnd = infoPtr->hwnd;
3411     TREEVIEW_ITEM *editedItem = infoPtr->selectedItem;
3412     NMTVDISPINFOA tvdi;
3413     BOOL bCommit;
3414     char tmpText[1024] = { '\0' };
3415     int iLength = 0;
3416
3417     if (!infoPtr->hwndEdit)
3418         return FALSE;
3419
3420     tvdi.hdr.hwndFrom = hwnd;
3421     tvdi.hdr.idFrom = GetWindowLongA(hwnd, GWL_ID);
3422     tvdi.hdr.code = TVN_ENDLABELEDITA;
3423     tvdi.item.mask = 0;
3424     tvdi.item.hItem = editedItem;
3425     tvdi.item.state = editedItem->state;
3426     tvdi.item.lParam = editedItem->lParam;
3427
3428     if (!bCancel)
3429     {
3430         iLength = GetWindowTextA(infoPtr->hwndEdit, tmpText, 1023);
3431
3432         if (iLength >= 1023)
3433         {
3434             ERR("Insuficient space to retrieve new item label.");
3435         }
3436
3437         tvdi.item.pszText = tmpText;
3438         tvdi.item.cchTextMax = iLength + 1;
3439     }
3440     else
3441     {
3442         tvdi.item.pszText = NULL;
3443         tvdi.item.cchTextMax = 0;
3444     }
3445
3446     bCommit = (BOOL)SendMessageA(infoPtr->hwndNotify,
3447                                  WM_NOTIFY,
3448                                  (WPARAM)tvdi.hdr.idFrom, (LPARAM)&tvdi);
3449
3450     if (!bCancel && bCommit)    /* Apply the changes */
3451     {
3452         if (strcmp(tmpText, editedItem->pszText) != 0)
3453         {
3454             if (NULL == COMCTL32_ReAlloc(editedItem->pszText, iLength + 1))
3455             {
3456                 ERR("OutOfMemory, cannot allocate space for label");
3457                 DestroyWindow(infoPtr->hwndEdit);
3458                 infoPtr->hwndEdit = 0;
3459                 return FALSE;
3460             }
3461             else
3462             {
3463                 editedItem->cchTextMax = iLength + 1;
3464                 lstrcpyA(editedItem->pszText, tmpText);
3465             }
3466         }
3467     }
3468
3469     ShowWindow(infoPtr->hwndEdit, SW_HIDE);
3470     DestroyWindow(infoPtr->hwndEdit);
3471     infoPtr->hwndEdit = 0;
3472     return TRUE;
3473 }
3474
3475 static LRESULT
3476 TREEVIEW_HandleTimer(TREEVIEW_INFO *infoPtr, WPARAM wParam)
3477 {
3478     if (wParam != TV_EDIT_TIMER)
3479     {
3480         ERR("got unknown timer\n");
3481         return 1;
3482     }
3483
3484     KillTimer(infoPtr->hwnd, TV_EDIT_TIMER);
3485     infoPtr->Timer &= ~TV_EDIT_TIMER_SET;
3486
3487     TREEVIEW_EditLabelA(infoPtr, infoPtr->selectedItem);
3488
3489     return 0;
3490 }
3491
3492
3493 /* Mouse Tracking/Drag **************************************************/
3494
3495 /***************************************************************************
3496  * This is quite unusual piece of code, but that's how it's implemented in
3497  * Windows.
3498  */
3499 static LRESULT
3500 TREEVIEW_TrackMouse(TREEVIEW_INFO *infoPtr, POINT pt)
3501 {
3502     INT cxDrag = GetSystemMetrics(SM_CXDRAG);
3503     INT cyDrag = GetSystemMetrics(SM_CYDRAG);
3504     RECT r;
3505     MSG msg;
3506
3507     r.top = pt.y - cyDrag;
3508     r.left = pt.x - cxDrag;
3509     r.bottom = pt.y + cyDrag;
3510     r.right = pt.x + cxDrag;
3511
3512     SetCapture(infoPtr->hwnd);
3513
3514     while (1)
3515     {
3516         if (PeekMessageA(&msg, 0, 0, 0, PM_REMOVE | PM_NOYIELD))
3517         {
3518             if (msg.message == WM_MOUSEMOVE)
3519             {
3520                 pt.x = SLOWORD(msg.lParam);
3521                 pt.y = SHIWORD(msg.lParam);
3522                 if (PtInRect(&r, pt))
3523                     continue;
3524                 else
3525                 {
3526                     ReleaseCapture();
3527                     return 1;
3528                 }
3529             }
3530             else if (msg.message >= WM_LBUTTONDOWN &&
3531                      msg.message <= WM_RBUTTONDBLCLK)
3532             {
3533                 if (msg.message == WM_RBUTTONUP)
3534                     TREEVIEW_RButtonUp(infoPtr, &pt);
3535                 break;
3536             }
3537
3538             DispatchMessageA(&msg);
3539         }
3540
3541         if (GetCapture() != infoPtr->hwnd)
3542             return 0;
3543     }
3544
3545     ReleaseCapture();
3546     return 0;
3547 }
3548
3549
3550 static LRESULT
3551 TREEVIEW_LButtonDoubleClick(TREEVIEW_INFO *infoPtr, LPARAM lParam)
3552 {
3553     TREEVIEW_ITEM *wineItem;
3554     TVHITTESTINFO hit;
3555
3556     TRACE("\n");
3557     SetFocus(infoPtr->hwnd);
3558
3559     if (infoPtr->Timer & TV_EDIT_TIMER_SET)
3560     {
3561         /* If there is pending 'edit label' event - kill it now */
3562         KillTimer(infoPtr->hwnd, TV_EDIT_TIMER);
3563     }
3564
3565     hit.pt.x = SLOWORD(lParam);
3566     hit.pt.y = SHIWORD(lParam);
3567
3568     wineItem = (TREEVIEW_ITEM *)TREEVIEW_HitTest(infoPtr, &hit);
3569     if (!wineItem)
3570         return 0;
3571     TRACE("item %d\n", TREEVIEW_GetItemIndex(infoPtr, wineItem));
3572
3573     if (TREEVIEW_SendSimpleNotify(infoPtr, NM_DBLCLK) == FALSE)
3574     {                           /* FIXME! */
3575         switch (hit.flags)
3576         {
3577         case TVHT_ONITEMRIGHT:
3578             /* FIXME: we should not have sent NM_DBLCLK in this case. */
3579             break;
3580
3581         case TVHT_ONITEMINDENT:
3582             if (!(infoPtr->dwStyle & TVS_HASLINES))
3583             {
3584                 break;
3585             }
3586             else
3587             {
3588                 int level = hit.pt.x / infoPtr->uIndent;
3589                 if (!(infoPtr->dwStyle & TVS_LINESATROOT)) level++;
3590
3591                 while (wineItem->iLevel > level)
3592                 {
3593                     wineItem = wineItem->parent;
3594                 }
3595
3596                 /* fall through */
3597             }
3598
3599         case TVHT_ONITEMLABEL:
3600         case TVHT_ONITEMICON:
3601         case TVHT_ONITEMBUTTON:
3602             TREEVIEW_Toggle(infoPtr, wineItem, TRUE);
3603             break;
3604
3605         case TVHT_ONITEMSTATEICON:
3606            if (infoPtr->dwStyle & TVS_CHECKBOXES)
3607                TREEVIEW_ToggleItemState(infoPtr, wineItem);
3608            else
3609                TREEVIEW_Toggle(infoPtr, wineItem, TRUE);
3610            break;
3611         }
3612     }
3613     return TRUE;
3614 }
3615
3616
3617 static LRESULT
3618 TREEVIEW_LButtonDown(TREEVIEW_INFO *infoPtr, LPARAM lParam)
3619 {
3620     HWND hwnd = infoPtr->hwnd;
3621     TVHITTESTINFO ht;
3622     BOOL bTrack;
3623     HTREEITEM tempItem;
3624
3625     /* If Edit control is active - kill it and return.
3626      * The best way to do it is to set focus to itself.
3627      * Edit control subclassed procedure will automatically call
3628      * EndEditLabelNow.
3629      */
3630     if (infoPtr->hwndEdit)
3631     {
3632         SetFocus(hwnd);
3633         return 0;
3634     }
3635
3636     ht.pt.x = SLOWORD(lParam);
3637     ht.pt.y = SHIWORD(lParam);
3638
3639     TREEVIEW_HitTest(infoPtr, &ht);
3640     TRACE("item %d\n", TREEVIEW_GetItemIndex(infoPtr, ht.hItem));
3641
3642     /* update focusedItem and redraw both items */
3643     if(ht.hItem && (ht.flags & TVHT_ONITEM))
3644     {
3645         infoPtr->focusedItem = ht.hItem;
3646         InvalidateRect(hwnd, &(((HTREEITEM)(ht.hItem))->rect), TRUE);
3647
3648         if(infoPtr->selectedItem)
3649             InvalidateRect(hwnd, &(infoPtr->selectedItem->rect), TRUE);
3650     }
3651
3652     bTrack = (ht.flags & TVHT_ONITEM)
3653         && !(infoPtr->dwStyle & TVS_DISABLEDRAGDROP);
3654
3655     /* Send NM_CLICK right away */
3656     if (!bTrack)
3657         if (TREEVIEW_SendSimpleNotify(infoPtr, NM_CLICK))
3658             goto setfocus;
3659
3660     if (ht.flags & TVHT_ONITEMBUTTON)
3661     {
3662         TREEVIEW_Toggle(infoPtr, ht.hItem, TRUE);
3663         goto setfocus;
3664     }
3665     else if (bTrack)
3666     {   /* if TREEVIEW_TrackMouse == 1 dragging occured and the cursor left the dragged item's rectangle */
3667         if (TREEVIEW_TrackMouse(infoPtr, ht.pt))
3668         {
3669             TREEVIEW_SendTreeviewDnDNotify(infoPtr, TVN_BEGINDRAGA, ht.hItem,
3670                                            ht.pt);
3671             infoPtr->dropItem = ht.hItem;
3672
3673             /* clean up focusedItem as we dragged and won't select this item */
3674             if(infoPtr->focusedItem)
3675             {
3676                 /* refresh the item that was focused */
3677                 tempItem = infoPtr->focusedItem;
3678                 infoPtr->focusedItem = 0;
3679                 InvalidateRect(infoPtr->hwnd, &tempItem->rect, TRUE);
3680
3681                 /* refresh the selected item to return the filled background */
3682                 InvalidateRect(infoPtr->hwnd, &(infoPtr->selectedItem->rect), TRUE);
3683             }
3684
3685             return 0;
3686         }
3687     }
3688
3689     if (TREEVIEW_SendSimpleNotify(infoPtr, NM_CLICK))
3690         goto setfocus;
3691
3692     /* 
3693      * If the style allows editing and the node is already selected 
3694      * and the click occured on the item label...
3695      */
3696     if ((infoPtr->dwStyle & TVS_EDITLABELS) &&
3697                 (ht.flags & TVHT_ONITEMLABEL) && (infoPtr->selectedItem == ht.hItem))
3698     {
3699         if (infoPtr->Timer & TV_EDIT_TIMER_SET)
3700             KillTimer(hwnd, TV_EDIT_TIMER);
3701
3702         SetTimer(hwnd, TV_EDIT_TIMER, GetDoubleClickTime(), 0);
3703         infoPtr->Timer |= TV_EDIT_TIMER_SET;
3704     }
3705     else if (ht.flags & TVHT_ONITEM) /* select the item if the hit was inside of the icon or text */
3706     {
3707         /*
3708          * if we are TVS_SINGLEEXPAND then we want this single click to
3709          * do a bunch of things.
3710          */
3711         if((infoPtr->dwStyle & TVS_SINGLEEXPAND) &&
3712           (infoPtr->hwndEdit == 0))
3713         {
3714             TREEVIEW_ITEM *SelItem;
3715
3716             /*
3717              * Send the notification
3718              */
3719             TREEVIEW_SendTreeviewNotify(infoPtr, TVN_SINGLEEXPAND, TVIF_HANDLE | TVIF_PARAM,
3720                                 0, ht.hItem, 0);
3721
3722             /*
3723              * Close the previous selection all the way to the root
3724              * as long as the new selection is not a child
3725              */
3726             if((infoPtr->selectedItem)
3727                 && (infoPtr->selectedItem != ht.hItem))
3728             {
3729                 BOOL closeit = TRUE;
3730                 SelItem = ht.hItem;
3731
3732                 /* determine if the hitItem is a child of the currently selected item */
3733                 while(closeit && SelItem && TREEVIEW_ValidItem(infoPtr, SelItem) && (SelItem != infoPtr->root))
3734                 {
3735                     closeit = (SelItem != infoPtr->selectedItem);
3736                     SelItem = SelItem->parent;
3737                 }
3738
3739                 if(closeit)
3740                 {
3741                     if(TREEVIEW_ValidItem(infoPtr, infoPtr->selectedItem))
3742                         SelItem = infoPtr->selectedItem;
3743
3744                     while(SelItem && (SelItem != ht.hItem) && TREEVIEW_ValidItem(infoPtr, SelItem) && (SelItem != infoPtr->root))
3745                     {
3746                         TREEVIEW_Collapse(infoPtr, SelItem, FALSE, FALSE);
3747                         SelItem = SelItem->parent;
3748                     }
3749                 }
3750             }
3751
3752             /*
3753              * Expand the current item
3754              */
3755             TREEVIEW_Expand(infoPtr, ht.hItem, TVE_TOGGLE, FALSE);
3756         }
3757
3758         /* Select the current item */
3759         TREEVIEW_DoSelectItem(infoPtr, TVGN_CARET, ht.hItem, TVC_BYMOUSE);
3760     }
3761     else if (ht.flags & TVHT_ONITEMSTATEICON)
3762     {
3763         /* TVS_CHECKBOXES requires us to toggle the current state */
3764         if (infoPtr->dwStyle & TVS_CHECKBOXES)
3765             TREEVIEW_ToggleItemState(infoPtr, ht.hItem);
3766     }
3767
3768   setfocus:
3769     SetFocus(hwnd);
3770     return 0;
3771 }
3772
3773
3774 static LRESULT
3775 TREEVIEW_RButtonDown(TREEVIEW_INFO *infoPtr, LPARAM lParam)
3776 {
3777     TVHITTESTINFO ht;
3778
3779     if (infoPtr->hwndEdit)
3780     {
3781         SetFocus(infoPtr->hwnd);
3782         return 0;
3783     }
3784
3785     ht.pt.x = SLOWORD(lParam);
3786     ht.pt.y = SHIWORD(lParam);
3787
3788     TREEVIEW_HitTest(infoPtr, &ht);
3789
3790     if (TREEVIEW_TrackMouse(infoPtr, ht.pt))
3791     {
3792         if (ht.hItem)
3793         {
3794             TREEVIEW_SendTreeviewDnDNotify(infoPtr, TVN_BEGINRDRAGA, ht.hItem,
3795                                            ht.pt);
3796             infoPtr->dropItem = ht.hItem;
3797         }
3798     }
3799     else
3800     {
3801         SetFocus(infoPtr->hwnd);
3802         TREEVIEW_SendSimpleNotify(infoPtr, NM_RCLICK);
3803     }
3804
3805     return 0;
3806 }
3807
3808 static LRESULT
3809 TREEVIEW_RButtonUp(TREEVIEW_INFO *infoPtr, LPPOINT pPt)
3810 {
3811     return 0;
3812 }
3813
3814
3815 static LRESULT
3816 TREEVIEW_CreateDragImage(TREEVIEW_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
3817 {
3818     TREEVIEW_ITEM *dragItem = (HTREEITEM)lParam;
3819     INT cx, cy;
3820     HDC hdc, htopdc;
3821     HWND hwtop;
3822     HBITMAP hbmp, hOldbmp;
3823     SIZE size;
3824     RECT rc;
3825     HFONT hOldFont;
3826
3827     TRACE("\n");
3828
3829     if (!(infoPtr->himlNormal))
3830         return 0;
3831
3832     if (!dragItem || !TREEVIEW_ValidItem(infoPtr, dragItem))
3833         return 0;
3834
3835     TREEVIEW_UpdateDispInfo(infoPtr, dragItem, TVIF_TEXT);
3836
3837     hwtop = GetDesktopWindow();
3838     htopdc = GetDC(hwtop);
3839     hdc = CreateCompatibleDC(htopdc);
3840
3841     hOldFont = SelectObject(hdc, infoPtr->hFont);
3842     GetTextExtentPoint32A(hdc, dragItem->pszText, lstrlenA(dragItem->pszText),
3843                           &size);
3844     TRACE("%ld %ld %s %d\n", size.cx, size.cy, dragItem->pszText,
3845           lstrlenA(dragItem->pszText));
3846     hbmp = CreateCompatibleBitmap(htopdc, size.cx, size.cy);
3847     hOldbmp = SelectObject(hdc, hbmp);
3848
3849     ImageList_GetIconSize(infoPtr->himlNormal, &cx, &cy);
3850     size.cx += cx;
3851     if (cy > size.cy)
3852         size.cy = cy;
3853
3854     infoPtr->dragList = ImageList_Create(size.cx, size.cy, ILC_COLOR, 10, 10);
3855     ImageList_Draw(infoPtr->himlNormal, dragItem->iImage, hdc, 0, 0,
3856                    ILD_NORMAL);
3857
3858 /*
3859  ImageList_GetImageInfo (infoPtr->himlNormal, dragItem->hItem, &iminfo);
3860  ImageList_AddMasked (infoPtr->dragList, iminfo.hbmImage, CLR_DEFAULT);
3861 */
3862
3863 /* draw item text */
3864
3865     SetRect(&rc, cx, 0, size.cx, size.cy);
3866     DrawTextA(hdc, dragItem->pszText, lstrlenA(dragItem->pszText), &rc,
3867               DT_LEFT);
3868     SelectObject(hdc, hOldFont);
3869     SelectObject(hdc, hOldbmp);
3870
3871     ImageList_Add(infoPtr->dragList, hbmp, 0);
3872
3873     DeleteDC(hdc);
3874     DeleteObject(hbmp);
3875     ReleaseDC(hwtop, htopdc);
3876
3877     return (LRESULT)infoPtr->dragList;
3878 }
3879
3880 /* Selection ************************************************************/
3881
3882 static LRESULT
3883 TREEVIEW_DoSelectItem(TREEVIEW_INFO *infoPtr, INT action, HTREEITEM newSelect,
3884                       INT cause)
3885 {
3886     TREEVIEW_ITEM *prevSelect;
3887     RECT rcFocused;
3888
3889     assert(newSelect == NULL || TREEVIEW_ValidItem(infoPtr, newSelect));
3890
3891     TRACE("Entering item %p (%s), flag %x, cause %x, state %d\n",
3892           newSelect, TREEVIEW_ItemName(newSelect), action, cause,
3893           newSelect ? newSelect->state : 0);
3894
3895     /* reset and redraw focusedItem if focusedItem was set so we don't */
3896     /* have to worry about the previously focused item when we set a new one */
3897     if(infoPtr->focusedItem)
3898     {
3899         rcFocused = (infoPtr->focusedItem)->rect;
3900         infoPtr->focusedItem = 0;
3901         InvalidateRect(infoPtr->hwnd, &rcFocused, TRUE);
3902     }
3903
3904     switch (action)
3905     {
3906     case TVGN_CARET:
3907         prevSelect = infoPtr->selectedItem;
3908
3909         if (prevSelect == newSelect)
3910             return FALSE;
3911
3912         if (TREEVIEW_SendTreeviewNotify(infoPtr,
3913                                         TVN_SELCHANGINGA,
3914                                         cause,
3915                                         TVIF_HANDLE | TVIF_STATE | TVIF_PARAM,
3916                                         prevSelect,
3917                                         newSelect))
3918             return FALSE;
3919
3920         if (prevSelect)
3921             prevSelect->state &= ~TVIS_SELECTED;
3922         if (newSelect)
3923             newSelect->state |= TVIS_SELECTED;
3924
3925         infoPtr->selectedItem = newSelect;
3926
3927         TREEVIEW_EnsureVisible(infoPtr, infoPtr->selectedItem, FALSE);
3928
3929         TREEVIEW_SendTreeviewNotify(infoPtr,
3930                                     TVN_SELCHANGEDA,
3931                                     cause,
3932                                     TVIF_HANDLE | TVIF_STATE | TVIF_PARAM,
3933                                     prevSelect,
3934                                     newSelect);
3935         TREEVIEW_Invalidate(infoPtr, prevSelect);
3936         TREEVIEW_Invalidate(infoPtr, newSelect);
3937         break;
3938
3939     case TVGN_DROPHILITE:
3940         prevSelect = infoPtr->dropItem;
3941
3942         if (prevSelect)
3943             prevSelect->state &= ~TVIS_DROPHILITED;
3944
3945         infoPtr->dropItem = newSelect;
3946
3947         if (newSelect)
3948             newSelect->state |= TVIS_DROPHILITED;
3949
3950         TREEVIEW_Invalidate(infoPtr, prevSelect);
3951         TREEVIEW_Invalidate(infoPtr, newSelect);
3952         break;
3953
3954     case TVGN_FIRSTVISIBLE:
3955         TREEVIEW_EnsureVisible(infoPtr, newSelect, FALSE);
3956         TREEVIEW_SetFirstVisible(infoPtr, newSelect, TRUE);
3957         TREEVIEW_Invalidate(infoPtr, NULL);
3958         break;
3959     }
3960
3961     TRACE("Leaving state %d\n", newSelect ? newSelect->state : 0);
3962     return TRUE;
3963 }
3964
3965 /* FIXME: handle NM_KILLFOCUS etc */
3966 static LRESULT
3967 TREEVIEW_SelectItem(TREEVIEW_INFO *infoPtr, INT wParam, HTREEITEM item)
3968 {
3969     if (item != NULL && !TREEVIEW_ValidItem(infoPtr, item))
3970         return FALSE;
3971
3972     TRACE("%p (%s) %d\n", item, TREEVIEW_ItemName(item), wParam);
3973
3974     if (!TREEVIEW_DoSelectItem(infoPtr, wParam, item, TVC_UNKNOWN))
3975         return FALSE;
3976
3977     return TRUE;
3978 }
3979
3980 /* Scrolling ************************************************************/
3981
3982 static LRESULT
3983 TREEVIEW_EnsureVisible(TREEVIEW_INFO *infoPtr, HTREEITEM item, BOOL bHScroll)
3984 {
3985     HTREEITEM newFirstVisible = NULL;
3986     int visible_pos;
3987
3988     if (!TREEVIEW_ValidItem(infoPtr, item))
3989         return FALSE;
3990
3991     if (!ISVISIBLE(item))
3992     {
3993         /* Expand parents as necessary. */
3994         HTREEITEM parent;
3995
3996         /* see if we are trying to ensure that root is vislble */
3997         if((item != infoPtr->root) && TREEVIEW_ValidItem(infoPtr, item))
3998           parent = item->parent;
3999         else
4000           parent = item; /* this item is the topmost item */
4001
4002         while (parent != infoPtr->root)
4003         {
4004             if (!(parent->state & TVIS_EXPANDED))
4005                 TREEVIEW_Expand(infoPtr, parent, FALSE, FALSE);
4006
4007             parent = parent->parent;
4008         }
4009     }
4010
4011     TRACE("%p (%s) %ld - %ld\n", item, TREEVIEW_ItemName(item), item->visibleOrder,
4012           infoPtr->firstVisible->visibleOrder);
4013
4014     visible_pos = item->visibleOrder - infoPtr->firstVisible->visibleOrder;
4015
4016     if (visible_pos < 0)
4017     {
4018         /* item is before the start of the list: put it at the top. */
4019         newFirstVisible = item;
4020     }
4021     else if (visible_pos >= TREEVIEW_GetVisibleCount(infoPtr)
4022              /* Sometimes, before we are displayed, GVC is 0, causing us to
4023               * spuriously scroll up. */
4024              && visible_pos > 0)
4025     {
4026         /* item is past the end of the list. */
4027         int scroll = visible_pos - TREEVIEW_GetVisibleCount(infoPtr);
4028
4029         newFirstVisible = TREEVIEW_GetListItem(infoPtr, infoPtr->firstVisible,
4030                                                scroll + 1);
4031     }
4032
4033     if (bHScroll)
4034     {
4035         /* Scroll window so item's text is visible as much as possible */
4036         /* Calculation of amount of extra space is taken from EditLabel code */
4037         INT pos, x;
4038         TEXTMETRICA textMetric;
4039         HDC hdc = GetWindowDC(infoPtr->hwnd);
4040
4041         x = item->textWidth;
4042
4043         GetTextMetricsA(hdc, &textMetric);
4044         ReleaseDC(infoPtr->hwnd, hdc);
4045
4046         x += (textMetric.tmMaxCharWidth * 2);
4047         x = max(x, textMetric.tmMaxCharWidth * 3);
4048
4049         if (item->textOffset < 0)
4050            pos = item->textOffset;
4051         else if (item->textOffset + x > infoPtr->clientWidth)
4052         {
4053            if (x > infoPtr->clientWidth)
4054               pos = item->textOffset;
4055            else
4056               pos = item->textOffset + x - infoPtr->clientWidth;
4057         }
4058         else
4059            pos = 0;
4060
4061         TREEVIEW_HScroll(infoPtr, MAKEWPARAM(SB_THUMBPOSITION, infoPtr->scrollX + pos));
4062     }
4063
4064     if (newFirstVisible != NULL && newFirstVisible != infoPtr->firstVisible)
4065     {
4066         TREEVIEW_SetFirstVisible(infoPtr, newFirstVisible, TRUE);
4067
4068         return TRUE;
4069     }
4070
4071     return FALSE;
4072 }
4073
4074 static VOID
4075 TREEVIEW_SetFirstVisible(TREEVIEW_INFO *infoPtr,
4076                          TREEVIEW_ITEM *newFirstVisible,
4077                          BOOL bUpdateScrollPos)
4078 {
4079     int gap_size;
4080
4081     TRACE("%p: %s\n", newFirstVisible, TREEVIEW_ItemName(newFirstVisible));
4082
4083     if (newFirstVisible != NULL)
4084     {
4085         /* Prevent an empty gap from appearing at the bottom... */
4086         gap_size = TREEVIEW_GetVisibleCount(infoPtr)
4087             - infoPtr->maxVisibleOrder + newFirstVisible->visibleOrder;
4088
4089         if (gap_size > 0)
4090         {
4091             newFirstVisible = TREEVIEW_GetListItem(infoPtr, newFirstVisible,
4092                                                    -gap_size);
4093
4094             /* ... unless we just don't have enough items. */
4095             if (newFirstVisible == NULL)
4096                 newFirstVisible = infoPtr->root->firstChild;
4097         }
4098     }
4099
4100     if (infoPtr->firstVisible != newFirstVisible)
4101     {
4102         if (infoPtr->firstVisible == NULL || newFirstVisible == NULL)
4103         {
4104             infoPtr->firstVisible = newFirstVisible;
4105             TREEVIEW_Invalidate(infoPtr, NULL);
4106         }
4107         else
4108         {
4109             TREEVIEW_ITEM *item;
4110             int scroll = infoPtr->uItemHeight *
4111                          (infoPtr->firstVisible->visibleOrder
4112                           - newFirstVisible->visibleOrder);
4113
4114             infoPtr->firstVisible = newFirstVisible;
4115
4116             for (item = infoPtr->root->firstChild; item != NULL;
4117                  item = TREEVIEW_GetNextListItem(infoPtr, item))
4118             {
4119                item->rect.top += scroll;
4120                item->rect.bottom += scroll;
4121             }
4122
4123             if (bUpdateScrollPos)
4124                 SetScrollPos(infoPtr->hwnd, SB_VERT,
4125                               newFirstVisible->visibleOrder, TRUE);
4126
4127             ScrollWindow(infoPtr->hwnd, 0, scroll, NULL, NULL);
4128             UpdateWindow(infoPtr->hwnd);
4129         }
4130     }
4131 }
4132
4133 /************************************************************************
4134  * VScroll is always in units of visible items. i.e. we always have a
4135  * visible item aligned to the top of the control. (Unless we have no
4136  * items at all.)
4137  */
4138 static LRESULT
4139 TREEVIEW_VScroll(TREEVIEW_INFO *infoPtr, WPARAM wParam)
4140 {
4141     TREEVIEW_ITEM *oldFirstVisible = infoPtr->firstVisible;
4142     TREEVIEW_ITEM *newFirstVisible = NULL;
4143
4144     int nScrollCode = LOWORD(wParam);
4145
4146     TRACE("wp %x\n", wParam);
4147
4148     if (!(infoPtr->uInternalStatus & TV_VSCROLL))
4149         return 0;
4150
4151     if (infoPtr->hwndEdit)
4152         SetFocus(infoPtr->hwnd);
4153
4154     if (!oldFirstVisible)
4155     {
4156         assert(infoPtr->root->firstChild == NULL);
4157         return 0;
4158     }
4159
4160     switch (nScrollCode)
4161     {
4162     case SB_TOP:
4163         newFirstVisible = infoPtr->root->firstChild;
4164         break;
4165
4166     case SB_BOTTOM:
4167         newFirstVisible = TREEVIEW_GetLastListItem(infoPtr, infoPtr->root);
4168         break;
4169
4170     case SB_LINEUP:
4171         newFirstVisible = TREEVIEW_GetPrevListItem(infoPtr, oldFirstVisible);
4172         break;
4173
4174     case SB_LINEDOWN:
4175         newFirstVisible = TREEVIEW_GetNextListItem(infoPtr, oldFirstVisible);
4176         break;
4177
4178     case SB_PAGEUP:
4179         newFirstVisible = TREEVIEW_GetListItem(infoPtr, oldFirstVisible,
4180                                                -max(1, TREEVIEW_GetVisibleCount(infoPtr)));
4181         break;
4182
4183     case SB_PAGEDOWN:
4184         newFirstVisible = TREEVIEW_GetListItem(infoPtr, oldFirstVisible,
4185                                                max(1, TREEVIEW_GetVisibleCount(infoPtr)));
4186         break;
4187
4188     case SB_THUMBTRACK:
4189     case SB_THUMBPOSITION:
4190         newFirstVisible = TREEVIEW_GetListItem(infoPtr,
4191                                                infoPtr->root->firstChild,
4192                                                (LONG)(SHORT)HIWORD(wParam));
4193         break;
4194
4195     case SB_ENDSCROLL:
4196         return 0;
4197     }
4198
4199     if (newFirstVisible != NULL)
4200     {
4201         if (newFirstVisible != oldFirstVisible)
4202             TREEVIEW_SetFirstVisible(infoPtr, newFirstVisible,
4203                                   nScrollCode != SB_THUMBTRACK);
4204         else if (nScrollCode == SB_THUMBPOSITION)
4205             SetScrollPos(infoPtr->hwnd, SB_VERT,
4206                          newFirstVisible->visibleOrder, TRUE);
4207     }
4208
4209     return 0;
4210 }
4211
4212 static LRESULT
4213 TREEVIEW_HScroll(TREEVIEW_INFO *infoPtr, WPARAM wParam)
4214 {
4215     int maxWidth;
4216     int scrollX = infoPtr->scrollX;
4217     int nScrollCode = LOWORD(wParam);
4218
4219     TRACE("wp %x\n", wParam);
4220
4221     if (!(infoPtr->uInternalStatus & TV_HSCROLL))
4222         return FALSE;
4223
4224     if (infoPtr->hwndEdit)
4225         SetFocus(infoPtr->hwnd);
4226
4227     maxWidth = infoPtr->treeWidth - infoPtr->clientWidth;
4228     /* shall never occur */
4229     if (maxWidth <= 0)
4230     {
4231        scrollX = 0;
4232        goto scroll;
4233     }
4234
4235     switch (nScrollCode)
4236     {
4237     case SB_LINELEFT:
4238         scrollX -= infoPtr->uItemHeight;
4239         break;
4240     case SB_LINERIGHT:
4241         scrollX += infoPtr->uItemHeight;
4242         break;
4243     case SB_PAGELEFT:
4244         scrollX -= infoPtr->clientWidth;
4245         break;
4246     case SB_PAGERIGHT:
4247         scrollX += infoPtr->clientWidth;
4248         break;
4249
4250     case SB_THUMBTRACK:
4251     case SB_THUMBPOSITION:
4252         scrollX = (int)(SHORT)HIWORD(wParam);
4253         break;
4254
4255     case SB_ENDSCROLL:
4256        return 0;
4257     }
4258
4259     if (scrollX > maxWidth)
4260         scrollX = maxWidth;
4261     else if (scrollX < 0)
4262         scrollX = 0;
4263
4264 scroll:
4265     if (scrollX != infoPtr->scrollX)
4266     {
4267         TREEVIEW_ITEM *item;
4268         LONG scroll_pixels = infoPtr->scrollX - scrollX;
4269         
4270         for (item = infoPtr->root->firstChild; item != NULL;
4271              item = TREEVIEW_GetNextListItem(infoPtr, item))
4272         {
4273            item->linesOffset += scroll_pixels;
4274            item->stateOffset += scroll_pixels;
4275            item->imageOffset += scroll_pixels;
4276            item->textOffset  += scroll_pixels;
4277         }
4278
4279         ScrollWindow(infoPtr->hwnd, scroll_pixels, 0, NULL, NULL);
4280         infoPtr->scrollX = scrollX;
4281         UpdateWindow(infoPtr->hwnd);
4282     }
4283
4284     if (nScrollCode != SB_THUMBTRACK)
4285        SetScrollPos(infoPtr->hwnd, SB_HORZ, scrollX, TRUE);
4286
4287     return 0;
4288 }
4289
4290 static LRESULT
4291 TREEVIEW_MouseWheel(TREEVIEW_INFO *infoPtr, WPARAM wParam)
4292 {
4293     short gcWheelDelta;
4294     UINT pulScrollLines = 3;
4295
4296     if (infoPtr->firstVisible == NULL)
4297         return TRUE;
4298
4299     SystemParametersInfoW(SPI_GETWHEELSCROLLLINES, 0, &pulScrollLines, 0);
4300
4301     gcWheelDelta = -(short)HIWORD(wParam);
4302     pulScrollLines *= (gcWheelDelta / WHEEL_DELTA);
4303
4304     if (abs(gcWheelDelta) >= WHEEL_DELTA && pulScrollLines)
4305     {
4306         int newDy = infoPtr->firstVisible->visibleOrder + pulScrollLines;
4307         int maxDy = infoPtr->maxVisibleOrder;
4308
4309         if (newDy > maxDy)
4310             newDy = maxDy;
4311
4312         if (newDy < 0)
4313             newDy = 0;
4314
4315         TREEVIEW_VScroll(infoPtr, MAKEWPARAM(SB_THUMBPOSITION, newDy));
4316     }
4317     return TRUE;
4318 }
4319
4320 /* Create/Destroy *******************************************************/
4321
4322 static LRESULT
4323 TREEVIEW_Create(HWND hwnd)
4324
4325     RECT rcClient;
4326     TREEVIEW_INFO *infoPtr;
4327
4328     TRACE("wnd %x, style %lx\n", hwnd, GetWindowLongA(hwnd, GWL_STYLE));
4329
4330     infoPtr = (TREEVIEW_INFO *)COMCTL32_Alloc(sizeof(TREEVIEW_INFO));
4331  
4332     if (infoPtr == NULL)
4333     {
4334         ERR("could not allocate info memory!\n");
4335         return 0;
4336     }
4337
4338     SetWindowLongA(hwnd, 0, (DWORD)infoPtr);
4339
4340     infoPtr->hwnd = hwnd;
4341     infoPtr->dwStyle = GetWindowLongA(hwnd, GWL_STYLE);
4342     infoPtr->uInternalStatus = 0;
4343     infoPtr->Timer = 0;
4344     infoPtr->uNumItems = 0;
4345     infoPtr->cdmode = 0;
4346     infoPtr->uScrollTime = 300; /* milliseconds */
4347     infoPtr->bRedraw = TRUE;
4348
4349     GetClientRect(hwnd, &rcClient);
4350
4351     /* No scroll bars yet. */
4352     infoPtr->clientWidth = rcClient.right;
4353     infoPtr->clientHeight = rcClient.bottom;
4354
4355     infoPtr->treeWidth = 0;
4356     infoPtr->treeHeight = 0;
4357
4358     infoPtr->uIndent = 19;
4359     infoPtr->selectedItem = 0;
4360     infoPtr->focusedItem = 0;
4361     /* hotItem? */
4362     infoPtr->firstVisible = 0;
4363     infoPtr->maxVisibleOrder = 0;
4364     infoPtr->dropItem = 0;
4365     infoPtr->insertMarkItem = 0;
4366     infoPtr->insertBeforeorAfter = 0;
4367     /* dragList */
4368
4369     infoPtr->scrollX = 0;
4370
4371     infoPtr->clrBk = GetSysColor(COLOR_WINDOW);
4372     infoPtr->clrText = -1;      /* use system color */
4373     infoPtr->clrLine = RGB(128, 128, 128);
4374     infoPtr->clrInsertMark = GetSysColor(COLOR_BTNTEXT);
4375
4376     /* hwndToolTip */
4377
4378     infoPtr->hwndEdit = 0;
4379     infoPtr->wpEditOrig = NULL;
4380     infoPtr->bIgnoreEditKillFocus = FALSE;
4381     infoPtr->bLabelChanged = FALSE;
4382
4383     infoPtr->himlNormal = NULL;
4384     infoPtr->himlState = NULL;
4385     infoPtr->normalImageWidth = 0;
4386     infoPtr->normalImageHeight = 0;
4387     infoPtr->stateImageWidth = 0;
4388     infoPtr->stateImageHeight = 0;
4389
4390     infoPtr->items = DPA_Create(16);
4391
4392     infoPtr->hFont = GetStockObject(DEFAULT_GUI_FONT);
4393     infoPtr->hBoldFont = TREEVIEW_CreateBoldFont(infoPtr->hFont);
4394
4395     infoPtr->uItemHeight = TREEVIEW_NaturalHeight(infoPtr);
4396
4397     infoPtr->root = TREEVIEW_AllocateItem(infoPtr);
4398     infoPtr->root->state = TVIS_EXPANDED;
4399     infoPtr->root->iLevel = -1;
4400     infoPtr->root->visibleOrder = -1;
4401
4402     infoPtr->hwndNotify = GetParent(hwnd);
4403 #if 0
4404     infoPtr->bTransparent = ( GetWindowLongA( hwnd, GWL_STYLE) & TBSTYLE_FLAT);
4405 #endif
4406
4407     infoPtr->hwndToolTip = 0;
4408     if (!(infoPtr->dwStyle & TVS_NOTOOLTIPS))
4409         infoPtr->hwndToolTip = COMCTL32_CreateToolTip(hwnd);
4410
4411     if (infoPtr->dwStyle & TVS_CHECKBOXES)
4412     {
4413         RECT rc;
4414         HBITMAP hbm, hbmOld;
4415         HDC hdc;
4416         int nIndex;
4417
4418         infoPtr->himlState =
4419             ImageList_Create(16, 16, ILC_COLOR | ILC_MASK, 3, 0);
4420
4421         hdc = CreateCompatibleDC(0);
4422         hbm = CreateCompatibleBitmap(hdc, 48, 16);
4423         hbmOld = SelectObject(hdc, hbm);
4424
4425         rc.left  = 0;   rc.top    = 0;
4426         rc.right = 48;  rc.bottom = 16;
4427         FillRect(hdc, &rc, (HBRUSH)(COLOR_WINDOW+1));
4428
4429         rc.left  = 18;   rc.top    = 2;
4430         rc.right = 30;   rc.bottom = 14;
4431         DrawFrameControl(hdc, &rc, DFC_BUTTON,
4432                           DFCS_BUTTONCHECK|DFCS_FLAT);
4433
4434         rc.left  = 34;   rc.right  = 46;
4435         DrawFrameControl(hdc, &rc, DFC_BUTTON,
4436                           DFCS_BUTTONCHECK|DFCS_FLAT|DFCS_CHECKED);
4437
4438         nIndex = ImageList_AddMasked(infoPtr->himlState, hbm,
4439                                       GetSysColor(COLOR_WINDOW));
4440         TRACE("chckbox index %d\n", nIndex);
4441         SelectObject(hdc, hbmOld);
4442         DeleteObject(hbm);
4443         DeleteDC(hdc);
4444
4445         infoPtr->stateImageWidth = 16;
4446         infoPtr->stateImageHeight = 16;
4447     }
4448     return 0;
4449 }
4450
4451
4452 static LRESULT
4453 TREEVIEW_Destroy(TREEVIEW_INFO *infoPtr)
4454 {
4455     TRACE("\n");
4456
4457     TREEVIEW_RemoveTree(infoPtr);
4458
4459     /* tool tip is automatically destroyed: we are its owner */
4460
4461     /* Restore original windproc. */
4462     if (infoPtr->hwndEdit)
4463         SetWindowLongA(infoPtr->hwndEdit, GWL_WNDPROC,
4464                        (LONG)infoPtr->wpEditOrig);
4465
4466     /* Deassociate treeview from the window before doing anything drastic. */
4467     SetWindowLongA(infoPtr->hwnd, 0, (LONG)NULL);
4468
4469     DeleteObject(infoPtr->hBoldFont);
4470     COMCTL32_Free(infoPtr);
4471
4472     return 0;
4473 }
4474
4475 /* Miscellaneous Messages ***********************************************/
4476
4477 static LRESULT
4478 TREEVIEW_ScrollKeyDown(TREEVIEW_INFO *infoPtr, WPARAM key)
4479 {
4480     static const struct
4481     {
4482         unsigned char code;
4483     }
4484     scroll[] =
4485     {
4486 #define SCROLL_ENTRY(dir, code) { ((dir) << 7) | (code) }
4487         SCROLL_ENTRY(SB_VERT, SB_PAGEUP),       /* VK_PRIOR */
4488         SCROLL_ENTRY(SB_VERT, SB_PAGEDOWN),     /* VK_NEXT */
4489         SCROLL_ENTRY(SB_VERT, SB_BOTTOM),       /* VK_END */
4490         SCROLL_ENTRY(SB_VERT, SB_TOP),          /* VK_HOME */
4491         SCROLL_ENTRY(SB_HORZ, SB_LINEUP),       /* VK_LEFT */
4492         SCROLL_ENTRY(SB_VERT, SB_LINEUP),       /* VK_UP */
4493         SCROLL_ENTRY(SB_HORZ, SB_LINEDOWN),     /* VK_RIGHT */
4494         SCROLL_ENTRY(SB_VERT, SB_LINEDOWN)      /* VK_DOWN */
4495 #undef SCROLL_ENTRY
4496     };
4497
4498     if (key >= VK_PRIOR && key <= VK_DOWN)
4499     {
4500         unsigned char code = scroll[key - VK_PRIOR].code;
4501
4502         (((code & (1 << 7)) == (SB_HORZ << 7))
4503          ? TREEVIEW_HScroll
4504          : TREEVIEW_VScroll)(infoPtr, code & 0x7F);
4505     }
4506
4507     return 0;
4508 }
4509
4510 /************************************************************************
4511  *        TREEVIEW_KeyDown
4512  *
4513  * VK_UP       Move selection to the previous non-hidden item.
4514  * VK_DOWN     Move selection to the next non-hidden item.
4515  * VK_HOME     Move selection to the first item.
4516  * VK_END      Move selection to the last item.
4517  * VK_LEFT     If expanded then collapse, otherwise move to parent.
4518  * VK_RIGHT    If collapsed then expand, otherwise move to first child.
4519  * VK_ADD      Expand.
4520  * VK_SUBTRACT Collapse.
4521  * VK_MULTIPLY Expand all.
4522  * VK_PRIOR    Move up GetVisibleCount items.
4523  * VK_NEXT     Move down GetVisibleCount items.
4524  * VK_BACK     Move to parent.
4525  * CTRL-Left,Right,Up,Down,PgUp,PgDown,Home,End: Scroll without changing selection
4526  */
4527 static LRESULT
4528 TREEVIEW_KeyDown(TREEVIEW_INFO *infoPtr, WPARAM wParam)
4529 {
4530     /* If it is non-NULL and different, it will be selected and visible. */
4531     TREEVIEW_ITEM *newSelection = NULL;
4532
4533     TREEVIEW_ITEM *prevItem = infoPtr->selectedItem;
4534
4535     TRACE("%x\n", wParam);
4536
4537     if (prevItem == NULL)
4538         return FALSE;
4539
4540     if (GetAsyncKeyState(VK_CONTROL) & 0x8000)
4541         return TREEVIEW_ScrollKeyDown(infoPtr, wParam);
4542
4543     switch (wParam)
4544     {
4545     case VK_UP:
4546         newSelection = TREEVIEW_GetPrevListItem(infoPtr, prevItem);
4547         if (!newSelection)
4548             newSelection = infoPtr->root->firstChild;
4549         break;
4550
4551     case VK_DOWN:
4552         newSelection = TREEVIEW_GetNextListItem(infoPtr, prevItem);
4553         break;
4554
4555     case VK_HOME:
4556         newSelection = infoPtr->root->firstChild;
4557         break;
4558
4559     case VK_END:
4560         newSelection = TREEVIEW_GetLastListItem(infoPtr, infoPtr->root);
4561         break;
4562
4563     case VK_LEFT:
4564         if (prevItem->state & TVIS_EXPANDED)
4565         {
4566             TREEVIEW_Collapse(infoPtr, prevItem, FALSE, TRUE);
4567         }
4568         else if (prevItem->parent != infoPtr->root)
4569         {
4570             newSelection = prevItem->parent;
4571         }
4572         break;
4573
4574     case VK_RIGHT:
4575         if (TREEVIEW_HasChildren(infoPtr, prevItem))
4576         {
4577             if (!(prevItem->state & TVIS_EXPANDED))
4578                 TREEVIEW_Expand(infoPtr, prevItem, FALSE, TRUE);
4579             else
4580             {
4581                 newSelection = prevItem->firstChild;
4582             }
4583         }
4584
4585         break;
4586
4587     case VK_MULTIPLY:
4588         TREEVIEW_ExpandAll(infoPtr, prevItem);
4589         break;
4590
4591     case VK_ADD:
4592         if (!(prevItem->state & TVIS_EXPANDED))
4593             TREEVIEW_Expand(infoPtr, prevItem, FALSE, TRUE);
4594         break;
4595
4596     case VK_SUBTRACT:
4597         if (prevItem->state & TVIS_EXPANDED)
4598             TREEVIEW_Collapse(infoPtr, prevItem, FALSE, TRUE);
4599         break;
4600
4601     case VK_PRIOR:
4602         newSelection
4603             = TREEVIEW_GetListItem(infoPtr, prevItem,
4604                                    -TREEVIEW_GetVisibleCount(infoPtr));
4605         break;
4606
4607     case VK_NEXT:
4608         newSelection
4609             = TREEVIEW_GetListItem(infoPtr, prevItem,
4610                                    TREEVIEW_GetVisibleCount(infoPtr));
4611         break;
4612
4613     case VK_BACK:
4614         newSelection = prevItem->parent;
4615         if (newSelection == infoPtr->root)
4616             newSelection = NULL;
4617         break;
4618
4619     case VK_SPACE:
4620         if (infoPtr->dwStyle & TVS_CHECKBOXES)
4621             TREEVIEW_ToggleItemState(infoPtr, prevItem);
4622         break;
4623     }
4624
4625     if (newSelection && newSelection != prevItem)
4626     {
4627         if (TREEVIEW_DoSelectItem(infoPtr, TVGN_CARET, newSelection,
4628                                   TVC_BYKEYBOARD))
4629         {
4630             TREEVIEW_EnsureVisible(infoPtr, newSelection, FALSE);
4631         }
4632     }
4633
4634     return FALSE;
4635 }
4636
4637 static LRESULT
4638 TREEVIEW_Size(TREEVIEW_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
4639 {
4640     if (wParam == SIZE_RESTORED)
4641     {
4642         infoPtr->clientWidth  = SLOWORD(lParam);
4643         infoPtr->clientHeight = SHIWORD(lParam);
4644
4645         TREEVIEW_RecalculateVisibleOrder(infoPtr, NULL);
4646         TREEVIEW_SetFirstVisible(infoPtr, infoPtr->firstVisible, TRUE);
4647         TREEVIEW_UpdateScrollBars(infoPtr);
4648     }
4649     else
4650     {
4651         FIXME("WM_SIZE flag %x %lx not handled\n", wParam, lParam);
4652     }
4653
4654     TREEVIEW_Invalidate(infoPtr, NULL);
4655     return 0;
4656 }
4657
4658 static LRESULT
4659 TREEVIEW_StyleChanged(TREEVIEW_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
4660 {
4661     TRACE("(%x %lx)\n", wParam, lParam);
4662
4663     if (wParam == GWL_STYLE)
4664     {
4665        DWORD dwNewStyle = ((LPSTYLESTRUCT)lParam)->styleNew;
4666
4667        /* we have to take special care about tooltips */
4668        if ((infoPtr->dwStyle ^ dwNewStyle) & TVS_NOTOOLTIPS)
4669        {
4670           if (infoPtr->dwStyle & TVS_NOTOOLTIPS)
4671           {
4672               infoPtr->hwndToolTip = COMCTL32_CreateToolTip(infoPtr->hwnd);
4673               TRACE("\n");
4674           }
4675           else
4676           {
4677              DestroyWindow(infoPtr->hwndToolTip);
4678              infoPtr->hwndToolTip = 0;
4679           }
4680        }
4681
4682        infoPtr->dwStyle = dwNewStyle;
4683     }
4684
4685     TREEVIEW_UpdateSubTree(infoPtr, infoPtr->root);
4686     TREEVIEW_UpdateScrollBars(infoPtr);
4687     TREEVIEW_Invalidate(infoPtr, NULL);
4688
4689     return 0;
4690 }
4691
4692 static LRESULT
4693 TREEVIEW_SetFocus(TREEVIEW_INFO *infoPtr)
4694 {
4695     TRACE("\n");
4696
4697     if (!infoPtr->selectedItem)
4698     {
4699         TREEVIEW_DoSelectItem(infoPtr, TVGN_CARET, infoPtr->firstVisible,
4700                               TVC_UNKNOWN);
4701     }
4702
4703     TREEVIEW_SendSimpleNotify(infoPtr, NM_SETFOCUS);
4704     TREEVIEW_Invalidate(infoPtr, infoPtr->selectedItem);
4705     return 0;
4706 }
4707
4708 static LRESULT
4709 TREEVIEW_KillFocus(TREEVIEW_INFO *infoPtr)
4710 {
4711     TRACE("\n");
4712
4713     TREEVIEW_SendSimpleNotify(infoPtr, NM_KILLFOCUS);
4714     TREEVIEW_Invalidate(infoPtr, infoPtr->selectedItem);
4715     return 0;
4716 }
4717
4718
4719 static LRESULT WINAPI
4720 TREEVIEW_WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
4721 {
4722     TREEVIEW_INFO *infoPtr = TREEVIEW_GetInfoPtr(hwnd);
4723     if (infoPtr) TREEVIEW_VerifyTree(infoPtr);
4724     else
4725     {
4726         if (uMsg == WM_CREATE)
4727             TREEVIEW_Create(hwnd);
4728         else
4729             goto def;
4730     }
4731
4732     switch (uMsg)
4733     {
4734     case TVM_CREATEDRAGIMAGE:
4735         return TREEVIEW_CreateDragImage(infoPtr, wParam, lParam);
4736
4737     case TVM_DELETEITEM:
4738         return TREEVIEW_DeleteItem(infoPtr, (HTREEITEM)lParam);
4739
4740     case TVM_EDITLABELA:
4741         return TREEVIEW_EditLabelA(infoPtr, (HTREEITEM)lParam);
4742
4743     case TVM_EDITLABELW:
4744         FIXME("Unimplemented msg TVM_EDITLABELW\n");
4745         return 0;
4746
4747     case TVM_ENDEDITLABELNOW:
4748         return TREEVIEW_EndEditLabelNow(infoPtr, (BOOL)wParam);
4749
4750     case TVM_ENSUREVISIBLE:
4751         return TREEVIEW_EnsureVisible(infoPtr, (HTREEITEM)lParam, TRUE);
4752
4753     case TVM_EXPAND:
4754         return TREEVIEW_ExpandMsg(infoPtr, (UINT)wParam, (HTREEITEM)lParam);
4755
4756     case TVM_GETBKCOLOR:
4757         return TREEVIEW_GetBkColor(infoPtr);
4758
4759     case TVM_GETCOUNT:
4760         return TREEVIEW_GetCount(infoPtr);
4761
4762     case TVM_GETEDITCONTROL:
4763         return TREEVIEW_GetEditControl(infoPtr);
4764
4765     case TVM_GETIMAGELIST:
4766         return TREEVIEW_GetImageList(infoPtr, wParam);
4767
4768     case TVM_GETINDENT:
4769         return TREEVIEW_GetIndent(infoPtr);
4770
4771     case TVM_GETINSERTMARKCOLOR:
4772         return TREEVIEW_GetInsertMarkColor(infoPtr);
4773
4774     case TVM_GETISEARCHSTRINGA:
4775         FIXME("Unimplemented msg TVM_GETISEARCHSTRINGA\n");
4776         return 0;
4777
4778     case TVM_GETISEARCHSTRINGW:
4779         FIXME("Unimplemented msg TVM_GETISEARCHSTRINGW\n");
4780         return 0;
4781
4782     case TVM_GETITEMA:
4783         return TREEVIEW_GetItemA(infoPtr, (LPTVITEMEXA)lParam);
4784
4785     case TVM_GETITEMW:
4786         return TREEVIEW_GetItemW(infoPtr, (LPTVITEMEXA)lParam);
4787
4788     case TVM_GETITEMHEIGHT:
4789         return TREEVIEW_GetItemHeight(infoPtr);
4790
4791     case TVM_GETITEMRECT:
4792         return TREEVIEW_GetItemRect(infoPtr, (BOOL)wParam, (LPRECT)lParam);
4793
4794     case TVM_GETITEMSTATE:
4795         return TREEVIEW_GetItemState(infoPtr, (HTREEITEM)wParam, (UINT)lParam);
4796
4797     case TVM_GETLINECOLOR:
4798         return TREEVIEW_GetLineColor(infoPtr);
4799
4800     case TVM_GETNEXTITEM:
4801         return TREEVIEW_GetNextItem(infoPtr, (UINT)wParam, (HTREEITEM)lParam);
4802
4803     case TVM_GETSCROLLTIME:
4804         return TREEVIEW_GetScrollTime(infoPtr);
4805
4806     case TVM_GETTEXTCOLOR:
4807         return TREEVIEW_GetTextColor(infoPtr);
4808
4809     case TVM_GETTOOLTIPS:
4810         return TREEVIEW_GetToolTips(infoPtr);
4811
4812     case TVM_GETUNICODEFORMAT:
4813         FIXME("Unimplemented msg TVM_GETUNICODEFORMAT\n");
4814         return 0;
4815
4816     case TVM_GETVISIBLECOUNT:
4817         return TREEVIEW_GetVisibleCount(infoPtr);
4818
4819     case TVM_HITTEST:
4820         return TREEVIEW_HitTest(infoPtr, (LPTVHITTESTINFO)lParam);
4821
4822     case TVM_INSERTITEMA:
4823         return TREEVIEW_InsertItemA(infoPtr, lParam);
4824
4825     case TVM_INSERTITEMW:
4826         return TREEVIEW_InsertItemW(infoPtr, lParam);
4827
4828     case TVM_SELECTITEM:
4829         return TREEVIEW_SelectItem(infoPtr, (INT)wParam, (HTREEITEM)lParam);
4830
4831     case TVM_SETBKCOLOR:
4832         return TREEVIEW_SetBkColor(infoPtr, (COLORREF)lParam);
4833
4834     case TVM_SETIMAGELIST:
4835         return TREEVIEW_SetImageList(infoPtr, wParam, (HIMAGELIST)lParam);
4836
4837     case TVM_SETINDENT:
4838         return TREEVIEW_SetIndent(infoPtr, (UINT)wParam);
4839
4840     case TVM_SETINSERTMARK:
4841         return TREEVIEW_SetInsertMark(infoPtr, (BOOL)wParam, (HTREEITEM)lParam);
4842
4843     case TVM_SETINSERTMARKCOLOR:
4844         return TREEVIEW_SetInsertMarkColor(infoPtr, (COLORREF)lParam);
4845
4846     case TVM_SETITEMA:
4847         return TREEVIEW_SetItemA(infoPtr, (LPTVITEMEXA)lParam);
4848
4849     case TVM_SETITEMW:
4850         FIXME("Unimplemented msg TVM_SETITEMW\n");
4851         return 0;
4852
4853     case TVM_SETLINECOLOR:
4854         return TREEVIEW_SetLineColor(infoPtr, (COLORREF)lParam);
4855
4856     case TVM_SETITEMHEIGHT:
4857         return TREEVIEW_SetItemHeight(infoPtr, (INT)(SHORT)wParam);
4858
4859     case TVM_SETSCROLLTIME:
4860         return TREEVIEW_SetScrollTime(infoPtr, (UINT)wParam);
4861
4862     case TVM_SETTEXTCOLOR:
4863         return TREEVIEW_SetTextColor(infoPtr, (COLORREF)lParam);
4864
4865     case TVM_SETTOOLTIPS:
4866         return TREEVIEW_SetToolTips(infoPtr, (HWND)wParam);
4867
4868     case TVM_SETUNICODEFORMAT:
4869         FIXME("Unimplemented msg TVM_SETUNICODEFORMAT\n");
4870         return 0;
4871
4872     case TVM_SORTCHILDREN:
4873         return TREEVIEW_SortChildren(infoPtr, wParam, lParam);
4874
4875     case TVM_SORTCHILDRENCB:
4876         return TREEVIEW_SortChildrenCB(infoPtr, wParam, (LPTVSORTCB)lParam);
4877
4878         /* WM_CHAR */
4879
4880     case WM_COMMAND:
4881         return TREEVIEW_Command(infoPtr, wParam, lParam);
4882
4883     case WM_DESTROY:
4884         return TREEVIEW_Destroy(infoPtr);
4885
4886         /* WM_ENABLE */
4887
4888     case WM_ERASEBKGND:
4889         return TREEVIEW_EraseBackground(infoPtr, (HDC)wParam);
4890
4891     case WM_GETDLGCODE:
4892         return DLGC_WANTARROWS | DLGC_WANTCHARS;
4893
4894     case WM_GETFONT:
4895         return TREEVIEW_GetFont(infoPtr);
4896
4897     case WM_HSCROLL:
4898         return TREEVIEW_HScroll(infoPtr, wParam);
4899
4900     case WM_KEYDOWN:
4901         return TREEVIEW_KeyDown(infoPtr, wParam);
4902
4903     case WM_KILLFOCUS:
4904         return TREEVIEW_KillFocus(infoPtr);
4905
4906     case WM_LBUTTONDBLCLK:
4907         return TREEVIEW_LButtonDoubleClick(infoPtr, lParam);
4908
4909     case WM_LBUTTONDOWN:
4910         return TREEVIEW_LButtonDown(infoPtr, lParam);
4911
4912         /* WM_MBUTTONDOWN */
4913
4914         /* WM_MOUSEMOVE */
4915
4916         /* WM_NOTIFY */
4917
4918         /* WM_NOTIFYFORMAT */
4919
4920     case WM_PAINT:
4921         return TREEVIEW_Paint(infoPtr, wParam);
4922
4923         /* WM_PRINTCLIENT */
4924
4925     case WM_RBUTTONDOWN:
4926         return TREEVIEW_RButtonDown(infoPtr, lParam);
4927
4928     case WM_SETFOCUS:
4929         return TREEVIEW_SetFocus(infoPtr);
4930
4931     case WM_SETFONT:
4932         return TREEVIEW_SetFont(infoPtr, (HFONT)wParam, (BOOL)lParam);
4933
4934     case WM_SETREDRAW:
4935         return TREEVIEW_SetRedraw(infoPtr, wParam, lParam);
4936
4937     case WM_SIZE:
4938         return TREEVIEW_Size(infoPtr, wParam, lParam);
4939
4940     case WM_STYLECHANGED:
4941         return TREEVIEW_StyleChanged(infoPtr, wParam, lParam);
4942
4943         /* WM_SYSCOLORCHANGE */
4944
4945         /* WM_SYSKEYDOWN */
4946
4947     case WM_TIMER:
4948         return TREEVIEW_HandleTimer(infoPtr, wParam);
4949
4950     case WM_VSCROLL:
4951         return TREEVIEW_VScroll(infoPtr, wParam);
4952
4953         /* WM_WININICHANGE */
4954
4955     case WM_MOUSEWHEEL:
4956         if (wParam & (MK_SHIFT | MK_CONTROL))
4957             goto def;
4958         return TREEVIEW_MouseWheel(infoPtr, wParam);
4959
4960     case WM_DRAWITEM:
4961         TRACE("drawItem\n");
4962         goto def;
4963
4964     default:
4965         /* This mostly catches MFC and Delphi messages. :( */
4966         if (uMsg >= WM_USER)
4967             TRACE("Unknown msg %04x wp=%08x lp=%08lx\n", uMsg, wParam, lParam);
4968 def:
4969         return DefWindowProcA(hwnd, uMsg, wParam, lParam);
4970     }
4971     return 0;
4972 }
4973
4974
4975 /* Class Registration ***************************************************/
4976
4977 VOID
4978 TREEVIEW_Register(void)
4979 {
4980     WNDCLASSA wndClass;
4981
4982     TRACE("\n");
4983
4984     ZeroMemory(&wndClass, sizeof(WNDCLASSA));
4985     wndClass.style = CS_GLOBALCLASS | CS_DBLCLKS;
4986     wndClass.lpfnWndProc = (WNDPROC)TREEVIEW_WindowProc;
4987     wndClass.cbClsExtra = 0;
4988     wndClass.cbWndExtra = sizeof(TREEVIEW_INFO *);
4989
4990     wndClass.hCursor = LoadCursorA(0, IDC_ARROWA);
4991     wndClass.hbrBackground = 0;
4992     wndClass.lpszClassName = WC_TREEVIEWA;
4993
4994     RegisterClassA(&wndClass);
4995 }
4996
4997
4998 VOID
4999 TREEVIEW_Unregister(void)
5000 {
5001     UnregisterClassA(WC_TREEVIEWA, (HINSTANCE) NULL);
5002 }
5003
5004
5005 /* Tree Verification ****************************************************/
5006
5007 #ifdef NDEBUG
5008 static inline void
5009 TREEVIEW_VerifyChildren(TREEVIEW_INFO *infoPtr, TREEVIEW_ITEM *item);
5010
5011 static inline void TREEVIEW_VerifyItemCommon(TREEVIEW_INFO *infoPtr,
5012                                              TREEVIEW_ITEM *item)
5013 {
5014     assert(infoPtr != NULL);
5015     assert(item != NULL);
5016
5017     /* both NULL, or both non-null */
5018     assert((item->firstChild == NULL) == (item->lastChild == NULL));
5019
5020     assert(item->firstChild != item);
5021     assert(item->lastChild != item);
5022
5023     if (item->firstChild)
5024     {
5025         assert(item->firstChild->parent == item);
5026         assert(item->firstChild->prevSibling == NULL);
5027     }
5028
5029     if (item->lastChild)
5030     {
5031         assert(item->lastChild->parent == item);
5032         assert(item->lastChild->nextSibling == NULL);
5033     }
5034
5035     assert(item->nextSibling != item);
5036     if (item->nextSibling)
5037     {
5038         assert(item->nextSibling->parent == item->parent);
5039         assert(item->nextSibling->prevSibling == item);
5040     }
5041
5042     assert(item->prevSibling != item);
5043     if (item->prevSibling)
5044     {
5045         assert(item->prevSibling->parent == item->parent);
5046         assert(item->prevSibling->nextSibling == item);
5047     }
5048 }
5049
5050 static inline void
5051 TREEVIEW_VerifyItem(TREEVIEW_INFO *infoPtr, TREEVIEW_ITEM *item)
5052 {
5053     assert(item != NULL);
5054
5055     assert(item->parent != NULL);
5056     assert(item->parent != item);
5057     assert(item->iLevel == item->parent->iLevel + 1);
5058
5059     assert(DPA_GetPtrIndex(infoPtr->items, item) != -1);
5060
5061     TREEVIEW_VerifyItemCommon(infoPtr, item);
5062
5063     TREEVIEW_VerifyChildren(infoPtr, item);
5064 }
5065
5066 static inline void
5067 TREEVIEW_VerifyChildren(TREEVIEW_INFO *infoPtr, TREEVIEW_ITEM *item)
5068 {
5069     TREEVIEW_ITEM *child;
5070     assert(item != NULL);
5071
5072     for (child = item->firstChild; child != NULL; child = child->nextSibling)
5073         TREEVIEW_VerifyItem(infoPtr, child);
5074 }
5075
5076 static inline void
5077 TREEVIEW_VerifyRoot(TREEVIEW_INFO *infoPtr)
5078 {
5079     TREEVIEW_ITEM *root = infoPtr->root;
5080
5081     assert(root != NULL);
5082     assert(root->iLevel == -1);
5083     assert(root->parent == NULL);
5084     assert(root->prevSibling == NULL);
5085
5086     TREEVIEW_VerifyItemCommon(infoPtr, root);
5087
5088     TREEVIEW_VerifyChildren(infoPtr, root);
5089 }
5090
5091 static void
5092 TREEVIEW_VerifyTree(TREEVIEW_INFO *infoPtr)
5093 {
5094     assert(infoPtr != NULL);
5095
5096     TREEVIEW_VerifyRoot(infoPtr);
5097 }
5098 #endif