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