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

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

redraw fix

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