1 /* Treeview control
2 *
3 * Copyright 1998 Eric Kohl <ekohl@abo.rhein-zeitung.de>
4 * Copyright 1998,1999 Alex Priem <alexp@sci.kun.nl>
5 * Copyright 1999 Sylvain St-Germain
6 * Copyright 2002 CodeWeavers, Aric Stewart
7 *
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public
10 * License as published by the Free Software Foundation; either
11 * version 2.1 of the License, or (at your option) any later version.
12 *
13 * This library is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Lesser General Public License for more details.
17 *
18 * You should have received a copy of the GNU Lesser General Public
19 * License along with this library; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
21 *
22 * NOTES
23 *
24 * Note that TREEVIEW_INFO * and HTREEITEM are the same thing.
25 *
26 * Note2: If item's text == LPSTR_TEXTCALLBACKA we allocate buffer
27 * of size TEXT_CALLBACK_SIZE in DoSetItem.
28 * We use callbackMask to keep track of fields to be updated.
29 *
30 * TODO:
31 * missing notifications: NM_SETCURSOR, TVN_GETINFOTIP, TVN_KEYDOWN,
32 * TVN_SETDISPINFO, TVN_SINGLEEXPAND
33 *
34 * missing styles: TVS_FULLROWSELECT, TVS_INFOTIP, TVS_RTLREADING,
35 *
36 * missing item styles: TVIS_CUT, TVIS_EXPANDPARTIAL
37 *
38 * Make the insertion mark look right.
39 * Scroll (instead of repaint) as much as possible.
40 */
41
42 #include "config.h"
43 #include "wine/port.h"
44
45 #include <assert.h>
46 #include <ctype.h>
47 #include <stdarg.h>
48 #include <string.h>
49 #include <limits.h>
50 #include <stdlib.h>
51
52 #define NONAMELESSUNION
53 #define NONAMELESSSTRUCT
54 #include "windef.h"
55 #include "winbase.h"
56 #include "wingdi.h"
57 #include "winuser.h"
58 #include "winnls.h"
59 #include "commctrl.h"
60 #include "comctl32.h"
61 #include "uxtheme.h"
62 #include "tmschema.h"
63 #include "wine/unicode.h"
64 #include "wine/debug.h"
65
66 /* internal structures */
67
68 typedef struct _TREEITEM /* HTREEITEM is a _TREEINFO *. */
69 {
70 UINT callbackMask;
71 UINT state;
72 UINT stateMask;
73 LPWSTR pszText;
74 int cchTextMax;
75 int iImage;
76 int iSelectedImage;
77 int cChildren;
78 LPARAM lParam;
79 int iIntegral; /* item height multiplier (1 is normal) */
80 int iLevel; /* indentation level:0=root level */
81 HTREEITEM parent; /* handle to parent or 0 if at root */
82 HTREEITEM firstChild; /* handle to first child or 0 if no child */
83 HTREEITEM lastChild;
84 HTREEITEM prevSibling; /* handle to prev item in list, 0 if first */
85 HTREEITEM nextSibling; /* handle to next item in list, 0 if last */
86 RECT rect;
87 LONG linesOffset;
88 LONG stateOffset;
89 LONG imageOffset;
90 LONG textOffset;
91 LONG textWidth; /* horizontal text extent for pszText */
92 LONG visibleOrder; /* visible ordering, 0 is first visible item */
93 } TREEVIEW_ITEM;
94
95
96 typedef struct tagTREEVIEW_INFO
97 {
98 HWND hwnd;
99 HWND hwndNotify; /* Owner window to send notifications to */
100 DWORD dwStyle;
101 HTREEITEM root;
102 UINT uInternalStatus;
103 INT Timer;
104 UINT uNumItems; /* number of valid TREEVIEW_ITEMs */
105 INT cdmode; /* last custom draw setting */
106 UINT uScrollTime; /* max. time for scrolling in milliseconds */
107 BOOL bRedraw; /* if FALSE we validate but don't redraw in TREEVIEW_Paint() */
108
109 UINT uItemHeight; /* item height */
110 BOOL bHeightSet;
111
112 LONG clientWidth; /* width of control window */
113 LONG clientHeight; /* height of control window */
114
115 LONG treeWidth; /* width of visible tree items */
116 LONG treeHeight; /* height of visible tree items */
117
118 UINT uIndent; /* indentation in pixels */
119 HTREEITEM selectedItem; /* handle to selected item or 0 if none */
120 HTREEITEM hotItem; /* handle currently under cursor, 0 if none */
121 HTREEITEM focusedItem; /* item that was under the cursor when WM_LBUTTONDOWN was received */
122
123 HTREEITEM firstVisible; /* handle to first visible item */
124 LONG maxVisibleOrder;
125 HTREEITEM dropItem; /* handle to item selected by drag cursor */
126 HTREEITEM insertMarkItem; /* item after which insertion mark is placed */
127 BOOL insertBeforeorAfter; /* flag used by TVM_SETINSERTMARK */
128 HIMAGELIST dragList; /* Bitmap of dragged item */
129 LONG scrollX;
130 COLORREF clrBk;
131 COLORREF clrText;
132 COLORREF clrLine;
133 COLORREF clrInsertMark;
134 HFONT hFont;
135 HFONT hDefaultFont;
136 HFONT hBoldFont;
137 HFONT hUnderlineFont;
138 HCURSOR hcurHand;
139 HWND hwndToolTip;
140
141 HWND hwndEdit;
142 WNDPROC wpEditOrig; /* orig window proc for subclassing edit */
143 BOOL bIgnoreEditKillFocus;
144 BOOL bLabelChanged;
145
146 BOOL bNtfUnicode; /* TRUE if should send NOTIFY with W */
147 HIMAGELIST himlNormal;
148 int normalImageHeight;
149 int normalImageWidth;
150 HIMAGELIST himlState;
151 int stateImageHeight;
152 int stateImageWidth;
153 HDPA items;
154
155 DWORD lastKeyPressTimestamp; /* Added */
156 WPARAM charCode; /* Added */
157 INT nSearchParamLength; /* Added */
158 WCHAR szSearchParam[ MAX_PATH ]; /* Added */
159 } TREEVIEW_INFO;
160
161
162 /******** Defines that TREEVIEW_ProcessLetterKeys uses ****************/
163 #define KEY_DELAY 450
164
165 /* bitflags for infoPtr->uInternalStatus */
166
167 #define TV_HSCROLL 0x01 /* treeview too large to fit in window */
168 #define TV_VSCROLL 0x02 /* (horizontal/vertical) */
169 #define TV_LDRAG 0x04 /* Lbutton pushed to start drag */
170 #define TV_LDRAGGING 0x08 /* Lbutton pushed, mouse moved. */
171 #define TV_RDRAG 0x10 /* ditto Rbutton */
172 #define TV_RDRAGGING 0x20
173
174 /* bitflags for infoPtr->timer */
175
176 #define TV_EDIT_TIMER 2
177 #define TV_EDIT_TIMER_SET 2
178
179
180 VOID TREEVIEW_Register (VOID);
181 VOID TREEVIEW_Unregister (VOID);
182
183
184 WINE_DEFAULT_DEBUG_CHANNEL(treeview);
185
186
187 #define TEXT_CALLBACK_SIZE 260
188
189 #define TREEVIEW_LEFT_MARGIN 8
190
191 #define MINIMUM_INDENT 19
192
193 #define CALLBACK_MASK_ALL (TVIF_TEXT|TVIF_CHILDREN|TVIF_IMAGE|TVIF_SELECTEDIMAGE)
194
195 #define STATEIMAGEINDEX(x) (((x) >> 12) & 0x0f)
196 #define OVERLAYIMAGEINDEX(x) (((x) >> 8) & 0x0f)
197 #define ISVISIBLE(x) ((x)->visibleOrder >= 0)
198
199
200 static const WCHAR themeClass[] = { 'T','r','e','e','v','i','e','w',0 };
201
202
203 typedef VOID (*TREEVIEW_ItemEnumFunc)(TREEVIEW_INFO *, TREEVIEW_ITEM *,LPVOID);
204
205
206 static VOID TREEVIEW_Invalidate(const TREEVIEW_INFO *, const TREEVIEW_ITEM *);
207
208 static LRESULT TREEVIEW_DoSelectItem(TREEVIEW_INFO *, INT, HTREEITEM, INT);
209 static VOID TREEVIEW_SetFirstVisible(TREEVIEW_INFO *, TREEVIEW_ITEM *, BOOL);
210 static LRESULT TREEVIEW_EnsureVisible(TREEVIEW_INFO *, HTREEITEM, BOOL);
211 static LRESULT TREEVIEW_RButtonUp(const TREEVIEW_INFO *, const POINT *);
212 static LRESULT TREEVIEW_EndEditLabelNow(TREEVIEW_INFO *infoPtr, BOOL bCancel);
213 static VOID TREEVIEW_UpdateScrollBars(TREEVIEW_INFO *infoPtr);
214 static LRESULT TREEVIEW_HScroll(TREEVIEW_INFO *, WPARAM);
215 static INT TREEVIEW_NotifyFormat (TREEVIEW_INFO *infoPtr, HWND wParam, UINT lParam);
216
217
218 /* Random Utilities *****************************************************/
219
220 #ifndef NDEBUG
221 static inline void
222 TREEVIEW_VerifyTree(TREEVIEW_INFO *infoPtr)
223 {
224 (void)infoPtr;
225 }
226 #else
227 /* The definition is at the end of the file. */
228 static void TREEVIEW_VerifyTree(TREEVIEW_INFO *infoPtr);
229 #endif
230
231 /* Returns the treeview private data if hwnd is a treeview.
232 * Otherwise returns an undefined value. */
233 static TREEVIEW_INFO *
234 TREEVIEW_GetInfoPtr(HWND hwnd)
235 {
236 return (TREEVIEW_INFO *)GetWindowLongPtrW(hwnd, 0);
237 }
238
239 /* Don't call this. Nothing wants an item index. */
240 static inline int
241 TREEVIEW_GetItemIndex(const TREEVIEW_INFO *infoPtr, HTREEITEM handle)
242 {
243 return DPA_GetPtrIndex(infoPtr->items, handle);
244 }
245
246 /* Checks if item has changed and needs to be redrawn */
247 static inline BOOL item_changed (const TREEVIEW_ITEM *tiOld, const TREEVIEW_ITEM *tiNew,
248 const TVITEMEXW *tvChange)
249 {
250 /* Number of children has changed */
251 if ((tvChange->mask & TVIF_CHILDREN) && (tiOld->cChildren != tiNew->cChildren))
252 return TRUE;
253
254 /* Image has changed and it's not a callback */
255 if ((tvChange->mask & TVIF_IMAGE) && (tiOld->iImage != tiNew->iImage) &&
256 tiNew->iImage != I_IMAGECALLBACK)
257 return TRUE;
258
259 /* Selected image has changed and it's not a callback */
260 if ((tvChange->mask & TVIF_SELECTEDIMAGE) && (tiOld->iSelectedImage != tiNew->iSelectedImage) &&
261 tiNew->iSelectedImage != I_IMAGECALLBACK)
262 return TRUE;
263
264 /* Text has changed and it's not a callback */
265 if ((tvChange->mask & TVIF_TEXT) && (tiOld->pszText != tiNew->pszText) &&
266 tiNew->pszText != LPSTR_TEXTCALLBACKW)
267 return TRUE;
268
269 /* Indent has changed */
270 if ((tvChange->mask & TVIF_INTEGRAL) && (tiOld->iIntegral != tiNew->iIntegral))
271 return TRUE;
272
273 /* Item state has changed */
274 if ((tvChange->mask & TVIF_STATE) && ((tiOld->state ^ tiNew->state) & tvChange->stateMask ))
275 return TRUE;
276
277 return FALSE;
278 }
279
280 /***************************************************************************
281 * This method checks that handle is an item for this tree.
282 */
283 static BOOL
284 TREEVIEW_ValidItem(const TREEVIEW_INFO *infoPtr, HTREEITEM handle)
285 {
286 if (TREEVIEW_GetItemIndex(infoPtr, handle) == -1)
287 {
288 TRACE("invalid item %p\n", handle);
289 return FALSE;
290 }
291 else
292 return TRUE;
293 }
294
295 static HFONT
296 TREEVIEW_CreateBoldFont(HFONT hOrigFont)
297 {
298 LOGFONTW font;
299
300 GetObjectW(hOrigFont, sizeof(font), &font);
301 font.lfWeight = FW_BOLD;
302 return CreateFontIndirectW(&font);
303 }
304
305 static HFONT
306 TREEVIEW_CreateUnderlineFont(HFONT hOrigFont)
307 {
308 LOGFONTW font;
309
310 GetObjectW(hOrigFont, sizeof(font), &font);
311 font.lfUnderline = TRUE;
312 return CreateFontIndirectW(&font);
313 }
314
315 static inline HFONT
316 TREEVIEW_FontForItem(const TREEVIEW_INFO *infoPtr, const TREEVIEW_ITEM *item)
317 {
318 if ((infoPtr->dwStyle & TVS_TRACKSELECT) && (item == infoPtr->hotItem))
319 return infoPtr->hUnderlineFont;
320 if (item->state & TVIS_BOLD)
321 return infoPtr->hBoldFont;
322 return infoPtr->hFont;
323 }
324
325 /* for trace/debugging purposes only */
326 static const char *
327 TREEVIEW_ItemName(const TREEVIEW_ITEM *item)
328 {
329 if (item == NULL) return "<null item>";
330 if (item->pszText == LPSTR_TEXTCALLBACKW) return "<callback>";
331 if (item->pszText == NULL) return "<null>";
332 return debugstr_w(item->pszText);
333 }
334
335 /* An item is not a child of itself. */
336 static BOOL
337 TREEVIEW_IsChildOf(const TREEVIEW_ITEM *parent, const TREEVIEW_ITEM *child)
338 {
339 do
340 {
341 child = child->parent;
342 if (child == parent) return TRUE;
343 } while (child != NULL);
344
345 return FALSE;
346 }
347
348
349 /* Tree Traversal *******************************************************/
350
351 /***************************************************************************
352 * This method returns the last expanded sibling or child child item
353 * of a tree node
354 */
355 static TREEVIEW_ITEM *
356 TREEVIEW_GetLastListItem(const TREEVIEW_INFO *infoPtr, TREEVIEW_ITEM *wineItem)
357 {
358 if (!wineItem)
359 return NULL;
360
361 while (wineItem->lastChild)
362 {
363 if (wineItem->state & TVIS_EXPANDED)
364 wineItem = wineItem->lastChild;
365 else
366 break;
367 }
368
369 if (wineItem == infoPtr->root)
370 return NULL;
371
372 return wineItem;
373 }
374
375 /***************************************************************************
376 * This method returns the previous non-hidden item in the list not
377 * considering the tree hierarchy.
378 */
379 static TREEVIEW_ITEM *
380 TREEVIEW_GetPrevListItem(const TREEVIEW_INFO *infoPtr, const TREEVIEW_ITEM *tvItem)
381 {
382 if (tvItem->prevSibling)
383 {
384 /* This item has a prevSibling, get the last item in the sibling's tree. */
385 TREEVIEW_ITEM *upItem = tvItem->prevSibling;
386
387 if ((upItem->state & TVIS_EXPANDED) && upItem->lastChild != NULL)
388 return TREEVIEW_GetLastListItem(infoPtr, upItem->lastChild);
389 else
390 return upItem;
391 }
392 else
393 {
394 /* this item does not have a prevSibling, get the parent */
395 return (tvItem->parent != infoPtr->root) ? tvItem->parent : NULL;
396 }
397 }
398
399
400 /***************************************************************************
401 * This method returns the next physical item in the treeview not
402 * considering the tree hierarchy.
403 */
404 static TREEVIEW_ITEM *
405 TREEVIEW_GetNextListItem(const TREEVIEW_INFO *infoPtr, const TREEVIEW_ITEM *tvItem)
406 {
407 /*
408 * If this item has children and is expanded, return the first child
409 */
410 if ((tvItem->state & TVIS_EXPANDED) && tvItem->firstChild != NULL)
411 {
412 return tvItem->firstChild;
413 }
414
415
416 /*
417 * try to get the sibling
418 */
419 if (tvItem->nextSibling)
420 return tvItem->nextSibling;
421
422 /*
423 * Otherwise, get the parent's sibling.
424 */
425 while (tvItem->parent)
426 {
427 tvItem = tvItem->parent;
428
429 if (tvItem->nextSibling)
430 return tvItem->nextSibling;
431 }
432
433 return NULL;
434 }
435
436 /***************************************************************************
437 * This method returns the nth item starting at the given item. It returns
438 * the last item (or first) we we run out of items.
439 *
440 * Will scroll backward if count is <0.
441 * forward if count is >0.
442 */
443 static TREEVIEW_ITEM *
444 TREEVIEW_GetListItem(const TREEVIEW_INFO *infoPtr, TREEVIEW_ITEM *wineItem,
445 LONG count)
446 {
447 TREEVIEW_ITEM *(*next_item)(const TREEVIEW_INFO *, const TREEVIEW_ITEM *);
448 TREEVIEW_ITEM *previousItem;
449
450 assert(wineItem != NULL);
451
452 if (count > 0)
453 {
454 next_item = TREEVIEW_GetNextListItem;
455 }
456 else if (count < 0)
457 {
458 count = -count;
459 next_item = TREEVIEW_GetPrevListItem;
460 }
461 else
462 return wineItem;
463
464 do
465 {
466 previousItem = wineItem;
467 wineItem = next_item(infoPtr, wineItem);
468
469 } while (--count && wineItem != NULL);
470
471
472 return wineItem ? wineItem : previousItem;
473 }
474
475 /* Notifications ************************************************************/
476
477 static INT get_notifycode(const TREEVIEW_INFO *infoPtr, INT code)
478 {
479 if (!infoPtr->bNtfUnicode) {
480 switch (code) {
481 case TVN_SELCHANGINGW: return TVN_SELCHANGINGA;
482 case TVN_SELCHANGEDW: return TVN_SELCHANGEDA;
483 case TVN_GETDISPINFOW: return TVN_GETDISPINFOA;
484 case TVN_SETDISPINFOW: return TVN_SETDISPINFOA;
485 case TVN_ITEMEXPANDINGW: return TVN_ITEMEXPANDINGA;
486 case TVN_ITEMEXPANDEDW: return TVN_ITEMEXPANDEDA;
487 case TVN_BEGINDRAGW: return TVN_BEGINDRAGA;
488 case TVN_BEGINRDRAGW: return TVN_BEGINRDRAGA;
489 case TVN_DELETEITEMW: return TVN_DELETEITEMA;
490 case TVN_BEGINLABELEDITW: return TVN_BEGINLABELEDITA;
491 case TVN_ENDLABELEDITW: return TVN_ENDLABELEDITA;
492 case TVN_GETINFOTIPW: return TVN_GETINFOTIPA;
493 }
494 }
495 return code;
496 }
497
498 static LRESULT
499 TREEVIEW_SendRealNotify(const TREEVIEW_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
500 {
501 TRACE("wParam=%ld, lParam=%ld\n", wParam, lParam);
502 return SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, wParam, lParam);
503 }
504
505 static BOOL
506 TREEVIEW_SendSimpleNotify(const TREEVIEW_INFO *infoPtr, UINT code)
507 {
508 NMHDR nmhdr;
509 HWND hwnd = infoPtr->hwnd;
510
511 TRACE("%d\n", code);
512 nmhdr.hwndFrom = hwnd;
513 nmhdr.idFrom = GetWindowLongPtrW(hwnd, GWLP_ID);
514 nmhdr.code = get_notifycode(infoPtr, code);
515
516 return (BOOL)TREEVIEW_SendRealNotify(infoPtr, nmhdr.idFrom, (LPARAM)&nmhdr);
517 }
518
519 static VOID
520 TREEVIEW_TVItemFromItem(const TREEVIEW_INFO *infoPtr, UINT mask, TVITEMW *tvItem, TREEVIEW_ITEM *item)
521 {
522 tvItem->mask = mask;
523 tvItem->hItem = item;
524 tvItem->state = item->state;
525 tvItem->stateMask = 0;
526 tvItem->iImage = item->iImage;
527 tvItem->iSelectedImage = item->iSelectedImage;
528 tvItem->cChildren = item->cChildren;
529 tvItem->lParam = item->lParam;
530
531 if(mask & TVIF_TEXT)
532 {
533 if (!infoPtr->bNtfUnicode)
534 {
535 tvItem->cchTextMax = WideCharToMultiByte( CP_ACP, 0, item->pszText, -1, NULL, 0, NULL, NULL );
536 tvItem->pszText = Alloc (tvItem->cchTextMax);
537 WideCharToMultiByte( CP_ACP, 0, item->pszText, -1, (LPSTR)tvItem->pszText, tvItem->cchTextMax, 0, 0 );
538 }
539 else
540 {
541 tvItem->cchTextMax = item->cchTextMax;
542 tvItem->pszText = item->pszText;
543 }
544 }
545 else
546 {
547 tvItem->cchTextMax = 0;
548 tvItem->pszText = NULL;
549 }
550 }
551
552 static BOOL
553 TREEVIEW_SendTreeviewNotify(const TREEVIEW_INFO *infoPtr, UINT code, UINT action,
554 UINT mask, HTREEITEM oldItem, HTREEITEM newItem)
555 {
556 HWND hwnd = infoPtr->hwnd;
557 NMTREEVIEWW nmhdr;
558 BOOL ret;
559
560 TRACE("code:%d action:%x olditem:%p newitem:%p\n",
561 code, action, oldItem, newItem);
562
563 ZeroMemory(&nmhdr, sizeof(NMTREEVIEWW));
564
565 nmhdr.hdr.hwndFrom = hwnd;
566 nmhdr.hdr.idFrom = GetWindowLongPtrW(hwnd, GWLP_ID);
567 nmhdr.hdr.code = get_notifycode(infoPtr, code);
568 nmhdr.action = action;
569
570 if (oldItem)
571 TREEVIEW_TVItemFromItem(infoPtr, mask, &nmhdr.itemOld, oldItem);
572
573 if (newItem)
574 TREEVIEW_TVItemFromItem(infoPtr, mask, &nmhdr.itemNew, newItem);
575
576 nmhdr.ptDrag.x = 0;
577 nmhdr.ptDrag.y = 0;
578
579 ret = (BOOL)TREEVIEW_SendRealNotify(infoPtr, nmhdr.hdr.idFrom, (LPARAM)&nmhdr);
580 if (!infoPtr->bNtfUnicode)
581 {
582 Free(nmhdr.itemOld.pszText);
583 Free(nmhdr.itemNew.pszText);
584 }
585 return ret;
586 }
587
588 static BOOL
589 TREEVIEW_SendTreeviewDnDNotify(const TREEVIEW_INFO *infoPtr, UINT code,
590 HTREEITEM dragItem, POINT pt)
591 {
592 HWND hwnd = infoPtr->hwnd;
593 NMTREEVIEWW nmhdr;
594
595 TRACE("code:%d dragitem:%p\n", code, dragItem);
596
597 nmhdr.hdr.hwndFrom = hwnd;
598 nmhdr.hdr.idFrom = GetWindowLongPtrW(hwnd, GWLP_ID);
599 nmhdr.hdr.code = get_notifycode(infoPtr, code);
600 nmhdr.action = 0;
601 nmhdr.itemNew.mask = TVIF_STATE | TVIF_PARAM | TVIF_HANDLE;
602 nmhdr.itemNew.hItem = dragItem;
603 nmhdr.itemNew.state = dragItem->state;
604 nmhdr.itemNew.lParam = dragItem->lParam;
605
606 nmhdr.ptDrag.x = pt.x;
607 nmhdr.ptDrag.y = pt.y;
608
609 return (BOOL)TREEVIEW_SendRealNotify(infoPtr, nmhdr.hdr.idFrom, (LPARAM)&nmhdr);
610 }
611
612
613 static BOOL
614 TREEVIEW_SendCustomDrawNotify(const TREEVIEW_INFO *infoPtr, DWORD dwDrawStage,
615 HDC hdc, RECT rc)
616 {
617 HWND hwnd = infoPtr->hwnd;
618 NMTVCUSTOMDRAW nmcdhdr;
619 LPNMCUSTOMDRAW nmcd;
620
621 TRACE("drawstage:%x hdc:%p\n", dwDrawStage, hdc);
622
623 nmcd = &nmcdhdr.nmcd;
624 nmcd->hdr.hwndFrom = hwnd;
625 nmcd->hdr.idFrom = GetWindowLongPtrW(hwnd, GWLP_ID);
626 nmcd->hdr.code = NM_CUSTOMDRAW;
627 nmcd->dwDrawStage = dwDrawStage;
628 nmcd->hdc = hdc;
629 nmcd->rc = rc;
630 nmcd->dwItemSpec = 0;
631 nmcd->uItemState = 0;
632 nmcd->lItemlParam = 0;
633 nmcdhdr.clrText = infoPtr->clrText;
634 nmcdhdr.clrTextBk = infoPtr->clrBk;
635 nmcdhdr.iLevel = 0;
636
637 return (BOOL)TREEVIEW_SendRealNotify(infoPtr, nmcd->hdr.idFrom, (LPARAM)&nmcdhdr);
638 }
639
640
641
642 /* FIXME: need to find out when the flags in uItemState need to be set */
643
644 static BOOL
645 TREEVIEW_SendCustomDrawItemNotify(const TREEVIEW_INFO *infoPtr, HDC hdc,
646 TREEVIEW_ITEM *wineItem, UINT uItemDrawState,
647 NMTVCUSTOMDRAW *nmcdhdr)
648 {
649 HWND hwnd = infoPtr->hwnd;
650 LPNMCUSTOMDRAW nmcd;
651 DWORD dwDrawStage;
652 DWORD_PTR dwItemSpec;
653 UINT uItemState;
654 INT retval;
655
656 dwDrawStage = CDDS_ITEM | uItemDrawState;
657 dwItemSpec = (DWORD_PTR)wineItem;
658 uItemState = 0;
659 if (wineItem->state & TVIS_SELECTED)
660 uItemState |= CDIS_SELECTED;
661 if (wineItem == infoPtr->selectedItem)
662 uItemState |= CDIS_FOCUS;
663 if (wineItem == infoPtr->hotItem)
664 uItemState |= CDIS_HOT;
665
666 nmcd = &nmcdhdr->nmcd;
667 nmcd->hdr.hwndFrom = hwnd;
668 nmcd->hdr.idFrom = GetWindowLongPtrW(hwnd, GWLP_ID);
669 nmcd->hdr.code = NM_CUSTOMDRAW;
670 nmcd->dwDrawStage = dwDrawStage;
671 nmcd->hdc = hdc;
672 nmcd->rc = wineItem->rect;
673 nmcd->dwItemSpec = dwItemSpec;
674 nmcd->uItemState = uItemState;
675 nmcd->lItemlParam = wineItem->lParam;
676 nmcdhdr->iLevel = wineItem->iLevel;
677
678 TRACE("drawstage:%x hdc:%p item:%lx, itemstate:%x, lItemlParam:%lx\n",
679 nmcd->dwDrawStage, nmcd->hdc, nmcd->dwItemSpec,
680 nmcd->uItemState, nmcd->lItemlParam);
681
682 retval = TREEVIEW_SendRealNotify(infoPtr, nmcd->hdr.idFrom, (LPARAM)nmcdhdr);
683
684 return retval;
685 }
686
687 static BOOL
688 TREEVIEW_BeginLabelEditNotify(const TREEVIEW_INFO *infoPtr, TREEVIEW_ITEM *editItem)
689 {
690 HWND hwnd = infoPtr->hwnd;
691 NMTVDISPINFOW tvdi;
692 BOOL ret;
693
694 tvdi.hdr.hwndFrom = hwnd;
695 tvdi.hdr.idFrom = GetWindowLongPtrW(hwnd, GWLP_ID);
696 tvdi.hdr.code = get_notifycode(infoPtr, TVN_BEGINLABELEDITW);
697
698 TREEVIEW_TVItemFromItem(infoPtr, TVIF_HANDLE | TVIF_STATE | TVIF_PARAM | TVIF_TEXT,
699 &tvdi.item, editItem);
700
701 ret = (BOOL)TREEVIEW_SendRealNotify(infoPtr, tvdi.hdr.idFrom, (LPARAM)&tvdi);
702
703 if (!infoPtr->bNtfUnicode)
704 Free(tvdi.item.pszText);
705
706 return ret;
707 }
708
709 static void
710 TREEVIEW_UpdateDispInfo(const TREEVIEW_INFO *infoPtr, TREEVIEW_ITEM *wineItem,
711 UINT mask)
712 {
713 NMTVDISPINFOW callback;
714 HWND hwnd = infoPtr->hwnd;
715
716 TRACE("mask %x callbackMask %x\n", mask, wineItem->callbackMask);
717 mask &= wineItem->callbackMask;
718
719 if (mask == 0) return;
720
721 callback.hdr.hwndFrom = hwnd;
722 callback.hdr.idFrom = GetWindowLongPtrW(hwnd, GWLP_ID);
723 callback.hdr.code = get_notifycode(infoPtr, TVN_GETDISPINFOW);
724
725 /* 'state' always contains valid value, as well as 'lParam'.
726 * All other parameters are uninitialized.
727 */
728 callback.item.pszText = wineItem->pszText;
729 callback.item.cchTextMax = wineItem->cchTextMax;
730 callback.item.mask = mask;
731 callback.item.hItem = wineItem;
732 callback.item.state = wineItem->state;
733 callback.item.lParam = wineItem->lParam;
734
735 /* If text is changed we need to recalculate textWidth */
736 if (mask & TVIF_TEXT)
737 wineItem->textWidth = 0;
738
739 TREEVIEW_SendRealNotify(infoPtr, callback.hdr.idFrom, (LPARAM)&callback);
740
741 /* It may have changed due to a call to SetItem. */
742 mask &= wineItem->callbackMask;
743
744 if ((mask & TVIF_TEXT) && callback.item.pszText != wineItem->pszText)
745 {
746 /* Instead of copying text into our buffer user specified its own */
747 if (!infoPtr->bNtfUnicode) {
748 LPWSTR newText;
749 int buflen;
750 int len = MultiByteToWideChar( CP_ACP, 0,
751 (LPSTR)callback.item.pszText, -1,
752 NULL, 0);
753 buflen = max((len)*sizeof(WCHAR), TEXT_CALLBACK_SIZE);
754 newText = (LPWSTR)ReAlloc(wineItem->pszText, buflen);
755
756 TRACE("returned str %s, len=%d, buflen=%d\n",
757 debugstr_a((LPSTR)callback.item.pszText), len, buflen);
758
759 if (newText)
760 {
761 wineItem->pszText = newText;
762 MultiByteToWideChar( CP_ACP, 0,
763 (LPSTR)callback.item.pszText, -1,
764 wineItem->pszText, buflen/sizeof(WCHAR));
765 wineItem->cchTextMax = buflen/sizeof(WCHAR);
766 }
767 /* If ReAlloc fails we have nothing to do, but keep original text */
768 }
769 else {
770 int len = max(lstrlenW(callback.item.pszText) + 1,
771 TEXT_CALLBACK_SIZE);
772 LPWSTR newText = ReAlloc(wineItem->pszText, len);
773
774 TRACE("returned wstr %s, len=%d\n",
775 debugstr_w(callback.item.pszText), len);
776
777 if (newText)
778 {
779 wineItem->pszText = newText;
780 strcpyW(wineItem->pszText, callback.item.pszText);
781 wineItem->cchTextMax = len;
782 }
783 /* If ReAlloc fails we have nothing to do, but keep original text */
784 }
785 }
786 else if (mask & TVIF_TEXT) {
787 /* User put text into our buffer, that is ok unless A string */
788 if (!infoPtr->bNtfUnicode) {
789 LPWSTR newText;
790 LPWSTR oldText = NULL;
791 int buflen;
792 int len = MultiByteToWideChar( CP_ACP, 0,
793 (LPSTR)callback.item.pszText, -1,
794 NULL, 0);
795 buflen = max((len)*sizeof(WCHAR), TEXT_CALLBACK_SIZE);
796 newText = (LPWSTR)Alloc(buflen);
797
798 TRACE("same buffer str %s, len=%d, buflen=%d\n",
799 debugstr_a((LPSTR)callback.item.pszText), len, buflen);
800
801 if (newText)
802 {
803 oldText = wineItem->pszText;
804 wineItem->pszText = newText;
805 MultiByteToWideChar( CP_ACP, 0,
806 (LPSTR)callback.item.pszText, -1,
807 wineItem->pszText, buflen/sizeof(WCHAR));
808 wineItem->cchTextMax = buflen/sizeof(WCHAR);
809 Free(oldText);
810 }
811 }
812 }
813
814 if (mask & TVIF_IMAGE)
815 wineItem->iImage = callback.item.iImage;
816
817 if (mask & TVIF_SELECTEDIMAGE)
818 wineItem->iSelectedImage = callback.item.iSelectedImage;
819
820 if (mask & TVIF_CHILDREN)
821 wineItem->cChildren = callback.item.cChildren;
822
823 /* These members are now permanently set. */
824 if (callback.item.mask & TVIF_DI_SETITEM)
825 wineItem->callbackMask &= ~callback.item.mask;
826 }
827
828 /***************************************************************************
829 * This function uses cChildren field to decide whether the item has
830 * children or not.
831 * Note: if this returns TRUE, the child items may not actually exist,
832 * they could be virtual.
833 *
834 * Just use wineItem->firstChild to check for physical children.
835 */
836 static BOOL
837 TREEVIEW_HasChildren(const TREEVIEW_INFO *infoPtr, TREEVIEW_ITEM *wineItem)
838 {
839 TREEVIEW_UpdateDispInfo(infoPtr, wineItem, TVIF_CHILDREN);
840
841 return wineItem->cChildren > 0;
842 }
843
844
845 /* Item Position ********************************************************/
846
847 /* Compute linesOffset, stateOffset, imageOffset, textOffset of an item. */
848 static VOID
849 TREEVIEW_ComputeItemInternalMetrics(const TREEVIEW_INFO *infoPtr, TREEVIEW_ITEM *item)
850 {
851 /* Same effect, different optimisation. */
852 #if 0
853 BOOL lar = ((infoPtr->dwStyle & TVS_LINESATROOT)
854 && (infoPtr->dwStyle & (TVS_HASLINES|TVS_HASBUTTONS)));
855 #else
856 BOOL lar = ((infoPtr->dwStyle
857 & (TVS_LINESATROOT|TVS_HASLINES|TVS_HASBUTTONS))
858 > TVS_LINESATROOT);
859 #endif
860
861 item->linesOffset = infoPtr->uIndent * (lar ? item->iLevel : item->iLevel - 1)
862 - infoPtr->scrollX;
863 item->stateOffset = item->linesOffset + infoPtr->uIndent;
864 item->imageOffset = item->stateOffset
865 + (STATEIMAGEINDEX(item->state) ? infoPtr->stateImageWidth : 0);
866 item->textOffset = item->imageOffset + infoPtr->normalImageWidth;
867 }
868
869 static VOID
870 TREEVIEW_ComputeTextWidth(const TREEVIEW_INFO *infoPtr, TREEVIEW_ITEM *item, HDC hDC)
871 {
872 HDC hdc;
873 HFONT hOldFont=0;
874 SIZE sz;
875
876 /* DRAW's OM docker creates items like this */
877 if (item->pszText == NULL)
878 {
879 item->textWidth = 0;
880 return;
881 }
882
883 if (hDC != 0)
884 {
885 hdc = hDC;
886 }
887 else
888 {
889 hdc = GetDC(infoPtr->hwnd);
890 hOldFont = SelectObject(hdc, TREEVIEW_FontForItem(infoPtr, item));
891 }
892
893 GetTextExtentPoint32W(hdc, item->pszText, strlenW(item->pszText), &sz);
894 item->textWidth = sz.cx;
895
896 if (hDC == 0)
897 {
898 SelectObject(hdc, hOldFont);
899 ReleaseDC(0, hdc);
900 }
901 }
902
903 static VOID
904 TREEVIEW_ComputeItemRect(const TREEVIEW_INFO *infoPtr, TREEVIEW_ITEM *item)
905 {
906 item->rect.top = infoPtr->uItemHeight *
907 (item->visibleOrder - infoPtr->firstVisible->visibleOrder);
908
909 item->rect.bottom = item->rect.top
910 + infoPtr->uItemHeight * item->iIntegral - 1;
911
912 item->rect.left = 0;
913 item->rect.right = infoPtr->clientWidth;
914 }
915
916 /* We know that only items after start need their order updated. */
917 static void
918 TREEVIEW_RecalculateVisibleOrder(TREEVIEW_INFO *infoPtr, TREEVIEW_ITEM *start)
919 {
920 TREEVIEW_ITEM *item;
921 int order;
922
923 if (!start)
924 {
925 start = infoPtr->root->firstChild;
926 order = 0;
927 }
928 else
929 order = start->visibleOrder;
930
931 for (item = start; item != NULL;
932 item = TREEVIEW_GetNextListItem(infoPtr, item))
933 {
934 if (!ISVISIBLE(item) && order > 0)
935 TREEVIEW_Compu