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