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