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 static VOID 2338 TREEVIEW_ToggleItemState(const TREEVIEW_INFO *infoPtr, TREEVIEW_ITEM *item) 2339 { 2340 if (infoPtr->dwStyle & TVS_CHECKBOXES) 2341 { 2342 static const unsigned int state_table[] = { 0, 2, 1 }; 2343 2344 unsigned int state; 2345 2346 state = STATEIMAGEINDEX(item->state); 2347 TRACE("state: 0x%x\n", state); 2348 item->state &= ~TVIS_STATEIMAGEMASK; 2349 2350 if (state < 3) 2351 state = state_table[state]; 2352 2353 item->state |= INDEXTOSTATEIMAGEMASK(state); 2354 2355 TRACE("state: 0x%x\n", state); 2356 TREEVIEW_Invalidate(infoPtr, item); 2357 } 2358 } 2359 2360 2361 /* Painting *************************************************************/ 2362 2363 /* Draw the lines and expand button for an item. Also draws one section 2364 * of the line from item's parent to item's parent's next sibling. */ 2365 static void 2366 TREEVIEW_DrawItemLines(const TREEVIEW_INFO *infoPtr, HDC hdc, const TREEVIEW_ITEM *item) 2367 { 2368 LONG centerx, centery; 2369 BOOL lar = ((infoPtr->dwStyle 2370 & (TVS_LINESATROOT|TVS_HASLINES|TVS_HASBUTTONS)) 2371 > TVS_LINESATROOT); 2372 HBRUSH hbr, hbrOld; 2373 COLORREF clrBk = GETBKCOLOR(infoPtr->clrBk); 2374 2375 if (!lar && item->iLevel == 0) 2376 return; 2377 2378 hbr = CreateSolidBrush(clrBk); 2379 hbrOld = SelectObject(hdc, hbr); 2380 2381 centerx = (item->linesOffset + item->stateOffset) / 2; 2382 centery = (item->rect.top + item->rect.bottom) / 2; 2383 2384 if (infoPtr->dwStyle & TVS_HASLINES) 2385 { 2386 HPEN hOldPen, hNewPen; 2387 HTREEITEM parent; 2388 LOGBRUSH lb; 2389 2390 /* Get a dotted grey pen */ 2391 lb.lbStyle = BS_SOLID; 2392 lb.lbColor = GETLINECOLOR(infoPtr->clrLine); 2393 hNewPen = ExtCreatePen(PS_COSMETIC|PS_ALTERNATE, 1, &lb, 0, NULL); 2394 hOldPen = SelectObject(hdc, hNewPen); 2395 2396 /* Make sure the center is on a dot (using +2 instead 2397 * of +1 gives us pixel-by-pixel compat with native) */ 2398 centery = (centery + 2) & ~1; 2399 2400 MoveToEx(hdc, item->stateOffset, centery, NULL); 2401 LineTo(hdc, centerx - 1, centery); 2402 2403 if (item->prevSibling || item->parent != infoPtr->root) 2404 { 2405 MoveToEx(hdc, centerx, item->rect.top, NULL); 2406 LineTo(hdc, centerx, centery); 2407 } 2408 2409 if (item->nextSibling) 2410 { 2411 MoveToEx(hdc, centerx, centery, NULL); 2412 LineTo(hdc, centerx, item->rect.bottom + 1); 2413 } 2414 2415 /* Draw the line from our parent to its next sibling. */ 2416 parent = item->parent; 2417 while (parent != infoPtr->root) 2418 { 2419 int pcenterx = (parent->linesOffset + parent->stateOffset) / 2; 2420 2421 if (parent->nextSibling 2422 /* skip top-levels unless TVS_LINESATROOT */ 2423 && parent->stateOffset > parent->linesOffset) 2424 { 2425 MoveToEx(hdc, pcenterx, item->rect.top, NULL); 2426 LineTo(hdc, pcenterx, item->rect.bottom + 1); 2427 } 2428 2429 parent = parent->parent; 2430 } 2431 2432 SelectObject(hdc, hOldPen); 2433 DeleteObject(hNewPen); 2434 } 2435 2436 /* 2437 * Display the (+/-) signs 2438 */ 2439 2440 if (infoPtr->dwStyle & TVS_HASBUTTONS) 2441 { 2442 if (item->cChildren) 2443 { 2444 HTHEME theme = GetWindowTheme(infoPtr->hwnd); 2445 if (theme) 2446 { 2447 RECT glyphRect = item->rect; 2448 glyphRect.left = item->linesOffset; 2449 glyphRect.right = item->stateOffset; 2450 DrawThemeBackground (theme, hdc, TVP_GLYPH, 2451 (item->state & TVIS_EXPANDED) ? GLPS_OPENED : GLPS_CLOSED, 2452 &glyphRect, NULL); 2453 } 2454 else 2455 { 2456 LONG height = item->rect.bottom - item->rect.top; 2457 LONG width = item->stateOffset - item->linesOffset; 2458 LONG rectsize = min(height, width) / 4; 2459 /* plussize = ceil(rectsize * 3/4) */ 2460 LONG plussize = (rectsize + 1) * 3 / 4; 2461 2462 HPEN new_pen = CreatePen(PS_SOLID, 0, GETLINECOLOR(infoPtr->clrLine)); 2463 HPEN old_pen = SelectObject(hdc, new_pen); 2464 2465 Rectangle(hdc, centerx - rectsize - 1, centery - rectsize - 1, 2466 centerx + rectsize + 2, centery + rectsize + 2); 2467 2468 SelectObject(hdc, old_pen); 2469 DeleteObject(new_pen); 2470 2471 /* draw +/- signs with current text color */ 2472 new_pen = CreatePen(PS_SOLID, 0, GETTXTCOLOR(infoPtr->clrText)); 2473 old_pen = SelectObject(hdc, new_pen); 2474 2475 if (height < 18 || width < 18) 2476 { 2477 MoveToEx(hdc, centerx - plussize + 1, centery, NULL); 2478 LineTo(hdc, centerx + plussize, centery); 2479 2480 if (!(item->state & TVIS_EXPANDED) || 2481 (item->state & TVIS_EXPANDPARTIAL)) 2482 { 2483 MoveToEx(hdc, centerx, centery - plussize + 1, NULL); 2484 LineTo(hdc, centerx, centery + plussize); 2485 } 2486 } 2487 else 2488 { 2489 Rectangle(hdc, centerx - plussize + 1, centery - 1, 2490 centerx + plussize, centery + 2); 2491 2492 if (!(item->state & TVIS_EXPANDED) || 2493 (item->state & TVIS_EXPANDPARTIAL)) 2494 { 2495 Rectangle(hdc, centerx - 1, centery - plussize + 1, 2496 centerx + 2, centery + plussize); 2497 SetPixel(hdc, centerx - 1, centery, clrBk); 2498 SetPixel(hdc, centerx + 1, centery, clrBk); 2499 } 2500 } 2501 2502 SelectObject(hdc, old_pen); 2503 DeleteObject(new_pen); 2504 } 2505 } 2506 } 2507 SelectObject(hdc, hbrOld); 2508 DeleteObject(hbr); 2509 } 2510 2511 static void 2512 TREEVIEW_DrawItem(const TREEVIEW_INFO *infoPtr, HDC hdc, TREEVIEW_ITEM *item) 2513 { 2514 INT cditem; 2515 HFONT hOldFont; 2516 COLORREF oldTextColor, oldTextBkColor; 2517 int centery; 2518 BOOL inFocus = (GetFocus() == infoPtr->hwnd); 2519 NMTVCUSTOMDRAW nmcdhdr; 2520 2521 TREEVIEW_UpdateDispInfo(infoPtr, item, CALLBACK_MASK_ALL); 2522 2523 /* - If item is drop target or it is selected and window is in focus - 2524 * use blue background (COLOR_HIGHLIGHT). 2525 * - If item is selected, window is not in focus, but it has style 2526 * TVS_SHOWSELALWAYS - use grey background (COLOR_BTNFACE) 2527 * - Otherwise - use background color 2528 */ 2529 if ((item->state & TVIS_DROPHILITED) || ((item == infoPtr->focusedItem) && !(item->state & TVIS_SELECTED)) || 2530 ((item->state & TVIS_SELECTED) && (!infoPtr->focusedItem || item == infoPtr->focusedItem) && 2531 (inFocus || (infoPtr->dwStyle & TVS_SHOWSELALWAYS)))) 2532 { 2533 if ((item->state & TVIS_DROPHILITED) || inFocus) 2534 { 2535 nmcdhdr.clrTextBk = comctl32_color.clrHighlight; 2536 nmcdhdr.clrText = comctl32_color.clrHighlightText; 2537 } 2538 else 2539 { 2540 nmcdhdr.clrTextBk = comctl32_color.clrBtnFace; 2541 nmcdhdr.clrText = GETTXTCOLOR(infoPtr->clrText); 2542 } 2543 } 2544 else 2545 { 2546 nmcdhdr.clrTextBk = GETBKCOLOR(infoPtr->clrBk); 2547 if ((infoPtr->dwStyle & TVS_TRACKSELECT) && (item == infoPtr->hotItem)) 2548 nmcdhdr.clrText = comctl32_color.clrHighlight; 2549 else 2550 nmcdhdr.clrText = GETTXTCOLOR(infoPtr->clrText); 2551 } 2552 2553 hOldFont = SelectObject(hdc, TREEVIEW_FontForItem(infoPtr, item)); 2554 oldTextColor = SetTextColor(hdc, nmcdhdr.clrText); 2555 oldTextBkColor = SetBkColor(hdc, nmcdhdr.clrTextBk); 2556 2557 /* The custom draw handler can query the text rectangle, 2558 * so get ready. */ 2559 /* should already be known, set to 0 when changed */ 2560 if (!item->textWidth) 2561 TREEVIEW_ComputeTextWidth(infoPtr, item, hdc); 2562 2563 cditem = 0; 2564 2565 if (infoPtr->cdmode & CDRF_NOTIFYITEMDRAW) 2566 { 2567 cditem = TREEVIEW_SendCustomDrawItemNotify 2568 (infoPtr, hdc, item, CDDS_ITEMPREPAINT, &nmcdhdr); 2569 TRACE("prepaint:cditem-app returns 0x%x\n", cditem); 2570 2571 if (cditem & CDRF_SKIPDEFAULT) 2572 { 2573 SelectObject(hdc, hOldFont); 2574 return; 2575 } 2576 } 2577 2578 if (cditem & CDRF_NEWFONT) 2579 TREEVIEW_ComputeTextWidth(infoPtr, item, hdc); 2580 2581 if (TREEVIEW_IsFullRowSelect(infoPtr)) 2582 { 2583 HBRUSH brush = CreateSolidBrush(nmcdhdr.clrTextBk); 2584 FillRect(hdc, &item->rect, brush); 2585 DeleteObject(brush); 2586 } 2587 2588 TREEVIEW_DrawItemLines(infoPtr, hdc, item); 2589 2590 /* reset colors. Custom draw handler can change them */ 2591 SetTextColor(hdc, nmcdhdr.clrText); 2592 SetBkColor(hdc, nmcdhdr.clrTextBk); 2593 2594 centery = (item->rect.top + item->rect.bottom) / 2; 2595 2596 /* 2597 * Display the images associated with this item 2598 */ 2599 { 2600 INT imageIndex; 2601 2602 /* State images are displayed to the left of the Normal image 2603 * image number is in state; zero should be `display no image'. 2604 */ 2605 imageIndex = STATEIMAGEINDEX(item->state); 2606 2607 if (infoPtr->himlState && imageIndex) 2608 { 2609 ImageList_Draw(infoPtr->himlState, imageIndex, hdc, 2610 item->stateOffset, 2611 centery - infoPtr->stateImageHeight / 2, 2612 ILD_NORMAL); 2613 } 2614 2615 /* Now, draw the normal image; can be either selected, 2616 * non-selected or expanded image. 2617 */ 2618 2619 if ((item->state & TVIS_SELECTED) && (item->iSelectedImage >= 0)) 2620 { 2621 /* The item is currently selected */ 2622 imageIndex = item->iSelectedImage; 2623 } 2624 else if ((item->state & TVIS_EXPANDED) && (item->iExpandedImage != (WORD)I_IMAGENONE)) 2625 { 2626 /* The item is currently not selected but expanded */ 2627 imageIndex = item->iExpandedImage; 2628 } 2629 else 2630 { 2631 /* The item is not selected and not expanded */ 2632 imageIndex = item->iImage; 2633 } 2634 2635 if (infoPtr->himlNormal) 2636 { 2637 UINT style = item->state & TVIS_CUT ? ILD_SELECTED : ILD_NORMAL; 2638 2639 style |= item->state & TVIS_OVERLAYMASK; 2640 2641 ImageList_DrawEx(infoPtr->himlNormal, imageIndex, hdc, 2642 item->imageOffset, centery - infoPtr->normalImageHeight / 2, 2643 0, 0, infoPtr->clrBk, item->state & TVIS_CUT ? GETBKCOLOR(infoPtr->clrBk) : CLR_DEFAULT, 2644 style); 2645 } 2646 } 2647 2648 2649 /* 2650 * Display the text associated with this item 2651 */ 2652 2653 /* Don't paint item's text if it's being edited */ 2654 if (!infoPtr->hwndEdit || (infoPtr->selectedItem != item)) 2655 { 2656 if (item->pszText) 2657 { 2658 RECT rcText; 2659 UINT align; 2660 SIZE sz; 2661 2662 rcText.top = item->rect.top; 2663 rcText.bottom = item->rect.bottom; 2664 rcText.left = item->textOffset; 2665 rcText.right = rcText.left + item->textWidth + 4; 2666 2667 TRACE("drawing text %s at (%s)\n", 2668 debugstr_w(item->pszText), wine_dbgstr_rect(&rcText)); 2669 2670 /* Draw it */ 2671 GetTextExtentPoint32W(hdc, item->pszText, strlenW(item->pszText), &sz); 2672 2673 align = SetTextAlign(hdc, TA_LEFT | TA_TOP); 2674 ExtTextOutW(hdc, rcText.left + 2, (rcText.top + rcText.bottom - sz.cy) / 2, 2675 ETO_CLIPPED | ETO_OPAQUE, 2676 &rcText, 2677 item->pszText, 2678 lstrlenW(item->pszText), 2679 NULL); 2680 SetTextAlign(hdc, align); 2681 2682 /* Draw focus box around the selected item */ 2683 if ((item == infoPtr->selectedItem) && inFocus) 2684 { 2685 DrawFocusRect(hdc,&rcText); 2686 } 2687 } 2688 } 2689 2690 /* Draw insertion mark if necessary */ 2691 2692 if (infoPtr->insertMarkItem) 2693 TRACE("item:%d,mark:%p\n", 2694 TREEVIEW_GetItemIndex(infoPtr, item), 2695 infoPtr->insertMarkItem); 2696 2697 if (item == infoPtr->insertMarkItem) 2698 { 2699 HPEN hNewPen, hOldPen; 2700 int offset; 2701 int left, right; 2702 2703 hNewPen = CreatePen(PS_SOLID, 2, GETINSCOLOR(infoPtr->clrInsertMark)); 2704 hOldPen = SelectObject(hdc, hNewPen); 2705 2706 if (infoPtr->insertBeforeorAfter) 2707 offset = item->rect.bottom - 1; 2708 else 2709 offset = item->rect.top + 1; 2710 2711 left = item->textOffset - 2; 2712 right = item->textOffset + item->textWidth + 2; 2713 2714 MoveToEx(hdc, left, offset - 3, NULL); 2715 LineTo(hdc, left, offset + 4); 2716 2717 MoveToEx(hdc, left, offset, NULL); 2718 LineTo(hdc, right + 1, offset); 2719 2720 MoveToEx(hdc, right, offset + 3, NULL); 2721 LineTo(hdc, right, offset - 4); 2722 2723 SelectObject(hdc, hOldPen); 2724 DeleteObject(hNewPen); 2725 } 2726 2727 /* Restore the hdc state */ 2728 SetTextColor(hdc, oldTextColor); 2729 SetBkColor(hdc, oldTextBkColor); 2730 SelectObject(hdc, hOldFont); 2731 2732 if (cditem & CDRF_NOTIFYPOSTPAINT) 2733 { 2734 cditem = TREEVIEW_SendCustomDrawItemNotify 2735 (infoPtr, hdc, item, CDDS_ITEMPOSTPAINT, &nmcdhdr); 2736 TRACE("postpaint:cditem-app returns 0x%x\n", cditem); 2737 } 2738 } 2739 2740 /* Computes treeHeight and treeWidth and updates the scroll bars. 2741 */ 2742 static void 2743 TREEVIEW_UpdateScrollBars(TREEVIEW_INFO *infoPtr) 2744 { 2745 TREEVIEW_ITEM *item; 2746 HWND hwnd = infoPtr->hwnd; 2747 BOOL vert = FALSE; 2748 BOOL horz = FALSE; 2749 SCROLLINFO si; 2750 LONG scrollX = infoPtr->scrollX; 2751 2752 infoPtr->treeWidth = 0; 2753 infoPtr->treeHeight = 0; 2754 2755 /* We iterate through all visible items in order to get the tree height 2756 * and width */ 2757 item = infoPtr->root->firstChild; 2758 2759 while (item != NULL) 2760 { 2761 if (ISVISIBLE(item)) 2762 { 2763 /* actually we draw text at textOffset + 2 */ 2764 if (2+item->textOffset+item->textWidth > infoPtr->treeWidth) 2765 infoPtr->treeWidth = item->textOffset+item->textWidth+2; 2766 2767 /* This is scroll-adjusted, but we fix this below. */ 2768 infoPtr->treeHeight = item->rect.bottom; 2769 } 2770 2771 item = TREEVIEW_GetNextListItem(infoPtr, item); 2772 } 2773 2774 /* Fix the scroll adjusted treeHeight and treeWidth. */ 2775 if (infoPtr->root->firstChild) 2776 infoPtr->treeHeight -= infoPtr->root->firstChild->rect.top; 2777 2778 infoPtr->treeWidth += infoPtr->scrollX; 2779 2780 if (infoPtr->dwStyle & TVS_NOSCROLL) return; 2781 2782 /* Adding one scroll bar may take up enough space that it forces us 2783 * to add the other as well. */ 2784 if (infoPtr->treeHeight > infoPtr->clientHeight) 2785 { 2786 vert = TRUE; 2787 2788 if (infoPtr->treeWidth 2789 > infoPtr->clientWidth - GetSystemMetrics(SM_CXVSCROLL)) 2790 horz = TRUE; 2791 } 2792 else if (infoPtr->treeWidth > infoPtr->clientWidth || infoPtr->scrollX > 0) 2793 horz = TRUE; 2794 2795 if (!vert && horz && infoPtr->treeHeight 2796 > infoPtr->clientHeight - GetSystemMetrics(SM_CYVSCROLL)) 2797 vert = TRUE; 2798 2799 if (horz && (infoPtr->dwStyle & TVS_NOHSCROLL)) horz = FALSE; 2800 2801 si.cbSize = sizeof(SCROLLINFO); 2802 si.fMask = SIF_POS|SIF_RANGE|SIF_PAGE; 2803 si.nMin = 0; 2804 2805 if (vert) 2806 { 2807 si.nPage = TREEVIEW_GetVisibleCount(infoPtr); 2808 if ( si.nPage && NULL != infoPtr->firstVisible) 2809 { 2810 si.nPos = infoPtr->firstVisible->visibleOrder; 2811 si.nMax = infoPtr->maxVisibleOrder - 1; 2812 2813 SetScrollInfo(hwnd, SB_VERT, &si, TRUE); 2814 2815 if (!(infoPtr->uInternalStatus & TV_VSCROLL)) 2816 ShowScrollBar(hwnd, SB_VERT, TRUE); 2817 infoPtr->uInternalStatus |= TV_VSCROLL; 2818 } 2819 else 2820 { 2821 if (infoPtr->uInternalStatus & TV_VSCROLL) 2822 ShowScrollBar(hwnd, SB_VERT, FALSE); 2823 infoPtr->uInternalStatus &= ~TV_VSCROLL; 2824 } 2825 } 2826 else 2827 { 2828 if (infoPtr->uInternalStatus & TV_VSCROLL) 2829 ShowScrollBar(hwnd, SB_VERT, FALSE); 2830 infoPtr->uInternalStatus &= ~TV_VSCROLL; 2831 } 2832 2833 if (horz) 2834 { 2835 si.nPage = infoPtr->clientWidth; 2836 si.nPos = infoPtr->scrollX; 2837 si.nMax = infoPtr->treeWidth - 1; 2838 2839 if (si.nPos > si.nMax - max( si.nPage-1, 0 )) 2840 { 2841 si.nPos = si.nMax - max( si.nPage-1, 0 ); 2842 scrollX = si.nPos; 2843 } 2844 2845 if (!(infoPtr->uInternalStatus & TV_HSCROLL)) 2846 ShowScrollBar(hwnd, SB_HORZ, TRUE); 2847 infoPtr->uInternalStatus |= TV_HSCROLL; 2848 2849 SetScrollInfo(hwnd, SB_HORZ, &si, TRUE); 2850 TREEVIEW_HScroll(infoPtr, 2851 MAKEWPARAM(SB_THUMBPOSITION, scrollX)); 2852 } 2853 else 2854 { 2855 if (infoPtr->uInternalStatus & TV_HSCROLL) 2856 ShowScrollBar(hwnd, SB_HORZ, FALSE); 2857 infoPtr->uInternalStatus &= ~TV_HSCROLL; 2858 2859 scrollX = 0; 2860 if (infoPtr->scrollX != 0) 2861 { 2862 TREEVIEW_HScroll(infoPtr, 2863 MAKEWPARAM(SB_THUMBPOSITION, scrollX)); 2864 } 2865 } 2866 2867 if (!horz) 2868 infoPtr->uInternalStatus &= ~TV_HSCROLL; 2869 } 2870 2871 static void 2872 TREEVIEW_FillBkgnd(const TREEVIEW_INFO *infoPtr, HDC hdc, const RECT *rc) 2873 { 2874 HBRUSH hBrush; 2875 COLORREF clrBk = GETBKCOLOR(infoPtr->clrBk); 2876 2877 hBrush = CreateSolidBrush(clrBk); 2878 FillRect(hdc, rc, hBrush); 2879 DeleteObject(hBrush); 2880 } 2881 2882 /* CtrlSpy doesn't mention this, but CorelDRAW's object manager needs it. */ 2883 static LRESULT 2884 TREEVIEW_EraseBackground(const TREEVIEW_INFO *infoPtr, HDC hdc) 2885 { 2886 RECT rect; 2887 2888 TRACE("%p\n", infoPtr); 2889 2890 GetClientRect(infoPtr->hwnd, &rect); 2891 TREEVIEW_FillBkgnd(infoPtr, hdc, &rect); 2892 2893 return 1; 2894 } 2895 2896 static void 2897 TREEVIEW_Refresh(TREEVIEW_INFO *infoPtr, HDC hdc, const RECT *rc) 2898 { 2899 HWND hwnd = infoPtr->hwnd; 2900 RECT rect = *rc; 2901 TREEVIEW_ITEM *item; 2902 2903 if (infoPtr->clientHeight == 0 || infoPtr->clientWidth == 0) 2904 { 2905 TRACE("empty window\n"); 2906 return; 2907 } 2908 2909 infoPtr->cdmode = TREEVIEW_SendCustomDrawNotify(infoPtr, CDDS_PREPAINT, 2910 hdc, rect); 2911 2912 if (infoPtr->cdmode == CDRF_SKIPDEFAULT) 2913 { 2914 ReleaseDC(hwnd, hdc); 2915 return; 2916 } 2917 2918 for (item = infoPtr->root->firstChild; 2919 item != NULL; 2920 item = TREEVIEW_GetNextListItem(infoPtr, item)) 2921 { 2922 if (ISVISIBLE(item)) 2923 { 2924 /* Avoid unneeded calculations */ 2925 if (item->rect.top > rect.bottom) 2926 break; 2927 if (item->rect.bottom < rect.top) 2928 continue; 2929 2930 TREEVIEW_DrawItem(infoPtr, hdc, item); 2931 } 2932 } 2933 2934 // 2935 // FIXME: This is correct, but is causes and infinite loop of WM_PAINT 2936 // messages, resulting in continuous painting of the scroll bar in reactos. 2937 // Comment out until the real bug is found. CORE-4912 2938 // 2939 #ifndef __REACTOS__ 2940 TREEVIEW_UpdateScrollBars(infoPtr); 2941 #endif 2942 2943 if (infoPtr->cdmode & CDRF_NOTIFYPOSTPAINT) 2944 infoPtr->cdmode = 2945 TREEVIEW_SendCustomDrawNotify(infoPtr, CDDS_POSTPAINT, hdc, rect); 2946 } 2947 2948 static inline void 2949 TREEVIEW_InvalidateItem(const TREEVIEW_INFO *infoPtr, const TREEVIEW_ITEM *item) 2950 { 2951 if (item) InvalidateRect(infoPtr->hwnd, &item->rect, TRUE); 2952 } 2953 2954 static void 2955 TREEVIEW_Invalidate(const TREEVIEW_INFO *infoPtr, const TREEVIEW_ITEM *item) 2956 { 2957 if (item) 2958 InvalidateRect(infoPtr->hwnd, &item->rect, TRUE); 2959 else 2960 InvalidateRect(infoPtr->hwnd, NULL, TRUE); 2961 } 2962 2963 static void 2964 TREEVIEW_InitCheckboxes(TREEVIEW_INFO *infoPtr) 2965 { 2966 RECT rc; 2967 HBITMAP hbm, hbmOld; 2968 HDC hdc, hdcScreen; 2969 int nIndex; 2970 2971 infoPtr->himlState = ImageList_Create(16, 16, ILC_COLOR | ILC_MASK, 3, 0); 2972 2973 hdcScreen = GetDC(0); 2974 2975 hdc = CreateCompatibleDC(hdcScreen); 2976 hbm = CreateCompatibleBitmap(hdcScreen, 48, 16); 2977 hbmOld = SelectObject(hdc, hbm); 2978 2979 SetRect(&rc, 0, 0, 48, 16); 2980 FillRect(hdc, &rc, (HBRUSH)(COLOR_WINDOW+1)); 2981 2982 SetRect(&rc, 18, 2, 30, 14); 2983 DrawFrameControl(hdc, &rc, DFC_BUTTON, 2984 DFCS_BUTTONCHECK|DFCS_FLAT); 2985 2986 SetRect(&rc, 34, 2, 46, 14); 2987 DrawFrameControl(hdc, &rc, DFC_BUTTON, 2988 DFCS_BUTTONCHECK|DFCS_FLAT|DFCS_CHECKED); 2989 2990 SelectObject(hdc, hbmOld); 2991 nIndex = ImageList_AddMasked(infoPtr->himlState, hbm, 2992 comctl32_color.clrWindow); 2993 TRACE("checkbox index %d\n", nIndex); 2994 2995 DeleteObject(hbm); 2996 DeleteDC(hdc); 2997 ReleaseDC(0, hdcScreen); 2998 2999 infoPtr->stateImageWidth = 16; 3000 infoPtr->stateImageHeight = 16; 3001 } 3002 3003 static void 3004 TREEVIEW_ResetImageStateIndex(TREEVIEW_INFO *infoPtr, TREEVIEW_ITEM *item) 3005 { 3006 TREEVIEW_ITEM *child = item->firstChild; 3007 3008 item->state &= ~TVIS_STATEIMAGEMASK; 3009 item->state |= INDEXTOSTATEIMAGEMASK(1); 3010 3011 while (child) 3012 { 3013 TREEVIEW_ITEM *next = child->nextSibling; 3014 TREEVIEW_ResetImageStateIndex(infoPtr, child); 3015 child = next; 3016 } 3017 } 3018 3019 static LRESULT 3020 TREEVIEW_Paint(TREEVIEW_INFO *infoPtr, HDC hdc_ref) 3021 { 3022 HDC hdc; 3023 PAINTSTRUCT ps; 3024 RECT rc; 3025 3026 TRACE("(%p %p)\n", infoPtr, hdc_ref); 3027 3028 if ((infoPtr->dwStyle & TVS_CHECKBOXES) && !infoPtr->himlState) 3029 { 3030 TREEVIEW_InitCheckboxes(infoPtr); 3031 TREEVIEW_ResetImageStateIndex(infoPtr, infoPtr->root); 3032 3033 TREEVIEW_EndEditLabelNow(infoPtr, TRUE); 3034 TREEVIEW_UpdateSubTree(infoPtr, infoPtr->root); 3035 TREEVIEW_UpdateScrollBars(infoPtr); 3036 TREEVIEW_Invalidate(infoPtr, NULL); 3037 } 3038 3039 if (hdc_ref) 3040 { 3041 hdc = hdc_ref; 3042 GetClientRect(infoPtr->hwnd, &rc); 3043 TREEVIEW_FillBkgnd(infoPtr, hdc, &rc); 3044 } 3045 else 3046 { 3047 hdc = BeginPaint(infoPtr->hwnd, &ps); 3048 rc = ps.rcPaint; 3049 if(ps.fErase) 3050 TREEVIEW_FillBkgnd(infoPtr, hdc, &rc); 3051 } 3052 3053 if(infoPtr->bRedraw) /* WM_SETREDRAW sets bRedraw */ 3054 TREEVIEW_Refresh(infoPtr, hdc, &rc); 3055 3056 if (!hdc_ref) 3057 EndPaint(infoPtr->hwnd, &ps); 3058 3059 return 0; 3060 } 3061 3062 static LRESULT 3063 TREEVIEW_PrintClient(TREEVIEW_INFO *infoPtr, HDC hdc, DWORD options) 3064 { 3065 FIXME("Partial Stub: (hdc=%p options=0x%08x)\n", hdc, options); 3066 3067 if ((options & PRF_CHECKVISIBLE) && !IsWindowVisible(infoPtr->hwnd)) 3068 return 0; 3069 3070 if (options & PRF_ERASEBKGND) 3071 TREEVIEW_EraseBackground(infoPtr, hdc); 3072 3073 if (options & PRF_CLIENT) 3074 { 3075 RECT rc; 3076 GetClientRect(infoPtr->hwnd, &rc); 3077 TREEVIEW_Refresh(infoPtr, hdc, &rc); 3078 } 3079 3080 return 0; 3081 } 3082 3083 /* Sorting **************************************************************/ 3084 3085 /*************************************************************************** 3086 * Forward the DPA local callback to the treeview owner callback 3087 */ 3088 static INT WINAPI 3089 TREEVIEW_CallBackCompare(const TREEVIEW_ITEM *first, const TREEVIEW_ITEM *second, 3090 const TVSORTCB *pCallBackSort) 3091 { 3092 /* Forward the call to the client-defined callback */ 3093 return pCallBackSort->lpfnCompare(first->lParam, 3094 second->lParam, 3095 pCallBackSort->lParam); 3096 } 3097 3098 /*************************************************************************** 3099 * Treeview native sort routine: sort on item text. 3100 */ 3101 static INT WINAPI 3102 TREEVIEW_SortOnName(TREEVIEW_ITEM *first, TREEVIEW_ITEM *second, 3103 const TREEVIEW_INFO *infoPtr) 3104 { 3105 TREEVIEW_UpdateDispInfo(infoPtr, first, TVIF_TEXT); 3106 TREEVIEW_UpdateDispInfo(infoPtr, second, TVIF_TEXT); 3107 3108 if(first->pszText && second->pszText) 3109 return lstrcmpiW(first->pszText, second->pszText); 3110 else if(first->pszText) 3111 return -1; 3112 else if(second->pszText) 3113 return 1; 3114 else 3115 return 0; 3116 } 3117 3118 /* Returns the number of physical children belonging to item. */ 3119 static INT 3120 TREEVIEW_CountChildren(const TREEVIEW_ITEM *item) 3121 { 3122 INT cChildren = 0; 3123 HTREEITEM hti; 3124 3125 for (hti = item->firstChild; hti != NULL; hti = hti->nextSibling) 3126 cChildren++; 3127 3128 return cChildren; 3129 } 3130 3131 /* Returns a DPA containing a pointer to each physical child of item in 3132 * sibling order. If item has no children, an empty DPA is returned. */ 3133 static HDPA 3134 TREEVIEW_BuildChildDPA(const TREEVIEW_ITEM *item) 3135 { 3136 HTREEITEM child; 3137 3138 HDPA list = DPA_Create(8); 3139 if (list == 0) return NULL; 3140 3141 for (child = item->firstChild; child != NULL; child = child->nextSibling) 3142 { 3143 if (DPA_InsertPtr(list, INT_MAX, child) == -1) 3144 { 3145 DPA_Destroy(list); 3146 return NULL; 3147 } 3148 } 3149 3150 return list; 3151 } 3152 3153 /*************************************************************************** 3154 * Setup the treeview structure with regards of the sort method 3155 * and sort the children of the TV item specified in lParam 3156 * fRecurse: currently unused. Should be zero. 3157 * parent: if pSort!=NULL, should equal pSort->hParent. 3158 * otherwise, item which child items are to be sorted. 3159 * pSort: sort method info. if NULL, sort on item text. 3160 * if non-NULL, sort on item's lParam content, and let the 3161 * application decide what that means. See also TVM_SORTCHILDRENCB. 3162 */ 3163 3164 static LRESULT 3165 TREEVIEW_Sort(TREEVIEW_INFO *infoPtr, HTREEITEM parent, 3166 LPTVSORTCB pSort) 3167 { 3168 INT cChildren; 3169 PFNDPACOMPARE pfnCompare; 3170 LPARAM lpCompare; 3171 3172 /* undocumented feature: TVI_ROOT or NULL means `sort the whole tree' */ 3173 if (parent == TVI_ROOT || parent == NULL) 3174 parent = infoPtr->root; 3175 3176 /* Check for a valid handle to the parent item */ 3177 if (!TREEVIEW_ValidItem(infoPtr, parent)) 3178 { 3179 ERR("invalid item hParent=%p\n", parent); 3180 return FALSE; 3181 } 3182 3183 if (pSort) 3184 { 3185 pfnCompare = (PFNDPACOMPARE)TREEVIEW_CallBackCompare; 3186 lpCompare = (LPARAM)pSort; 3187 } 3188 else 3189 { 3190 pfnCompare = (PFNDPACOMPARE)TREEVIEW_SortOnName; 3191 lpCompare = (LPARAM)infoPtr; 3192 } 3193 3194 cChildren = TREEVIEW_CountChildren(parent); 3195 3196 /* Make sure there is something to sort */ 3197 if (cChildren > 1) 3198 { 3199 /* TREEVIEW_ITEM rechaining */ 3200 INT count = 0; 3201 HTREEITEM item = 0; 3202 HTREEITEM nextItem = 0; 3203 HTREEITEM prevItem = 0; 3204 3205 HDPA sortList = TREEVIEW_BuildChildDPA(parent); 3206 3207 if (sortList == NULL) 3208 return FALSE; 3209 3210 /* let DPA sort the list */ 3211 DPA_Sort(sortList, pfnCompare, lpCompare); 3212 3213 /* The order of DPA entries has been changed, so fixup the 3214 * nextSibling and prevSibling pointers. */ 3215 3216 item = DPA_GetPtr(sortList, count++); 3217 while ((nextItem = DPA_GetPtr(sortList, count++)) != NULL) 3218 { 3219 /* link the two current item together */ 3220 item->nextSibling = nextItem; 3221 nextItem->prevSibling = item; 3222 3223 if (prevItem == NULL) 3224 { 3225 /* this is the first item, update the parent */ 3226 parent->firstChild = item; 3227 item->prevSibling = NULL; 3228 } 3229 else 3230 { 3231 /* fix the back chaining */ 3232 item->prevSibling = prevItem; 3233 } 3234 3235 /* get ready for the next one */ 3236 prevItem = item; 3237 item = nextItem; 3238 } 3239 3240 /* the last item is pointed to by item and never has a sibling */ 3241 item->nextSibling = NULL; 3242 parent->lastChild = item; 3243 3244 DPA_Destroy(sortList); 3245 3246 TREEVIEW_VerifyTree(infoPtr); 3247 3248 if (parent->state & TVIS_EXPANDED) 3249 { 3250 int visOrder = infoPtr->firstVisible->visibleOrder; 3251 3252 if (parent == infoPtr->root) 3253 TREEVIEW_RecalculateVisibleOrder(infoPtr, NULL); 3254 else 3255 TREEVIEW_RecalculateVisibleOrder(infoPtr, parent); 3256 3257 if (TREEVIEW_IsChildOf(parent, infoPtr->firstVisible)) 3258 { 3259 TREEVIEW_ITEM *item; 3260 3261 for (item = infoPtr->root->firstChild; item != NULL; 3262 item = TREEVIEW_GetNextListItem(infoPtr, item)) 3263 { 3264 if (item->visibleOrder == visOrder) 3265 break; 3266 } 3267 3268 if (!item) item = parent->firstChild; 3269 TREEVIEW_SetFirstVisible(infoPtr, item, FALSE); 3270 } 3271 3272 TREEVIEW_Invalidate(infoPtr, NULL); 3273 } 3274 3275 return TRUE; 3276 } 3277 return FALSE; 3278 } 3279 3280 3281 /*************************************************************************** 3282 * Setup the treeview structure with regards of the sort method 3283 * and sort the children of the TV item specified in lParam 3284 */ 3285 static LRESULT 3286 TREEVIEW_SortChildrenCB(TREEVIEW_INFO *infoPtr, LPTVSORTCB pSort) 3287 { 3288 return TREEVIEW_Sort(infoPtr, pSort->hParent, pSort); 3289 } 3290 3291 3292 /*************************************************************************** 3293 * Sort the children of the TV item specified in lParam. 3294 */ 3295 static LRESULT 3296 TREEVIEW_SortChildren(TREEVIEW_INFO *infoPtr, LPARAM lParam) 3297 { 3298 return TREEVIEW_Sort(infoPtr, (HTREEITEM)lParam, NULL); 3299 } 3300 3301 3302 /* Expansion/Collapse ***************************************************/ 3303 3304 static BOOL 3305 TREEVIEW_SendExpanding(const TREEVIEW_INFO *infoPtr, TREEVIEW_ITEM *item, 3306 UINT action) 3307 { 3308 return !TREEVIEW_SendTreeviewNotify(infoPtr, TVN_ITEMEXPANDINGW, action, 3309 TVIF_HANDLE | TVIF_STATE | TVIF_PARAM 3310 | TVIF_IMAGE | TVIF_SELECTEDIMAGE, 3311 0, item); 3312 } 3313 3314 static VOID 3315 TREEVIEW_SendExpanded(const TREEVIEW_INFO *infoPtr, TREEVIEW_ITEM *item, 3316 UINT action) 3317 { 3318 TREEVIEW_SendTreeviewNotify(infoPtr, TVN_ITEMEXPANDEDW, action, 3319 TVIF_HANDLE | TVIF_STATE | TVIF_PARAM 3320 | TVIF_IMAGE | TVIF_SELECTEDIMAGE, 3321 0, item); 3322 } 3323 3324 3325 /* This corresponds to TVM_EXPAND with TVE_COLLAPSE. 3326 * bRemoveChildren corresponds to TVE_COLLAPSERESET. */ 3327 static BOOL 3328 TREEVIEW_Collapse(TREEVIEW_INFO *infoPtr, TREEVIEW_ITEM *item, 3329 BOOL bRemoveChildren, BOOL bUser) 3330 { 3331 UINT action = TVE_COLLAPSE | (bRemoveChildren ? TVE_COLLAPSERESET : 0); 3332 BOOL bSetSelection, bSetFirstVisible; 3333 RECT scrollRect; 3334 LONG scrollDist = 0; 3335 TREEVIEW_ITEM *nextItem = NULL, *tmpItem; 3336 BOOL wasExpanded; 3337 3338 TRACE("TVE_COLLAPSE %p %s\n", item, TREEVIEW_ItemName(item)); 3339 3340 if (!TREEVIEW_HasChildren(infoPtr, item)) 3341 return FALSE; 3342 3343 if (bUser) 3344 TREEVIEW_SendExpanding(infoPtr, item, action); 3345 3346 if (item->firstChild == NULL) 3347 return FALSE; 3348 3349 wasExpanded = (item->state & TVIS_EXPANDED) != 0; 3350 item->state &= ~TVIS_EXPANDED; 3351 3352 if (wasExpanded && bUser) 3353 TREEVIEW_SendExpanded(infoPtr, item, action); 3354 3355 bSetSelection = (infoPtr->selectedItem != NULL 3356 && TREEVIEW_IsChildOf(item, infoPtr->selectedItem)); 3357 3358 bSetFirstVisible = (infoPtr->firstVisible != NULL 3359 && TREEVIEW_IsChildOf(item, infoPtr->firstVisible)); 3360 3361 tmpItem = item; 3362 while (tmpItem) 3363 { 3364 if (tmpItem->nextSibling) 3365 { 3366 nextItem = tmpItem->nextSibling; 3367 break; 3368 } 3369 tmpItem = tmpItem->parent; 3370 } 3371 3372 if (nextItem) 3373 scrollDist = nextItem->rect.top; 3374 3375 if (bRemoveChildren) 3376 { 3377 INT old_cChildren = item->cChildren; 3378 TRACE("TVE_COLLAPSERESET\n"); 3379 item->state &= ~TVIS_EXPANDEDONCE; 3380 TREEVIEW_RemoveAllChildren(infoPtr, item); 3381 item->cChildren = old_cChildren; 3382 } 3383 if (!wasExpanded) 3384 return FALSE; 3385 3386 if (item->firstChild) 3387 { 3388 TREEVIEW_ITEM *i, *sibling; 3389 3390 sibling = TREEVIEW_GetNextListItem(infoPtr, item); 3391 3392 for (i = item->firstChild; i != sibling; 3393 i = TREEVIEW_GetNextListItem(infoPtr, i)) 3394 { 3395 i->visibleOrder = -1; 3396 } 3397 } 3398 3399 TREEVIEW_RecalculateVisibleOrder(infoPtr, item); 3400 3401 if (nextItem) 3402 scrollDist = -(scrollDist - nextItem->rect.top); 3403 3404 if (bSetSelection) 3405 { 3406 /* Don't call DoSelectItem, it sends notifications. */ 3407 if (TREEVIEW_ValidItem(infoPtr, infoPtr->selectedItem)) 3408 infoPtr->selectedItem->state &= ~TVIS_SELECTED; 3409 item->state |= TVIS_SELECTED; 3410 infoPtr->selectedItem = item; 3411 } 3412 3413 TREEVIEW_UpdateScrollBars(infoPtr); 3414 3415 scrollRect.left = 0; 3416 scrollRect.right = infoPtr->clientWidth; 3417 scrollRect.bottom = infoPtr->clientHeight; 3418 3419 if (nextItem) 3420 { 3421 scrollRect.top = nextItem->rect.top; 3422 3423 ScrollWindowEx (infoPtr->hwnd, 0, scrollDist, &scrollRect, &scrollRect, 3424 NULL, NULL, SW_ERASE | SW_INVALIDATE); 3425 TREEVIEW_Invalidate(infoPtr, item); 3426 } else { 3427 scrollRect.top = item->rect.top; 3428 InvalidateRect(infoPtr->hwnd, &scrollRect, TRUE); 3429 } 3430 3431 TREEVIEW_SetFirstVisible(infoPtr, 3432 bSetFirstVisible ? item : infoPtr->firstVisible, 3433 TRUE); 3434 3435 return wasExpanded; 3436 } 3437 3438 static BOOL 3439 TREEVIEW_Expand(TREEVIEW_INFO *infoPtr, TREEVIEW_ITEM *item, 3440 BOOL partial, BOOL user) 3441 { 3442 LONG scrollDist; 3443 LONG orgNextTop = 0; 3444 RECT scrollRect; 3445 TREEVIEW_ITEM *nextItem, *tmpItem; 3446 BOOL sendsNotifications; 3447 3448 TRACE("(%p, %p, partial=%d, %d)\n", infoPtr, item, partial, user); 3449 3450 if (!TREEVIEW_HasChildren(infoPtr, item)) 3451 return FALSE; 3452 3453 tmpItem = item; nextItem = NULL; 3454 while (tmpItem) 3455 { 3456 if (tmpItem->nextSibling) 3457 { 3458 nextItem = tmpItem->nextSibling; 3459 break; 3460 } 3461 tmpItem = tmpItem->parent; 3462 } 3463 3464 if (nextItem) 3465 orgNextTop = nextItem->rect.top; 3466 3467 TRACE("TVE_EXPAND %p %s\n", item, TREEVIEW_ItemName(item)); 3468 3469 sendsNotifications = user || ((item->cChildren != 0) && 3470 !(item->state & TVIS_EXPANDEDONCE)); 3471 if (sendsNotifications) 3472 { 3473 if (!TREEVIEW_SendExpanding(infoPtr, item, TVE_EXPAND)) 3474 { 3475 TRACE(" TVN_ITEMEXPANDING returned TRUE, exiting...\n"); 3476 return FALSE; 3477 } 3478 } 3479 if (!item->firstChild) 3480 return FALSE; 3481 3482 item->state |= TVIS_EXPANDED; 3483 3484 if (partial) 3485 FIXME("TVE_EXPANDPARTIAL not implemented\n"); 3486 3487 if (ISVISIBLE(item)) 3488 { 3489 TREEVIEW_RecalculateVisibleOrder(infoPtr, item); 3490 TREEVIEW_UpdateSubTree(infoPtr, item); 3491 TREEVIEW_UpdateScrollBars(infoPtr); 3492 3493 scrollRect.left = 0; 3494 scrollRect.bottom = infoPtr->treeHeight; 3495 scrollRect.right = infoPtr->clientWidth; 3496 if (nextItem) 3497 { 3498 scrollDist = nextItem->rect.top - orgNextTop; 3499 scrollRect.top = orgNextTop; 3500 3501 ScrollWindowEx (infoPtr->hwnd, 0, scrollDist, &scrollRect, NULL, 3502 NULL, NULL, SW_ERASE | SW_INVALIDATE); 3503 TREEVIEW_Invalidate (infoPtr, item); 3504 } else { 3505 scrollRect.top = item->rect.top; 3506 InvalidateRect(infoPtr->hwnd, &scrollRect, FALSE); 3507 } 3508 3509 /* Scroll up so that as many children as possible are visible. 3510 * This fails when expanding causes an HScroll bar to appear, but we 3511 * don't know that yet, so the last item is obscured. */ 3512 if (item->firstChild != NULL) 3513 { 3514 int nChildren = item->lastChild->visibleOrder 3515 - item->firstChild->visibleOrder + 1; 3516 3517 int visible_pos = item->visibleOrder 3518 - infoPtr->firstVisible->visibleOrder; 3519 3520 int rows_below = TREEVIEW_GetVisibleCount(infoPtr) - visible_pos - 1; 3521 3522 if (visible_pos > 0 && nChildren > rows_below) 3523 { 3524 int scroll = nChildren - rows_below; 3525 3526 if (scroll > visible_pos) 3527 scroll = visible_pos; 3528 3529 if (scroll > 0) 3530 { 3531 TREEVIEW_ITEM *newFirstVisible 3532 = TREEVIEW_GetListItem(infoPtr, infoPtr->firstVisible, 3533 scroll); 3534 3535 3536 TREEVIEW_SetFirstVisible(infoPtr, newFirstVisible, TRUE); 3537 } 3538 } 3539 } 3540 } 3541 3542 if (sendsNotifications) { 3543 TREEVIEW_SendExpanded(infoPtr, item, TVE_EXPAND); 3544 item->state |= TVIS_EXPANDEDONCE; 3545 } 3546 3547 return TRUE; 3548 } 3549 3550 /* Handler for TVS_SINGLEEXPAND behaviour. Used on response 3551 to mouse messages and TVM_SELECTITEM. 3552 3553 selection - previously selected item, used to collapse a part of a tree 3554 item - new selected item 3555 */ 3556 static void TREEVIEW_SingleExpand(TREEVIEW_INFO *infoPtr, 3557 HTREEITEM selection, HTREEITEM item) 3558 { 3559 TREEVIEW_ITEM *prev, *curr; 3560 3561 if ((infoPtr->dwStyle & TVS_SINGLEEXPAND) == 0 || infoPtr->hwndEdit || !item) return; 3562 3563 TREEVIEW_SendTreeviewNotify(infoPtr, TVN_SINGLEEXPAND, TVC_UNKNOWN, TVIF_HANDLE | TVIF_PARAM, item, 0); 3564 3565 /* 3566 * Close the previous item and its ancestors as long as they are not 3567 * ancestors of the current item 3568 */ 3569 for (prev = selection; prev && TREEVIEW_ValidItem(infoPtr, prev); prev = prev->parent) 3570 { 3571 for (curr = item; curr && TREEVIEW_ValidItem(infoPtr, curr); curr = curr->parent) 3572 { 3573 if (curr == prev) 3574 goto finish; 3575 } 3576 TREEVIEW_Collapse(infoPtr, prev, FALSE, TRUE); 3577 } 3578 3579 finish: 3580 /* 3581 * Expand the current item 3582 */ 3583 TREEVIEW_Expand(infoPtr, item, FALSE, TRUE); 3584 } 3585 3586 static BOOL 3587 TREEVIEW_Toggle(TREEVIEW_INFO *infoPtr, TREEVIEW_ITEM *item, BOOL user) 3588 { 3589 TRACE("item=%p, user=%d\n", item, user); 3590 3591 if (item->state & TVIS_EXPANDED) 3592 return TREEVIEW_Collapse(infoPtr, item, FALSE, user); 3593 else 3594 return TREEVIEW_Expand(infoPtr, item, FALSE, user); 3595 } 3596 3597 static VOID 3598 TREEVIEW_ExpandAll(TREEVIEW_INFO *infoPtr, TREEVIEW_ITEM *item) 3599 { 3600 TREEVIEW_Expand(infoPtr, item, FALSE, TRUE); 3601 3602 for (item = item->firstChild; item != NULL; item = item->nextSibling) 3603 { 3604 if (TREEVIEW_HasChildren(infoPtr, item)) 3605 TREEVIEW_ExpandAll(infoPtr, item); 3606 } 3607 } 3608 3609 /* Note:If the specified item is the child of a collapsed parent item, 3610 the parent's list of child items is (recursively) expanded to reveal the 3611 specified item. This is mentioned for TREEVIEW_SelectItem; don't 3612 know if it also applies here. 3613 */ 3614 3615 static LRESULT 3616 TREEVIEW_ExpandMsg(TREEVIEW_INFO *infoPtr, UINT flag, HTREEITEM item) 3617 { 3618 if (!TREEVIEW_ValidItem(infoPtr, item)) 3619 return 0; 3620 3621 TRACE("For (%s) item:%d, flags 0x%x, state:%d\n", 3622 TREEVIEW_ItemName(item), TREEVIEW_GetItemIndex(infoPtr, item), 3623 flag, item->state); 3624 3625 switch (flag & TVE_TOGGLE) 3626 { 3627 case TVE_COLLAPSE: 3628 return TREEVIEW_Collapse(infoPtr, item, flag & TVE_COLLAPSERESET, 3629 FALSE); 3630 3631 case TVE_EXPAND: 3632 return TREEVIEW_Expand(infoPtr, item, flag & TVE_EXPANDPARTIAL, 3633 FALSE); 3634 3635 case TVE_TOGGLE: 3636 return TREEVIEW_Toggle(infoPtr, item, FALSE); 3637 3638 default: 3639 return 0; 3640 } 3641 } 3642 3643 /* Hit-Testing **********************************************************/ 3644 3645 static TREEVIEW_ITEM * 3646 TREEVIEW_HitTestPoint(const TREEVIEW_INFO *infoPtr, POINT pt) 3647 { 3648 TREEVIEW_ITEM *item; 3649 LONG row; 3650 3651 if (!infoPtr->firstVisible) 3652 return NULL; 3653 3654 row = pt.y / infoPtr->uItemHeight + infoPtr->firstVisible->visibleOrder; 3655 3656 for (item = infoPtr->firstVisible; item != NULL; 3657 item = TREEVIEW_GetNextListItem(infoPtr, item)) 3658 { 3659 if (row >= item->visibleOrder 3660 && row < item->visibleOrder + item->iIntegral) 3661 break; 3662 } 3663 3664 return item; 3665 } 3666 3667 static TREEVIEW_ITEM * 3668 TREEVIEW_HitTest(const TREEVIEW_INFO *infoPtr, LPTVHITTESTINFO lpht) 3669 { 3670 TREEVIEW_ITEM *item; 3671 RECT rect; 3672 UINT status; 3673 LONG x, y; 3674 3675 lpht->hItem = 0; 3676 GetClientRect(infoPtr->hwnd, &rect); 3677 status = 0; 3678 x = lpht->pt.x; 3679 y = lpht->pt.y; 3680 3681 if (x < rect.left) 3682 { 3683 status |= TVHT_TOLEFT; 3684 } 3685 else if (x > rect.right) 3686 { 3687 status |= TVHT_TORIGHT; 3688 } 3689 3690 if (y < rect.top) 3691 { 3692 status |= TVHT_ABOVE; 3693 } 3694 else if (y > rect.bottom) 3695 { 3696 status |= TVHT_BELOW; 3697 } 3698 3699 if (status) 3700 { 3701 lpht->flags = status; 3702 return NULL; 3703 } 3704 3705 item = TREEVIEW_HitTestPoint(infoPtr, lpht->pt); 3706 if (!item) 3707 { 3708 lpht->flags = TVHT_NOWHERE; 3709 return NULL; 3710 } 3711 3712 if (!item->textWidth) 3713 TREEVIEW_ComputeTextWidth(infoPtr, item, 0); 3714 3715 if (x >= item->textOffset + item->textWidth) 3716 { 3717 lpht->flags = TVHT_ONITEMRIGHT; 3718 } 3719 else if (x >= item->textOffset) 3720 { 3721 lpht->flags = TVHT_ONITEMLABEL; 3722 } 3723 else if (x >= item->imageOffset) 3724 { 3725 lpht->flags = TVHT_ONITEMICON; 3726 } 3727 else if (x >= item->stateOffset) 3728 { 3729 lpht->flags = TVHT_ONITEMSTATEICON; 3730 } 3731 else if (x >= item->linesOffset && infoPtr->dwStyle & TVS_HASBUTTONS) 3732 { 3733 lpht->flags = TVHT_ONITEMBUTTON; 3734 } 3735 else 3736 { 3737 lpht->flags = TVHT_ONITEMINDENT; 3738 } 3739 3740 lpht->hItem = item; 3741 TRACE("(%d,%d):result 0x%x\n", lpht->pt.x, lpht->pt.y, lpht->flags); 3742 3743 return item; 3744 } 3745 3746 /* Item Label Editing ***************************************************/ 3747 3748 static LRESULT 3749 TREEVIEW_GetEditControl(const TREEVIEW_INFO *infoPtr) 3750 { 3751 return (LRESULT)infoPtr->hwndEdit; 3752 } 3753 3754 static LRESULT CALLBACK 3755 TREEVIEW_Edit_SubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) 3756 { 3757 TREEVIEW_INFO *infoPtr = TREEVIEW_GetInfoPtr(GetParent(hwnd)); 3758 BOOL bCancel = FALSE; 3759 LRESULT rc; 3760 3761 switch (uMsg) 3762 { 3763 case WM_PAINT: 3764 TRACE("WM_PAINT start\n"); 3765 rc = CallWindowProcW(infoPtr->wpEditOrig, hwnd, uMsg, wParam, 3766 lParam); 3767 TRACE("WM_PAINT done\n"); 3768 return rc; 3769 3770 case WM_KILLFOCUS: 3771 if (infoPtr->bIgnoreEditKillFocus) 3772 return TRUE; 3773 break; 3774 3775 case WM_DESTROY: 3776 { 3777 WNDPROC editProc = infoPtr->wpEditOrig; 3778 infoPtr->wpEditOrig = 0; 3779 SetWindowLongPtrW(hwnd, GWLP_WNDPROC, (DWORD_PTR)editProc); 3780 return CallWindowProcW(editProc, hwnd, uMsg, wParam, lParam); 3781 } 3782 3783 case WM_GETDLGCODE: 3784 return DLGC_WANTARROWS | DLGC_WANTALLKEYS; 3785 3786 case WM_KEYDOWN: 3787 if (wParam == VK_ESCAPE) 3788 { 3789 bCancel = TRUE; 3790 break; 3791 } 3792 else if (wParam == VK_RETURN) 3793 { 3794 break; 3795 } 3796 3797 /* fall through */ 3798 default: 3799 return CallWindowProcW(infoPtr->wpEditOrig, hwnd, uMsg, wParam, lParam); 3800 } 3801 3802 /* Processing TVN_ENDLABELEDIT message could kill the focus */ 3803 /* eg. Using a messagebox */ 3804 3805 infoPtr->bIgnoreEditKillFocus = TRUE; 3806 TREEVIEW_EndEditLabelNow(infoPtr, bCancel || !infoPtr->bLabelChanged); 3807 infoPtr->bIgnoreEditKillFocus = FALSE; 3808 3809 return 0; 3810 } 3811 3812 3813 /* should handle edit control messages here */ 3814 3815 static LRESULT 3816 TREEVIEW_Command(TREEVIEW_INFO *infoPtr, WPARAM wParam, LPARAM lParam) 3817 { 3818 TRACE("code=0x%x, id=0x%x, handle=0x%lx\n", HIWORD(wParam), LOWORD(wParam), lParam); 3819 3820 switch (HIWORD(wParam)) 3821 { 3822 case EN_UPDATE: 3823 { 3824 /* 3825 * Adjust the edit window size 3826 */ 3827 WCHAR buffer[1024]; 3828 TREEVIEW_ITEM *editItem = infoPtr->editItem; 3829 HDC hdc = GetDC(infoPtr->hwndEdit); 3830 SIZE sz; 3831 HFONT hFont, hOldFont = 0; 3832 3833 TRACE("edit=%p\n", infoPtr->hwndEdit); 3834 3835 if (!IsWindow(infoPtr->hwndEdit) || !hdc) return FALSE; 3836 3837 infoPtr->bLabelChanged = TRUE; 3838 3839 GetWindowTextW(infoPtr->hwndEdit, buffer, ARRAY_SIZE(buffer)); 3840 3841 /* Select font to get the right dimension of the string */ 3842 hFont = (HFONT)SendMessageW(infoPtr->hwndEdit, WM_GETFONT, 0, 0); 3843 3844 if (hFont != 0) 3845 { 3846 hOldFont = SelectObject(hdc, hFont); 3847 } 3848 3849 if (GetTextExtentPoint32W(hdc, buffer, strlenW(buffer), &sz)) 3850 { 3851 TEXTMETRICW textMetric; 3852 3853 /* Add Extra spacing for the next character */ 3854 GetTextMetricsW(hdc, &textMetric); 3855 sz.cx += (textMetric.tmMaxCharWidth * 2); 3856 3857 sz.cx = max(sz.cx, textMetric.tmMaxCharWidth * 3); 3858 sz.cx = min(sz.cx, 3859 infoPtr->clientWidth - editItem->textOffset + 2); 3860 3861 SetWindowPos(infoPtr->hwndEdit, 3862 HWND_TOP, 3863 0, 3864 0, 3865 sz.cx, 3866 editItem->rect.bottom - editItem->rect.top + 3, 3867 SWP_NOMOVE | SWP_DRAWFRAME); 3868 } 3869 3870 if (hFont != 0) 3871 { 3872 SelectObject(hdc, hOldFont); 3873 } 3874 3875 ReleaseDC(infoPtr->hwnd, hdc); 3876 break; 3877 } 3878 case EN_KILLFOCUS: 3879 /* apparently we should respect passed handle value */ 3880 if (infoPtr->hwndEdit != (HWND)lParam) return FALSE; 3881 3882 TREEVIEW_EndEditLabelNow(infoPtr, FALSE); 3883 break; 3884 3885 default: 3886 return SendMessageW(infoPtr->hwndNotify, WM_COMMAND, wParam, lParam); 3887 } 3888 3889 return 0; 3890 } 3891 3892 static HWND 3893 TREEVIEW_EditLabel(TREEVIEW_INFO *infoPtr, HTREEITEM hItem) 3894 { 3895 HWND hwnd = infoPtr->hwnd; 3896 HWND hwndEdit; 3897 SIZE sz; 3898 HINSTANCE hinst = (HINSTANCE)GetWindowLongPtrW(hwnd, GWLP_HINSTANCE); 3899 HDC hdc; 3900 HFONT hOldFont=0; 3901 TEXTMETRICW textMetric; 3902 3903 TRACE("%p %p\n", hwnd, hItem); 3904 if (!(infoPtr->dwStyle & TVS_EDITLABELS)) 3905 return NULL; 3906 3907 if (!TREEVIEW_ValidItem(infoPtr, hItem)) 3908 return NULL; 3909 3910 if (infoPtr->hwndEdit) 3911 return infoPtr->hwndEdit; 3912 3913 infoPtr->bLabelChanged = FALSE; 3914 3915 /* make edit item visible */ 3916 TREEVIEW_EnsureVisible(infoPtr, hItem, TRUE); 3917 3918 TREEVIEW_UpdateDispInfo(infoPtr, hItem, TVIF_TEXT); 3919 3920 hdc = GetDC(hwnd); 3921 /* Select the font to get appropriate metric dimensions */ 3922 if (infoPtr->hFont != 0) 3923 { 3924 hOldFont = SelectObject(hdc, infoPtr->hFont); 3925 } 3926 3927 /* Get string length in pixels */ 3928 if (hItem->pszText) 3929 GetTextExtentPoint32W(hdc, hItem->pszText, strlenW(hItem->pszText), 3930 &sz); 3931 else 3932 GetTextExtentPoint32A(hdc, "", 0, &sz); 3933 3934 /* Add Extra spacing for the next character */ 3935 GetTextMetricsW(hdc, &textMetric); 3936 sz.cx += (textMetric.tmMaxCharWidth * 2); 3937 3938 sz.cx = max(sz.cx, textMetric.tmMaxCharWidth * 3); 3939 sz.cx = min(sz.cx, infoPtr->clientWidth - hItem->textOffset + 2); 3940 3941 if (infoPtr->hFont != 0) 3942 { 3943 SelectObject(hdc, hOldFont); 3944 } 3945 3946 ReleaseDC(hwnd, hdc); 3947 3948 infoPtr->editItem = hItem; 3949 3950 hwndEdit = CreateWindowExW(WS_EX_LEFT, 3951 WC_EDITW, 3952 0, 3953 WS_CHILD | WS_BORDER | ES_AUTOHSCROLL | 3954 WS_CLIPSIBLINGS | ES_WANTRETURN | 3955 ES_LEFT, hItem->textOffset - 2, 3956 hItem->rect.top - 1, sz.cx + 3, 3957 hItem->rect.bottom - 3958 hItem->rect.top + 3, hwnd, 0, hinst, 0); 3959 /* FIXME: (HMENU)IDTVEDIT,pcs->hInstance,0); */ 3960 3961 infoPtr->hwndEdit = hwndEdit; 3962 3963 /* Get a 2D border. */ 3964 SetWindowLongW(hwndEdit, GWL_EXSTYLE, 3965 GetWindowLongW(hwndEdit, GWL_EXSTYLE) & ~WS_EX_CLIENTEDGE); 3966 SetWindowLongW(hwndEdit, GWL_STYLE, 3967 GetWindowLongW(hwndEdit, GWL_STYLE) | WS_BORDER); 3968 3969 SendMessageW(hwndEdit, WM_SETFONT, 3970 (WPARAM)TREEVIEW_FontForItem(infoPtr, hItem), FALSE); 3971 3972 infoPtr->wpEditOrig = (WNDPROC)SetWindowLongPtrW(hwndEdit, GWLP_WNDPROC, 3973 (DWORD_PTR) 3974 TREEVIEW_Edit_SubclassProc); 3975 if (hItem->pszText) 3976 SetWindowTextW(hwndEdit, hItem->pszText); 3977 3978 if (TREEVIEW_BeginLabelEditNotify(infoPtr, hItem)) 3979 { 3980 DestroyWindow(hwndEdit); 3981 infoPtr->hwndEdit = 0; 3982 infoPtr->editItem = NULL; 3983 return NULL; 3984 } 3985 3986 SetFocus(hwndEdit); 3987 SendMessageW(hwndEdit, EM_SETSEL, 0, -1); 3988 ShowWindow(hwndEdit, SW_SHOW); 3989 3990 return hwndEdit; 3991 } 3992 3993 3994 static LRESULT 3995 TREEVIEW_EndEditLabelNow(TREEVIEW_INFO *infoPtr, BOOL bCancel) 3996 { 3997 TREEVIEW_ITEM *editedItem = infoPtr->editItem; 3998 NMTVDISPINFOW tvdi; 3999 BOOL bCommit; 4000 WCHAR tmpText[1024] = { '\0' }; 4001 WCHAR *newText = tmpText; 4002 int iLength = 0; 4003 4004 if (!IsWindow(infoPtr->hwndEdit)) return FALSE; 4005 4006 tvdi.item.mask = 0; 4007 tvdi.item.hItem = editedItem; 4008 tvdi.item.state = editedItem->state; 4009 tvdi.item.lParam = editedItem->lParam; 4010 4011 if (!bCancel) 4012 { 4013 if (!infoPtr->bNtfUnicode) 4014 iLength = GetWindowTextA(infoPtr->hwndEdit, (LPSTR)tmpText, 1023); 4015 else 4016 iLength = GetWindowTextW(infoPtr->hwndEdit, tmpText, 1023); 4017 4018 if (iLength >= 1023) 4019 { 4020 ERR("Insufficient space to retrieve new item label\n"); 4021 } 4022 4023 tvdi.item.mask = TVIF_TEXT; 4024 tvdi.item.pszText = tmpText; 4025 tvdi.item.cchTextMax = iLength + 1; 4026 } 4027 else 4028 { 4029 tvdi.item.pszText = NULL; 4030 tvdi.item.cchTextMax = 0; 4031 } 4032 4033 bCommit = TREEVIEW_SendRealNotify(infoPtr, TVN_ENDLABELEDITW, &tvdi.hdr); 4034 4035 if (!bCancel && bCommit) /* Apply the changes */ 4036 { 4037 if (!infoPtr->bNtfUnicode) 4038 { 4039 DWORD len = MultiByteToWideChar( CP_ACP, 0, (LPSTR)tmpText, -1, NULL, 0 ); 4040 newText = heap_alloc(len * sizeof(WCHAR)); 4041 MultiByteToWideChar( CP_ACP, 0, (LPSTR)tmpText, -1, newText, len ); 4042 iLength = len - 1; 4043 } 4044 4045 if (strcmpW(newText, editedItem->pszText) != 0) 4046 { 4047 WCHAR *ptr = heap_realloc(editedItem->pszText, sizeof(WCHAR)*(iLength + 1)); 4048 if (ptr == NULL) 4049 { 4050 ERR("OutOfMemory, cannot allocate space for label\n"); 4051 if (newText != tmpText) heap_free(newText); 4052 DestroyWindow(infoPtr->hwndEdit); 4053 infoPtr->hwndEdit = 0; 4054 infoPtr->editItem = NULL; 4055 return FALSE; 4056 } 4057 else 4058 { 4059 editedItem->pszText = ptr; 4060 editedItem->cchTextMax = iLength + 1; 4061 strcpyW(editedItem->pszText, newText); 4062 TREEVIEW_ComputeTextWidth(infoPtr, editedItem, 0); 4063 } 4064 } 4065 if (newText != tmpText) heap_free(newText); 4066 } 4067 4068 ShowWindow(infoPtr->hwndEdit, SW_HIDE); 4069 DestroyWindow(infoPtr->hwndEdit); 4070 infoPtr->hwndEdit = 0; 4071 infoPtr->editItem = NULL; 4072 return TRUE; 4073 } 4074 4075 static LRESULT 4076 TREEVIEW_HandleTimer(TREEVIEW_INFO *infoPtr, WPARAM wParam) 4077 { 4078 if (wParam != TV_EDIT_TIMER) 4079 { 4080 ERR("got unknown timer\n"); 4081 return 1; 4082 } 4083 4084 KillTimer(infoPtr->hwnd, TV_EDIT_TIMER); 4085 infoPtr->Timer &= ~TV_EDIT_TIMER_SET; 4086 4087 TREEVIEW_EditLabel(infoPtr, infoPtr->selectedItem); 4088 4089 return 0; 4090 } 4091 4092 4093 /* Mouse Tracking/Drag **************************************************/ 4094 4095 /*************************************************************************** 4096 * This is quite unusual piece of code, but that's how it's implemented in 4097 * Windows. 4098 */ 4099 static LRESULT 4100 TREEVIEW_TrackMouse(const TREEVIEW_INFO *infoPtr, POINT pt) 4101 { 4102 INT cxDrag = GetSystemMetrics(SM_CXDRAG); 4103 INT cyDrag = GetSystemMetrics(SM_CYDRAG); 4104 RECT r; 4105 MSG msg; 4106 4107 r.top = pt.y - cyDrag; 4108 r.left = pt.x - cxDrag; 4109 r.bottom = pt.y + cyDrag; 4110 r.right = pt.x + cxDrag; 4111 4112 SetCapture(infoPtr->hwnd); 4113 4114 while (1) 4115 { 4116 if (PeekMessageW(&msg, 0, 0, 0, PM_REMOVE | PM_NOYIELD)) 4117 { 4118 if (msg.message == WM_MOUSEMOVE) 4119 { 4120 pt.x = (short)LOWORD(msg.lParam); 4121 pt.y = (short)HIWORD(msg.lParam); 4122 if (PtInRect(&r, pt)) 4123 continue; 4124 else 4125 { 4126 ReleaseCapture(); 4127 return 1; 4128 } 4129 } 4130 else if (msg.message >= WM_LBUTTONDOWN && 4131 msg.message <= WM_RBUTTONDBLCLK) 4132 { 4133 break; 4134 } 4135 4136 DispatchMessageW(&msg); 4137 } 4138 4139 if (GetCapture() != infoPtr->hwnd) 4140 return 0; 4141 } 4142 4143 ReleaseCapture(); 4144 return 0; 4145 } 4146 4147 4148 static LRESULT 4149 TREEVIEW_LButtonDoubleClick(TREEVIEW_INFO *infoPtr, LPARAM lParam) 4150 { 4151 TREEVIEW_ITEM *item; 4152 TVHITTESTINFO hit; 4153 4154 TRACE("\n"); 4155 SetFocus(infoPtr->hwnd); 4156 4157 if (infoPtr->Timer & TV_EDIT_TIMER_SET) 4158 { 4159 /* If there is pending 'edit label' event - kill it now */ 4160 KillTimer(infoPtr->hwnd, TV_EDIT_TIMER); 4161 } 4162 4163 hit.pt.x = (short)LOWORD(lParam); 4164 hit.pt.y = (short)HIWORD(lParam); 4165 4166 item = TREEVIEW_HitTest(infoPtr, &hit); 4167 if (!item) 4168 return 0; 4169 TRACE("item %d\n", TREEVIEW_GetItemIndex(infoPtr, item)); 4170 4171 if (TREEVIEW_SendSimpleNotify(infoPtr, NM_DBLCLK) == FALSE) 4172 { /* FIXME! */ 4173 switch (hit.flags) 4174 { 4175 case TVHT_ONITEMRIGHT: 4176 /* FIXME: we should not have sent NM_DBLCLK in this case. */ 4177 break; 4178 4179 case TVHT_ONITEMINDENT: 4180 if (!(infoPtr->dwStyle & TVS_HASLINES)) 4181 { 4182 break; 4183 } 4184 else 4185 { 4186 int level = hit.pt.x / infoPtr->uIndent; 4187 if (!(infoPtr->dwStyle & TVS_LINESATROOT)) level++; 4188 4189 while (item->iLevel > level) 4190 { 4191 item = item->parent; 4192 } 4193 4194 /* fall through */ 4195 } 4196 4197 case TVHT_ONITEMLABEL: 4198 case TVHT_ONITEMICON: 4199 case TVHT_ONITEMBUTTON: 4200 TREEVIEW_Toggle(infoPtr, item, TRUE); 4201 break; 4202 4203 case TVHT_ONITEMSTATEICON: 4204 if (infoPtr->dwStyle & TVS_CHECKBOXES) 4205 TREEVIEW_ToggleItemState(infoPtr, item); 4206 else 4207 TREEVIEW_Toggle(infoPtr, item, TRUE); 4208 break; 4209 } 4210 } 4211 return TRUE; 4212 } 4213 4214 4215 static LRESULT 4216 TREEVIEW_LButtonDown(TREEVIEW_INFO *infoPtr, LPARAM lParam) 4217 { 4218 BOOL do_track, do_select, bDoLabelEdit; 4219 HWND hwnd = infoPtr->hwnd; 4220 TVHITTESTINFO ht; 4221 4222 /* If Edit control is active - kill it and return. 4223 * The best way to do it is to set focus to itself. 4224 * Edit control subclassed procedure will automatically call 4225 * EndEditLabelNow. 4226 */ 4227 if (infoPtr->hwndEdit) 4228 { 4229 SetFocus(hwnd); 4230 return 0; 4231 } 4232 4233 ht.pt.x = (short)LOWORD(lParam); 4234 ht.pt.y = (short)HIWORD(lParam); 4235 4236 TREEVIEW_HitTest(infoPtr, &ht); 4237 TRACE("item %d\n", TREEVIEW_GetItemIndex(infoPtr, ht.hItem)); 4238 4239 /* update focusedItem and redraw both items */ 4240 if (ht.hItem) 4241 { 4242 BOOL do_focus; 4243 4244 if (TREEVIEW_IsFullRowSelect(infoPtr)) 4245 do_focus = ht.flags & (TVHT_ONITEMINDENT | TVHT_ONITEM | TVHT_ONITEMRIGHT); 4246 else 4247 do_focus = ht.flags & TVHT_ONITEM; 4248 4249 if (do_focus) 4250 { 4251 infoPtr->focusedItem = ht.hItem; 4252 TREEVIEW_InvalidateItem(infoPtr, infoPtr->focusedItem); 4253 TREEVIEW_InvalidateItem(infoPtr, infoPtr->selectedItem); 4254 } 4255 } 4256 4257 if (!(infoPtr->dwStyle & TVS_DISABLEDRAGDROP)) 4258 { 4259 if (TREEVIEW_IsFullRowSelect(infoPtr)) 4260 do_track = ht.flags & (TVHT_ONITEMINDENT | TVHT_ONITEM | TVHT_ONITEMRIGHT); 4261 else 4262 do_track = ht.flags & TVHT_ONITEM; 4263 } 4264 else 4265 do_track = FALSE; 4266 4267 /* 4268 * If the style allows editing and the node is already selected 4269 * and the click occurred on the item label... 4270 */ 4271 bDoLabelEdit = (infoPtr->dwStyle & TVS_EDITLABELS) && 4272 (ht.flags & TVHT_ONITEMLABEL) && (infoPtr->selectedItem == ht.hItem); 4273 4274 /* Send NM_CLICK right away */ 4275 if (!do_track && TREEVIEW_SendSimpleNotify(infoPtr, NM_CLICK)) 4276 goto setfocus; 4277 4278 if (ht.flags & TVHT_ONITEMBUTTON) 4279 { 4280 TREEVIEW_Toggle(infoPtr, ht.hItem, TRUE); 4281 goto setfocus; 4282 } 4283 else if (do_track) 4284 { /* if TREEVIEW_TrackMouse == 1 dragging occurred and the cursor left the dragged item's rectangle */ 4285 if (TREEVIEW_TrackMouse(infoPtr, ht.pt)) 4286 { 4287 TREEVIEW_SendTreeviewDnDNotify(infoPtr, TVN_BEGINDRAGW, ht.hItem, ht.pt); 4288 infoPtr->dropItem = ht.hItem; 4289 4290 /* clean up focusedItem as we dragged and won't select this item */ 4291 if(infoPtr->focusedItem) 4292 { 4293 /* refresh the item that was focused */ 4294 TREEVIEW_InvalidateItem(infoPtr, infoPtr->focusedItem); 4295 infoPtr->focusedItem = NULL; 4296 4297 /* refresh the selected item to return the filled background */ 4298 TREEVIEW_InvalidateItem(infoPtr, infoPtr->selectedItem); 4299 } 4300 4301 return 0; 4302 } 4303 } 4304 4305 if (do_track && TREEVIEW_SendSimpleNotify(infoPtr, NM_CLICK)) 4306 goto setfocus; 4307 4308 if (TREEVIEW_IsFullRowSelect(infoPtr)) 4309 do_select = ht.flags & (TVHT_ONITEMINDENT | TVHT_ONITEMICON | TVHT_ONITEMLABEL | TVHT_ONITEMRIGHT); 4310 else 4311 do_select = ht.flags & (TVHT_ONITEMICON | TVHT_ONITEMLABEL); 4312 4313 if (bDoLabelEdit) 4314 { 4315 if (infoPtr->Timer & TV_EDIT_TIMER_SET) 4316 KillTimer(hwnd, TV_EDIT_TIMER); 4317 4318 SetTimer(hwnd, TV_EDIT_TIMER, GetDoubleClickTime(), 0); 4319 infoPtr->Timer |= TV_EDIT_TIMER_SET; 4320 } 4321 else if (do_select) 4322 { 4323 TREEVIEW_ITEM *selection = infoPtr->selectedItem; 4324 4325 /* Select the current item */ 4326 TREEVIEW_DoSelectItem(infoPtr, TVGN_CARET, ht.hItem, TVC_BYMOUSE); 4327 TREEVIEW_SingleExpand(infoPtr, selection, ht.hItem); 4328 } 4329 else if (ht.flags & TVHT_ONITEMSTATEICON) 4330 { 4331 /* TVS_CHECKBOXES requires us to toggle the current state */ 4332 if (infoPtr->dwStyle & TVS_CHECKBOXES) 4333 TREEVIEW_ToggleItemState(infoPtr, ht.hItem); 4334 } 4335 4336 setfocus: 4337 SetFocus(hwnd); 4338 return 0; 4339 } 4340 4341 4342 static LRESULT 4343 TREEVIEW_RButtonDown(TREEVIEW_INFO *infoPtr, LPARAM lParam) 4344 { 4345 TVHITTESTINFO ht; 4346 4347 if (infoPtr->hwndEdit) 4348 { 4349 SetFocus(infoPtr->hwnd); 4350 return 0; 4351 } 4352 4353 ht.pt.x = (short)LOWORD(lParam); 4354 ht.pt.y = (short)HIWORD(lParam); 4355 4356 if (TREEVIEW_HitTest(infoPtr, &ht)) 4357 { 4358 infoPtr->focusedItem = ht.hItem; 4359 TREEVIEW_InvalidateItem(infoPtr, infoPtr->focusedItem); 4360 TREEVIEW_InvalidateItem(infoPtr, infoPtr->selectedItem); 4361 } 4362 4363 if (TREEVIEW_TrackMouse(infoPtr, ht.pt)) 4364 { 4365 if (ht.hItem) 4366 { 4367 TREEVIEW_SendTreeviewDnDNotify(infoPtr, TVN_BEGINRDRAGW, ht.hItem, ht.pt); 4368 infoPtr->dropItem = ht.hItem; 4369 } 4370 } 4371 else 4372 { 4373 SetFocus(infoPtr->hwnd); 4374 if(!TREEVIEW_SendSimpleNotify(infoPtr, NM_RCLICK)) 4375 { 4376 /* Send a WM_CONTEXTMENU message in response to the RBUTTONUP */ 4377 SendMessageW(infoPtr->hwndNotify, WM_CONTEXTMENU, 4378 (WPARAM)infoPtr->hwnd, (LPARAM)GetMessagePos()); 4379 } 4380 } 4381 4382 if (ht.hItem) 4383 { 4384 TREEVIEW_InvalidateItem(infoPtr, infoPtr->focusedItem); 4385 infoPtr->focusedItem = infoPtr->selectedItem; 4386 TREEVIEW_InvalidateItem(infoPtr, infoPtr->focusedItem); 4387 } 4388 4389 return 0; 4390 } 4391 4392 static LRESULT 4393 TREEVIEW_CreateDragImage(TREEVIEW_INFO *infoPtr, LPARAM lParam) 4394 { 4395 TREEVIEW_ITEM *dragItem = (HTREEITEM)lParam; 4396 INT cx, cy; 4397 HDC hdc, htopdc; 4398 HWND hwtop; 4399 HBITMAP hbmp, hOldbmp; 4400 SIZE size; 4401 RECT rc; 4402 HFONT hOldFont; 4403 4404 TRACE("\n"); 4405 4406 if (!(infoPtr->himlNormal)) 4407 return 0; 4408 4409 if (!dragItem || !TREEVIEW_ValidItem(infoPtr, dragItem)) 4410 return 0; 4411 4412 TREEVIEW_UpdateDispInfo(infoPtr, dragItem, TVIF_TEXT); 4413 4414 hwtop = GetDesktopWindow(); 4415 htopdc = GetDC(hwtop); 4416 hdc = CreateCompatibleDC(htopdc); 4417 4418 hOldFont = SelectObject(hdc, infoPtr->hFont); 4419 4420 if (dragItem->pszText) 4421 GetTextExtentPoint32W(hdc, dragItem->pszText, strlenW(dragItem->pszText), 4422 &size); 4423 else 4424 GetTextExtentPoint32A(hdc, "", 0, &size); 4425 4426 TRACE("%d %d %s\n", size.cx, size.cy, debugstr_w(dragItem->pszText)); 4427 hbmp = CreateCompatibleBitmap(htopdc, size.cx, size.cy); 4428 hOldbmp = SelectObject(hdc, hbmp); 4429 4430 ImageList_GetIconSize(infoPtr->himlNormal, &cx, &cy); 4431 size.cx += cx; 4432 if (cy > size.cy) 4433 size.cy = cy; 4434 4435 infoPtr->dragList = ImageList_Create(size.cx, size.cy, ILC_COLOR, 10, 10); 4436 ImageList_Draw(infoPtr->himlNormal, dragItem->iImage, hdc, 0, 0, 4437 ILD_NORMAL); 4438 4439 /* 4440 ImageList_GetImageInfo (infoPtr->himlNormal, dragItem->hItem, &iminfo); 4441 ImageList_AddMasked (infoPtr->dragList, iminfo.hbmImage, CLR_DEFAULT); 4442 */ 4443 4444 /* draw item text */ 4445 4446 SetRect(&rc, cx, 0, size.cx, size.cy); 4447 4448 if (dragItem->pszText) 4449 DrawTextW(hdc, dragItem->pszText, strlenW(dragItem->pszText), &rc, 4450 DT_LEFT); 4451 4452 SelectObject(hdc, hOldFont); 4453 SelectObject(hdc, hOldbmp); 4454 4455 ImageList_Add(infoPtr->dragList, hbmp, 0); 4456 4457 DeleteDC(hdc); 4458 DeleteObject(hbmp); 4459 ReleaseDC(hwtop, htopdc); 4460 4461 return (LRESULT)infoPtr->dragList; 4462 } 4463 4464 /* Selection ************************************************************/ 4465 4466 static LRESULT 4467 TREEVIEW_DoSelectItem(TREEVIEW_INFO *infoPtr, INT action, HTREEITEM newSelect, 4468 INT cause) 4469 { 4470 TREEVIEW_ITEM *prevSelect; 4471 4472 assert(newSelect == NULL || TREEVIEW_ValidItem(infoPtr, newSelect)); 4473 4474 TRACE("Entering item %p (%s), flag 0x%x, cause 0x%x, state 0x%x\n", 4475 newSelect, TREEVIEW_ItemName(newSelect), action, cause, 4476 newSelect ? newSelect->state : 0); 4477 4478 /* reset and redraw focusedItem if focusedItem was set so we don't */ 4479 /* have to worry about the previously focused item when we set a new one */ 4480 TREEVIEW_InvalidateItem(infoPtr, infoPtr->focusedItem); 4481 infoPtr->focusedItem = NULL; 4482 4483 switch (action) 4484 { 4485 case TVGN_CARET|TVSI_NOSINGLEEXPAND: 4486 FIXME("TVSI_NOSINGLEEXPAND specified.\n"); 4487 /* Fall through */ 4488 case TVGN_CARET: 4489 prevSelect = infoPtr->selectedItem; 4490 4491 if (prevSelect == newSelect) { 4492 TREEVIEW_EnsureVisible(infoPtr, infoPtr->selectedItem, FALSE); 4493 break; 4494 } 4495 4496 if (TREEVIEW_SendTreeviewNotify(infoPtr, 4497 TVN_SELCHANGINGW, 4498 cause, 4499 TVIF_TEXT | TVIF_HANDLE | TVIF_STATE | TVIF_PARAM, 4500 prevSelect, 4501 newSelect)) 4502 return FALSE; 4503 4504 if (prevSelect) 4505 prevSelect->state &= ~TVIS_SELECTED; 4506 if (newSelect) 4507 newSelect->state |= TVIS_SELECTED; 4508 4509 infoPtr->selectedItem = newSelect; 4510 4511 TREEVIEW_EnsureVisible(infoPtr, infoPtr->selectedItem, FALSE); 4512 4513 TREEVIEW_InvalidateItem(infoPtr, prevSelect); 4514 TREEVIEW_InvalidateItem(infoPtr, newSelect); 4515 4516 TREEVIEW_SendTreeviewNotify(infoPtr, 4517 TVN_SELCHANGEDW, 4518 cause, 4519 TVIF_TEXT | TVIF_HANDLE | TVIF_STATE | TVIF_PARAM, 4520 prevSelect, 4521 newSelect); 4522 break; 4523 4524 case TVGN_DROPHILITE: 4525 prevSelect = infoPtr->dropItem; 4526 4527 if (prevSelect) 4528 prevSelect->state &= ~TVIS_DROPHILITED; 4529 4530 infoPtr->dropItem = newSelect; 4531 4532 if (newSelect) 4533 newSelect->state |= TVIS_DROPHILITED; 4534 4535 TREEVIEW_Invalidate(infoPtr, prevSelect); 4536 TREEVIEW_Invalidate(infoPtr, newSelect); 4537 break; 4538 4539 case TVGN_FIRSTVISIBLE: 4540 if (newSelect != NULL) 4541 { 4542 TREEVIEW_EnsureVisible(infoPtr, newSelect, FALSE); 4543 TREEVIEW_SetFirstVisible(infoPtr, newSelect, TRUE); 4544 TREEVIEW_Invalidate(infoPtr, NULL); 4545 } 4546 break; 4547 } 4548 4549 TRACE("Leaving state 0x%x\n", newSelect ? newSelect->state : 0); 4550 return TRUE; 4551 } 4552 4553 /* FIXME: handle NM_KILLFOCUS etc */ 4554 static LRESULT 4555 TREEVIEW_SelectItem(TREEVIEW_INFO *infoPtr, INT wParam, HTREEITEM item) 4556 { 4557 TREEVIEW_ITEM *selection = infoPtr->selectedItem; 4558 4559 if (item && !TREEVIEW_ValidItem(infoPtr, item)) 4560 return FALSE; 4561 4562 if (item == infoPtr->selectedItem) 4563 return TRUE; 4564 4565 TRACE("%p (%s) %d\n", item, TREEVIEW_ItemName(item), wParam); 4566 4567 if (!TREEVIEW_DoSelectItem(infoPtr, wParam, item, TVC_UNKNOWN)) 4568 return FALSE; 4569 4570 TREEVIEW_SingleExpand(infoPtr, selection, item); 4571 4572 return TRUE; 4573 } 4574 4575 /************************************************************************* 4576 * TREEVIEW_ProcessLetterKeys 4577 * 4578 * Processes keyboard messages generated by pressing the letter keys 4579 * on the keyboard. 4580 * What this does is perform a case insensitive search from the 4581 * current position with the following quirks: 4582 * - If two chars or more are pressed in quick succession we search 4583 * for the corresponding string (e.g. 'abc'). 4584 * - If there is a delay we wipe away the current search string and 4585 * restart with just that char. 4586 * - If the user keeps pressing the same character, whether slowly or 4587 * fast, so that the search string is entirely composed of this 4588 * character ('aaaaa' for instance), then we search for first item 4589 * that starting with that character. 4590 * - If the user types the above character in quick succession, then 4591 * we must also search for the corresponding string ('aaaaa'), and 4592 * go to that string if there is a match. 4593 * 4594 * RETURNS 4595 * 4596 * Zero. 4597 * 4598 * BUGS 4599 * 4600 * - The current implementation has a list of characters it will 4601 * accept and it ignores everything else. In particular it will 4602 * ignore accentuated characters which seems to match what 4603 * Windows does. But I'm not sure it makes sense to follow 4604 * Windows there. 4605 * - We don't sound a beep when the search fails. 4606 * - The search should start from the focused item, not from the selected 4607 * item. One reason for this is to allow for multiple selections in trees. 4608 * But currently infoPtr->focusedItem does not seem very usable. 4609 * 4610 * SEE ALSO 4611 * 4612 * TREEVIEW_ProcessLetterKeys 4613 */ 4614 static INT TREEVIEW_ProcessLetterKeys(TREEVIEW_INFO *infoPtr, WPARAM charCode, LPARAM keyData) 4615 { 4616 HTREEITEM nItem; 4617 HTREEITEM endidx,idx; 4618 TVITEMEXW item; 4619 WCHAR buffer[MAX_PATH]; 4620 DWORD timestamp,elapsed; 4621 4622 /* simple parameter checking */ 4623 if (!charCode || !keyData) return 0; 4624 4625 /* only allow the valid WM_CHARs through */ 4626 if (!isalnum(charCode) && 4627 charCode != '.' && charCode != '`' && charCode != '!' && 4628 charCode != '@' && charCode != '#' && charCode != '$' && 4629 charCode != '%' && charCode != '^' && charCode != '&' && 4630 charCode != '*' && charCode != '(' && charCode != ')' && 4631 charCode != '-' && charCode != '_' && charCode != '+' && 4632 charCode != '=' && charCode != '\\'&& charCode != ']' && 4633 charCode != '}' && charCode != '[' && charCode != '{' && 4634 charCode != '/' && charCode != '?' && charCode != '>' && 4635 charCode != '<' && charCode != ',' && charCode != '~') 4636 return 0; 4637 4638 /* compute how much time elapsed since last keypress */ 4639 timestamp = GetTickCount(); 4640 if (timestamp > infoPtr->lastKeyPressTimestamp) { 4641 elapsed=timestamp-infoPtr->lastKeyPressTimestamp; 4642 } else { 4643 elapsed=infoPtr->lastKeyPressTimestamp-timestamp; 4644 } 4645 4646 /* update the search parameters */ 4647 infoPtr->lastKeyPressTimestamp=timestamp; 4648 if (elapsed < KEY_DELAY) { 4649 if (infoPtr->nSearchParamLength < ARRAY_SIZE(infoPtr->szSearchParam)) { 4650 infoPtr->szSearchParam[infoPtr->nSearchParamLength++]=charCode; 4651 } 4652 if (infoPtr->charCode != charCode) { 4653 infoPtr->charCode=charCode=0; 4654 } 4655 } else { 4656 infoPtr->charCode=charCode; 4657 infoPtr->szSearchParam[0]=charCode; 4658 infoPtr->nSearchParamLength=1; 4659 /* Redundant with the 1 char string */ 4660 charCode=0; 4661 } 4662 4663 /* and search from the current position */ 4664 nItem=NULL; 4665 if (infoPtr->selectedItem != NULL) { 4666 endidx=infoPtr->selectedItem; 4667 /* if looking for single character match, 4668 * then we must always move forward 4669 */ 4670 if (infoPtr->nSearchParamLength == 1) 4671 idx=TREEVIEW_GetNextListItem(infoPtr,endidx); 4672 else 4673 idx=endidx; 4674 } else { 4675 endidx=NULL; 4676 idx=infoPtr->root->firstChild; 4677 } 4678 do { 4679 /* At the end point, sort out wrapping */ 4680 if (idx == NULL) { 4681 4682 /* If endidx is null, stop at the last item (ie top to bottom) */ 4683 if (endidx == NULL) 4684 break; 4685 4686 /* Otherwise, start again at the very beginning */ 4687 idx=infoPtr->root->firstChild; 4688 4689 /* But if we are stopping on the first child, end now! */ 4690 if (idx == endidx) break; 4691 } 4692 4693 /* get item */ 4694 ZeroMemory(&item, sizeof(item)); 4695 item.mask = TVIF_TEXT; 4696 item.hItem = idx; 4697 item.pszText = buffer; 4698 item.cchTextMax = sizeof(buffer); 4699 TREEVIEW_GetItemT( infoPtr, &item, TRUE ); 4700 4701 /* check for a match */ 4702 if (strncmpiW(item.pszText,infoPtr->szSearchParam,infoPtr->nSearchParamLength) == 0) { 4703 nItem=idx; 4704 break; 4705 } else if ( (charCode != 0) && (nItem == NULL) && 4706 (nItem != infoPtr->selectedItem) && 4707 (strncmpiW(item.pszText,infoPtr->szSearchParam,1) == 0) ) { 4708 /* This would work but we must keep looking for a longer match */ 4709 nItem=idx; 4710 } 4711 idx=TREEVIEW_GetNextListItem(infoPtr,idx); 4712 } while (idx != endidx); 4713 4714 if (nItem != NULL) { 4715 if (TREEVIEW_DoSelectItem(infoPtr, TVGN_CARET, nItem, TVC_BYKEYBOARD)) { 4716 TREEVIEW_EnsureVisible(infoPtr, nItem, FALSE); 4717 } 4718 } 4719 4720 return 0; 4721 } 4722 4723 /* Scrolling ************************************************************/ 4724 4725 static LRESULT 4726 TREEVIEW_EnsureVisible(TREEVIEW_INFO *infoPtr, HTREEITEM item, BOOL bHScroll) 4727 { 4728 int viscount; 4729 BOOL hasFirstVisible = infoPtr->firstVisible != NULL; 4730 HTREEITEM newFirstVisible = NULL; 4731 int visible_pos = -1; 4732 4733 if (!TREEVIEW_ValidItem(infoPtr, item)) 4734 return FALSE; 4735 4736 if (!ISVISIBLE(item)) 4737 { 4738 /* Expand parents as necessary. */ 4739 HTREEITEM parent; 4740 4741 /* see if we are trying to ensure that root is visible */ 4742 if((item != infoPtr->root) && TREEVIEW_ValidItem(infoPtr, item)) 4743 parent = item->parent; 4744 else 4745 parent = item; /* this item is the topmost item */ 4746 4747 while (parent != infoPtr->root) 4748 { 4749 if (!(parent->state & TVIS_EXPANDED)) 4750 TREEVIEW_Expand(infoPtr, parent, FALSE, TRUE); 4751 4752 parent = parent->parent; 4753 } 4754 } 4755 4756 viscount = TREEVIEW_GetVisibleCount(infoPtr); 4757 4758 TRACE("%p (%s) %d - %d viscount(%d)\n", item, TREEVIEW_ItemName(item), item->visibleOrder, 4759 hasFirstVisible ? infoPtr->firstVisible->visibleOrder : -1, viscount); 4760 4761 if (hasFirstVisible) 4762 visible_pos = item->visibleOrder - infoPtr->firstVisible->visibleOrder; 4763 4764 if (visible_pos < 0) 4765 { 4766 /* item is before the start of the list: put it at the top. */ 4767 newFirstVisible = item; 4768 } 4769 else if (visible_pos >= viscount 4770 /* Sometimes, before we are displayed, GVC is 0, causing us to 4771 * spuriously scroll up. */ 4772 && visible_pos > 0 && !(infoPtr->dwStyle & TVS_NOSCROLL) ) 4773 { 4774 /* item is past the end of the list. */ 4775 int scroll = visible_pos - viscount; 4776 4777 newFirstVisible = TREEVIEW_GetListItem(infoPtr, infoPtr->firstVisible, 4778 scroll + 1); 4779 } 4780 4781 if (bHScroll) 4782 { 4783 /* Scroll window so item's text is visible as much as possible */ 4784 /* Calculation of amount of extra space is taken from EditLabel code */ 4785 INT pos, x; 4786 TEXTMETRICW textMetric; 4787 HDC hdc = GetWindowDC(infoPtr->hwnd); 4788 4789 x = item->textWidth; 4790 4791 GetTextMetricsW(hdc, &textMetric); 4792 ReleaseDC(infoPtr->hwnd, hdc); 4793 4794 x += (textMetric.tmMaxCharWidth * 2); 4795 x = max(x, textMetric.tmMaxCharWidth * 3); 4796 4797 if (item->textOffset < 0) 4798 pos = item->textOffset; 4799 else if (item->textOffset + x > infoPtr->clientWidth) 4800 { 4801 if (x > infoPtr->clientWidth) 4802 pos = item->textOffset; 4803 else 4804 pos = item->textOffset + x - infoPtr->clientWidth; 4805 } 4806 else 4807 pos = 0; 4808 4809 TREEVIEW_HScroll(infoPtr, MAKEWPARAM(SB_THUMBPOSITION, infoPtr->scrollX + pos)); 4810 } 4811 4812 if (newFirstVisible != NULL && newFirstVisible != infoPtr->firstVisible) 4813 { 4814 TREEVIEW_SetFirstVisible(infoPtr, newFirstVisible, TRUE); 4815 4816 return TRUE; 4817 } 4818 4819 return FALSE; 4820 } 4821 4822 static VOID 4823 TREEVIEW_SetFirstVisible(TREEVIEW_INFO *infoPtr, 4824 TREEVIEW_ITEM *newFirstVisible, 4825 BOOL bUpdateScrollPos) 4826 { 4827 int gap_size; 4828 4829 TRACE("%p: %s\n", newFirstVisible, TREEVIEW_ItemName(newFirstVisible)); 4830 4831 if (newFirstVisible != NULL) 4832 { 4833 /* Prevent an empty gap from appearing at the bottom... */ 4834 gap_size = TREEVIEW_GetVisibleCount(infoPtr) 4835 - infoPtr->maxVisibleOrder + newFirstVisible->visibleOrder; 4836 4837 if (gap_size > 0) 4838 { 4839 newFirstVisible = TREEVIEW_GetListItem(infoPtr, newFirstVisible, 4840 -gap_size); 4841 4842 /* ... unless we just don't have enough items. */ 4843 if (newFirstVisible == NULL) 4844 newFirstVisible = infoPtr->root->firstChild; 4845 } 4846 } 4847 4848 if (infoPtr->firstVisible != newFirstVisible) 4849 { 4850 if (infoPtr->firstVisible == NULL || newFirstVisible == NULL) 4851 { 4852 infoPtr->firstVisible = newFirstVisible; 4853 TREEVIEW_Invalidate(infoPtr, NULL); 4854 } 4855 else 4856 { 4857 TREEVIEW_ITEM *item; 4858 int scroll = infoPtr->uItemHeight * 4859 (infoPtr->firstVisible->visibleOrder 4860 - newFirstVisible->visibleOrder); 4861 4862 infoPtr->firstVisible = newFirstVisible; 4863 4864 for (item = infoPtr->root->firstChild; item != NULL; 4865 item = TREEVIEW_GetNextListItem(infoPtr, item)) 4866 { 4867 item->rect.top += scroll; 4868 item->rect.bottom += scroll; 4869 } 4870 4871 if (bUpdateScrollPos) 4872 SetScrollPos(infoPtr->hwnd, SB_VERT, 4873 newFirstVisible->visibleOrder, TRUE); 4874 4875 ScrollWindowEx(infoPtr->hwnd, 0, scroll, NULL, NULL, NULL, NULL, SW_ERASE | SW_INVALIDATE); 4876 } 4877 } 4878 } 4879 4880 /************************************************************************ 4881 * VScroll is always in units of visible items. i.e. we always have a 4882 * visible item aligned to the top of the control. (Unless we have no 4883 * items at all.) 4884 */ 4885 static LRESULT 4886 TREEVIEW_VScroll(TREEVIEW_INFO *infoPtr, WPARAM wParam) 4887 { 4888 TREEVIEW_ITEM *oldFirstVisible = infoPtr->firstVisible; 4889 TREEVIEW_ITEM *newFirstVisible = NULL; 4890 4891 int nScrollCode = LOWORD(wParam); 4892 4893 TRACE("wp %lx\n", wParam); 4894 4895 if (!(infoPtr->uInternalStatus & TV_VSCROLL)) 4896 return 0; 4897 4898 if (!oldFirstVisible) 4899 { 4900 assert(infoPtr->root->firstChild == NULL); 4901 return 0; 4902 } 4903 4904 switch (nScrollCode) 4905 { 4906 case SB_TOP: 4907 newFirstVisible = infoPtr->root->firstChild; 4908 break; 4909 4910 case SB_BOTTOM: 4911 newFirstVisible = TREEVIEW_GetLastListItem(infoPtr, infoPtr->root); 4912 break; 4913 4914 case SB_LINEUP: 4915 newFirstVisible = TREEVIEW_GetPrevListItem(infoPtr, oldFirstVisible); 4916 break; 4917 4918 case SB_LINEDOWN: 4919 newFirstVisible = TREEVIEW_GetNextListItem(infoPtr, oldFirstVisible); 4920 break; 4921 4922 case SB_PAGEUP: 4923 newFirstVisible = TREEVIEW_GetListItem(infoPtr, oldFirstVisible, 4924 -max(1, TREEVIEW_GetVisibleCount(infoPtr))); 4925 break; 4926 4927 case SB_PAGEDOWN: 4928 newFirstVisible = TREEVIEW_GetListItem(infoPtr, oldFirstVisible, 4929 max(1, TREEVIEW_GetVisibleCount(infoPtr))); 4930 break; 4931 4932 case SB_THUMBTRACK: 4933 case SB_THUMBPOSITION: 4934 newFirstVisible = TREEVIEW_GetListItem(infoPtr, 4935 infoPtr->root->firstChild, 4936 (LONG)(SHORT)HIWORD(wParam)); 4937 break; 4938 4939 case SB_ENDSCROLL: 4940 return 0; 4941 } 4942 4943 if (newFirstVisible != NULL) 4944 { 4945 if (newFirstVisible != oldFirstVisible) 4946 TREEVIEW_SetFirstVisible(infoPtr, newFirstVisible, 4947 nScrollCode != SB_THUMBTRACK); 4948 else if (nScrollCode == SB_THUMBPOSITION) 4949 SetScrollPos(infoPtr->hwnd, SB_VERT, 4950 newFirstVisible->visibleOrder, TRUE); 4951 } 4952 4953 return 0; 4954 } 4955 4956 static LRESULT 4957 TREEVIEW_HScroll(TREEVIEW_INFO *infoPtr, WPARAM wParam) 4958 { 4959 int maxWidth; 4960 int scrollX = infoPtr->scrollX; 4961 int nScrollCode = LOWORD(wParam); 4962 4963 TRACE("wp %lx\n", wParam); 4964 4965 if (!(infoPtr->uInternalStatus & TV_HSCROLL)) 4966 return FALSE; 4967 4968 maxWidth = infoPtr->treeWidth - infoPtr->clientWidth; 4969 /* shall never occur */ 4970 if (maxWidth <= 0) 4971 { 4972 scrollX = 0; 4973 goto scroll; 4974 } 4975 4976 switch (nScrollCode) 4977 { 4978 case SB_LINELEFT: 4979 scrollX -= infoPtr->uItemHeight; 4980 break; 4981 case SB_LINERIGHT: 4982 scrollX += infoPtr->uItemHeight; 4983 break; 4984 case SB_PAGELEFT: 4985 scrollX -= infoPtr->clientWidth; 4986 break; 4987 case SB_PAGERIGHT: 4988 scrollX += infoPtr->clientWidth; 4989 break; 4990 4991 case SB_THUMBTRACK: 4992 case SB_THUMBPOSITION: 4993 scrollX = (int)(SHORT)HIWORD(wParam); 4994 break; 4995 4996 case SB_ENDSCROLL: 4997 return 0; 4998 } 4999 5000 if (scrollX > maxWidth) 5001 scrollX = maxWidth; 5002 else if (scrollX < 0) 5003 scrollX = 0; 5004 5005 scroll: 5006 if (scrollX != infoPtr->scrollX) 5007 { 5008 TREEVIEW_ITEM *item; 5009 LONG scroll_pixels = infoPtr->scrollX - scrollX; 5010 5011 for (item = infoPtr->root->firstChild; item != NULL; 5012 item = TREEVIEW_GetNextListItem(infoPtr, item)) 5013 { 5014 item->linesOffset += scroll_pixels; 5015 item->stateOffset += scroll_pixels; 5016 item->imageOffset += scroll_pixels; 5017 item->textOffset += scroll_pixels; 5018 } 5019 5020 ScrollWindow(infoPtr->hwnd, scroll_pixels, 0, NULL, NULL); 5021 infoPtr->scrollX = scrollX; 5022 UpdateWindow(infoPtr->hwnd); 5023 } 5024 5025 if (nScrollCode != SB_THUMBTRACK) 5026 SetScrollPos(infoPtr->hwnd, SB_HORZ, scrollX, TRUE); 5027 5028 return 0; 5029 } 5030 5031 static LRESULT 5032 TREEVIEW_MouseWheel(TREEVIEW_INFO *infoPtr, WPARAM wParam, LPARAM lParam) 5033 { 5034 short wheelDelta; 5035 UINT pulScrollLines = 3; 5036 5037 if (wParam & (MK_SHIFT | MK_CONTROL)) 5038 return DefWindowProcW(infoPtr->hwnd, WM_MOUSEWHEEL, wParam, lParam); 5039 5040 if (infoPtr->firstVisible == NULL) 5041 return TRUE; 5042 5043 SystemParametersInfoW(SPI_GETWHEELSCROLLLINES, 0, &pulScrollLines, 0); 5044 5045 wheelDelta = GET_WHEEL_DELTA_WPARAM(wParam); 5046 /* if scrolling changes direction, ignore left overs */ 5047 if ((wheelDelta < 0 && infoPtr->wheelRemainder < 0) || 5048 (wheelDelta > 0 && infoPtr->wheelRemainder > 0)) 5049 infoPtr->wheelRemainder += wheelDelta; 5050 else 5051 infoPtr->wheelRemainder = wheelDelta; 5052 5053 if (infoPtr->wheelRemainder && pulScrollLines) 5054 { 5055 int newDy; 5056 int maxDy; 5057 int lineScroll; 5058 5059 lineScroll = pulScrollLines * (float)infoPtr->wheelRemainder / WHEEL_DELTA; 5060 infoPtr->wheelRemainder -= WHEEL_DELTA * lineScroll / (int)pulScrollLines; 5061 5062 newDy = infoPtr->firstVisible->visibleOrder - lineScroll; 5063 maxDy = infoPtr->maxVisibleOrder; 5064 5065 if (newDy > maxDy) 5066 newDy = maxDy; 5067 5068 if (newDy < 0) 5069 newDy = 0; 5070 5071 TREEVIEW_VScroll(infoPtr, MAKEWPARAM(SB_THUMBPOSITION, newDy)); 5072 } 5073 return TRUE; 5074 } 5075 5076 /* Create/Destroy *******************************************************/ 5077 5078 static LRESULT 5079 TREEVIEW_Create(HWND hwnd, const CREATESTRUCTW *lpcs) 5080 { 5081 RECT rcClient; 5082 TREEVIEW_INFO *infoPtr; 5083 LOGFONTW lf; 5084 5085 TRACE("wnd %p, style 0x%x\n", hwnd, GetWindowLongW(hwnd, GWL_STYLE)); 5086 5087 infoPtr = heap_alloc_zero(sizeof(TREEVIEW_INFO)); 5088 5089 if (infoPtr == NULL) 5090 { 5091 ERR("could not allocate info memory!\n"); 5092 return 0; 5093 } 5094 5095 SetWindowLongPtrW(hwnd, 0, (DWORD_PTR)infoPtr); 5096 5097 infoPtr->hwnd = hwnd; 5098 infoPtr->dwStyle = GetWindowLongW(hwnd, GWL_STYLE); 5099 infoPtr->Timer = 0; 5100 infoPtr->uNumItems = 0; 5101 infoPtr->cdmode = 0; 5102 infoPtr->uScrollTime = 300; /* milliseconds */ 5103 infoPtr->bRedraw = TRUE; 5104 5105 GetClientRect(hwnd, &rcClient); 5106 5107 /* No scroll bars yet. */ 5108 infoPtr->clientWidth = rcClient.right; 5109 infoPtr->clientHeight = rcClient.bottom; 5110 infoPtr->uInternalStatus = 0; 5111 5112 infoPtr->treeWidth = 0; 5113 infoPtr->treeHeight = 0; 5114 5115 infoPtr->uIndent = MINIMUM_INDENT; 5116 infoPtr->selectedItem = NULL; 5117 infoPtr->focusedItem = NULL; 5118 infoPtr->hotItem = NULL; 5119 infoPtr->editItem = NULL; 5120 infoPtr->firstVisible = NULL; 5121 infoPtr->maxVisibleOrder = 0; 5122 infoPtr->dropItem = NULL; 5123 infoPtr->insertMarkItem = NULL; 5124 infoPtr->insertBeforeorAfter = 0; 5125 /* dragList */ 5126 5127 infoPtr->scrollX = 0; 5128 infoPtr->wheelRemainder = 0; 5129 5130 infoPtr->clrBk = CLR_NONE; /* use system color */ 5131 infoPtr->clrText = CLR_NONE; /* use system color */ 5132 infoPtr->clrLine = CLR_DEFAULT; 5133 infoPtr->clrInsertMark = CLR_DEFAULT; 5134 5135 /* hwndToolTip */ 5136 5137 infoPtr->hwndEdit = NULL; 5138 infoPtr->wpEditOrig = NULL; 5139 infoPtr->bIgnoreEditKillFocus = FALSE; 5140 infoPtr->bLabelChanged = FALSE; 5141 5142 infoPtr->himlNormal = NULL; 5143 infoPtr->himlState = NULL; 5144 infoPtr->normalImageWidth = 0; 5145 infoPtr->normalImageHeight = 0; 5146 infoPtr->stateImageWidth = 0; 5147 infoPtr->stateImageHeight = 0; 5148 5149 infoPtr->items = DPA_Create(16); 5150 5151 SystemParametersInfoW(SPI_GETICONTITLELOGFONT, sizeof(lf), &lf, 0); 5152 infoPtr->hFont = infoPtr->hDefaultFont = CreateFontIndirectW(&lf); 5153 infoPtr->hBoldFont = TREEVIEW_CreateBoldFont(infoPtr->hFont); 5154 infoPtr->hUnderlineFont = TREEVIEW_CreateUnderlineFont(infoPtr->hFont); 5155 infoPtr->hBoldUnderlineFont = TREEVIEW_CreateBoldUnderlineFont(infoPtr->hFont); 5156 infoPtr->hcurHand = LoadCursorW(NULL, (LPWSTR)IDC_HAND); 5157 5158 infoPtr->uItemHeight = TREEVIEW_NaturalHeight(infoPtr); 5159 5160 infoPtr->root = TREEVIEW_AllocateItem(infoPtr); 5161 infoPtr->root->state = TVIS_EXPANDED; 5162 infoPtr->root->iLevel = -1; 5163 infoPtr->root->visibleOrder = -1; 5164 5165 infoPtr->hwndNotify = lpcs->hwndParent; 5166 infoPtr->hwndToolTip = 0; 5167 5168 /* Determine what type of notify should be issued (sets infoPtr->bNtfUnicode) */ 5169 TREEVIEW_NotifyFormat(infoPtr, infoPtr->hwndNotify, NF_REQUERY); 5170 5171 if (!(infoPtr->dwStyle & TVS_NOTOOLTIPS)) 5172 infoPtr->hwndToolTip = CreateWindowExW(0, TOOLTIPS_CLASSW, NULL, WS_POPUP, 5173 CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, 5174 hwnd, 0, 0, 0); 5175 5176 /* Make sure actual scrollbar state is consistent with uInternalStatus */ 5177 ShowScrollBar(hwnd, SB_VERT, FALSE); 5178 ShowScrollBar(hwnd, SB_HORZ, FALSE); 5179 5180 OpenThemeData (hwnd, themeClass); 5181 5182 return 0; 5183 } 5184 5185 5186 static LRESULT 5187 TREEVIEW_Destroy(TREEVIEW_INFO *infoPtr) 5188 { 5189 TRACE("\n"); 5190 5191 /* free item data */ 5192 TREEVIEW_RemoveTree(infoPtr); 5193 /* root isn't freed with other items */ 5194 TREEVIEW_FreeItem(infoPtr, infoPtr->root); 5195 DPA_Destroy(infoPtr->items); 5196 5197 /* Restore original wndproc */ 5198 if (infoPtr->hwndEdit) 5199 SetWindowLongPtrW(infoPtr->hwndEdit, GWLP_WNDPROC, 5200 (DWORD_PTR)infoPtr->wpEditOrig); 5201 5202 CloseThemeData (GetWindowTheme (infoPtr->hwnd)); 5203 5204 /* Deassociate treeview from the window before doing anything drastic. */ 5205 SetWindowLongPtrW(infoPtr->hwnd, 0, 0); 5206 5207 DeleteObject(infoPtr->hDefaultFont); 5208 DeleteObject(infoPtr->hBoldFont); 5209 DeleteObject(infoPtr->hUnderlineFont); 5210 DeleteObject(infoPtr->hBoldUnderlineFont); 5211 DestroyWindow(infoPtr->hwndToolTip); 5212 heap_free(infoPtr); 5213 5214 return 0; 5215 } 5216 5217 /* Miscellaneous Messages ***********************************************/ 5218 5219 static LRESULT 5220 TREEVIEW_ScrollKeyDown(TREEVIEW_INFO *infoPtr, WPARAM key) 5221 { 5222 static const struct 5223 { 5224 unsigned char code; 5225 } 5226 scroll[] = 5227 { 5228 #define SCROLL_ENTRY(dir, code) { ((dir) << 7) | (code) } 5229 SCROLL_ENTRY(SB_VERT, SB_PAGEUP), /* VK_PRIOR */ 5230 SCROLL_ENTRY(SB_VERT, SB_PAGEDOWN), /* VK_NEXT */ 5231 SCROLL_ENTRY(SB_VERT, SB_BOTTOM), /* VK_END */ 5232 SCROLL_ENTRY(SB_VERT, SB_TOP), /* VK_HOME */ 5233 SCROLL_ENTRY(SB_HORZ, SB_LINEUP), /* VK_LEFT */ 5234 SCROLL_ENTRY(SB_VERT, SB_LINEUP), /* VK_UP */ 5235 SCROLL_ENTRY(SB_HORZ, SB_LINEDOWN), /* VK_RIGHT */ 5236 SCROLL_ENTRY(SB_VERT, SB_LINEDOWN) /* VK_DOWN */ 5237 #undef SCROLL_ENTRY 5238 }; 5239 5240 if (key >= VK_PRIOR && key <= VK_DOWN) 5241 { 5242 unsigned char code = scroll[key - VK_PRIOR].code; 5243 5244 (((code & (1 << 7)) == (SB_HORZ << 7)) 5245 ? TREEVIEW_HScroll 5246 : TREEVIEW_VScroll)(infoPtr, code & 0x7F); 5247 } 5248 5249 return 0; 5250 } 5251 5252 /************************************************************************ 5253 * TREEVIEW_KeyDown 5254 * 5255 * VK_UP Move selection to the previous non-hidden item. 5256 * VK_DOWN Move selection to the next non-hidden item. 5257 * VK_HOME Move selection to the first item. 5258 * VK_END Move selection to the last item. 5259 * VK_LEFT If expanded then collapse, otherwise move to parent. 5260 * VK_RIGHT If collapsed then expand, otherwise move to first child. 5261 * VK_ADD Expand. 5262 * VK_SUBTRACT Collapse. 5263 * VK_MULTIPLY Expand all. 5264 * VK_PRIOR Move up GetVisibleCount items. 5265 * VK_NEXT Move down GetVisibleCount items. 5266 * VK_BACK Move to parent. 5267 * CTRL-Left,Right,Up,Down,PgUp,PgDown,Home,End: Scroll without changing selection 5268 */ 5269 static LRESULT 5270 TREEVIEW_KeyDown(TREEVIEW_INFO *infoPtr, WPARAM wParam) 5271 { 5272 /* If it is non-NULL and different, it will be selected and visible. */ 5273 TREEVIEW_ITEM *newSelection = NULL; 5274 TREEVIEW_ITEM *prevItem = infoPtr->selectedItem; 5275 NMTVKEYDOWN nmkeydown; 5276 5277 TRACE("%lx\n", wParam); 5278 5279 nmkeydown.wVKey = wParam; 5280 nmkeydown.flags = 0; 5281 TREEVIEW_SendRealNotify(infoPtr, TVN_KEYDOWN, &nmkeydown.hdr); 5282 5283 if (prevItem == NULL) 5284 return FALSE; 5285 5286 if (GetAsyncKeyState(VK_CONTROL) & 0x8000) 5287 return TREEVIEW_ScrollKeyDown(infoPtr, wParam); 5288 5289 switch (wParam) 5290 { 5291 case VK_UP: 5292 newSelection = TREEVIEW_GetPrevListItem(infoPtr, prevItem); 5293 if (!newSelection) 5294 newSelection = infoPtr->root->firstChild; 5295 break; 5296 5297 case VK_DOWN: 5298 newSelection = TREEVIEW_GetNextListItem(infoPtr, prevItem); 5299 break; 5300 5301 case VK_RETURN: 5302 TREEVIEW_SendSimpleNotify(infoPtr, NM_RETURN); 5303 break; 5304 5305 case VK_HOME: 5306 newSelection = infoPtr->root->firstChild; 5307 break; 5308 5309 case VK_END: 5310 newSelection = TREEVIEW_GetLastListItem(infoPtr, infoPtr->root); 5311 break; 5312 5313 case VK_LEFT: 5314 if (prevItem->state & TVIS_EXPANDED) 5315 { 5316 TREEVIEW_Collapse(infoPtr, prevItem, FALSE, TRUE); 5317 } 5318 else if (prevItem->parent != infoPtr->root) 5319 { 5320 newSelection = prevItem->parent; 5321 } 5322 break; 5323 5324 case VK_RIGHT: 5325 if (TREEVIEW_HasChildren(infoPtr, prevItem)) 5326 { 5327 if (!(prevItem->state & TVIS_EXPANDED)) 5328 TREEVIEW_Expand(infoPtr, prevItem, FALSE, TRUE); 5329 else 5330 { 5331 newSelection = prevItem->firstChild; 5332 } 5333 } 5334 5335 break; 5336 5337 case VK_MULTIPLY: 5338 TREEVIEW_ExpandAll(infoPtr, prevItem); 5339 break; 5340 5341 case VK_ADD: 5342 TREEVIEW_Expand(infoPtr, prevItem, FALSE, TRUE); 5343 break; 5344 5345 case VK_SUBTRACT: 5346 TREEVIEW_Collapse(infoPtr, prevItem, FALSE, TRUE); 5347 break; 5348 5349 case VK_PRIOR: 5350 newSelection 5351 = TREEVIEW_GetListItem(infoPtr, prevItem, 5352 -TREEVIEW_GetVisibleCount(infoPtr)); 5353 break; 5354 5355 case VK_NEXT: 5356 newSelection 5357 = TREEVIEW_GetListItem(infoPtr, prevItem, 5358 TREEVIEW_GetVisibleCount(infoPtr)); 5359 break; 5360 5361 case VK_BACK: 5362 newSelection = prevItem->parent; 5363 if (newSelection == infoPtr->root) 5364 newSelection = NULL; 5365 break; 5366 5367 case VK_SPACE: 5368 if (infoPtr->dwStyle & TVS_CHECKBOXES) 5369 TREEVIEW_ToggleItemState(infoPtr, prevItem); 5370 break; 5371 } 5372 5373 if (newSelection && newSelection != prevItem) 5374 { 5375 if (TREEVIEW_DoSelectItem(infoPtr, TVGN_CARET, newSelection, 5376 TVC_BYKEYBOARD)) 5377 { 5378 TREEVIEW_EnsureVisible(infoPtr, newSelection, FALSE); 5379 } 5380 } 5381 5382 return FALSE; 5383 } 5384 5385 static LRESULT 5386 TREEVIEW_MouseLeave (TREEVIEW_INFO * infoPtr) 5387 { 5388 /* remove hot effect from item */ 5389 TREEVIEW_InvalidateItem(infoPtr, infoPtr->hotItem); 5390 infoPtr->hotItem = NULL; 5391 5392 return 0; 5393 } 5394 5395 static LRESULT 5396 TREEVIEW_MouseMove (TREEVIEW_INFO * infoPtr, LPARAM lParam) 5397 { 5398 TRACKMOUSEEVENT trackinfo; 5399 TREEVIEW_ITEM * item; 5400 TVHITTESTINFO ht; 5401 BOOL item_hit; 5402 5403 if (!(infoPtr->dwStyle & TVS_TRACKSELECT)) return 0; 5404 5405 /* fill in the TRACKMOUSEEVENT struct */ 5406 trackinfo.cbSize = sizeof(TRACKMOUSEEVENT); 5407 trackinfo.dwFlags = TME_QUERY; 5408 trackinfo.hwndTrack = infoPtr->hwnd; 5409 5410 /* call _TrackMouseEvent to see if we are currently tracking for this hwnd */ 5411 _TrackMouseEvent(&trackinfo); 5412 5413 /* Make sure tracking is enabled so we receive a WM_MOUSELEAVE message */ 5414 if(!(trackinfo.dwFlags & TME_LEAVE)) 5415 { 5416 trackinfo.dwFlags = TME_LEAVE; /* notify upon leaving */ 5417 trackinfo.hwndTrack = infoPtr->hwnd; 5418 /* do it as fast as possible, minimal systimer latency will be used */ 5419 trackinfo.dwHoverTime = 1; 5420 5421 /* call TRACKMOUSEEVENT so we receive a WM_MOUSELEAVE message */ 5422 /* and can properly deactivate the hot item */ 5423 _TrackMouseEvent(&trackinfo); 5424 } 5425 5426 ht.pt.x = (short)LOWORD(lParam); 5427 ht.pt.y = (short)HIWORD(lParam); 5428 5429 item = TREEVIEW_HitTest(infoPtr, &ht); 5430 item_hit = TREEVIEW_IsItemHit(infoPtr, &ht); 5431 if ((item != infoPtr->hotItem) || !item_hit) 5432 { 5433 /* redraw old hot item */ 5434 TREEVIEW_InvalidateItem(infoPtr, infoPtr->hotItem); 5435 infoPtr->hotItem = NULL; 5436 if (item && item_hit) 5437 { 5438 infoPtr->hotItem = item; 5439 /* redraw new hot item */ 5440 TREEVIEW_InvalidateItem(infoPtr, infoPtr->hotItem); 5441 } 5442 } 5443 5444 return 0; 5445 } 5446 5447 /* Draw themed border */ 5448 static BOOL TREEVIEW_NCPaint (const TREEVIEW_INFO *infoPtr, HRGN region, LPARAM lParam) 5449 { 5450 HTHEME theme = GetWindowTheme (infoPtr->hwnd); 5451 HDC dc; 5452 RECT r; 5453 HRGN cliprgn; 5454 int cxEdge = GetSystemMetrics (SM_CXEDGE), 5455 cyEdge = GetSystemMetrics (SM_CYEDGE); 5456 5457 if (!theme) 5458 return DefWindowProcW (infoPtr->hwnd, WM_NCPAINT, (WPARAM)region, lParam); 5459 5460 GetWindowRect(infoPtr->hwnd, &r); 5461 5462 cliprgn = CreateRectRgn (r.left + cxEdge, r.top + cyEdge, 5463 r.right - cxEdge, r.bottom - cyEdge); 5464 if (region != (HRGN)1) 5465 CombineRgn (cliprgn, cliprgn, region, RGN_AND); 5466 OffsetRect(&r, -r.left, -r.top); 5467 5468 #ifdef __REACTOS__ /* r73789 */ 5469 dc = GetWindowDC(infoPtr->hwnd); 5470 /* Exclude client part */ 5471 ExcludeClipRect(dc, r.left + cxEdge, r.top + cyEdge, 5472 r.right - cxEdge, r.bottom -cyEdge); 5473 #else 5474 dc = GetDCEx(infoPtr->hwnd, region, DCX_WINDOW|DCX_INTERSECTRGN); 5475 OffsetRect(&r, -r.left, -r.top); 5476 #endif 5477 5478 if (IsThemeBackgroundPartiallyTransparent (theme, 0, 0)) 5479 DrawThemeParentBackground(infoPtr->hwnd, dc, &r); 5480 DrawThemeBackground (theme, dc, 0, 0, &r, 0); 5481 ReleaseDC(infoPtr->hwnd, dc); 5482 5483 /* Call default proc to get the scrollbars etc. painted */ 5484 DefWindowProcW (infoPtr->hwnd, WM_NCPAINT, (WPARAM)cliprgn, 0); 5485 5486 return TRUE; 5487 } 5488 5489 static LRESULT 5490 TREEVIEW_Notify(const TREEVIEW_INFO *infoPtr, WPARAM wParam, LPARAM lParam) 5491 { 5492 LPNMHDR lpnmh = (LPNMHDR)lParam; 5493 5494 if (lpnmh->code == PGN_CALCSIZE) { 5495 LPNMPGCALCSIZE lppgc = (LPNMPGCALCSIZE)lParam; 5496 5497 if (lppgc->dwFlag == PGF_CALCWIDTH) { 5498 lppgc->iWidth = infoPtr->treeWidth; 5499 TRACE("got PGN_CALCSIZE, returning horz size = %d, client=%d\n", 5500 infoPtr->treeWidth, infoPtr->clientWidth); 5501 } 5502 else { 5503 lppgc->iHeight = infoPtr->treeHeight; 5504 TRACE("got PGN_CALCSIZE, returning vert size = %d, client=%d\n", 5505 infoPtr->treeHeight, infoPtr->clientHeight); 5506 } 5507 return 0; 5508 } 5509 return DefWindowProcW(infoPtr->hwnd, WM_NOTIFY, wParam, lParam); 5510 } 5511 5512 static LRESULT 5513 TREEVIEW_Size(TREEVIEW_INFO *infoPtr, WPARAM wParam, LPARAM lParam) 5514 { 5515 if (wParam == SIZE_RESTORED) 5516 { 5517 infoPtr->clientWidth = (short)LOWORD(lParam); 5518 infoPtr->clientHeight = (short)HIWORD(lParam); 5519 5520 TREEVIEW_RecalculateVisibleOrder(infoPtr, NULL); 5521 TREEVIEW_SetFirstVisible(infoPtr, infoPtr->firstVisible, TRUE); 5522 TREEVIEW_UpdateScrollBars(infoPtr); 5523 } 5524 else 5525 { 5526 FIXME("WM_SIZE flag %lx %lx not handled\n", wParam, lParam); 5527 } 5528 5529 TREEVIEW_Invalidate(infoPtr, NULL); 5530 return 0; 5531 } 5532 5533 static LRESULT 5534 TREEVIEW_StyleChanged(TREEVIEW_INFO *infoPtr, WPARAM wParam, LPARAM lParam) 5535 { 5536 TRACE("(%lx %lx)\n", wParam, lParam); 5537 5538 if (wParam == GWL_STYLE) 5539 { 5540 DWORD dwNewStyle = ((LPSTYLESTRUCT)lParam)->styleNew; 5541 5542 if ((infoPtr->dwStyle ^ dwNewStyle) & TVS_CHECKBOXES) 5543 { 5544 if (dwNewStyle & TVS_CHECKBOXES) 5545 { 5546 TREEVIEW_InitCheckboxes(infoPtr); 5547 TRACE("checkboxes enabled\n"); 5548 5549 /* set all items to state image index 1 */ 5550 TREEVIEW_ResetImageStateIndex(infoPtr, infoPtr->root); 5551 } 5552 else 5553 { 5554 FIXME("tried to disable checkboxes\n"); 5555 } 5556 } 5557 5558 if ((infoPtr->dwStyle ^ dwNewStyle) & TVS_NOTOOLTIPS) 5559 { 5560 if (infoPtr->dwStyle & TVS_NOTOOLTIPS) 5561 { 5562 infoPtr->hwndToolTip = COMCTL32_CreateToolTip(infoPtr->hwnd); 5563 TRACE("tooltips enabled\n"); 5564 } 5565 else 5566 { 5567 DestroyWindow(infoPtr->hwndToolTip); 5568 infoPtr->hwndToolTip = 0; 5569 TRACE("tooltips disabled\n"); 5570 } 5571 } 5572 5573 infoPtr->dwStyle = dwNewStyle; 5574 } 5575 5576 TREEVIEW_EndEditLabelNow(infoPtr, TRUE); 5577 TREEVIEW_UpdateSubTree(infoPtr, infoPtr->root); 5578 TREEVIEW_UpdateScrollBars(infoPtr); 5579 TREEVIEW_Invalidate(infoPtr, NULL); 5580 5581 return 0; 5582 } 5583 5584 static LRESULT 5585 TREEVIEW_SetCursor(const TREEVIEW_INFO *infoPtr, WPARAM wParam, LPARAM lParam) 5586 { 5587 TREEVIEW_ITEM * item; 5588 TVHITTESTINFO ht; 5589 NMMOUSE nmmouse; 5590 5591 GetCursorPos(&ht.pt); 5592 ScreenToClient(infoPtr->hwnd, &ht.pt); 5593 5594 item = TREEVIEW_HitTest(infoPtr, &ht); 5595 5596 memset(&nmmouse, 0, sizeof(nmmouse)); 5597 if (item) 5598 { 5599 nmmouse.dwItemSpec = (DWORD_PTR)item; 5600 nmmouse.dwItemData = item->lParam; 5601 } 5602 nmmouse.pt.x = 0; 5603 nmmouse.pt.y = 0; 5604 nmmouse.dwHitInfo = lParam; 5605 if (TREEVIEW_SendRealNotify(infoPtr, NM_SETCURSOR, &nmmouse.hdr)) 5606 return 0; 5607 5608 if (item && (infoPtr->dwStyle & TVS_TRACKSELECT) && TREEVIEW_IsItemHit(infoPtr, &ht)) 5609 { 5610 SetCursor(infoPtr->hcurHand); 5611 return 0; 5612 } 5613 else 5614 return DefWindowProcW(infoPtr->hwnd, WM_SETCURSOR, wParam, lParam); 5615 } 5616 5617 static LRESULT 5618 TREEVIEW_SetFocus(TREEVIEW_INFO *infoPtr) 5619 { 5620 TRACE("\n"); 5621 5622 if (!infoPtr->selectedItem) 5623 { 5624 TREEVIEW_DoSelectItem(infoPtr, TVGN_CARET, infoPtr->firstVisible, 5625 TVC_UNKNOWN); 5626 } 5627 5628 TREEVIEW_Invalidate(infoPtr, infoPtr->selectedItem); 5629 TREEVIEW_SendSimpleNotify(infoPtr, NM_SETFOCUS); 5630 return 0; 5631 } 5632 5633 static LRESULT 5634 TREEVIEW_KillFocus(const TREEVIEW_INFO *infoPtr) 5635 { 5636 TRACE("\n"); 5637 5638 TREEVIEW_Invalidate(infoPtr, infoPtr->selectedItem); 5639 UpdateWindow(infoPtr->hwnd); 5640 TREEVIEW_SendSimpleNotify(infoPtr, NM_KILLFOCUS); 5641 return 0; 5642 } 5643 5644 /* update theme after a WM_THEMECHANGED message */ 5645 static LRESULT TREEVIEW_ThemeChanged(const TREEVIEW_INFO *infoPtr) 5646 { 5647 HTHEME theme = GetWindowTheme (infoPtr->hwnd); 5648 CloseThemeData (theme); 5649 OpenThemeData (infoPtr->hwnd, themeClass); 5650 return 0; 5651 } 5652 5653 5654 static LRESULT WINAPI 5655 TREEVIEW_WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) 5656 { 5657 TREEVIEW_INFO *infoPtr = TREEVIEW_GetInfoPtr(hwnd); 5658 5659 TRACE("hwnd %p msg %04x wp=%08lx lp=%08lx\n", hwnd, uMsg, wParam, lParam); 5660 5661 if (infoPtr) TREEVIEW_VerifyTree(infoPtr); 5662 else 5663 { 5664 if (uMsg == WM_CREATE) 5665 TREEVIEW_Create(hwnd, (LPCREATESTRUCTW)lParam); 5666 else 5667 goto def; 5668 } 5669 5670 switch (uMsg) 5671 { 5672 case TVM_CREATEDRAGIMAGE: 5673 return TREEVIEW_CreateDragImage(infoPtr, lParam); 5674 5675 case TVM_DELETEITEM: 5676 return TREEVIEW_DeleteItem(infoPtr, (HTREEITEM)lParam); 5677 5678 case TVM_EDITLABELA: 5679 case TVM_EDITLABELW: 5680 return (LRESULT)TREEVIEW_EditLabel(infoPtr, (HTREEITEM)lParam); 5681 5682 case TVM_ENDEDITLABELNOW: 5683 return TREEVIEW_EndEditLabelNow(infoPtr, (BOOL)wParam); 5684 5685 case TVM_ENSUREVISIBLE: 5686 return TREEVIEW_EnsureVisible(infoPtr, (HTREEITEM)lParam, TRUE); 5687 5688 case TVM_EXPAND: 5689 return TREEVIEW_ExpandMsg(infoPtr, (UINT)wParam, (HTREEITEM)lParam); 5690 5691 case TVM_GETBKCOLOR: 5692 return TREEVIEW_GetBkColor(infoPtr); 5693 5694 case TVM_GETCOUNT: 5695 return TREEVIEW_GetCount(infoPtr); 5696 5697 case TVM_GETEDITCONTROL: 5698 return TREEVIEW_GetEditControl(infoPtr); 5699 5700 case TVM_GETIMAGELIST: 5701 return TREEVIEW_GetImageList(infoPtr, wParam); 5702 5703 case TVM_GETINDENT: 5704 return TREEVIEW_GetIndent(infoPtr); 5705 5706 case TVM_GETINSERTMARKCOLOR: 5707 return TREEVIEW_GetInsertMarkColor(infoPtr); 5708 5709 case TVM_GETISEARCHSTRINGA: 5710 FIXME("Unimplemented msg TVM_GETISEARCHSTRINGA\n"); 5711 return 0; 5712 5713 case TVM_GETISEARCHSTRINGW: 5714 FIXME("Unimplemented msg TVM_GETISEARCHSTRINGW\n"); 5715 return 0; 5716 5717 case TVM_GETITEMA: 5718 case TVM_GETITEMW: 5719 return TREEVIEW_GetItemT(infoPtr, (LPTVITEMEXW)lParam, 5720 uMsg == TVM_GETITEMW); 5721 case TVM_GETITEMHEIGHT: 5722 return TREEVIEW_GetItemHeight(infoPtr); 5723 5724 case TVM_GETITEMRECT: 5725 return TREEVIEW_GetItemRect(infoPtr, (BOOL)wParam, (LPRECT)lParam); 5726 5727 case TVM_GETITEMSTATE: 5728 return TREEVIEW_GetItemState(infoPtr, (HTREEITEM)wParam, (UINT)lParam); 5729 5730 case TVM_GETLINECOLOR: 5731 return TREEVIEW_GetLineColor(infoPtr); 5732 5733 case TVM_GETNEXTITEM: 5734 return TREEVIEW_GetNextItem(infoPtr, (UINT)wParam, (HTREEITEM)lParam); 5735 5736 case TVM_GETSCROLLTIME: 5737 return TREEVIEW_GetScrollTime(infoPtr); 5738 5739 case TVM_GETTEXTCOLOR: 5740 return TREEVIEW_GetTextColor(infoPtr); 5741 5742 case TVM_GETTOOLTIPS: 5743 return TREEVIEW_GetToolTips(infoPtr); 5744 5745 case TVM_GETUNICODEFORMAT: 5746 return TREEVIEW_GetUnicodeFormat(infoPtr); 5747 5748 case TVM_GETVISIBLECOUNT: 5749 return TREEVIEW_GetVisibleCount(infoPtr); 5750 5751 case TVM_HITTEST: 5752 return (LRESULT)TREEVIEW_HitTest(infoPtr, (TVHITTESTINFO*)lParam); 5753 5754 case TVM_INSERTITEMA: 5755 case TVM_INSERTITEMW: 5756 return TREEVIEW_InsertItemT(infoPtr, (LPTVINSERTSTRUCTW)lParam, 5757 uMsg == TVM_INSERTITEMW); 5758 case TVM_SELECTITEM: 5759 return TREEVIEW_SelectItem(infoPtr, (INT)wParam, (HTREEITEM)lParam); 5760 5761 case TVM_SETBKCOLOR: 5762 return TREEVIEW_SetBkColor(infoPtr, (COLORREF)lParam); 5763 5764 case TVM_SETIMAGELIST: 5765 return TREEVIEW_SetImageList(infoPtr, wParam, (HIMAGELIST)lParam); 5766 5767 case TVM_SETINDENT: 5768 return TREEVIEW_SetIndent(infoPtr, (UINT)wParam); 5769 5770 case TVM_SETINSERTMARK: 5771 return TREEVIEW_SetInsertMark(infoPtr, (BOOL)wParam, (HTREEITEM)lParam); 5772 5773 case TVM_SETINSERTMARKCOLOR: 5774 return TREEVIEW_SetInsertMarkColor(infoPtr, (COLORREF)lParam); 5775 5776 case TVM_SETITEMA: 5777 case TVM_SETITEMW: 5778 return TREEVIEW_SetItemT(infoPtr, (LPTVITEMEXW)lParam, 5779 uMsg == TVM_SETITEMW); 5780 case TVM_SETLINECOLOR: 5781 return TREEVIEW_SetLineColor(infoPtr, (COLORREF)lParam); 5782 5783 case TVM_SETITEMHEIGHT: 5784 return TREEVIEW_SetItemHeight(infoPtr, (INT)(SHORT)wParam); 5785 5786 case TVM_SETSCROLLTIME: 5787 return TREEVIEW_SetScrollTime(infoPtr, (UINT)wParam); 5788 5789 case TVM_SETTEXTCOLOR: 5790 return TREEVIEW_SetTextColor(infoPtr, (COLORREF)lParam); 5791 5792 case TVM_SETTOOLTIPS: 5793 return TREEVIEW_SetToolTips(infoPtr, (HWND)wParam); 5794 5795 case TVM_SETUNICODEFORMAT: 5796 return TREEVIEW_SetUnicodeFormat(infoPtr, (BOOL)wParam); 5797 5798 case TVM_SORTCHILDREN: 5799 return TREEVIEW_SortChildren(infoPtr, lParam); 5800 5801 case TVM_SORTCHILDRENCB: 5802 return TREEVIEW_SortChildrenCB(infoPtr, (LPTVSORTCB)lParam); 5803 5804 case WM_CHAR: 5805 return TREEVIEW_ProcessLetterKeys(infoPtr, wParam, lParam); 5806 5807 case WM_COMMAND: 5808 return TREEVIEW_Command(infoPtr, wParam, lParam); 5809 5810 case WM_DESTROY: 5811 return TREEVIEW_Destroy(infoPtr); 5812 5813 /* WM_ENABLE */ 5814 5815 case WM_ERASEBKGND: 5816 return TREEVIEW_EraseBackground(infoPtr, (HDC)wParam); 5817 5818 case WM_GETDLGCODE: 5819 return DLGC_WANTARROWS | DLGC_WANTCHARS; 5820 5821 case WM_GETFONT: 5822 return TREEVIEW_GetFont(infoPtr); 5823 5824 case WM_HSCROLL: 5825 return TREEVIEW_HScroll(infoPtr, wParam); 5826 5827 case WM_KEYDOWN: 5828 #ifndef __REACTOS__ 5829 case WM_SYSKEYDOWN: 5830 #endif 5831 return TREEVIEW_KeyDown(infoPtr, wParam); 5832 5833 case WM_KILLFOCUS: 5834 return TREEVIEW_KillFocus(infoPtr); 5835 5836 case WM_LBUTTONDBLCLK: 5837 return TREEVIEW_LButtonDoubleClick(infoPtr, lParam); 5838 5839 case WM_LBUTTONDOWN: 5840 return TREEVIEW_LButtonDown(infoPtr, lParam); 5841 5842 /* WM_MBUTTONDOWN */ 5843 5844 case WM_MOUSELEAVE: 5845 return TREEVIEW_MouseLeave(infoPtr); 5846 5847 case WM_MOUSEMOVE: 5848 return TREEVIEW_MouseMove(infoPtr, lParam); 5849 5850 case WM_NCLBUTTONDOWN: 5851 if (infoPtr->hwndEdit) 5852 SetFocus(infoPtr->hwnd); 5853 goto def; 5854 5855 case WM_NCPAINT: 5856 return TREEVIEW_NCPaint (infoPtr, (HRGN)wParam, lParam); 5857 5858 case WM_NOTIFY: 5859 return TREEVIEW_Notify(infoPtr, wParam, lParam); 5860 5861 case WM_NOTIFYFORMAT: 5862 return TREEVIEW_NotifyFormat(infoPtr, (HWND)wParam, (UINT)lParam); 5863 5864 case WM_PRINTCLIENT: 5865 return TREEVIEW_PrintClient(infoPtr, (HDC)wParam, lParam); 5866 5867 case WM_PAINT: 5868 return TREEVIEW_Paint(infoPtr, (HDC)wParam); 5869 5870 case WM_RBUTTONDOWN: 5871 return TREEVIEW_RButtonDown(infoPtr, lParam); 5872 5873 case WM_SETCURSOR: 5874 return TREEVIEW_SetCursor(infoPtr, wParam, lParam); 5875 5876 case WM_SETFOCUS: 5877 return TREEVIEW_SetFocus(infoPtr); 5878 5879 case WM_SETFONT: 5880 return TREEVIEW_SetFont(infoPtr, (HFONT)wParam, (BOOL)lParam); 5881 5882 case WM_SETREDRAW: 5883 return TREEVIEW_SetRedraw(infoPtr, wParam); 5884 5885 case WM_SIZE: 5886 return TREEVIEW_Size(infoPtr, wParam, lParam); 5887 5888 case WM_STYLECHANGED: 5889 return TREEVIEW_StyleChanged(infoPtr, wParam, lParam); 5890 5891 case WM_SYSCOLORCHANGE: 5892 COMCTL32_RefreshSysColors(); 5893 return 0; 5894 5895 case WM_TIMER: 5896 return TREEVIEW_HandleTimer(infoPtr, wParam); 5897 5898 case WM_THEMECHANGED: 5899 return TREEVIEW_ThemeChanged (infoPtr); 5900 5901 case WM_VSCROLL: 5902 return TREEVIEW_VScroll(infoPtr, wParam); 5903 5904 /* WM_WININICHANGE */ 5905 5906 case WM_MOUSEWHEEL: 5907 return TREEVIEW_MouseWheel(infoPtr, wParam, lParam); 5908 5909 case WM_DRAWITEM: 5910 TRACE("drawItem\n"); 5911 goto def; 5912 5913 default: 5914 /* This mostly catches MFC and Delphi messages. :( */ 5915 if ((uMsg >= WM_USER) && (uMsg < WM_APP) && !COMCTL32_IsReflectedMessage(uMsg)) 5916 TRACE("Unknown msg %04x wp=%08lx lp=%08lx\n", uMsg, wParam, lParam); 5917 def: 5918 return DefWindowProcW(hwnd, uMsg, wParam, lParam); 5919 } 5920 } 5921 5922 5923 /* Class Registration ***************************************************/ 5924 5925 VOID 5926 TREEVIEW_Register(void) 5927 { 5928 WNDCLASSW wndClass; 5929 5930 TRACE("\n"); 5931 5932 ZeroMemory(&wndClass, sizeof(WNDCLASSW)); 5933 wndClass.style = CS_GLOBALCLASS | CS_DBLCLKS; 5934 wndClass.lpfnWndProc = TREEVIEW_WindowProc; 5935 wndClass.cbClsExtra = 0; 5936 wndClass.cbWndExtra = sizeof(TREEVIEW_INFO *); 5937 5938 wndClass.hCursor = LoadCursorW(0, (LPWSTR)IDC_ARROW); 5939 wndClass.hbrBackground = 0; 5940 wndClass.lpszClassName = WC_TREEVIEWW; 5941 5942 RegisterClassW(&wndClass); 5943 } 5944 5945 5946 VOID 5947 TREEVIEW_Unregister(void) 5948 { 5949 UnregisterClassW(WC_TREEVIEWW, NULL); 5950 } 5951 5952 5953 /* Tree Verification ****************************************************/ 5954 5955 static inline void 5956 TREEVIEW_VerifyChildren(TREEVIEW_INFO *infoPtr, const TREEVIEW_ITEM *item); 5957 5958 static inline void TREEVIEW_VerifyItemCommon(TREEVIEW_INFO *infoPtr, 5959 const TREEVIEW_ITEM *item) 5960 { 5961 assert(infoPtr != NULL); 5962 assert(item != NULL); 5963 5964 /* both NULL, or both non-null */ 5965 assert((item->firstChild == NULL) == (item->lastChild == NULL)); 5966 5967 assert(item->firstChild != item); 5968 assert(item->lastChild != item); 5969 5970 if (item->firstChild) 5971 { 5972 assert(item->firstChild->parent == item); 5973 assert(item->firstChild->prevSibling == NULL); 5974 } 5975 5976 if (item->lastChild) 5977 { 5978 assert(item->lastChild->parent == item); 5979 assert(item->lastChild->nextSibling == NULL); 5980 } 5981 5982 assert(item->nextSibling != item); 5983 if (item->nextSibling) 5984 { 5985 assert(item->nextSibling->parent == item->parent); 5986 assert(item->nextSibling->prevSibling == item); 5987 } 5988 5989 assert(item->prevSibling != item); 5990 if (item->prevSibling) 5991 { 5992 assert(item->prevSibling->parent == item->parent); 5993 assert(item->prevSibling->nextSibling == item); 5994 } 5995 } 5996 5997 static inline void 5998 TREEVIEW_VerifyItem(TREEVIEW_INFO *infoPtr, const TREEVIEW_ITEM *item) 5999 { 6000 assert(item != NULL); 6001 6002 assert(item->parent != NULL); 6003 assert(item->parent != item); 6004 assert(item->iLevel == item->parent->iLevel + 1); 6005 6006 assert(DPA_GetPtrIndex(infoPtr->items, item) != -1); 6007 6008 TREEVIEW_VerifyItemCommon(infoPtr, item); 6009 6010 TREEVIEW_VerifyChildren(infoPtr, item); 6011 } 6012 6013 static inline void 6014 TREEVIEW_VerifyChildren(TREEVIEW_INFO *infoPtr, const TREEVIEW_ITEM *item) 6015 { 6016 const TREEVIEW_ITEM *child; 6017 assert(item != NULL); 6018 6019 for (child = item->firstChild; child != NULL; child = child->nextSibling) 6020 TREEVIEW_VerifyItem(infoPtr, child); 6021 } 6022 6023 static inline void 6024 TREEVIEW_VerifyRoot(TREEVIEW_INFO *infoPtr) 6025 { 6026 TREEVIEW_ITEM *root = infoPtr->root; 6027 6028 assert(root != NULL); 6029 assert(root->iLevel == -1); 6030 assert(root->parent == NULL); 6031 assert(root->prevSibling == NULL); 6032 6033 TREEVIEW_VerifyItemCommon(infoPtr, root); 6034 6035 TREEVIEW_VerifyChildren(infoPtr, root); 6036 } 6037 6038 static void 6039 TREEVIEW_VerifyTree(TREEVIEW_INFO *infoPtr) 6040 { 6041 if (!TRACE_ON(treeview)) return; 6042 6043 assert(infoPtr != NULL); 6044 TREEVIEW_VerifyRoot(infoPtr); 6045 } 6046