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