1 /* 2 * Tab control 3 * 4 * Copyright 1998 Anders Carlsson 5 * Copyright 1999 Alex Priem <alexp@sci.kun.nl> 6 * Copyright 1999 Francis Beaudet 7 * Copyright 2003 Vitaliy Margolen 8 * 9 * This library is free software; you can redistribute it and/or 10 * modify it under the terms of the GNU Lesser General Public 11 * License as published by the Free Software Foundation; either 12 * version 2.1 of the License, or (at your option) any later version. 13 * 14 * This library is distributed in the hope that it will be useful, 15 * but WITHOUT ANY WARRANTY; without even the implied warranty of 16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 17 * Lesser General Public License for more details. 18 * 19 * You should have received a copy of the GNU Lesser General Public 20 * License along with this library; if not, write to the Free Software 21 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA 22 * 23 * NOTES 24 * 25 * This code was audited for completeness against the documented features 26 * of Comctl32.dll version 6.0 on May. 20, 2005, by James Hawkins. 27 * 28 * Unless otherwise noted, we believe this code to be complete, as per 29 * the specification mentioned above. 30 * If you discover missing features, or bugs, please note them below. 31 * 32 * TODO: 33 * 34 * Styles: 35 * TCS_MULTISELECT - implement for VK_SPACE selection 36 * TCS_RIGHT 37 * TCS_RIGHTJUSTIFY 38 * TCS_SCROLLOPPOSITE 39 * TCS_SINGLELINE 40 * TCIF_RTLREADING 41 * 42 * Extended Styles: 43 * TCS_EX_REGISTERDROP 44 * 45 * Notifications: 46 * NM_RELEASEDCAPTURE 47 * TCN_FOCUSCHANGE 48 * TCN_GETOBJECT 49 * 50 * Macros: 51 * TabCtrl_AdjustRect 52 * 53 */ 54 55 #include <assert.h> 56 #include <stdarg.h> 57 #include <string.h> 58 59 #include "windef.h" 60 #include "winbase.h" 61 #include "wingdi.h" 62 #include "winuser.h" 63 #include "winnls.h" 64 #include "commctrl.h" 65 #include "comctl32.h" 66 #include "uxtheme.h" 67 #include "vssym32.h" 68 #include "wine/debug.h" 69 #include <math.h> 70 71 WINE_DEFAULT_DEBUG_CHANNEL(tab); 72 73 typedef struct 74 { 75 DWORD dwState; 76 LPWSTR pszText; 77 INT iImage; 78 RECT rect; /* bounding rectangle of the item relative to the 79 * leftmost item (the leftmost item, 0, would have a 80 * "left" member of 0 in this rectangle) 81 * 82 * additionally the top member holds the row number 83 * and bottom is unused and should be 0 */ 84 BYTE extra[1]; /* Space for caller supplied info, variable size */ 85 } TAB_ITEM; 86 87 /* The size of a tab item depends on how much extra data is requested. 88 TCM_INSERTITEM always stores at least LPARAM sized data. */ 89 #define EXTRA_ITEM_SIZE(infoPtr) (max((infoPtr)->cbInfo, sizeof(LPARAM))) 90 #define TAB_ITEM_SIZE(infoPtr) FIELD_OFFSET(TAB_ITEM, extra[EXTRA_ITEM_SIZE(infoPtr)]) 91 92 typedef struct 93 { 94 HWND hwnd; /* Tab control window */ 95 HWND hwndNotify; /* notification window (parent) */ 96 UINT uNumItem; /* number of tab items */ 97 UINT uNumRows; /* number of tab rows */ 98 INT tabHeight; /* height of the tab row */ 99 INT tabWidth; /* width of tabs */ 100 INT tabMinWidth; /* minimum width of items */ 101 USHORT uHItemPadding; /* amount of horizontal padding, in pixels */ 102 USHORT uVItemPadding; /* amount of vertical padding, in pixels */ 103 USHORT uHItemPadding_s; /* Set amount of horizontal padding, in pixels */ 104 USHORT uVItemPadding_s; /* Set amount of vertical padding, in pixels */ 105 HFONT hFont; /* handle to the current font */ 106 HCURSOR hcurArrow; /* handle to the current cursor */ 107 HIMAGELIST himl; /* handle to an image list (may be 0) */ 108 HWND hwndToolTip; /* handle to tab's tooltip */ 109 INT leftmostVisible; /* Used for scrolling, this member contains 110 * the index of the first visible item */ 111 INT iSelected; /* the currently selected item */ 112 INT iHotTracked; /* the highlighted item under the mouse */ 113 INT uFocus; /* item which has the focus */ 114 BOOL DoRedraw; /* flag for redrawing when tab contents is changed*/ 115 BOOL needsScrolling; /* TRUE if the size of the tabs is greater than 116 * the size of the control */ 117 BOOL fHeightSet; /* was the height of the tabs explicitly set? */ 118 BOOL bUnicode; /* Unicode control? */ 119 HWND hwndUpDown; /* Updown control used for scrolling */ 120 INT cbInfo; /* Number of bytes of caller supplied info per tab */ 121 122 DWORD exStyle; /* Extended style used, currently: 123 TCS_EX_FLATSEPARATORS, TCS_EX_REGISTERDROP */ 124 DWORD dwStyle; /* the cached window GWL_STYLE */ 125 126 HDPA items; /* dynamic array of TAB_ITEM* pointers */ 127 } TAB_INFO; 128 129 /****************************************************************************** 130 * Positioning constants 131 */ 132 #define SELECTED_TAB_OFFSET 2 133 #define ROUND_CORNER_SIZE 2 134 #define DISPLAY_AREA_PADDINGX 2 135 #define DISPLAY_AREA_PADDINGY 2 136 #define CONTROL_BORDER_SIZEX 2 137 #define CONTROL_BORDER_SIZEY 2 138 #define BUTTON_SPACINGX 3 139 #define BUTTON_SPACINGY 3 140 #define FLAT_BTN_SPACINGX 8 141 #define DEFAULT_MIN_TAB_WIDTH 54 142 #define DEFAULT_PADDING_X 6 143 #define EXTRA_ICON_PADDING 3 144 145 #define TAB_GetInfoPtr(hwnd) ((TAB_INFO *)GetWindowLongPtrW(hwnd,0)) 146 147 #define GET_DEFAULT_MIN_TAB_WIDTH(infoPtr) (DEFAULT_MIN_TAB_WIDTH - (DEFAULT_PADDING_X - (infoPtr)->uHItemPadding) * 2) 148 149 /****************************************************************************** 150 * Hot-tracking timer constants 151 */ 152 #define TAB_HOTTRACK_TIMER 1 153 #define TAB_HOTTRACK_TIMER_INTERVAL 100 /* milliseconds */ 154 155 static const WCHAR themeClass[] = { 'T','a','b',0 }; 156 157 static inline TAB_ITEM* TAB_GetItem(const TAB_INFO *infoPtr, INT i) 158 { 159 assert(i >= 0 && i < infoPtr->uNumItem); 160 return DPA_GetPtr(infoPtr->items, i); 161 } 162 163 /****************************************************************************** 164 * Prototypes 165 */ 166 static void TAB_InvalidateTabArea(const TAB_INFO *); 167 static void TAB_EnsureSelectionVisible(TAB_INFO *); 168 static void TAB_DrawItemInterior(const TAB_INFO *, HDC, INT, RECT*); 169 static LRESULT TAB_DeselectAll(TAB_INFO *, BOOL); 170 static BOOL TAB_InternalGetItemRect(const TAB_INFO *, INT, RECT*, RECT*); 171 172 static BOOL 173 TAB_SendSimpleNotify (const TAB_INFO *infoPtr, UINT code) 174 { 175 NMHDR nmhdr; 176 177 nmhdr.hwndFrom = infoPtr->hwnd; 178 nmhdr.idFrom = GetWindowLongPtrW(infoPtr->hwnd, GWLP_ID); 179 nmhdr.code = code; 180 181 return (BOOL) SendMessageW (infoPtr->hwndNotify, WM_NOTIFY, 182 nmhdr.idFrom, (LPARAM) &nmhdr); 183 } 184 185 static void 186 TAB_RelayEvent (HWND hwndTip, HWND hwndMsg, UINT uMsg, 187 WPARAM wParam, LPARAM lParam) 188 { 189 MSG msg; 190 191 msg.hwnd = hwndMsg; 192 msg.message = uMsg; 193 msg.wParam = wParam; 194 msg.lParam = lParam; 195 msg.time = GetMessageTime (); 196 msg.pt.x = (short)LOWORD(GetMessagePos ()); 197 msg.pt.y = (short)HIWORD(GetMessagePos ()); 198 199 SendMessageW (hwndTip, TTM_RELAYEVENT, 0, (LPARAM)&msg); 200 } 201 202 static void 203 TAB_DumpItemExternalT(const TCITEMW *pti, UINT iItem, BOOL isW) 204 { 205 if (TRACE_ON(tab)) { 206 TRACE("external tab %d, mask=0x%08x, dwState=0x%08x, dwStateMask=0x%08x, cchTextMax=0x%08x\n", 207 iItem, pti->mask, pti->dwState, pti->dwStateMask, pti->cchTextMax); 208 TRACE("external tab %d, iImage=%d, lParam=0x%08lx, pszTextW=%s\n", 209 iItem, pti->iImage, pti->lParam, isW ? debugstr_w(pti->pszText) : debugstr_a((LPSTR)pti->pszText)); 210 } 211 } 212 213 static void 214 TAB_DumpItemInternal(const TAB_INFO *infoPtr, UINT iItem) 215 { 216 if (TRACE_ON(tab)) { 217 TAB_ITEM *ti = TAB_GetItem(infoPtr, iItem); 218 219 TRACE("tab %d, dwState=0x%08x, pszText=%s, iImage=%d\n", 220 iItem, ti->dwState, debugstr_w(ti->pszText), ti->iImage); 221 TRACE("tab %d, rect.left=%d, rect.top(row)=%d\n", 222 iItem, ti->rect.left, ti->rect.top); 223 } 224 } 225 226 /* RETURNS 227 * the index of the selected tab, or -1 if no tab is selected. */ 228 static inline LRESULT TAB_GetCurSel (const TAB_INFO *infoPtr) 229 { 230 TRACE("(%p)\n", infoPtr); 231 return infoPtr->iSelected; 232 } 233 234 /* RETURNS 235 * the index of the tab item that has the focus. */ 236 static inline LRESULT 237 TAB_GetCurFocus (const TAB_INFO *infoPtr) 238 { 239 TRACE("(%p)\n", infoPtr); 240 return infoPtr->uFocus; 241 } 242 243 static inline LRESULT TAB_GetToolTips (const TAB_INFO *infoPtr) 244 { 245 TRACE("(%p)\n", infoPtr); 246 return (LRESULT)infoPtr->hwndToolTip; 247 } 248 249 static inline LRESULT TAB_SetCurSel (TAB_INFO *infoPtr, INT iItem) 250 { 251 INT prevItem = infoPtr->iSelected; 252 253 TRACE("(%p %d)\n", infoPtr, iItem); 254 255 if (iItem >= (INT)infoPtr->uNumItem) 256 return -1; 257 258 if (prevItem != iItem) { 259 if (prevItem != -1) 260 TAB_GetItem(infoPtr, prevItem)->dwState &= ~TCIS_BUTTONPRESSED; 261 262 if (iItem >= 0) 263 { 264 TAB_GetItem(infoPtr, iItem)->dwState |= TCIS_BUTTONPRESSED; 265 infoPtr->iSelected = iItem; 266 infoPtr->uFocus = iItem; 267 } 268 else 269 { 270 infoPtr->iSelected = -1; 271 infoPtr->uFocus = -1; 272 } 273 274 TAB_EnsureSelectionVisible(infoPtr); 275 TAB_InvalidateTabArea(infoPtr); 276 } 277 278 return prevItem; 279 } 280 281 static LRESULT TAB_SetCurFocus (TAB_INFO *infoPtr, INT iItem) 282 { 283 TRACE("(%p %d)\n", infoPtr, iItem); 284 285 if (iItem < 0) { 286 infoPtr->uFocus = -1; 287 if (infoPtr->iSelected != -1) { 288 #ifdef __REACTOS__ 289 TAB_GetItem(infoPtr, infoPtr->iSelected)->dwState &= ~TCIS_BUTTONPRESSED; 290 #endif 291 infoPtr->iSelected = -1; 292 TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGE); 293 TAB_InvalidateTabArea(infoPtr); 294 } 295 } 296 else if (iItem < infoPtr->uNumItem) { 297 if (infoPtr->dwStyle & TCS_BUTTONS) { 298 /* set focus to new item, leave selection as is */ 299 if (infoPtr->uFocus != iItem) { 300 INT prev_focus = infoPtr->uFocus; 301 RECT r; 302 303 infoPtr->uFocus = iItem; 304 305 if (prev_focus != infoPtr->iSelected) { 306 if (TAB_InternalGetItemRect(infoPtr, prev_focus, &r, NULL)) 307 InvalidateRect(infoPtr->hwnd, &r, FALSE); 308 } 309 310 if (TAB_InternalGetItemRect(infoPtr, iItem, &r, NULL)) 311 InvalidateRect(infoPtr->hwnd, &r, FALSE); 312 313 TAB_SendSimpleNotify(infoPtr, TCN_FOCUSCHANGE); 314 } 315 } else { 316 #ifdef __REACTOS__ 317 INT oldItem = infoPtr->iSelected; 318 #endif 319 INT oldFocus = infoPtr->uFocus; 320 if (infoPtr->iSelected != iItem || oldFocus == -1 ) { 321 infoPtr->uFocus = iItem; 322 if (oldFocus != -1) { 323 if (!TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGING)) { 324 #ifdef __REACTOS__ 325 if (oldItem != -1) 326 TAB_GetItem(infoPtr, oldItem)->dwState &= ~TCIS_BUTTONPRESSED; 327 #endif 328 infoPtr->iSelected = iItem; 329 #ifdef __REACTOS__ 330 TAB_GetItem(infoPtr, iItem)->dwState |= TCIS_BUTTONPRESSED; 331 #endif 332 TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGE); 333 } 334 else 335 infoPtr->iSelected = iItem; 336 TAB_EnsureSelectionVisible(infoPtr); 337 TAB_InvalidateTabArea(infoPtr); 338 } 339 } 340 } 341 } 342 return 0; 343 } 344 345 static inline LRESULT 346 TAB_SetToolTips (TAB_INFO *infoPtr, HWND hwndToolTip) 347 { 348 TRACE("%p %p\n", infoPtr, hwndToolTip); 349 infoPtr->hwndToolTip = hwndToolTip; 350 return 0; 351 } 352 353 static inline LRESULT 354 TAB_SetPadding (TAB_INFO *infoPtr, LPARAM lParam) 355 { 356 TRACE("(%p %d %d)\n", infoPtr, LOWORD(lParam), HIWORD(lParam)); 357 infoPtr->uHItemPadding_s = LOWORD(lParam); 358 infoPtr->uVItemPadding_s = HIWORD(lParam); 359 360 return 0; 361 } 362 363 /****************************************************************************** 364 * TAB_InternalGetItemRect 365 * 366 * This method will calculate the rectangle representing a given tab item in 367 * client coordinates. This method takes scrolling into account. 368 * 369 * This method returns TRUE if the item is visible in the window and FALSE 370 * if it is completely outside the client area. 371 */ 372 static BOOL TAB_InternalGetItemRect( 373 const TAB_INFO* infoPtr, 374 INT itemIndex, 375 RECT* itemRect, 376 RECT* selectedRect) 377 { 378 RECT tmpItemRect,clientRect; 379 380 /* Perform a sanity check and a trivial visibility check. */ 381 if ( (infoPtr->uNumItem <= 0) || 382 (itemIndex >= infoPtr->uNumItem) || 383 (!(((infoPtr->dwStyle & TCS_MULTILINE) || (infoPtr->dwStyle & TCS_VERTICAL))) && 384 (itemIndex < infoPtr->leftmostVisible))) 385 { 386 TRACE("Not Visible\n"); 387 SetRect(itemRect, 0, 0, 0, infoPtr->tabHeight); 388 SetRectEmpty(selectedRect); 389 return FALSE; 390 } 391 392 /* 393 * Avoid special cases in this procedure by assigning the "out" 394 * parameters if the caller didn't supply them 395 */ 396 if (itemRect == NULL) 397 itemRect = &tmpItemRect; 398 399 /* Retrieve the unmodified item rect. */ 400 *itemRect = TAB_GetItem(infoPtr,itemIndex)->rect; 401 402 /* calculate the times bottom and top based on the row */ 403 GetClientRect(infoPtr->hwnd, &clientRect); 404 405 if ((infoPtr->dwStyle & TCS_BOTTOM) && (infoPtr->dwStyle & TCS_VERTICAL)) 406 { 407 itemRect->right = clientRect.right - SELECTED_TAB_OFFSET - itemRect->left * infoPtr->tabHeight - 408 ((infoPtr->dwStyle & TCS_BUTTONS) ? itemRect->left * BUTTON_SPACINGX : 0); 409 itemRect->left = itemRect->right - infoPtr->tabHeight; 410 } 411 else if (infoPtr->dwStyle & TCS_VERTICAL) 412 { 413 itemRect->left = clientRect.left + SELECTED_TAB_OFFSET + itemRect->left * infoPtr->tabHeight + 414 ((infoPtr->dwStyle & TCS_BUTTONS) ? itemRect->left * BUTTON_SPACINGX : 0); 415 itemRect->right = itemRect->left + infoPtr->tabHeight; 416 } 417 else if (infoPtr->dwStyle & TCS_BOTTOM) 418 { 419 itemRect->bottom = clientRect.bottom - itemRect->top * infoPtr->tabHeight - 420 ((infoPtr->dwStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : SELECTED_TAB_OFFSET); 421 itemRect->top = itemRect->bottom - infoPtr->tabHeight; 422 } 423 else /* not TCS_BOTTOM and not TCS_VERTICAL */ 424 { 425 itemRect->top = clientRect.top + itemRect->top * infoPtr->tabHeight + 426 ((infoPtr->dwStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : SELECTED_TAB_OFFSET); 427 itemRect->bottom = itemRect->top + infoPtr->tabHeight; 428 } 429 430 /* 431 * "scroll" it to make sure the item at the very left of the 432 * tab control is the leftmost visible tab. 433 */ 434 if(infoPtr->dwStyle & TCS_VERTICAL) 435 { 436 OffsetRect(itemRect, 437 0, 438 -TAB_GetItem(infoPtr, infoPtr->leftmostVisible)->rect.top); 439 440 /* 441 * Move the rectangle so the first item is slightly offset from 442 * the bottom of the tab control. 443 */ 444 OffsetRect(itemRect, 445 0, 446 SELECTED_TAB_OFFSET); 447 448 } else 449 { 450 OffsetRect(itemRect, 451 -TAB_GetItem(infoPtr, infoPtr->leftmostVisible)->rect.left, 452 0); 453 454 /* 455 * Move the rectangle so the first item is slightly offset from 456 * the left of the tab control. 457 */ 458 OffsetRect(itemRect, 459 SELECTED_TAB_OFFSET, 460 0); 461 } 462 TRACE("item %d tab h=%d, rect=(%s)\n", 463 itemIndex, infoPtr->tabHeight, wine_dbgstr_rect(itemRect)); 464 465 /* Now, calculate the position of the item as if it were selected. */ 466 if (selectedRect!=NULL) 467 { 468 *selectedRect = *itemRect; 469 470 /* The rectangle of a selected item is a bit wider. */ 471 if(infoPtr->dwStyle & TCS_VERTICAL) 472 InflateRect(selectedRect, 0, SELECTED_TAB_OFFSET); 473 else 474 InflateRect(selectedRect, SELECTED_TAB_OFFSET, 0); 475 476 /* If it also a bit higher. */ 477 if ((infoPtr->dwStyle & TCS_BOTTOM) && (infoPtr->dwStyle & TCS_VERTICAL)) 478 { 479 selectedRect->left -= 2; /* the border is thicker on the right */ 480 selectedRect->right += SELECTED_TAB_OFFSET; 481 } 482 else if (infoPtr->dwStyle & TCS_VERTICAL) 483 { 484 selectedRect->left -= SELECTED_TAB_OFFSET; 485 selectedRect->right += 1; 486 } 487 else if (infoPtr->dwStyle & TCS_BOTTOM) 488 { 489 selectedRect->bottom += SELECTED_TAB_OFFSET; 490 } 491 else /* not TCS_BOTTOM and not TCS_VERTICAL */ 492 { 493 selectedRect->top -= SELECTED_TAB_OFFSET; 494 selectedRect->bottom -= 1; 495 } 496 } 497 498 /* Check for visibility */ 499 if (infoPtr->dwStyle & TCS_VERTICAL) 500 return (itemRect->top < clientRect.bottom) && (itemRect->bottom > clientRect.top); 501 else 502 return (itemRect->left < clientRect.right) && (itemRect->right > clientRect.left); 503 } 504 505 static inline BOOL 506 TAB_GetItemRect(const TAB_INFO *infoPtr, INT item, RECT *rect) 507 { 508 TRACE("(%p, %d, %p)\n", infoPtr, item, rect); 509 return TAB_InternalGetItemRect(infoPtr, item, rect, NULL); 510 } 511 512 /****************************************************************************** 513 * TAB_KeyDown 514 * 515 * This method is called to handle keyboard input 516 */ 517 static LRESULT TAB_KeyDown(TAB_INFO* infoPtr, WPARAM keyCode, LPARAM lParam) 518 { 519 INT newItem = -1; 520 NMTCKEYDOWN nm; 521 522 /* TCN_KEYDOWN notification sent always */ 523 nm.hdr.hwndFrom = infoPtr->hwnd; 524 nm.hdr.idFrom = GetWindowLongPtrW(infoPtr->hwnd, GWLP_ID); 525 nm.hdr.code = TCN_KEYDOWN; 526 nm.wVKey = keyCode; 527 nm.flags = lParam; 528 SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, nm.hdr.idFrom, (LPARAM)&nm); 529 530 switch (keyCode) 531 { 532 case VK_LEFT: 533 newItem = infoPtr->uFocus - 1; 534 break; 535 case VK_RIGHT: 536 newItem = infoPtr->uFocus + 1; 537 break; 538 } 539 540 /* If we changed to a valid item, change focused item */ 541 if (newItem >= 0 && newItem < infoPtr->uNumItem && infoPtr->uFocus != newItem) 542 TAB_SetCurFocus(infoPtr, newItem); 543 544 return 0; 545 } 546 547 /* 548 * WM_KILLFOCUS handler 549 */ 550 static void TAB_KillFocus(TAB_INFO *infoPtr) 551 { 552 /* clear current focused item back to selected for TCS_BUTTONS */ 553 if ((infoPtr->dwStyle & TCS_BUTTONS) && (infoPtr->uFocus != infoPtr->iSelected)) 554 { 555 RECT r; 556 557 if (TAB_InternalGetItemRect(infoPtr, infoPtr->uFocus, &r, NULL)) 558 InvalidateRect(infoPtr->hwnd, &r, FALSE); 559 560 infoPtr->uFocus = infoPtr->iSelected; 561 } 562 } 563 564 /****************************************************************************** 565 * TAB_FocusChanging 566 * 567 * This method is called whenever the focus goes in or out of this control 568 * it is used to update the visual state of the control. 569 */ 570 static void TAB_FocusChanging(const TAB_INFO *infoPtr) 571 { 572 RECT selectedRect; 573 BOOL isVisible; 574 575 /* 576 * Get the rectangle for the item. 577 */ 578 isVisible = TAB_InternalGetItemRect(infoPtr, 579 infoPtr->uFocus, 580 NULL, 581 &selectedRect); 582 583 /* 584 * If the rectangle is not completely invisible, invalidate that 585 * portion of the window. 586 */ 587 if (isVisible) 588 { 589 TRACE("invalidate (%s)\n", wine_dbgstr_rect(&selectedRect)); 590 InvalidateRect(infoPtr->hwnd, &selectedRect, TRUE); 591 } 592 } 593 594 static INT TAB_InternalHitTest (const TAB_INFO *infoPtr, POINT pt, UINT *flags) 595 { 596 RECT rect; 597 INT iCount; 598 599 for (iCount = 0; iCount < infoPtr->uNumItem; iCount++) 600 { 601 TAB_InternalGetItemRect(infoPtr, iCount, &rect, NULL); 602 603 if (PtInRect(&rect, pt)) 604 { 605 *flags = TCHT_ONITEM; 606 return iCount; 607 } 608 } 609 610 *flags = TCHT_NOWHERE; 611 return -1; 612 } 613 614 static inline LRESULT 615 TAB_HitTest (const TAB_INFO *infoPtr, LPTCHITTESTINFO lptest) 616 { 617 TRACE("(%p, %p)\n", infoPtr, lptest); 618 return TAB_InternalHitTest (infoPtr, lptest->pt, &lptest->flags); 619 } 620 621 /****************************************************************************** 622 * TAB_NCHitTest 623 * 624 * Napster v2b5 has a tab control for its main navigation which has a client 625 * area that covers the whole area of the dialog pages. 626 * That's why it receives all msgs for that area and the underlying dialog ctrls 627 * are dead. 628 * So I decided that we should handle WM_NCHITTEST here and return 629 * HTTRANSPARENT if we don't hit the tab control buttons. 630 * FIXME: WM_NCHITTEST handling correct ? Fix it if you know that Windows 631 * doesn't do it that way. Maybe depends on tab control styles ? 632 */ 633 static inline LRESULT 634 TAB_NCHitTest (const TAB_INFO *infoPtr, LPARAM lParam) 635 { 636 POINT pt; 637 UINT dummyflag; 638 639 pt.x = (short)LOWORD(lParam); 640 pt.y = (short)HIWORD(lParam); 641 ScreenToClient(infoPtr->hwnd, &pt); 642 643 if (TAB_InternalHitTest(infoPtr, pt, &dummyflag) == -1) 644 return HTTRANSPARENT; 645 else 646 return HTCLIENT; 647 } 648 649 static LRESULT 650 TAB_LButtonDown (TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam) 651 { 652 POINT pt; 653 INT newItem; 654 UINT dummy; 655 656 if (infoPtr->hwndToolTip) 657 TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd, 658 WM_LBUTTONDOWN, wParam, lParam); 659 660 if (!(infoPtr->dwStyle & TCS_FOCUSNEVER)) { 661 SetFocus (infoPtr->hwnd); 662 } 663 664 if (infoPtr->hwndToolTip) 665 TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd, 666 WM_LBUTTONDOWN, wParam, lParam); 667 668 pt.x = (short)LOWORD(lParam); 669 pt.y = (short)HIWORD(lParam); 670 671 newItem = TAB_InternalHitTest (infoPtr, pt, &dummy); 672 673 TRACE("On Tab, item %d\n", newItem); 674 675 if ((newItem != -1) && (infoPtr->iSelected != newItem)) 676 { 677 if ((infoPtr->dwStyle & TCS_BUTTONS) && (infoPtr->dwStyle & TCS_MULTISELECT) && 678 (wParam & MK_CONTROL)) 679 { 680 RECT r; 681 682 /* toggle multiselection */ 683 TAB_GetItem(infoPtr, newItem)->dwState ^= TCIS_BUTTONPRESSED; 684 if (TAB_InternalGetItemRect (infoPtr, newItem, &r, NULL)) 685 InvalidateRect (infoPtr->hwnd, &r, TRUE); 686 } 687 else 688 { 689 INT i; 690 BOOL pressed = FALSE; 691 692 /* any button pressed ? */ 693 for (i = 0; i < infoPtr->uNumItem; i++) 694 if ((TAB_GetItem (infoPtr, i)->dwState & TCIS_BUTTONPRESSED) && 695 (infoPtr->iSelected != i)) 696 { 697 pressed = TRUE; 698 break; 699 } 700 701 if (TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGING)) 702 return 0; 703 704 if (pressed) 705 TAB_DeselectAll (infoPtr, FALSE); 706 else 707 TAB_SetCurSel(infoPtr, newItem); 708 709 TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGE); 710 } 711 } 712 713 return 0; 714 } 715 716 static inline LRESULT 717 TAB_LButtonUp (const TAB_INFO *infoPtr) 718 { 719 TAB_SendSimpleNotify(infoPtr, NM_CLICK); 720 721 return 0; 722 } 723 724 static inline void 725 TAB_RButtonUp (const TAB_INFO *infoPtr) 726 { 727 TAB_SendSimpleNotify(infoPtr, NM_RCLICK); 728 } 729 730 /****************************************************************************** 731 * TAB_DrawLoneItemInterior 732 * 733 * This calls TAB_DrawItemInterior. However, TAB_DrawItemInterior is normally 734 * called by TAB_DrawItem which is normally called by TAB_Refresh which sets 735 * up the device context and font. This routine does the same setup but 736 * only calls TAB_DrawItemInterior for the single specified item. 737 */ 738 static void 739 TAB_DrawLoneItemInterior(const TAB_INFO* infoPtr, int iItem) 740 { 741 HDC hdc = GetDC(infoPtr->hwnd); 742 RECT r, rC; 743 744 /* Clip UpDown control to not draw over it */ 745 if (infoPtr->needsScrolling) 746 { 747 GetWindowRect(infoPtr->hwnd, &rC); 748 GetWindowRect(infoPtr->hwndUpDown, &r); 749 ExcludeClipRect(hdc, r.left - rC.left, r.top - rC.top, r.right - rC.left, r.bottom - rC.top); 750 } 751 TAB_DrawItemInterior(infoPtr, hdc, iItem, NULL); 752 ReleaseDC(infoPtr->hwnd, hdc); 753 } 754 755 /* update a tab after hottracking - invalidate it or just redraw the interior, 756 * based on whether theming is used or not */ 757 static inline void hottrack_refresh(const TAB_INFO *infoPtr, int tabIndex) 758 { 759 if (tabIndex == -1) return; 760 761 if (GetWindowTheme (infoPtr->hwnd)) 762 { 763 RECT rect; 764 TAB_InternalGetItemRect(infoPtr, tabIndex, &rect, NULL); 765 InvalidateRect (infoPtr->hwnd, &rect, FALSE); 766 } 767 else 768 TAB_DrawLoneItemInterior(infoPtr, tabIndex); 769 } 770 771 /****************************************************************************** 772 * TAB_HotTrackTimerProc 773 * 774 * When a mouse-move event causes a tab to be highlighted (hot-tracking), a 775 * timer is setup so we can check if the mouse is moved out of our window. 776 * (We don't get an event when the mouse leaves, the mouse-move events just 777 * stop being delivered to our window and just start being delivered to 778 * another window.) This function is called when the timer triggers so 779 * we can check if the mouse has left our window. If so, we un-highlight 780 * the hot-tracked tab. 781 */ 782 static void CALLBACK 783 TAB_HotTrackTimerProc 784 ( 785 HWND hwnd, /* handle of window for timer messages */ 786 UINT uMsg, /* WM_TIMER message */ 787 UINT_PTR idEvent, /* timer identifier */ 788 DWORD dwTime /* current system time */ 789 ) 790 { 791 TAB_INFO* infoPtr = TAB_GetInfoPtr(hwnd); 792 793 if (infoPtr != NULL && infoPtr->iHotTracked >= 0) 794 { 795 POINT pt; 796 797 /* 798 ** If we can't get the cursor position, or if the cursor is outside our 799 ** window, we un-highlight the hot-tracked tab. Note that the cursor is 800 ** "outside" even if it is within our bounding rect if another window 801 ** overlaps. Note also that the case where the cursor stayed within our 802 ** window but has moved off the hot-tracked tab will be handled by the 803 ** WM_MOUSEMOVE event. 804 */ 805 if (!GetCursorPos(&pt) || WindowFromPoint(pt) != hwnd) 806 { 807 /* Redraw iHotTracked to look normal */ 808 INT iRedraw = infoPtr->iHotTracked; 809 infoPtr->iHotTracked = -1; 810 hottrack_refresh (infoPtr, iRedraw); 811 812 /* Kill this timer */ 813 KillTimer(hwnd, TAB_HOTTRACK_TIMER); 814 } 815 } 816 } 817 818 /****************************************************************************** 819 * TAB_RecalcHotTrack 820 * 821 * If a tab control has the TCS_HOTTRACK style, then the tab under the mouse 822 * should be highlighted. This function determines which tab in a tab control, 823 * if any, is under the mouse and records that information. The caller may 824 * supply output parameters to receive the item number of the tab item which 825 * was highlighted but isn't any longer and of the tab item which is now 826 * highlighted but wasn't previously. The caller can use this information to 827 * selectively redraw those tab items. 828 * 829 * If the caller has a mouse position, it can supply it through the pos 830 * parameter. For example, TAB_MouseMove does this. Otherwise, the caller 831 * supplies NULL and this function determines the current mouse position 832 * itself. 833 */ 834 static void 835 TAB_RecalcHotTrack 836 ( 837 TAB_INFO* infoPtr, 838 const LPARAM* pos, 839 int* out_redrawLeave, 840 int* out_redrawEnter 841 ) 842 { 843 int item = -1; 844 845 846 if (out_redrawLeave != NULL) 847 *out_redrawLeave = -1; 848 if (out_redrawEnter != NULL) 849 *out_redrawEnter = -1; 850 851 if ((infoPtr->dwStyle & TCS_HOTTRACK) || GetWindowTheme(infoPtr->hwnd)) 852 { 853 POINT pt; 854 UINT flags; 855 856 if (pos == NULL) 857 { 858 GetCursorPos(&pt); 859 ScreenToClient(infoPtr->hwnd, &pt); 860 } 861 else 862 { 863 pt.x = (short)LOWORD(*pos); 864 pt.y = (short)HIWORD(*pos); 865 } 866 867 item = TAB_InternalHitTest(infoPtr, pt, &flags); 868 } 869 870 if (item != infoPtr->iHotTracked) 871 { 872 if (infoPtr->iHotTracked >= 0) 873 { 874 /* Mark currently hot-tracked to be redrawn to look normal */ 875 if (out_redrawLeave != NULL) 876 *out_redrawLeave = infoPtr->iHotTracked; 877 878 if (item < 0) 879 { 880 /* Kill timer which forces recheck of mouse pos */ 881 KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER); 882 } 883 } 884 else 885 { 886 /* Start timer so we recheck mouse pos */ 887 UINT timerID = SetTimer 888 ( 889 infoPtr->hwnd, 890 TAB_HOTTRACK_TIMER, 891 TAB_HOTTRACK_TIMER_INTERVAL, 892 TAB_HotTrackTimerProc 893 ); 894 895 if (timerID == 0) 896 return; /* Hot tracking not available */ 897 } 898 899 infoPtr->iHotTracked = item; 900 901 if (item >= 0) 902 { 903 /* Mark new hot-tracked to be redrawn to look highlighted */ 904 if (out_redrawEnter != NULL) 905 *out_redrawEnter = item; 906 } 907 } 908 } 909 910 /****************************************************************************** 911 * TAB_MouseMove 912 * 913 * Handles the mouse-move event. Updates tooltips. Updates hot-tracking. 914 */ 915 static LRESULT 916 TAB_MouseMove (TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam) 917 { 918 int redrawLeave; 919 int redrawEnter; 920 921 if (infoPtr->hwndToolTip) 922 TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd, 923 WM_LBUTTONDOWN, wParam, lParam); 924 925 /* Determine which tab to highlight. Redraw tabs which change highlight 926 ** status. */ 927 TAB_RecalcHotTrack(infoPtr, &lParam, &redrawLeave, &redrawEnter); 928 929 hottrack_refresh (infoPtr, redrawLeave); 930 hottrack_refresh (infoPtr, redrawEnter); 931 932 return 0; 933 } 934 935 /****************************************************************************** 936 * TAB_AdjustRect 937 * 938 * Calculates the tab control's display area given the window rectangle or 939 * the window rectangle given the requested display rectangle. 940 */ 941 static LRESULT TAB_AdjustRect(const TAB_INFO *infoPtr, WPARAM fLarger, LPRECT prc) 942 { 943 LONG *iRightBottom, *iLeftTop; 944 945 TRACE ("hwnd=%p fLarger=%ld (%s)\n", infoPtr->hwnd, fLarger, 946 wine_dbgstr_rect(prc)); 947 948 if (!prc) return -1; 949 950 if(infoPtr->dwStyle & TCS_VERTICAL) 951 { 952 iRightBottom = &(prc->right); 953 iLeftTop = &(prc->left); 954 } 955 else 956 { 957 iRightBottom = &(prc->bottom); 958 iLeftTop = &(prc->top); 959 } 960 961 if (fLarger) /* Go from display rectangle */ 962 { 963 /* Add the height of the tabs. */ 964 if (infoPtr->dwStyle & TCS_BOTTOM) 965 *iRightBottom += infoPtr->tabHeight * infoPtr->uNumRows; 966 else 967 *iLeftTop -= infoPtr->tabHeight * infoPtr->uNumRows + 968 ((infoPtr->dwStyle & TCS_BUTTONS)? 3 * (infoPtr->uNumRows - 1) : 0); 969 970 /* Inflate the rectangle for the padding */ 971 InflateRect(prc, DISPLAY_AREA_PADDINGX, DISPLAY_AREA_PADDINGY); 972 973 /* Inflate for the border */ 974 InflateRect(prc, CONTROL_BORDER_SIZEX, CONTROL_BORDER_SIZEY); 975 } 976 else /* Go from window rectangle. */ 977 { 978 /* Deflate the rectangle for the border */ 979 InflateRect(prc, -CONTROL_BORDER_SIZEX, -CONTROL_BORDER_SIZEY); 980 981 /* Deflate the rectangle for the padding */ 982 InflateRect(prc, -DISPLAY_AREA_PADDINGX, -DISPLAY_AREA_PADDINGY); 983 984 /* Remove the height of the tabs. */ 985 if (infoPtr->dwStyle & TCS_BOTTOM) 986 *iRightBottom -= infoPtr->tabHeight * infoPtr->uNumRows; 987 else 988 *iLeftTop += (infoPtr->tabHeight) * infoPtr->uNumRows + 989 ((infoPtr->dwStyle & TCS_BUTTONS)? 3 * (infoPtr->uNumRows - 1) : 0); 990 } 991 992 return 0; 993 } 994 995 /****************************************************************************** 996 * TAB_OnHScroll 997 * 998 * This method will handle the notification from the scroll control and 999 * perform the scrolling operation on the tab control. 1000 */ 1001 static LRESULT TAB_OnHScroll(TAB_INFO *infoPtr, int nScrollCode, int nPos) 1002 { 1003 if(nScrollCode == SB_THUMBPOSITION && nPos != infoPtr->leftmostVisible) 1004 { 1005 if(nPos < infoPtr->leftmostVisible) 1006 infoPtr->leftmostVisible--; 1007 else 1008 infoPtr->leftmostVisible++; 1009 1010 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL); 1011 TAB_InvalidateTabArea(infoPtr); 1012 SendMessageW(infoPtr->hwndUpDown, UDM_SETPOS, 0, 1013 MAKELONG(infoPtr->leftmostVisible, 0)); 1014 } 1015 1016 return 0; 1017 } 1018 1019 /****************************************************************************** 1020 * TAB_SetupScrolling 1021 * 1022 * This method will check the current scrolling state and make sure the 1023 * scrolling control is displayed (or not). 1024 */ 1025 static void TAB_SetupScrolling( 1026 TAB_INFO* infoPtr, 1027 const RECT* clientRect) 1028 { 1029 static const WCHAR emptyW[] = { 0 }; 1030 INT maxRange = 0; 1031 1032 if (infoPtr->needsScrolling) 1033 { 1034 RECT controlPos; 1035 INT vsize, tabwidth; 1036 1037 /* 1038 * Calculate the position of the scroll control. 1039 */ 1040 controlPos.right = clientRect->right; 1041 controlPos.left = controlPos.right - 2 * GetSystemMetrics(SM_CXHSCROLL); 1042 1043 if (infoPtr->dwStyle & TCS_BOTTOM) 1044 { 1045 controlPos.top = clientRect->bottom - infoPtr->tabHeight; 1046 controlPos.bottom = controlPos.top + GetSystemMetrics(SM_CYHSCROLL); 1047 } 1048 else 1049 { 1050 controlPos.bottom = clientRect->top + infoPtr->tabHeight; 1051 controlPos.top = controlPos.bottom - GetSystemMetrics(SM_CYHSCROLL); 1052 } 1053 1054 /* 1055 * If we don't have a scroll control yet, we want to create one. 1056 * If we have one, we want to make sure it's positioned properly. 1057 */ 1058 if (infoPtr->hwndUpDown==0) 1059 { 1060 infoPtr->hwndUpDown = CreateWindowW(UPDOWN_CLASSW, emptyW, 1061 WS_VISIBLE | WS_CHILD | UDS_HORZ, 1062 controlPos.left, controlPos.top, 1063 controlPos.right - controlPos.left, 1064 controlPos.bottom - controlPos.top, 1065 infoPtr->hwnd, NULL, NULL, NULL); 1066 } 1067 else 1068 { 1069 SetWindowPos(infoPtr->hwndUpDown, 1070 NULL, 1071 controlPos.left, controlPos.top, 1072 controlPos.right - controlPos.left, 1073 controlPos.bottom - controlPos.top, 1074 SWP_SHOWWINDOW | SWP_NOZORDER); 1075 } 1076 1077 /* Now calculate upper limit of the updown control range. 1078 * We do this by calculating how many tabs will be offscreen when the 1079 * last tab is visible. 1080 */ 1081 if(infoPtr->uNumItem) 1082 { 1083 vsize = clientRect->right - (controlPos.right - controlPos.left + 1); 1084 maxRange = infoPtr->uNumItem; 1085 tabwidth = TAB_GetItem(infoPtr, infoPtr->uNumItem - 1)->rect.right; 1086 1087 for(; maxRange > 0; maxRange--) 1088 { 1089 if(tabwidth - TAB_GetItem(infoPtr,maxRange - 1)->rect.left > vsize) 1090 break; 1091 } 1092 1093 if(maxRange == infoPtr->uNumItem) 1094 maxRange--; 1095 } 1096 } 1097 else 1098 { 1099 /* If we once had a scroll control... hide it */ 1100 if (infoPtr->hwndUpDown) 1101 ShowWindow(infoPtr->hwndUpDown, SW_HIDE); 1102 } 1103 if (infoPtr->hwndUpDown) 1104 SendMessageW(infoPtr->hwndUpDown, UDM_SETRANGE32, 0, maxRange); 1105 } 1106 1107 /****************************************************************************** 1108 * TAB_SetItemBounds 1109 * 1110 * This method will calculate the position rectangles of all the items in the 1111 * control. The rectangle calculated starts at 0 for the first item in the 1112 * list and ignores scrolling and selection. 1113 * It also uses the current font to determine the height of the tab row and 1114 * it checks if all the tabs fit in the client area of the window. If they 1115 * don't, a scrolling control is added. 1116 */ 1117 static void TAB_SetItemBounds (TAB_INFO *infoPtr) 1118 { 1119 TEXTMETRICW fontMetrics; 1120 UINT curItem; 1121 INT curItemLeftPos; 1122 INT curItemRowCount; 1123 HFONT hFont, hOldFont; 1124 HDC hdc; 1125 RECT clientRect; 1126 INT iTemp; 1127 RECT* rcItem; 1128 INT iIndex; 1129 INT icon_width = 0; 1130 1131 /* 1132 * We need to get text information so we need a DC and we need to select 1133 * a font. 1134 */ 1135 hdc = GetDC(infoPtr->hwnd); 1136 1137 hFont = infoPtr->hFont ? infoPtr->hFont : GetStockObject (SYSTEM_FONT); 1138 hOldFont = SelectObject (hdc, hFont); 1139 1140 /* 1141 * We will base the rectangle calculations on the client rectangle 1142 * of the control. 1143 */ 1144 GetClientRect(infoPtr->hwnd, &clientRect); 1145 1146 /* if TCS_VERTICAL then swap the height and width so this code places the 1147 tabs along the top of the rectangle and we can just rotate them after 1148 rather than duplicate all of the below code */ 1149 if(infoPtr->dwStyle & TCS_VERTICAL) 1150 { 1151 iTemp = clientRect.bottom; 1152 clientRect.bottom = clientRect.right; 1153 clientRect.right = iTemp; 1154 } 1155 1156 /* Now use hPadding and vPadding */ 1157 infoPtr->uHItemPadding = infoPtr->uHItemPadding_s; 1158 infoPtr->uVItemPadding = infoPtr->uVItemPadding_s; 1159 1160 /* The leftmost item will be "0" aligned */ 1161 curItemLeftPos = 0; 1162 curItemRowCount = infoPtr->uNumItem ? 1 : 0; 1163 1164 if (!(infoPtr->fHeightSet)) 1165 { 1166 int item_height; 1167 INT icon_height = 0, cx; 1168 1169 /* Use the current font to determine the height of a tab. */ 1170 GetTextMetricsW(hdc, &fontMetrics); 1171 1172 /* Get the icon height */ 1173 if (infoPtr->himl) 1174 ImageList_GetIconSize(infoPtr->himl, &cx, &icon_height); 1175 1176 /* Take the highest between font or icon */ 1177 if (fontMetrics.tmHeight > icon_height) 1178 item_height = fontMetrics.tmHeight + 2; 1179 else 1180 item_height = icon_height; 1181 1182 /* 1183 * Make sure there is enough space for the letters + icon + growing the 1184 * selected item + extra space for the selected item. 1185 */ 1186 infoPtr->tabHeight = item_height + 1187 ((infoPtr->dwStyle & TCS_BUTTONS) ? 2 : 1) * 1188 infoPtr->uVItemPadding; 1189 1190 TRACE("tabH=%d, tmH=%d, iconh=%d\n", 1191 infoPtr->tabHeight, fontMetrics.tmHeight, icon_height); 1192 } 1193 1194 TRACE("client right=%d\n", clientRect.right); 1195 1196 /* Get the icon width */ 1197 if (infoPtr->himl) 1198 { 1199 INT cy; 1200 1201 ImageList_GetIconSize(infoPtr->himl, &icon_width, &cy); 1202 1203 if (infoPtr->dwStyle & TCS_FIXEDWIDTH) 1204 icon_width += 4; 1205 else 1206 /* Add padding if icon is present */ 1207 icon_width += infoPtr->uHItemPadding; 1208 } 1209 1210 for (curItem = 0; curItem < infoPtr->uNumItem; curItem++) 1211 { 1212 TAB_ITEM *curr = TAB_GetItem(infoPtr, curItem); 1213 1214 /* Set the leftmost position of the tab. */ 1215 curr->rect.left = curItemLeftPos; 1216 1217 if (infoPtr->dwStyle & TCS_FIXEDWIDTH) 1218 { 1219 curr->rect.right = curr->rect.left + 1220 max(infoPtr->tabWidth, icon_width); 1221 } 1222 else if (!curr->pszText) 1223 { 1224 /* If no text use minimum tab width including padding. */ 1225 if (infoPtr->tabMinWidth < 0) 1226 curr->rect.right = curr->rect.left + GET_DEFAULT_MIN_TAB_WIDTH(infoPtr); 1227 else 1228 { 1229 curr->rect.right = curr->rect.left + infoPtr->tabMinWidth; 1230 1231 /* Add extra padding if icon is present */ 1232 if (infoPtr->himl && infoPtr->tabMinWidth > 0 && infoPtr->tabMinWidth < DEFAULT_MIN_TAB_WIDTH 1233 && infoPtr->uHItemPadding > 1) 1234 curr->rect.right += EXTRA_ICON_PADDING * (infoPtr->uHItemPadding-1); 1235 } 1236 } 1237 else 1238 { 1239 int tabwidth; 1240 SIZE size; 1241 /* Calculate how wide the tab is depending on the text it contains */ 1242 GetTextExtentPoint32W(hdc, curr->pszText, 1243 lstrlenW(curr->pszText), &size); 1244 1245 tabwidth = size.cx + icon_width + 2 * infoPtr->uHItemPadding; 1246 1247 if (infoPtr->tabMinWidth < 0) 1248 tabwidth = max(tabwidth, GET_DEFAULT_MIN_TAB_WIDTH(infoPtr)); 1249 else 1250 tabwidth = max(tabwidth, infoPtr->tabMinWidth); 1251 1252 curr->rect.right = curr->rect.left + tabwidth; 1253 TRACE("for <%s>, rect %s\n", debugstr_w(curr->pszText), wine_dbgstr_rect(&curr->rect)); 1254 } 1255 1256 /* 1257 * Check if this is a multiline tab control and if so 1258 * check to see if we should wrap the tabs 1259 * 1260 * Wrap all these tabs. We will arrange them evenly later. 1261 * 1262 */ 1263 1264 if (((infoPtr->dwStyle & TCS_MULTILINE) || (infoPtr->dwStyle & TCS_VERTICAL)) && 1265 (curr->rect.right > 1266 (clientRect.right - CONTROL_BORDER_SIZEX - DISPLAY_AREA_PADDINGX))) 1267 { 1268 curr->rect.right -= curr->rect.left; 1269 1270 curr->rect.left = 0; 1271 curItemRowCount++; 1272 TRACE("wrapping <%s>, rect %s\n", debugstr_w(curr->pszText), wine_dbgstr_rect(&curr->rect)); 1273 } 1274 1275 curr->rect.bottom = 0; 1276 curr->rect.top = curItemRowCount - 1; 1277 1278 TRACE("Rect: %s\n", wine_dbgstr_rect(&curr->rect)); 1279 1280 /* 1281 * The leftmost position of the next item is the rightmost position 1282 * of this one. 1283 */ 1284 if (infoPtr->dwStyle & TCS_BUTTONS) 1285 { 1286 curItemLeftPos = curr->rect.right + BUTTON_SPACINGX; 1287 if (infoPtr->dwStyle & TCS_FLATBUTTONS) 1288 curItemLeftPos += FLAT_BTN_SPACINGX; 1289 } 1290 else 1291 curItemLeftPos = curr->rect.right; 1292 } 1293 1294 if (!((infoPtr->dwStyle & TCS_MULTILINE) || (infoPtr->dwStyle & TCS_VERTICAL))) 1295 { 1296 /* 1297 * Check if we need a scrolling control. 1298 */ 1299 infoPtr->needsScrolling = (curItemLeftPos + (2 * SELECTED_TAB_OFFSET) > 1300 clientRect.right); 1301 1302 /* Don't need scrolling, then update infoPtr->leftmostVisible */ 1303 if(!infoPtr->needsScrolling) 1304 infoPtr->leftmostVisible = 0; 1305 } 1306 else 1307 { 1308 /* 1309 * No scrolling in Multiline or Vertical styles. 1310 */ 1311 infoPtr->needsScrolling = FALSE; 1312 infoPtr->leftmostVisible = 0; 1313 } 1314 TAB_SetupScrolling(infoPtr, &clientRect); 1315 1316 /* Set the number of rows */ 1317 infoPtr->uNumRows = curItemRowCount; 1318 1319 /* Arrange all tabs evenly if style says so */ 1320 if (!(infoPtr->dwStyle & TCS_RAGGEDRIGHT) && 1321 ((infoPtr->dwStyle & TCS_MULTILINE) || (infoPtr->dwStyle & TCS_VERTICAL)) && 1322 (infoPtr->uNumItem > 0) && 1323 (infoPtr->uNumRows > 1)) 1324 { 1325 INT tabPerRow,remTab,iRow; 1326 UINT iItm; 1327 INT iCount=0; 1328 1329 /* 1330 * Ok windows tries to even out the rows. place the same 1331 * number of tabs in each row. So lets give that a shot 1332 */ 1333 1334 tabPerRow = infoPtr->uNumItem / (infoPtr->uNumRows); 1335 remTab = infoPtr->uNumItem % (infoPtr->uNumRows); 1336 1337 for (iItm=0,iRow=0,iCount=0,curItemLeftPos=0; 1338 iItm<infoPtr->uNumItem; 1339 iItm++,iCount++) 1340 { 1341 /* normalize the current rect */ 1342 TAB_ITEM *curr = TAB_GetItem(infoPtr, iItm); 1343 1344 /* shift the item to the left side of the clientRect */ 1345 curr->rect.right -= curr->rect.left; 1346 curr->rect.left = 0; 1347 1348 TRACE("r=%d, cl=%d, cl.r=%d, iCount=%d, iRow=%d, uNumRows=%d, remTab=%d, tabPerRow=%d\n", 1349 curr->rect.right, curItemLeftPos, clientRect.right, 1350 iCount, iRow, infoPtr->uNumRows, remTab, tabPerRow); 1351 1352 /* if we have reached the maximum number of tabs on this row */ 1353 /* move to the next row, reset our current item left position and */ 1354 /* the count of items on this row */ 1355 1356 if (infoPtr->dwStyle & TCS_VERTICAL) { 1357 /* Vert: Add the remaining tabs in the *last* remainder rows */ 1358 if (iCount >= ((iRow>=(INT)infoPtr->uNumRows - remTab)?tabPerRow + 1:tabPerRow)) { 1359 iRow++; 1360 curItemLeftPos = 0; 1361 iCount = 0; 1362 } 1363 } else { 1364 /* Horz: Add the remaining tabs in the *first* remainder rows */ 1365 if (iCount >= ((iRow<remTab)?tabPerRow + 1:tabPerRow)) { 1366 iRow++; 1367 curItemLeftPos = 0; 1368 iCount = 0; 1369 } 1370 } 1371 1372 /* shift the item to the right to place it as the next item in this row */ 1373 curr->rect.left += curItemLeftPos; 1374 curr->rect.right += curItemLeftPos; 1375 curr->rect.top = iRow; 1376 if (infoPtr->dwStyle & TCS_BUTTONS) 1377 { 1378 curItemLeftPos = curr->rect.right + 1; 1379 if (infoPtr->dwStyle & TCS_FLATBUTTONS) 1380 curItemLeftPos += FLAT_BTN_SPACINGX; 1381 } 1382 else 1383 curItemLeftPos = curr->rect.right; 1384 1385 TRACE("arranging <%s>, rect %s\n", debugstr_w(curr->pszText), wine_dbgstr_rect(&curr->rect)); 1386 } 1387 1388 /* 1389 * Justify the rows 1390 */ 1391 { 1392 INT widthDiff, iIndexStart=0, iIndexEnd=0; 1393 INT remainder; 1394 INT iCount=0; 1395 1396 while(iIndexStart < infoPtr->uNumItem) 1397 { 1398 TAB_ITEM *start = TAB_GetItem(infoPtr, iIndexStart); 1399 1400 /* 1401 * find the index of the row 1402 */ 1403 /* find the first item on the next row */ 1404 for (iIndexEnd=iIndexStart; 1405 (iIndexEnd < infoPtr->uNumItem) && 1406 (TAB_GetItem(infoPtr, iIndexEnd)->rect.top == 1407 start->rect.top) ; 1408 iIndexEnd++) 1409 /* intentionally blank */; 1410 1411 /* 1412 * we need to justify these tabs so they fill the whole given 1413 * client area 1414 * 1415 */ 1416 /* find the amount of space remaining on this row */ 1417 widthDiff = clientRect.right - (2 * SELECTED_TAB_OFFSET) - 1418 TAB_GetItem(infoPtr, iIndexEnd - 1)->rect.right; 1419 1420 /* iCount is the number of tab items on this row */ 1421 iCount = iIndexEnd - iIndexStart; 1422 1423 if (iCount > 1) 1424 { 1425 remainder = widthDiff % iCount; 1426 widthDiff = widthDiff / iCount; 1427 /* add widthDiff/iCount, or extra space/items on row, to each item on this row */ 1428 for (iIndex=iIndexStart, iCount=0; iIndex < iIndexEnd; iIndex++, iCount++) 1429 { 1430 TAB_ITEM *item = TAB_GetItem(infoPtr, iIndex); 1431 1432 item->rect.left += iCount * widthDiff; 1433 item->rect.right += (iCount + 1) * widthDiff; 1434 1435 TRACE("adjusting 1 <%s>, rect %s\n", debugstr_w(item->pszText), wine_dbgstr_rect(&item->rect)); 1436 1437 } 1438 TAB_GetItem(infoPtr, iIndex - 1)->rect.right += remainder; 1439 } 1440 else /* we have only one item on this row, make it take up the entire row */ 1441 { 1442 start->rect.left = clientRect.left; 1443 start->rect.right = clientRect.right - 4; 1444 1445 TRACE("adjusting 2 <%s>, rect %s\n", debugstr_w(start->pszText), wine_dbgstr_rect(&start->rect)); 1446 } 1447 1448 iIndexStart = iIndexEnd; 1449 } 1450 } 1451 } 1452 1453 /* if TCS_VERTICAL rotate the tabs so they are along the side of the clientRect */ 1454 if(infoPtr->dwStyle & TCS_VERTICAL) 1455 { 1456 RECT rcOriginal; 1457 for(iIndex = 0; iIndex < infoPtr->uNumItem; iIndex++) 1458 { 1459 rcItem = &TAB_GetItem(infoPtr, iIndex)->rect; 1460 1461 rcOriginal = *rcItem; 1462 1463 /* this is rotating the items by 90 degrees clockwise around the center of the control */ 1464 rcItem->top = (rcOriginal.left - clientRect.left); 1465 rcItem->bottom = rcItem->top + (rcOriginal.right - rcOriginal.left); 1466 rcItem->left = rcOriginal.top; 1467 rcItem->right = rcOriginal.bottom; 1468 } 1469 } 1470 1471 TAB_EnsureSelectionVisible(infoPtr); 1472 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL); 1473 1474 /* Cleanup */ 1475 SelectObject (hdc, hOldFont); 1476 ReleaseDC (infoPtr->hwnd, hdc); 1477 } 1478 1479 1480 static void 1481 TAB_EraseTabInterior(const TAB_INFO *infoPtr, HDC hdc, INT iItem, const RECT *drawRect) 1482 { 1483 HBRUSH hbr = CreateSolidBrush (comctl32_color.clrBtnFace); 1484 BOOL deleteBrush = TRUE; 1485 RECT rTemp = *drawRect; 1486 1487 if (infoPtr->dwStyle & TCS_BUTTONS) 1488 { 1489 if (iItem == infoPtr->iSelected) 1490 { 1491 /* Background color */ 1492 if (!(infoPtr->dwStyle & TCS_OWNERDRAWFIXED)) 1493 { 1494 DeleteObject(hbr); 1495 hbr = GetSysColorBrush(COLOR_SCROLLBAR); 1496 1497 SetTextColor(hdc, comctl32_color.clr3dFace); 1498 SetBkColor(hdc, comctl32_color.clr3dHilight); 1499 1500 /* if COLOR_WINDOW happens to be the same as COLOR_3DHILIGHT 1501 * we better use 0x55aa bitmap brush to make scrollbar's background 1502 * look different from the window background. 1503 */ 1504 if (comctl32_color.clr3dHilight == comctl32_color.clrWindow) 1505 hbr = COMCTL32_hPattern55AABrush; 1506 1507 deleteBrush = FALSE; 1508 } 1509 FillRect(hdc, &rTemp, hbr); 1510 } 1511 else /* ! selected */ 1512 { 1513 if (infoPtr->dwStyle & TCS_FLATBUTTONS) 1514 { 1515 InflateRect(&rTemp, 2, 2); 1516 FillRect(hdc, &rTemp, hbr); 1517 if (iItem == infoPtr->iHotTracked || 1518 (iItem != infoPtr->iSelected && iItem == infoPtr->uFocus)) 1519 DrawEdge(hdc, &rTemp, BDR_RAISEDINNER, BF_RECT); 1520 } 1521 else 1522 FillRect(hdc, &rTemp, hbr); 1523 } 1524 1525 } 1526 else /* !TCS_BUTTONS */ 1527 { 1528 InflateRect(&rTemp, -2, -2); 1529 if (!GetWindowTheme (infoPtr->hwnd)) 1530 FillRect(hdc, &rTemp, hbr); 1531 } 1532 1533 /* highlighting is drawn on top of previous fills */ 1534 if (TAB_GetItem(infoPtr, iItem)->dwState & TCIS_HIGHLIGHTED) 1535 { 1536 if (deleteBrush) 1537 { 1538 DeleteObject(hbr); 1539 deleteBrush = FALSE; 1540 } 1541 hbr = GetSysColorBrush(COLOR_HIGHLIGHT); 1542 FillRect(hdc, &rTemp, hbr); 1543 } 1544 1545 /* Cleanup */ 1546 if (deleteBrush) DeleteObject(hbr); 1547 } 1548 1549 /****************************************************************************** 1550 * TAB_DrawItemInterior 1551 * 1552 * This method is used to draw the interior (text and icon) of a single tab 1553 * into the tab control. 1554 */ 1555 static void 1556 TAB_DrawItemInterior(const TAB_INFO *infoPtr, HDC hdc, INT iItem, RECT *drawRect) 1557 { 1558 RECT localRect; 1559 1560 HPEN htextPen; 1561 HPEN holdPen; 1562 INT oldBkMode; 1563 HFONT hOldFont; 1564 #ifdef __REACTOS__ 1565 HTHEME theme = GetWindowTheme (infoPtr->hwnd); 1566 #endif 1567 1568 /* if (drawRect == NULL) */ 1569 { 1570 BOOL isVisible; 1571 RECT itemRect; 1572 RECT selectedRect; 1573 1574 /* 1575 * Get the rectangle for the item. 1576 */ 1577 isVisible = TAB_InternalGetItemRect(infoPtr, iItem, &itemRect, &selectedRect); 1578 if (!isVisible) 1579 return; 1580 1581 /* 1582 * Make sure drawRect points to something valid; simplifies code. 1583 */ 1584 drawRect = &localRect; 1585 1586 /* 1587 * This logic copied from the part of TAB_DrawItem which draws 1588 * the tab background. It's important to keep it in sync. I 1589 * would have liked to avoid code duplication, but couldn't figure 1590 * out how without making spaghetti of TAB_DrawItem. 1591 */ 1592 if (iItem == infoPtr->iSelected) 1593 *drawRect = selectedRect; 1594 else 1595 *drawRect = itemRect; 1596 1597 if (infoPtr->dwStyle & TCS_BUTTONS) 1598 { 1599 if (iItem == infoPtr->iSelected) 1600 { 1601 drawRect->left += 4; 1602 drawRect->top += 4; 1603 drawRect->right -= 4; 1604 1605 if (infoPtr->dwStyle & TCS_VERTICAL) 1606 { 1607 if (!(infoPtr->dwStyle & TCS_BOTTOM)) drawRect->right += 1; 1608 drawRect->bottom -= 4; 1609 } 1610 else 1611 { 1612 if (infoPtr->dwStyle & TCS_BOTTOM) 1613 { 1614 drawRect->top -= 2; 1615 drawRect->bottom -= 4; 1616 } 1617 else 1618 drawRect->bottom -= 1; 1619 } 1620 } 1621 else 1622 InflateRect(drawRect, -2, -2); 1623 } 1624 else 1625 { 1626 if ((infoPtr->dwStyle & TCS_VERTICAL) && (infoPtr->dwStyle & TCS_BOTTOM)) 1627 { 1628 if (iItem != infoPtr->iSelected) 1629 { 1630 drawRect->left += 2; 1631 InflateRect(drawRect, 0, -2); 1632 } 1633 } 1634 else if (infoPtr->dwStyle & TCS_VERTICAL) 1635 { 1636 if (iItem == infoPtr->iSelected) 1637 { 1638 drawRect->right += 1; 1639 } 1640 else 1641 { 1642 drawRect->right -= 2; 1643 InflateRect(drawRect, 0, -2); 1644 } 1645 } 1646 else if (infoPtr->dwStyle & TCS_BOTTOM) 1647 { 1648 if (iItem == infoPtr->iSelected) 1649 { 1650 drawRect->top -= 2; 1651 } 1652 else 1653 { 1654 InflateRect(drawRect, -2, -2); 1655 drawRect->bottom += 2; 1656 } 1657 } 1658 else 1659 { 1660 if (iItem == infoPtr->iSelected) 1661 { 1662 drawRect->bottom += 3; 1663 } 1664 else 1665 { 1666 drawRect->bottom -= 2; 1667 InflateRect(drawRect, -2, 0); 1668 } 1669 } 1670 } 1671 } 1672 TRACE("drawRect=(%s)\n", wine_dbgstr_rect(drawRect)); 1673 1674 /* Clear interior */ 1675 TAB_EraseTabInterior (infoPtr, hdc, iItem, drawRect); 1676 1677 /* Draw the focus rectangle */ 1678 if (!(infoPtr->dwStyle & TCS_FOCUSNEVER) && 1679 (GetFocus() == infoPtr->hwnd) && 1680 (iItem == infoPtr->uFocus) ) 1681 { 1682 RECT rFocus = *drawRect; 1683 1684 if (!(infoPtr->dwStyle & TCS_BUTTONS)) InflateRect(&rFocus, -3, -3); 1685 if (infoPtr->dwStyle & TCS_BOTTOM && !(infoPtr->dwStyle & TCS_VERTICAL)) 1686 rFocus.top -= 3; 1687 1688 /* focus should stay on selected item for TCS_BUTTONS style */ 1689 if (!((infoPtr->dwStyle & TCS_BUTTONS) && (infoPtr->iSelected != iItem))) 1690 DrawFocusRect(hdc, &rFocus); 1691 } 1692 1693 /* 1694 * Text pen 1695 */ 1696 htextPen = CreatePen( PS_SOLID, 1, comctl32_color.clrBtnText ); 1697 holdPen = SelectObject(hdc, htextPen); 1698 hOldFont = SelectObject(hdc, infoPtr->hFont); 1699 1700 /* 1701 * Setup for text output 1702 */ 1703 oldBkMode = SetBkMode(hdc, TRANSPARENT); 1704 if (!GetWindowTheme (infoPtr->hwnd) || (infoPtr->dwStyle & TCS_BUTTONS)) 1705 { 1706 if ((infoPtr->dwStyle & TCS_HOTTRACK) && (iItem == infoPtr->iHotTracked) && 1707 !(infoPtr->dwStyle & TCS_FLATBUTTONS)) 1708 SetTextColor(hdc, comctl32_color.clrHighlight); 1709 else if (TAB_GetItem(infoPtr, iItem)->dwState & TCIS_HIGHLIGHTED) 1710 SetTextColor(hdc, comctl32_color.clrHighlightText); 1711 else 1712 SetTextColor(hdc, comctl32_color.clrBtnText); 1713 } 1714 1715 /* 1716 * if owner draw, tell the owner to draw 1717 */ 1718 if ((infoPtr->dwStyle & TCS_OWNERDRAWFIXED) && IsWindow(infoPtr->hwndNotify)) 1719 { 1720 DRAWITEMSTRUCT dis; 1721 UINT id; 1722 1723 drawRect->top += 2; 1724 drawRect->right -= 1; 1725 if ( iItem == infoPtr->iSelected ) 1726 InflateRect(drawRect, -1, 0); 1727 1728 id = (UINT)GetWindowLongPtrW( infoPtr->hwnd, GWLP_ID ); 1729 1730 /* fill DRAWITEMSTRUCT */ 1731 dis.CtlType = ODT_TAB; 1732 dis.CtlID = id; 1733 dis.itemID = iItem; 1734 dis.itemAction = ODA_DRAWENTIRE; 1735 dis.itemState = 0; 1736 if ( iItem == infoPtr->iSelected ) 1737 dis.itemState |= ODS_SELECTED; 1738 if (infoPtr->uFocus == iItem) 1739 dis.itemState |= ODS_FOCUS; 1740 dis.hwndItem = infoPtr->hwnd; 1741 dis.hDC = hdc; 1742 dis.rcItem = *drawRect; 1743 1744 /* when extra data fits ULONG_PTR, store it directly */ 1745 if (infoPtr->cbInfo > sizeof(LPARAM)) 1746 dis.itemData = (ULONG_PTR) TAB_GetItem(infoPtr, iItem)->extra; 1747 else 1748 { 1749 /* this could be considered broken on 64 bit, but that's how it works - 1750 only first 4 bytes are copied */ 1751 dis.itemData = 0; 1752 memcpy(&dis.itemData, (ULONG_PTR*)TAB_GetItem(infoPtr, iItem)->extra, 4); 1753 } 1754 1755 /* draw notification */ 1756 SendMessageW( infoPtr->hwndNotify, WM_DRAWITEM, id, (LPARAM)&dis ); 1757 } 1758 else 1759 { 1760 TAB_ITEM *item = TAB_GetItem(infoPtr, iItem); 1761 RECT rcTemp; 1762 RECT rcImage; 1763 1764 /* used to center the icon and text in the tab */ 1765 RECT rcText; 1766 INT center_offset_h, center_offset_v; 1767 1768 /* set rcImage to drawRect, we will use top & left in our ImageList_Draw call */ 1769 rcImage = *drawRect; 1770 1771 rcTemp = *drawRect; 1772 SetRectEmpty(&rcText); 1773 1774 /* get the rectangle that the text fits in */ 1775 if (item->pszText) 1776 { 1777 DrawTextW(hdc, item->pszText, -1, &rcText, DT_CALCRECT); 1778 } 1779 /* 1780 * If not owner draw, then do the drawing ourselves. 1781 * 1782 * Draw the icon. 1783 */ 1784 if (infoPtr->himl && item->iImage != -1) 1785 { 1786 INT cx; 1787 INT cy; 1788 1789 ImageList_GetIconSize(infoPtr->himl, &cx, &cy); 1790 1791 if(infoPtr->dwStyle & TCS_VERTICAL) 1792 { 1793 center_offset_h = ((drawRect->bottom - drawRect->top) - (cy + infoPtr->uHItemPadding + (rcText.right - rcText.left))) / 2; 1794 center_offset_v = ((drawRect->right - drawRect->left) - cx) / 2; 1795 } 1796 else 1797 { 1798 center_offset_h = ((drawRect->right - drawRect->left) - (cx + infoPtr->uHItemPadding + (rcText.right - rcText.left))) / 2; 1799 center_offset_v = ((drawRect->bottom - drawRect->top) - cy) / 2; 1800 } 1801 1802 /* if an item is selected, the icon is shifted up instead of down */ 1803 if (iItem == infoPtr->iSelected) 1804 center_offset_v -= infoPtr->uVItemPadding / 2; 1805 else 1806 center_offset_v += infoPtr->uVItemPadding / 2; 1807 1808 if (infoPtr->dwStyle & TCS_FIXEDWIDTH && infoPtr->dwStyle & (TCS_FORCELABELLEFT | TCS_FORCEICONLEFT)) 1809 center_offset_h = infoPtr->uHItemPadding; 1810 1811 if (center_offset_h < 2) 1812 center_offset_h = 2; 1813 1814 if (center_offset_v < 0) 1815 center_offset_v = 0; 1816 1817 TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%s), textlen=%d\n", 1818 debugstr_w(item->pszText), center_offset_h, center_offset_v, 1819 wine_dbgstr_rect(drawRect), (rcText.right-rcText.left)); 1820 1821 if((infoPtr->dwStyle & TCS_VERTICAL) && (infoPtr->dwStyle & TCS_BOTTOM)) 1822 { 1823 rcImage.top = drawRect->top + center_offset_h; 1824 /* if tab is TCS_VERTICAL and TCS_BOTTOM, the text is drawn from the */ 1825 /* right side of the tab, but the image still uses the left as its x position */ 1826 /* this keeps the image always drawn off of the same side of the tab */ 1827 rcImage.left = drawRect->right - cx - center_offset_v; 1828 drawRect->top += cy + infoPtr->uHItemPadding; 1829 } 1830 else if(infoPtr->dwStyle & TCS_VERTICAL) 1831 { 1832 rcImage.top = drawRect->bottom - cy - center_offset_h; 1833 rcImage.left = drawRect->left + center_offset_v; 1834 drawRect->bottom -= cy + infoPtr->uHItemPadding; 1835 } 1836 else /* normal style, whether TCS_BOTTOM or not */ 1837 { 1838 rcImage.left = drawRect->left + center_offset_h; 1839 rcImage.top = drawRect->top + center_offset_v; 1840 drawRect->left += cx + infoPtr->uHItemPadding; 1841 } 1842 1843 TRACE("drawing image=%d, left=%d, top=%d\n", 1844 item->iImage, rcImage.left, rcImage.top-1); 1845 ImageList_Draw 1846 ( 1847 infoPtr->himl, 1848 item->iImage, 1849 hdc, 1850 rcImage.left, 1851 rcImage.top, 1852 ILD_NORMAL 1853 ); 1854 } 1855 1856 /* Now position text */ 1857 if (infoPtr->dwStyle & TCS_FIXEDWIDTH && infoPtr->dwStyle & TCS_FORCELABELLEFT) 1858 center_offset_h = infoPtr->uHItemPadding; 1859 else 1860 if(infoPtr->dwStyle & TCS_VERTICAL) 1861 center_offset_h = ((drawRect->bottom - drawRect->top) - (rcText.right - rcText.left)) / 2; 1862 else 1863 center_offset_h = ((drawRect->right - drawRect->left) - (rcText.right - rcText.left)) / 2; 1864 1865 if(infoPtr->dwStyle & TCS_VERTICAL) 1866 { 1867 if(infoPtr->dwStyle & TCS_BOTTOM) 1868 drawRect->top+=center_offset_h; 1869 else 1870 drawRect->bottom-=center_offset_h; 1871 1872 center_offset_v = ((drawRect->right - drawRect->left) - (rcText.bottom - rcText.top)) / 2; 1873 } 1874 else 1875 { 1876 drawRect->left += center_offset_h; 1877 center_offset_v = ((drawRect->bottom - drawRect->top) - (rcText.bottom - rcText.top)) / 2; 1878 } 1879 1880 /* if an item is selected, the text is shifted up instead of down */ 1881 if (iItem == infoPtr->iSelected) 1882 center_offset_v -= infoPtr->uVItemPadding / 2; 1883 else 1884 center_offset_v += infoPtr->uVItemPadding / 2; 1885 1886 if (center_offset_v < 0) 1887 center_offset_v = 0; 1888 1889 if(infoPtr->dwStyle & TCS_VERTICAL) 1890 drawRect->left += center_offset_v; 1891 else 1892 drawRect->top += center_offset_v; 1893 1894 /* Draw the text */ 1895 if(infoPtr->dwStyle & TCS_VERTICAL) /* if we are vertical rotate the text and each character */ 1896 { 1897 LOGFONTW logfont; 1898 HFONT hFont; 1899 INT nEscapement = 900; 1900 INT nOrientation = 900; 1901 1902 if(infoPtr->dwStyle & TCS_BOTTOM) 1903 { 1904 nEscapement = -900; 1905 nOrientation = -900; 1906 } 1907 1908 /* to get a font with the escapement and orientation we are looking for, we need to */ 1909 /* call CreateFontIndirect, which requires us to set the values of the logfont we pass in */ 1910 if (!GetObjectW(infoPtr->hFont, sizeof(logfont), &logfont)) 1911 GetObjectW(GetStockObject(DEFAULT_GUI_FONT), sizeof(logfont), &logfont); 1912 1913 logfont.lfEscapement = nEscapement; 1914 logfont.lfOrientation = nOrientation; 1915 hFont = CreateFontIndirectW(&logfont); 1916 SelectObject(hdc, hFont); 1917 1918 if (item->pszText) 1919 { 1920 ExtTextOutW(hdc, 1921 (infoPtr->dwStyle & TCS_BOTTOM) ? drawRect->right : drawRect->left, 1922 (!(infoPtr->dwStyle & TCS_BOTTOM)) ? drawRect->bottom : drawRect->top, 1923 ETO_CLIPPED, 1924 drawRect, 1925 item->pszText, 1926 lstrlenW(item->pszText), 1927 0); 1928 } 1929 1930 DeleteObject(hFont); 1931 } 1932 else 1933 { 1934 TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%s), textlen=%d\n", 1935 debugstr_w(item->pszText), center_offset_h, center_offset_v, 1936 wine_dbgstr_rect(drawRect), (rcText.right-rcText.left)); 1937 #ifdef __REACTOS__ 1938 if (theme && item->pszText) 1939 { 1940 int partIndex = iItem == infoPtr->iSelected ? TABP_TABITEM : TABP_TOPTABITEM; 1941 int stateId = TIS_NORMAL; 1942 1943 if (iItem == infoPtr->iSelected) 1944 stateId = TIS_SELECTED; 1945 else if (iItem == infoPtr->iHotTracked) 1946 stateId = TIS_HOT; 1947 else if (iItem == infoPtr->uFocus) 1948 stateId = TIS_FOCUSED; 1949 1950 DrawThemeText(theme, 1951 hdc, 1952 partIndex, 1953 stateId, 1954 item->pszText, 1955 lstrlenW(item->pszText), 1956 DT_LEFT | DT_SINGLELINE, 0, drawRect); 1957 } 1958 else 1959 #endif 1960 if (item->pszText) 1961 { 1962 DrawTextW 1963 ( 1964 hdc, 1965 item->pszText, 1966 lstrlenW(item->pszText), 1967 drawRect, 1968 DT_LEFT | DT_SINGLELINE 1969 ); 1970 } 1971 } 1972 1973 *drawRect = rcTemp; /* restore drawRect */ 1974 } 1975 1976 /* 1977 * Cleanup 1978 */ 1979 SelectObject(hdc, hOldFont); 1980 SetBkMode(hdc, oldBkMode); 1981 SelectObject(hdc, holdPen); 1982 DeleteObject( htextPen ); 1983 } 1984 1985 /****************************************************************************** 1986 * TAB_DrawItem 1987 * 1988 * This method is used to draw a single tab into the tab control. 1989 */ 1990 static void TAB_DrawItem(const TAB_INFO *infoPtr, HDC hdc, INT iItem) 1991 { 1992 RECT itemRect; 1993 RECT selectedRect; 1994 BOOL isVisible; 1995 RECT r, fillRect, r1; 1996 INT clRight = 0; 1997 INT clBottom = 0; 1998 COLORREF bkgnd, corner; 1999 HTHEME theme; 2000 2001 /* 2002 * Get the rectangle for the item. 2003 */ 2004 isVisible = TAB_InternalGetItemRect(infoPtr, 2005 iItem, 2006 &itemRect, 2007 &selectedRect); 2008 2009 if (isVisible) 2010 { 2011 RECT rUD, rC; 2012 2013 /* Clip UpDown control to not draw over it */ 2014 if (infoPtr->needsScrolling) 2015 { 2016 GetWindowRect(infoPtr->hwnd, &rC); 2017 GetWindowRect(infoPtr->hwndUpDown, &rUD); 2018 ExcludeClipRect(hdc, rUD.left - rC.left, rUD.top - rC.top, rUD.right - rC.left, rUD.bottom - rC.top); 2019 } 2020 2021 /* If you need to see what the control is doing, 2022 * then override these variables. They will change what 2023 * fill colors are used for filling the tabs, and the 2024 * corners when drawing the edge. 2025 */ 2026 bkgnd = comctl32_color.clrBtnFace; 2027 corner = comctl32_color.clrBtnFace; 2028 2029 if (infoPtr->dwStyle & TCS_BUTTONS) 2030 { 2031 /* Get item rectangle */ 2032 r = itemRect; 2033 2034 /* Separators between flat buttons */ 2035 if ((infoPtr->dwStyle & TCS_FLATBUTTONS) && (infoPtr->exStyle & TCS_EX_FLATSEPARATORS)) 2036 { 2037 r1 = r; 2038 r1.right += (FLAT_BTN_SPACINGX -2); 2039 DrawEdge(hdc, &r1, EDGE_ETCHED, BF_RIGHT); 2040 } 2041 2042 if (iItem == infoPtr->iSelected) 2043 { 2044 DrawEdge(hdc, &r, EDGE_SUNKEN, BF_SOFT|BF_RECT); 2045 2046 OffsetRect(&r, 1, 1); 2047 } 2048 else /* ! selected */ 2049 { 2050 DWORD state = TAB_GetItem(infoPtr, iItem)->dwState; 2051 2052 if ((state & TCIS_BUTTONPRESSED) || (iItem == infoPtr->uFocus)) 2053 DrawEdge(hdc, &r, EDGE_SUNKEN, BF_SOFT|BF_RECT); 2054 else 2055 if (!(infoPtr->dwStyle & TCS_FLATBUTTONS)) 2056 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_RECT); 2057 } 2058 } 2059 else /* !TCS_BUTTONS */ 2060 { 2061 /* We draw a rectangle of different sizes depending on the selection 2062 * state. */ 2063 if (iItem == infoPtr->iSelected) { 2064 RECT rect; 2065 GetClientRect (infoPtr->hwnd, &rect); 2066 clRight = rect.right; 2067 clBottom = rect.bottom; 2068 r = selectedRect; 2069 } 2070 else 2071 r = itemRect; 2072 2073 /* 2074 * Erase the background. (Delay it but setup rectangle.) 2075 * This is necessary when drawing the selected item since it is larger 2076 * than the others, it might overlap with stuff already drawn by the 2077 * other tabs 2078 */ 2079 fillRect = r; 2080 2081 /* Draw themed tabs - but only if they are at the top. 2082 * Windows draws even side or bottom tabs themed, with wacky results. 2083 * However, since in Wine apps may get themed that did not opt in via 2084 * a manifest avoid theming when we know the result will be wrong */ 2085 if ((theme = GetWindowTheme (infoPtr->hwnd)) 2086 && ((infoPtr->dwStyle & (TCS_VERTICAL | TCS_BOTTOM)) == 0)) 2087 { 2088 static const int partIds[8] = { 2089 /* Normal item */ 2090 TABP_TABITEM, 2091 TABP_TABITEMLEFTEDGE, 2092 TABP_TABITEMRIGHTEDGE, 2093 TABP_TABITEMBOTHEDGE, 2094 /* Selected tab */ 2095 TABP_TOPTABITEM, 2096 TABP_TOPTABITEMLEFTEDGE, 2097 TABP_TOPTABITEMRIGHTEDGE, 2098 TABP_TOPTABITEMBOTHEDGE, 2099 }; 2100 int partIndex = 0; 2101 int stateId = TIS_NORMAL; 2102 2103 /* selected and unselected tabs have different parts */ 2104 if (iItem == infoPtr->iSelected) 2105 partIndex += 4; 2106 /* The part also differs on the position of a tab on a line. 2107 * "Visually" determining the position works well enough. */ 2108 GetClientRect(infoPtr->hwnd, &r1); 2109 if(selectedRect.left == 0) 2110 partIndex += 1; 2111 if(selectedRect.right == r1.right) 2112 partIndex += 2; 2113 2114 if (iItem == infoPtr->iSelected) 2115 stateId = TIS_SELECTED; 2116 else if (iItem == infoPtr->iHotTracked) 2117 stateId = TIS_HOT; 2118 else if (iItem == infoPtr->uFocus) 2119 stateId = TIS_FOCUSED; 2120 2121 /* Adjust rectangle for bottommost row */ 2122 if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1) 2123 r.bottom += 3; 2124 2125 DrawThemeBackground (theme, hdc, partIds[partIndex], stateId, &r, NULL); 2126 GetThemeBackgroundContentRect (theme, hdc, partIds[partIndex], stateId, &r, &r); 2127 } 2128 else if(infoPtr->dwStyle & TCS_VERTICAL) 2129 { 2130 /* These are for adjusting the drawing of a Selected tab */ 2131 /* The initial values are for the normal case of non-Selected */ 2132 int ZZ = 1; /* Do not stretch if selected */ 2133 if (iItem == infoPtr->iSelected) { 2134 ZZ = 0; 2135 2136 /* if leftmost draw the line longer */ 2137 if(selectedRect.top == 0) 2138 fillRect.top += CONTROL_BORDER_SIZEY; 2139 /* if rightmost draw the line longer */ 2140 if(selectedRect.bottom == clBottom) 2141 fillRect.bottom -= CONTROL_BORDER_SIZEY; 2142 } 2143 2144 if (infoPtr->dwStyle & TCS_BOTTOM) 2145 { 2146 /* Adjust both rectangles to match native */ 2147 r.left += (1-ZZ); 2148 2149 TRACE("<right> item=%d, fill=(%s), edge=(%s)\n", 2150 iItem, wine_dbgstr_rect(&fillRect), wine_dbgstr_rect(&r)); 2151 2152 /* Clear interior */ 2153 SetBkColor(hdc, bkgnd); 2154 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0); 2155 2156 /* Draw rectangular edge around tab */ 2157 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_RIGHT|BF_TOP|BF_BOTTOM); 2158 2159 /* Now erase the top corner and draw diagonal edge */ 2160 SetBkColor(hdc, corner); 2161 r1.left = r.right - ROUND_CORNER_SIZE - 1; 2162 r1.top = r.top; 2163 r1.right = r.right; 2164 r1.bottom = r1.top + ROUND_CORNER_SIZE; 2165 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0); 2166 r1.right--; 2167 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPLEFT); 2168 2169 /* Now erase the bottom corner and draw diagonal edge */ 2170 r1.left = r.right - ROUND_CORNER_SIZE - 1; 2171 r1.bottom = r.bottom; 2172 r1.right = r.right; 2173 r1.top = r1.bottom - ROUND_CORNER_SIZE; 2174 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0); 2175 r1.right--; 2176 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMLEFT); 2177 2178 if ((iItem == infoPtr->iSelected) && (selectedRect.top == 0)) { 2179 r1 = r; 2180 r1.right = r1.left; 2181 r1.left--; 2182 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_TOP); 2183 } 2184 2185 } 2186 else 2187 { 2188 TRACE("<left> item=%d, fill=(%s), edge=(%s)\n", 2189 iItem, wine_dbgstr_rect(&fillRect), wine_dbgstr_rect(&r)); 2190 2191 /* Clear interior */ 2192 SetBkColor(hdc, bkgnd); 2193 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0); 2194 2195 /* Draw rectangular edge around tab */ 2196 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_TOP|BF_BOTTOM); 2197 2198 /* Now erase the top corner and draw diagonal edge */ 2199 SetBkColor(hdc, corner); 2200 r1.left = r.left; 2201 r1.top = r.top; 2202 r1.right = r1.left + ROUND_CORNER_SIZE + 1; 2203 r1.bottom = r1.top + ROUND_CORNER_SIZE; 2204 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0); 2205 r1.left++; 2206 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPRIGHT); 2207 2208 /* Now erase the bottom corner and draw diagonal edge */ 2209 r1.left = r.left; 2210 r1.bottom = r.bottom; 2211 r1.right = r1.left + ROUND_CORNER_SIZE + 1; 2212 r1.top = r1.bottom - ROUND_CORNER_SIZE; 2213 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0); 2214 r1.left++; 2215 DrawEdge(hdc, &r1, EDGE_SUNKEN, BF_DIAGONAL_ENDTOPLEFT); 2216 } 2217 } 2218 else /* ! TCS_VERTICAL */ 2219 { 2220 /* These are for adjusting the drawing of a Selected tab */ 2221 /* The initial values are for the normal case of non-Selected */ 2222 if (iItem == infoPtr->iSelected) { 2223 /* if leftmost draw the line longer */ 2224 if(selectedRect.left == 0) 2225 fillRect.left += CONTROL_BORDER_SIZEX; 2226 /* if rightmost draw the line longer */ 2227 if(selectedRect.right == clRight) 2228 fillRect.right -= CONTROL_BORDER_SIZEX; 2229 } 2230 2231 if (infoPtr->dwStyle & TCS_BOTTOM) 2232 { 2233 /* Adjust both rectangles for topmost row */ 2234 if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1) 2235 { 2236 fillRect.top -= 2; 2237 r.top -= 1; 2238 } 2239 2240 TRACE("<bottom> item=%d, fill=(%s), edge=(%s)\n", 2241 iItem, wine_dbgstr_rect(&fillRect), wine_dbgstr_rect(&r)); 2242 2243 /* Clear interior */ 2244 SetBkColor(hdc, bkgnd); 2245 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0); 2246 2247 /* Draw rectangular edge around tab */ 2248 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_BOTTOM|BF_RIGHT); 2249 2250 /* Now erase the righthand corner and draw diagonal edge */ 2251 SetBkColor(hdc, corner); 2252 r1.left = r.right - ROUND_CORNER_SIZE; 2253 r1.bottom = r.bottom; 2254 r1.right = r.right; 2255 r1.top = r1.bottom - ROUND_CORNER_SIZE - 1; 2256 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0); 2257 r1.bottom--; 2258 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMLEFT); 2259 2260 /* Now erase the lefthand corner and draw diagonal edge */ 2261 r1.left = r.left; 2262 r1.bottom = r.bottom; 2263 r1.right = r1.left + ROUND_CORNER_SIZE; 2264 r1.top = r1.bottom - ROUND_CORNER_SIZE - 1; 2265 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0); 2266 r1.bottom--; 2267 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPLEFT); 2268 2269 if (iItem == infoPtr->iSelected) 2270 { 2271 r.top += 2; 2272 r.left += 1; 2273 if (selectedRect.left == 0) 2274 { 2275 r1 = r; 2276 r1.bottom = r1.top; 2277 r1.top--; 2278 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_LEFT); 2279 } 2280 } 2281 2282 } 2283 else 2284 { 2285 /* Adjust both rectangles for bottommost row */ 2286 if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1) 2287 { 2288 fillRect.bottom += 3; 2289 r.bottom += 2; 2290 } 2291 2292 TRACE("<top> item=%d, fill=(%s), edge=(%s)\n", 2293 iItem, wine_dbgstr_rect(&fillRect), wine_dbgstr_rect(&r)); 2294 2295 /* Clear interior */ 2296 SetBkColor(hdc, bkgnd); 2297 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0); 2298 2299 /* Draw rectangular edge around tab */ 2300 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_TOP|BF_RIGHT); 2301 2302 /* Now erase the righthand corner and draw diagonal edge */ 2303 SetBkColor(hdc, corner); 2304 r1.left = r.right - ROUND_CORNER_SIZE; 2305 r1.top = r.top; 2306 r1.right = r.right; 2307 r1.bottom = r1.top + ROUND_CORNER_SIZE + 1; 2308 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0); 2309 r1.top++; 2310 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMRIGHT); 2311 2312 /* Now erase the lefthand corner and draw diagonal edge */ 2313 r1.left = r.left; 2314 r1.top = r.top; 2315 r1.right = r1.left + ROUND_CORNER_SIZE; 2316 r1.bottom = r1.top + ROUND_CORNER_SIZE + 1; 2317 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0); 2318 r1.top++; 2319 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPRIGHT); 2320 } 2321 } 2322 } 2323 2324 TAB_DumpItemInternal(infoPtr, iItem); 2325 2326 /* This modifies r to be the text rectangle. */ 2327 TAB_DrawItemInterior(infoPtr, hdc, iItem, &r); 2328 } 2329 } 2330 2331 /****************************************************************************** 2332 * TAB_DrawBorder 2333 * 2334 * This method is used to draw the raised border around the tab control 2335 * "content" area. 2336 */ 2337 static void TAB_DrawBorder(const TAB_INFO *infoPtr, HDC hdc) 2338 { 2339 RECT rect; 2340 HTHEME theme = GetWindowTheme (infoPtr->hwnd); 2341 2342 GetClientRect (infoPtr->hwnd, &rect); 2343 2344 /* 2345 * Adjust for the style 2346 */ 2347 2348 if (infoPtr->uNumItem) 2349 { 2350 if ((infoPtr->dwStyle & TCS_BOTTOM) && !(infoPtr->dwStyle & TCS_VERTICAL)) 2351 rect.bottom -= infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX; 2352 else if((infoPtr->dwStyle & TCS_BOTTOM) && (infoPtr->dwStyle & TCS_VERTICAL)) 2353 rect.right -= infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX; 2354 else if(infoPtr->dwStyle & TCS_VERTICAL) 2355 rect.left += infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX; 2356 else /* not TCS_VERTICAL and not TCS_BOTTOM */ 2357 rect.top += infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX; 2358 } 2359 2360 TRACE("border=(%s)\n", wine_dbgstr_rect(&rect)); 2361 2362 if (theme) 2363 DrawThemeBackground (theme, hdc, TABP_PANE, 0, &rect, NULL); 2364 else 2365 DrawEdge(hdc, &rect, EDGE_RAISED, BF_SOFT|BF_RECT); 2366 } 2367 2368 /****************************************************************************** 2369 * TAB_Refresh 2370 * 2371 * This method repaints the tab control.. 2372 */ 2373 static void TAB_Refresh (const TAB_INFO *infoPtr, HDC hdc) 2374 { 2375 HFONT hOldFont; 2376 INT i; 2377 2378 if (!infoPtr->DoRedraw) 2379 return; 2380 2381 hOldFont = SelectObject (hdc, infoPtr->hFont); 2382 2383 if (infoPtr->dwStyle & TCS_BUTTONS) 2384 { 2385 for (i = 0; i < infoPtr->uNumItem; i++) 2386 TAB_DrawItem (infoPtr, hdc, i); 2387 } 2388 else 2389 { 2390 /* Draw all the non selected item first */ 2391 for (i = 0; i < infoPtr->uNumItem; i++) 2392 { 2393 if (i != infoPtr->iSelected) 2394 TAB_DrawItem (infoPtr, hdc, i); 2395 } 2396 2397 /* Now, draw the border, draw it before the selected item 2398 * since the selected item overwrites part of the border. */ 2399 TAB_DrawBorder (infoPtr, hdc); 2400 2401 /* Then, draw the selected item */ 2402 TAB_DrawItem (infoPtr, hdc, infoPtr->iSelected); 2403 } 2404 2405 SelectObject (hdc, hOldFont); 2406 } 2407 2408 static inline DWORD TAB_GetRowCount (const TAB_INFO *infoPtr) 2409 { 2410 TRACE("(%p)\n", infoPtr); 2411 return infoPtr->uNumRows; 2412 } 2413 2414 static inline LRESULT TAB_SetRedraw (TAB_INFO *infoPtr, BOOL doRedraw) 2415 { 2416 infoPtr->DoRedraw = doRedraw; 2417 return 0; 2418 } 2419 2420 /****************************************************************************** 2421 * TAB_EnsureSelectionVisible 2422 * 2423 * This method will make sure that the current selection is completely 2424 * visible by scrolling until it is. 2425 */ 2426 static void TAB_EnsureSelectionVisible( 2427 TAB_INFO* infoPtr) 2428 { 2429 INT iSelected = infoPtr->iSelected; 2430 INT iOrigLeftmostVisible = infoPtr->leftmostVisible; 2431 2432 if (iSelected < 0) 2433 return; 2434 2435 /* set the items row to the bottommost row or topmost row depending on 2436 * style */ 2437 if ((infoPtr->uNumRows > 1) && !(infoPtr->dwStyle & TCS_BUTTONS)) 2438 { 2439 TAB_ITEM *selected = TAB_GetItem(infoPtr, iSelected); 2440 INT newselected; 2441 INT iTargetRow; 2442 2443 if(infoPtr->dwStyle & TCS_VERTICAL) 2444 newselected = selected->rect.left; 2445 else 2446 newselected = selected->rect.top; 2447 2448 /* the target row is always (number of rows - 1) 2449 as row 0 is furthest from the clientRect */ 2450 iTargetRow = infoPtr->uNumRows - 1; 2451 2452 if (newselected != iTargetRow) 2453 { 2454 UINT i; 2455 if(infoPtr->dwStyle & TCS_VERTICAL) 2456 { 2457 for (i=0; i < infoPtr->uNumItem; i++) 2458 { 2459 /* move everything in the row of the selected item to the iTargetRow */ 2460 TAB_ITEM *item = TAB_GetItem(infoPtr, i); 2461 2462 if (item->rect.left == newselected ) 2463 item->rect.left = iTargetRow; 2464 else 2465 { 2466 if (item->rect.left > newselected) 2467 item->rect.left-=1; 2468 } 2469 } 2470 } 2471 else 2472 { 2473 for (i=0; i < infoPtr->uNumItem; i++) 2474 { 2475 TAB_ITEM *item = TAB_GetItem(infoPtr, i); 2476 2477 if (item->rect.top == newselected ) 2478 item->rect.top = iTargetRow; 2479 else 2480 { 2481 if (item->rect.top > newselected) 2482 item->rect.top-=1; 2483 } 2484 } 2485 } 2486 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL); 2487 } 2488 } 2489 2490 /* 2491 * Do the trivial cases first. 2492 */ 2493 if ( (!infoPtr->needsScrolling) || 2494 (infoPtr->hwndUpDown==0) || (infoPtr->dwStyle & TCS_VERTICAL)) 2495 return; 2496 2497 if (infoPtr->leftmostVisible >= iSelected) 2498 { 2499 infoPtr->leftmostVisible = iSelected; 2500 } 2501 else 2502 { 2503 TAB_ITEM *selected = TAB_GetItem(infoPtr, iSelected); 2504 RECT r; 2505 INT width; 2506 UINT i; 2507 2508 /* Calculate the part of the client area that is visible */ 2509 GetClientRect(infoPtr->hwnd, &r); 2510 width = r.right; 2511 2512 GetClientRect(infoPtr->hwndUpDown, &r); 2513 width -= r.right; 2514 2515 if ((selected->rect.right - 2516 selected->rect.left) >= width ) 2517 { 2518 /* Special case: width of selected item is greater than visible 2519 * part of control. 2520 */ 2521 infoPtr->leftmostVisible = iSelected; 2522 } 2523 else 2524 { 2525 for (i = infoPtr->leftmostVisible; i < infoPtr->uNumItem; i++) 2526 { 2527 if ((selected->rect.right - TAB_GetItem(infoPtr, i)->rect.left) < width) 2528 break; 2529 } 2530 infoPtr->leftmostVisible = i; 2531 } 2532 } 2533 2534 if (infoPtr->leftmostVisible != iOrigLeftmostVisible) 2535 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL); 2536 2537 SendMessageW(infoPtr->hwndUpDown, UDM_SETPOS, 0, 2538 MAKELONG(infoPtr->leftmostVisible, 0)); 2539 } 2540 2541 /****************************************************************************** 2542 * TAB_InvalidateTabArea 2543 * 2544 * This method will invalidate the portion of the control that contains the 2545 * tabs. It is called when the state of the control changes and needs 2546 * to be redisplayed 2547 */ 2548 static void TAB_InvalidateTabArea(const TAB_INFO *infoPtr) 2549 { 2550 RECT clientRect, rInvalidate, rAdjClient; 2551 INT lastRow = infoPtr->uNumRows - 1; 2552 RECT rect; 2553 2554 if (lastRow < 0) return; 2555 2556 GetClientRect(infoPtr->hwnd, &clientRect); 2557 rInvalidate = clientRect; 2558 rAdjClient = clientRect; 2559 2560 TAB_AdjustRect(infoPtr, 0, &rAdjClient); 2561 2562 TAB_InternalGetItemRect(infoPtr, infoPtr->uNumItem-1 , &rect, NULL); 2563 if ((infoPtr->dwStyle & TCS_BOTTOM) && (infoPtr->dwStyle & TCS_VERTICAL)) 2564 { 2565 rInvalidate.left = rAdjClient.right; 2566 if (infoPtr->uNumRows == 1) 2567 rInvalidate.bottom = clientRect.top + rect.bottom + 2 * SELECTED_TAB_OFFSET; 2568 } 2569 else if(infoPtr->dwStyle & TCS_VERTICAL) 2570 { 2571 rInvalidate.right = rAdjClient.left; 2572 if (infoPtr->uNumRows == 1) 2573 rInvalidate.bottom = clientRect.top + rect.bottom + 2 * SELECTED_TAB_OFFSET; 2574 } 2575 else if (infoPtr->dwStyle & TCS_BOTTOM) 2576 { 2577 rInvalidate.top = rAdjClient.bottom; 2578 if (infoPtr->uNumRows == 1) 2579 rInvalidate.right = clientRect.left + rect.right + 2 * SELECTED_TAB_OFFSET; 2580 } 2581 else 2582 { 2583 rInvalidate.bottom = rAdjClient.top; 2584 if (infoPtr->uNumRows == 1) 2585 rInvalidate.right = clientRect.left + rect.right + 2 * SELECTED_TAB_OFFSET; 2586 } 2587 2588 /* Punch out the updown control */ 2589 if (infoPtr->needsScrolling && (rInvalidate.right > 0)) { 2590 RECT r; 2591 GetClientRect(infoPtr->hwndUpDown, &r); 2592 if (rInvalidate.right > clientRect.right - r.left) 2593 rInvalidate.right = rInvalidate.right - (r.right - r.left); 2594 else 2595 rInvalidate.right = clientRect.right - r.left; 2596 } 2597 2598 TRACE("invalidate (%s)\n", wine_dbgstr_rect(&rInvalidate)); 2599 2600 InvalidateRect(infoPtr->hwnd, &rInvalidate, TRUE); 2601 } 2602 2603 static inline LRESULT TAB_Paint (TAB_INFO *infoPtr, HDC hdcPaint) 2604 { 2605 HDC hdc; 2606 PAINTSTRUCT ps; 2607 2608 if (hdcPaint) 2609 hdc = hdcPaint; 2610 else 2611 { 2612 hdc = BeginPaint (infoPtr->hwnd, &ps); 2613 TRACE("erase %d, rect=(%s)\n", ps.fErase, wine_dbgstr_rect(&ps.rcPaint)); 2614 } 2615 2616 TAB_Refresh (infoPtr, hdc); 2617 2618 if (!hdcPaint) 2619 EndPaint (infoPtr->hwnd, &ps); 2620 2621 return 0; 2622 } 2623 2624 static LRESULT 2625 TAB_InsertItemT (TAB_INFO *infoPtr, INT iItem, const TCITEMW *pti, BOOL bUnicode) 2626 { 2627 TAB_ITEM *item; 2628 RECT rect; 2629 2630 GetClientRect (infoPtr->hwnd, &rect); 2631 TRACE("Rect: %p %s\n", infoPtr->hwnd, wine_dbgstr_rect(&rect)); 2632 2633 if (iItem < 0) return -1; 2634 if (iItem > infoPtr->uNumItem) 2635 iItem = infoPtr->uNumItem; 2636 2637 TAB_DumpItemExternalT(pti, iItem, bUnicode); 2638 2639 if (!(item = Alloc(TAB_ITEM_SIZE(infoPtr)))) return FALSE; 2640 if (DPA_InsertPtr(infoPtr->items, iItem, item) == -1) 2641 { 2642 Free(item); 2643 return FALSE; 2644 } 2645 2646 if (infoPtr->uNumItem == 0) 2647 infoPtr->iSelected = 0; 2648 else if (iItem <= infoPtr->iSelected) 2649 infoPtr->iSelected++; 2650 2651 infoPtr->uNumItem++; 2652 2653 item->pszText = NULL; 2654 if (pti->mask & TCIF_TEXT) 2655 { 2656 if (bUnicode) 2657 Str_SetPtrW (&item->pszText, pti->pszText); 2658 else 2659 Str_SetPtrAtoW (&item->pszText, (LPSTR)pti->pszText); 2660 } 2661 2662 if (pti->mask & TCIF_IMAGE) 2663 item->iImage = pti->iImage; 2664 else 2665 item->iImage = -1; 2666 2667 if (pti->mask & TCIF_PARAM) 2668 memcpy(item->extra, &pti->lParam, EXTRA_ITEM_SIZE(infoPtr)); 2669 else 2670 memset(item->extra, 0, EXTRA_ITEM_SIZE(infoPtr)); 2671 2672 TAB_SetItemBounds(infoPtr); 2673 if (infoPtr->uNumItem > 1) 2674 TAB_InvalidateTabArea(infoPtr); 2675 else 2676 InvalidateRect(infoPtr->hwnd, NULL, TRUE); 2677 2678 TRACE("[%p]: added item %d %s\n", 2679 infoPtr->hwnd, iItem, debugstr_w(item->pszText)); 2680 2681 /* If we haven't set the current focus yet, set it now. */ 2682 if (infoPtr->uFocus == -1) 2683 TAB_SetCurFocus(infoPtr, iItem); 2684 2685 return iItem; 2686 } 2687 2688 static LRESULT 2689 TAB_SetItemSize (TAB_INFO *infoPtr, INT cx, INT cy) 2690 { 2691 LONG lResult = 0; 2692 BOOL bNeedPaint = FALSE; 2693 2694 lResult = MAKELONG(infoPtr->tabWidth, infoPtr->tabHeight); 2695 2696 /* UNDOCUMENTED: If requested Width or Height is 0 this means that program wants to use auto size. */ 2697 if (infoPtr->dwStyle & TCS_FIXEDWIDTH && (infoPtr->tabWidth != cx)) 2698 { 2699 infoPtr->tabWidth = cx; 2700 bNeedPaint = TRUE; 2701 } 2702 2703 if (infoPtr->tabHeight != cy) 2704 { 2705 if ((infoPtr->fHeightSet = (cy != 0))) 2706 infoPtr->tabHeight = cy; 2707 2708 bNeedPaint = TRUE; 2709 } 2710 TRACE("was h=%d,w=%d, now h=%d,w=%d\n", 2711 HIWORD(lResult), LOWORD(lResult), 2712 infoPtr->tabHeight, infoPtr->tabWidth); 2713 2714 if (bNeedPaint) 2715 { 2716 TAB_SetItemBounds(infoPtr); 2717 RedrawWindow(infoPtr->hwnd, NULL, NULL, RDW_ERASE | RDW_INVALIDATE | RDW_UPDATENOW); 2718 } 2719 2720 return lResult; 2721 } 2722 2723 static inline LRESULT TAB_SetMinTabWidth (TAB_INFO *infoPtr, INT cx) 2724 { 2725 INT oldcx = 0; 2726 2727 TRACE("(%p,%d)\n", infoPtr, cx); 2728 2729 if (infoPtr->tabMinWidth < 0) 2730 oldcx = DEFAULT_MIN_TAB_WIDTH; 2731 else 2732 oldcx = infoPtr->tabMinWidth; 2733 infoPtr->tabMinWidth = cx; 2734 TAB_SetItemBounds(infoPtr); 2735 return oldcx; 2736 } 2737 2738 static inline LRESULT 2739 TAB_HighlightItem (TAB_INFO *infoPtr, INT iItem, BOOL fHighlight) 2740 { 2741 LPDWORD lpState; 2742 DWORD oldState; 2743 RECT r; 2744 2745 TRACE("(%p,%d,%s)\n", infoPtr, iItem, fHighlight ? "true" : "false"); 2746 2747 if (iItem < 0 || iItem >= infoPtr->uNumItem) 2748 return FALSE; 2749 2750 lpState = &TAB_GetItem(infoPtr, iItem)->dwState; 2751 oldState = *lpState; 2752 2753 if (fHighlight) 2754 *lpState |= TCIS_HIGHLIGHTED; 2755 else 2756 *lpState &= ~TCIS_HIGHLIGHTED; 2757 2758 if ((oldState != *lpState) && TAB_InternalGetItemRect (infoPtr, iItem, &r, NULL)) 2759 InvalidateRect (infoPtr->hwnd, &r, TRUE); 2760 2761 return TRUE; 2762 } 2763 2764 static LRESULT 2765 TAB_SetItemT (TAB_INFO *infoPtr, INT iItem, LPTCITEMW tabItem, BOOL bUnicode) 2766 { 2767 TAB_ITEM *wineItem; 2768 2769 TRACE("(%p,%d,%p,%s)\n", infoPtr, iItem, tabItem, bUnicode ? "true" : "false"); 2770 2771 if (iItem < 0 || iItem >= infoPtr->uNumItem) 2772 return FALSE; 2773 2774 TAB_DumpItemExternalT(tabItem, iItem, bUnicode); 2775 2776 wineItem = TAB_GetItem(infoPtr, iItem); 2777 2778 if (tabItem->mask & TCIF_IMAGE) 2779 wineItem->iImage = tabItem->iImage; 2780 2781 if (tabItem->mask & TCIF_PARAM) 2782 memcpy(wineItem->extra, &tabItem->lParam, infoPtr->cbInfo); 2783 2784 if (tabItem->mask & TCIF_RTLREADING) 2785 FIXME("TCIF_RTLREADING\n"); 2786 2787 if (tabItem->mask & TCIF_STATE) 2788 wineItem->dwState = (wineItem->dwState & ~tabItem->dwStateMask) | 2789 ( tabItem->dwState & tabItem->dwStateMask); 2790 2791 if (tabItem->mask & TCIF_TEXT) 2792 { 2793 Free(wineItem->pszText); 2794 wineItem->pszText = NULL; 2795 if (bUnicode) 2796 Str_SetPtrW(&wineItem->pszText, tabItem->pszText); 2797 else 2798 Str_SetPtrAtoW(&wineItem->pszText, (LPSTR)tabItem->pszText); 2799 } 2800 2801 /* Update and repaint tabs */ 2802 TAB_SetItemBounds(infoPtr); 2803 TAB_InvalidateTabArea(infoPtr); 2804 2805 return TRUE; 2806 } 2807 2808 static inline LRESULT TAB_GetItemCount (const TAB_INFO *infoPtr) 2809 { 2810 TRACE("\n"); 2811 return infoPtr->uNumItem; 2812 } 2813 2814 2815 static LRESULT 2816 TAB_GetItemT (TAB_INFO *infoPtr, INT iItem, LPTCITEMW tabItem, BOOL bUnicode) 2817 { 2818 TAB_ITEM *wineItem; 2819 2820 TRACE("(%p,%d,%p,%s)\n", infoPtr, iItem, tabItem, bUnicode ? "true" : "false"); 2821 2822 if (!tabItem) return FALSE; 2823 2824 if (iItem < 0 || iItem >= infoPtr->uNumItem) 2825 { 2826 /* init requested fields */ 2827 if (tabItem->mask & TCIF_IMAGE) tabItem->iImage = 0; 2828 if (tabItem->mask & TCIF_PARAM) tabItem->lParam = 0; 2829 if (tabItem->mask & TCIF_STATE) tabItem->dwState = 0; 2830 return FALSE; 2831 } 2832 2833 wineItem = TAB_GetItem(infoPtr, iItem); 2834 2835 if (tabItem->mask & TCIF_IMAGE) 2836 tabItem->iImage = wineItem->iImage; 2837 2838 if (tabItem->mask & TCIF_PARAM) 2839 memcpy(&tabItem->lParam, wineItem->extra, infoPtr->cbInfo); 2840 2841 if (tabItem->mask & TCIF_RTLREADING) 2842 FIXME("TCIF_RTLREADING\n"); 2843 2844 if (tabItem->mask & TCIF_STATE) 2845 tabItem->dwState = wineItem->dwState & tabItem->dwStateMask; 2846 2847 if (tabItem->mask & TCIF_TEXT) 2848 { 2849 if (bUnicode) 2850 Str_GetPtrW (wineItem->pszText, tabItem->pszText, tabItem->cchTextMax); 2851 else 2852 Str_GetPtrWtoA (wineItem->pszText, (LPSTR)tabItem->pszText, tabItem->cchTextMax); 2853 } 2854 2855 TAB_DumpItemExternalT(tabItem, iItem, bUnicode); 2856 2857 return TRUE; 2858 } 2859 2860 2861 static LRESULT TAB_DeleteItem (TAB_INFO *infoPtr, INT iItem) 2862 { 2863 TAB_ITEM *item; 2864 2865 TRACE("(%p, %d)\n", infoPtr, iItem); 2866 2867 if (iItem < 0 || iItem >= infoPtr->uNumItem) return FALSE; 2868 2869 TAB_InvalidateTabArea(infoPtr); 2870 item = TAB_GetItem(infoPtr, iItem); 2871 Free(item->pszText); 2872 Free(item); 2873 infoPtr->uNumItem--; 2874 DPA_DeletePtr(infoPtr->items, iItem); 2875 2876 if (infoPtr->uNumItem == 0) 2877 { 2878 if (infoPtr->iHotTracked >= 0) 2879 { 2880 KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER); 2881 infoPtr->iHotTracked = -1; 2882 } 2883 2884 infoPtr->iSelected = -1; 2885 } 2886 else 2887 { 2888 if (iItem <= infoPtr->iHotTracked) 2889 { 2890 /* When tabs move left/up, the hot track item may change */ 2891 FIXME("Recalc hot track\n"); 2892 } 2893 } 2894 2895 /* adjust the selected index */ 2896 if (iItem == infoPtr->iSelected) 2897 infoPtr->iSelected = -1; 2898 else if (iItem < infoPtr->iSelected) 2899 infoPtr->iSelected--; 2900 2901 /* reposition and repaint tabs */ 2902 TAB_SetItemBounds(infoPtr); 2903 2904 return TRUE; 2905 } 2906 2907 static inline LRESULT TAB_DeleteAllItems (TAB_INFO *infoPtr) 2908 { 2909 TRACE("(%p)\n", infoPtr); 2910 while (infoPtr->uNumItem) 2911 TAB_DeleteItem (infoPtr, 0); 2912 return TRUE; 2913 } 2914 2915 2916 static inline LRESULT TAB_GetFont (const TAB_INFO *infoPtr) 2917 { 2918 TRACE("(%p) returning %p\n", infoPtr, infoPtr->hFont); 2919 return (LRESULT)infoPtr->hFont; 2920 } 2921 2922 static inline LRESULT TAB_SetFont (TAB_INFO *infoPtr, HFONT hNewFont) 2923 { 2924 TRACE("(%p,%p)\n", infoPtr, hNewFont); 2925 2926 infoPtr->hFont = hNewFont; 2927 2928 TAB_SetItemBounds(infoPtr); 2929 2930 TAB_InvalidateTabArea(infoPtr); 2931 2932 return 0; 2933 } 2934 2935 2936 static inline LRESULT TAB_GetImageList (const TAB_INFO *infoPtr) 2937 { 2938 TRACE("\n"); 2939 return (LRESULT)infoPtr->himl; 2940 } 2941 2942 static inline LRESULT TAB_SetImageList (TAB_INFO *infoPtr, HIMAGELIST himlNew) 2943 { 2944 HIMAGELIST himlPrev = infoPtr->himl; 2945 TRACE("himl=%p\n", himlNew); 2946 infoPtr->himl = himlNew; 2947 TAB_SetItemBounds(infoPtr); 2948 InvalidateRect(infoPtr->hwnd, NULL, TRUE); 2949 return (LRESULT)himlPrev; 2950 } 2951 2952 static inline LRESULT TAB_GetUnicodeFormat (const TAB_INFO *infoPtr) 2953 { 2954 TRACE("(%p)\n", infoPtr); 2955 return infoPtr->bUnicode; 2956 } 2957 2958 static inline LRESULT TAB_SetUnicodeFormat (TAB_INFO *infoPtr, BOOL bUnicode) 2959 { 2960 BOOL bTemp = infoPtr->bUnicode; 2961 2962 TRACE("(%p %d)\n", infoPtr, bUnicode); 2963 infoPtr->bUnicode = bUnicode; 2964 2965 return bTemp; 2966 } 2967 2968 static inline LRESULT TAB_Size (TAB_INFO *infoPtr) 2969 { 2970 /* I'm not really sure what the following code was meant to do. 2971 This is what it is doing: 2972 When WM_SIZE is sent with SIZE_RESTORED, the control 2973 gets positioned in the top left corner. 2974 2975 RECT parent_rect; 2976 HWND parent; 2977 UINT uPosFlags,cx,cy; 2978 2979 uPosFlags=0; 2980 if (!wParam) { 2981 parent = GetParent (hwnd); 2982 GetClientRect(parent, &parent_rect); 2983 cx=LOWORD (lParam); 2984 cy=HIWORD (lParam); 2985 if (GetWindowLongW(hwnd, GWL_STYLE) & CCS_NORESIZE) 2986 uPosFlags |= (SWP_NOSIZE | SWP_NOMOVE); 2987 2988 SetWindowPos (hwnd, 0, parent_rect.left, parent_rect.top, 2989 cx, cy, uPosFlags | SWP_NOZORDER); 2990 } else { 2991 FIXME("WM_SIZE flag %x %lx not handled\n", wParam, lParam); 2992 } */ 2993 2994 /* Recompute the size/position of the tabs. */ 2995 TAB_SetItemBounds (infoPtr); 2996 2997 /* Force a repaint of the control. */ 2998 InvalidateRect(infoPtr->hwnd, NULL, TRUE); 2999 3000 return 0; 3001 } 3002 3003 3004 static LRESULT TAB_Create (HWND hwnd, LPARAM lParam) 3005 { 3006 TAB_INFO *infoPtr; 3007 TEXTMETRICW fontMetrics; 3008 HDC hdc; 3009 HFONT hOldFont; 3010 DWORD style; 3011 3012 infoPtr = Alloc (sizeof(TAB_INFO)); 3013 3014 SetWindowLongPtrW(hwnd, 0, (DWORD_PTR)infoPtr); 3015 3016 infoPtr->hwnd = hwnd; 3017 infoPtr->hwndNotify = ((LPCREATESTRUCTW)lParam)->hwndParent; 3018 infoPtr->uNumItem = 0; 3019 infoPtr->uNumRows = 0; 3020 infoPtr->uHItemPadding = 6; 3021 infoPtr->uVItemPadding = 3; 3022 infoPtr->uHItemPadding_s = 6; 3023 infoPtr->uVItemPadding_s = 3; 3024 infoPtr->hFont = 0; 3025 infoPtr->items = DPA_Create(8); 3026 infoPtr->hcurArrow = LoadCursorW (0, (LPWSTR)IDC_ARROW); 3027 infoPtr->iSelected = -1; 3028 infoPtr->iHotTracked = -1; 3029 infoPtr->uFocus = -1; 3030 infoPtr->hwndToolTip = 0; 3031 infoPtr->DoRedraw = TRUE; 3032 infoPtr->needsScrolling = FALSE; 3033 infoPtr->hwndUpDown = 0; 3034 infoPtr->leftmostVisible = 0; 3035 infoPtr->fHeightSet = FALSE; 3036 infoPtr->bUnicode = IsWindowUnicode (hwnd); 3037 infoPtr->cbInfo = sizeof(LPARAM); 3038 3039 TRACE("Created tab control, hwnd [%p]\n", hwnd); 3040 3041 /* The tab control always has the WS_CLIPSIBLINGS style. Even 3042 if you don't specify it in CreateWindow. This is necessary in 3043 order for paint to work correctly. This follows windows behaviour. */ 3044 style = GetWindowLongW(hwnd, GWL_STYLE); 3045 if (style & TCS_VERTICAL) style |= TCS_MULTILINE; 3046 style |= WS_CLIPSIBLINGS; 3047 SetWindowLongW(hwnd, GWL_STYLE, style); 3048 3049 infoPtr->dwStyle = style; 3050 infoPtr->exStyle = (style & TCS_FLATBUTTONS) ? TCS_EX_FLATSEPARATORS : 0; 3051 3052 if (infoPtr->dwStyle & TCS_TOOLTIPS) { 3053 /* Create tooltip control */ 3054 infoPtr->hwndToolTip = 3055 CreateWindowExW (0, TOOLTIPS_CLASSW, NULL, WS_POPUP, 3056 CW_USEDEFAULT, CW_USEDEFAULT, 3057 CW_USEDEFAULT, CW_USEDEFAULT, 3058 hwnd, 0, 0, 0); 3059 3060 /* Send NM_TOOLTIPSCREATED notification */ 3061 if (infoPtr->hwndToolTip) { 3062 NMTOOLTIPSCREATED nmttc; 3063 3064 nmttc.hdr.hwndFrom = hwnd; 3065 nmttc.hdr.idFrom = GetWindowLongPtrW(hwnd, GWLP_ID); 3066 nmttc.hdr.code = NM_TOOLTIPSCREATED; 3067 nmttc.hwndToolTips = infoPtr->hwndToolTip; 3068 3069 SendMessageW (infoPtr->hwndNotify, WM_NOTIFY, 3070 GetWindowLongPtrW(hwnd, GWLP_ID), (LPARAM)&nmttc); 3071 } 3072 } 3073 3074 OpenThemeData (infoPtr->hwnd, themeClass); 3075 3076 /* 3077 * We need to get text information so we need a DC and we need to select 3078 * a font. 3079 */ 3080 hdc = GetDC(hwnd); 3081 hOldFont = SelectObject (hdc, GetStockObject (SYSTEM_FONT)); 3082 3083 /* Use the system font to determine the initial height of a tab. */ 3084 GetTextMetricsW(hdc, &fontMetrics); 3085 3086 /* 3087 * Make sure there is enough space for the letters + growing the 3088 * selected item + extra space for the selected item. 3089 */ 3090 infoPtr->tabHeight = fontMetrics.tmHeight + SELECTED_TAB_OFFSET + 3091 ((infoPtr->dwStyle & TCS_BUTTONS) ? 2 : 1) * 3092 infoPtr->uVItemPadding; 3093 3094 /* Initialize the width of a tab. */ 3095 if (infoPtr->dwStyle & TCS_FIXEDWIDTH) 3096 infoPtr->tabWidth = GetDeviceCaps(hdc, LOGPIXELSX); 3097 3098 infoPtr->tabMinWidth = -1; 3099 3100 TRACE("tabH=%d, tabW=%d\n", infoPtr->tabHeight, infoPtr->tabWidth); 3101 3102 SelectObject (hdc, hOldFont); 3103 ReleaseDC(hwnd, hdc); 3104 3105 return 0; 3106 } 3107 3108 static LRESULT 3109 TAB_Destroy (TAB_INFO *infoPtr) 3110 { 3111 INT iItem; 3112 3113 SetWindowLongPtrW(infoPtr->hwnd, 0, 0); 3114 3115 for (iItem = infoPtr->uNumItem - 1; iItem >= 0; iItem--) 3116 { 3117 TAB_ITEM *tab = TAB_GetItem(infoPtr, iItem); 3118 3119 DPA_DeletePtr(infoPtr->items, iItem); 3120 infoPtr->uNumItem--; 3121 3122 Free(tab->pszText); 3123 Free(tab); 3124 } 3125 DPA_Destroy(infoPtr->items); 3126 infoPtr->items = NULL; 3127 3128 if (infoPtr->hwndToolTip) 3129 DestroyWindow (infoPtr->hwndToolTip); 3130 3131 if (infoPtr->hwndUpDown) 3132 DestroyWindow(infoPtr->hwndUpDown); 3133 3134 if (infoPtr->iHotTracked >= 0) 3135 KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER); 3136 3137 CloseThemeData (GetWindowTheme (infoPtr->hwnd)); 3138 3139 Free (infoPtr); 3140 return 0; 3141 } 3142 3143 /* update theme after a WM_THEMECHANGED message */ 3144 static LRESULT theme_changed(const TAB_INFO *infoPtr) 3145 { 3146 HTHEME theme = GetWindowTheme (infoPtr->hwnd); 3147 CloseThemeData (theme); 3148 OpenThemeData (infoPtr->hwnd, themeClass); 3149 return 0; 3150 } 3151 3152 static LRESULT TAB_NCCalcSize(WPARAM wParam) 3153 { 3154 if (!wParam) 3155 return 0; 3156 return WVR_ALIGNTOP; 3157 } 3158 3159 static inline LRESULT 3160 TAB_SetItemExtra (TAB_INFO *infoPtr, INT cbInfo) 3161 { 3162 TRACE("(%p %d)\n", infoPtr, cbInfo); 3163 3164 if (cbInfo < 0 || infoPtr->uNumItem) return FALSE; 3165 3166 infoPtr->cbInfo = cbInfo; 3167 return TRUE; 3168 } 3169 3170 static LRESULT TAB_RemoveImage (TAB_INFO *infoPtr, INT image) 3171 { 3172 TRACE("%p %d\n", infoPtr, image); 3173 3174 if (ImageList_Remove (infoPtr->himl, image)) 3175 { 3176 INT i, *idx; 3177 RECT r; 3178 3179 /* shift indices, repaint items if needed */ 3180 for (i = 0; i < infoPtr->uNumItem; i++) 3181 { 3182 idx = &TAB_GetItem(infoPtr, i)->iImage; 3183 if (*idx >= image) 3184 { 3185 if (*idx == image) 3186 *idx = -1; 3187 else 3188 (*idx)--; 3189 3190 /* repaint item */ 3191 if (TAB_InternalGetItemRect (infoPtr, i, &r, NULL)) 3192 InvalidateRect (infoPtr->hwnd, &r, TRUE); 3193 } 3194 } 3195 } 3196 3197 return 0; 3198 } 3199 3200 static LRESULT 3201 TAB_SetExtendedStyle (TAB_INFO *infoPtr, DWORD exMask, DWORD exStyle) 3202 { 3203 DWORD prevstyle = infoPtr->exStyle; 3204 3205 /* zero mask means all styles */ 3206 if (exMask == 0) exMask = ~0; 3207 3208 if (exMask & TCS_EX_REGISTERDROP) 3209 { 3210 FIXME("TCS_EX_REGISTERDROP style unimplemented\n"); 3211 exMask &= ~TCS_EX_REGISTERDROP; 3212 exStyle &= ~TCS_EX_REGISTERDROP; 3213 } 3214 3215 if (exMask & TCS_EX_FLATSEPARATORS) 3216 { 3217 if ((prevstyle ^ exStyle) & TCS_EX_FLATSEPARATORS) 3218 { 3219 infoPtr->exStyle ^= TCS_EX_FLATSEPARATORS; 3220 TAB_InvalidateTabArea(infoPtr); 3221 } 3222 } 3223 3224 return prevstyle; 3225 } 3226 3227 static inline LRESULT 3228 TAB_GetExtendedStyle (const TAB_INFO *infoPtr) 3229 { 3230 return infoPtr->exStyle; 3231 } 3232 3233 static LRESULT 3234 TAB_DeselectAll (TAB_INFO *infoPtr, BOOL excludesel) 3235 { 3236 BOOL paint = FALSE; 3237 INT i, selected = infoPtr->iSelected; 3238 3239 TRACE("(%p, %d)\n", infoPtr, excludesel); 3240 3241 if (!(infoPtr->dwStyle & TCS_BUTTONS)) 3242 return 0; 3243 3244 for (i = 0; i < infoPtr->uNumItem; i++) 3245 { 3246 if ((TAB_GetItem(infoPtr, i)->dwState & TCIS_BUTTONPRESSED) && 3247 (selected != i)) 3248 { 3249 TAB_GetItem(infoPtr, i)->dwState &= ~TCIS_BUTTONPRESSED; 3250 paint = TRUE; 3251 } 3252 } 3253 3254 if (!excludesel && (selected != -1)) 3255 { 3256 TAB_GetItem(infoPtr, selected)->dwState &= ~TCIS_BUTTONPRESSED; 3257 infoPtr->iSelected = -1; 3258 paint = TRUE; 3259 } 3260 3261 if (paint) 3262 TAB_InvalidateTabArea (infoPtr); 3263 3264 return 0; 3265 } 3266 3267 /*** 3268 * DESCRIPTION: 3269 * Processes WM_STYLECHANGED messages. 3270 * 3271 * PARAMETER(S): 3272 * [I] infoPtr : valid pointer to the tab data structure 3273 * [I] wStyleType : window style type (normal or extended) 3274 * [I] lpss : window style information 3275 * 3276 * RETURN: 3277 * Zero 3278 */ 3279 static INT TAB_StyleChanged(TAB_INFO *infoPtr, WPARAM wStyleType, 3280 const STYLESTRUCT *lpss) 3281 { 3282 TRACE("(styletype=%lx, styleOld=0x%08x, styleNew=0x%08x)\n", 3283 wStyleType, lpss->styleOld, lpss->styleNew); 3284 3285 if (wStyleType != GWL_STYLE) return 0; 3286 3287 infoPtr->dwStyle = lpss->styleNew; 3288 3289 TAB_SetItemBounds (infoPtr); 3290 InvalidateRect(infoPtr->hwnd, NULL, TRUE); 3291 3292 return 0; 3293 } 3294 3295 static LRESULT WINAPI 3296 TAB_WindowProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) 3297 { 3298 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd); 3299 3300 TRACE("hwnd=%p msg=%x wParam=%lx lParam=%lx\n", hwnd, uMsg, wParam, lParam); 3301 if (!infoPtr && (uMsg != WM_CREATE)) 3302 return DefWindowProcW (hwnd, uMsg, wParam, lParam); 3303 3304 switch (uMsg) 3305 { 3306 case TCM_GETIMAGELIST: 3307 return TAB_GetImageList (infoPtr); 3308 3309 case TCM_SETIMAGELIST: 3310 return TAB_SetImageList (infoPtr, (HIMAGELIST)lParam); 3311 3312 case TCM_GETITEMCOUNT: 3313 return TAB_GetItemCount (infoPtr); 3314 3315 case TCM_GETITEMA: 3316 case TCM_GETITEMW: 3317 return TAB_GetItemT (infoPtr, (INT)wParam, (LPTCITEMW)lParam, uMsg == TCM_GETITEMW); 3318 3319 case TCM_SETITEMA: 3320 case TCM_SETITEMW: 3321 return TAB_SetItemT (infoPtr, (INT)wParam, (LPTCITEMW)lParam, uMsg == TCM_SETITEMW); 3322 3323 case TCM_DELETEITEM: 3324 return TAB_DeleteItem (infoPtr, (INT)wParam); 3325 3326 case TCM_DELETEALLITEMS: 3327 return TAB_DeleteAllItems (infoPtr); 3328 3329 case TCM_GETITEMRECT: 3330 return TAB_GetItemRect (infoPtr, (INT)wParam, (LPRECT)lParam); 3331 3332 case TCM_GETCURSEL: 3333 return TAB_GetCurSel (infoPtr); 3334 3335 case TCM_HITTEST: 3336 return TAB_HitTest (infoPtr, (LPTCHITTESTINFO)lParam); 3337 3338 case TCM_SETCURSEL: 3339 return TAB_SetCurSel (infoPtr, (INT)wParam); 3340 3341 case TCM_INSERTITEMA: 3342 case TCM_INSERTITEMW: 3343 return TAB_InsertItemT (infoPtr, (INT)wParam, (TCITEMW*)lParam, uMsg == TCM_INSERTITEMW); 3344 3345 case TCM_SETITEMEXTRA: 3346 return TAB_SetItemExtra (infoPtr, (INT)wParam); 3347 3348 case TCM_ADJUSTRECT: 3349 return TAB_AdjustRect (infoPtr, (BOOL)wParam, (LPRECT)lParam); 3350 3351 case TCM_SETITEMSIZE: 3352 return TAB_SetItemSize (infoPtr, (INT)LOWORD(lParam), (INT)HIWORD(lParam)); 3353 3354 case TCM_REMOVEIMAGE: 3355 return TAB_RemoveImage (infoPtr, (INT)wParam); 3356 3357 case TCM_SETPADDING: 3358 return TAB_SetPadding (infoPtr, lParam); 3359 3360 case TCM_GETROWCOUNT: 3361 return TAB_GetRowCount(infoPtr); 3362 3363 case TCM_GETUNICODEFORMAT: 3364 return TAB_GetUnicodeFormat (infoPtr); 3365 3366 case TCM_SETUNICODEFORMAT: 3367 return TAB_SetUnicodeFormat (infoPtr, (BOOL)wParam); 3368 3369 case TCM_HIGHLIGHTITEM: 3370 return TAB_HighlightItem (infoPtr, (INT)wParam, (BOOL)LOWORD(lParam)); 3371 3372 case TCM_GETTOOLTIPS: 3373 return TAB_GetToolTips (infoPtr); 3374 3375 case TCM_SETTOOLTIPS: 3376 return TAB_SetToolTips (infoPtr, (HWND)wParam); 3377 3378 case TCM_GETCURFOCUS: 3379 return TAB_GetCurFocus (infoPtr); 3380 3381 case TCM_SETCURFOCUS: 3382 return TAB_SetCurFocus (infoPtr, (INT)wParam); 3383 3384 case TCM_SETMINTABWIDTH: 3385 return TAB_SetMinTabWidth(infoPtr, (INT)lParam); 3386 3387 case TCM_DESELECTALL: 3388 return TAB_DeselectAll (infoPtr, (BOOL)wParam); 3389 3390 case TCM_GETEXTENDEDSTYLE: 3391 return TAB_GetExtendedStyle (infoPtr); 3392 3393 case TCM_SETEXTENDEDSTYLE: 3394 return TAB_SetExtendedStyle (infoPtr, wParam, lParam); 3395 3396 case WM_GETFONT: 3397 return TAB_GetFont (infoPtr); 3398 3399 case WM_SETFONT: 3400 return TAB_SetFont (infoPtr, (HFONT)wParam); 3401 3402 case WM_CREATE: 3403 return TAB_Create (hwnd, lParam); 3404 3405 case WM_NCDESTROY: 3406 return TAB_Destroy (infoPtr); 3407 3408 case WM_GETDLGCODE: 3409 return DLGC_WANTARROWS | DLGC_WANTCHARS; 3410 3411 case WM_LBUTTONDOWN: 3412 return TAB_LButtonDown (infoPtr, wParam, lParam); 3413 3414 case WM_LBUTTONUP: 3415 return TAB_LButtonUp (infoPtr); 3416 3417 case WM_NOTIFY: 3418 return SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, wParam, lParam); 3419 3420 case WM_RBUTTONUP: 3421 TAB_RButtonUp (infoPtr); 3422 return DefWindowProcW (hwnd, uMsg, wParam, lParam); 3423 3424 case WM_MOUSEMOVE: 3425 return TAB_MouseMove (infoPtr, wParam, lParam); 3426 3427 case WM_PRINTCLIENT: 3428 case WM_PAINT: 3429 return TAB_Paint (infoPtr, (HDC)wParam); 3430 3431 case WM_SIZE: 3432 return TAB_Size (infoPtr); 3433 3434 case WM_SETREDRAW: 3435 return TAB_SetRedraw (infoPtr, (BOOL)wParam); 3436 3437 case WM_HSCROLL: 3438 return TAB_OnHScroll(infoPtr, (int)LOWORD(wParam), (int)HIWORD(wParam)); 3439 3440 case WM_STYLECHANGED: 3441 return TAB_StyleChanged(infoPtr, wParam, (LPSTYLESTRUCT)lParam); 3442 3443 case WM_SYSCOLORCHANGE: 3444 COMCTL32_RefreshSysColors(); 3445 return 0; 3446 3447 case WM_THEMECHANGED: 3448 return theme_changed (infoPtr); 3449 3450 case WM_KILLFOCUS: 3451 TAB_KillFocus(infoPtr); 3452 case WM_SETFOCUS: 3453 TAB_FocusChanging(infoPtr); 3454 break; /* Don't disturb normal focus behavior */ 3455 3456 case WM_KEYDOWN: 3457 return TAB_KeyDown(infoPtr, wParam, lParam); 3458 3459 case WM_NCHITTEST: 3460 return TAB_NCHitTest(infoPtr, lParam); 3461 3462 case WM_NCCALCSIZE: 3463 return TAB_NCCalcSize(wParam); 3464 3465 default: 3466 if (uMsg >= WM_USER && uMsg < WM_APP && !COMCTL32_IsReflectedMessage(uMsg)) 3467 WARN("unknown msg %04x wp=%08lx lp=%08lx\n", 3468 uMsg, wParam, lParam); 3469 break; 3470 } 3471 return DefWindowProcW(hwnd, uMsg, wParam, lParam); 3472 } 3473 3474 3475 void 3476 TAB_Register (void) 3477 { 3478 WNDCLASSW wndClass; 3479 3480 ZeroMemory (&wndClass, sizeof(WNDCLASSW)); 3481 wndClass.style = CS_GLOBALCLASS | CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW; 3482 wndClass.lpfnWndProc = TAB_WindowProc; 3483 wndClass.cbClsExtra = 0; 3484 wndClass.cbWndExtra = sizeof(TAB_INFO *); 3485 wndClass.hCursor = LoadCursorW (0, (LPWSTR)IDC_ARROW); 3486 wndClass.hbrBackground = (HBRUSH)(COLOR_BTNFACE+1); 3487 wndClass.lpszClassName = WC_TABCONTROLW; 3488 3489 RegisterClassW (&wndClass); 3490 } 3491 3492 3493 void 3494 TAB_Unregister (void) 3495 { 3496 UnregisterClassW (WC_TABCONTROLW, NULL); 3497 } 3498