4 * Copyright 1998 Anders Carlsson
5 * Copyright 1999 Alex Priem <alexp@sci.kun.nl>
6 * Copyright 1999 Francis Beaudet
7 * Copyright 2003 Vitaliy Margolen
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.
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.
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
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.
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.
35 * TCS_MULTISELECT - implement for VK_SPACE selection
57 WINE_DEFAULT_DEBUG_CHANNEL(tab
);
64 RECT rect
; /* bounding rectangle of the item relative to the
65 * leftmost item (the leftmost item, 0, would have a
66 * "left" member of 0 in this rectangle)
68 * additionally the top member holds the row number
69 * and bottom is unused and should be 0 */
70 BYTE extra
[1]; /* Space for caller supplied info, variable size */
73 /* The size of a tab item depends on how much extra data is requested.
74 TCM_INSERTITEM always stores at least LPARAM sized data. */
75 #define EXTRA_ITEM_SIZE(infoPtr) (max((infoPtr)->cbInfo, sizeof(LPARAM)))
76 #define TAB_ITEM_SIZE(infoPtr) FIELD_OFFSET(TAB_ITEM, extra[EXTRA_ITEM_SIZE(infoPtr)])
80 HWND hwnd
; /* Tab control window */
81 HWND hwndNotify
; /* notification window (parent) */
82 UINT uNumItem
; /* number of tab items */
83 UINT uNumRows
; /* number of tab rows */
84 INT tabHeight
; /* height of the tab row */
85 INT tabWidth
; /* width of tabs */
86 INT tabMinWidth
; /* minimum width of items */
87 USHORT uHItemPadding
; /* amount of horizontal padding, in pixels */
88 USHORT uVItemPadding
; /* amount of vertical padding, in pixels */
89 USHORT uHItemPadding_s
; /* Set amount of horizontal padding, in pixels */
90 USHORT uVItemPadding_s
; /* Set amount of vertical padding, in pixels */
91 HFONT hFont
; /* handle to the current font */
92 HCURSOR hcurArrow
; /* handle to the current cursor */
93 HIMAGELIST himl
; /* handle to an image list (may be 0) */
94 HWND hwndToolTip
; /* handle to tab's tooltip */
95 INT leftmostVisible
; /* Used for scrolling, this member contains
96 * the index of the first visible item */
97 INT iSelected
; /* the currently selected item */
98 INT iHotTracked
; /* the highlighted item under the mouse */
99 INT uFocus
; /* item which has the focus */
100 BOOL DoRedraw
; /* flag for redrawing when tab contents is changed*/
101 BOOL needsScrolling
; /* TRUE if the size of the tabs is greater than
102 * the size of the control */
103 BOOL fHeightSet
; /* was the height of the tabs explicitly set? */
104 BOOL bUnicode
; /* Unicode control? */
105 HWND hwndUpDown
; /* Updown control used for scrolling */
106 INT cbInfo
; /* Number of bytes of caller supplied info per tab */
108 DWORD exStyle
; /* Extended style used, currently:
109 TCS_EX_FLATSEPARATORS, TCS_EX_REGISTERDROP */
110 DWORD dwStyle
; /* the cached window GWL_STYLE */
112 HDPA items
; /* dynamic array of TAB_ITEM* pointers */
115 /******************************************************************************
116 * Positioning constants
118 #define SELECTED_TAB_OFFSET 2
119 #define ROUND_CORNER_SIZE 2
120 #define DISPLAY_AREA_PADDINGX 2
121 #define DISPLAY_AREA_PADDINGY 2
122 #define CONTROL_BORDER_SIZEX 2
123 #define CONTROL_BORDER_SIZEY 2
124 #define BUTTON_SPACINGX 3
125 #define BUTTON_SPACINGY 3
126 #define FLAT_BTN_SPACINGX 8
127 #define DEFAULT_MIN_TAB_WIDTH 54
128 #define DEFAULT_PADDING_X 6
129 #define EXTRA_ICON_PADDING 3
131 #define TAB_GetInfoPtr(hwnd) ((TAB_INFO *)GetWindowLongPtrW(hwnd,0))
133 #define GET_DEFAULT_MIN_TAB_WIDTH(infoPtr) (DEFAULT_MIN_TAB_WIDTH - (DEFAULT_PADDING_X - (infoPtr)->uHItemPadding) * 2)
135 /******************************************************************************
136 * Hot-tracking timer constants
138 #define TAB_HOTTRACK_TIMER 1
139 #define TAB_HOTTRACK_TIMER_INTERVAL 100 /* milliseconds */
141 static const WCHAR themeClass
[] = { 'T','a','b',0 };
143 static inline TAB_ITEM
* TAB_GetItem(const TAB_INFO
*infoPtr
, INT i
)
145 assert(i
>= 0 && i
< infoPtr
->uNumItem
);
146 return DPA_GetPtr(infoPtr
->items
, i
);
149 /******************************************************************************
152 static void TAB_InvalidateTabArea(const TAB_INFO
*);
153 static void TAB_EnsureSelectionVisible(TAB_INFO
*);
154 static void TAB_DrawItemInterior(const TAB_INFO
*, HDC
, INT
, RECT
*);
155 static LRESULT
TAB_DeselectAll(TAB_INFO
*, BOOL
);
156 static BOOL
TAB_InternalGetItemRect(const TAB_INFO
*, INT
, RECT
*, RECT
*);
159 TAB_SendSimpleNotify (const TAB_INFO
*infoPtr
, UINT code
)
163 nmhdr
.hwndFrom
= infoPtr
->hwnd
;
164 nmhdr
.idFrom
= GetWindowLongPtrW(infoPtr
->hwnd
, GWLP_ID
);
167 return (BOOL
) SendMessageW (infoPtr
->hwndNotify
, WM_NOTIFY
,
168 nmhdr
.idFrom
, (LPARAM
) &nmhdr
);
172 TAB_RelayEvent (HWND hwndTip
, HWND hwndMsg
, UINT uMsg
,
173 WPARAM wParam
, LPARAM lParam
)
181 msg
.time
= GetMessageTime ();
182 msg
.pt
.x
= (short)LOWORD(GetMessagePos ());
183 msg
.pt
.y
= (short)HIWORD(GetMessagePos ());
185 SendMessageW (hwndTip
, TTM_RELAYEVENT
, 0, (LPARAM
)&msg
);
189 TAB_DumpItemExternalT(const TCITEMW
*pti
, UINT iItem
, BOOL isW
)
192 TRACE("external tab %d, mask=0x%08x, dwState=0x%08x, dwStateMask=0x%08x, cchTextMax=0x%08x\n",
193 iItem
, pti
->mask
, pti
->dwState
, pti
->dwStateMask
, pti
->cchTextMax
);
194 TRACE("external tab %d, iImage=%d, lParam=0x%08lx, pszTextW=%s\n",
195 iItem
, pti
->iImage
, pti
->lParam
, isW
? debugstr_w(pti
->pszText
) : debugstr_a((LPSTR
)pti
->pszText
));
200 TAB_DumpItemInternal(const TAB_INFO
*infoPtr
, UINT iItem
)
203 TAB_ITEM
*ti
= TAB_GetItem(infoPtr
, iItem
);
205 TRACE("tab %d, dwState=0x%08x, pszText=%s, iImage=%d\n",
206 iItem
, ti
->dwState
, debugstr_w(ti
->pszText
), ti
->iImage
);
207 TRACE("tab %d, rect.left=%d, rect.top(row)=%d\n",
208 iItem
, ti
->rect
.left
, ti
->rect
.top
);
213 * the index of the selected tab, or -1 if no tab is selected. */
214 static inline LRESULT
TAB_GetCurSel (const TAB_INFO
*infoPtr
)
216 TRACE("(%p)\n", infoPtr
);
217 return infoPtr
->iSelected
;
221 * the index of the tab item that has the focus. */
222 static inline LRESULT
223 TAB_GetCurFocus (const TAB_INFO
*infoPtr
)
225 TRACE("(%p)\n", infoPtr
);
226 return infoPtr
->uFocus
;
229 static inline LRESULT
TAB_GetToolTips (const TAB_INFO
*infoPtr
)
231 TRACE("(%p)\n", infoPtr
);
232 return (LRESULT
)infoPtr
->hwndToolTip
;
235 static inline LRESULT
TAB_SetCurSel (TAB_INFO
*infoPtr
, INT iItem
)
237 INT prevItem
= infoPtr
->iSelected
;
239 TRACE("(%p %d)\n", infoPtr
, iItem
);
241 if (iItem
>= (INT
)infoPtr
->uNumItem
)
244 if (prevItem
!= iItem
) {
246 TAB_GetItem(infoPtr
, prevItem
)->dwState
&= ~TCIS_BUTTONPRESSED
;
250 TAB_GetItem(infoPtr
, iItem
)->dwState
|= TCIS_BUTTONPRESSED
;
251 infoPtr
->iSelected
= iItem
;
252 infoPtr
->uFocus
= iItem
;
256 infoPtr
->iSelected
= -1;
257 infoPtr
->uFocus
= -1;
260 TAB_EnsureSelectionVisible(infoPtr
);
261 TAB_InvalidateTabArea(infoPtr
);
267 static LRESULT
TAB_SetCurFocus (TAB_INFO
*infoPtr
, INT iItem
)
269 TRACE("(%p %d)\n", infoPtr
, iItem
);
272 infoPtr
->uFocus
= -1;
273 if (infoPtr
->iSelected
!= -1) {
274 infoPtr
->iSelected
= -1;
275 TAB_SendSimpleNotify(infoPtr
, TCN_SELCHANGE
);
276 TAB_InvalidateTabArea(infoPtr
);
279 else if (iItem
< infoPtr
->uNumItem
) {
280 if (infoPtr
->dwStyle
& TCS_BUTTONS
) {
281 /* set focus to new item, leave selection as is */
282 if (infoPtr
->uFocus
!= iItem
) {
283 INT prev_focus
= infoPtr
->uFocus
;
286 infoPtr
->uFocus
= iItem
;
288 if (prev_focus
!= infoPtr
->iSelected
) {
289 if (TAB_InternalGetItemRect(infoPtr
, prev_focus
, &r
, NULL
))
290 InvalidateRect(infoPtr
->hwnd
, &r
, FALSE
);
293 if (TAB_InternalGetItemRect(infoPtr
, iItem
, &r
, NULL
))
294 InvalidateRect(infoPtr
->hwnd
, &r
, FALSE
);
296 TAB_SendSimpleNotify(infoPtr
, TCN_FOCUSCHANGE
);
299 INT oldFocus
= infoPtr
->uFocus
;
300 if (infoPtr
->iSelected
!= iItem
|| oldFocus
== -1 ) {
301 infoPtr
->uFocus
= iItem
;
302 if (oldFocus
!= -1) {
303 if (!TAB_SendSimpleNotify(infoPtr
, TCN_SELCHANGING
)) {
304 infoPtr
->iSelected
= iItem
;
305 TAB_SendSimpleNotify(infoPtr
, TCN_SELCHANGE
);
308 infoPtr
->iSelected
= iItem
;
309 TAB_EnsureSelectionVisible(infoPtr
);
310 TAB_InvalidateTabArea(infoPtr
);
318 static inline LRESULT
319 TAB_SetToolTips (TAB_INFO
*infoPtr
, HWND hwndToolTip
)
321 TRACE("%p %p\n", infoPtr
, hwndToolTip
);
322 infoPtr
->hwndToolTip
= hwndToolTip
;
326 static inline LRESULT
327 TAB_SetPadding (TAB_INFO
*infoPtr
, LPARAM lParam
)
329 TRACE("(%p %d %d)\n", infoPtr
, LOWORD(lParam
), HIWORD(lParam
));
330 infoPtr
->uHItemPadding_s
= LOWORD(lParam
);
331 infoPtr
->uVItemPadding_s
= HIWORD(lParam
);
336 /******************************************************************************
337 * TAB_InternalGetItemRect
339 * This method will calculate the rectangle representing a given tab item in
340 * client coordinates. This method takes scrolling into account.
342 * This method returns TRUE if the item is visible in the window and FALSE
343 * if it is completely outside the client area.
345 static BOOL
TAB_InternalGetItemRect(
346 const TAB_INFO
* infoPtr
,
351 RECT tmpItemRect
,clientRect
;
353 /* Perform a sanity check and a trivial visibility check. */
354 if ( (infoPtr
->uNumItem
<= 0) ||
355 (itemIndex
>= infoPtr
->uNumItem
) ||
356 (!(((infoPtr
->dwStyle
& TCS_MULTILINE
) || (infoPtr
->dwStyle
& TCS_VERTICAL
))) &&
357 (itemIndex
< infoPtr
->leftmostVisible
)))
359 TRACE("Not Visible\n");
360 /* need to initialize these to empty rects */
363 memset(itemRect
,0,sizeof(RECT
));
364 itemRect
->bottom
= infoPtr
->tabHeight
;
367 memset(selectedRect
,0,sizeof(RECT
));
372 * Avoid special cases in this procedure by assigning the "out"
373 * parameters if the caller didn't supply them
375 if (itemRect
== NULL
)
376 itemRect
= &tmpItemRect
;
378 /* Retrieve the unmodified item rect. */
379 *itemRect
= TAB_GetItem(infoPtr
,itemIndex
)->rect
;
381 /* calculate the times bottom and top based on the row */
382 GetClientRect(infoPtr
->hwnd
, &clientRect
);
384 if ((infoPtr
->dwStyle
& TCS_BOTTOM
) && (infoPtr
->dwStyle
& TCS_VERTICAL
))
386 itemRect
->right
= clientRect
.right
- SELECTED_TAB_OFFSET
- itemRect
->left
* infoPtr
->tabHeight
-
387 ((infoPtr
->dwStyle
& TCS_BUTTONS
) ? itemRect
->left
* BUTTON_SPACINGX
: 0);
388 itemRect
->left
= itemRect
->right
- infoPtr
->tabHeight
;
390 else if (infoPtr
->dwStyle
& TCS_VERTICAL
)
392 itemRect
->left
= clientRect
.left
+ SELECTED_TAB_OFFSET
+ itemRect
->left
* infoPtr
->tabHeight
+
393 ((infoPtr
->dwStyle
& TCS_BUTTONS
) ? itemRect
->left
* BUTTON_SPACINGX
: 0);
394 itemRect
->right
= itemRect
->left
+ infoPtr
->tabHeight
;
396 else if (infoPtr
->dwStyle
& TCS_BOTTOM
)
398 itemRect
->bottom
= clientRect
.bottom
- itemRect
->top
* infoPtr
->tabHeight
-
399 ((infoPtr
->dwStyle
& TCS_BUTTONS
) ? itemRect
->top
* BUTTON_SPACINGY
: SELECTED_TAB_OFFSET
);
400 itemRect
->top
= itemRect
->bottom
- infoPtr
->tabHeight
;
402 else /* not TCS_BOTTOM and not TCS_VERTICAL */
404 itemRect
->top
= clientRect
.top
+ itemRect
->top
* infoPtr
->tabHeight
+
405 ((infoPtr
->dwStyle
& TCS_BUTTONS
) ? itemRect
->top
* BUTTON_SPACINGY
: SELECTED_TAB_OFFSET
);
406 itemRect
->bottom
= itemRect
->top
+ infoPtr
->tabHeight
;
410 * "scroll" it to make sure the item at the very left of the
411 * tab control is the leftmost visible tab.
413 if(infoPtr
->dwStyle
& TCS_VERTICAL
)
417 -TAB_GetItem(infoPtr
, infoPtr
->leftmostVisible
)->rect
.top
);
420 * Move the rectangle so the first item is slightly offset from
421 * the bottom of the tab control.
425 SELECTED_TAB_OFFSET
);
430 -TAB_GetItem(infoPtr
, infoPtr
->leftmostVisible
)->rect
.left
,
434 * Move the rectangle so the first item is slightly offset from
435 * the left of the tab control.
441 TRACE("item %d tab h=%d, rect=(%s)\n",
442 itemIndex
, infoPtr
->tabHeight
, wine_dbgstr_rect(itemRect
));
444 /* Now, calculate the position of the item as if it were selected. */
445 if (selectedRect
!=NULL
)
447 CopyRect(selectedRect
, itemRect
);
449 /* The rectangle of a selected item is a bit wider. */
450 if(infoPtr
->dwStyle
& TCS_VERTICAL
)
451 InflateRect(selectedRect
, 0, SELECTED_TAB_OFFSET
);
453 InflateRect(selectedRect
, SELECTED_TAB_OFFSET
, 0);
455 /* If it also a bit higher. */
456 if ((infoPtr
->dwStyle
& TCS_BOTTOM
) && (infoPtr
->dwStyle
& TCS_VERTICAL
))
458 selectedRect
->left
-= 2; /* the border is thicker on the right */
459 selectedRect
->right
+= SELECTED_TAB_OFFSET
;
461 else if (infoPtr
->dwStyle
& TCS_VERTICAL
)
463 selectedRect
->left
-= SELECTED_TAB_OFFSET
;
464 selectedRect
->right
+= 1;
466 else if (infoPtr
->dwStyle
& TCS_BOTTOM
)
468 selectedRect
->bottom
+= SELECTED_TAB_OFFSET
;
470 else /* not TCS_BOTTOM and not TCS_VERTICAL */
472 selectedRect
->top
-= SELECTED_TAB_OFFSET
;
473 selectedRect
->bottom
-= 1;
477 /* Check for visibility */
478 if (infoPtr
->dwStyle
& TCS_VERTICAL
)
479 return (itemRect
->top
< clientRect
.bottom
) && (itemRect
->bottom
> clientRect
.top
);
481 return (itemRect
->left
< clientRect
.right
) && (itemRect
->right
> clientRect
.left
);
485 TAB_GetItemRect(const TAB_INFO
*infoPtr
, INT item
, RECT
*rect
)
487 TRACE("(%p, %d, %p)\n", infoPtr
, item
, rect
);
488 return TAB_InternalGetItemRect(infoPtr
, item
, rect
, NULL
);
491 /******************************************************************************
494 * This method is called to handle keyboard input
496 static LRESULT
TAB_KeyDown(TAB_INFO
* infoPtr
, WPARAM keyCode
, LPARAM lParam
)
501 /* TCN_KEYDOWN notification sent always */
502 nm
.hdr
.hwndFrom
= infoPtr
->hwnd
;
503 nm
.hdr
.idFrom
= GetWindowLongPtrW(infoPtr
->hwnd
, GWLP_ID
);
504 nm
.hdr
.code
= TCN_KEYDOWN
;
507 SendMessageW(infoPtr
->hwndNotify
, WM_NOTIFY
, nm
.hdr
.idFrom
, (LPARAM
)&nm
);
512 newItem
= infoPtr
->uFocus
- 1;
515 newItem
= infoPtr
->uFocus
+ 1;
519 /* If we changed to a valid item, change focused item */
520 if (newItem
>= 0 && newItem
< infoPtr
->uNumItem
&& infoPtr
->uFocus
!= newItem
)
521 TAB_SetCurFocus(infoPtr
, newItem
);
527 * WM_KILLFOCUS handler
529 static void TAB_KillFocus(TAB_INFO
*infoPtr
)
531 /* clear current focused item back to selected for TCS_BUTTONS */
532 if ((infoPtr
->dwStyle
& TCS_BUTTONS
) && (infoPtr
->uFocus
!= infoPtr
->iSelected
))
536 if (TAB_InternalGetItemRect(infoPtr
, infoPtr
->uFocus
, &r
, NULL
))
537 InvalidateRect(infoPtr
->hwnd
, &r
, FALSE
);
539 infoPtr
->uFocus
= infoPtr
->iSelected
;
543 /******************************************************************************
546 * This method is called whenever the focus goes in or out of this control
547 * it is used to update the visual state of the control.
549 static void TAB_FocusChanging(const TAB_INFO
*infoPtr
)
555 * Get the rectangle for the item.
557 isVisible
= TAB_InternalGetItemRect(infoPtr
,
563 * If the rectangle is not completely invisible, invalidate that
564 * portion of the window.
568 TRACE("invalidate (%s)\n", wine_dbgstr_rect(&selectedRect
));
569 InvalidateRect(infoPtr
->hwnd
, &selectedRect
, TRUE
);
573 static INT
TAB_InternalHitTest (const TAB_INFO
*infoPtr
, POINT pt
, UINT
*flags
)
578 for (iCount
= 0; iCount
< infoPtr
->uNumItem
; iCount
++)
580 TAB_InternalGetItemRect(infoPtr
, iCount
, &rect
, NULL
);
582 if (PtInRect(&rect
, pt
))
584 *flags
= TCHT_ONITEM
;
589 *flags
= TCHT_NOWHERE
;
593 static inline LRESULT
594 TAB_HitTest (const TAB_INFO
*infoPtr
, LPTCHITTESTINFO lptest
)
596 TRACE("(%p, %p)\n", infoPtr
, lptest
);
597 return TAB_InternalHitTest (infoPtr
, lptest
->pt
, &lptest
->flags
);
600 /******************************************************************************
603 * Napster v2b5 has a tab control for its main navigation which has a client
604 * area that covers the whole area of the dialog pages.
605 * That's why it receives all msgs for that area and the underlying dialog ctrls
607 * So I decided that we should handle WM_NCHITTEST here and return
608 * HTTRANSPARENT if we don't hit the tab control buttons.
609 * FIXME: WM_NCHITTEST handling correct ? Fix it if you know that Windows
610 * doesn't do it that way. Maybe depends on tab control styles ?
612 static inline LRESULT
613 TAB_NCHitTest (const TAB_INFO
*infoPtr
, LPARAM lParam
)
618 pt
.x
= (short)LOWORD(lParam
);
619 pt
.y
= (short)HIWORD(lParam
);
620 ScreenToClient(infoPtr
->hwnd
, &pt
);
622 if (TAB_InternalHitTest(infoPtr
, pt
, &dummyflag
) == -1)
623 return HTTRANSPARENT
;
629 TAB_LButtonDown (TAB_INFO
*infoPtr
, WPARAM wParam
, LPARAM lParam
)
635 if (infoPtr
->hwndToolTip
)
636 TAB_RelayEvent (infoPtr
->hwndToolTip
, infoPtr
->hwnd
,
637 WM_LBUTTONDOWN
, wParam
, lParam
);
639 if (!(infoPtr
->dwStyle
& TCS_FOCUSNEVER
)) {
640 SetFocus (infoPtr
->hwnd
);
643 if (infoPtr
->hwndToolTip
)
644 TAB_RelayEvent (infoPtr
->hwndToolTip
, infoPtr
->hwnd
,
645 WM_LBUTTONDOWN
, wParam
, lParam
);
647 pt
.x
= (short)LOWORD(lParam
);
648 pt
.y
= (short)HIWORD(lParam
);
650 newItem
= TAB_InternalHitTest (infoPtr
, pt
, &dummy
);
652 TRACE("On Tab, item %d\n", newItem
);
654 if ((newItem
!= -1) && (infoPtr
->iSelected
!= newItem
))
656 if ((infoPtr
->dwStyle
& TCS_BUTTONS
) && (infoPtr
->dwStyle
& TCS_MULTISELECT
) &&
657 (wParam
& MK_CONTROL
))
661 /* toggle multiselection */
662 TAB_GetItem(infoPtr
, newItem
)->dwState
^= TCIS_BUTTONPRESSED
;
663 if (TAB_InternalGetItemRect (infoPtr
, newItem
, &r
, NULL
))
664 InvalidateRect (infoPtr
->hwnd
, &r
, TRUE
);
669 BOOL pressed
= FALSE
;
671 /* any button pressed ? */
672 for (i
= 0; i
< infoPtr
->uNumItem
; i
++)
673 if ((TAB_GetItem (infoPtr
, i
)->dwState
& TCIS_BUTTONPRESSED
) &&
674 (infoPtr
->iSelected
!= i
))
680 if (TAB_SendSimpleNotify(infoPtr
, TCN_SELCHANGING
))
684 TAB_DeselectAll (infoPtr
, FALSE
);
686 TAB_SetCurSel(infoPtr
, newItem
);
688 TAB_SendSimpleNotify(infoPtr
, TCN_SELCHANGE
);
695 static inline LRESULT
696 TAB_LButtonUp (const TAB_INFO
*infoPtr
)
698 TAB_SendSimpleNotify(infoPtr
, NM_CLICK
);
704 TAB_RButtonUp (const TAB_INFO
*infoPtr
)
706 TAB_SendSimpleNotify(infoPtr
, NM_RCLICK
);
709 /******************************************************************************
710 * TAB_DrawLoneItemInterior
712 * This calls TAB_DrawItemInterior. However, TAB_DrawItemInterior is normally
713 * called by TAB_DrawItem which is normally called by TAB_Refresh which sets
714 * up the device context and font. This routine does the same setup but
715 * only calls TAB_DrawItemInterior for the single specified item.
718 TAB_DrawLoneItemInterior(const TAB_INFO
* infoPtr
, int iItem
)
720 HDC hdc
= GetDC(infoPtr
->hwnd
);
723 /* Clip UpDown control to not draw over it */
724 if (infoPtr
->needsScrolling
)
726 GetWindowRect(infoPtr
->hwnd
, &rC
);
727 GetWindowRect(infoPtr
->hwndUpDown
, &r
);
728 ExcludeClipRect(hdc
, r
.left
- rC
.left
, r
.top
- rC
.top
, r
.right
- rC
.left
, r
.bottom
- rC
.top
);
730 TAB_DrawItemInterior(infoPtr
, hdc
, iItem
, NULL
);
731 ReleaseDC(infoPtr
->hwnd
, hdc
);
734 /* update a tab after hottracking - invalidate it or just redraw the interior,
735 * based on whether theming is used or not */
736 static inline void hottrack_refresh(const TAB_INFO
*infoPtr
, int tabIndex
)
738 if (tabIndex
== -1) return;
740 if (GetWindowTheme (infoPtr
->hwnd
))
743 TAB_InternalGetItemRect(infoPtr
, tabIndex
, &rect
, NULL
);
744 InvalidateRect (infoPtr
->hwnd
, &rect
, FALSE
);
747 TAB_DrawLoneItemInterior(infoPtr
, tabIndex
);
750 /******************************************************************************
751 * TAB_HotTrackTimerProc
753 * When a mouse-move event causes a tab to be highlighted (hot-tracking), a
754 * timer is setup so we can check if the mouse is moved out of our window.
755 * (We don't get an event when the mouse leaves, the mouse-move events just
756 * stop being delivered to our window and just start being delivered to
757 * another window.) This function is called when the timer triggers so
758 * we can check if the mouse has left our window. If so, we un-highlight
759 * the hot-tracked tab.
762 TAB_HotTrackTimerProc
764 HWND hwnd
, /* handle of window for timer messages */
765 UINT uMsg
, /* WM_TIMER message */
766 UINT_PTR idEvent
, /* timer identifier */
767 DWORD dwTime
/* current system time */
770 TAB_INFO
* infoPtr
= TAB_GetInfoPtr(hwnd
);
772 if (infoPtr
!= NULL
&& infoPtr
->iHotTracked
>= 0)
777 ** If we can't get the cursor position, or if the cursor is outside our
778 ** window, we un-highlight the hot-tracked tab. Note that the cursor is
779 ** "outside" even if it is within our bounding rect if another window
780 ** overlaps. Note also that the case where the cursor stayed within our
781 ** window but has moved off the hot-tracked tab will be handled by the
782 ** WM_MOUSEMOVE event.
784 if (!GetCursorPos(&pt
) || WindowFromPoint(pt
) != hwnd
)
786 /* Redraw iHotTracked to look normal */
787 INT iRedraw
= infoPtr
->iHotTracked
;
788 infoPtr
->iHotTracked
= -1;
789 hottrack_refresh (infoPtr
, iRedraw
);
791 /* Kill this timer */
792 KillTimer(hwnd
, TAB_HOTTRACK_TIMER
);
797 /******************************************************************************
800 * If a tab control has the TCS_HOTTRACK style, then the tab under the mouse
801 * should be highlighted. This function determines which tab in a tab control,
802 * if any, is under the mouse and records that information. The caller may
803 * supply output parameters to receive the item number of the tab item which
804 * was highlighted but isn't any longer and of the tab item which is now
805 * highlighted but wasn't previously. The caller can use this information to
806 * selectively redraw those tab items.
808 * If the caller has a mouse position, it can supply it through the pos
809 * parameter. For example, TAB_MouseMove does this. Otherwise, the caller
810 * supplies NULL and this function determines the current mouse position
818 int* out_redrawLeave
,
825 if (out_redrawLeave
!= NULL
)
826 *out_redrawLeave
= -1;
827 if (out_redrawEnter
!= NULL
)
828 *out_redrawEnter
= -1;
830 if ((infoPtr
->dwStyle
& TCS_HOTTRACK
) || GetWindowTheme(infoPtr
->hwnd
))
838 ScreenToClient(infoPtr
->hwnd
, &pt
);
842 pt
.x
= (short)LOWORD(*pos
);
843 pt
.y
= (short)HIWORD(*pos
);
846 item
= TAB_InternalHitTest(infoPtr
, pt
, &flags
);
849 if (item
!= infoPtr
->iHotTracked
)
851 if (infoPtr
->iHotTracked
>= 0)
853 /* Mark currently hot-tracked to be redrawn to look normal */
854 if (out_redrawLeave
!= NULL
)
855 *out_redrawLeave
= infoPtr
->iHotTracked
;
859 /* Kill timer which forces recheck of mouse pos */
860 KillTimer(infoPtr
->hwnd
, TAB_HOTTRACK_TIMER
);
865 /* Start timer so we recheck mouse pos */
866 UINT timerID
= SetTimer
870 TAB_HOTTRACK_TIMER_INTERVAL
,
871 TAB_HotTrackTimerProc
875 return; /* Hot tracking not available */
878 infoPtr
->iHotTracked
= item
;
882 /* Mark new hot-tracked to be redrawn to look highlighted */
883 if (out_redrawEnter
!= NULL
)
884 *out_redrawEnter
= item
;
889 /******************************************************************************
892 * Handles the mouse-move event. Updates tooltips. Updates hot-tracking.
895 TAB_MouseMove (TAB_INFO
*infoPtr
, WPARAM wParam
, LPARAM lParam
)
900 if (infoPtr
->hwndToolTip
)
901 TAB_RelayEvent (infoPtr
->hwndToolTip
, infoPtr
->hwnd
,
902 WM_LBUTTONDOWN
, wParam
, lParam
);
904 /* Determine which tab to highlight. Redraw tabs which change highlight
906 TAB_RecalcHotTrack(infoPtr
, &lParam
, &redrawLeave
, &redrawEnter
);
908 hottrack_refresh (infoPtr
, redrawLeave
);
909 hottrack_refresh (infoPtr
, redrawEnter
);
914 /******************************************************************************
917 * Calculates the tab control's display area given the window rectangle or
918 * the window rectangle given the requested display rectangle.
920 static LRESULT
TAB_AdjustRect(const TAB_INFO
*infoPtr
, WPARAM fLarger
, LPRECT prc
)
922 LONG
*iRightBottom
, *iLeftTop
;
924 TRACE ("hwnd=%p fLarger=%ld (%s)\n", infoPtr
->hwnd
, fLarger
,
925 wine_dbgstr_rect(prc
));
929 if(infoPtr
->dwStyle
& TCS_VERTICAL
)
931 iRightBottom
= &(prc
->right
);
932 iLeftTop
= &(prc
->left
);
936 iRightBottom
= &(prc
->bottom
);
937 iLeftTop
= &(prc
->top
);
940 if (fLarger
) /* Go from display rectangle */
942 /* Add the height of the tabs. */
943 if (infoPtr
->dwStyle
& TCS_BOTTOM
)
944 *iRightBottom
+= infoPtr
->tabHeight
* infoPtr
->uNumRows
;
946 *iLeftTop
-= infoPtr
->tabHeight
* infoPtr
->uNumRows
+
947 ((infoPtr
->dwStyle
& TCS_BUTTONS
)? 3 * (infoPtr
->uNumRows
- 1) : 0);
949 /* Inflate the rectangle for the padding */
950 InflateRect(prc
, DISPLAY_AREA_PADDINGX
, DISPLAY_AREA_PADDINGY
);
952 /* Inflate for the border */
953 InflateRect(prc
, CONTROL_BORDER_SIZEX
, CONTROL_BORDER_SIZEY
);
955 else /* Go from window rectangle. */
957 /* Deflate the rectangle for the border */
958 InflateRect(prc
, -CONTROL_BORDER_SIZEX
, -CONTROL_BORDER_SIZEY
);
960 /* Deflate the rectangle for the padding */
961 InflateRect(prc
, -DISPLAY_AREA_PADDINGX
, -DISPLAY_AREA_PADDINGY
);
963 /* Remove the height of the tabs. */
964 if (infoPtr
->dwStyle
& TCS_BOTTOM
)
965 *iRightBottom
-= infoPtr
->tabHeight
* infoPtr
->uNumRows
;
967 *iLeftTop
+= (infoPtr
->tabHeight
) * infoPtr
->uNumRows
+
968 ((infoPtr
->dwStyle
& TCS_BUTTONS
)? 3 * (infoPtr
->uNumRows
- 1) : 0);
974 /******************************************************************************
977 * This method will handle the notification from the scroll control and
978 * perform the scrolling operation on the tab control.
980 static LRESULT
TAB_OnHScroll(TAB_INFO
*infoPtr
, int nScrollCode
, int nPos
)
982 if(nScrollCode
== SB_THUMBPOSITION
&& nPos
!= infoPtr
->leftmostVisible
)
984 if(nPos
< infoPtr
->leftmostVisible
)
985 infoPtr
->leftmostVisible
--;
987 infoPtr
->leftmostVisible
++;
989 TAB_RecalcHotTrack(infoPtr
, NULL
, NULL
, NULL
);
990 TAB_InvalidateTabArea(infoPtr
);
991 SendMessageW(infoPtr
->hwndUpDown
, UDM_SETPOS
, 0,
992 MAKELONG(infoPtr
->leftmostVisible
, 0));
998 /******************************************************************************
1001 * This method will check the current scrolling state and make sure the
1002 * scrolling control is displayed (or not).
1004 static void TAB_SetupScrolling(
1006 const RECT
* clientRect
)
1008 static const WCHAR emptyW
[] = { 0 };
1011 if (infoPtr
->needsScrolling
)
1014 INT vsize
, tabwidth
;
1017 * Calculate the position of the scroll control.
1019 controlPos
.right
= clientRect
->right
;
1020 controlPos
.left
= controlPos
.right
- 2 * GetSystemMetrics(SM_CXHSCROLL
);
1022 if (infoPtr
->dwStyle
& TCS_BOTTOM
)
1024 controlPos
.top
= clientRect
->bottom
- infoPtr
->tabHeight
;
1025 controlPos
.bottom
= controlPos
.top
+ GetSystemMetrics(SM_CYHSCROLL
);
1029 controlPos
.bottom
= clientRect
->top
+ infoPtr
->tabHeight
;
1030 controlPos
.top
= controlPos
.bottom
- GetSystemMetrics(SM_CYHSCROLL
);
1034 * If we don't have a scroll control yet, we want to create one.
1035 * If we have one, we want to make sure it's positioned properly.
1037 if (infoPtr
->hwndUpDown
==0)
1039 infoPtr
->hwndUpDown
= CreateWindowW(UPDOWN_CLASSW
, emptyW
,
1040 WS_VISIBLE
| WS_CHILD
| UDS_HORZ
,
1041 controlPos
.left
, controlPos
.top
,
1042 controlPos
.right
- controlPos
.left
,
1043 controlPos
.bottom
- controlPos
.top
,
1044 infoPtr
->hwnd
, NULL
, NULL
, NULL
);
1048 SetWindowPos(infoPtr
->hwndUpDown
,
1050 controlPos
.left
, controlPos
.top
,
1051 controlPos
.right
- controlPos
.left
,
1052 controlPos
.bottom
- controlPos
.top
,
1053 SWP_SHOWWINDOW
| SWP_NOZORDER
);
1056 /* Now calculate upper limit of the updown control range.
1057 * We do this by calculating how many tabs will be offscreen when the
1058 * last tab is visible.
1060 if(infoPtr
->uNumItem
)
1062 vsize
= clientRect
->right
- (controlPos
.right
- controlPos
.left
+ 1);
1063 maxRange
= infoPtr
->uNumItem
;
1064 tabwidth
= TAB_GetItem(infoPtr
, infoPtr
->uNumItem
- 1)->rect
.right
;
1066 for(; maxRange
> 0; maxRange
--)
1068 if(tabwidth
- TAB_GetItem(infoPtr
,maxRange
- 1)->rect
.left
> vsize
)
1072 if(maxRange
== infoPtr
->uNumItem
)
1078 /* If we once had a scroll control... hide it */
1079 if (infoPtr
->hwndUpDown
)
1080 ShowWindow(infoPtr
->hwndUpDown
, SW_HIDE
);
1082 if (infoPtr
->hwndUpDown
)
1083 SendMessageW(infoPtr
->hwndUpDown
, UDM_SETRANGE32
, 0, maxRange
);
1086 /******************************************************************************
1089 * This method will calculate the position rectangles of all the items in the
1090 * control. The rectangle calculated starts at 0 for the first item in the
1091 * list and ignores scrolling and selection.
1092 * It also uses the current font to determine the height of the tab row and
1093 * it checks if all the tabs fit in the client area of the window. If they
1094 * don't, a scrolling control is added.
1096 static void TAB_SetItemBounds (TAB_INFO
*infoPtr
)
1098 TEXTMETRICW fontMetrics
;
1101 INT curItemRowCount
;
1102 HFONT hFont
, hOldFont
;
1111 * We need to get text information so we need a DC and we need to select
1114 hdc
= GetDC(infoPtr
->hwnd
);
1116 hFont
= infoPtr
->hFont
? infoPtr
->hFont
: GetStockObject (SYSTEM_FONT
);
1117 hOldFont
= SelectObject (hdc
, hFont
);
1120 * We will base the rectangle calculations on the client rectangle
1123 GetClientRect(infoPtr
->hwnd
, &clientRect
);
1125 /* if TCS_VERTICAL then swap the height and width so this code places the
1126 tabs along the top of the rectangle and we can just rotate them after
1127 rather than duplicate all of the below code */
1128 if(infoPtr
->dwStyle
& TCS_VERTICAL
)
1130 iTemp
= clientRect
.bottom
;
1131 clientRect
.bottom
= clientRect
.right
;
1132 clientRect
.right
= iTemp
;
1135 /* Now use hPadding and vPadding */
1136 infoPtr
->uHItemPadding
= infoPtr
->uHItemPadding_s
;
1137 infoPtr
->uVItemPadding
= infoPtr
->uVItemPadding_s
;
1139 /* The leftmost item will be "0" aligned */
1141 curItemRowCount
= infoPtr
->uNumItem
? 1 : 0;
1143 if (!(infoPtr
->fHeightSet
))
1146 INT icon_height
= 0, cx
;
1148 /* Use the current font to determine the height of a tab. */
1149 GetTextMetricsW(hdc
, &fontMetrics
);
1151 /* Get the icon height */
1153 ImageList_GetIconSize(infoPtr
->himl
, &cx
, &icon_height
);
1155 /* Take the highest between font or icon */
1156 if (fontMetrics
.tmHeight
> icon_height
)
1157 item_height
= fontMetrics
.tmHeight
+ 2;
1159 item_height
= icon_height
;
1162 * Make sure there is enough space for the letters + icon + growing the
1163 * selected item + extra space for the selected item.
1165 infoPtr
->tabHeight
= item_height
+
1166 ((infoPtr
->dwStyle
& TCS_BUTTONS
) ? 2 : 1) *
1167 infoPtr
->uVItemPadding
;
1169 TRACE("tabH=%d, tmH=%d, iconh=%d\n",
1170 infoPtr
->tabHeight
, fontMetrics
.tmHeight
, icon_height
);
1173 TRACE("client right=%d\n", clientRect
.right
);
1175 /* Get the icon width */
1180 ImageList_GetIconSize(infoPtr
->himl
, &icon_width
, &cy
);
1182 if (infoPtr
->dwStyle
& TCS_FIXEDWIDTH
)
1185 /* Add padding if icon is present */
1186 icon_width
+= infoPtr
->uHItemPadding
;
1189 for (curItem
= 0; curItem
< infoPtr
->uNumItem
; curItem
++)
1191 TAB_ITEM
*curr
= TAB_GetItem(infoPtr
, curItem
);
1193 /* Set the leftmost position of the tab. */
1194 curr
->rect
.left
= curItemLeftPos
;
1196 if (infoPtr
->dwStyle
& TCS_FIXEDWIDTH
)
1198 curr
->rect
.right
= curr
->rect
.left
+
1199 max(infoPtr
->tabWidth
, icon_width
);
1201 else if (!curr
->pszText
)
1203 /* If no text use minimum tab width including padding. */
1204 if (infoPtr
->tabMinWidth
< 0)
1205 curr
->rect
.right
= curr
->rect
.left
+ GET_DEFAULT_MIN_TAB_WIDTH(infoPtr
);
1208 curr
->rect
.right
= curr
->rect
.left
+ infoPtr
->tabMinWidth
;
1210 /* Add extra padding if icon is present */
1211 if (infoPtr
->himl
&& infoPtr
->tabMinWidth
> 0 && infoPtr
->tabMinWidth
< DEFAULT_MIN_TAB_WIDTH
1212 && infoPtr
->uHItemPadding
> 1)
1213 curr
->rect
.right
+= EXTRA_ICON_PADDING
* (infoPtr
->uHItemPadding
-1);
1220 /* Calculate how wide the tab is depending on the text it contains */
1221 GetTextExtentPoint32W(hdc
, curr
->pszText
,
1222 lstrlenW(curr
->pszText
), &size
);
1224 tabwidth
= size
.cx
+ icon_width
+ 2 * infoPtr
->uHItemPadding
;
1226 if (infoPtr
->tabMinWidth
< 0)
1227 tabwidth
= max(tabwidth
, GET_DEFAULT_MIN_TAB_WIDTH(infoPtr
));
1229 tabwidth
= max(tabwidth
, infoPtr
->tabMinWidth
);
1231 curr
->rect
.right
= curr
->rect
.left
+ tabwidth
;
1232 TRACE("for <%s>, l,r=%d,%d\n",
1233 debugstr_w(curr
->pszText
), curr
->rect
.left
, curr
->rect
.right
);
1237 * Check if this is a multiline tab control and if so
1238 * check to see if we should wrap the tabs
1240 * Wrap all these tabs. We will arrange them evenly later.
1244 if (((infoPtr
->dwStyle
& TCS_MULTILINE
) || (infoPtr
->dwStyle
& TCS_VERTICAL
)) &&
1246 (clientRect
.right
- CONTROL_BORDER_SIZEX
- DISPLAY_AREA_PADDINGX
)))
1248 curr
->rect
.right
-= curr
->rect
.left
;
1250 curr
->rect
.left
= 0;
1252 TRACE("wrapping <%s>, l,r=%d,%d\n", debugstr_w(curr
->pszText
),
1253 curr
->rect
.left
, curr
->rect
.right
);
1256 curr
->rect
.bottom
= 0;
1257 curr
->rect
.top
= curItemRowCount
- 1;
1259 TRACE("Rect: %s\n", wine_dbgstr_rect(&curr
->rect
));
1262 * The leftmost position of the next item is the rightmost position
1265 if (infoPtr
->dwStyle
& TCS_BUTTONS
)
1267 curItemLeftPos
= curr
->rect
.right
+ BUTTON_SPACINGX
;
1268 if (infoPtr
->dwStyle
& TCS_FLATBUTTONS
)
1269 curItemLeftPos
+= FLAT_BTN_SPACINGX
;
1272 curItemLeftPos
= curr
->rect
.right
;
1275 if (!((infoPtr
->dwStyle
& TCS_MULTILINE
) || (infoPtr
->dwStyle
& TCS_VERTICAL
)))
1278 * Check if we need a scrolling control.
1280 infoPtr
->needsScrolling
= (curItemLeftPos
+ (2 * SELECTED_TAB_OFFSET
) >
1283 /* Don't need scrolling, then update infoPtr->leftmostVisible */
1284 if(!infoPtr
->needsScrolling
)
1285 infoPtr
->leftmostVisible
= 0;
1290 * No scrolling in Multiline or Vertical styles.
1292 infoPtr
->needsScrolling
= FALSE
;
1293 infoPtr
->leftmostVisible
= 0;
1295 TAB_SetupScrolling(infoPtr
, &clientRect
);
1297 /* Set the number of rows */
1298 infoPtr
->uNumRows
= curItemRowCount
;
1300 /* Arrange all tabs evenly if style says so */
1301 if (!(infoPtr
->dwStyle
& TCS_RAGGEDRIGHT
) &&
1302 ((infoPtr
->dwStyle
& TCS_MULTILINE
) || (infoPtr
->dwStyle
& TCS_VERTICAL
)) &&
1303 (infoPtr
->uNumItem
> 0) &&
1304 (infoPtr
->uNumRows
> 1))
1306 INT tabPerRow
,remTab
,iRow
;
1311 * Ok windows tries to even out the rows. place the same
1312 * number of tabs in each row. So lets give that a shot
1315 tabPerRow
= infoPtr
->uNumItem
/ (infoPtr
->uNumRows
);
1316 remTab
= infoPtr
->uNumItem
% (infoPtr
->uNumRows
);
1318 for (iItm
=0,iRow
=0,iCount
=0,curItemLeftPos
=0;
1319 iItm
<infoPtr
->uNumItem
;
1322 /* normalize the current rect */
1323 TAB_ITEM
*curr
= TAB_GetItem(infoPtr
, iItm
);
1325 /* shift the item to the left side of the clientRect */
1326 curr
->rect
.right
-= curr
->rect
.left
;
1327 curr
->rect
.left
= 0;
1329 TRACE("r=%d, cl=%d, cl.r=%d, iCount=%d, iRow=%d, uNumRows=%d, remTab=%d, tabPerRow=%d\n",
1330 curr
->rect
.right
, curItemLeftPos
, clientRect
.right
,
1331 iCount
, iRow
, infoPtr
->uNumRows
, remTab
, tabPerRow
);
1333 /* if we have reached the maximum number of tabs on this row */
1334 /* move to the next row, reset our current item left position and */
1335 /* the count of items on this row */
1337 if (infoPtr
->dwStyle
& TCS_VERTICAL
) {
1338 /* Vert: Add the remaining tabs in the *last* remainder rows */
1339 if (iCount
>= ((iRow
>=(INT
)infoPtr
->uNumRows
- remTab
)?tabPerRow
+ 1:tabPerRow
)) {
1345 /* Horz: Add the remaining tabs in the *first* remainder rows */
1346 if (iCount
>= ((iRow
<remTab
)?tabPerRow
+ 1:tabPerRow
)) {
1353 /* shift the item to the right to place it as the next item in this row */
1354 curr
->rect
.left
+= curItemLeftPos
;
1355 curr
->rect
.right
+= curItemLeftPos
;
1356 curr
->rect
.top
= iRow
;
1357 if (infoPtr
->dwStyle
& TCS_BUTTONS
)
1359 curItemLeftPos
= curr
->rect
.right
+ 1;
1360 if (infoPtr
->dwStyle
& TCS_FLATBUTTONS
)
1361 curItemLeftPos
+= FLAT_BTN_SPACINGX
;
1364 curItemLeftPos
= curr
->rect
.right
;
1366 TRACE("arranging <%s>, l,r=%d,%d, row=%d\n",
1367 debugstr_w(curr
->pszText
), curr
->rect
.left
,
1368 curr
->rect
.right
, curr
->rect
.top
);
1375 INT widthDiff
, iIndexStart
=0, iIndexEnd
=0;
1379 while(iIndexStart
< infoPtr
->uNumItem
)
1381 TAB_ITEM
*start
= TAB_GetItem(infoPtr
, iIndexStart
);
1384 * find the index of the row
1386 /* find the first item on the next row */
1387 for (iIndexEnd
=iIndexStart
;
1388 (iIndexEnd
< infoPtr
->uNumItem
) &&
1389 (TAB_GetItem(infoPtr
, iIndexEnd
)->rect
.top
==
1392 /* intentionally blank */;
1395 * we need to justify these tabs so they fill the whole given
1399 /* find the amount of space remaining on this row */
1400 widthDiff
= clientRect
.right
- (2 * SELECTED_TAB_OFFSET
) -
1401 TAB_GetItem(infoPtr
, iIndexEnd
- 1)->rect
.right
;
1403 /* iCount is the number of tab items on this row */
1404 iCount
= iIndexEnd
- iIndexStart
;
1408 remainder
= widthDiff
% iCount
;
1409 widthDiff
= widthDiff
/ iCount
;
1410 /* add widthDiff/iCount, or extra space/items on row, to each item on this row */
1411 for (iIndex
=iIndexStart
, iCount
=0; iIndex
< iIndexEnd
; iIndex
++, iCount
++)
1413 TAB_ITEM
*item
= TAB_GetItem(infoPtr
, iIndex
);
1415 item
->rect
.left
+= iCount
* widthDiff
;
1416 item
->rect
.right
+= (iCount
+ 1) * widthDiff
;
1418 TRACE("adjusting 1 <%s>, l,r=%d,%d\n",
1419 debugstr_w(item
->pszText
),
1420 item
->rect
.left
, item
->rect
.right
);
1423 TAB_GetItem(infoPtr
, iIndex
- 1)->rect
.right
+= remainder
;
1425 else /* we have only one item on this row, make it take up the entire row */
1427 start
->rect
.left
= clientRect
.left
;
1428 start
->rect
.right
= clientRect
.right
- 4;
1430 TRACE("adjusting 2 <%s>, l,r=%d,%d\n",
1431 debugstr_w(start
->pszText
),
1432 start
->rect
.left
, start
->rect
.right
);
1437 iIndexStart
= iIndexEnd
;
1442 /* if TCS_VERTICAL rotate the tabs so they are along the side of the clientRect */
1443 if(infoPtr
->dwStyle
& TCS_VERTICAL
)
1446 for(iIndex
= 0; iIndex
< infoPtr
->uNumItem
; iIndex
++)
1448 rcItem
= &TAB_GetItem(infoPtr
, iIndex
)->rect
;
1450 rcOriginal
= *rcItem
;
1452 /* this is rotating the items by 90 degrees clockwise around the center of the control */
1453 rcItem
->top
= (rcOriginal
.left
- clientRect
.left
);
1454 rcItem
->bottom
= rcItem
->top
+ (rcOriginal
.right
- rcOriginal
.left
);
1455 rcItem
->left
= rcOriginal
.top
;
1456 rcItem
->right
= rcOriginal
.bottom
;
1460 TAB_EnsureSelectionVisible(infoPtr
);
1461 TAB_RecalcHotTrack(infoPtr
, NULL
, NULL
, NULL
);
1464 SelectObject (hdc
, hOldFont
);
1465 ReleaseDC (infoPtr
->hwnd
, hdc
);
1470 TAB_EraseTabInterior(const TAB_INFO
*infoPtr
, HDC hdc
, INT iItem
, const RECT
*drawRect
)
1472 HBRUSH hbr
= CreateSolidBrush (comctl32_color
.clrBtnFace
);
1473 BOOL deleteBrush
= TRUE
;
1474 RECT rTemp
= *drawRect
;
1476 if (infoPtr
->dwStyle
& TCS_BUTTONS
)
1478 if (iItem
== infoPtr
->iSelected
)
1480 /* Background color */
1481 if (!(infoPtr
->dwStyle
& TCS_OWNERDRAWFIXED
))
1484 hbr
= GetSysColorBrush(COLOR_SCROLLBAR
);
1486 SetTextColor(hdc
, comctl32_color
.clr3dFace
);
1487 SetBkColor(hdc
, comctl32_color
.clr3dHilight
);
1489 /* if COLOR_WINDOW happens to be the same as COLOR_3DHILIGHT
1490 * we better use 0x55aa bitmap brush to make scrollbar's background
1491 * look different from the window background.
1493 if (comctl32_color
.clr3dHilight
== comctl32_color
.clrWindow
)
1494 hbr
= COMCTL32_hPattern55AABrush
;
1496 deleteBrush
= FALSE
;
1498 FillRect(hdc
, &rTemp
, hbr
);
1500 else /* ! selected */
1502 if (infoPtr
->dwStyle
& TCS_FLATBUTTONS
)
1504 InflateRect(&rTemp
, 2, 2);
1505 FillRect(hdc
, &rTemp
, hbr
);
1506 if (iItem
== infoPtr
->iHotTracked
||
1507 (iItem
!= infoPtr
->iSelected
&& iItem
== infoPtr
->uFocus
))
1508 DrawEdge(hdc
, &rTemp
, BDR_RAISEDINNER
, BF_RECT
);
1511 FillRect(hdc
, &rTemp
, hbr
);
1515 else /* !TCS_BUTTONS */
1517 InflateRect(&rTemp
, -2, -2);
1518 if (!GetWindowTheme (infoPtr
->hwnd
))
1519 FillRect(hdc
, &rTemp
, hbr
);
1522 /* highlighting is drawn on top of previous fills */
1523 if (TAB_GetItem(infoPtr
, iItem
)->dwState
& TCIS_HIGHLIGHTED
)
1528 deleteBrush
= FALSE
;
1530 hbr
= GetSysColorBrush(COLOR_HIGHLIGHT
);
1531 FillRect(hdc
, &rTemp
, hbr
);
1535 if (deleteBrush
) DeleteObject(hbr
);
1538 /******************************************************************************
1539 * TAB_DrawItemInterior
1541 * This method is used to draw the interior (text and icon) of a single tab
1542 * into the tab control.
1545 TAB_DrawItemInterior(const TAB_INFO
*infoPtr
, HDC hdc
, INT iItem
, RECT
*drawRect
)
1554 /* if (drawRect == NULL) */
1561 * Get the rectangle for the item.
1563 isVisible
= TAB_InternalGetItemRect(infoPtr
, iItem
, &itemRect
, &selectedRect
);
1568 * Make sure drawRect points to something valid; simplifies code.
1570 drawRect
= &localRect
;
1573 * This logic copied from the part of TAB_DrawItem which draws
1574 * the tab background. It's important to keep it in sync. I
1575 * would have liked to avoid code duplication, but couldn't figure
1576 * out how without making spaghetti of TAB_DrawItem.
1578 if (iItem
== infoPtr
->iSelected
)
1579 *drawRect
= selectedRect
;
1581 *drawRect
= itemRect
;
1583 if (infoPtr
->dwStyle
& TCS_BUTTONS
)
1585 if (iItem
== infoPtr
->iSelected
)
1587 drawRect
->left
+= 4;
1589 drawRect
->right
-= 4;
1591 if (infoPtr
->dwStyle
& TCS_VERTICAL
)
1593 if (!(infoPtr
->dwStyle
& TCS_BOTTOM
)) drawRect
->right
+= 1;
1594 drawRect
->bottom
-= 4;
1598 if (infoPtr
->dwStyle
& TCS_BOTTOM
)
1601 drawRect
->bottom
-= 4;
1604 drawRect
->bottom
-= 1;
1609 drawRect
->left
+= 2;
1611 drawRect
->right
-= 2;
1612 drawRect
->bottom
-= 2;
1617 if ((infoPtr
->dwStyle
& TCS_VERTICAL
) && (infoPtr
->dwStyle
& TCS_BOTTOM
))
1619 if (iItem
!= infoPtr
->iSelected
)
1621 drawRect
->left
+= 2;
1623 drawRect
->bottom
-= 2;
1626 else if (infoPtr
->dwStyle
& TCS_VERTICAL
)
1628 if (iItem
== infoPtr
->iSelected
)
1630 drawRect
->right
+= 1;
1635 drawRect
->right
-= 2;
1636 drawRect
->bottom
-= 2;
1639 else if (infoPtr
->dwStyle
& TCS_BOTTOM
)
1641 if (iItem
== infoPtr
->iSelected
)
1647 InflateRect(drawRect
, -2, -2);
1648 drawRect
->bottom
+= 2;
1653 if (iItem
== infoPtr
->iSelected
)
1655 drawRect
->bottom
+= 3;
1659 drawRect
->bottom
-= 2;
1660 InflateRect(drawRect
, -2, 0);
1665 TRACE("drawRect=(%s)\n", wine_dbgstr_rect(drawRect
));
1667 /* Clear interior */
1668 TAB_EraseTabInterior (infoPtr
, hdc
, iItem
, drawRect
);
1670 /* Draw the focus rectangle */
1671 if (!(infoPtr
->dwStyle
& TCS_FOCUSNEVER
) &&
1672 (GetFocus() == infoPtr
->hwnd
) &&
1673 (iItem
== infoPtr
->uFocus
) )
1675 RECT rFocus
= *drawRect
;
1677 if (!(infoPtr
->dwStyle
& TCS_BUTTONS
)) InflateRect(&rFocus
, -3, -3);
1678 if (infoPtr
->dwStyle
& TCS_BOTTOM
&& !(infoPtr
->dwStyle
& TCS_VERTICAL
))
1681 /* focus should stay on selected item for TCS_BUTTONS style */
1682 if (!((infoPtr
->dwStyle
& TCS_BUTTONS
) && (infoPtr
->iSelected
!= iItem
)))
1683 DrawFocusRect(hdc
, &rFocus
);
1689 htextPen
= CreatePen( PS_SOLID
, 1, comctl32_color
.clrBtnText
);
1690 holdPen
= SelectObject(hdc
, htextPen
);
1691 hOldFont
= SelectObject(hdc
, infoPtr
->hFont
);
1694 * Setup for text output
1696 oldBkMode
= SetBkMode(hdc
, TRANSPARENT
);
1697 if (!GetWindowTheme (infoPtr
->hwnd
) || (infoPtr
->dwStyle
& TCS_BUTTONS
))
1699 if ((infoPtr
->dwStyle
& TCS_HOTTRACK
) && (iItem
== infoPtr
->iHotTracked
) &&
1700 !(infoPtr
->dwStyle
& TCS_FLATBUTTONS
))
1701 SetTextColor(hdc
, comctl32_color
.clrHighlight
);
1702 else if (TAB_GetItem(infoPtr
, iItem
)->dwState
& TCIS_HIGHLIGHTED
)
1703 SetTextColor(hdc
, comctl32_color
.clrHighlightText
);
1705 SetTextColor(hdc
, comctl32_color
.clrBtnText
);
1709 * if owner draw, tell the owner to draw
1711 if ((infoPtr
->dwStyle
& TCS_OWNERDRAWFIXED
) && IsWindow(infoPtr
->hwndNotify
))
1717 drawRect
->right
-= 1;
1718 if ( iItem
== infoPtr
->iSelected
)
1720 drawRect
->right
-= 1;
1721 drawRect
->left
+= 1;
1724 id
= (UINT
)GetWindowLongPtrW( infoPtr
->hwnd
, GWLP_ID
);
1726 /* fill DRAWITEMSTRUCT */
1727 dis
.CtlType
= ODT_TAB
;
1730 dis
.itemAction
= ODA_DRAWENTIRE
;
1732 if ( iItem
== infoPtr
->iSelected
)
1733 dis
.itemState
|= ODS_SELECTED
;
1734 if (infoPtr
->uFocus
== iItem
)
1735 dis
.itemState
|= ODS_FOCUS
;
1736 dis
.hwndItem
= infoPtr
->hwnd
;
1738 CopyRect(&dis
.rcItem
,drawRect
);
1740 /* when extra data fits ULONG_PTR, store it directly */
1741 if (infoPtr
->cbInfo
> sizeof(LPARAM
))
1742 dis
.itemData
= (ULONG_PTR
) TAB_GetItem(infoPtr
, iItem
)->extra
;
1745 /* this could be considered broken on 64 bit, but that's how it works -
1746 only first 4 bytes are copied */
1748 memcpy(&dis
.itemData
, (ULONG_PTR
*)TAB_GetItem(infoPtr
, iItem
)->extra
, 4);
1751 /* draw notification */
1752 SendMessageW( infoPtr
->hwndNotify
, WM_DRAWITEM
, id
, (LPARAM
)&dis
);
1756 TAB_ITEM
*item
= TAB_GetItem(infoPtr
, iItem
);
1760 /* used to center the icon and text in the tab */
1762 INT center_offset_h
, center_offset_v
;
1764 /* set rcImage to drawRect, we will use top & left in our ImageList_Draw call */
1765 rcImage
= *drawRect
;
1769 rcText
.left
= rcText
.top
= rcText
.right
= rcText
.bottom
= 0;
1771 /* get the rectangle that the text fits in */
1774 DrawTextW(hdc
, item
->pszText
, -1, &rcText
, DT_CALCRECT
);
1777 * If not owner draw, then do the drawing ourselves.
1781 if (infoPtr
->himl
&& item
->iImage
!= -1)
1786 ImageList_GetIconSize(infoPtr
->himl
, &cx
, &cy
);
1788 if(infoPtr
->dwStyle
& TCS_VERTICAL
)
1790 center_offset_h
= ((drawRect
->bottom
- drawRect
->top
) - (cy
+ infoPtr
->uHItemPadding
+ (rcText
.right
- rcText
.left
))) / 2;
1791 center_offset_v
= ((drawRect
->right
- drawRect
->left
) - cx
) / 2;
1795 center_offset_h
= ((drawRect
->right
- drawRect
->left
) - (cx
+ infoPtr
->uHItemPadding
+ (rcText
.right
- rcText
.left
))) / 2;
1796 center_offset_v
= ((drawRect
->bottom
- drawRect
->top
) - cy
) / 2;
1799 /* if an item is selected, the icon is shifted up instead of down */
1800 if (iItem
== infoPtr
->iSelected
)
1801 center_offset_v
-= infoPtr
->uVItemPadding
/ 2;
1803 center_offset_v
+= infoPtr
->uVItemPadding
/ 2;
1805 if (infoPtr
->dwStyle
& TCS_FIXEDWIDTH
&& infoPtr
->dwStyle
& (TCS_FORCELABELLEFT
| TCS_FORCEICONLEFT
))
1806 center_offset_h
= infoPtr
->uHItemPadding
;
1808 if (center_offset_h
< 2)
1809 center_offset_h
= 2;
1811 if (center_offset_v
< 0)
1812 center_offset_v
= 0;
1814 TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%s), textlen=%d\n",
1815 debugstr_w(item
->pszText
), center_offset_h
, center_offset_v
,
1816 wine_dbgstr_rect(drawRect
), (rcText
.right
-rcText
.left
));
1818 if((infoPtr
->dwStyle
& TCS_VERTICAL
) && (infoPtr
->dwStyle
& TCS_BOTTOM
))
1820 rcImage
.top
= drawRect
->top
+ center_offset_h
;
1821 /* if tab is TCS_VERTICAL and TCS_BOTTOM, the text is drawn from the */
1822 /* right side of the tab, but the image still uses the left as its x position */
1823 /* this keeps the image always drawn off of the same side of the tab */
1824 rcImage
.left
= drawRect
->right
- cx
- center_offset_v
;
1825 drawRect
->top
+= cy
+ infoPtr
->uHItemPadding
;
1827 else if(infoPtr
->dwStyle
& TCS_VERTICAL
)
1829 rcImage
.top
= drawRect
->bottom
- cy
- center_offset_h
;
1830 rcImage
.left
= drawRect
->left
+ center_offset_v
;
1831 drawRect
->bottom
-= cy
+ infoPtr
->uHItemPadding
;
1833 else /* normal style, whether TCS_BOTTOM or not */
1835 rcImage
.left
= drawRect
->left
+ center_offset_h
;
1836 rcImage
.top
= drawRect
->top
+ center_offset_v
;
1837 drawRect
->left
+= cx
+ infoPtr
->uHItemPadding
;
1840 TRACE("drawing image=%d, left=%d, top=%d\n",
1841 item
->iImage
, rcImage
.left
, rcImage
.top
-1);
1853 /* Now position text */
1854 if (infoPtr
->dwStyle
& TCS_FIXEDWIDTH
&& infoPtr
->dwStyle
& TCS_FORCELABELLEFT
)
1855 center_offset_h
= infoPtr
->uHItemPadding
;
1857 if(infoPtr
->dwStyle
& TCS_VERTICAL
)
1858 center_offset_h
= ((drawRect
->bottom
- drawRect
->top
) - (rcText
.right
- rcText
.left
)) / 2;
1860 center_offset_h
= ((drawRect
->right
- drawRect
->left
) - (rcText
.right
- rcText
.left
)) / 2;
1862 if(infoPtr
->dwStyle
& TCS_VERTICAL
)
1864 if(infoPtr
->dwStyle
& TCS_BOTTOM
)
1865 drawRect
->top
+=center_offset_h
;
1867 drawRect
->bottom
-=center_offset_h
;
1869 center_offset_v
= ((drawRect
->right
- drawRect
->left
) - (rcText
.bottom
- rcText
.top
)) / 2;
1873 drawRect
->left
+= center_offset_h
;
1874 center_offset_v
= ((drawRect
->bottom
- drawRect
->top
) - (rcText
.bottom
- rcText
.top
)) / 2;
1877 /* if an item is selected, the text is shifted up instead of down */
1878 if (iItem
== infoPtr
->iSelected
)
1879 center_offset_v
-= infoPtr
->uVItemPadding
/ 2;
1881 center_offset_v
+= infoPtr
->uVItemPadding
/ 2;
1883 if (center_offset_v
< 0)
1884 center_offset_v
= 0;
1886 if(infoPtr
->dwStyle
& TCS_VERTICAL
)
1887 drawRect
->left
+= center_offset_v
;
1889 drawRect
->top
+= center_offset_v
;
1892 if(infoPtr
->dwStyle
& TCS_VERTICAL
) /* if we are vertical rotate the text and each character */
1896 INT nEscapement
= 900;
1897 INT nOrientation
= 900;
1899 if(infoPtr
->dwStyle
& TCS_BOTTOM
)
1902 nOrientation
= -900;
1905 /* to get a font with the escapement and orientation we are looking for, we need to */
1906 /* call CreateFontIndirect, which requires us to set the values of the logfont we pass in */
1907 if (!GetObjectW(infoPtr
->hFont
, sizeof(logfont
), &logfont
))
1908 GetObjectW(GetStockObject(DEFAULT_GUI_FONT
), sizeof(logfont
), &logfont
);
1910 logfont
.lfEscapement
= nEscapement
;
1911 logfont
.lfOrientation
= nOrientation
;
1912 hFont
= CreateFontIndirectW(&logfont
);
1913 SelectObject(hdc
, hFont
);
1918 (infoPtr
->dwStyle
& TCS_BOTTOM
) ? drawRect
->right
: drawRect
->left
,
1919 (!(infoPtr
->dwStyle
& TCS_BOTTOM
)) ? drawRect
->bottom
: drawRect
->top
,
1923 lstrlenW(item
->pszText
),
1927 DeleteObject(hFont
);
1931 TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%s), textlen=%d\n",
1932 debugstr_w(item
->pszText
), center_offset_h
, center_offset_v
,
1933 wine_dbgstr_rect(drawRect
), (rcText
.right
-rcText
.left
));
1940 lstrlenW(item
->pszText
),
1942 DT_LEFT
| DT_SINGLELINE
1947 *drawRect
= rcTemp
; /* restore drawRect */
1953 SelectObject(hdc
, hOldFont
);
1954 SetBkMode(hdc
, oldBkMode
);
1955 SelectObject(hdc
, holdPen
);
1956 DeleteObject( htextPen
);
1959 /******************************************************************************
1962 * This method is used to draw a single tab into the tab control.
1964 static void TAB_DrawItem(const TAB_INFO
*infoPtr
, HDC hdc
, INT iItem
)
1969 RECT r
, fillRect
, r1
;
1972 COLORREF bkgnd
, corner
;
1976 * Get the rectangle for the item.
1978 isVisible
= TAB_InternalGetItemRect(infoPtr
,
1987 /* Clip UpDown control to not draw over it */
1988 if (infoPtr
->needsScrolling
)
1990 GetWindowRect(infoPtr
->hwnd
, &rC
);
1991 GetWindowRect(infoPtr
->hwndUpDown
, &rUD
);
1992 ExcludeClipRect(hdc
, rUD
.left
- rC
.left
, rUD
.top
- rC
.top
, rUD
.right
- rC
.left
, rUD
.bottom
- rC
.top
);
1995 /* If you need to see what the control is doing,
1996 * then override these variables. They will change what
1997 * fill colors are used for filling the tabs, and the
1998 * corners when drawing the edge.
2000 bkgnd
= comctl32_color
.clrBtnFace
;
2001 corner
= comctl32_color
.clrBtnFace
;
2003 if (infoPtr
->dwStyle
& TCS_BUTTONS
)
2005 /* Get item rectangle */
2008 /* Separators between flat buttons */
2009 if ((infoPtr
->dwStyle
& TCS_FLATBUTTONS
) && (infoPtr
->exStyle
& TCS_EX_FLATSEPARATORS
))
2012 r1
.right
+= (FLAT_BTN_SPACINGX
-2);
2013 DrawEdge(hdc
, &r1
, EDGE_ETCHED
, BF_RIGHT
);
2016 if (iItem
== infoPtr
->iSelected
)
2018 DrawEdge(hdc
, &r
, EDGE_SUNKEN
, BF_SOFT
|BF_RECT
);
2020 OffsetRect(&r
, 1, 1);
2022 else /* ! selected */
2024 DWORD state
= TAB_GetItem(infoPtr
, iItem
)->dwState
;
2026 if ((state
& TCIS_BUTTONPRESSED
) || (iItem
== infoPtr
->uFocus
))
2027 DrawEdge(hdc
, &r
, EDGE_SUNKEN
, BF_SOFT
|BF_RECT
);
2029 if (!(infoPtr
->dwStyle
& TCS_FLATBUTTONS
))
2030 DrawEdge(hdc
, &r
, EDGE_RAISED
, BF_SOFT
|BF_RECT
);
2033 else /* !TCS_BUTTONS */
2035 /* We draw a rectangle of different sizes depending on the selection
2037 if (iItem
== infoPtr
->iSelected
) {
2039 GetClientRect (infoPtr
->hwnd
, &rect
);
2040 clRight
= rect
.right
;
2041 clBottom
= rect
.bottom
;
2048 * Erase the background. (Delay it but setup rectangle.)
2049 * This is necessary when drawing the selected item since it is larger
2050 * than the others, it might overlap with stuff already drawn by the
2055 /* Draw themed tabs - but only if they are at the top.
2056 * Windows draws even side or bottom tabs themed, with wacky results.
2057 * However, since in Wine apps may get themed that did not opt in via
2058 * a manifest avoid theming when we know the result will be wrong */
2059 if ((theme
= GetWindowTheme (infoPtr
->hwnd
))
2060 && ((infoPtr
->dwStyle
& (TCS_VERTICAL
| TCS_BOTTOM
)) == 0))
2062 static const int partIds
[8] = {
2065 TABP_TABITEMLEFTEDGE
,
2066 TABP_TABITEMRIGHTEDGE
,
2067 TABP_TABITEMBOTHEDGE
,
2070 TABP_TOPTABITEMLEFTEDGE
,
2071 TABP_TOPTABITEMRIGHTEDGE
,
2072 TABP_TOPTABITEMBOTHEDGE
,
2075 int stateId
= TIS_NORMAL
;
2077 /* selected and unselected tabs have different parts */
2078 if (iItem
== infoPtr
->iSelected
)
2080 /* The part also differs on the position of a tab on a line.
2081 * "Visually" determining the position works well enough. */
2082 GetClientRect(infoPtr
->hwnd
, &r1
);
2083 if(selectedRect
.left
== 0)
2085 if(selectedRect
.right
== r1
.right
)
2088 if (iItem
== infoPtr
->iSelected
)
2089 stateId
= TIS_SELECTED
;
2090 else if (iItem
== infoPtr
->iHotTracked
)
2092 else if (iItem
== infoPtr
->uFocus
)
2093 stateId
= TIS_FOCUSED
;
2095 /* Adjust rectangle for bottommost row */
2096 if (TAB_GetItem(infoPtr
, iItem
)->rect
.top
== infoPtr
->uNumRows
-1)
2099 DrawThemeBackground (theme
, hdc
, partIds
[partIndex
], stateId
, &r
, NULL
);
2100 GetThemeBackgroundContentRect (theme
, hdc
, partIds
[partIndex
], stateId
, &r
, &r
);
2102 else if(infoPtr
->dwStyle
& TCS_VERTICAL
)
2104 /* These are for adjusting the drawing of a Selected tab */
2105 /* The initial values are for the normal case of non-Selected */
2106 int ZZ
= 1; /* Do not stretch if selected */
2107 if (iItem
== infoPtr
->iSelected
) {
2110 /* if leftmost draw the line longer */
2111 if(selectedRect
.top
== 0)
2112 fillRect
.top
+= CONTROL_BORDER_SIZEY
;
2113 /* if rightmost draw the line longer */
2114 if(selectedRect
.bottom
== clBottom
)
2115 fillRect
.bottom
-= CONTROL_BORDER_SIZEY
;
2118 if (infoPtr
->dwStyle
& TCS_BOTTOM
)
2120 /* Adjust both rectangles to match native */
2123 TRACE("<right> item=%d, fill=(%s), edge=(%s)\n",
2124 iItem
, wine_dbgstr_rect(&fillRect
), wine_dbgstr_rect(&r
));
2126 /* Clear interior */
2127 SetBkColor(hdc
, bkgnd
);
2128 ExtTextOutW(hdc
, 0, 0, 2, &fillRect
, NULL
, 0, 0);
2130 /* Draw rectangular edge around tab */
2131 DrawEdge(hdc
, &r
, EDGE_RAISED
, BF_SOFT
|BF_RIGHT
|BF_TOP
|BF_BOTTOM
);
2133 /* Now erase the top corner and draw diagonal edge */
2134 SetBkColor(hdc
, corner
);
2135 r1
.left
= r
.right
- ROUND_CORNER_SIZE
- 1;
2138 r1
.bottom
= r1
.top
+ ROUND_CORNER_SIZE
;
2139 ExtTextOutW(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2141 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_DIAGONAL_ENDTOPLEFT
);
2143 /* Now erase the bottom corner and draw diagonal edge */
2144 r1
.left
= r
.right
- ROUND_CORNER_SIZE
- 1;
2145 r1
.bottom
= r
.bottom
;
2147 r1
.top
= r1
.bottom
- ROUND_CORNER_SIZE
;
2148 ExtTextOutW(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2150 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_DIAGONAL_ENDBOTTOMLEFT
);
2152 if ((iItem
== infoPtr
->iSelected
) && (selectedRect
.top
== 0)) {
2156 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_TOP
);
2162 TRACE("<left> item=%d, fill=(%s), edge=(%s)\n",
2163 iItem
, wine_dbgstr_rect(&fillRect
), wine_dbgstr_rect(&r
));
2165 /* Clear interior */
2166 SetBkColor(hdc
, bkgnd
);
2167 ExtTextOutW(hdc
, 0, 0, 2, &fillRect
, NULL
, 0, 0);
2169 /* Draw rectangular edge around tab */
2170 DrawEdge(hdc
, &r
, EDGE_RAISED
, BF_SOFT
|BF_LEFT
|BF_TOP
|BF_BOTTOM
);
2172 /* Now erase the top corner and draw diagonal edge */
2173 SetBkColor(hdc
, corner
);
2176 r1
.right
= r1
.left
+ ROUND_CORNER_SIZE
+ 1;
2177 r1
.bottom
= r1
.top
+ ROUND_CORNER_SIZE
;
2178 ExtTextOutW(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2180 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_DIAGONAL_ENDTOPRIGHT
);
2182 /* Now erase the bottom corner and draw diagonal edge */
2184 r1
.bottom
= r
.bottom
;
2185 r1
.right
= r1
.left
+ ROUND_CORNER_SIZE
+ 1;
2186 r1
.top
= r1
.bottom
- ROUND_CORNER_SIZE
;
2187 ExtTextOutW(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2189 DrawEdge(hdc
, &r1
, EDGE_SUNKEN
, BF_DIAGONAL_ENDTOPLEFT
);
2192 else /* ! TCS_VERTICAL */
2194 /* These are for adjusting the drawing of a Selected tab */
2195 /* The initial values are for the normal case of non-Selected */
2196 if (iItem
== infoPtr
->iSelected
) {
2197 /* if leftmost draw the line longer */
2198 if(selectedRect
.left
== 0)
2199 fillRect
.left
+= CONTROL_BORDER_SIZEX
;
2200 /* if rightmost draw the line longer */
2201 if(selectedRect
.right
== clRight
)
2202 fillRect
.right
-= CONTROL_BORDER_SIZEX
;
2205 if (infoPtr
->dwStyle
& TCS_BOTTOM
)
2207 /* Adjust both rectangles for topmost row */
2208 if (TAB_GetItem(infoPtr
, iItem
)->rect
.top
== infoPtr
->uNumRows
-1)
2214 TRACE("<bottom> item=%d, fill=(%s), edge=(%s)\n",
2215 iItem
, wine_dbgstr_rect(&fillRect
), wine_dbgstr_rect(&r
));
2217 /* Clear interior */
2218 SetBkColor(hdc
, bkgnd
);
2219 ExtTextOutW(hdc
, 0, 0, 2, &fillRect
, NULL
, 0, 0);
2221 /* Draw rectangular edge around tab */
2222 DrawEdge(hdc
, &r
, EDGE_RAISED
, BF_SOFT
|BF_LEFT
|BF_BOTTOM
|BF_RIGHT
);
2224 /* Now erase the righthand corner and draw diagonal edge */
2225 SetBkColor(hdc
, corner
);
2226 r1
.left
= r
.right
- ROUND_CORNER_SIZE
;
2227 r1
.bottom
= r
.bottom
;
2229 r1
.top
= r1
.bottom
- ROUND_CORNER_SIZE
- 1;
2230 ExtTextOutW(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2232 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_DIAGONAL_ENDBOTTOMLEFT
);
2234 /* Now erase the lefthand corner and draw diagonal edge */
2236 r1
.bottom
= r
.bottom
;
2237 r1
.right
= r1
.left
+ ROUND_CORNER_SIZE
;
2238 r1
.top
= r1
.bottom
- ROUND_CORNER_SIZE
- 1;
2239 ExtTextOutW(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2241 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_DIAGONAL_ENDTOPLEFT
);
2243 if (iItem
== infoPtr
->iSelected
)
2247 if (selectedRect
.left
== 0)
2252 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_LEFT
);
2259 /* Adjust both rectangles for bottommost row */
2260 if (TAB_GetItem(infoPtr
, iItem
)->rect
.top
== infoPtr
->uNumRows
-1)
2262 fillRect
.bottom
+= 3;
2266 TRACE("<top> item=%d, fill=(%s), edge=(%s)\n",
2267 iItem
, wine_dbgstr_rect(&fillRect
), wine_dbgstr_rect(&r
));
2269 /* Clear interior */
2270 SetBkColor(hdc
, bkgnd
);
2271 ExtTextOutW(hdc
, 0, 0, 2, &fillRect
, NULL
, 0, 0);
2273 /* Draw rectangular edge around tab */
2274 DrawEdge(hdc
, &r
, EDGE_RAISED
, BF_SOFT
|BF_LEFT
|BF_TOP
|BF_RIGHT
);
2276 /* Now erase the righthand corner and draw diagonal edge */
2277 SetBkColor(hdc
, corner
);
2278 r1
.left
= r
.right
- ROUND_CORNER_SIZE
;
2281 r1
.bottom
= r1
.top
+ ROUND_CORNER_SIZE
+ 1;
2282 ExtTextOutW(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2284 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_DIAGONAL_ENDBOTTOMRIGHT
);
2286 /* Now erase the lefthand corner and draw diagonal edge */
2289 r1
.right
= r1
.left
+ ROUND_CORNER_SIZE
;
2290 r1
.bottom
= r1
.top
+ ROUND_CORNER_SIZE
+ 1;
2291 ExtTextOutW(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2293 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_DIAGONAL_ENDTOPRIGHT
);
2298 TAB_DumpItemInternal(infoPtr
, iItem
);
2300 /* This modifies r to be the text rectangle. */
2301 TAB_DrawItemInterior(infoPtr
, hdc
, iItem
, &r
);
2305 /******************************************************************************
2308 * This method is used to draw the raised border around the tab control
2311 static void TAB_DrawBorder(const TAB_INFO
*infoPtr
, HDC hdc
)
2314 HTHEME theme
= GetWindowTheme (infoPtr
->hwnd
);
2316 GetClientRect (infoPtr
->hwnd
, &rect
);
2319 * Adjust for the style
2322 if (infoPtr
->uNumItem
)
2324 if ((infoPtr
->dwStyle
& TCS_BOTTOM
) && !(infoPtr
->dwStyle
& TCS_VERTICAL
))
2325 rect
.bottom
-= infoPtr
->tabHeight
* infoPtr
->uNumRows
+ CONTROL_BORDER_SIZEX
;
2326 else if((infoPtr
->dwStyle
& TCS_BOTTOM
) && (infoPtr
->dwStyle
& TCS_VERTICAL
))
2327 rect
.right
-= infoPtr
->tabHeight
* infoPtr
->uNumRows
+ CONTROL_BORDER_SIZEX
;
2328 else if(infoPtr
->dwStyle
& TCS_VERTICAL
)
2329 rect
.left
+= infoPtr
->tabHeight
* infoPtr
->uNumRows
+ CONTROL_BORDER_SIZEX
;
2330 else /* not TCS_VERTICAL and not TCS_BOTTOM */
2331 rect
.top
+= infoPtr
->tabHeight
* infoPtr
->uNumRows
+ CONTROL_BORDER_SIZEX
;
2334 TRACE("border=(%s)\n", wine_dbgstr_rect(&rect
));
2337 DrawThemeBackground (theme
, hdc
, TABP_PANE
, 0, &rect
, NULL
);
2339 DrawEdge(hdc
, &rect
, EDGE_RAISED
, BF_SOFT
|BF_RECT
);
2342 /******************************************************************************
2345 * This method repaints the tab control..
2347 static void TAB_Refresh (const TAB_INFO
*infoPtr
, HDC hdc
)
2352 if (!infoPtr
->DoRedraw
)
2355 hOldFont
= SelectObject (hdc
, infoPtr
->hFont
);
2357 if (infoPtr
->dwStyle
& TCS_BUTTONS
)
2359 for (i
= 0; i
< infoPtr
->uNumItem
; i
++)
2360 TAB_DrawItem (infoPtr
, hdc
, i
);
2364 /* Draw all the non selected item first */
2365 for (i
= 0; i
< infoPtr
->uNumItem
; i
++)
2367 if (i
!= infoPtr
->iSelected
)
2368 TAB_DrawItem (infoPtr
, hdc
, i
);
2371 /* Now, draw the border, draw it before the selected item
2372 * since the selected item overwrites part of the border. */
2373 TAB_DrawBorder (infoPtr
, hdc
);
2375 /* Then, draw the selected item */
2376 TAB_DrawItem (infoPtr
, hdc
, infoPtr
->iSelected
);
2379 SelectObject (hdc
, hOldFont
);
2382 static inline DWORD
TAB_GetRowCount (const TAB_INFO
*infoPtr
)
2384 TRACE("(%p)\n", infoPtr
);
2385 return infoPtr
->uNumRows
;
2388 static inline LRESULT
TAB_SetRedraw (TAB_INFO
*infoPtr
, BOOL doRedraw
)
2390 infoPtr
->DoRedraw
= doRedraw
;
2394 /******************************************************************************
2395 * TAB_EnsureSelectionVisible
2397 * This method will make sure that the current selection is completely
2398 * visible by scrolling until it is.
2400 static void TAB_EnsureSelectionVisible(
2403 INT iSelected
= infoPtr
->iSelected
;
2404 INT iOrigLeftmostVisible
= infoPtr
->leftmostVisible
;
2409 /* set the items row to the bottommost row or topmost row depending on
2411 if ((infoPtr
->uNumRows
> 1) && !(infoPtr
->dwStyle
& TCS_BUTTONS
))
2413 TAB_ITEM
*selected
= TAB_GetItem(infoPtr
, iSelected
);
2417 if(infoPtr
->dwStyle
& TCS_VERTICAL
)
2418 newselected
= selected
->rect
.left
;
2420 newselected
= selected
->rect
.top
;
2422 /* the target row is always (number of rows - 1)
2423 as row 0 is furthest from the clientRect */
2424 iTargetRow
= infoPtr
->uNumRows
- 1;
2426 if (newselected
!= iTargetRow
)
2429 if(infoPtr
->dwStyle
& TCS_VERTICAL
)
2431 for (i
=0; i
< infoPtr
->uNumItem
; i
++)
2433 /* move everything in the row of the selected item to the iTargetRow */
2434 TAB_ITEM
*item
= TAB_GetItem(infoPtr
, i
);
2436 if (item
->rect
.left
== newselected
)
2437 item
->rect
.left
= iTargetRow
;
2440 if (item
->rect
.left
> newselected
)
2447 for (i
=0; i
< infoPtr
->uNumItem
; i
++)
2449 TAB_ITEM
*item
= TAB_GetItem(infoPtr
, i
);
2451 if (item
->rect
.top
== newselected
)
2452 item
->rect
.top
= iTargetRow
;
2455 if (item
->rect
.top
> newselected
)
2460 TAB_RecalcHotTrack(infoPtr
, NULL
, NULL
, NULL
);
2465 * Do the trivial cases first.
2467 if ( (!infoPtr
->needsScrolling
) ||
2468 (infoPtr
->hwndUpDown
==0) || (infoPtr
->dwStyle
& TCS_VERTICAL
))
2471 if (infoPtr
->leftmostVisible
>= iSelected
)
2473 infoPtr
->leftmostVisible
= iSelected
;
2477 TAB_ITEM
*selected
= TAB_GetItem(infoPtr
, iSelected
);
2482 /* Calculate the part of the client area that is visible */
2483 GetClientRect(infoPtr
->hwnd
, &r
);
2486 GetClientRect(infoPtr
->hwndUpDown
, &r
);
2489 if ((selected
->rect
.right
-
2490 selected
->rect
.left
) >= width
)
2492 /* Special case: width of selected item is greater than visible
2495 infoPtr
->leftmostVisible
= iSelected
;
2499 for (i
= infoPtr
->leftmostVisible
; i
< infoPtr
->uNumItem
; i
++)
2501 if ((selected
->rect
.right
- TAB_GetItem(infoPtr
, i
)->rect
.left
) < width
)
2504 infoPtr
->leftmostVisible
= i
;
2508 if (infoPtr
->leftmostVisible
!= iOrigLeftmostVisible
)
2509 TAB_RecalcHotTrack(infoPtr
, NULL
, NULL
, NULL
);
2511 SendMessageW(infoPtr
->hwndUpDown
, UDM_SETPOS
, 0,
2512 MAKELONG(infoPtr
->leftmostVisible
, 0));
2515 /******************************************************************************
2516 * TAB_InvalidateTabArea
2518 * This method will invalidate the portion of the control that contains the
2519 * tabs. It is called when the state of the control changes and needs
2522 static void TAB_InvalidateTabArea(const TAB_INFO
*infoPtr
)
2524 RECT clientRect
, rInvalidate
, rAdjClient
;
2525 INT lastRow
= infoPtr
->uNumRows
- 1;
2528 if (lastRow
< 0) return;
2530 GetClientRect(infoPtr
->hwnd
, &clientRect
);
2531 rInvalidate
= clientRect
;
2532 rAdjClient
= clientRect
;
2534 TAB_AdjustRect(infoPtr
, 0, &rAdjClient
);
2536 TAB_InternalGetItemRect(infoPtr
, infoPtr
->uNumItem
-1 , &rect
, NULL
);
2537 if ((infoPtr
->dwStyle
& TCS_BOTTOM
) && (infoPtr
->dwStyle
& TCS_VERTICAL
))
2539 rInvalidate
.left
= rAdjClient
.right
;
2540 if (infoPtr
->uNumRows
== 1)
2541 rInvalidate
.bottom
= clientRect
.top
+ rect
.bottom
+ 2 * SELECTED_TAB_OFFSET
;
2543 else if(infoPtr
->dwStyle
& TCS_VERTICAL
)
2545 rInvalidate
.right
= rAdjClient
.left
;
2546 if (infoPtr
->uNumRows
== 1)
2547 rInvalidate
.bottom
= clientRect
.top
+ rect
.bottom
+ 2 * SELECTED_TAB_OFFSET
;
2549 else if (infoPtr
->dwStyle
& TCS_BOTTOM
)
2551 rInvalidate
.top
= rAdjClient
.bottom
;
2552 if (infoPtr
->uNumRows
== 1)
2553 rInvalidate
.right
= clientRect
.left
+ rect
.right
+ 2 * SELECTED_TAB_OFFSET
;
2557 rInvalidate
.bottom
= rAdjClient
.top
;
2558 if (infoPtr
->uNumRows
== 1)
2559 rInvalidate
.right
= clientRect
.left
+ rect
.right
+ 2 * SELECTED_TAB_OFFSET
;
2562 /* Punch out the updown control */
2563 if (infoPtr
->needsScrolling
&& (rInvalidate
.right
> 0)) {
2565 GetClientRect(infoPtr
->hwndUpDown
, &r
);
2566 if (rInvalidate
.right
> clientRect
.right
- r
.left
)
2567 rInvalidate
.right
= rInvalidate
.right
- (r
.right
- r
.left
);
2569 rInvalidate
.right
= clientRect
.right
- r
.left
;
2572 TRACE("invalidate (%s)\n", wine_dbgstr_rect(&rInvalidate
));
2574 InvalidateRect(infoPtr
->hwnd
, &rInvalidate
, TRUE
);
2577 static inline LRESULT
TAB_Paint (TAB_INFO
*infoPtr
, HDC hdcPaint
)
2586 hdc
= BeginPaint (infoPtr
->hwnd
, &ps
);
2587 TRACE("erase %d, rect=(%s)\n", ps
.fErase
, wine_dbgstr_rect(&ps
.rcPaint
));
2590 TAB_Refresh (infoPtr
, hdc
);
2593 EndPaint (infoPtr
->hwnd
, &ps
);
2599 TAB_InsertItemT (TAB_INFO
*infoPtr
, INT iItem
, const TCITEMW
*pti
, BOOL bUnicode
)
2604 GetClientRect (infoPtr
->hwnd
, &rect
);
2605 TRACE("Rect: %p %s\n", infoPtr
->hwnd
, wine_dbgstr_rect(&rect
));
2607 if (iItem
< 0) return -1;
2608 if (iItem
> infoPtr
->uNumItem
)
2609 iItem
= infoPtr
->uNumItem
;
2611 TAB_DumpItemExternalT(pti
, iItem
, bUnicode
);
2613 if (!(item
= Alloc(TAB_ITEM_SIZE(infoPtr
)))) return FALSE
;
2614 if (DPA_InsertPtr(infoPtr
->items
, iItem
, item
) == -1)
2620 if (infoPtr
->uNumItem
== 0)
2621 infoPtr
->iSelected
= 0;
2622 else if (iItem
<= infoPtr
->iSelected
)
2623 infoPtr
->iSelected
++;
2625 infoPtr
->uNumItem
++;
2627 item
->pszText
= NULL
;
2628 if (pti
->mask
& TCIF_TEXT
)
2631 Str_SetPtrW (&item
->pszText
, pti
->pszText
);
2633 Str_SetPtrAtoW (&item
->pszText
, (LPSTR
)pti
->pszText
);
2636 if (pti
->mask
& TCIF_IMAGE
)
2637 item
->iImage
= pti
->iImage
;
2641 if (pti
->mask
& TCIF_PARAM
)
2642 memcpy(item
->extra
, &pti
->lParam
, EXTRA_ITEM_SIZE(infoPtr
));
2644 memset(item
->extra
, 0, EXTRA_ITEM_SIZE(infoPtr
));
2646 TAB_SetItemBounds(infoPtr
);
2647 if (infoPtr
->uNumItem
> 1)
2648 TAB_InvalidateTabArea(infoPtr
);
2650 InvalidateRect(infoPtr
->hwnd
, NULL
, TRUE
);
2652 TRACE("[%p]: added item %d %s\n",
2653 infoPtr
->hwnd
, iItem
, debugstr_w(item
->pszText
));
2655 /* If we haven't set the current focus yet, set it now. */
2656 if (infoPtr
->uFocus
== -1)
2657 TAB_SetCurFocus(infoPtr
, iItem
);
2663 TAB_SetItemSize (TAB_INFO
*infoPtr
, INT cx
, INT cy
)
2666 BOOL bNeedPaint
= FALSE
;
2668 lResult
= MAKELONG(infoPtr
->tabWidth
, infoPtr
->tabHeight
);
2670 /* UNDOCUMENTED: If requested Width or Height is 0 this means that program wants to use auto size. */
2671 if (infoPtr
->dwStyle
& TCS_FIXEDWIDTH
&& (infoPtr
->tabWidth
!= cx
))
2673 infoPtr
->tabWidth
= cx
;
2677 if (infoPtr
->tabHeight
!= cy
)
2679 if ((infoPtr
->fHeightSet
= (cy
!= 0)))
2680 infoPtr
->tabHeight
= cy
;
2684 TRACE("was h=%d,w=%d, now h=%d,w=%d\n",
2685 HIWORD(lResult
), LOWORD(lResult
),
2686 infoPtr
->tabHeight
, infoPtr
->tabWidth
);
2690 TAB_SetItemBounds(infoPtr
);
2691 RedrawWindow(infoPtr
->hwnd
, NULL
, NULL
, RDW_ERASE
| RDW_INVALIDATE
| RDW_UPDATENOW
);
2697 static inline LRESULT
TAB_SetMinTabWidth (TAB_INFO
*infoPtr
, INT cx
)
2701 TRACE("(%p,%d)\n", infoPtr
, cx
);
2703 if (infoPtr
->tabMinWidth
< 0)
2704 oldcx
= DEFAULT_MIN_TAB_WIDTH
;
2706 oldcx
= infoPtr
->tabMinWidth
;
2707 infoPtr
->tabMinWidth
= cx
;
2708 TAB_SetItemBounds(infoPtr
);
2712 static inline LRESULT
2713 TAB_HighlightItem (TAB_INFO
*infoPtr
, INT iItem
, BOOL fHighlight
)
2719 TRACE("(%p,%d,%s)\n", infoPtr
, iItem
, fHighlight
? "true" : "false");
2721 if (iItem
< 0 || iItem
>= infoPtr
->uNumItem
)
2724 lpState
= &TAB_GetItem(infoPtr
, iItem
)->dwState
;
2725 oldState
= *lpState
;
2728 *lpState
|= TCIS_HIGHLIGHTED
;
2730 *lpState
&= ~TCIS_HIGHLIGHTED
;
2732 if ((oldState
!= *lpState
) && TAB_InternalGetItemRect (infoPtr
, iItem
, &r
, NULL
))
2733 InvalidateRect (infoPtr
->hwnd
, &r
, TRUE
);
2739 TAB_SetItemT (TAB_INFO
*infoPtr
, INT iItem
, LPTCITEMW tabItem
, BOOL bUnicode
)
2743 TRACE("(%p,%d,%p,%s)\n", infoPtr
, iItem
, tabItem
, bUnicode
? "true" : "false");
2745 if (iItem
< 0 || iItem
>= infoPtr
->uNumItem
)
2748 TAB_DumpItemExternalT(tabItem
, iItem
, bUnicode
);
2750 wineItem
= TAB_GetItem(infoPtr
, iItem
);
2752 if (tabItem
->mask
& TCIF_IMAGE
)
2753 wineItem
->iImage
= tabItem
->iImage
;
2755 if (tabItem
->mask
& TCIF_PARAM
)
2756 memcpy(wineItem
->extra
, &tabItem
->lParam
, infoPtr
->cbInfo
);
2758 if (tabItem
->mask
& TCIF_RTLREADING
)
2759 FIXME("TCIF_RTLREADING\n");
2761 if (tabItem
->mask
& TCIF_STATE
)
2762 wineItem
->dwState
= (wineItem
->dwState
& ~tabItem
->dwStateMask
) |
2763 ( tabItem
->dwState
& tabItem
->dwStateMask
);
2765 if (tabItem
->mask
& TCIF_TEXT
)
2767 Free(wineItem
->pszText
);
2768 wineItem
->pszText
= NULL
;
2770 Str_SetPtrW(&wineItem
->pszText
, tabItem
->pszText
);
2772 Str_SetPtrAtoW(&wineItem
->pszText
, (LPSTR
)tabItem
->pszText
);
2775 /* Update and repaint tabs */
2776 TAB_SetItemBounds(infoPtr
);
2777 TAB_InvalidateTabArea(infoPtr
);
2782 static inline LRESULT
TAB_GetItemCount (const TAB_INFO
*infoPtr
)
2785 return infoPtr
->uNumItem
;
2790 TAB_GetItemT (TAB_INFO
*infoPtr
, INT iItem
, LPTCITEMW tabItem
, BOOL bUnicode
)
2794 TRACE("(%p,%d,%p,%s)\n", infoPtr
, iItem
, tabItem
, bUnicode
? "true" : "false");
2796 if (!tabItem
) return FALSE
;
2798 if (iItem
< 0 || iItem
>= infoPtr
->uNumItem
)
2800 /* init requested fields */
2801 if (tabItem
->mask
& TCIF_IMAGE
) tabItem
->iImage
= 0;
2802 if (tabItem
->mask
& TCIF_PARAM
) tabItem
->lParam
= 0;
2803 if (tabItem
->mask
& TCIF_STATE
) tabItem
->dwState
= 0;
2807 wineItem
= TAB_GetItem(infoPtr
, iItem
);
2809 if (tabItem
->mask
& TCIF_IMAGE
)
2810 tabItem
->iImage
= wineItem
->iImage
;
2812 if (tabItem
->mask
& TCIF_PARAM
)
2813 memcpy(&tabItem
->lParam
, wineItem
->extra
, infoPtr
->cbInfo
);
2815 if (tabItem
->mask
& TCIF_RTLREADING
)
2816 FIXME("TCIF_RTLREADING\n");
2818 if (tabItem
->mask
& TCIF_STATE
)
2819 tabItem
->dwState
= wineItem
->dwState
& tabItem
->dwStateMask
;
2821 if (tabItem
->mask
& TCIF_TEXT
)
2824 Str_GetPtrW (wineItem
->pszText
, tabItem
->pszText
, tabItem
->cchTextMax
);
2826 Str_GetPtrWtoA (wineItem
->pszText
, (LPSTR
)tabItem
->pszText
, tabItem
->cchTextMax
);
2829 TAB_DumpItemExternalT(tabItem
, iItem
, bUnicode
);
2835 static LRESULT
TAB_DeleteItem (TAB_INFO
*infoPtr
, INT iItem
)
2839 TRACE("(%p, %d)\n", infoPtr
, iItem
);
2841 if (iItem
< 0 || iItem
>= infoPtr
->uNumItem
) return FALSE
;
2843 TAB_InvalidateTabArea(infoPtr
);
2844 item
= TAB_GetItem(infoPtr
, iItem
);
2845 Free(item
->pszText
);
2847 infoPtr
->uNumItem
--;
2848 DPA_DeletePtr(infoPtr
->items
, iItem
);
2850 if (infoPtr
->uNumItem
== 0)
2852 if (infoPtr
->iHotTracked
>= 0)
2854 KillTimer(infoPtr
->hwnd
, TAB_HOTTRACK_TIMER
);
2855 infoPtr
->iHotTracked
= -1;
2858 infoPtr
->iSelected
= -1;
2862 if (iItem
<= infoPtr
->iHotTracked
)
2864 /* When tabs move left/up, the hot track item may change */
2865 FIXME("Recalc hot track\n");
2869 /* adjust the selected index */
2870 if (iItem
== infoPtr
->iSelected
)
2871 infoPtr
->iSelected
= -1;
2872 else if (iItem
< infoPtr
->iSelected
)
2873 infoPtr
->iSelected
--;
2875 /* reposition and repaint tabs */
2876 TAB_SetItemBounds(infoPtr
);
2881 static inline LRESULT
TAB_DeleteAllItems (TAB_INFO
*infoPtr
)
2883 TRACE("(%p)\n", infoPtr
);
2884 while (infoPtr
->uNumItem
)
2885 TAB_DeleteItem (infoPtr
, 0);
2890 static inline LRESULT
TAB_GetFont (const TAB_INFO
*infoPtr
)
2892 TRACE("(%p) returning %p\n", infoPtr
, infoPtr
->hFont
);
2893 return (LRESULT
)infoPtr
->hFont
;
2896 static inline LRESULT
TAB_SetFont (TAB_INFO
*infoPtr
, HFONT hNewFont
)
2898 TRACE("(%p,%p)\n", infoPtr
, hNewFont
);
2900 infoPtr
->hFont
= hNewFont
;
2902 TAB_SetItemBounds(infoPtr
);
2904 TAB_InvalidateTabArea(infoPtr
);
2910 static inline LRESULT
TAB_GetImageList (const TAB_INFO
*infoPtr
)
2913 return (LRESULT
)infoPtr
->himl
;
2916 static inline LRESULT
TAB_SetImageList (TAB_INFO
*infoPtr
, HIMAGELIST himlNew
)
2918 HIMAGELIST himlPrev
= infoPtr
->himl
;
2919 TRACE("himl=%p\n", himlNew
);
2920 infoPtr
->himl
= himlNew
;
2921 TAB_SetItemBounds(infoPtr
);
2922 InvalidateRect(infoPtr
->hwnd
, NULL
, TRUE
);
2923 return (LRESULT
)himlPrev
;
2926 static inline LRESULT
TAB_GetUnicodeFormat (const TAB_INFO
*infoPtr
)
2928 TRACE("(%p)\n", infoPtr
);
2929 return infoPtr
->bUnicode
;
2932 static inline LRESULT
TAB_SetUnicodeFormat (TAB_INFO
*infoPtr
, BOOL bUnicode
)
2934 BOOL bTemp
= infoPtr
->bUnicode
;
2936 TRACE("(%p %d)\n", infoPtr
, bUnicode
);
2937 infoPtr
->bUnicode
= bUnicode
;
2942 static inline LRESULT
TAB_Size (TAB_INFO
*infoPtr
)
2944 /* I'm not really sure what the following code was meant to do.
2945 This is what it is doing:
2946 When WM_SIZE is sent with SIZE_RESTORED, the control
2947 gets positioned in the top left corner.
2951 UINT uPosFlags,cx,cy;
2955 parent = GetParent (hwnd);
2956 GetClientRect(parent, &parent_rect);
2959 if (GetWindowLongW(hwnd, GWL_STYLE) & CCS_NORESIZE)
2960 uPosFlags |= (SWP_NOSIZE | SWP_NOMOVE);
2962 SetWindowPos (hwnd, 0, parent_rect.left, parent_rect.top,
2963 cx, cy, uPosFlags | SWP_NOZORDER);
2965 FIXME("WM_SIZE flag %x %lx not handled\n", wParam, lParam);
2968 /* Recompute the size/position of the tabs. */
2969 TAB_SetItemBounds (infoPtr
);
2971 /* Force a repaint of the control. */
2972 InvalidateRect(infoPtr
->hwnd
, NULL
, TRUE
);
2978 static LRESULT
TAB_Create (HWND hwnd
, LPARAM lParam
)
2981 TEXTMETRICW fontMetrics
;
2986 infoPtr
= Alloc (sizeof(TAB_INFO
));
2988 SetWindowLongPtrW(hwnd
, 0, (DWORD_PTR
)infoPtr
);
2990 infoPtr
->hwnd
= hwnd
;
2991 infoPtr
->hwndNotify
= ((LPCREATESTRUCTW
)lParam
)->hwndParent
;
2992 infoPtr
->uNumItem
= 0;
2993 infoPtr
->uNumRows
= 0;
2994 infoPtr
->uHItemPadding
= 6;
2995 infoPtr
->uVItemPadding
= 3;
2996 infoPtr
->uHItemPadding_s
= 6;
2997 infoPtr
->uVItemPadding_s
= 3;
2999 infoPtr
->items
= DPA_Create(8);
3000 infoPtr
->hcurArrow
= LoadCursorW (0, (LPWSTR
)IDC_ARROW
);
3001 infoPtr
->iSelected
= -1;
3002 infoPtr
->iHotTracked
= -1;
3003 infoPtr
->uFocus
= -1;
3004 infoPtr
->hwndToolTip
= 0;
3005 infoPtr
->DoRedraw
= TRUE
;
3006 infoPtr
->needsScrolling
= FALSE
;
3007 infoPtr
->hwndUpDown
= 0;
3008 infoPtr
->leftmostVisible
= 0;
3009 infoPtr
->fHeightSet
= FALSE
;
3010 infoPtr
->bUnicode
= IsWindowUnicode (hwnd
);
3011 infoPtr
->cbInfo
= sizeof(LPARAM
);
3013 TRACE("Created tab control, hwnd [%p]\n", hwnd
);
3015 /* The tab control always has the WS_CLIPSIBLINGS style. Even
3016 if you don't specify it in CreateWindow. This is necessary in
3017 order for paint to work correctly. This follows windows behaviour. */
3018 style
= GetWindowLongW(hwnd
, GWL_STYLE
);
3019 if (style
& TCS_VERTICAL
) style
|= TCS_MULTILINE
;
3020 style
|= WS_CLIPSIBLINGS
;
3021 SetWindowLongW(hwnd
, GWL_STYLE
, style
);
3023 infoPtr
->dwStyle
= style
;
3024 infoPtr
->exStyle
= (style
& TCS_FLATBUTTONS
) ? TCS_EX_FLATSEPARATORS
: 0;
3026 if (infoPtr
->dwStyle
& TCS_TOOLTIPS
) {
3027 /* Create tooltip control */
3028 infoPtr
->hwndToolTip
=
3029 CreateWindowExW (0, TOOLTIPS_CLASSW
, NULL
, WS_POPUP
,
3030 CW_USEDEFAULT
, CW_USEDEFAULT
,
3031 CW_USEDEFAULT
, CW_USEDEFAULT
,
3034 /* Send NM_TOOLTIPSCREATED notification */
3035 if (infoPtr
->hwndToolTip
) {
3036 NMTOOLTIPSCREATED nmttc
;
3038 nmttc
.hdr
.hwndFrom
= hwnd
;
3039 nmttc
.hdr
.idFrom
= GetWindowLongPtrW(hwnd
, GWLP_ID
);
3040 nmttc
.hdr
.code
= NM_TOOLTIPSCREATED
;
3041 nmttc
.hwndToolTips
= infoPtr
->hwndToolTip
;
3043 SendMessageW (infoPtr
->hwndNotify
, WM_NOTIFY
,
3044 GetWindowLongPtrW(hwnd
, GWLP_ID
), (LPARAM
)&nmttc
);
3048 OpenThemeData (infoPtr
->hwnd
, themeClass
);
3051 * We need to get text information so we need a DC and we need to select
3055 hOldFont
= SelectObject (hdc
, GetStockObject (SYSTEM_FONT
));
3057 /* Use the system font to determine the initial height of a tab. */
3058 GetTextMetricsW(hdc
, &fontMetrics
);
3061 * Make sure there is enough space for the letters + growing the
3062 * selected item + extra space for the selected item.
3064 infoPtr
->tabHeight
= fontMetrics
.tmHeight
+ SELECTED_TAB_OFFSET
+
3065 ((infoPtr
->dwStyle
& TCS_BUTTONS
) ? 2 : 1) *
3066 infoPtr
->uVItemPadding
;
3068 /* Initialize the width of a tab. */
3069 if (infoPtr
->dwStyle
& TCS_FIXEDWIDTH
)
3070 infoPtr
->tabWidth
= GetDeviceCaps(hdc
, LOGPIXELSX
);
3072 infoPtr
->tabMinWidth
= -1;
3074 TRACE("tabH=%d, tabW=%d\n", infoPtr
->tabHeight
, infoPtr
->tabWidth
);
3076 SelectObject (hdc
, hOldFont
);
3077 ReleaseDC(hwnd
, hdc
);
3083 TAB_Destroy (TAB_INFO
*infoPtr
)
3087 SetWindowLongPtrW(infoPtr
->hwnd
, 0, 0);
3089 for (iItem
= infoPtr
->uNumItem
- 1; iItem
>= 0; iItem
--)
3091 TAB_ITEM
*tab
= TAB_GetItem(infoPtr
, iItem
);
3093 DPA_DeletePtr(infoPtr
->items
, iItem
);
3094 infoPtr
->uNumItem
--;
3099 DPA_Destroy(infoPtr
->items
);
3100 infoPtr
->items
= NULL
;
3102 if (infoPtr
->hwndToolTip
)
3103 DestroyWindow (infoPtr
->hwndToolTip
);
3105 if (infoPtr
->hwndUpDown
)
3106 DestroyWindow(infoPtr
->hwndUpDown
);
3108 if (infoPtr
->iHotTracked
>= 0)
3109 KillTimer(infoPtr
->hwnd
, TAB_HOTTRACK_TIMER
);
3111 CloseThemeData (GetWindowTheme (infoPtr
->hwnd
));
3117 /* update theme after a WM_THEMECHANGED message */
3118 static LRESULT
theme_changed(const TAB_INFO
*infoPtr
)
3120 HTHEME theme
= GetWindowTheme (infoPtr
->hwnd
);
3121 CloseThemeData (theme
);
3122 OpenThemeData (infoPtr
->hwnd
, themeClass
);
3126 static LRESULT
TAB_NCCalcSize(WPARAM wParam
)
3130 return WVR_ALIGNTOP
;
3133 static inline LRESULT
3134 TAB_SetItemExtra (TAB_INFO
*infoPtr
, INT cbInfo
)
3136 TRACE("(%p %d)\n", infoPtr
, cbInfo
);
3138 if (cbInfo
< 0 || infoPtr
->uNumItem
) return FALSE
;
3140 infoPtr
->cbInfo
= cbInfo
;
3144 static LRESULT
TAB_RemoveImage (TAB_INFO
*infoPtr
, INT image
)
3146 TRACE("%p %d\n", infoPtr
, image
);
3148 if (ImageList_Remove (infoPtr
->himl
, image
))
3153 /* shift indices, repaint items if needed */
3154 for (i
= 0; i
< infoPtr
->uNumItem
; i
++)
3156 idx
= &TAB_GetItem(infoPtr
, i
)->iImage
;
3165 if (TAB_InternalGetItemRect (infoPtr
, i
, &r
, NULL
))
3166 InvalidateRect (infoPtr
->hwnd
, &r
, TRUE
);
3175 TAB_SetExtendedStyle (TAB_INFO
*infoPtr
, DWORD exMask
, DWORD exStyle
)
3177 DWORD prevstyle
= infoPtr
->exStyle
;
3179 /* zero mask means all styles */
3180 if (exMask
== 0) exMask
= ~0;
3182 if (exMask
& TCS_EX_REGISTERDROP
)
3184 FIXME("TCS_EX_REGISTERDROP style unimplemented\n");
3185 exMask
&= ~TCS_EX_REGISTERDROP
;
3186 exStyle
&= ~TCS_EX_REGISTERDROP
;
3189 if (exMask
& TCS_EX_FLATSEPARATORS
)
3191 if ((prevstyle
^ exStyle
) & TCS_EX_FLATSEPARATORS
)
3193 infoPtr
->exStyle
^= TCS_EX_FLATSEPARATORS
;
3194 TAB_InvalidateTabArea(infoPtr
);
3201 static inline LRESULT
3202 TAB_GetExtendedStyle (const TAB_INFO
*infoPtr
)
3204 return infoPtr
->exStyle
;
3208 TAB_DeselectAll (TAB_INFO
*infoPtr
, BOOL excludesel
)
3211 INT i
, selected
= infoPtr
->iSelected
;
3213 TRACE("(%p, %d)\n", infoPtr
, excludesel
);
3215 if (!(infoPtr
->dwStyle
& TCS_BUTTONS
))
3218 for (i
= 0; i
< infoPtr
->uNumItem
; i
++)
3220 if ((TAB_GetItem(infoPtr
, i
)->dwState
& TCIS_BUTTONPRESSED
) &&
3223 TAB_GetItem(infoPtr
, i
)->dwState
&= ~TCIS_BUTTONPRESSED
;
3228 if (!excludesel
&& (selected
!= -1))
3230 TAB_GetItem(infoPtr
, selected
)->dwState
&= ~TCIS_BUTTONPRESSED
;
3231 infoPtr
->iSelected
= -1;
3236 TAB_InvalidateTabArea (infoPtr
);
3243 * Processes WM_STYLECHANGED messages.
3246 * [I] infoPtr : valid pointer to the tab data structure
3247 * [I] wStyleType : window style type (normal or extended)
3248 * [I] lpss : window style information
3253 static INT
TAB_StyleChanged(TAB_INFO
*infoPtr
, WPARAM wStyleType
,
3254 const STYLESTRUCT
*lpss
)
3256 TRACE("(styletype=%lx, styleOld=0x%08x, styleNew=0x%08x)\n",
3257 wStyleType
, lpss
->styleOld
, lpss
->styleNew
);
3259 if (wStyleType
!= GWL_STYLE
) return 0;
3261 infoPtr
->dwStyle
= lpss
->styleNew
;
3263 TAB_SetItemBounds (infoPtr
);
3264 InvalidateRect(infoPtr
->hwnd
, NULL
, TRUE
);
3269 static LRESULT WINAPI
3270 TAB_WindowProc (HWND hwnd
, UINT uMsg
, WPARAM wParam
, LPARAM lParam
)
3272 TAB_INFO
*infoPtr
= TAB_GetInfoPtr(hwnd
);
3274 TRACE("hwnd=%p msg=%x wParam=%lx lParam=%lx\n", hwnd
, uMsg
, wParam
, lParam
);
3275 if (!infoPtr
&& (uMsg
!= WM_CREATE
))
3276 return DefWindowProcW (hwnd
, uMsg
, wParam
, lParam
);
3280 case TCM_GETIMAGELIST
:
3281 return TAB_GetImageList (infoPtr
);
3283 case TCM_SETIMAGELIST
:
3284 return TAB_SetImageList (infoPtr
, (HIMAGELIST
)lParam
);
3286 case TCM_GETITEMCOUNT
:
3287 return TAB_GetItemCount (infoPtr
);
3291 return TAB_GetItemT (infoPtr
, (INT
)wParam
, (LPTCITEMW
)lParam
, uMsg
== TCM_GETITEMW
);
3295 return TAB_SetItemT (infoPtr
, (INT
)wParam
, (LPTCITEMW
)lParam
, uMsg
== TCM_SETITEMW
);
3297 case TCM_DELETEITEM
:
3298 return TAB_DeleteItem (infoPtr
, (INT
)wParam
);
3300 case TCM_DELETEALLITEMS
:
3301 return TAB_DeleteAllItems (infoPtr
);
3303 case TCM_GETITEMRECT
:
3304 return TAB_GetItemRect (infoPtr
, (INT
)wParam
, (LPRECT
)lParam
);
3307 return TAB_GetCurSel (infoPtr
);
3310 return TAB_HitTest (infoPtr
, (LPTCHITTESTINFO
)lParam
);
3313 return TAB_SetCurSel (infoPtr
, (INT
)wParam
);
3315 case TCM_INSERTITEMA
:
3316 case TCM_INSERTITEMW
:
3317 return TAB_InsertItemT (infoPtr
, (INT
)wParam
, (TCITEMW
*)lParam
, uMsg
== TCM_INSERTITEMW
);
3319 case TCM_SETITEMEXTRA
:
3320 return TAB_SetItemExtra (infoPtr
, (INT
)wParam
);
3322 case TCM_ADJUSTRECT
:
3323 return TAB_AdjustRect (infoPtr
, (BOOL
)wParam
, (LPRECT
)lParam
);
3325 case TCM_SETITEMSIZE
:
3326 return TAB_SetItemSize (infoPtr
, (INT
)LOWORD(lParam
), (INT
)HIWORD(lParam
));
3328 case TCM_REMOVEIMAGE
:
3329 return TAB_RemoveImage (infoPtr
, (INT
)wParam
);
3331 case TCM_SETPADDING
:
3332 return TAB_SetPadding (infoPtr
, lParam
);
3334 case TCM_GETROWCOUNT
:
3335 return TAB_GetRowCount(infoPtr
);
3337 case TCM_GETUNICODEFORMAT
:
3338 return TAB_GetUnicodeFormat (infoPtr
);
3340 case TCM_SETUNICODEFORMAT
:
3341 return TAB_SetUnicodeFormat (infoPtr
, (BOOL
)wParam
);
3343 case TCM_HIGHLIGHTITEM
:
3344 return TAB_HighlightItem (infoPtr
, (INT
)wParam
, (BOOL
)LOWORD(lParam
));
3346 case TCM_GETTOOLTIPS
:
3347 return TAB_GetToolTips (infoPtr
);
3349 case TCM_SETTOOLTIPS
:
3350 return TAB_SetToolTips (infoPtr
, (HWND
)wParam
);
3352 case TCM_GETCURFOCUS
:
3353 return TAB_GetCurFocus (infoPtr
);
3355 case TCM_SETCURFOCUS
:
3356 return TAB_SetCurFocus (infoPtr
, (INT
)wParam
);
3358 case TCM_SETMINTABWIDTH
:
3359 return TAB_SetMinTabWidth(infoPtr
, (INT
)lParam
);
3361 case TCM_DESELECTALL
:
3362 return TAB_DeselectAll (infoPtr
, (BOOL
)wParam
);
3364 case TCM_GETEXTENDEDSTYLE
:
3365 return TAB_GetExtendedStyle (infoPtr
);
3367 case TCM_SETEXTENDEDSTYLE
:
3368 return TAB_SetExtendedStyle (infoPtr
, wParam
, lParam
);
3371 return TAB_GetFont (infoPtr
);
3374 return TAB_SetFont (infoPtr
, (HFONT
)wParam
);
3377 return TAB_Create (hwnd
, lParam
);
3380 return TAB_Destroy (infoPtr
);
3383 return DLGC_WANTARROWS
| DLGC_WANTCHARS
;
3385 case WM_LBUTTONDOWN
:
3386 return TAB_LButtonDown (infoPtr
, wParam
, lParam
);
3389 return TAB_LButtonUp (infoPtr
);
3392 return SendMessageW(infoPtr
->hwndNotify
, WM_NOTIFY
, wParam
, lParam
);
3395 TAB_RButtonUp (infoPtr
);
3396 return DefWindowProcW (hwnd
, uMsg
, wParam
, lParam
);
3399 return TAB_MouseMove (infoPtr
, wParam
, lParam
);
3401 case WM_PRINTCLIENT
:
3403 return TAB_Paint (infoPtr
, (HDC
)wParam
);
3406 return TAB_Size (infoPtr
);
3409 return TAB_SetRedraw (infoPtr
, (BOOL
)wParam
);
3412 return TAB_OnHScroll(infoPtr
, (int)LOWORD(wParam
), (int)HIWORD(wParam
));
3414 case WM_STYLECHANGED
:
3415 return TAB_StyleChanged(infoPtr
, wParam
, (LPSTYLESTRUCT
)lParam
);
3417 case WM_SYSCOLORCHANGE
:
3418 COMCTL32_RefreshSysColors();
3421 case WM_THEMECHANGED
:
3422 return theme_changed (infoPtr
);
3425 TAB_KillFocus(infoPtr
);
3427 TAB_FocusChanging(infoPtr
);
3428 break; /* Don't disturb normal focus behavior */
3431 return TAB_KeyDown(infoPtr
, wParam
, lParam
);
3434 return TAB_NCHitTest(infoPtr
, lParam
);
3437 return TAB_NCCalcSize(wParam
);
3440 if (uMsg
>= WM_USER
&& uMsg
< WM_APP
&& !COMCTL32_IsReflectedMessage(uMsg
))
3441 WARN("unknown msg %04x wp=%08lx lp=%08lx\n",
3442 uMsg
, wParam
, lParam
);
3445 return DefWindowProcW(hwnd
, uMsg
, wParam
, lParam
);
3454 ZeroMemory (&wndClass
, sizeof(WNDCLASSW
));
3455 wndClass
.style
= CS_GLOBALCLASS
| CS_DBLCLKS
| CS_HREDRAW
| CS_VREDRAW
;
3456 wndClass
.lpfnWndProc
= TAB_WindowProc
;
3457 wndClass
.cbClsExtra
= 0;
3458 wndClass
.cbWndExtra
= sizeof(TAB_INFO
*);
3459 wndClass
.hCursor
= LoadCursorW (0, (LPWSTR
)IDC_ARROW
);
3460 wndClass
.hbrBackground
= (HBRUSH
)(COLOR_BTNFACE
+1);
3461 wndClass
.lpszClassName
= WC_TABCONTROLW
;
3463 RegisterClassW (&wndClass
);
3468 TAB_Unregister (void)
3470 UnregisterClassW (WC_TABCONTROLW
, NULL
);