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