source: trunk/src/comctl32/treeview.cpp@ 3970

Last change on this file since 3970 was 3970, checked in by cbratschi, 25 years ago

Corel WINE 20000807 changes - part 1

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