ReactOS  r76032
treeview.c
Go to the documentation of this file.
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: TVN_GETINFOTIP, TVN_KEYDOWN,
32  * TVN_SETDISPINFO
33  *
34  * missing styles: TVS_INFOTIP, TVS_RTLREADING,
35  *
36  * missing item styles: TVIS_EXPANDPARTIAL, TVIS_EX_FLAT,
37  * TVIS_EX_DISABLED
38  *
39  * Make the insertion mark look right.
40  * Scroll (instead of repaint) as much as possible.
41  */
42 
43 #include "comctl32.h"
44 
45 #include <wine/exception.h>
46 
48 
49 /* internal structures */
50 typedef struct tagTREEVIEW_INFO
51 {
53  HWND hwndNotify; /* Owner window to send notifications to */
58  UINT uNumItems; /* number of valid TREEVIEW_ITEMs */
59  INT cdmode; /* last custom draw setting */
60  UINT uScrollTime; /* max. time for scrolling in milliseconds */
61  BOOL bRedraw; /* if FALSE we validate but don't redraw in TREEVIEW_Paint() */
62 
63  UINT uItemHeight; /* item height */
65 
66  LONG clientWidth; /* width of control window */
67  LONG clientHeight; /* height of control window */
68 
69  LONG treeWidth; /* width of visible tree items */
70  LONG treeHeight; /* height of visible tree items */
71 
72  UINT uIndent; /* indentation in pixels */
73  HTREEITEM selectedItem; /* handle to selected item or 0 if none */
74  HTREEITEM hotItem; /* handle currently under cursor, 0 if none */
75  HTREEITEM focusedItem; /* item that was under the cursor when WM_LBUTTONDOWN was received */
76  HTREEITEM editItem; /* item being edited with builtin edit box */
77 
78  HTREEITEM firstVisible; /* handle to item whose top edge is at y = 0 */
80  HTREEITEM dropItem; /* handle to item selected by drag cursor */
81  HTREEITEM insertMarkItem; /* item after which insertion mark is placed */
82  BOOL insertBeforeorAfter; /* flag used by TVM_SETINSERTMARK */
83  HIMAGELIST dragList; /* Bitmap of dragged item */
97 
99  WNDPROC wpEditOrig; /* orig window proc for subclassing edit */
102 
103  BOOL bNtfUnicode; /* TRUE if should send NOTIFY with W */
111 
116 } TREEVIEW_INFO;
117 
118 typedef struct _TREEITEM /* HTREEITEM is a _TREEINFO *. */
119 {
120  HTREEITEM parent; /* handle to parent or 0 if at root */
121  HTREEITEM nextSibling; /* handle to next item in list, 0 if last */
122  HTREEITEM firstChild; /* handle to first child or 0 if no child */
123 
129  int iImage;
134  int iIntegral; /* item height multiplier (1 is normal) */
135  int iLevel; /* indentation level:0=root level */
137  HTREEITEM prevSibling; /* handle to prev item in list, 0 if first */
143  LONG textWidth; /* horizontal text extent for pszText */
144  LONG visibleOrder; /* Depth-first numbering of the items whose ancestors are all expanded,
145  corresponding to a top-to-bottom ordering in the tree view.
146  Each item takes up "item.iIntegral" spots in the visible order.
147  0 is the root's first child. */
148  const TREEVIEW_INFO *infoPtr; /* tree data this item belongs to */
149 } TREEVIEW_ITEM;
150 
151 /******** Defines that TREEVIEW_ProcessLetterKeys uses ****************/
152 #define KEY_DELAY 450
153 
154 /* bitflags for infoPtr->uInternalStatus */
155 
156 #define TV_HSCROLL 0x01 /* treeview too large to fit in window */
157 #define TV_VSCROLL 0x02 /* (horizontal/vertical) */
158 #define TV_LDRAG 0x04 /* Lbutton pushed to start drag */
159 #define TV_LDRAGGING 0x08 /* Lbutton pushed, mouse moved. */
160 #define TV_RDRAG 0x10 /* ditto Rbutton */
161 #define TV_RDRAGGING 0x20
162 
163 /* bitflags for infoPtr->timer */
164 
165 #define TV_EDIT_TIMER 2
166 #define TV_EDIT_TIMER_SET 2
167 
168 #define TEXT_CALLBACK_SIZE 260
169 
170 #define TREEVIEW_LEFT_MARGIN 8
171 
172 #define MINIMUM_INDENT 19
173 
174 #define CALLBACK_MASK_ALL (TVIF_TEXT|TVIF_CHILDREN|TVIF_IMAGE|TVIF_SELECTEDIMAGE)
175 
176 #define STATEIMAGEINDEX(x) (((x) >> 12) & 0x0f)
177 #define OVERLAYIMAGEINDEX(x) (((x) >> 8) & 0x0f)
178 #define ISVISIBLE(x) ((x)->visibleOrder >= 0)
179 
180 #define GETLINECOLOR(x) ((x) == CLR_DEFAULT ? comctl32_color.clrGrayText : (x))
181 #define GETBKCOLOR(x) ((x) == CLR_NONE ? comctl32_color.clrWindow : (x))
182 #define GETTXTCOLOR(x) ((x) == CLR_NONE ? comctl32_color.clrWindowText : (x))
183 #define GETINSCOLOR(x) ((x) == CLR_DEFAULT ? comctl32_color.clrBtnText : (x))
184 
185 static const WCHAR themeClass[] = { 'T','r','e','e','v','i','e','w',0 };
186 
187 
189 
190 
191 static VOID TREEVIEW_Invalidate(const TREEVIEW_INFO *, const TREEVIEW_ITEM *);
192 
196 static LRESULT TREEVIEW_EndEditLabelNow(TREEVIEW_INFO *infoPtr, BOOL bCancel);
199 
200 /* Random Utilities *****************************************************/
201 static void TREEVIEW_VerifyTree(TREEVIEW_INFO *infoPtr);
202 
203 /* Returns the treeview private data if hwnd is a treeview.
204  * Otherwise returns an undefined value. */
205 static inline TREEVIEW_INFO *
207 {
208  return (TREEVIEW_INFO *)GetWindowLongPtrW(hwnd, 0);
209 }
210 
211 /* Don't call this. Nothing wants an item index. */
212 static inline int
214 {
215  return DPA_GetPtrIndex(infoPtr->items, handle);
216 }
217 
218 /* Checks if item has changed and needs to be redrawn */
219 static inline BOOL item_changed (const TREEVIEW_ITEM *tiOld, const TREEVIEW_ITEM *tiNew,
220  const TVITEMEXW *tvChange)
221 {
222  /* Number of children has changed */
223  if ((tvChange->mask & TVIF_CHILDREN) && (tiOld->cChildren != tiNew->cChildren))
224  return TRUE;
225 
226  /* Image has changed and it's not a callback */
227  if ((tvChange->mask & TVIF_IMAGE) && (tiOld->iImage != tiNew->iImage) &&
228  tiNew->iImage != I_IMAGECALLBACK)
229  return TRUE;
230 
231  /* Selected image has changed and it's not a callback */
232  if ((tvChange->mask & TVIF_SELECTEDIMAGE) && (tiOld->iSelectedImage != tiNew->iSelectedImage) &&
234  return TRUE;
235 
236  if ((tvChange->mask & TVIF_EXPANDEDIMAGE) && (tiOld->iExpandedImage != tiNew->iExpandedImage) &&
238  return TRUE;
239 
240  /* Text has changed and it's not a callback */
241  if ((tvChange->mask & TVIF_TEXT) && (tiOld->pszText != tiNew->pszText) &&
242  tiNew->pszText != LPSTR_TEXTCALLBACKW)
243  return TRUE;
244 
245  /* Indent has changed */
246  if ((tvChange->mask & TVIF_INTEGRAL) && (tiOld->iIntegral != tiNew->iIntegral))
247  return TRUE;
248 
249  /* Item state has changed */
250  if ((tvChange->mask & TVIF_STATE) && ((tiOld->state ^ tiNew->state) & tvChange->stateMask ))
251  return TRUE;
252 
253  return FALSE;
254 }
255 
256 /***************************************************************************
257  * This method checks that handle is an item for this tree.
258  */
259 static BOOL
261 {
262  if (TREEVIEW_GetItemIndex(infoPtr, handle) == -1)
263  {
264  TRACE("invalid item %p\n", handle);
265  return FALSE;
266  }
267  else
268  return TRUE;
269 }
270 
271 static HFONT
273 {
274  LOGFONTW font;
275 
276  GetObjectW(hOrigFont, sizeof(font), &font);
277  font.lfWeight = FW_BOLD;
278  return CreateFontIndirectW(&font);
279 }
280 
281 static HFONT
283 {
284  LOGFONTW font;
285 
286  GetObjectW(hOrigFont, sizeof(font), &font);
287  font.lfUnderline = TRUE;
288  return CreateFontIndirectW(&font);
289 }
290 
291 static HFONT
293 {
294  LOGFONTW font;
295 
296  GetObjectW(hfont, sizeof(font), &font);
297  font.lfWeight = FW_BOLD;
298  font.lfUnderline = TRUE;
299  return CreateFontIndirectW(&font);
300 }
301 
302 static inline HFONT
304 {
305  if ((infoPtr->dwStyle & TVS_TRACKSELECT) && (item == infoPtr->hotItem))
306  return item->state & TVIS_BOLD ? infoPtr->hBoldUnderlineFont : infoPtr->hUnderlineFont;
307  if (item->state & TVIS_BOLD)
308  return infoPtr->hBoldFont;
309  return infoPtr->hFont;
310 }
311 
312 /* for trace/debugging purposes only */
313 static const char *
315 {
316  if (item == NULL) return "<null item>";
317  if (item->pszText == LPSTR_TEXTCALLBACKW) return "<callback>";
318  if (item->pszText == NULL) return "<null>";
319  return debugstr_w(item->pszText);
320 }
321 
322 /* An item is not a child of itself. */
323 static BOOL
325 {
326  do
327  {
328  child = child->parent;
329  if (child == parent) return TRUE;
330  } while (child != NULL);
331 
332  return FALSE;
333 }
334 
335 static BOOL
337 {
338  return !(infoPtr->dwStyle & TVS_HASLINES) && (infoPtr->dwStyle & TVS_FULLROWSELECT);
339 }
340 
341 static BOOL
343 {
344  if (TREEVIEW_IsFullRowSelect(infoPtr))
346  else
347  return ht->flags & TVHT_ONITEM;
348 }
349 
350 /* Tree Traversal *******************************************************/
351 
352 /***************************************************************************
353  * This method returns the last expanded sibling or child child item
354  * of a tree node
355  */
356 static TREEVIEW_ITEM *
358 {
359  if (!item) return NULL;
360 
361  while (item->lastChild)
362  {
363  if (item->state & TVIS_EXPANDED)
364  item = item->lastChild;
365  else
366  break;
367  }
368 
369  if (item == infoPtr->root)
370  return NULL;
371 
372  return item;
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 *
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 *
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 *
445  LONG count)
446 {
447  TREEVIEW_ITEM *(*next_item)(const TREEVIEW_INFO *, const TREEVIEW_ITEM *);
448  TREEVIEW_ITEM *previousItem;
449 
450  assert(item != NULL);
451 
452  if (count > 0)
453  {
455  }
456  else if (count < 0)
457  {
458  count = -count;
460  }
461  else
462  return item;
463 
464  do
465  {
466  previousItem = item;
467  item = next_item(infoPtr, item);
468 
469  } while (--count && item != NULL);
470 
471 
472  return item ? item : 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;
487  case TVN_BEGINDRAGW: return TVN_BEGINDRAGA;
488  case TVN_BEGINRDRAGW: return TVN_BEGINRDRAGA;
489  case TVN_DELETEITEMW: return TVN_DELETEITEMA;
492  case TVN_GETINFOTIPW: return TVN_GETINFOTIPA;
493  }
494  }
495  return code;
496 }
497 
498 static inline BOOL
500 {
501  TRACE("code=%d, hdr=%p\n", code, hdr);
502 
503  hdr->hwndFrom = infoPtr->hwnd;
504  hdr->idFrom = GetWindowLongPtrW(infoPtr->hwnd, GWLP_ID);
505  hdr->code = get_notifycode(infoPtr, code);
506 
507  return SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, hdr->idFrom, (LPARAM)hdr);
508 }
509 
510 static BOOL
512 {
513  NMHDR hdr;
514  return TREEVIEW_SendRealNotify(infoPtr, code, &hdr);
515 }
516 
517 static VOID
519 {
520  tvItem->mask = mask;
521  tvItem->hItem = item;
522  tvItem->state = item->state;
523  tvItem->stateMask = 0;
524  tvItem->iImage = item->iImage;
525  tvItem->iSelectedImage = item->iSelectedImage;
526  tvItem->cChildren = item->cChildren;
527  tvItem->lParam = item->lParam;
528 
529  if(mask & TVIF_TEXT)
530  {
531  if (!infoPtr->bNtfUnicode)
532  {
533  tvItem->cchTextMax = WideCharToMultiByte( CP_ACP, 0, item->pszText, -1, NULL, 0, NULL, NULL );
534  tvItem->pszText = Alloc (tvItem->cchTextMax);
535  WideCharToMultiByte( CP_ACP, 0, item->pszText, -1, (LPSTR)tvItem->pszText, tvItem->cchTextMax, 0, 0 );
536  }
537  else
538  {
539  tvItem->cchTextMax = item->cchTextMax;
540  tvItem->pszText = item->pszText;
541  }
542  }
543  else
544  {
545  tvItem->cchTextMax = 0;
546  tvItem->pszText = NULL;
547  }
548 }
549 
550 static BOOL
552  UINT mask, HTREEITEM oldItem, HTREEITEM newItem)
553 {
554  NMTREEVIEWW nmhdr;
555  BOOL ret;
556 
557  TRACE("code:%d action:0x%x olditem:%p newitem:%p\n",
558  code, action, oldItem, newItem);
559 
560  memset(&nmhdr, 0, sizeof(NMTREEVIEWW));
561  nmhdr.action = action;
562 
563  if (oldItem)
564  TREEVIEW_TVItemFromItem(infoPtr, mask, &nmhdr.itemOld, oldItem);
565 
566  if (newItem)
567  TREEVIEW_TVItemFromItem(infoPtr, mask, &nmhdr.itemNew, newItem);
568 
569  nmhdr.ptDrag.x = 0;
570  nmhdr.ptDrag.y = 0;
571 
572  ret = TREEVIEW_SendRealNotify(infoPtr, code, &nmhdr.hdr);
573  if (!infoPtr->bNtfUnicode)
574  {
575  Free(nmhdr.itemOld.pszText);
576  Free(nmhdr.itemNew.pszText);
577  }
578  return ret;
579 }
580 
581 static BOOL
583  HTREEITEM dragItem, POINT pt)
584 {
585  NMTREEVIEWW nmhdr;
586 
587  TRACE("code:%d dragitem:%p\n", code, dragItem);
588 
589  nmhdr.action = 0;
591  nmhdr.itemNew.hItem = dragItem;
592  nmhdr.itemNew.state = dragItem->state;
593  nmhdr.itemNew.lParam = dragItem->lParam;
594 
595  nmhdr.ptDrag.x = pt.x;
596  nmhdr.ptDrag.y = pt.y;
597 
598  return TREEVIEW_SendRealNotify(infoPtr, code, &nmhdr.hdr);
599 }
600 
601 
602 static BOOL
604  HDC hdc, RECT rc)
605 {
606  NMTVCUSTOMDRAW nmcdhdr;
607  NMCUSTOMDRAW *nmcd;
608 
609  TRACE("drawstage:0x%x hdc:%p\n", dwDrawStage, hdc);
610 
611  nmcd = &nmcdhdr.nmcd;
612  nmcd->dwDrawStage = dwDrawStage;
613  nmcd->hdc = hdc;
614  nmcd->rc = rc;
615  nmcd->dwItemSpec = 0;
616  nmcd->uItemState = 0;
617  nmcd->lItemlParam = 0;
618  nmcdhdr.clrText = infoPtr->clrText;
619  nmcdhdr.clrTextBk = infoPtr->clrBk;
620  nmcdhdr.iLevel = 0;
621 
622  return TREEVIEW_SendRealNotify(infoPtr, NM_CUSTOMDRAW, &nmcdhdr.nmcd.hdr);
623 }
624 
625 /* FIXME: need to find out when the flags in uItemState need to be set */
626 
627 static BOOL
629  TREEVIEW_ITEM *item, UINT uItemDrawState,
630  NMTVCUSTOMDRAW *nmcdhdr)
631 {
632  NMCUSTOMDRAW *nmcd;
633  DWORD dwDrawStage;
634  DWORD_PTR dwItemSpec;
635  UINT uItemState;
636 
637  dwDrawStage = CDDS_ITEM | uItemDrawState;
638  dwItemSpec = (DWORD_PTR)item;
639  uItemState = 0;
640  if (item->state & TVIS_SELECTED)
641  uItemState |= CDIS_SELECTED;
642  if (item == infoPtr->selectedItem)
643  uItemState |= CDIS_FOCUS;
644  if (item == infoPtr->hotItem)
645  uItemState |= CDIS_HOT;
646 
647  nmcd = &nmcdhdr->nmcd;
648  nmcd->dwDrawStage = dwDrawStage;
649  nmcd->hdc = hdc;
650  nmcd->rc = item->rect;
651  nmcd->dwItemSpec = dwItemSpec;
652  nmcd->uItemState = uItemState;
653  nmcd->lItemlParam = item->lParam;
654  nmcdhdr->iLevel = item->iLevel;
655 
656  TRACE("drawstage:0x%x hdc:%p item:%lx, itemstate:0x%x, lItemlParam:0x%lx\n",
657  nmcd->dwDrawStage, nmcd->hdc, nmcd->dwItemSpec,
658  nmcd->uItemState, nmcd->lItemlParam);
659 
660  return TREEVIEW_SendRealNotify(infoPtr, NM_CUSTOMDRAW, &nmcdhdr->nmcd.hdr);
661 }
662 
663 static BOOL
665 {
666  NMTVDISPINFOW tvdi;
667  BOOL ret;
668 
670  &tvdi.item, editItem);
671 
672  ret = TREEVIEW_SendRealNotify(infoPtr, TVN_BEGINLABELEDITW, &tvdi.hdr);
673 
674  if (!infoPtr->bNtfUnicode)
675  Free(tvdi.item.pszText);
676 
677  return ret;
678 }
679 
680 static void
682  UINT mask)
683 {
684  NMTVDISPINFOEXW callback;
685 
686  TRACE("mask=0x%x, callbackmask=0x%x\n", mask, item->callbackMask);
687  mask &= item->callbackMask;
688 
689  if (mask == 0) return;
690 
691  /* 'state' always contains valid value, as well as 'lParam'.
692  * All other parameters are uninitialized.
693  */
694  callback.item.pszText = item->pszText;
695  callback.item.cchTextMax = item->cchTextMax;
696  callback.item.mask = mask;
697  callback.item.hItem = item;
698  callback.item.state = item->state;
699  callback.item.lParam = item->lParam;
700 
701  /* If text is changed we need to recalculate textWidth */
702  if (mask & TVIF_TEXT)
703  item->textWidth = 0;
704 
705  TREEVIEW_SendRealNotify(infoPtr, TVN_GETDISPINFOW, &callback.hdr);
706  TRACE("resulting code 0x%08x\n", callback.hdr.code);
707 
708  /* It may have changed due to a call to SetItem. */
709  mask &= item->callbackMask;
710 
711  if ((mask & TVIF_TEXT) && callback.item.pszText != item->pszText)
712  {
713  /* Instead of copying text into our buffer user specified his own */
714  if (!infoPtr->bNtfUnicode && (callback.hdr.code == TVN_GETDISPINFOA)) {
715  LPWSTR newText;
716  int buflen;
717  int len = MultiByteToWideChar( CP_ACP, 0,
718  (LPSTR)callback.item.pszText, -1,
719  NULL, 0);
720  buflen = max((len)*sizeof(WCHAR), TEXT_CALLBACK_SIZE);
721  newText = ReAlloc(item->pszText, buflen);
722 
723  TRACE("returned str %s, len=%d, buflen=%d\n",
724  debugstr_a((LPSTR)callback.item.pszText), len, buflen);
725 
726  if (newText)
727  {
728  item->pszText = newText;
730  (LPSTR)callback.item.pszText, -1,
731  item->pszText, buflen/sizeof(WCHAR));
732  item->cchTextMax = buflen/sizeof(WCHAR);
733  }
734  /* If ReAlloc fails we have nothing to do, but keep original text */
735  }
736  else {
737  int len = max(lstrlenW(callback.item.pszText) + 1,
739  LPWSTR newText = ReAlloc(item->pszText, len);
740 
741  TRACE("returned wstr %s, len=%d\n",
742  debugstr_w(callback.item.pszText), len);
743 
744  if (newText)
745  {
746  item->pszText = newText;
747  strcpyW(item->pszText, callback.item.pszText);
748  item->cchTextMax = len;
749  }
750  /* If ReAlloc fails we have nothing to do, but keep original text */
751  }
752  }
753  else if (mask & TVIF_TEXT) {
754  /* User put text into our buffer, that is ok unless A string */
755  if (!infoPtr->bNtfUnicode && (callback.hdr.code == TVN_GETDISPINFOA)) {
756  LPWSTR newText;
757  int buflen;
758  int len = MultiByteToWideChar( CP_ACP, 0,
759  (LPSTR)callback.item.pszText, -1,
760  NULL, 0);
761  buflen = max((len)*sizeof(WCHAR), TEXT_CALLBACK_SIZE);
762  newText = Alloc(buflen);
763 
764  TRACE("same buffer str %s, len=%d, buflen=%d\n",
765  debugstr_a((LPSTR)callback.item.pszText), len, buflen);
766 
767  if (newText)
768  {
769  LPWSTR oldText = item->pszText;
770  item->pszText = newText;
772  (LPSTR)callback.item.pszText, -1,
773  item->pszText, buflen/sizeof(WCHAR));
774  item->cchTextMax = buflen/sizeof(WCHAR);
775  Free(oldText);
776  }
777  }
778  }
779 
780  if (mask & TVIF_IMAGE)
781  item->iImage = callback.item.iImage;
782 
783  if (mask & TVIF_SELECTEDIMAGE)
784  item->iSelectedImage = callback.item.iSelectedImage;
785 
786  if (mask & TVIF_EXPANDEDIMAGE)
787  item->iExpandedImage = callback.item.iExpandedImage;
788 
789  if (mask & TVIF_CHILDREN)
790  item->cChildren = callback.item.cChildren;
791 
792  if (callback.item.mask & TVIF_STATE)
793  {
794  item->state &= ~callback.item.stateMask;
795  item->state |= (callback.item.state & callback.item.stateMask);
796  }
797 
798  /* These members are now permanently set. */
799  if (callback.item.mask & TVIF_DI_SETITEM)
800  item->callbackMask &= ~callback.item.mask;
801 }
802 
803 /***************************************************************************
804  * This function uses cChildren field to decide whether the item has
805  * children or not.
806  * Note: if this returns TRUE, the child items may not actually exist,
807  * they could be virtual.
808  *
809  * Just use item->firstChild to check for physical children.
810  */
811 static BOOL
813 {
814  TREEVIEW_UpdateDispInfo(infoPtr, item, TVIF_CHILDREN);
815  /* Protect for a case when callback field is not changed by a host,
816  otherwise negative values trigger normal notifications. */
817  return item->cChildren != 0 && item->cChildren != I_CHILDRENCALLBACK;
818 }
819 
820 static INT TREEVIEW_NotifyFormat (TREEVIEW_INFO *infoPtr, HWND hwndFrom, UINT nCommand)
821 {
822  INT format;
823 
824  TRACE("(hwndFrom=%p, nCommand=%d)\n", hwndFrom, nCommand);
825 
826  if (nCommand != NF_REQUERY) return 0;
827 
828  format = SendMessageW(hwndFrom, WM_NOTIFYFORMAT, (WPARAM)infoPtr->hwnd, NF_QUERY);
829  TRACE("format=%d\n", format);
830 
831  /* Invalid format returned by NF_QUERY defaults to ANSI*/
832  if (format != NFR_ANSI && format != NFR_UNICODE)
833  format = NFR_ANSI;
834 
835  infoPtr->bNtfUnicode = (format == NFR_UNICODE);
836 
837  return format;
838 }
839 
840 /* Item Position ********************************************************/
841 
842 /* Compute linesOffset, stateOffset, imageOffset, textOffset of an item. */
843 static VOID
845 {
846  /* has TVS_LINESATROOT and (TVS_HASLINES|TVS_HASBUTTONS) */
848  > TVS_LINESATROOT);
849 
850  item->linesOffset = infoPtr->uIndent * (lar ? item->iLevel : item->iLevel - 1)
851  - infoPtr->scrollX;
852  item->stateOffset = item->linesOffset + infoPtr->uIndent;
853  item->imageOffset = item->stateOffset
854  + (STATEIMAGEINDEX(item->state) ? infoPtr->stateImageWidth : 0);
855  item->textOffset = item->imageOffset + infoPtr->normalImageWidth;
856 }
857 
858 static VOID
860 {
861  HDC hdc;
862  HFONT hOldFont=0;
863  SIZE sz;
864 
865  /* DRAW's OM docker creates items like this */
866  if (item->pszText == NULL)
867  {
868  item->textWidth = 0;
869  return;
870  }
871 
872  if (hDC != 0)
873  {
874  hdc = hDC;
875  }
876  else
877  {
878  hdc = GetDC(infoPtr->hwnd);
879  hOldFont = SelectObject(hdc, TREEVIEW_FontForItem(infoPtr, item));
880  }
881 
882  GetTextExtentPoint32W(hdc, item->pszText, strlenW(item->pszText), &sz);
883  item->textWidth = sz.cx;
884 
885  if (hDC == 0)
886  {
887  SelectObject(hdc, hOldFont);
888  ReleaseDC(0, hdc);
889  }
890 }
891 
892 static VOID
894 {
895  item->rect.top = infoPtr->uItemHeight *
896  (item->visibleOrder - infoPtr->firstVisible->visibleOrder);
897 
898  item->rect.bottom = item->rect.top
899  + infoPtr->uItemHeight * item->iIntegral - 1;
900 
901  item->rect.left = 0;
902  item->rect.right = infoPtr->clientWidth;
903 }
904 
905 /* We know that only items after start need their order updated. */
906 static void
908 {
910  int order;
911 
912  if (!start)
913  {
914  start = infoPtr->root->firstChild;
915  order = 0;
916  }
917  else
918  order = start->visibleOrder;
919 
920  for (item = start; item != NULL;
921  item = TREEVIEW_GetNextListItem(infoPtr, item))
922  {
923  if (!ISVISIBLE(item) && order > 0)
925  item->visibleOrder = order;
926  order += item->iIntegral;
927  }
928 
929  infoPtr->maxVisibleOrder = order;
930 
931  for (item = start; item != NULL;
932  item = TREEVIEW_GetNextListItem(infoPtr, item))
933  {
934  TREEVIEW_ComputeItemRect(infoPtr, item);
935  }
936 }
937 
938 
939 /* Update metrics of all items in selected subtree.
940  * root must be expanded
941  */
942 static VOID
944 {
945  TREEVIEW_ITEM *sibling;
946  HDC hdc;
947  HFONT hOldFont;
948 
949  if (!root->firstChild || !(root->state & TVIS_EXPANDED))
950  return;
951 
952  root->state &= ~TVIS_EXPANDED;
953  sibling = TREEVIEW_GetNextListItem(infoPtr, root);
954  root->state |= TVIS_EXPANDED;
955 
956  hdc = GetDC(infoPtr->hwnd);
957  hOldFont = SelectObject(hdc, infoPtr->hFont);
958 
959  for (; root != sibling;
960  root = TREEVIEW_GetNextListItem(infoPtr, root))
961  {
963 
964  if (root->callbackMask & TVIF_TEXT)
965  TREEVIEW_UpdateDispInfo(infoPtr, root, TVIF_TEXT);
966 
967  if (root->textWidth == 0)
968  {
969  SelectObject(hdc, TREEVIEW_FontForItem(infoPtr, root));
970  TREEVIEW_ComputeTextWidth(infoPtr, root, hdc);
971  }
972  }
973 
974  SelectObject(hdc, hOldFont);
975  ReleaseDC(infoPtr->hwnd, hdc);
976 }
977 
978 /* Item Allocation **********************************************************/
979 
980 static TREEVIEW_ITEM *
982 {
983  TREEVIEW_ITEM *newItem = Alloc(sizeof(TREEVIEW_ITEM));
984 
985  if (!newItem)
986  return NULL;
987 
988  /* I_IMAGENONE would make more sense but this is neither what is
989  * documented (MSDN doesn't specify) nor what Windows actually does
990  * (it sets it to zero)... and I can so imagine an application using
991  * inc/dec to toggle the images. */
992  newItem->iImage = 0;
993  newItem->iSelectedImage = 0;
994  newItem->iExpandedImage = (WORD)I_IMAGENONE;
995  newItem->infoPtr = infoPtr;
996 
997  if (DPA_InsertPtr(infoPtr->items, INT_MAX, newItem) == -1)
998  {
999  Free(newItem);
1000  return NULL;
1001  }
1002 
1003  return newItem;
1004 }
1005 
1006 /* Exact opposite of TREEVIEW_AllocateItem. In particular, it does not
1007  * free item->pszText. */
1008 static void
1010 {
1011  DPA_DeletePtr(infoPtr->items, DPA_GetPtrIndex(infoPtr->items, item));
1012  if (infoPtr->selectedItem == item)
1013  infoPtr->selectedItem = NULL;
1014  if (infoPtr->hotItem == item)
1015  infoPtr->hotItem = NULL;
1016  if (infoPtr->focusedItem == item)
1017  infoPtr->focusedItem = NULL;
1018  if (infoPtr->firstVisible == item)
1019  infoPtr->firstVisible = NULL;
1020  if (infoPtr->dropItem == item)
1021  infoPtr->dropItem = NULL;
1022  if (infoPtr->insertMarkItem == item)
1023  infoPtr->insertMarkItem = NULL;
1024  Free(item);
1025 }
1026 
1027 
1028 /* Item Insertion *******************************************************/
1029 
1030 /***************************************************************************
1031  * This method inserts newItem before sibling as a child of parent.
1032  * sibling can be NULL, but only if parent has no children.
1033  */
1034 static void
1037 {
1038  assert(parent != NULL);
1039 
1040  if (sibling != NULL)
1041  {
1042  assert(sibling->parent == parent);
1043 
1044  if (sibling->prevSibling != NULL)
1045  sibling->prevSibling->nextSibling = newItem;
1046 
1047  newItem->prevSibling = sibling->prevSibling;
1048  sibling->prevSibling = newItem;
1049  }
1050  else
1051  newItem->prevSibling = NULL;
1052 
1053  newItem->nextSibling = sibling;
1054 
1055  if (parent->firstChild == sibling)
1056  parent->firstChild = newItem;
1057 
1058  if (parent->lastChild == NULL)
1059  parent->lastChild = newItem;
1060 }
1061 
1062 /***************************************************************************
1063  * This method inserts newItem after sibling as a child of parent.
1064  * sibling can be NULL, but only if parent has no children.
1065  */
1066 static void
1069 {
1070  assert(parent != NULL);
1071 
1072  if (sibling != NULL)
1073  {
1074  assert(sibling->parent == parent);
1075 
1076  if (sibling->nextSibling != NULL)
1077  sibling->nextSibling->prevSibling = newItem;
1078 
1079  newItem->nextSibling = sibling->nextSibling;
1080  sibling->nextSibling = newItem;
1081  }
1082  else
1083  newItem->nextSibling = NULL;
1084 
1085  newItem->prevSibling = sibling;
1086 
1087  if (parent->lastChild == sibling)
1088  parent->lastChild = newItem;
1089 
1090  if (parent->firstChild == NULL)
1091  parent->firstChild = newItem;
1092 }
1093 
1094 static BOOL
1096  const TVITEMEXW *tvItem, BOOL isW)
1097 {
1098  UINT callbackClear = 0;
1099  UINT callbackSet = 0;
1100 
1101  TRACE("item %p\n", item);
1102  /* Do this first in case it fails. */
1103  if (tvItem->mask & TVIF_TEXT)
1104  {
1105  item->textWidth = 0; /* force width recalculation */
1106  if (tvItem->pszText != LPSTR_TEXTCALLBACKW && tvItem->pszText != NULL) /* covers != TEXTCALLBACKA too, and undocumented: pszText of NULL also means TEXTCALLBACK */
1107  {
1108  int len;
1109  LPWSTR newText;
1110  if (isW)
1111  len = lstrlenW(tvItem->pszText) + 1;
1112  else
1113  len = MultiByteToWideChar(CP_ACP, 0, (LPSTR)tvItem->pszText, -1, NULL, 0);
1114 
1115  newText = ReAlloc(item->pszText, len * sizeof(WCHAR));
1116 
1117  if (newText == NULL) return FALSE;
1118 
1119  callbackClear |= TVIF_TEXT;
1120 
1121  item->pszText = newText;
1122  item->cchTextMax = len;
1123  if (isW)
1124  lstrcpynW(item->pszText, tvItem->pszText, len);
1125  else
1126  MultiByteToWideChar(CP_ACP, 0, (LPSTR)tvItem->pszText, -1,
1127  item->pszText, len);
1128 
1129  TRACE("setting text %s, item %p\n", debugstr_w(item->pszText), item);
1130  }
1131  else
1132  {
1133  callbackSet |= TVIF_TEXT;
1134 
1135  item->pszText = ReAlloc(item->pszText,
1136  TEXT_CALLBACK_SIZE * sizeof(WCHAR));
1138  TRACE("setting callback, item %p\n", item);
1139  }
1140  }
1141 
1142  if (tvItem->mask & TVIF_CHILDREN)
1143  {
1144  item->cChildren = tvItem->cChildren;
1145 
1146  if (item->cChildren == I_CHILDRENCALLBACK)
1147  callbackSet |= TVIF_CHILDREN;
1148  else
1149  callbackClear |= TVIF_CHILDREN;
1150  }
1151 
1152  if (tvItem->mask & TVIF_IMAGE)
1153  {
1154  item->iImage = tvItem->iImage;
1155 
1156  if (item->iImage == I_IMAGECALLBACK)
1157  callbackSet |= TVIF_IMAGE;
1158  else
1159  callbackClear |= TVIF_IMAGE;
1160  }
1161 
1162  if (tvItem->mask & TVIF_SELECTEDIMAGE)
1163  {
1164  item->iSelectedImage = tvItem->iSelectedImage;
1165 
1166  if (item->iSelectedImage == I_IMAGECALLBACK)
1167  callbackSet |= TVIF_SELECTEDIMAGE;
1168  else
1169  callbackClear |= TVIF_SELECTEDIMAGE;
1170  }
1171 
1172  if (tvItem->mask & TVIF_EXPANDEDIMAGE)
1173  {
1174  item->iExpandedImage = tvItem->iExpandedImage;
1175 
1176  if (item->iExpandedImage == I_IMAGECALLBACK)
1177  callbackSet |= TVIF_EXPANDEDIMAGE;
1178  else
1179  callbackClear |= TVIF_EXPANDEDIMAGE;
1180  }
1181 
1182  if (tvItem->mask & TVIF_PARAM)
1183  item->lParam = tvItem->lParam;
1184 
1185  /* If the application sets TVIF_INTEGRAL without
1186  * supplying a TVITEMEX structure, it's toast. */
1187  if (tvItem->mask & TVIF_INTEGRAL)
1188  item->iIntegral = tvItem->iIntegral;
1189 
1190  if (tvItem->mask & TVIF_STATE)
1191  {
1192  TRACE("prevstate 0x%x, state 0x%x, mask 0x%x\n", item->state, tvItem->state,
1193  tvItem->stateMask);
1194  item->state &= ~tvItem->stateMask;
1195  item->state |= (tvItem->state & tvItem->stateMask);
1196  }
1197 
1198  if (tvItem->mask & TVIF_STATEEX)
1199  {
1200  FIXME("New extended state: 0x%x\n", tvItem->uStateEx);
1201  }
1202 
1203  item->callbackMask |= callbackSet;
1204  item->callbackMask &= ~callbackClear;
1205 
1206  return TRUE;
1207 }
1208 
1209 /* Note that the new item is pre-zeroed. */
1210 static LRESULT
1212 {
1213  const TVITEMEXW *tvItem = &ptdi->u.itemex;
1214  HTREEITEM insertAfter;
1215  TREEVIEW_ITEM *newItem, *parentItem;
1216  BOOL bTextUpdated = FALSE;
1217 
1218  if (ptdi->hParent == TVI_ROOT || ptdi->hParent == 0)
1219  {
1220  parentItem = infoPtr->root;
1221  }
1222  else
1223  {
1224  parentItem = ptdi->hParent;
1225 
1226  if (!TREEVIEW_ValidItem(infoPtr, parentItem))
1227  {
1228  WARN("invalid parent %p\n", parentItem);
1229  return 0;
1230  }
1231  }
1232 
1233  insertAfter = ptdi->hInsertAfter;
1234 
1235  /* Validate this now for convenience. */
1236  switch ((DWORD_PTR)insertAfter)
1237  {
1238  case (DWORD_PTR)TVI_FIRST:
1239  case (DWORD_PTR)TVI_LAST:
1240  case (DWORD_PTR)TVI_SORT:
1241  break;
1242 
1243  default:
1244  if (!TREEVIEW_ValidItem(infoPtr, insertAfter) ||
1245  insertAfter->parent != parentItem)
1246  {
1247  WARN("invalid insert after %p\n", insertAfter);
1248  insertAfter = TVI_LAST;
1249  }
1250  }
1251 
1252  TRACE("parent %p position %p: %s\n", parentItem, insertAfter,
1253  (tvItem->mask & TVIF_TEXT)
1254  ? ((tvItem->pszText == LPSTR_TEXTCALLBACKW) ? "<callback>"
1255  : (isW ? debugstr_w(tvItem->pszText) : debugstr_a((LPSTR)tvItem->pszText)))
1256  : "<no label>");
1257 
1258  newItem = TREEVIEW_AllocateItem(infoPtr);
1259  if (newItem == NULL)
1260  return 0;
1261 
1262  newItem->parent = parentItem;
1263  newItem->iIntegral = 1;
1264  newItem->visibleOrder = -1;
1265 
1266  if (!TREEVIEW_DoSetItemT(infoPtr, newItem, tvItem, isW))
1267  return 0;
1268 
1269  /* After this point, nothing can fail. (Except for TVI_SORT.) */
1270 
1271  infoPtr->uNumItems++;
1272 
1273  switch ((DWORD_PTR)insertAfter)
1274  {
1275  case (DWORD_PTR)TVI_FIRST:
1276  {
1277  TREEVIEW_ITEM *originalFirst = parentItem->firstChild;
1278  TREEVIEW_InsertBefore(newItem, parentItem->firstChild, parentItem);
1279  if (infoPtr->firstVisible == originalFirst)
1280  TREEVIEW_SetFirstVisible(infoPtr, newItem, TRUE);
1281  }
1282  break;
1283 
1284  case (DWORD_PTR)TVI_LAST:
1285  TREEVIEW_InsertAfter(newItem, parentItem->lastChild, parentItem);
1286  break;
1287 
1288  /* hInsertAfter names a specific item we want to insert after */
1289  default:
1290  TREEVIEW_InsertAfter(newItem, insertAfter, insertAfter->parent);
1291  break;
1292 
1293  case (DWORD_PTR)TVI_SORT:
1294  {
1295  TREEVIEW_ITEM *aChild;
1296  TREEVIEW_ITEM *previousChild = NULL;
1297  TREEVIEW_ITEM *originalFirst = parentItem->firstChild;
1298  BOOL bItemInserted = FALSE;
1299 
1300  aChild = parentItem->firstChild;
1301 
1302  bTextUpdated = TRUE;
1303  TREEVIEW_UpdateDispInfo(infoPtr, newItem, TVIF_TEXT);
1304 
1305  /* Iterate the parent children to see where we fit in */
1306  while (aChild != NULL)
1307  {
1308  INT comp;
1309 
1310  TREEVIEW_UpdateDispInfo(infoPtr, aChild, TVIF_TEXT);
1311  comp = lstrcmpW(newItem->pszText, aChild->pszText);
1312 
1313  if (comp < 0) /* we are smaller than the current one */
1314  {
1315  TREEVIEW_InsertBefore(newItem, aChild, parentItem);
1316  if (infoPtr->firstVisible == originalFirst &&
1317  aChild == originalFirst)
1318  TREEVIEW_SetFirstVisible(infoPtr, newItem, TRUE);
1319  bItemInserted = TRUE;
1320  break;
1321  }
1322  else if (comp > 0) /* we are bigger than the current one */
1323  {
1324  previousChild = aChild;
1325 
1326  /* This will help us to exit if there is no more sibling */
1327  aChild = (aChild->nextSibling == 0)
1328  ? NULL
1329  : aChild->nextSibling;
1330 
1331  /* Look at the next item */
1332  continue;
1333  }
1334  else if (comp == 0)
1335  {
1336  /*
1337  * An item with this name is already existing, therefore,
1338  * we add after the one we found
1339  */
1340  TREEVIEW_InsertAfter(newItem, aChild, parentItem);
1341  bItemInserted = TRUE;
1342  break;
1343  }
1344  }
1345 
1346  /*
1347  * we reach the end of the child list and the item has not
1348  * yet been inserted, therefore, insert it after the last child.
1349  */
1350  if ((!bItemInserted) && (aChild == NULL))
1351  TREEVIEW_InsertAfter(newItem, previousChild, parentItem);
1352 
1353  break;
1354  }
1355  }
1356 
1357 
1358  TRACE("new item %p; parent %p, mask 0x%x\n", newItem,
1359  newItem->parent, tvItem->mask);
1360 
1361  newItem->iLevel = newItem->parent->iLevel + 1;
1362 
1363  if (newItem->parent->cChildren == 0)
1364  newItem->parent->cChildren = 1;
1365 
1366  if (infoPtr->dwStyle & TVS_CHECKBOXES)
1367  {
1368  if (STATEIMAGEINDEX(newItem->state) == 0)
1369  newItem->state |= INDEXTOSTATEIMAGEMASK(1);
1370  }
1371 
1372  if (infoPtr->firstVisible == NULL)
1373  infoPtr->firstVisible = newItem;
1374 
1375  TREEVIEW_VerifyTree(infoPtr);
1376 
1377  if (!infoPtr->bRedraw) return (LRESULT)newItem;
1378 
1379  if (parentItem == infoPtr->root ||
1380  (ISVISIBLE(parentItem) && parentItem->state & TVIS_EXPANDED))
1381  {
1383  TREEVIEW_ITEM *prev = TREEVIEW_GetPrevListItem(infoPtr, newItem);
1384 
1385  TREEVIEW_RecalculateVisibleOrder(infoPtr, prev);
1386  TREEVIEW_ComputeItemInternalMetrics(infoPtr, newItem);
1387 
1388  if (!bTextUpdated)
1389  TREEVIEW_UpdateDispInfo(infoPtr, newItem, TVIF_TEXT);
1390 
1391  TREEVIEW_ComputeTextWidth(infoPtr, newItem, 0);
1392  TREEVIEW_UpdateScrollBars(infoPtr);
1393  /*
1394  * if the item was inserted in a visible part of the tree,
1395  * invalidate it, as well as those after it
1396  */
1397  for (item = newItem;
1398  item != NULL;
1399  item = TREEVIEW_GetNextListItem(infoPtr, item))
1400  TREEVIEW_Invalidate(infoPtr, item);
1401  }
1402  else
1403  {
1404  /* refresh treeview if newItem is the first item inserted under parentItem */
1405  if (ISVISIBLE(parentItem) && newItem->prevSibling == newItem->nextSibling)
1406  {
1407  /* parent got '+' - update it */
1408  TREEVIEW_Invalidate(infoPtr, parentItem);
1409  }
1410  }
1411 
1412  return (LRESULT)newItem;
1413 }
1414 
1415 /* Item Deletion ************************************************************/
1416 static void
1418 
1419 static void
1421 {
1422  TREEVIEW_ITEM *kill = parentItem->firstChild;
1423 
1424  while (kill != NULL)
1425  {
1426  TREEVIEW_ITEM *next = kill->nextSibling;
1427 
1428  TREEVIEW_RemoveItem(infoPtr, kill);
1429 
1430  kill = next;
1431  }
1432 
1433  assert(parentItem->cChildren <= 0); /* I_CHILDRENCALLBACK or 0 */
1434  assert(parentItem->firstChild == NULL);
1435  assert(parentItem->lastChild == NULL);
1436 }
1437 
1438 static void
1440 {
1441  TREEVIEW_ITEM *parentItem;
1442 
1443  assert(item != NULL);
1444  assert(item->parent != NULL); /* i.e. it must not be the root */
1445 
1446  parentItem = item->parent;
1447 
1448  if (parentItem->firstChild == item)
1449  parentItem->firstChild = item->nextSibling;
1450 
1451  if (parentItem->lastChild == item)
1452  parentItem->lastChild = item->prevSibling;
1453 
1454  if (parentItem->firstChild == NULL && parentItem->lastChild == NULL
1455  && parentItem->cChildren > 0)
1456  parentItem->cChildren = 0;
1457 
1458  if (item->prevSibling)
1459  item->prevSibling->nextSibling = item->nextSibling;
1460 
1461  if (item->nextSibling)
1462  item->nextSibling->prevSibling = item->prevSibling;
1463 }
1464 
1465 static void
1467 {
1468  TRACE("%p, (%s)\n", item, TREEVIEW_ItemName(item));
1469 
1470  if (item->firstChild)
1471  TREEVIEW_RemoveAllChildren(infoPtr, item);
1472 
1474  TVIF_HANDLE | TVIF_PARAM, item, 0);
1475 
1476  TREEVIEW_UnlinkItem(item);
1477 
1478  infoPtr->uNumItems--;
1479 
1480  if (item->pszText != LPSTR_TEXTCALLBACKW)
1481  Free(item->pszText);
1482 
1483  TREEVIEW_FreeItem(infoPtr, item);
1484 }
1485 
1486 
1487 /* Empty out the tree. */
1488 static void
1490 {
1491  TREEVIEW_RemoveAllChildren(infoPtr, infoPtr->root);
1492 
1493  assert(infoPtr->uNumItems == 0); /* root isn't counted in uNumItems */
1494 }
1495 
1496 static LRESULT
1498 {
1499  TREEVIEW_ITEM *newSelection = NULL;
1500  TREEVIEW_ITEM *newFirstVisible = NULL;
1501  TREEVIEW_ITEM *parent, *prev = NULL;
1502  BOOL visible = FALSE;
1503 
1504  if (item == TVI_ROOT || !item)
1505  {
1506  TRACE("TVI_ROOT\n");
1507  parent = infoPtr->root;
1508  newSelection = NULL;
1509  visible = TRUE;
1510  TREEVIEW_RemoveTree(infoPtr);
1511  }
1512  else
1513  {
1514  if (!TREEVIEW_ValidItem(infoPtr, item))
1515  return FALSE;
1516 
1517  TRACE("%p (%s)\n", item, TREEVIEW_ItemName(item));
1518  parent = item->parent;
1519 
1520  if (ISVISIBLE(item))
1521  {
1522  prev = TREEVIEW_GetPrevListItem(infoPtr, item);
1523  visible = TRUE;
1524  }
1525 
1526  if (infoPtr->selectedItem != NULL
1527  && (item == infoPtr->selectedItem
1528  || TREEVIEW_IsChildOf(item, infoPtr->selectedItem)))
1529  {
1530  if (item->nextSibling)
1531  newSelection = item->nextSibling;
1532  else if (item->parent != infoPtr->root)
1533  newSelection = item->parent;
1534  else
1535  newSelection = item->prevSibling;
1536  TRACE("newSelection = %p\n", newSelection);
1537  }
1538 
1539  if (infoPtr->firstVisible == item)
1540  {
1541  visible = TRUE;
1542  if (item->nextSibling)
1543  newFirstVisible = item->nextSibling;
1544  else if (item->prevSibling)
1545  newFirstVisible = item->prevSibling;
1546  else if (item->parent != infoPtr->root)
1547  newFirstVisible = item->parent;
1548  TREEVIEW_SetFirstVisible(infoPtr, NULL, TRUE);
1549  }
1550  else
1551  newFirstVisible = infoPtr->firstVisible;
1552 
1553  TREEVIEW_RemoveItem(infoPtr, item);
1554  }
1555 
1556  /* Don't change if somebody else already has (infoPtr->selectedItem is cleared by FreeItem). */
1557  if (!infoPtr->selectedItem && newSelection)
1558  {
1559  if (TREEVIEW_ValidItem(infoPtr, newSelection))
1560  TREEVIEW_DoSelectItem(infoPtr, TVGN_CARET, newSelection, TVC_UNKNOWN);
1561  }
1562 
1563  /* Validate insertMark dropItem.
1564  * hotItem ??? - used for comparison only.
1565  */
1566  if (!TREEVIEW_ValidItem(infoPtr, infoPtr->insertMarkItem))
1567  infoPtr->insertMarkItem = 0;
1568 
1569  if (!TREEVIEW_ValidItem(infoPtr, infoPtr->dropItem))
1570  infoPtr->dropItem = 0;
1571 
1572  if (!TREEVIEW_ValidItem(infoPtr, newFirstVisible))
1573  newFirstVisible = infoPtr->root->firstChild;
1574 
1575  TREEVIEW_VerifyTree(infoPtr);
1576 
1577  if (visible)
1578  TREEVIEW_SetFirstVisible(infoPtr, newFirstVisible, TRUE);
1579 
1580  if (!infoPtr->bRedraw) return TRUE;
1581 
1582  if (visible)
1583  {
1584  TREEVIEW_RecalculateVisibleOrder(infoPtr, prev);
1585  TREEVIEW_UpdateScrollBars(infoPtr);
1586  TREEVIEW_Invalidate(infoPtr, NULL);
1587  }
1588  else if (ISVISIBLE(parent) && !TREEVIEW_HasChildren(infoPtr, parent))
1589  {
1590  /* parent lost '+/-' - update it */
1591  TREEVIEW_Invalidate(infoPtr, parent);
1592  }
1593 
1594  return TRUE;
1595 }
1596 
1597 
1598 /* Get/Set Messages *********************************************************/
1599 static LRESULT
1601 {
1602  infoPtr->bRedraw = wParam != 0;
1603 
1604  if (infoPtr->bRedraw)
1605  {
1606  TREEVIEW_UpdateSubTree(infoPtr, infoPtr->root);
1608  TREEVIEW_UpdateScrollBars(infoPtr);
1609  TREEVIEW_Invalidate(infoPtr, NULL);
1610  }
1611  return 0;
1612 }
1613 
1614 static LRESULT
1616 {
1617  TRACE("\n");
1618  return infoPtr->uIndent;
1619 }
1620 
1621 static LRESULT
1623 {
1624  TRACE("\n");
1625 
1626  if (newIndent < MINIMUM_INDENT)
1627  newIndent = MINIMUM_INDENT;
1628 
1629  if (infoPtr->uIndent != newIndent)
1630  {
1631  infoPtr->uIndent = newIndent;
1632  TREEVIEW_UpdateSubTree(infoPtr, infoPtr->root);
1633  TREEVIEW_UpdateScrollBars(infoPtr);
1634  TREEVIEW_Invalidate(infoPtr, NULL);
1635  }
1636 
1637  return 0;
1638 }
1639 
1640 
1641 static LRESULT
1643 {
1644  TRACE("\n");
1645  return (LRESULT)infoPtr->hwndToolTip;
1646 }
1647 
1648 static LRESULT
1650 {
1651  HWND prevToolTip;
1652 
1653  TRACE("\n");
1654  prevToolTip = infoPtr->hwndToolTip;
1655  infoPtr->hwndToolTip = hwndTT;
1656 
1657  return (LRESULT)prevToolTip;
1658 }
1659 
1660 static LRESULT
1662 {
1663  BOOL rc = infoPtr->bNtfUnicode;
1664  infoPtr->bNtfUnicode = fUnicode;
1665  return rc;
1666 }
1667 
1668 static LRESULT
1670 {
1671  return infoPtr->bNtfUnicode;
1672 }
1673 
1674 static LRESULT
1676 {
1677  return infoPtr->uScrollTime;
1678 }
1679 
1680 static LRESULT
1682 {
1683  UINT uOldScrollTime = infoPtr->uScrollTime;
1684 
1685  infoPtr->uScrollTime = min(uScrollTime, 100);
1686 
1687  return uOldScrollTime;
1688 }
1689 
1690 
1691 static LRESULT
1693 {
1694  TRACE("\n");
1695 
1696  switch (wParam)
1697  {
1698  case TVSIL_NORMAL:
1699  return (LRESULT)infoPtr->himlNormal;
1700 
1701  case TVSIL_STATE:
1702  return (LRESULT)infoPtr->himlState;
1703 
1704  default:
1705  return 0;
1706  }
1707 }
1708 
1709 #define TVHEIGHT_MIN 16
1710 #define TVHEIGHT_FONT_ADJUST 3 /* 2 for focus border + 1 for margin some apps assume */
1711 
1712 /* Compute the natural height for items. */
1713 static UINT
1715 {
1716  TEXTMETRICW tm;
1717  HDC hdc = GetDC(0);
1718  HFONT hOldFont = SelectObject(hdc, infoPtr->hFont);
1719  UINT height;
1720 
1721  /* Height is the maximum of:
1722  * 16 (a hack because our fonts are tiny), and
1723  * The text height + border & margin, and
1724  * The size of the normal image list
1725  */
1726  GetTextMetricsW(hdc, &tm);
1727  SelectObject(hdc, hOldFont);
1728  ReleaseDC(0, hdc);
1729 
1730  height = TVHEIGHT_MIN;
1731  if (height < tm.tmHeight + tm.tmExternalLeading + TVHEIGHT_FONT_ADJUST)
1732  height = tm.tmHeight + tm.tmExternalLeading + TVHEIGHT_FONT_ADJUST;
1733  if (height < infoPtr->normalImageHeight)
1734  height = infoPtr->normalImageHeight;
1735 
1736  /* Round down, unless we support odd ("non even") heights. */
1737  if (!(infoPtr->dwStyle & TVS_NONEVENHEIGHT))
1738  height &= ~1;
1739 
1740  return height;
1741 }
1742 
1743 static LRESULT
1745 {
1746  HIMAGELIST himlOld = 0;
1747  int oldWidth = infoPtr->normalImageWidth;
1748  int oldHeight = infoPtr->normalImageHeight;
1749 
1750  TRACE("%u,%p\n", type, himlNew);
1751 
1752  switch (type)
1753  {
1754  case TVSIL_NORMAL:
1755  himlOld = infoPtr->himlNormal;
1756  infoPtr->himlNormal = himlNew;
1757 
1758  if (himlNew)
1759  ImageList_GetIconSize(himlNew, &infoPtr->normalImageWidth,
1760  &infoPtr->normalImageHeight);
1761  else
1762  {
1763  infoPtr->normalImageWidth = 0;
1764  infoPtr->normalImageHeight = 0;
1765  }
1766 
1767  break;
1768 
1769  case TVSIL_STATE:
1770  himlOld = infoPtr->himlState;
1771  infoPtr->himlState = himlNew;
1772 
1773  if (himlNew)
1774  ImageList_GetIconSize(himlNew, &infoPtr->stateImageWidth,
1775  &infoPtr->stateImageHeight);
1776  else
1777  {
1778  infoPtr->stateImageWidth = 0;
1779  infoPtr->stateImageHeight = 0;
1780  }
1781 
1782  break;
1783 
1784  default:
1785  ERR("unknown imagelist type %u\n", type);
1786  }
1787 
1788  if (oldWidth != infoPtr->normalImageWidth ||
1789  oldHeight != infoPtr->normalImageHeight)
1790  {
1791  BOOL bRecalcVisible = FALSE;
1792 
1793  if (oldHeight != infoPtr->normalImageHeight &&
1794  !infoPtr->bHeightSet)
1795  {
1796  infoPtr->uItemHeight = TREEVIEW_NaturalHeight(infoPtr);
1797  bRecalcVisible = TRUE;
1798  }
1799 
1800  if (infoPtr->normalImageWidth > MINIMUM_INDENT &&
1801  infoPtr->normalImageWidth != infoPtr->uIndent)
1802  {
1803  infoPtr->uIndent = infoPtr->normalImageWidth;
1804  bRecalcVisible = TRUE;
1805  }
1806 
1807  if (bRecalcVisible)
1809 
1810  TREEVIEW_UpdateSubTree(infoPtr, infoPtr->root);
1811  TREEVIEW_UpdateScrollBars(infoPtr);
1812  }
1813 
1814  TREEVIEW_Invalidate(infoPtr, NULL);
1815 
1816  return (LRESULT)himlOld;
1817 }
1818 
1819 static LRESULT
1821 {
1822  INT prevHeight = infoPtr->uItemHeight;
1823 
1824  TRACE("new=%d, old=%d\n", newHeight, prevHeight);
1825  if (newHeight == -1)
1826  {
1827  infoPtr->uItemHeight = TREEVIEW_NaturalHeight(infoPtr);
1828  infoPtr->bHeightSet = FALSE;
1829  }
1830  else
1831  {
1832  if (newHeight == 0) newHeight = 1;
1833  infoPtr->uItemHeight = newHeight;
1834  infoPtr->bHeightSet = TRUE;
1835  }
1836 
1837  /* Round down, unless we support odd ("non even") heights. */
1838  if (!(infoPtr->dwStyle & TVS_NONEVENHEIGHT) && infoPtr->uItemHeight != 1)
1839  {
1840  infoPtr->uItemHeight &= ~1;
1841  TRACE("after rounding=%d\n", infoPtr->uItemHeight);
1842  }
1843 
1844  if (infoPtr->uItemHeight != prevHeight)
1845  {
1847  TREEVIEW_UpdateScrollBars(infoPtr);
1848  TREEVIEW_Invalidate(infoPtr, NULL);
1849  }
1850 
1851  return prevHeight;
1852 }
1853 
1854 static LRESULT
1856 {
1857  TRACE("\n");
1858  return infoPtr->uItemHeight;
1859 }
1860 
1861 
1862 static LRESULT
1864 {
1865  TRACE("%p\n", infoPtr->hFont);
1866  return (LRESULT)infoPtr->hFont;
1867 }
1868 
1869 
1870 static INT CALLBACK
1872 {
1873  (void)unused;
1874 
1875  ((TREEVIEW_ITEM *)pItem)->textWidth = 0;
1876 
1877  return 1;
1878 }
1879 
1880 static LRESULT
1882 {
1883  UINT uHeight = infoPtr->uItemHeight;
1884 
1885  TRACE("%p %i\n", hFont, bRedraw);
1886 
1887  infoPtr->hFont = hFont ? hFont : infoPtr->hDefaultFont;
1888 
1889  DeleteObject(infoPtr->hBoldFont);
1890  DeleteObject(infoPtr->hUnderlineFont);
1891  DeleteObject(infoPtr->hBoldUnderlineFont);
1892  infoPtr->hBoldFont = TREEVIEW_CreateBoldFont(infoPtr->hFont);
1893  infoPtr->hUnderlineFont = TREEVIEW_CreateUnderlineFont(infoPtr->hFont);
1895 
1896  if (!infoPtr->bHeightSet)
1897  infoPtr->uItemHeight = TREEVIEW_NaturalHeight(infoPtr);
1898 
1899  if (uHeight != infoPtr->uItemHeight)
1901 
1903 
1904  TREEVIEW_UpdateSubTree(infoPtr, infoPtr->root);
1905  TREEVIEW_UpdateScrollBars(infoPtr);
1906 
1907  if (bRedraw)
1908  TREEVIEW_Invalidate(infoPtr, NULL);
1909 
1910  return 0;
1911 }
1912 
1913 
1914 static LRESULT
1916 {
1917  TRACE("\n");
1918  return (LRESULT)infoPtr->clrLine;
1919 }
1920 
1921 static LRESULT
1923 {
1924  COLORREF prevColor = infoPtr->clrLine;
1925 
1926  TRACE("\n");
1927  infoPtr->clrLine = color;
1928  return (LRESULT)prevColor;
1929 }
1930 
1931 
1932 static LRESULT
1934 {
1935  TRACE("\n");
1936  return (LRESULT)infoPtr->clrText;
1937 }
1938 
1939 static LRESULT
1941 {
1942  COLORREF prevColor = infoPtr->clrText;
1943 
1944  TRACE("\n");
1945  infoPtr->clrText = color;
1946 
1947  if (infoPtr->clrText != prevColor)
1948  TREEVIEW_Invalidate(infoPtr, NULL);
1949 
1950  return (LRESULT)prevColor;
1951 }
1952 
1953 
1954 static LRESULT
1956 {
1957  TRACE("\n");
1958  return (LRESULT)infoPtr->clrBk;
1959 }
1960 
1961 static LRESULT
1963 {
1964  COLORREF prevColor = infoPtr->clrBk;
1965 
1966  TRACE("\n");
1967  infoPtr->clrBk = newColor;
1968 
1969  if (newColor != prevColor)
1970  TREEVIEW_Invalidate(infoPtr, NULL);
1971 
1972  return (LRESULT)prevColor;
1973 }
1974 
1975 
1976 static LRESULT
1978 {
1979  TRACE("\n");
1980  return (LRESULT)infoPtr->clrInsertMark;
1981 }
1982 
1983 static LRESULT
1985 {
1986  COLORREF prevColor = infoPtr->clrInsertMark;
1987 
1988  TRACE("0x%08x\n", color);
1989  infoPtr->clrInsertMark = color;
1990 
1991  return (LRESULT)prevColor;
1992 }
1993 
1994 
1995 static LRESULT
1997 {
1998  TRACE("%d %p\n", wParam, item);
1999 
2000  if (!TREEVIEW_ValidItem(infoPtr, item))
2001  return 0;
2002 
2003  infoPtr->insertBeforeorAfter = wParam;
2004  infoPtr->insertMarkItem = item;
2005 
2006  TREEVIEW_Invalidate(infoPtr, NULL);
2007 
2008  return 1;
2009 }
2010 
2011 
2012 /************************************************************************
2013  * Some serious braindamage here. lParam is a pointer to both the
2014  * input HTREEITEM and the output RECT.
2015  */
2016 static LRESULT
2017 TREEVIEW_GetItemRect(const TREEVIEW_INFO *infoPtr, BOOL fTextRect, LPRECT lpRect)
2018 {
2020  const HTREEITEM *pItem = (HTREEITEM *)lpRect;
2021 
2022  TRACE("\n");
2023 
2024  if (pItem == NULL)
2025  return FALSE;
2026 
2027  item = *pItem;
2028  if (!TREEVIEW_ValidItem(infoPtr, item) || !ISVISIBLE(item))
2029  return FALSE;
2030 
2031  /*
2032  * If wParam is TRUE return the text size otherwise return
2033  * the whole item size
2034  */
2035  if (fTextRect)
2036  {
2037  /* Windows does not send TVN_GETDISPINFO here. */
2038 
2039  lpRect->top = item->rect.top;
2040  lpRect->bottom = item->rect.bottom;
2041 
2042  lpRect->left = item->textOffset;
2043  if (!item->textWidth)
2044  TREEVIEW_ComputeTextWidth(infoPtr, item, 0);
2045 
2046  lpRect->right = item->textOffset + item->textWidth + 4;
2047  }
2048  else
2049  {
2050  *lpRect = item->rect;
2051  }
2052 
2053  TRACE("%s [%s]\n", fTextRect ? "text" : "item", wine_dbgstr_rect(lpRect));
2054 
2055  return TRUE;
2056 }
2057 
2058 static inline LRESULT
2060 {
2061  /* Surprise! This does not take integral height into account. */
2062  TRACE("client=%d, item=%d\n", infoPtr->clientHeight, infoPtr->uItemHeight);
2063  return infoPtr->clientHeight / infoPtr->uItemHeight;
2064 }
2065 
2066 
2067 static LRESULT
2069 {
2070  TREEVIEW_ITEM *item = tvItem->hItem;
2071 
2072  if (!TREEVIEW_ValidItem(infoPtr, item))
2073  {
2074  BOOL valid_item = FALSE;
2075  if (!item) return FALSE;
2076 
2077  __TRY
2078  {
2079  infoPtr = item->infoPtr;
2080  TRACE("got item from different tree %p, called from %p\n", item->infoPtr, infoPtr);
2081  valid_item = TREEVIEW_ValidItem(infoPtr, item);
2082  }
2084  {
2085  }
2086  __ENDTRY
2087  if (!valid_item) return FALSE;
2088  }
2089 
2090  TREEVIEW_UpdateDispInfo(infoPtr, item, tvItem->mask);
2091 
2092  if (tvItem->mask & TVIF_CHILDREN)
2093  {
2094  if (item->cChildren==I_CHILDRENCALLBACK)
2095  FIXME("I_CHILDRENCALLBACK not supported\n");
2096  tvItem->cChildren = item->cChildren;
2097  }
2098 
2099  if (tvItem->mask & TVIF_HANDLE)
2100  tvItem->hItem = item;
2101 
2102  if (tvItem->mask & TVIF_IMAGE)
2103  tvItem->iImage = item->iImage;
2104 
2105  if (tvItem->mask & TVIF_INTEGRAL)
2106  tvItem->iIntegral = item->iIntegral;
2107 
2108  /* undocumented: (mask & TVIF_PARAM) ignored and lParam is always set */
2109  tvItem->lParam = item->lParam;
2110 
2111  if (tvItem->mask & TVIF_SELECTEDIMAGE)
2112  tvItem->iSelectedImage = item->iSelectedImage;
2113 
2114  if (tvItem->mask & TVIF_EXPANDEDIMAGE)
2115  tvItem->iExpandedImage = item->iExpandedImage;
2116 
2117  /* undocumented: stateMask and (state & TVIF_STATE) ignored, so state is always set */
2118  tvItem->state = item->state;
2119 
2120  if (tvItem->mask & TVIF_TEXT)
2121  {
2122  if (item->pszText == NULL)
2123  {
2124  if (tvItem->cchTextMax > 0)
2125  tvItem->pszText[0] = '\0';
2126  }
2127  else if (isW)
2128  {
2129  if (item->pszText == LPSTR_TEXTCALLBACKW)
2130  {
2131  tvItem->pszText = LPSTR_TEXTCALLBACKW;
2132  FIXME(" GetItem called with LPSTR_TEXTCALLBACK\n");
2133  }
2134  else
2135  {
2136  lstrcpynW(tvItem->pszText, item->pszText, tvItem->cchTextMax);
2137  }
2138  }
2139  else
2140  {
2141  if (item->pszText == LPSTR_TEXTCALLBACKW)
2142  {
2143  tvItem->pszText = (LPWSTR)LPSTR_TEXTCALLBACKA;
2144  FIXME(" GetItem called with LPSTR_TEXTCALLBACK\n");
2145  }
2146  else
2147  {
2148  WideCharToMultiByte(CP_ACP, 0, item->pszText, -1,
2149  (LPSTR)tvItem->pszText, tvItem->cchTextMax, NULL, NULL);
2150  }
2151  }
2152  }
2153 
2154  if (tvItem->mask & TVIF_STATEEX)
2155  {
2156  FIXME("Extended item state not supported, returning 0.\n");
2157  tvItem->uStateEx = 0;
2158  }
2159 
2160  TRACE("item <%p>, txt %p, img %d, mask 0x%x\n",
2161  item, tvItem->pszText, tvItem->iImage, tvItem->mask);
2162 
2163  return TRUE;
2164 }
2165 
2166 /* Beware MSDN Library Visual Studio 6.0. It says -1 on failure, 0 on success,
2167  * which is wrong. */
2168 static LRESULT
2170 {
2172  TREEVIEW_ITEM originalItem;
2173 
2174  item = tvItem->hItem;
2175 
2176  TRACE("item %d, mask 0x%x\n", TREEVIEW_GetItemIndex(infoPtr, item),
2177  tvItem->mask);
2178 
2179  if (!TREEVIEW_ValidItem(infoPtr, item))
2180  return FALSE;
2181 
2182  /* store the original item values */
2183  originalItem = *item;
2184 
2185  if (!TREEVIEW_DoSetItemT(infoPtr, item, tvItem, isW))
2186  return FALSE;
2187 
2188  /* If the text or TVIS_BOLD was changed, and it is visible, recalculate. */
2189  if ((tvItem->mask & TVIF_TEXT
2190  || (tvItem->mask & TVIF_STATE && tvItem->stateMask & TVIS_BOLD))
2191  && ISVISIBLE(item))
2192  {
2193  TREEVIEW_UpdateDispInfo(infoPtr, item, TVIF_TEXT);
2194  TREEVIEW_ComputeTextWidth(infoPtr, item, 0);
2195  }
2196 
2197  if (tvItem->mask != 0 && ISVISIBLE(item))
2198  {
2199  /* The refresh updates everything, but we can't wait until then. */
2200  TREEVIEW_ComputeItemInternalMetrics(infoPtr, item);
2201 
2202  /* if any of the item's values changed and it's not a callback, redraw the item */
2203  if (item_changed(&originalItem, item, tvItem))
2204  {
2205  if (tvItem->mask & TVIF_INTEGRAL)
2206  {
2207  TREEVIEW_RecalculateVisibleOrder(infoPtr, item);
2208  TREEVIEW_UpdateScrollBars(infoPtr);
2209 
2210  TREEVIEW_Invalidate(infoPtr, NULL);
2211  }
2212  else
2213  {
2214  TREEVIEW_UpdateScrollBars(infoPtr);
2215  TREEVIEW_Invalidate(infoPtr, item);
2216  }
2217  }
2218  }
2219 
2220  return TRUE;
2221 }
2222 
2223 static LRESULT
2225 {
2226  TRACE("\n");
2227 
2228  if (!item || !TREEVIEW_ValidItem(infoPtr, item))
2229  return 0;
2230 
2231  return (item->state & mask);
2232 }
2233 
2234 static LRESULT
2236 {
2237  TREEVIEW_ITEM *retval;
2238 
2239  retval = 0;
2240 
2241  /* handle all the global data here */
2242  switch (which)
2243  {
2244  case TVGN_CHILD: /* Special case: child of 0 is root */
2245  if (item)
2246  break;
2247  /* fall through */
2248  case TVGN_ROOT:
2249  retval = infoPtr->root->firstChild;
2250  break;
2251 
2252  case TVGN_CARET:
2253  retval = infoPtr->selectedItem;
2254  break;
2255 
2256  case TVGN_FIRSTVISIBLE:
2257  retval = infoPtr->firstVisible;
2258  break;
2259 
2260  case TVGN_DROPHILITE:
2261  retval = infoPtr->dropItem;
2262  break;
2263 
2264  case TVGN_LASTVISIBLE:
2265  retval = TREEVIEW_GetLastListItem(infoPtr, infoPtr->root);
2266  break;
2267  }
2268 
2269  if (retval)
2270  {
2271  TRACE("flags:0x%x, returns %p\n", which, retval);
2272  return (LRESULT)retval;
2273  }
2274 
2275  if (item == TVI_ROOT) item = infoPtr->root;
2276 
2277  if (!TREEVIEW_ValidItem(infoPtr, item))
2278  return FALSE;
2279 
2280  switch (which)
2281  {
2282  case TVGN_NEXT:
2283  retval = item->nextSibling;
2284  break;
2285  case TVGN_PREVIOUS:
2286  retval = item->prevSibling;
2287  break;
2288  case TVGN_PARENT:
2289  retval = (item->parent != infoPtr->root) ? item->parent : NULL;
2290  break;
2291  case TVGN_CHILD:
2292  retval = item->firstChild;
2293  break;
2294  case TVGN_NEXTVISIBLE:
2295  retval = TREEVIEW_GetNextListItem(infoPtr, item);
2296  break;
2297  case TVGN_PREVIOUSVISIBLE:
2298  retval = TREEVIEW_GetPrevListItem(infoPtr, item);
2299  break;
2300  default:
2301  TRACE("Unknown msg 0x%x, item %p\n", which, item);
2302  break;
2303  }
2304 
2305  TRACE("flags: 0x%x, item %p;returns %p\n", which, item, retval);
2306  return (LRESULT)retval;
2307 }
2308 
2309 
2310 static LRESULT
2312 {
2313  TRACE(" %d\n", infoPtr->uNumItems);
2314  return (LRESULT)infoPtr->uNumItems;
2315 }
2316 
2317 static VOID
2319 {
2320  if (infoPtr->dwStyle & TVS_CHECKBOXES)
2321  {
2322  static const unsigned int state_table[] = { 0, 2, 1 };
2323 
2324  unsigned int state;
2325 
2326  state = STATEIMAGEINDEX(item->state);
2327  TRACE("state: 0x%x\n", state);
2328  item->state &= ~TVIS_STATEIMAGEMASK;
2329 
2330  if (state < 3)
2331  state = state_table[state];
2332 
2333  item->state |= INDEXTOSTATEIMAGEMASK(state);
2334 
2335  TRACE("state: 0x%x\n", state);
2336  TREEVIEW_Invalidate(infoPtr, item);
2337  }
2338 }
2339 
2340 
2341 /* Painting *************************************************************/
2342 
2343 /* Draw the lines and expand button for an item. Also draws one section
2344  * of the line from item's parent to item's parent's next sibling. */
2345 static void
2347 {
2348  LONG centerx, centery;
2349  BOOL lar = ((infoPtr->dwStyle
2351  > TVS_LINESATROOT);
2352  HBRUSH hbr, hbrOld;
2353  COLORREF clrBk = GETBKCOLOR(infoPtr->clrBk);
2354 
2355  if (!lar && item->iLevel == 0)
2356  return;
2357 
2358  hbr = CreateSolidBrush(clrBk);
2359  hbrOld = SelectObject(hdc, hbr);
2360 
2361  centerx = (item->linesOffset + item->stateOffset) / 2;
2362  centery = (item->rect.top + item->rect.bottom) / 2;
2363 
2364  if (infoPtr->dwStyle & TVS_HASLINES)
2365  {
2366  HPEN hOldPen, hNewPen;
2367  HTREEITEM parent;
2368  LOGBRUSH lb;
2369 
2370  /* Get a dotted grey pen */
2371  lb.lbStyle = BS_SOLID;
2372  lb.lbColor = GETLINECOLOR(infoPtr->clrLine);
2373  hNewPen = ExtCreatePen(PS_COSMETIC|PS_ALTERNATE, 1, &lb, 0, NULL);
2374  hOldPen = SelectObject(hdc, hNewPen);
2375 
2376  /* Make sure the center is on a dot (using +2 instead
2377  * of +1 gives us pixel-by-pixel compat with native) */
2378  centery = (centery + 2) & ~1;
2379 
2380  MoveToEx(hdc, item->stateOffset, centery, NULL);
2381  LineTo(hdc, centerx - 1, centery);
2382 
2383  if (item->prevSibling || item->parent != infoPtr->root)
2384  {
2385  MoveToEx(hdc, centerx, item->rect.top, NULL);
2386  LineTo(hdc, centerx, centery);
2387  }
2388 
2389  if (item->nextSibling)
2390  {
2391  MoveToEx(hdc, centerx, centery, NULL);
2392  LineTo(hdc, centerx, item->rect.bottom + 1);
2393  }
2394 
2395  /* Draw the line from our parent to its next sibling. */
2396  parent = item->parent;
2397  while (parent != infoPtr->root)
2398  {
2399  int pcenterx = (parent->linesOffset + parent->stateOffset) / 2;
2400 
2401  if (parent->nextSibling
2402  /* skip top-levels unless TVS_LINESATROOT */
2403  && parent->stateOffset > parent->linesOffset)
2404  {
2405  MoveToEx(hdc, pcenterx, item->rect.top, NULL);
2406  LineTo(hdc, pcenterx, item->rect.bottom + 1);
2407  }
2408 
2409  parent = parent->parent;
2410  }
2411 
2412  SelectObject(hdc, hOldPen);
2413  DeleteObject(hNewPen);
2414  }
2415 
2416  /*
2417  * Display the (+/-) signs
2418  */
2419 
2420  if (infoPtr->dwStyle & TVS_HASBUTTONS)
2421  {
2422  if (item->cChildren)
2423  {
2424  HTHEME theme = GetWindowTheme(infoPtr->hwnd);
2425  if (theme)
2426  {
2427  RECT glyphRect = item->rect;
2428  glyphRect.left = item->linesOffset;
2429  glyphRect.right = item->stateOffset;
2430  DrawThemeBackground (theme, hdc, TVP_GLYPH,
2432  &glyphRect, NULL);
2433  }
2434  else
2435  {
2436  LONG height = item->rect.bottom - item->rect.top;
2437  LONG width = item->stateOffset - item->linesOffset;
2438  LONG rectsize = min(height, width) / 4;
2439  /* plussize = ceil(rectsize * 3/4) */
2440  LONG plussize = (rectsize + 1) * 3 / 4;
2441 
2442  HPEN new_pen = CreatePen(PS_SOLID, 0, GETLINECOLOR(infoPtr->clrLine));
2443  HPEN old_pen = SelectObject(hdc, new_pen);
2444 
2445  Rectangle(hdc, centerx - rectsize - 1, centery - rectsize - 1,
2446  centerx + rectsize + 2, centery + rectsize + 2);
2447 
2448  SelectObject(hdc, old_pen);
2449  DeleteObject(new_pen);
2450 
2451  /* draw +/- signs with current text color */
2452  new_pen = CreatePen(PS_SOLID, 0, GETTXTCOLOR(infoPtr->clrText));
2453  old_pen = SelectObject(hdc, new_pen);
2454 
2455  if (height < 18 || width < 18)
2456  {
2457  MoveToEx(hdc, centerx - plussize + 1, centery, NULL);
2458  LineTo(hdc, centerx + plussize, centery);
2459 
2460  if (!(item->state & TVIS_EXPANDED) ||
2461  (item->state & TVIS_EXPANDPARTIAL))
2462  {
2463  MoveToEx(hdc, centerx, centery - plussize + 1, NULL);
2464  LineTo(hdc, centerx, centery + plussize);
2465  }
2466  }
2467  else
2468  {
2469  Rectangle(hdc, centerx - plussize + 1, centery - 1,
2470  centerx + plussize, centery + 2);
2471 
2472  if (!(item->state & TVIS_EXPANDED) ||
2473  (item->state & TVIS_EXPANDPARTIAL))
2474  {
2475  Rectangle(hdc, centerx - 1, centery - plussize + 1,
2476  centerx + 2, centery + plussize);
2477  SetPixel(hdc, centerx - 1, centery, clrBk);
2478  SetPixel(hdc, centerx + 1, centery, clrBk);
2479  }
2480  }
2481 
2482  SelectObject(hdc, old_pen);
2483  DeleteObject(new_pen);
2484  }
2485  }
2486  }
2487  SelectObject(hdc, hbrOld);
2488  DeleteObject(hbr);
2489 }
2490 
2491 static void
2493 {
2494  INT cditem;
2495  HFONT hOldFont;
2496  COLORREF oldTextColor, oldTextBkColor;
2497  int centery;
2498  BOOL inFocus = (GetFocus() == infoPtr->hwnd);
2499  NMTVCUSTOMDRAW nmcdhdr;
2500 
2502 
2503  /* - If item is drop target or it is selected and window is in focus -
2504  * use blue background (COLOR_HIGHLIGHT).
2505  * - If item is selected, window is not in focus, but it has style
2506  * TVS_SHOWSELALWAYS - use grey background (COLOR_BTNFACE)
2507  * - Otherwise - use background color
2508  */
2509  if ((item->state & TVIS_DROPHILITED) || ((item == infoPtr->focusedItem) && !(item->state & TVIS_SELECTED)) ||
2510  ((item->state & TVIS_SELECTED) && (!infoPtr->focusedItem || item == infoPtr->focusedItem) &&
2511  (inFocus || (infoPtr->dwStyle & TVS_SHOWSELALWAYS))))
2512  {
2513  if ((item->state & TVIS_DROPHILITED) || inFocus)
2514  {
2517  }
2518  else
2519  {
2521  nmcdhdr.clrText = GETTXTCOLOR(infoPtr->clrText);
2522  }
2523  }
2524  else
2525  {
2526  nmcdhdr.clrTextBk = GETBKCOLOR(infoPtr->clrBk);
2527  if ((infoPtr->dwStyle & TVS_TRACKSELECT) && (item == infoPtr->hotItem))
2529  else
2530  nmcdhdr.clrText = GETTXTCOLOR(infoPtr->clrText);
2531  }
2532 
2533  hOldFont = SelectObject(hdc, TREEVIEW_FontForItem(infoPtr, item));
2534  oldTextColor = SetTextColor(hdc, nmcdhdr.clrText);
2535  oldTextBkColor = SetBkColor(hdc, nmcdhdr.clrTextBk);
2536 
2537  /* The custom draw handler can query the text rectangle,
2538  * so get ready. */
2539  /* should already be known, set to 0 when changed */
2540  if (!item->textWidth)
2541  TREEVIEW_ComputeTextWidth(infoPtr, item, hdc);
2542 
2543  cditem = 0;
2544 
2545  if (infoPtr->cdmode & CDRF_NOTIFYITEMDRAW)
2546  {
2548  (infoPtr, hdc, item, CDDS_ITEMPREPAINT, &nmcdhdr);
2549  TRACE("prepaint:cditem-app returns 0x%x\n", cditem);
2550 
2551  if (cditem & CDRF_SKIPDEFAULT)
2552  {
2553  SelectObject(hdc, hOldFont);
2554  return;
2555  }
2556  }
2557 
2558  if (cditem & CDRF_NEWFONT)
2559  TREEVIEW_ComputeTextWidth(infoPtr, item, hdc);
2560 
2561  if (TREEVIEW_IsFullRowSelect(infoPtr))
2562  {
2563  HBRUSH brush = CreateSolidBrush(nmcdhdr.clrTextBk);
2564  FillRect(hdc, &item->rect, brush);
2565  DeleteObject(brush);
2566  }
2567 
2568  TREEVIEW_DrawItemLines(infoPtr, hdc, item);
2569 
2570  /* reset colors. Custom draw handler can change them */
2571  SetTextColor(hdc, nmcdhdr.clrText);
2572  SetBkColor(hdc, nmcdhdr.clrTextBk);
2573 
2574  centery = (item->rect.top + item->rect.bottom) / 2;
2575 
2576  /*
2577  * Display the images associated with this item
2578  */
2579  {
2580  INT imageIndex;
2581 
2582  /* State images are displayed to the left of the Normal image
2583  * image number is in state; zero should be `display no image'.
2584  */
2585  imageIndex = STATEIMAGEINDEX(item->state);
2586 
2587  if (infoPtr->himlState && imageIndex)
2588  {
2589  ImageList_Draw(infoPtr->himlState, imageIndex, hdc,
2590  item->stateOffset,
2591  centery - infoPtr->stateImageHeight / 2,
2592  ILD_NORMAL);
2593  }
2594 
2595  /* Now, draw the normal image; can be either selected,
2596  * non-selected or expanded image.
2597  */
2598 
2599  if ((item->state & TVIS_SELECTED) && (item->iSelectedImage >= 0))
2600  {
2601  /* The item is currently selected */
2602  imageIndex = item->iSelectedImage;
2603  }
2604  else if ((item->state & TVIS_EXPANDED) && (item->iExpandedImage != (WORD)I_IMAGENONE))
2605  {
2606  /* The item is currently not selected but expanded */
2607  imageIndex = item->iExpandedImage;
2608  }
2609  else
2610  {
2611  /* The item is not selected and not expanded */
2612  imageIndex = item->iImage;
2613  }
2614 
2615  if (infoPtr->himlNormal)
2616  {
2618 
2619  style |= item->state & TVIS_OVERLAYMASK;
2620 
2621  ImageList_DrawEx(infoPtr->himlNormal, imageIndex, hdc,
2622  item->imageOffset, centery - infoPtr->normalImageHeight / 2,
2623  0, 0, infoPtr->clrBk, item->state & TVIS_CUT ? GETBKCOLOR(infoPtr->clrBk) : CLR_DEFAULT,
2624  style);
2625  }
2626  }
2627 
2628 
2629  /*
2630  * Display the text associated with this item
2631  */
2632 
2633  /* Don't paint item's text if it's being edited */
2634  if (!infoPtr->hwndEdit || (infoPtr->selectedItem != item))
2635  {
2636  if (item->pszText)
2637  {
2638  RECT rcText;
2639  UINT align;
2640  SIZE sz;
2641 
2642  rcText.top = item->rect.top;
2643  rcText.bottom = item->rect.bottom;
2644  rcText.left = item->textOffset;
2645  rcText.right = rcText.left + item->textWidth + 4;
2646 
2647  TRACE("drawing text %s at (%s)\n",
2648  debugstr_w(item->pszText), wine_dbgstr_rect(&rcText));
2649 
2650  /* Draw it */
2651  GetTextExtentPoint32W(hdc, item->pszText, strlenW(item->pszText), &sz);
2652 
2653  align = SetTextAlign(hdc, TA_LEFT | TA_TOP);
2654  ExtTextOutW(hdc, rcText.left + 2, (rcText.top + rcText.bottom - sz.cy) / 2,
2656  &rcText,
2657  item->pszText,
2658  lstrlenW(item->pszText),
2659  NULL);
2660  SetTextAlign(hdc, align);
2661 
2662  /* Draw focus box around the selected item */
2663  if ((item == infoPtr->selectedItem) && inFocus)
2664  {
2665  DrawFocusRect(hdc,&rcText);
2666  }
2667  }
2668  }
2669 
2670  /* Draw insertion mark if necessary */
2671 
2672  if (infoPtr->insertMarkItem)
2673  TRACE("item:%d,mark:%p\n",
2674  TREEVIEW_GetItemIndex(infoPtr, item),
2675  infoPtr->insertMarkItem);
2676 
2677  if (item == infoPtr->insertMarkItem)
2678  {
2679  HPEN hNewPen, hOldPen;
2680  int offset;
2681  int left, right;
2682 
2683  hNewPen = CreatePen(PS_SOLID, 2, GETINSCOLOR(infoPtr->clrInsertMark));
2684  hOldPen = SelectObject(hdc, hNewPen);
2685 
2686  if (infoPtr->insertBeforeorAfter)
2687  offset = item->rect.bottom - 1;
2688  else
2689  offset = item->rect.top + 1;
2690 
2691  left = item->textOffset - 2;
2692  right = item->textOffset + item->textWidth + 2;
2693 
2694  MoveToEx(hdc, left, offset - 3, NULL);
2695  LineTo(hdc, left, offset + 4);
2696 
2697  MoveToEx(hdc, left, offset, NULL);
2698  LineTo(hdc, right + 1, offset);
2699 
2700  MoveToEx(hdc, right, offset + 3, NULL);
2701  LineTo(hdc, right, offset - 4);
2702 
2703  SelectObject(hdc, hOldPen);
2704  DeleteObject(hNewPen);
2705  }
2706 
2707  /* Restore the hdc state */
2708  SetTextColor(hdc, oldTextColor);
2709  SetBkColor(hdc, oldTextBkColor);
2710  SelectObject(hdc, hOldFont);
2711 
2712  if (cditem & CDRF_NOTIFYPOSTPAINT)
2713  {
2715  (infoPtr, hdc, item, CDDS_ITEMPOSTPAINT, &nmcdhdr);
2716  TRACE("postpaint:cditem-app returns 0x%x\n", cditem);
2717  }
2718 }
2719 
2720 /* Computes treeHeight and treeWidth and updates the scroll bars.
2721  */
2722 static void
2724 {
2726  HWND hwnd = infoPtr->hwnd;
2727  BOOL vert = FALSE;
2728  BOOL horz = FALSE;
2729  SCROLLINFO si;
2730  LONG scrollX = infoPtr->scrollX;
2731 
2732  infoPtr->treeWidth = 0;
2733  infoPtr->treeHeight = 0;
2734 
2735  /* We iterate through all visible items in order to get the tree height
2736  * and width */
2737  item = infoPtr->root->firstChild;
2738 
2739  while (item != NULL)
2740  {
2741  if (ISVISIBLE(item))
2742  {
2743  /* actually we draw text at textOffset + 2 */
2744  if (2+item->textOffset+item->textWidth > infoPtr->treeWidth)
2745  infoPtr->treeWidth = item->textOffset+item->textWidth+2;
2746 
2747  /* This is scroll-adjusted, but we fix this below. */
2748  infoPtr->treeHeight = item->rect.bottom;
2749  }
2750 
2751  item = TREEVIEW_GetNextListItem(infoPtr, item);
2752  }
2753 
2754  /* Fix the scroll adjusted treeHeight and treeWidth. */
2755  if (infoPtr->root->firstChild)
2756  infoPtr->treeHeight -= infoPtr->root->firstChild->rect.top;
2757 
2758  infoPtr->treeWidth += infoPtr->scrollX;
2759 
2760  if (infoPtr->dwStyle & TVS_NOSCROLL) return;
2761 
2762  /* Adding one scroll bar may take up enough space that it forces us
2763  * to add the other as well. */
2764  if (infoPtr->treeHeight > infoPtr->clientHeight)
2765  {
2766  vert = TRUE;
2767 
2768  if (infoPtr->treeWidth
2770  horz = TRUE;
2771  }
2772  else if (infoPtr->treeWidth > infoPtr->clientWidth || infoPtr->scrollX > 0)
2773  horz = TRUE;
2774 
2775  if (!vert && horz && infoPtr->treeHeight
2777  vert = TRUE;
2778 
2779  if (horz && (infoPtr->dwStyle & TVS_NOHSCROLL)) horz = FALSE;
2780 
2781  si.cbSize = sizeof(SCROLLINFO);
2783  si.nMin = 0;
2784 
2785  if (vert)
2786  {
2787  si.nPage = TREEVIEW_GetVisibleCount(infoPtr);
2788  if ( si.nPage && NULL != infoPtr->firstVisible)
2789  {
2790  si.nPos = infoPtr->firstVisible->visibleOrder;
2791  si.nMax = infoPtr->maxVisibleOrder - 1;
2792 
2793  SetScrollInfo(hwnd, SB_VERT, &si, TRUE);
2794 
2795  if (!(infoPtr->uInternalStatus & TV_VSCROLL))
2796  ShowScrollBar(hwnd, SB_VERT, TRUE);
2797  infoPtr->uInternalStatus |= TV_VSCROLL;
2798  }
2799  else
2800  {
2801  if (infoPtr->uInternalStatus & TV_VSCROLL)
2802  ShowScrollBar(hwnd, SB_VERT, FALSE);
2803  infoPtr->uInternalStatus &= ~TV_VSCROLL;
2804  }
2805  }
2806  else
2807  {
2808  if (infoPtr->uInternalStatus & TV_VSCROLL)
2809  ShowScrollBar(hwnd, SB_VERT, FALSE);
2810  infoPtr->uInternalStatus &= ~TV_VSCROLL;
2811  }
2812 
2813  if (horz)
2814  {
2815  si.nPage = infoPtr->clientWidth;
2816  si.nPos = infoPtr->scrollX;
2817  si.nMax = infoPtr->treeWidth - 1;
2818 
2819  if (si.nPos > si.nMax - max( si.nPage-1, 0 ))
2820  {
2821  si.nPos = si.nMax - max( si.nPage-1, 0 );
2822  scrollX = si.nPos;
2823  }
2824 
2825  if (!(infoPtr->uInternalStatus & TV_HSCROLL))
2826  ShowScrollBar(hwnd, SB_HORZ, TRUE);
2827  infoPtr->uInternalStatus |= TV_HSCROLL;
2828 
2829  SetScrollInfo(hwnd, SB_HORZ, &si, TRUE);
2830  TREEVIEW_HScroll(infoPtr,
2831  MAKEWPARAM(SB_THUMBPOSITION, scrollX));
2832  }
2833  else
2834  {
2835  if (infoPtr->uInternalStatus & TV_HSCROLL)
2836  ShowScrollBar(hwnd, SB_HORZ, FALSE);
2837  infoPtr->uInternalStatus &= ~TV_HSCROLL;
2838 
2839  scrollX = 0;
2840  if (infoPtr->scrollX != 0)
2841  {
2842  TREEVIEW_HScroll(infoPtr,
2843  MAKEWPARAM(SB_THUMBPOSITION, scrollX));
2844  }
2845  }
2846 
2847  if (!horz)
2848  infoPtr->uInternalStatus &= ~TV_HSCROLL;
2849 }
2850 
2851 static void
2852 TREEVIEW_FillBkgnd(const TREEVIEW_INFO *infoPtr, HDC hdc, const RECT *rc)
2853 {
2854  HBRUSH hBrush;
2855  COLORREF clrBk = GETBKCOLOR(infoPtr->clrBk);
2856 
2857  hBrush = CreateSolidBrush(clrBk);
2858  FillRect(hdc, rc, hBrush);
2859  DeleteObject(hBrush);
2860 }
2861 
2862 /* CtrlSpy doesn't mention this, but CorelDRAW's object manager needs it. */
2863 static LRESULT
2865 {
2866  RECT rect;
2867 
2868  TRACE("%p\n", infoPtr);
2869 
2870  GetClientRect(infoPtr->hwnd, &rect);
2871  TREEVIEW_FillBkgnd(infoPtr, hdc, &rect);
2872 
2873  return 1;
2874 }
2875 
2876 static void
2878 {
2879  HWND hwnd = infoPtr->hwnd;
2880  RECT rect = *rc;
2882 
2883  if (infoPtr->clientHeight == 0 || infoPtr->clientWidth == 0)
2884  {
2885  TRACE("empty window\n");
2886  return;
2887  }
2888 
2890  hdc, rect);
2891 
2892  if (infoPtr->cdmode == CDRF_SKIPDEFAULT)
2893  {
2894  ReleaseDC(hwnd, hdc);
2895  return;
2896  }
2897 
2898  for (item = infoPtr->root->firstChild;
2899  item != NULL;
2900  item = TREEVIEW_GetNextListItem(infoPtr, item))
2901  {
2902  if (ISVISIBLE(item))
2903  {
2904  /* Avoid unneeded calculations */
2905  if (item->rect.top > rect.bottom)
2906  break;
2907  if (item->rect.bottom < rect.top)
2908  continue;
2909 
2910  TREEVIEW_DrawItem(infoPtr, hdc, item);
2911  }
2912  }
2913 
2914  //
2915  // FIXME: This is correct, but is causes and infinite loop of WM_PAINT
2916  // messages, resulting in continuous painting of the scroll bar in reactos.
2917  // Comment out until the real bug is found. CORE-4912
2918  //
2919 #ifndef __REACTOS__
2920  TREEVIEW_UpdateScrollBars(infoPtr);
2921 #endif
2922 
2923  if (infoPtr->cdmode & CDRF_NOTIFYPOSTPAINT)
2924  infoPtr->cdmode =
2925  TREEVIEW_SendCustomDrawNotify(infoPtr, CDDS_POSTPAINT, hdc, rect);
2926 }
2927 
2928 static inline void
2930 {
2931  if (item) InvalidateRect(infoPtr->hwnd, &item->rect, TRUE);
2932 }
2933 
2934 static void
2936 {
2937  if (item)
2938  InvalidateRect(infoPtr->hwnd, &item->rect, TRUE);
2939  else
2940  InvalidateRect(infoPtr->hwnd, NULL, TRUE);
2941 }
2942 
2943 static void
2945 {
2946  RECT rc;
2947  HBITMAP hbm, hbmOld;
2948  HDC hdc, hdcScreen;
2949  int nIndex;
2950 
2951  infoPtr->himlState = ImageList_Create(16, 16, ILC_COLOR | ILC_MASK, 3, 0);
2952 
2953  hdcScreen = GetDC(0);
2954 
2955  hdc = CreateCompatibleDC(hdcScreen);
2956  hbm = CreateCompatibleBitmap(hdcScreen, 48, 16);
2957  hbmOld = SelectObject(hdc, hbm);
2958 
2959  SetRect(&rc, 0, 0, 48, 16);
2960  FillRect(hdc, &rc, (HBRUSH)(COLOR_WINDOW+1));
2961 
2962  SetRect(&rc, 18, 2, 30, 14);
2963  DrawFrameControl(hdc, &rc, DFC_BUTTON,
2965 
2966  SetRect(&rc, 34, 2, 46, 14);
2967  DrawFrameControl(hdc, &rc, DFC_BUTTON,
2969 
2970  SelectObject(hdc, hbmOld);
2971  nIndex = ImageList_AddMasked(infoPtr->himlState, hbm,
2973  TRACE("checkbox index %d\n", nIndex);
2974 
2975  DeleteObject(hbm);
2976  DeleteDC(hdc);
2977  ReleaseDC(0, hdcScreen);
2978 
2979  infoPtr->stateImageWidth = 16;
2980  infoPtr->stateImageHeight = 16;
2981 }
2982 
2983 static void
2985 {
2986  TREEVIEW_ITEM *child = item->firstChild;
2987 
2988  item->state &= ~TVIS_STATEIMAGEMASK;
2989  item->state |= INDEXTOSTATEIMAGEMASK(1);
2990 
2991  while (child)
2992  {
2993  TREEVIEW_ITEM *next = child->nextSibling;
2994  TREEVIEW_ResetImageStateIndex(infoPtr, child);
2995  child = next;
2996  }
2997 }
2998 
2999 static LRESULT
3001 {
3002  HDC hdc;
3003  PAINTSTRUCT ps;
3004  RECT rc;
3005 
3006  TRACE("(%p %p)\n", infoPtr, hdc_ref);
3007 
3008  if ((infoPtr->dwStyle & TVS_CHECKBOXES) && !infoPtr->himlState)
3009  {
3010  TREEVIEW_InitCheckboxes(infoPtr);
3011  TREEVIEW_ResetImageStateIndex(infoPtr, infoPtr->root);
3012 
3013  TREEVIEW_EndEditLabelNow(infoPtr, TRUE);
3014  TREEVIEW_UpdateSubTree(infoPtr, infoPtr->root);
3015  TREEVIEW_UpdateScrollBars(infoPtr);
3016  TREEVIEW_Invalidate(infoPtr, NULL);
3017  }
3018 
3019  if (hdc_ref)
3020  {
3021  hdc = hdc_ref;
3022  GetClientRect(infoPtr->hwnd, &rc);
3023  TREEVIEW_FillBkgnd(infoPtr, hdc, &rc);
3024  }
3025  else
3026  {
3027  hdc = BeginPaint(infoPtr->hwnd, &ps);
3028  rc = ps.rcPaint;
3029  if(ps.fErase)
3030  TREEVIEW_FillBkgnd(infoPtr, hdc, &rc);
3031  }
3032 
3033  if(infoPtr->bRedraw) /* WM_SETREDRAW sets bRedraw */
3034  TREEVIEW_Refresh(infoPtr, hdc, &rc);
3035 
3036  if (!hdc_ref)
3037  EndPaint(infoPtr->hwnd, &ps);
3038 
3039  return 0;
3040 }
3041 
3042 static LRESULT
3044 {
3045  FIXME("Partial Stub: (hdc=%p options=0x%08x)\n", hdc, options);
3046 
3047  if ((options & PRF_CHECKVISIBLE) && !IsWindowVisible(infoPtr->hwnd))
3048  return 0;
3049 
3050  if (options & PRF_ERASEBKGND)
3051  TREEVIEW_EraseBackground(infoPtr, hdc);
3052 
3053  if (options & PRF_CLIENT)
3054  {
3055  RECT rc;
3056  GetClientRect(infoPtr->hwnd, &rc);
3057  TREEVIEW_Refresh(infoPtr, hdc, &rc);
3058  }
3059 
3060  return 0;
3061 }
3062 
3063 /* Sorting **************************************************************/
3064 
3065 /***************************************************************************
3066  * Forward the DPA local callback to the treeview owner callback
3067  */
3068 static INT WINAPI
3070  const TVSORTCB *pCallBackSort)
3071 {
3072  /* Forward the call to the client-defined callback */
3073  return pCallBackSort->lpfnCompare(first->lParam,
3074  second->lParam,
3075  pCallBackSort->lParam);
3076 }
3077 
3078 /***************************************************************************
3079  * Treeview native sort routine: sort on item text.
3080  */
3081 static INT WINAPI
3083  const TREEVIEW_INFO *infoPtr)
3084 {
3085  TREEVIEW_UpdateDispInfo(infoPtr, first, TVIF_TEXT);
3086  TREEVIEW_UpdateDispInfo(infoPtr, second, TVIF_TEXT);
3087 
3088  if(first->pszText && second->pszText)
3089  return lstrcmpiW(first->pszText, second->pszText);
3090  else if(first->pszText)
3091  return -1;
3092  else if(second->pszText)
3093  return 1;
3094  else
3095  return 0;
3096 }
3097 
3098 /* Returns the number of physical children belonging to item. */
3099 static INT
3101 {
3102  INT cChildren = 0;
3103  HTREEITEM hti;
3104 
3105  for (hti = item->firstChild; hti != NULL; hti = hti->nextSibling)
3106  cChildren++;
3107 
3108  return cChildren;
3109 }
3110 
3111 /* Returns a DPA containing a pointer to each physical child of item in
3112  * sibling order. If item has no children, an empty DPA is returned. */
3113 static HDPA
3115 {
3116  HTREEITEM child;
3117 
3118  HDPA list = DPA_Create(8);
3119  if (list == 0) return NULL;
3120 
3121  for (child = item->firstChild; child != NULL; child = child->nextSibling)
3122  {
3123  if (DPA_InsertPtr(list, INT_MAX, child) == -1)
3124  {
3125  DPA_Destroy(list);
3126  return NULL;
3127  }
3128  }
3129 
3130  return list;
3131 }
3132 
3133 /***************************************************************************
3134  * Setup the treeview structure with regards of the sort method
3135  * and sort the children of the TV item specified in lParam
3136  * fRecurse: currently unused. Should be zero.
3137  * parent: if pSort!=NULL, should equal pSort->hParent.
3138  * otherwise, item which child items are to be sorted.
3139  * pSort: sort method info. if NULL, sort on item text.
3140  * if non-NULL, sort on item's lParam content, and let the
3141  * application decide what that means. See also TVM_SORTCHILDRENCB.
3142  */
3143 
3144 static LRESULT
3146  LPTVSORTCB pSort)
3147 {
3148  INT cChildren;
3149  PFNDPACOMPARE pfnCompare;
3150  LPARAM lpCompare;
3151 
3152  /* undocumented feature: TVI_ROOT or NULL means `sort the whole tree' */
3153  if (parent == TVI_ROOT || parent == NULL)
3154  parent = infoPtr->root;
3155 
3156  /* Check for a valid handle to the parent item */
3157  if (!TREEVIEW_ValidItem(infoPtr, parent))
3158  {
3159  ERR("invalid item hParent=%p\n", parent);
3160  return FALSE;
3161  }
3162 
3163  if (pSort)
3164  {
3166  lpCompare = (LPARAM)pSort;
3167  }
3168  else
3169  {
3170  pfnCompare = (PFNDPACOMPARE)TREEVIEW_SortOnName;
3171  lpCompare = (LPARAM)infoPtr;
3172  }
3173 
3174  cChildren = TREEVIEW_CountChildren(parent);
3175 
3176  /* Make sure there is something to sort */
3177  if (cChildren > 1)
3178  {
3179  /* TREEVIEW_ITEM rechaining */
3180  INT count = 0;
3181  HTREEITEM item = 0;
3182  HTREEITEM nextItem = 0;
3183  HTREEITEM prevItem = 0;
3184 
3185  HDPA sortList = TREEVIEW_BuildChildDPA(parent);
3186 
3187  if (sortList == NULL)
3188  return FALSE;
3189 
3190  /* let DPA sort the list */
3191  DPA_Sort(sortList, pfnCompare, lpCompare);
3192 
3193  /* The order of DPA entries has been changed, so fixup the
3194  * nextSibling and prevSibling pointers. */
3195 
3196  item = DPA_GetPtr(sortList, count++);
3197  while ((nextItem = DPA_GetPtr(sortList, count++)) != NULL)
3198  {
3199  /* link the two current item together */
3200  item->nextSibling = nextItem;
3201  nextItem->prevSibling = item;
3202 
3203  if (prevItem == NULL)
3204  {
3205  /* this is the first item, update the parent */
3206  parent->firstChild = item;
3207  item->prevSibling = NULL;
3208  }
3209  else
3210  {
3211  /* fix the back chaining */
3212  item->prevSibling = prevItem;
3213  }
3214 
3215  /* get ready for the next one */
3216  prevItem = item;
3217  item = nextItem;
3218  }
3219 
3220  /* the last item is pointed to by item and never has a sibling */
3221  item->nextSibling = NULL;
3222  parent->lastChild = item;
3223 
3224  DPA_Destroy(sortList);
3225 
3226  TREEVIEW_VerifyTree(infoPtr);
3227 
3228  if (parent->state & TVIS_EXPANDED)
3229  {
3230  int visOrder = infoPtr->firstVisible->visibleOrder;
3231 
3232  if (parent == infoPtr->root)
3234  else
3235  TREEVIEW_RecalculateVisibleOrder(infoPtr, parent);
3236 
3237  if (TREEVIEW_IsChildOf(parent, infoPtr->firstVisible))
3238  {
3240 
3241  for (item = infoPtr->root->firstChild; item != NULL;
3242  item = TREEVIEW_GetNextListItem(infoPtr, item))
3243  {
3244  if (item->visibleOrder == visOrder)
3245  break;
3246  }
3247 
3248  if (!item) item = parent->firstChild;
3249  TREEVIEW_SetFirstVisible(infoPtr, item, FALSE);
3250  }
3251 
3252  TREEVIEW_Invalidate(infoPtr, NULL);
3253  }
3254 
3255  return TRUE;
3256  }
3257  return FALSE;
3258 }
3259 
3260 
3261 /***************************************************************************
3262  * Setup the treeview structure with regards of the sort method
3263  * and sort the children of the TV item specified in lParam
3264  */
3265 static LRESULT
3267 {
3268  return TREEVIEW_Sort(infoPtr, pSort->hParent, pSort);
3269 }
3270 
3271 
3272 /***************************************************************************
3273  * Sort the children of the TV item specified in lParam.
3274  */
3275 static LRESULT
3277 {
3278  return TREEVIEW_Sort(infoPtr, (HTREEITEM)lParam, NULL);
3279 }
3280 
3281 
3282 /* Expansion/Collapse ***************************************************/
3283 
3284 static BOOL
3286  UINT action)
3287 {
3288  return !TREEVIEW_SendTreeviewNotify(infoPtr, TVN_ITEMEXPANDINGW, action,
3291  0, item);
3292 }
3293 
3294 static VOID
3296  UINT action)
3297 {
3301  0, item);
3302 }
3303 
3304 
3305 /* This corresponds to TVM_EXPAND with TVE_COLLAPSE.
3306  * bRemoveChildren corresponds to TVE_COLLAPSERESET. */
3307 static BOOL
3309  BOOL bRemoveChildren, BOOL bUser)
3310 {
3311  UINT action = TVE_COLLAPSE | (bRemoveChildren ? TVE_COLLAPSERESET : 0);
3312  BOOL bSetSelection, bSetFirstVisible;
3313  RECT scrollRect;
3314  LONG scrollDist = 0;
3315  TREEVIEW_ITEM *nextItem = NULL, *tmpItem;
3316  BOOL wasExpanded;
3317 
3318  TRACE("TVE_COLLAPSE %p %s\n", item, TREEVIEW_ItemName(item));
3319 
3320  if (!TREEVIEW_HasChildren(infoPtr, item))
3321  return FALSE;
3322 
3323  if (bUser)
3324  TREEVIEW_SendExpanding(infoPtr, item, action);
3325 
3326  if (item->firstChild == NULL)
3327  return FALSE;
3328 
3329  wasExpanded = (item->state & TVIS_EXPANDED) != 0;
3330  item->state &= ~TVIS_EXPANDED;
3331 
3332  if (wasExpanded && bUser)
3333  TREEVIEW_SendExpanded(infoPtr, item, action);
3334 
3335  bSetSelection = (infoPtr->selectedItem != NULL
3336  && TREEVIEW_IsChildOf(item, infoPtr->selectedItem));
3337 
3338  bSetFirstVisible = (infoPtr->firstVisible != NULL
3339  && TREEVIEW_IsChildOf(item, infoPtr->firstVisible));
3340 
3341  tmpItem = item;
3342  while (tmpItem)
3343  {
3344  if (tmpItem->nextSibling)
3345  {
3346  nextItem = tmpItem->nextSibling;
3347  break;
3348  }
3349  tmpItem = tmpItem->parent;
3350  }
3351 
3352  if (nextItem)
3353  scrollDist = nextItem->rect.top;
3354 
3355  if (bRemoveChildren)
3356  {
3357  INT old_cChildren = item->cChildren;
3358  TRACE("TVE_COLLAPSERESET\n");
3359  item->state &= ~TVIS_EXPANDEDONCE;
3360  TREEVIEW_RemoveAllChildren(infoPtr, item);
3361  item->cChildren = old_cChildren;
3362  }
3363  if (!wasExpanded)
3364  return FALSE;
3365 
3366  if (item->firstChild)
3367  {
3368  TREEVIEW_ITEM *i, *sibling;
3369 
3370  sibling = TREEVIEW_GetNextListItem(infoPtr, item);
3371 
3372  for (i = item->firstChild; i != sibling;
3373  i = TREEVIEW_GetNextListItem(infoPtr, i))
3374  {
3375  i->visibleOrder = -1;
3376  }
3377  }
3378 
3379  TREEVIEW_RecalculateVisibleOrder(infoPtr, item);
3380 
3381  if (nextItem)
3382  scrollDist = -(scrollDist - nextItem->rect.top);
3383 
3384  if (bSetSelection)
3385  {
3386  /* Don't call DoSelectItem, it sends notifications. */
3387  if (TREEVIEW_ValidItem(infoPtr, infoPtr->selectedItem))
3388  infoPtr->selectedItem->state &= ~TVIS_SELECTED;
3389  item->state |= TVIS_SELECTED;
3390  infoPtr->selectedItem = item;
3391  }
3392 
3393  TREEVIEW_UpdateScrollBars(infoPtr);
3394 
3395  scrollRect.left = 0;
3396  scrollRect.right = infoPtr->clientWidth;
3397  scrollRect.bottom = infoPtr->clientHeight;
3398 
3399  if (nextItem)
3400  {
3401  scrollRect.top = nextItem->rect.top;
3402 
3403  ScrollWindowEx (infoPtr->hwnd, 0, scrollDist, &scrollRect, &scrollRect,
3405  TREEVIEW_Invalidate(infoPtr, item);
3406  } else {
3407  scrollRect.top = item->rect.top;
3408  InvalidateRect(infoPtr->hwnd, &scrollRect, TRUE);
3409  }
3410 
3411  TREEVIEW_SetFirstVisible(infoPtr,
3412  bSetFirstVisible ? item : infoPtr->firstVisible,
3413  TRUE);
3414 
3415  return wasExpanded;
3416 }
3417 
3418 static BOOL
3420  BOOL partial, BOOL user)
3421 {
3422  LONG scrollDist;
3423  LONG orgNextTop = 0;
3424  RECT scrollRect;
3425  TREEVIEW_ITEM *nextItem, *tmpItem;
3426  BOOL sendsNotifications;
3427 
3428  TRACE("(%p, %p, partial=%d, %d)\n", infoPtr, item, partial, user);
3429 
3430  if (!TREEVIEW_HasChildren(infoPtr, item))
3431  return FALSE;
3432 
3433  tmpItem = item; nextItem = NULL;
3434  while (tmpItem)
3435  {
3436  if (tmpItem->nextSibling)
3437  {
3438  nextItem = tmpItem->nextSibling;
3439  break;
3440  }
3441  tmpItem = tmpItem->parent;
3442  }
3443 
3444  if (nextItem)
3445  orgNextTop = nextItem->rect.top;
3446 
3447  TRACE("TVE_EXPAND %p %s\n", item, TREEVIEW_ItemName(item));
3448 
3449  sendsNotifications = user || ((item->cChildren != 0) &&
3450  !(item->state & TVIS_EXPANDEDONCE));
3451  if (sendsNotifications)
3452  {
3453  if (!TREEVIEW_SendExpanding(infoPtr, item, TVE_EXPAND))
3454  {
3455  TRACE(" TVN_ITEMEXPANDING returned TRUE, exiting...\n");
3456  return FALSE;
3457  }
3458  }
3459  if (!item->firstChild)
3460  return FALSE;
3461 
3462  item->state |= TVIS_EXPANDED;
3463 
3464  if (partial)
3465  FIXME("TVE_EXPANDPARTIAL not implemented\n");
3466 
3467  if (ISVISIBLE(item))
3468  {
3469  TREEVIEW_RecalculateVisibleOrder(infoPtr, item);
3470  TREEVIEW_UpdateSubTree(infoPtr, item);
3471  TREEVIEW_UpdateScrollBars(infoPtr);
3472 
3473  scrollRect.left = 0;
3474  scrollRect.bottom = infoPtr->treeHeight;
3475  scrollRect.right = infoPtr->clientWidth;
3476  if (nextItem)
3477  {
3478  scrollDist = nextItem->rect.top - orgNextTop;
3479  scrollRect.top = orgNextTop;
3480 
3481  ScrollWindowEx (infoPtr->hwnd, 0, scrollDist, &scrollRect, NULL,
3483  TREEVIEW_Invalidate (infoPtr, item);
3484  } else {
3485  scrollRect.top = item->rect.top;
3486  InvalidateRect(infoPtr->hwnd, &scrollRect, FALSE);
3487  }
3488 
3489  /* Scroll up so that as many children as possible are visible.
3490  * This fails when expanding causes an HScroll bar to appear, but we
3491  * don't know that yet, so the last item is obscured. */
3492  if (item->firstChild != NULL)
3493  {
3494  int nChildren = item->lastChild->visibleOrder
3495  - item->firstChild->visibleOrder + 1;
3496 
3497  int visible_pos = item->visibleOrder
3498  - infoPtr->firstVisible->visibleOrder;
3499 
3500  int rows_below = TREEVIEW_GetVisibleCount(infoPtr) - visible_pos - 1;
3501 
3502  if (visible_pos > 0 && nChildren > rows_below)
3503  {
3504  int scroll = nChildren - rows_below;
3505 
3506  if (scroll > visible_pos)
3507  scroll = visible_pos;
3508 
3509  if (scroll > 0)
3510  {
3511  TREEVIEW_ITEM *newFirstVisible
3512  = TREEVIEW_GetListItem(infoPtr, infoPtr->firstVisible,
3513  scroll);
3514 
3515 
3516  TREEVIEW_SetFirstVisible(infoPtr, newFirstVisible, TRUE);
3517  }
3518  }
3519  }
3520  }
3521 
3522  if (sendsNotifications) {
3523  TREEVIEW_SendExpanded(infoPtr, item, TVE_EXPAND);
3524  item->state |= TVIS_EXPANDEDONCE;
3525  }
3526 
3527  return TRUE;
3528 }
3529 
3530 /* Handler for TVS_SINGLEEXPAND behaviour. Used on response
3531  to mouse messages and TVM_SELECTITEM.
3532 
3533  selection - previously selected item, used to collapse a part of a tree
3534  item - new selected item
3535 */
3538 {
3539  TREEVIEW_ITEM *prev, *curr;
3540 
3541  if ((infoPtr->dwStyle & TVS_SINGLEEXPAND) == 0 || infoPtr->hwndEdit || !item) return;
3542 
3544 
3545  /*
3546  * Close the previous item and its ancestors as long as they are not
3547  * ancestors of the current item
3548  */
3549  for (prev = selection; prev && TREEVIEW_ValidItem(infoPtr, prev); prev = prev->parent)
3550  {
3551  for (curr = item; curr && TREEVIEW_ValidItem(infoPtr, curr); curr = curr->parent)
3552  {
3553  if (curr == prev)
3554  goto finish;
3555  }
3556  TREEVIEW_Collapse(infoPtr, prev, FALSE, TRUE);
3557  }
3558 
3559 finish:
3560  /*
3561  * Expand the current item
3562  */
3563  TREEVIEW_Expand(infoPtr, item, FALSE, TRUE);
3564 }
3565 
3566 static BOOL
3568 {
3569  TRACE("item=%p, user=%d\n", item, user);
3570 
3571  if (item->state & TVIS_EXPANDED)
3572  return TREEVIEW_Collapse(infoPtr, item, FALSE, user);
3573  else
3574  return TREEVIEW_Expand(infoPtr, item, FALSE, user);
3575 }
3576 
3577 static VOID
3579 {
3580  TREEVIEW_Expand(infoPtr, item, FALSE, TRUE);
3581 
3582  for (item = item->firstChild; item != NULL; item = item->nextSibling)
3583  {
3584  if (TREEVIEW_HasChildren(infoPtr, item))
3585  TREEVIEW_ExpandAll(infoPtr, item);
3586  }
3587 }
3588 
3589 /* Note:If the specified item is the child of a collapsed parent item,
3590  the parent's list of child items is (recursively) expanded to reveal the
3591  specified item. This is mentioned for TREEVIEW_SelectItem; don't
3592  know if it also applies here.
3593 */
3594 
3595 static LRESULT
3597 {
3598  if (!TREEVIEW_ValidItem(infoPtr, item))
3599  return 0;
3600 
3601  TRACE("For (%s) item:%d, flags 0x%x, state:%d\n",
3602  TREEVIEW_ItemName(item), TREEVIEW_GetItemIndex(infoPtr, item),
3603  flag, item->state);
3604 
3605  switch (flag & TVE_TOGGLE)
3606  {
3607  case TVE_COLLAPSE:
3608  return TREEVIEW_Collapse(infoPtr, item, flag & TVE_COLLAPSERESET,
3609  FALSE);
3610 
3611  case TVE_EXPAND:
3612  return TREEVIEW_Expand(infoPtr, item, flag & TVE_EXPANDPARTIAL,
3613  FALSE);
3614 
3615  case TVE_TOGGLE:
3616  return TREEVIEW_Toggle(infoPtr, item, FALSE);
3617 
3618  default:
3619  return 0;
3620  }
3621 }
3622 
3623 /* Hit-Testing **********************************************************/
3624 
3625 static TREEVIEW_ITEM *
3627 {
3629  LONG row;
3630 
3631  if (!infoPtr->firstVisible)
3632  return NULL;
3633 
3634  row = pt.y / infoPtr->uItemHeight + infoPtr->firstVisible->visibleOrder;
3635 
3636  for (item = infoPtr->firstVisible; item != NULL;
3637  item = TREEVIEW_GetNextListItem(infoPtr, item))
3638  {
3639  if (row >= item->visibleOrder
3640  && row < item->visibleOrder + item->iIntegral)
3641  break;
3642  }
3643 
3644  return item;
3645 }
3646 
3647 static TREEVIEW_ITEM *
3649 {
3651  RECT rect;
3652  UINT status;
3653  LONG x, y;
3654 
3655  lpht->hItem = 0;
3656  GetClientRect(infoPtr->hwnd, &rect);
3657  status = 0;
3658  x = lpht->pt.x;
3659  y = lpht->pt.y;
3660 
3661  if (x < rect.left)
3662  {
3663  status |= TVHT_TOLEFT;
3664  }
3665  else if (x > rect.right)
3666  {
3667  status |= TVHT_TORIGHT;
3668  }
3669 
3670  if (y < rect.top)
3671  {
3672  status |= TVHT_ABOVE;
3673  }
3674  else if (y > rect.bottom)
3675  {
3676  status |= TVHT_BELOW;
3677  }
3678 
3679  if (status)
3680  {
3681  lpht->flags = status;
3682  return NULL;
3683  }
3684 
3685  item = TREEVIEW_HitTestPoint(infoPtr, lpht->pt);
3686  if (!item)
3687  {
3688  lpht->flags = TVHT_NOWHERE;
3689  return NULL;
3690  }
3691 
3692  if (!item->textWidth)
3693  TREEVIEW_ComputeTextWidth(infoPtr, item, 0);
3694 
3695  if (x >= item->textOffset + item->textWidth)
3696  {
3697  lpht->flags = TVHT_ONITEMRIGHT;
3698  }
3699  else if (x >= item->textOffset)
3700  {
3701  lpht->flags = TVHT_ONITEMLABEL;
3702  }
3703  else if (x >= item->imageOffset)
3704  {
3705  lpht->flags = TVHT_ONITEMICON;
3706  }
3707  else if (x >= item->stateOffset)
3708  {
3709  lpht->flags = TVHT_ONITEMSTATEICON;
3710  }
3711  else if (x >= item->linesOffset && infoPtr->dwStyle & TVS_HASBUTTONS)
3712  {
3713  lpht->flags = TVHT_ONITEMBUTTON;
3714  }
3715  else
3716  {
3717  lpht->flags = TVHT_ONITEMINDENT;
3718  }
3719 
3720  lpht->hItem = item;
3721  TRACE("(%d,%d):result 0x%x\n", lpht->pt.x, lpht->pt.y, lpht->flags);
3722 
3723  return item;
3724 }
3725 
3726 /* Item Label Editing ***************************************************/
3727 
3728 static LRESULT
3730 {
3731  return (LRESULT)infoPtr->hwndEdit;
3732 }
3733 
3734 static LRESULT CALLBACK
3736 {
3737  TREEVIEW_INFO *infoPtr = TREEVIEW_GetInfoPtr(GetParent(hwnd));
3738  BOOL bCancel = FALSE;
3739  LRESULT rc;
3740 
3741  switch (uMsg)
3742  {
3743  case WM_PAINT:
3744  TRACE("WM_PAINT start\n");
3745  rc = CallWindowProcW(infoPtr->wpEditOrig, hwnd, uMsg, wParam,
3746  lParam);
3747  TRACE("WM_PAINT done\n");
3748  return rc;
3749 
3750  case WM_KILLFOCUS:
3751  if (infoPtr->bIgnoreEditKillFocus)
3752  return TRUE;
3753  break;
3754 
3755  case WM_DESTROY:
3756  {
3757  WNDPROC editProc = infoPtr->wpEditOrig;
3758  infoPtr->wpEditOrig = 0;
3759  SetWindowLongPtrW(hwnd, GWLP_WNDPROC, (DWORD_PTR)editProc);
3760  return CallWindowProcW(editProc, hwnd, uMsg, wParam, lParam);
3761  }
3762 
3763  case WM_GETDLGCODE:
3765 
3766  case WM_KEYDOWN:
3767  if (wParam == VK_ESCAPE)
3768  {
3769  bCancel = TRUE;
3770  break;
3771  }
3772  else if (wParam == VK_RETURN)
3773  {
3774  break;
3775  }
3776 
3777  /* fall through */
3778  default:
3779  return CallWindowProcW(infoPtr->wpEditOrig, hwnd, uMsg, wParam, lParam);
3780  }
3781 
3782  /* Processing TVN_ENDLABELEDIT message could kill the focus */
3783  /* eg. Using a messagebox */
3784 
3785  infoPtr->bIgnoreEditKillFocus = TRUE;
3786  TREEVIEW_EndEditLabelNow(infoPtr, bCancel || !infoPtr->bLabelChanged);
3787  infoPtr->bIgnoreEditKillFocus = FALSE;
3788 
3789  return 0;
3790 }
3791 
3792 
3793 /* should handle edit control messages here */
3794 
3795 static LRESULT
3797 {
3798  TRACE("code=0x%x, id=0x%x, handle=0x%lx\n", HIWORD(wParam), LOWORD(wParam), lParam);
3799 
3800  switch (HIWORD(wParam))
3801  {
3802  case EN_UPDATE:
3803  {
3804  /*
3805  * Adjust the edit window size
3806  */
3807  WCHAR buffer[1024];
3808  TREEVIEW_ITEM *editItem = infoPtr->editItem;
3809  HDC hdc = GetDC(infoPtr->hwndEdit);
3810  SIZE sz;
3811  HFONT hFont, hOldFont = 0;
3812 
3813  TRACE("edit=%p\n", infoPtr->hwndEdit);
3814 
3815  if (!IsWindow(infoPtr->hwndEdit) || !hdc) return FALSE;
3816 
3817  infoPtr->bLabelChanged = TRUE;
3818 
3819  GetWindowTextW(infoPtr->hwndEdit, buffer, sizeof(buffer)/sizeof(buffer[0]));
3820 
3821  /* Select font to get the right dimension of the string */
3822  hFont = (HFONT)SendMessageW(infoPtr->hwndEdit, WM_GETFONT, 0, 0);
3823 
3824  if (hFont != 0)
3825  {
3826  hOldFont = SelectObject(hdc, hFont);
3827  }
3828 
3829  if (GetTextExtentPoint32W(hdc, buffer, strlenW(buffer), &sz))
3830  {
3831  TEXTMETRICW textMetric;
3832 
3833  /* Add Extra spacing for the next character */
3834  GetTextMetricsW(hdc, &textMetric);
3835  sz.cx += (textMetric.tmMaxCharWidth * 2);
3836 
3837  sz.cx = max(sz.cx, textMetric.tmMaxCharWidth * 3);
3838  sz.cx = min(sz.cx,
3839  infoPtr->clientWidth - editItem->textOffset + 2);
3840 
3841  SetWindowPos(infoPtr->hwndEdit,
3842  HWND_TOP,
3843  0,
3844  0,
3845  sz.cx,
3846  editItem->rect.bottom - editItem->rect.top + 3,
3848  }
3849 
3850  if (hFont != 0)
3851  {
3852  SelectObject(hdc, hOldFont);
3853  }
3854 
3855  ReleaseDC(infoPtr->hwnd, hdc);
3856  break;
3857  }
3858  case EN_KILLFOCUS:
3859  /* apparently we should respect passed handle value */
3860  if (infoPtr->hwndEdit != (HWND)lParam) return FALSE;
3861 
3862  TREEVIEW_EndEditLabelNow(infoPtr, FALSE);
3863  break;
3864 
3865  default:
3866  return SendMessageW(infoPtr->hwndNotify, WM_COMMAND, wParam, lParam);
3867  }
3868 
3869  return 0;
3870 }
3871 
3872 static HWND
3874 {
3875  HWND hwnd = infoPtr->hwnd;
3876  HWND hwndEdit;
3877  SIZE sz;
3879  HDC hdc;
3880  HFONT hOldFont=0;
3881  TEXTMETRICW textMetric;
3882 
3883  TRACE("%p %p\n", hwnd, hItem);
3884  if (!(infoPtr->dwStyle & TVS_EDITLABELS))
3885  return NULL;
3886 
3887  if (!TREEVIEW_ValidItem(infoPtr, hItem))
3888  return NULL;
3889 
3890  if (infoPtr->hwndEdit)
3891  return infoPtr->hwndEdit;
3892 
3893  infoPtr->bLabelChanged = FALSE;
3894 
3895  /* make edit item visible */
3896  TREEVIEW_EnsureVisible(infoPtr, hItem, TRUE);
3897 
3898  TREEVIEW_UpdateDispInfo(infoPtr, hItem, TVIF_TEXT);
3899 
3900  hdc = GetDC(hwnd);
3901  /* Select the font to get appropriate metric dimensions */
3902  if (infoPtr->hFont != 0)
3903  {
3904  hOldFont = SelectObject(hdc, infoPtr->hFont);
3905  }
3906 
3907  /* Get string length in pixels */
3908  if (hItem->pszText)
3909  GetTextExtentPoint32W(hdc, hItem->pszText, strlenW(hItem->pszText),
3910  &sz);
3911  else
3912  GetTextExtentPoint32A(hdc, "", 0, &sz);
3913 
3914  /* Add Extra spacing for the next character */
3915  GetTextMetricsW(hdc, &textMetric);
3916  sz.cx += (textMetric.tmMaxCharWidth * 2);
3917 
3918  sz.cx = max(sz.cx, textMetric.tmMaxCharWidth * 3);
3919  sz.cx = min(sz.cx, infoPtr->clientWidth - hItem->textOffset + 2);
3920 
3921  if (infoPtr->hFont != 0)
3922  {
3923  SelectObject(hdc, hOldFont);
3924  }
3925 
3926  ReleaseDC(hwnd, hdc);
3927 
3928  infoPtr->editItem = hItem;
3929 
3930  hwndEdit = CreateWindowExW(WS_EX_LEFT,
3931  WC_EDITW,
3932  0,
3935  ES_LEFT, hItem->textOffset - 2,
3936  hItem->rect.top - 1, sz.cx + 3,
3937  hItem->rect.bottom -
3938  hItem->rect.top + 3, hwnd, 0, hinst, 0);
3939 /* FIXME: (HMENU)IDTVEDIT,pcs->hInstance,0); */
3940 
3941  infoPtr->hwndEdit = hwndEdit;
3942 
3943  /* Get a 2D border. */
3944  SetWindowLongW(hwndEdit, GWL_EXSTYLE,
3946  SetWindowLongW(hwndEdit, GWL_STYLE,
3947  GetWindowLongW(hwndEdit, GWL_STYLE) | WS_BORDER);
3948 
3949  SendMessageW(hwndEdit, WM_SETFONT,
3950  (WPARAM)TREEVIEW_FontForItem(infoPtr, hItem), FALSE);
3951 
3952  infoPtr->wpEditOrig = (WNDPROC)SetWindowLongPtrW(hwndEdit, GWLP_WNDPROC,
3953  (DWORD_PTR)
3955  if (hItem->pszText)
3956  SetWindowTextW(hwndEdit, hItem->pszText);
3957 
3958  if (TREEVIEW_BeginLabelEditNotify(infoPtr, hItem))
3959  {
3960  DestroyWindow(hwndEdit);
3961  infoPtr->hwndEdit = 0;
3962  infoPtr->editItem = NULL;
3963  return NULL;
3964  }
3965 
3966  SetFocus(hwndEdit);
3967  SendMessageW(hwndEdit, EM_SETSEL, 0, -1);
3968  ShowWindow(hwndEdit, SW_SHOW);
3969 
3970  return hwndEdit;
3971 }
3972 
3973 
3974 static LRESULT
3976 {
3977  TREEVIEW_ITEM *editedItem = infoPtr->editItem;
3978  NMTVDISPINFOW tvdi;
3979  BOOL bCommit;
3980  WCHAR tmpText[1024] = { '\0' };
3981  WCHAR *newText = tmpText;
3982  int iLength = 0;
3983 
3984  if (!IsWindow(infoPtr->hwndEdit)) return FALSE;
3985 
3986  tvdi.item.mask = 0;
3987  tvdi.item.hItem = editedItem;
3988  tvdi.item.state = editedItem->state;
3989  tvdi.item.lParam = editedItem->lParam;
3990 
3991  if (!bCancel)
3992  {
3993  if (!infoPtr->bNtfUnicode)
3994  iLength = GetWindowTextA(infoPtr->hwndEdit, (LPSTR)tmpText, 1023);
3995  else
3996  iLength = GetWindowTextW(infoPtr->hwndEdit, tmpText, 1023);
3997 
3998  if (iLength >= 1023)
3999  {
4000  ERR("Insufficient space to retrieve new item label\n");
4001  }
4002 
4003  tvdi.item.mask = TVIF_TEXT;
4004  tvdi.item.pszText = tmpText;
4005  tvdi.item.cchTextMax = iLength + 1;
4006  }
4007  else
4008  {
4009  tvdi.item.pszText = NULL;
4010  tvdi.item.cchTextMax = 0;
4011  }
4012 
4013  bCommit = TREEVIEW_SendRealNotify(infoPtr, TVN_ENDLABELEDITW, &tvdi.hdr);
4014 
4015  if (!bCancel && bCommit) /* Apply the changes */
4016  {
4017  if (!infoPtr->bNtfUnicode)
4018  {
4019  DWORD len = MultiByteToWideChar( CP_ACP, 0, (LPSTR)tmpText, -1, NULL, 0 );
4020  newText = Alloc(len * sizeof(WCHAR));
4021  MultiByteToWideChar( CP_ACP, 0, (LPSTR)tmpText, -1, newText, len );
4022  iLength = len - 1;
4023  }
4024 
4025  if (strcmpW(newText, editedItem->pszText) != 0)
4026  {
4027  WCHAR *ptr = ReAlloc(editedItem->pszText, sizeof(WCHAR)*(iLength + 1));
4028  if (ptr == NULL)
4029  {
4030  ERR("OutOfMemory, cannot allocate space for label\n");
4031  if(newText != tmpText) Free(newText);
4032  DestroyWindow(infoPtr->hwndEdit);
4033  infoPtr->hwndEdit = 0;
4034  infoPtr->editItem = NULL;
4035  return FALSE;
4036  }
4037  else
4038  {
4039  editedItem->pszText = ptr;
4040  editedItem->cchTextMax = iLength + 1;
4041  strcpyW(editedItem->pszText, newText);
4042  TREEVIEW_ComputeTextWidth(infoPtr, editedItem, 0);
4043  }
4044  }
4045  if(newText != tmpText) Free(newText);
4046  }
4047 
4048  ShowWindow(infoPtr->hwndEdit, SW_HIDE);
4049  DestroyWindow(infoPtr->hwndEdit);
4050  infoPtr->hwndEdit = 0;
4051  infoPtr->editItem = NULL;
4052  return TRUE;
4053 }
4054 
4055 static LRESULT
4057 {
4058  if (wParam != TV_EDIT_TIMER)
4059  {
4060  ERR("got unknown timer\n");
4061  return 1;
4062  }
4063 
4064  KillTimer(infoPtr->hwnd, TV_EDIT_TIMER);
4065  infoPtr->Timer &= ~TV_EDIT_TIMER_SET;
4066 
4067  TREEVIEW_EditLabel(infoPtr, infoPtr->selectedItem);
4068 
4069  return 0;
4070 }
4071 
4072 
4073 /* Mouse Tracking/Drag **************************************************/
4074 
4075 /***************************************************************************
4076  * This is quite unusual piece of code, but that's how it's implemented in
4077  * Windows.
4078  */
4079 static LRESULT
4081 {
4082  INT cxDrag = GetSystemMetrics(SM_CXDRAG);
4083  INT cyDrag = GetSystemMetrics(SM_CYDRAG);
4084  RECT r;
4085  MSG msg;
4086 
4087  r.top = pt.y - cyDrag;
4088  r.left = pt.x - cxDrag;
4089  r.bottom = pt.y + cyDrag;
4090  r.right = pt.x + cxDrag;
4091 
4092  SetCapture(infoPtr->hwnd);
4093 
4094  while (1)
4095  {
4096  if (PeekMessageW(&msg, 0, 0, 0, PM_REMOVE | PM_NOYIELD))
4097  {
4098  if (msg.message == WM_MOUSEMOVE)
4099  {
4100  pt.x = (short)LOWORD(msg.lParam);
4101  pt.y = (short)HIWORD(msg.lParam);
4102  if (PtInRect(&r, pt))
4103  continue;
4104  else
4105  {
4106  ReleaseCapture();
4107  return 1;
4108  }
4109  }
4110  else if (msg.message >= WM_LBUTTONDOWN &&
4111  msg.message <= WM_RBUTTONDBLCLK)
4112  {
4113  break;
4114  }
4115 
4116  DispatchMessageW(&msg);
4117  }
4118 
4119  if (GetCapture() != infoPtr->hwnd)
4120  return 0;
4121  }
4122 
4123  ReleaseCapture();
4124  return 0;
4125 }
4126 
4127 
4128 static LRESULT
4130 {
4132  TVHITTESTINFO hit;
4133 
4134  TRACE("\n");
4135  SetFocus(infoPtr->hwnd);
4136 
4137  if (infoPtr->Timer & TV_EDIT_TIMER_SET)
4138  {
4139  /* If there is pending 'edit label' event - kill it now */
4140  KillTimer(infoPtr->hwnd, TV_EDIT_TIMER);
4141  }
4142 
4143  hit.pt.x = (short)LOWORD(lParam);
4144  hit.pt.y = (short)HIWORD(lParam);
4145 
4146  item = TREEVIEW_HitTest(infoPtr, &hit);
4147  if (!item)
4148  return 0;
4149  TRACE("item %d\n", TREEVIEW_GetItemIndex(infoPtr, item));
4150 
4151  if (TREEVIEW_SendSimpleNotify(infoPtr, NM_DBLCLK) == FALSE)
4152  { /* FIXME! */
4153  switch (hit.flags)
4154  {
4155  case TVHT_ONITEMRIGHT:
4156  /* FIXME: we should not have sent NM_DBLCLK in this case. */
4157  break;
4158 
4159  case TVHT_ONITEMINDENT:
4160  if (!(infoPtr->dwStyle & TVS_HASLINES))
4161  {
4162  break;
4163  }
4164  else
4165  {
4166  int level = hit.pt.x / infoPtr->uIndent;
4167  if (!(infoPtr->dwStyle & TVS_LINESATROOT)) level++;
4168 
4169  while (item->iLevel > level)
4170  {
4171  item = item->parent;
4172  }
4173 
4174  /* fall through */
4175  }
4176 
4177  case TVHT_ONITEMLABEL:
4178  case TVHT_ONITEMICON:
4179  case TVHT_ONITEMBUTTON:
4180  TREEVIEW_Toggle(infoPtr, item, TRUE);
4181  break;
4182 
4183  case TVHT_ONITEMSTATEICON:
4184  if (infoPtr->dwStyle & TVS_CHECKBOXES)
4185  TREEVIEW_ToggleItemState(infoPtr, item);
4186  else
4187  TREEVIEW_Toggle(infoPtr, item, TRUE);
4188  break;
4189  }
4190  }
4191  return TRUE;
4192 }
4193 
4194 
4195 static LRESULT
4197 {
4198  BOOL do_track, do_select, bDoLabelEdit;
4199  HWND hwnd = infoPtr->hwnd;
4200  TVHITTESTINFO ht;
4201 
4202  /* If Edit control is active - kill it and return.
4203  * The best way to do it is to set focus to itself.
4204  * Edit control subclassed procedure will automatically call
4205  * EndEditLabelNow.
4206  */
4207  if (infoPtr->hwndEdit)
4208  {
4209  SetFocus(hwnd);
4210  return 0;
4211  }
4212 
4213  ht.pt.x = (short)LOWORD(lParam);
4214  ht.pt.y = (short)HIWORD(lParam);
4215 
4216  TREEVIEW_HitTest(infoPtr, &ht);
4217  TRACE("item %d\n", TREEVIEW_GetItemIndex(infoPtr, ht.hItem));
4218 
4219  /* update focusedItem and redraw both items */
4220  if (ht.hItem)
4221  {
4222  BOOL do_focus;
4223 
4224  if (TREEVIEW_IsFullRowSelect(infoPtr))
4225  do_focus = ht.flags & (TVHT_ONITEMINDENT | TVHT_ONITEM | TVHT_ONITEMRIGHT);
4226  else
4227  do_focus = ht.flags & TVHT_ONITEM;
4228 
4229  if (do_focus)
4230  {
4231  infoPtr->focusedItem = ht.hItem;
4232  TREEVIEW_InvalidateItem(infoPtr, infoPtr->focusedItem);
4233  TREEVIEW_InvalidateItem(infoPtr, infoPtr->selectedItem);
4234  }
4235  }
4236 
4237  if (!(infoPtr->dwStyle & TVS_DISABLEDRAGDROP))
4238  {
4239  if (TREEVIEW_IsFullRowSelect(infoPtr))
4240  do_track = ht.flags & (TVHT_ONITEMINDENT | TVHT_ONITEM | TVHT_ONITEMRIGHT);
4241  else
4242  do_track = ht.flags & TVHT_ONITEM;
4243  }
4244  else
4245  do_track = FALSE;
4246 
4247  /*
4248  * If the style allows editing and the node is already selected
4249  * and the click occurred on the item label...
4250  */
4251  bDoLabelEdit = (infoPtr->dwStyle & TVS_EDITLABELS) &&
4252  (ht.flags & TVHT_ONITEMLABEL) && (infoPtr->selectedItem == ht.hItem);
4253 
4254  /* Send NM_CLICK right away */
4255  if (!do_track && TREEVIEW_SendSimpleNotify(infoPtr, NM_CLICK))
4256  goto setfocus;
4257 
4258  if (ht.flags & TVHT_ONITEMBUTTON)
4259  {
4260  TREEVIEW_Toggle(infoPtr, ht.hItem, TRUE);
4261  goto setfocus;
4262  }
4263  else if (do_track)
4264  { /* if TREEVIEW_TrackMouse == 1 dragging occurred and the cursor left the dragged item's rectangle */
4265  if (TREEVIEW_TrackMouse(infoPtr, ht.pt))
4266  {
4268  infoPtr->dropItem = ht.hItem;
4269 
4270  /* clean up focusedItem as we dragged and won't select this item */
4271  if(infoPtr->focusedItem)
4272  {
4273  /* refresh the item that was focused */
4274  TREEVIEW_InvalidateItem(infoPtr, infoPtr->focusedItem);
4275  infoPtr->focusedItem = NULL;
4276 
4277  /* refresh the selected item to return the filled background */
4278  TREEVIEW_InvalidateItem(infoPtr, infoPtr->selectedItem);
4279  }
4280 
4281  return 0;
4282  }
4283  }
4284 
4285  if (do_track && TREEVIEW_SendSimpleNotify(infoPtr, NM_CLICK))
4286  goto setfocus;
4287 
4288  if (TREEVIEW_IsFullRowSelect(infoPtr))
4290  else
4291  do_select = ht.flags & (TVHT_ONITEMICON | TVHT_ONITEMLABEL);
4292 
4293  if (bDoLabelEdit)
4294  {
4295  if (infoPtr->Timer & TV_EDIT_TIMER_SET)
4296  KillTimer(hwnd, TV_EDIT_TIMER);
4297 
4299  infoPtr->Timer |= TV_EDIT_TIMER_SET;
4300  }
4301  else if (do_select)
4302  {
4303  TREEVIEW_ITEM *selection = infoPtr->selectedItem;
4304 
4305  /* Select the current item */
4307  TREEVIEW_SingleExpand(infoPtr, selection, ht.hItem);
4308  }
4309  else if (ht.flags & TVHT_ONITEMSTATEICON)
4310  {
4311  /* TVS_CHECKBOXES requires us to toggle the current state */
4312  if (infoPtr->dwStyle & TVS_CHECKBOXES)
4313  TREEVIEW_ToggleItemState(infoPtr, ht.hItem);
4314  }
4315 
4316  setfocus:
4317  SetFocus(hwnd);
4318  return 0;
4319 }
4320 
4321 
4322 static LRESULT
4324 {
4325  TVHITTESTINFO ht;
4326 
4327  if (infoPtr->hwndEdit)
4328  {
4329  SetFocus(infoPtr->hwnd);
4330  return 0;
4331  }
4332 
4333  ht.pt.x = (short)LOWORD(lParam);
4334  ht.pt.y = (short)HIWORD(lParam);
4335 
4336  if (TREEVIEW_HitTest(infoPtr, &ht))
4337  {
4338  infoPtr->focusedItem = ht.hItem;
4339  TREEVIEW_InvalidateItem(infoPtr, infoPtr->focusedItem);
4340  TREEVIEW_InvalidateItem(infoPtr, infoPtr->selectedItem);
4341  }
4342 
4343  if (TREEVIEW_TrackMouse(infoPtr, ht.pt))
4344  {
4345  if (ht.hItem)
4346  {
4348  infoPtr->dropItem = ht.hItem;
4349  }
4350  }
4351  else
4352  {
4353  SetFocus(infoPtr->hwnd);
4354  if(!TREEVIEW_SendSimpleNotify(infoPtr, NM_RCLICK))
4355  {
4356  /* Send a WM_CONTEXTMENU message in response to the RBUTTONUP */
4358  (WPARAM)infoPtr->hwnd, (LPARAM)GetMessagePos());
4359  }
4360  }
4361 
4362  if (ht.hItem)
4363  {
4364  TREEVIEW_InvalidateItem(infoPtr, infoPtr->focusedItem);
4365  infoPtr->focusedItem = infoPtr->selectedItem;
4366  TREEVIEW_InvalidateItem(infoPtr, infoPtr->focusedItem);
4367  }
4368 
4369  return 0;
4370 }
4371 
4372 static LRESULT
4374 {
4375  TREEVIEW_ITEM *dragItem = (HTREEITEM)lParam;
4376  INT cx, cy;
4377  HDC hdc, htopdc;
4378  HWND hwtop;
4379  HBITMAP hbmp, hOldbmp;
4380  SIZE size;
4381  RECT rc;
4382  HFONT hOldFont;
4383 
4384  TRACE("\n");
4385 
4386  if (!(infoPtr->himlNormal))
4387  return 0;
4388 
4389  if (!dragItem || !TREEVIEW_ValidItem(infoPtr, dragItem))
4390  return 0;
4391 
4392  TREEVIEW_UpdateDispInfo(infoPtr, dragItem, TVIF_TEXT);
4393 
4394  hwtop = GetDesktopWindow();
4395  htopdc = GetDC(hwtop);
4396  hdc = CreateCompatibleDC(htopdc);
4397 
4398  hOldFont = SelectObject(hdc, infoPtr->hFont);
4399 
4400  if (dragItem->pszText)
4401  GetTextExtentPoint32W(hdc, dragItem->pszText, strlenW(dragItem->pszText),
4402  &size);
4403  else
4404  GetTextExtentPoint32A(hdc, "", 0, &size);
4405 
4406  TRACE("%d %d %s\n", size.cx, size.cy, debugstr_w(dragItem->pszText));
4407  hbmp = CreateCompatibleBitmap(htopdc, size.cx, size.cy);
4408  hOldbmp = SelectObject(hdc, hbmp);
4409 
4410  ImageList_GetIconSize(infoPtr->himlNormal, &cx, &cy);
4411  size.cx += cx;
4412  if (cy > size.cy)
4413  size.cy = cy;
4414 
4415  infoPtr->dragList = ImageList_Create(size.cx, size.cy, ILC_COLOR, 10, 10);
4416  ImageList_Draw(infoPtr->himlNormal, dragItem->iImage, hdc, 0, 0,
4417  ILD_NORMAL);
4418 
4419 /*
4420  ImageList_GetImageInfo (infoPtr->himlNormal, dragItem->hItem, &iminfo);
4421  ImageList_AddMasked (infoPtr->dragList, iminfo.hbmImage, CLR_DEFAULT);
4422 */
4423 
4424 /* draw item text */
4425 
4426  SetRect(&rc, cx, 0, size.cx, size.cy);
4427 
4428  if (dragItem->pszText)
4429  DrawTextW(hdc, dragItem->pszText, strlenW(dragItem->pszText), &rc,
4430  DT_LEFT);
4431 
4432  SelectObject(hdc, hOldFont);
4433  SelectObject(hdc, hOldbmp);
4434 
4435  ImageList_Add(infoPtr->dragList, hbmp, 0);
4436 
4437  DeleteDC(hdc);
4438  DeleteObject(hbmp);
4439  ReleaseDC(hwtop, htopdc);
4440 
4441  return (LRESULT)infoPtr->dragList;
4442 }
4443 
4444 /* Selection ************************************************************/
4445 
4446 static LRESULT
4448  INT cause)
4449 {
4450  TREEVIEW_ITEM *prevSelect;
4451 
4452  assert(newSelect == NULL || TREEVIEW_ValidItem(infoPtr, newSelect));
4453 
4454  TRACE("Entering item %p (%s), flag 0x%x, cause 0x%x, state 0x%x\n",
4455  newSelect, TREEVIEW_ItemName(newSelect), action, cause,
4456  newSelect ? newSelect->state : 0);
4457 
4458  /* reset and redraw focusedItem if focusedItem was set so we don't */
4459  /* have to worry about the previously focused item when we set a new one */
4460  TREEVIEW_InvalidateItem(infoPtr, infoPtr->focusedItem);
4461  infoPtr->focusedItem = NULL;
4462 
4463  switch (action)
4464  {
4466  FIXME("TVSI_NOSINGLEEXPAND specified.\n");
4467  /* Fall through */
4468  case TVGN_CARET:
4469  prevSelect = infoPtr->selectedItem;
4470 
4471  if (prevSelect == newSelect) {
4472  TREEVIEW_EnsureVisible(infoPtr, infoPtr->selectedItem, FALSE);
4473  break;
4474  }
4475 
4476  if (TREEVIEW_SendTreeviewNotify(infoPtr,
4478  cause,
4480  prevSelect,
4481  newSelect))
4482  return FALSE;
4483 
4484  if (prevSelect)
4485  prevSelect->state &= ~TVIS_SELECTED;
4486  if (newSelect)
4487  newSelect->state |= TVIS_SELECTED;
4488 
4489  infoPtr->selectedItem = newSelect;
4490 
4491  TREEVIEW_EnsureVisible(infoPtr, infoPtr->selectedItem, FALSE);
4492 
4493  TREEVIEW_InvalidateItem(infoPtr, prevSelect);
4494  TREEVIEW_InvalidateItem(infoPtr, newSelect);
4495 
4498  cause,
4500  prevSelect,
4501  newSelect);
4502  break;
4503 
4504  case TVGN_DROPHILITE:
4505  prevSelect = infoPtr->dropItem;
4506 
4507  if (prevSelect)
4508  prevSelect->state &= ~TVIS_DROPHILITED;
4509 
4510  infoPtr->dropItem = newSelect;
4511 
4512  if (newSelect)
4513  newSelect->state |= TVIS_DROPHILITED;
4514 
4515  TREEVIEW_Invalidate(infoPtr, prevSelect);
4516  TREEVIEW_Invalidate(infoPtr, newSelect);
4517  break;
4518 
4519  case TVGN_FIRSTVISIBLE:
4520  if (newSelect != NULL)
4521  {
4522  TREEVIEW_EnsureVisible(infoPtr, newSelect, FALSE);
4523  TREEVIEW_SetFirstVisible(infoPtr, newSelect, TRUE);
4524  TREEVIEW_Invalidate(infoPtr, NULL);
4525  }
4526  break;
4527  }
4528 
4529  TRACE("Leaving state 0x%x\n", newSelect ? newSelect->state : 0);
4530  return TRUE;
4531 }
4532 
4533 /* FIXME: handle NM_KILLFOCUS etc */
4534 static LRESULT
4536 {
4537  TREEVIEW_ITEM *selection = infoPtr->selectedItem;
4538 
4539  if (item && !TREEVIEW_ValidItem(infoPtr, item))
4540  return FALSE;
4541 
4542  if (item == infoPtr->selectedItem)
4543  return TRUE;
4544 
4545  TRACE("%p (%s) %d\n", item, TREEVIEW_ItemName(item), wParam);
4546 
4547  if (!TREEVIEW_DoSelectItem(infoPtr, wParam, item, TVC_UNKNOWN))
4548  return FALSE;
4549 
4550  TREEVIEW_SingleExpand(infoPtr, selection, item);
4551 
4552  return TRUE;
4553 }
4554 
4555 /*************************************************************************
4556  * TREEVIEW_ProcessLetterKeys
4557  *
4558  * Processes keyboard messages generated by pressing the letter keys
4559  * on the keyboard.
4560  * What this does is perform a case insensitive search from the
4561  * current position with the following quirks:
4562  * - If two chars or more are pressed in quick succession we search
4563  * for the corresponding string (e.g. 'abc').
4564  * - If there is a delay we wipe away the current search string and
4565  * restart with just that char.
4566  * - If the user keeps pressing the same character, whether slowly or
4567  * fast, so that the search string is entirely composed of this
4568  * character ('aaaaa' for instance), then we search for first item
4569  * that starting with that character.
4570  * - If the user types the above character in quick succession, then
4571  * we must also search for the corresponding string ('aaaaa'), and
4572  * go to that string if there is a match.
4573  *
4574  * RETURNS
4575  *
4576  * Zero.
4577  *
4578  * BUGS
4579  *
4580  * - The current implementation has a list of characters it will
4581  * accept and it ignores everything else. In particular it will
4582  * ignore accentuated characters which seems to match what
4583  * Windows does. But I'm not sure it makes sense to follow
4584  * Windows there.
4585  * - We don't sound a beep when the search fails.
4586  * - The search should start from the focused item, not from the selected
4587  * item. One reason for this is to allow for multiple selections in trees.
4588  * But currently infoPtr->focusedItem does not seem very usable.
4589  *
4590  * SEE ALSO
4591  *
4592  * TREEVIEW_ProcessLetterKeys
4593  */
4594 static INT TREEVIEW_ProcessLetterKeys(TREEVIEW_INFO *infoPtr, WPARAM charCode, LPARAM keyData)
4595 {
4596  HTREEITEM nItem;
4597  HTREEITEM endidx,idx;
4598  TVITEMEXW item;
4600  DWORD timestamp,elapsed;
4601 
4602  /* simple parameter checking */
4603  if (!charCode || !keyData) return 0;
4604 
4605  /* only allow the valid WM_CHARs through */
4606  if (!isalnum(charCode) &&
4607  charCode != '.' && charCode != '`' && charCode != '!' &&
4608  charCode != '@' && charCode != '#' && charCode != '$' &&
4609  charCode != '%' && charCode != '^' && charCode != '&' &&
4610  charCode != '*' && charCode != '(' && charCode != ')' &&
4611  charCode != '-' && charCode != '_' && charCode != '+' &&
4612  charCode != '=' && charCode != '\\'&& charCode != ']' &&
4613  charCode != '}' && charCode != '[' && charCode != '{' &&
4614  charCode != '/' && charCode != '?' && charCode != '>' &&
4615  charCode != '<' && charCode != ',' && charCode != '~')
4616  return 0;
4617 
4618  /* compute how much time elapsed since last keypress */
4619  timestamp = GetTickCount();
4620  if (timestamp > infoPtr->lastKeyPressTimestamp) {
4621  elapsed=timestamp-infoPtr->lastKeyPressTimestamp;
4622  } else {
4623  elapsed=infoPtr->lastKeyPressTimestamp-timestamp;
4624  }
4625 
4626  /* update the search parameters */
4627  infoPtr->lastKeyPressTimestamp=timestamp;
4628  if (elapsed < KEY_DELAY) {
4629  if (infoPtr->nSearchParamLength < sizeof(infoPtr->szSearchParam) / sizeof(WCHAR)) {
4630  infoPtr->szSearchParam[infoPtr->nSearchParamLength++]=charCode;
4631  }
4632  if (infoPtr->charCode != charCode) {
4633  infoPtr->charCode=charCode=0;
4634  }
4635  } else {
4636  infoPtr->charCode=charCode;
4637  infoPtr->szSearchParam[0]=charCode;
4638  infoPtr->nSearchParamLength=1;
4639  /* Redundant with the 1 char string */
4640  charCode=0;
4641  }
4642 
4643  /* and search from the current position */
4644  nItem=NULL;
4645  if (infoPtr->selectedItem != NULL) {
4646  endidx=infoPtr->selectedItem;
4647  /* if looking for single character match,
4648  * then we must always move forward
4649  */
4650  if (infoPtr->nSearchParamLength == 1)
4651  idx=TREEVIEW_GetNextListItem(infoPtr,endidx);
4652  else
4653  idx=endidx;
4654  } else {
4655  endidx=NULL;
4656  idx=infoPtr->root->firstChild;
4657  }
4658  do {
4659  /* At the end point, sort out wrapping */
4660  if (idx == NULL) {
4661 
4662  /* If endidx is null, stop at the last item (ie top to bottom) */
4663  if (endidx == NULL)
4664  break;
4665 
4666  /* Otherwise, start again at the very beginning */
4667  idx=infoPtr->root->firstChild;
4668 
4669  /* But if we are stopping on the first child, end now! */
4670  if (idx == endidx) break;
4671  }
4672 
4673  /* get item */
4674  ZeroMemory(&item, sizeof(item));
4675  item.mask = TVIF_TEXT;
4676  item.hItem = idx;
4677  item.pszText = buffer;
4678  item.cchTextMax = sizeof(buffer);
4679  TREEVIEW_GetItemT( infoPtr, &item, TRUE );
4680 
4681  /* check for a match */
4682  if (strncmpiW(item.pszText,infoPtr->szSearchParam,infoPtr->nSearchParamLength) == 0) {
4683  nItem=idx;
4684  break;
4685  } else if ( (charCode != 0) && (nItem == NULL) &&
4686  (nItem != infoPtr->selectedItem) &&
4687  (strncmpiW(item.pszText,infoPtr->szSearchParam,1) == 0) ) {
4688  /* This would work but we must keep looking for a longer match */
4689  nItem=idx;
4690  }
4691  idx=TREEVIEW_GetNextListItem(infoPtr,idx);
4692  } while (idx != endidx);
4693 
4694  if (nItem != NULL) {
4695  if (TREEVIEW_DoSelectItem(infoPtr, TVGN_CARET, nItem, TVC_BYKEYBOARD)) {
4696  TREEVIEW_EnsureVisible(infoPtr, nItem, FALSE);
4697  }
4698  }
4699 
4700  return 0;
4701 }
4702 
4703 /* Scrolling ************************************************************/
4704 
4705 static LRESULT
4707 {
4708  int viscount;
4709  BOOL hasFirstVisible = infoPtr->firstVisible != NULL;
4710  HTREEITEM newFirstVisible = NULL;
4711  int visible_pos = -1;
4712 
4713  if (!TREEVIEW_ValidItem(infoPtr, item))
4714  return FALSE;
4715 
4716  if (!ISVISIBLE(item))
4717  {
4718  /* Expand parents as necessary. */
4719  HTREEITEM parent;
4720 
4721  /* see if we are trying to ensure that root is visible */
4722  if((item != infoPtr->root) && TREEVIEW_ValidItem(infoPtr, item))
4723  parent = item->parent;
4724  else
4725  parent = item; /* this item is the topmost item */
4726 
4727  while (parent != infoPtr->root)
4728  {
4729  if (!(parent->state & TVIS_EXPANDED))
4730  TREEVIEW_Expand(infoPtr, parent, FALSE, TRUE);
4731 
4732  parent = parent->parent;
4733  }
4734  }
4735 
4736  viscount = TREEVIEW_GetVisibleCount(infoPtr);
4737 
4738  TRACE("%p (%s) %d - %d viscount(%d)\n", item, TREEVIEW_ItemName(item), item->visibleOrder,
4739  hasFirstVisible ? infoPtr->firstVisible->visibleOrder : -1, viscount);
4740 
4741  if (hasFirstVisible)
4742  visible_pos = item->visibleOrder - infoPtr->firstVisible->visibleOrder;
4743 
4744  if (visible_pos < 0)
4745  {
4746  /* item is before the start of the list: put it at the top. */
4747  newFirstVisible = item;
4748  }
4749  else if (visible_pos >= viscount
4750  /* Sometimes, before we are displayed, GVC is 0, causing us to
4751  * spuriously scroll up. */
4752  && visible_pos > 0 && !(infoPtr->dwStyle & TVS_NOSCROLL) )
4753  {
4754  /* item is past the end of the list. */
4755  int scroll = visible_pos - viscount;
4756 
4757  newFirstVisible = TREEVIEW_GetListItem(infoPtr, infoPtr->firstVisible,
4758  scroll + 1);
4759  }
4760 
4761  if (bHScroll)
4762  {
4763  /* Scroll window so item's text is visible as much as possible */
4764  /* Calculation of amount of extra space is taken from EditLabel code */
4765  INT pos, x;
4766  TEXTMETRICW textMetric;
4767  HDC hdc = GetWindowDC(infoPtr->hwnd);
4768 
4769  x = item->textWidth;
4770 
4771  GetTextMetricsW(hdc, &textMetric);
4772  ReleaseDC(infoPtr->hwnd, hdc);
4773 
4774  x += (textMetric.tmMaxCharWidth * 2);
4775  x = max(x, textMetric.tmMaxCharWidth * 3);
4776 
4777  if (item->textOffset < 0)
4778  pos = item->textOffset;
4779  else if (item->textOffset + x > infoPtr->clientWidth)
4780  {
4781  if (x > infoPtr->clientWidth)
4782  pos = item->textOffset;
4783  else
4784  pos = item->textOffset + x - infoPtr->clientWidth;
4785  }
4786  else
4787  pos = 0;
4788 
4789  TREEVIEW_HScroll(infoPtr, MAKEWPARAM(SB_THUMBPOSITION, infoPtr->scrollX + pos));
4790  }
4791 
4792  if (newFirstVisible != NULL && newFirstVisible != infoPtr->firstVisible)
4793  {
4794  TREEVIEW_SetFirstVisible(infoPtr, newFirstVisible, TRUE);
4795 
4796  return TRUE;
4797  }
4798 
4799  return FALSE;
4800 }
4801 
4802 static VOID
4804  TREEVIEW_ITEM *newFirstVisible,
4805  BOOL bUpdateScrollPos)
4806 {
4807  int gap_size;
4808 
4809  TRACE("%p: %s\n", newFirstVisible, TREEVIEW_ItemName(newFirstVisible));
4810 
4811  if (newFirstVisible != NULL)
4812  {
4813  /* Prevent an empty gap from appearing at the bottom... */
4814  gap_size = TREEVIEW_GetVisibleCount(infoPtr)
4815  - infoPtr->maxVisibleOrder + newFirstVisible->visibleOrder;
4816 
4817  if (gap_size > 0)
4818  {
4819  newFirstVisible = TREEVIEW_GetListItem(infoPtr, newFirstVisible,
4820  -gap_size);
4821 
4822  /* ... unless we just don't have enough items. */
4823  if (newFirstVisible == NULL)
4824  newFirstVisible = infoPtr->root->firstChild;
4825  }
4826  }
4827 
4828  if (infoPtr->firstVisible != newFirstVisible)
4829  {
4830  if (infoPtr->firstVisible == NULL || newFirstVisible == NULL)
4831  {
4832  infoPtr->firstVisible = newFirstVisible;
4833  TREEVIEW_Invalidate(infoPtr, NULL);
4834  }
4835  else
4836  {
4838  int scroll = infoPtr->uItemHeight *
4839  (infoPtr->firstVisible->visibleOrder
4840  - newFirstVisible->visibleOrder);