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
68 #include "wine/debug.h"
71 WINE_DEFAULT_DEBUG_CHANNEL(tab
);
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)
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 */
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)])
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 */
122 DWORD exStyle
; /* Extended style used, currently:
123 TCS_EX_FLATSEPARATORS, TCS_EX_REGISTERDROP */
124 DWORD dwStyle
; /* the cached window GWL_STYLE */
126 HDPA items
; /* dynamic array of TAB_ITEM* pointers */
129 /******************************************************************************
130 * Positioning constants
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
145 #define TAB_GetInfoPtr(hwnd) ((TAB_INFO *)GetWindowLongPtrW(hwnd,0))
147 #define GET_DEFAULT_MIN_TAB_WIDTH(infoPtr) (DEFAULT_MIN_TAB_WIDTH - (DEFAULT_PADDING_X - (infoPtr)->uHItemPadding) * 2)
149 /******************************************************************************
150 * Hot-tracking timer constants
152 #define TAB_HOTTRACK_TIMER 1
153 #define TAB_HOTTRACK_TIMER_INTERVAL 100 /* milliseconds */
155 static const WCHAR themeClass
[] = L
"Tab";
157 static inline TAB_ITEM
* TAB_GetItem(const TAB_INFO
*infoPtr
, INT i
)
159 assert(i
>= 0 && i
< infoPtr
->uNumItem
);
160 return DPA_GetPtr(infoPtr
->items
, i
);
163 /******************************************************************************
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
*);
173 TAB_SendSimpleNotify (const TAB_INFO
*infoPtr
, UINT code
)
177 nmhdr
.hwndFrom
= infoPtr
->hwnd
;
178 nmhdr
.idFrom
= GetWindowLongPtrW(infoPtr
->hwnd
, GWLP_ID
);
181 return (BOOL
) SendMessageW (infoPtr
->hwndNotify
, WM_NOTIFY
,
182 nmhdr
.idFrom
, (LPARAM
) &nmhdr
);
186 TAB_RelayEvent (HWND hwndTip
, HWND hwndMsg
, UINT uMsg
,
187 WPARAM wParam
, LPARAM lParam
)
195 msg
.time
= GetMessageTime ();
196 msg
.pt
.x
= (short)LOWORD(GetMessagePos ());
197 msg
.pt
.y
= (short)HIWORD(GetMessagePos ());
199 SendMessageW (hwndTip
, TTM_RELAYEVENT
, 0, (LPARAM
)&msg
);
203 TAB_DumpItemExternalT(const TCITEMW
*pti
, UINT iItem
, BOOL isW
)
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
));
214 TAB_DumpItemInternal(const TAB_INFO
*infoPtr
, UINT iItem
)
217 TAB_ITEM
*ti
= TAB_GetItem(infoPtr
, iItem
);
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
);
227 * the index of the selected tab, or -1 if no tab is selected. */
228 static inline LRESULT
TAB_GetCurSel (const TAB_INFO
*infoPtr
)
230 TRACE("(%p)\n", infoPtr
);
231 return infoPtr
->iSelected
;
235 * the index of the tab item that has the focus. */
236 static inline LRESULT
237 TAB_GetCurFocus (const TAB_INFO
*infoPtr
)
239 TRACE("(%p)\n", infoPtr
);
240 return infoPtr
->uFocus
;
243 static inline LRESULT
TAB_GetToolTips (const TAB_INFO
*infoPtr
)
245 TRACE("(%p)\n", infoPtr
);
246 return (LRESULT
)infoPtr
->hwndToolTip
;
249 static inline LRESULT
TAB_SetCurSel (TAB_INFO
*infoPtr
, INT iItem
)
251 INT prevItem
= infoPtr
->iSelected
;
253 TRACE("(%p %d)\n", infoPtr
, iItem
);
255 if (iItem
>= (INT
)infoPtr
->uNumItem
)
258 if (prevItem
!= iItem
) {
260 TAB_GetItem(infoPtr
, prevItem
)->dwState
&= ~TCIS_BUTTONPRESSED
;
264 TAB_GetItem(infoPtr
, iItem
)->dwState
|= TCIS_BUTTONPRESSED
;
265 infoPtr
->iSelected
= iItem
;
266 infoPtr
->uFocus
= iItem
;
270 infoPtr
->iSelected
= -1;
271 infoPtr
->uFocus
= -1;
274 TAB_EnsureSelectionVisible(infoPtr
);
275 TAB_InvalidateTabArea(infoPtr
);
281 static LRESULT
TAB_SetCurFocus (TAB_INFO
*infoPtr
, INT iItem
)
283 TRACE("(%p %d)\n", infoPtr
, iItem
);
286 infoPtr
->uFocus
= -1;
287 if (infoPtr
->iSelected
!= -1) {
288 infoPtr
->iSelected
= -1;
289 TAB_SendSimpleNotify(infoPtr
, TCN_SELCHANGE
);
290 TAB_InvalidateTabArea(infoPtr
);
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
;
300 infoPtr
->uFocus
= iItem
;
302 if (prev_focus
!= infoPtr
->iSelected
) {
303 if (TAB_InternalGetItemRect(infoPtr
, prev_focus
, &r
, NULL
))
304 InvalidateRect(infoPtr
->hwnd
, &r
, FALSE
);
307 if (TAB_InternalGetItemRect(infoPtr
, iItem
, &r
, NULL
))
308 InvalidateRect(infoPtr
->hwnd
, &r
, FALSE
);
310 TAB_SendSimpleNotify(infoPtr
, TCN_FOCUSCHANGE
);
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
);
322 infoPtr
->iSelected
= iItem
;
323 TAB_EnsureSelectionVisible(infoPtr
);
324 TAB_InvalidateTabArea(infoPtr
);
332 static inline LRESULT
333 TAB_SetToolTips (TAB_INFO
*infoPtr
, HWND hwndToolTip
)
335 TRACE("%p %p\n", infoPtr
, hwndToolTip
);
336 infoPtr
->hwndToolTip
= hwndToolTip
;
340 static inline LRESULT
341 TAB_SetPadding (TAB_INFO
*infoPtr
, LPARAM lParam
)
343 TRACE("(%p %d %d)\n", infoPtr
, LOWORD(lParam
), HIWORD(lParam
));
344 infoPtr
->uHItemPadding_s
= LOWORD(lParam
);
345 infoPtr
->uVItemPadding_s
= HIWORD(lParam
);
350 /******************************************************************************
351 * TAB_InternalGetItemRect
353 * This method will calculate the rectangle representing a given tab item in
354 * client coordinates. This method takes scrolling into account.
356 * This method returns TRUE if the item is visible in the window and FALSE
357 * if it is completely outside the client area.
359 static BOOL
TAB_InternalGetItemRect(
360 const TAB_INFO
* infoPtr
,
365 RECT tmpItemRect
,clientRect
;
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
)))
373 TRACE("Not Visible\n");
374 SetRect(itemRect
, 0, 0, 0, infoPtr
->tabHeight
);
375 SetRectEmpty(selectedRect
);
380 * Avoid special cases in this procedure by assigning the "out"
381 * parameters if the caller didn't supply them
383 if (itemRect
== NULL
)
384 itemRect
= &tmpItemRect
;
386 /* Retrieve the unmodified item rect. */
387 *itemRect
= TAB_GetItem(infoPtr
,itemIndex
)->rect
;
389 /* calculate the times bottom and top based on the row */
390 GetClientRect(infoPtr
->hwnd
, &clientRect
);
392 if ((infoPtr
->dwStyle
& TCS_BOTTOM
) && (infoPtr
->dwStyle
& TCS_VERTICAL
))
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
;
398 else if (infoPtr
->dwStyle
& TCS_VERTICAL
)
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
;
404 else if (infoPtr
->dwStyle
& TCS_BOTTOM
)
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
;
410 else /* not TCS_BOTTOM and not TCS_VERTICAL */
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
;
418 * "scroll" it to make sure the item at the very left of the
419 * tab control is the leftmost visible tab.
421 if(infoPtr
->dwStyle
& TCS_VERTICAL
)
425 -TAB_GetItem(infoPtr
, infoPtr
->leftmostVisible
)->rect
.top
);
428 * Move the rectangle so the first item is slightly offset from
429 * the bottom of the tab control.
433 SELECTED_TAB_OFFSET
);
438 -TAB_GetItem(infoPtr
, infoPtr
->leftmostVisible
)->rect
.left
,
442 * Move the rectangle so the first item is slightly offset from
443 * the left of the tab control.
449 TRACE("item %d tab h=%d, rect=(%s)\n",
450 itemIndex
, infoPtr
->tabHeight
, wine_dbgstr_rect(itemRect
));
452 /* Now, calculate the position of the item as if it were selected. */
453 if (selectedRect
!=NULL
)
455 *selectedRect
= *itemRect
;
457 /* The rectangle of a selected item is a bit wider. */
458 if(infoPtr
->dwStyle
& TCS_VERTICAL
)
459 InflateRect(selectedRect
, 0, SELECTED_TAB_OFFSET
);
461 InflateRect(selectedRect
, SELECTED_TAB_OFFSET
, 0);
463 /* If it also a bit higher. */
464 if ((infoPtr
->dwStyle
& TCS_BOTTOM
) && (infoPtr
->dwStyle
& TCS_VERTICAL
))
466 selectedRect
->left
-= 2; /* the border is thicker on the right */
467 selectedRect
->right
+= SELECTED_TAB_OFFSET
;
469 else if (infoPtr
->dwStyle
& TCS_VERTICAL
)
471 selectedRect
->left
-= SELECTED_TAB_OFFSET
;
472 selectedRect
->right
+= 1;
474 else if (infoPtr
->dwStyle
& TCS_BOTTOM
)
476 selectedRect
->bottom
+= SELECTED_TAB_OFFSET
;
478 else /* not TCS_BOTTOM and not TCS_VERTICAL */
480 selectedRect
->top
-= SELECTED_TAB_OFFSET
;
481 selectedRect
->bottom
-= 1;
485 /* Check for visibility */
486 if (infoPtr
->dwStyle
& TCS_VERTICAL
)
487 return (itemRect
->top
< clientRect
.bottom
) && (itemRect
->bottom
> clientRect
.top
);
489 return (itemRect
->left
< clientRect
.right
) && (itemRect
->right
> clientRect
.left
);
493 TAB_GetItemRect(const TAB_INFO
*infoPtr
, INT item
, RECT
*rect
)
495 TRACE("(%p, %d, %p)\n", infoPtr
, item
, rect
);
496 return TAB_InternalGetItemRect(infoPtr
, item
, rect
, NULL
);
499 /******************************************************************************
502 * This method is called to handle keyboard input
504 static LRESULT
TAB_KeyDown(TAB_INFO
* infoPtr
, WPARAM keyCode
, LPARAM lParam
)
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
;
515 SendMessageW(infoPtr
->hwndNotify
, WM_NOTIFY
, nm
.hdr
.idFrom
, (LPARAM
)&nm
);
520 newItem
= infoPtr
->uFocus
- 1;
523 newItem
= infoPtr
->uFocus
+ 1;
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
);
535 * WM_KILLFOCUS handler
537 static void TAB_KillFocus(TAB_INFO
*infoPtr
)
539 /* clear current focused item back to selected for TCS_BUTTONS */
540 if ((infoPtr
->dwStyle
& TCS_BUTTONS
) && (infoPtr
->uFocus
!= infoPtr
->iSelected
))
544 if (TAB_InternalGetItemRect(infoPtr
, infoPtr
->uFocus
, &r
, NULL
))
545 InvalidateRect(infoPtr
->hwnd
, &r
, FALSE
);
547 infoPtr
->uFocus
= infoPtr
->iSelected
;
551 /******************************************************************************
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.
557 static void TAB_FocusChanging(const TAB_INFO
*infoPtr
)
563 * Get the rectangle for the item.
565 isVisible
= TAB_InternalGetItemRect(infoPtr
,
571 * If the rectangle is not completely invisible, invalidate that
572 * portion of the window.
576 TRACE("invalidate (%s)\n", wine_dbgstr_rect(&selectedRect
));
577 InvalidateRect(infoPtr
->hwnd
, &selectedRect
, TRUE
);
581 static INT
TAB_InternalHitTest (const TAB_INFO
*infoPtr
, POINT pt
, UINT
*flags
)
586 for (iCount
= 0; iCount
< infoPtr
->uNumItem
; iCount
++)
588 TAB_InternalGetItemRect(infoPtr
, iCount
, &rect
, NULL
);
590 if (PtInRect(&rect
, pt
))
592 *flags
= TCHT_ONITEM
;
597 *flags
= TCHT_NOWHERE
;
601 static inline LRESULT
602 TAB_HitTest (const TAB_INFO
*infoPtr
, LPTCHITTESTINFO lptest
)
604 TRACE("(%p, %p)\n", infoPtr
, lptest
);
605 return TAB_InternalHitTest (infoPtr
, lptest
->pt
, &lptest
->flags
);
608 /******************************************************************************
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
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 ?
620 static inline LRESULT
621 TAB_NCHitTest (const TAB_INFO
*infoPtr
, LPARAM lParam
)
626 pt
.x
= (short)LOWORD(lParam
);
627 pt
.y
= (short)HIWORD(lParam
);
628 ScreenToClient(infoPtr
->hwnd
, &pt
);
630 if (TAB_InternalHitTest(infoPtr
, pt
, &dummyflag
) == -1)
631 return HTTRANSPARENT
;
637 TAB_LButtonDown (TAB_INFO
*infoPtr
, WPARAM wParam
, LPARAM lParam
)
643 if (infoPtr
->hwndToolTip
)
644 TAB_RelayEvent (infoPtr
->hwndToolTip
, infoPtr
->hwnd
,
645 WM_LBUTTONDOWN
, wParam
, lParam
);
647 if (!(infoPtr
->dwStyle
& TCS_FOCUSNEVER
)) {
648 SetFocus (infoPtr
->hwnd
);
651 if (infoPtr
->hwndToolTip
)
652 TAB_RelayEvent (infoPtr
->hwndToolTip
, infoPtr
->hwnd
,
653 WM_LBUTTONDOWN
, wParam
, lParam
);
655 pt
.x
= (short)LOWORD(lParam
);
656 pt
.y
= (short)HIWORD(lParam
);
658 newItem
= TAB_InternalHitTest (infoPtr
, pt
, &dummy
);
660 TRACE("On Tab, item %d\n", newItem
);
662 if ((newItem
!= -1) && (infoPtr
->iSelected
!= newItem
))
664 if ((infoPtr
->dwStyle
& TCS_BUTTONS
) && (infoPtr
->dwStyle
& TCS_MULTISELECT
) &&
665 (wParam
& MK_CONTROL
))
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
);
677 BOOL pressed
= FALSE
;
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
))
688 if (TAB_SendSimpleNotify(infoPtr
, TCN_SELCHANGING
))
692 TAB_DeselectAll (infoPtr
, FALSE
);
694 TAB_SetCurSel(infoPtr
, newItem
);
696 TAB_SendSimpleNotify(infoPtr
, TCN_SELCHANGE
);
703 static inline LRESULT
704 TAB_LButtonUp (const TAB_INFO
*infoPtr
)
706 TAB_SendSimpleNotify(infoPtr
, NM_CLICK
);
712 TAB_RButtonUp (const TAB_INFO
*infoPtr
)
714 TAB_SendSimpleNotify(infoPtr
, NM_RCLICK
);
717 /******************************************************************************
718 * TAB_DrawLoneItemInterior
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.
726 TAB_DrawLoneItemInterior(const TAB_INFO
* infoPtr
, int iItem
)
728 HDC hdc
= GetDC(infoPtr
->hwnd
);
731 /* Clip UpDown control to not draw over it */
732 if (infoPtr
->needsScrolling
)
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
);
738 TAB_DrawItemInterior(infoPtr
, hdc
, iItem
, NULL
);
739 ReleaseDC(infoPtr
->hwnd
, hdc
);
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
)
746 if (tabIndex
== -1) return;
748 if (GetWindowTheme (infoPtr
->hwnd
))
751 TAB_InternalGetItemRect(infoPtr
, tabIndex
, &rect
, NULL
);
752 InvalidateRect (infoPtr
->hwnd
, &rect
, FALSE
);
755 TAB_DrawLoneItemInterior(infoPtr
, tabIndex
);
758 /******************************************************************************
759 * TAB_HotTrackTimerProc
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.
770 TAB_HotTrackTimerProc
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 */
778 TAB_INFO
* infoPtr
= TAB_GetInfoPtr(hwnd
);
780 if (infoPtr
!= NULL
&& infoPtr
->iHotTracked
>= 0)
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.
792 if (!GetCursorPos(&pt
) || WindowFromPoint(pt
) != hwnd
)
794 /* Redraw iHotTracked to look normal */
795 INT iRedraw
= infoPtr
->iHotTracked
;
796 infoPtr
->iHotTracked
= -1;
797 hottrack_refresh (infoPtr
, iRedraw
);
799 /* Kill this timer */
800 KillTimer(hwnd
, TAB_HOTTRACK_TIMER
);
805 /******************************************************************************
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.
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
826 int* out_redrawLeave
,
833 if (out_redrawLeave
!= NULL
)
834 *out_redrawLeave
= -1;
835 if (out_redrawEnter
!= NULL
)
836 *out_redrawEnter
= -1;
838 if ((infoPtr
->dwStyle
& TCS_HOTTRACK
) || GetWindowTheme(infoPtr
->hwnd
))
846 ScreenToClient(infoPtr
->hwnd
, &pt
);
850 pt
.x
= (short)LOWORD(*pos
);
851 pt
.y
= (short)HIWORD(*pos
);
854 item
= TAB_InternalHitTest(infoPtr
, pt
, &flags
);
857 if (item
!= infoPtr
->iHotTracked
)
859 if (infoPtr
->iHotTracked
>= 0)
861 /* Mark currently hot-tracked to be redrawn to look normal */
862 if (out_redrawLeave
!= NULL
)
863 *out_redrawLeave
= infoPtr
->iHotTracked
;
867 /* Kill timer which forces recheck of mouse pos */
868 KillTimer(infoPtr
->hwnd
, TAB_HOTTRACK_TIMER
);
873 /* Start timer so we recheck mouse pos */
874 UINT timerID
= SetTimer
878 TAB_HOTTRACK_TIMER_INTERVAL
,
879 TAB_HotTrackTimerProc
883 return; /* Hot tracking not available */
886 infoPtr
->iHotTracked
= item
;
890 /* Mark new hot-tracked to be redrawn to look highlighted */
891 if (out_redrawEnter
!= NULL
)
892 *out_redrawEnter
= item
;
897 /******************************************************************************
900 * Handles the mouse-move event. Updates tooltips. Updates hot-tracking.
903 TAB_MouseMove (TAB_INFO
*infoPtr
, WPARAM wParam
, LPARAM lParam
)
908 if (infoPtr
->hwndToolTip
)
909 TAB_RelayEvent (infoPtr
->hwndToolTip
, infoPtr
->hwnd
,
910 WM_LBUTTONDOWN
, wParam
, lParam
);
912 /* Determine which tab to highlight. Redraw tabs which change highlight
914 TAB_RecalcHotTrack(infoPtr
, &lParam
, &redrawLeave
, &redrawEnter
);
916 hottrack_refresh (infoPtr
, redrawLeave
);
917 hottrack_refresh (infoPtr
, redrawEnter
);
922 /******************************************************************************
925 * Calculates the tab control's display area given the window rectangle or
926 * the window rectangle given the requested display rectangle.
928 static LRESULT
TAB_AdjustRect(const TAB_INFO
*infoPtr
, WPARAM fLarger
, LPRECT prc
)
930 LONG
*iRightBottom
, *iLeftTop
;
932 TRACE ("hwnd=%p fLarger=%ld (%s)\n", infoPtr
->hwnd
, fLarger
,
933 wine_dbgstr_rect(prc
));
937 if(infoPtr
->dwStyle
& TCS_VERTICAL
)
939 iRightBottom
= &(prc
->right
);
940 iLeftTop
= &(prc
->left
);
944 iRightBottom
= &(prc
->bottom
);
945 iLeftTop
= &(prc
->top
);
948 if (fLarger
) /* Go from display rectangle */
950 /* Add the height of the tabs. */
951 if (infoPtr
->dwStyle
& TCS_BOTTOM
)
952 *iRightBottom
+= infoPtr
->tabHeight
* infoPtr
->uNumRows
;
954 *iLeftTop
-= infoPtr
->tabHeight
* infoPtr
->uNumRows
+
955 ((infoPtr
->dwStyle
& TCS_BUTTONS
)? 3 * (infoPtr
->uNumRows
- 1) : 0);
957 /* Inflate the rectangle for the padding */
958 InflateRect(prc
, DISPLAY_AREA_PADDINGX
, DISPLAY_AREA_PADDINGY
);
960 /* Inflate for the border */
961 InflateRect(prc
, CONTROL_BORDER_SIZEX
, CONTROL_BORDER_SIZEY
);
963 else /* Go from window rectangle. */
965 /* Deflate the rectangle for the border */
966 InflateRect(prc
, -CONTROL_BORDER_SIZEX
, -CONTROL_BORDER_SIZEY
);
968 /* Deflate the rectangle for the padding */
969 InflateRect(prc
, -DISPLAY_AREA_PADDINGX
, -DISPLAY_AREA_PADDINGY
);
971 /* Remove the height of the tabs. */
972 if (infoPtr
->dwStyle
& TCS_BOTTOM
)
973 *iRightBottom
-= infoPtr
->tabHeight
* infoPtr
->uNumRows
;
975 *iLeftTop
+= (infoPtr
->tabHeight
) * infoPtr
->uNumRows
+
976 ((infoPtr
->dwStyle
& TCS_BUTTONS
)? 3 * (infoPtr
->uNumRows
- 1) : 0);
982 /******************************************************************************
985 * This method will handle the notification from the scroll control and
986 * perform the scrolling operation on the tab control.
988 static LRESULT
TAB_OnHScroll(TAB_INFO
*infoPtr
, int nScrollCode
, int nPos
)
990 if(nScrollCode
== SB_THUMBPOSITION
&& nPos
!= infoPtr
->leftmostVisible
)
992 if(nPos
< infoPtr
->leftmostVisible
)
993 infoPtr
->leftmostVisible
--;
995 infoPtr
->leftmostVisible
++;
997 TAB_RecalcHotTrack(infoPtr
, NULL
, NULL
, NULL
);
998 TAB_InvalidateTabArea(infoPtr
);
999 SendMessageW(infoPtr
->hwndUpDown
, UDM_SETPOS
, 0,
1000 MAKELONG(infoPtr
->leftmostVisible
, 0));
1006 /******************************************************************************
1007 * TAB_SetupScrolling
1009 * This method will check the current scrolling state and make sure the
1010 * scrolling control is displayed (or not).
1012 static void TAB_SetupScrolling(
1014 const RECT
* clientRect
)
1018 if (infoPtr
->needsScrolling
)
1021 INT vsize
, tabwidth
;
1024 * Calculate the position of the scroll control.
1026 controlPos
.right
= clientRect
->right
;
1027 controlPos
.left
= controlPos
.right
- 2 * GetSystemMetrics(SM_CXHSCROLL
);
1029 if (infoPtr
->dwStyle
& TCS_BOTTOM
)
1031 controlPos
.top
= clientRect
->bottom
- infoPtr
->tabHeight
;
1032 controlPos
.bottom
= controlPos
.top
+ GetSystemMetrics(SM_CYHSCROLL
);
1036 controlPos
.bottom
= clientRect
->top
+ infoPtr
->tabHeight
;
1037 controlPos
.top
= controlPos
.bottom
- GetSystemMetrics(SM_CYHSCROLL
);
1041 * If we don't have a scroll control yet, we want to create one.
1042 * If we have one, we want to make sure it's positioned properly.
1044 if (infoPtr
->hwndUpDown
==0)
1046 infoPtr
->hwndUpDown
= CreateWindowW(UPDOWN_CLASSW
, L
"",
1047 WS_VISIBLE
| WS_CHILD
| UDS_HORZ
,
1048 controlPos
.left
, controlPos
.top
,
1049 controlPos
.right
- controlPos
.left
,
1050 controlPos
.bottom
- controlPos
.top
,
1051 infoPtr
->hwnd
, NULL
, NULL
, NULL
);
1055 SetWindowPos(infoPtr
->hwndUpDown
,
1057 controlPos
.left
, controlPos
.top
,
1058 controlPos
.right
- controlPos
.left
,
1059 controlPos
.bottom
- controlPos
.top
,
1060 SWP_SHOWWINDOW
| SWP_NOZORDER
);
1063 /* Now calculate upper limit of the updown control range.
1064 * We do this by calculating how many tabs will be offscreen when the
1065 * last tab is visible.
1067 if(infoPtr
->uNumItem
)
1069 vsize
= clientRect
->right
- (controlPos
.right
- controlPos
.left
+ 1);
1070 maxRange
= infoPtr
->uNumItem
;
1071 tabwidth
= TAB_GetItem(infoPtr
, infoPtr
->uNumItem
- 1)->rect
.right
;
1073 for(; maxRange
> 0; maxRange
--)
1075 if(tabwidth
- TAB_GetItem(infoPtr
,maxRange
- 1)->rect
.left
> vsize
)
1079 if(maxRange
== infoPtr
->uNumItem
)
1085 /* If we once had a scroll control... hide it */
1086 if (infoPtr
->hwndUpDown
)
1087 ShowWindow(infoPtr
->hwndUpDown
, SW_HIDE
);
1089 if (infoPtr
->hwndUpDown
)
1090 SendMessageW(infoPtr
->hwndUpDown
, UDM_SETRANGE32
, 0, maxRange
);
1093 /******************************************************************************
1096 * This method will calculate the position rectangles of all the items in the
1097 * control. The rectangle calculated starts at 0 for the first item in the
1098 * list and ignores scrolling and selection.
1099 * It also uses the current font to determine the height of the tab row and
1100 * it checks if all the tabs fit in the client area of the window. If they
1101 * don't, a scrolling control is added.
1103 static void TAB_SetItemBounds (TAB_INFO
*infoPtr
)
1105 TEXTMETRICW fontMetrics
;
1108 INT curItemRowCount
;
1109 HFONT hFont
, hOldFont
;
1118 * We need to get text information so we need a DC and we need to select
1121 hdc
= GetDC(infoPtr
->hwnd
);
1123 hFont
= infoPtr
->hFont
? infoPtr
->hFont
: GetStockObject (SYSTEM_FONT
);
1124 hOldFont
= SelectObject (hdc
, hFont
);
1127 * We will base the rectangle calculations on the client rectangle
1130 GetClientRect(infoPtr
->hwnd
, &clientRect
);
1132 /* if TCS_VERTICAL then swap the height and width so this code places the
1133 tabs along the top of the rectangle and we can just rotate them after
1134 rather than duplicate all of the below code */
1135 if(infoPtr
->dwStyle
& TCS_VERTICAL
)
1137 iTemp
= clientRect
.bottom
;
1138 clientRect
.bottom
= clientRect
.right
;
1139 clientRect
.right
= iTemp
;
1142 /* Now use hPadding and vPadding */
1143 infoPtr
->uHItemPadding
= infoPtr
->uHItemPadding_s
;
1144 infoPtr
->uVItemPadding
= infoPtr
->uVItemPadding_s
;
1146 /* The leftmost item will be "0" aligned */
1148 curItemRowCount
= infoPtr
->uNumItem
? 1 : 0;
1150 if (!(infoPtr
->fHeightSet
))
1153 INT icon_height
= 0, cx
;
1155 /* Use the current font to determine the height of a tab. */
1156 GetTextMetricsW(hdc
, &fontMetrics
);
1158 /* Get the icon height */
1160 ImageList_GetIconSize(infoPtr
->himl
, &cx
, &icon_height
);
1162 /* Take the highest between font or icon */
1163 if (fontMetrics
.tmHeight
> icon_height
)
1164 item_height
= fontMetrics
.tmHeight
+ 2;
1166 item_height
= icon_height
;
1169 * Make sure there is enough space for the letters + icon + growing the
1170 * selected item + extra space for the selected item.
1172 infoPtr
->tabHeight
= item_height
+
1173 ((infoPtr
->dwStyle
& TCS_BUTTONS
) ? 2 : 1) *
1174 infoPtr
->uVItemPadding
;
1176 TRACE("tabH=%d, tmH=%d, iconh=%d\n",
1177 infoPtr
->tabHeight
, fontMetrics
.tmHeight
, icon_height
);
1180 TRACE("client right=%d\n", clientRect
.right
);
1182 /* Get the icon width */
1187 ImageList_GetIconSize(infoPtr
->himl
, &icon_width
, &cy
);
1189 if (infoPtr
->dwStyle
& TCS_FIXEDWIDTH
)
1192 /* Add padding if icon is present */
1193 icon_width
+= infoPtr
->uHItemPadding
;
1196 for (curItem
= 0; curItem
< infoPtr
->uNumItem
; curItem
++)
1198 TAB_ITEM
*curr
= TAB_GetItem(infoPtr
, curItem
);
1200 /* Set the leftmost position of the tab. */
1201 curr
->rect
.left
= curItemLeftPos
;
1203 if (infoPtr
->dwStyle
& TCS_FIXEDWIDTH
)
1205 curr
->rect
.right
= curr
->rect
.left
+
1206 max(infoPtr
->tabWidth
, icon_width
);
1208 else if (!curr
->pszText
)
1210 /* If no text use minimum tab width including padding. */
1211 if (infoPtr
->tabMinWidth
< 0)
1212 curr
->rect
.right
= curr
->rect
.left
+ GET_DEFAULT_MIN_TAB_WIDTH(infoPtr
);
1215 curr
->rect
.right
= curr
->rect
.left
+ infoPtr
->tabMinWidth
;
1217 /* Add extra padding if icon is present */
1218 if (infoPtr
->himl
&& infoPtr
->tabMinWidth
> 0 && infoPtr
->tabMinWidth
< DEFAULT_MIN_TAB_WIDTH
1219 && infoPtr
->uHItemPadding
> 1)
1220 curr
->rect
.right
+= EXTRA_ICON_PADDING
* (infoPtr
->uHItemPadding
-1);
1227 /* Calculate how wide the tab is depending on the text it contains */
1228 GetTextExtentPoint32W(hdc
, curr
->pszText
,
1229 lstrlenW(curr
->pszText
), &size
);
1231 tabwidth
= size
.cx
+ icon_width
+ 2 * infoPtr
->uHItemPadding
;
1233 if (infoPtr
->tabMinWidth
< 0)
1234 tabwidth
= max(tabwidth
, GET_DEFAULT_MIN_TAB_WIDTH(infoPtr
));
1236 tabwidth
= max(tabwidth
, infoPtr
->tabMinWidth
);
1238 curr
->rect
.right
= curr
->rect
.left
+ tabwidth
;
1239 TRACE("for <%s>, rect %s\n", debugstr_w(curr
->pszText
), wine_dbgstr_rect(&curr
->rect
));
1243 * Check if this is a multiline tab control and if so
1244 * check to see if we should wrap the tabs
1246 * Wrap all these tabs. We will arrange them evenly later.
1250 if (((infoPtr
->dwStyle
& TCS_MULTILINE
) || (infoPtr
->dwStyle
& TCS_VERTICAL
)) &&
1252 (clientRect
.right
- CONTROL_BORDER_SIZEX
- DISPLAY_AREA_PADDINGX
)))
1254 curr
->rect
.right
-= curr
->rect
.left
;
1256 curr
->rect
.left
= 0;
1258 TRACE("wrapping <%s>, rect %s\n", debugstr_w(curr
->pszText
), wine_dbgstr_rect(&curr
->rect
));
1261 curr
->rect
.bottom
= 0;
1262 curr
->rect
.top
= curItemRowCount
- 1;
1264 TRACE("Rect: %s\n", wine_dbgstr_rect(&curr
->rect
));
1267 * The leftmost position of the next item is the rightmost position
1270 if (infoPtr
->dwStyle
& TCS_BUTTONS
)
1272 curItemLeftPos
= curr
->rect
.right
+ BUTTON_SPACINGX
;
1273 if (infoPtr
->dwStyle
& TCS_FLATBUTTONS
)
1274 curItemLeftPos
+= FLAT_BTN_SPACINGX
;
1277 curItemLeftPos
= curr
->rect
.right
;
1280 if (!((infoPtr
->dwStyle
& TCS_MULTILINE
) || (infoPtr
->dwStyle
& TCS_VERTICAL
)))
1283 * Check if we need a scrolling control.
1285 infoPtr
->needsScrolling
= (curItemLeftPos
+ (2 * SELECTED_TAB_OFFSET
) >
1288 /* Don't need scrolling, then update infoPtr->leftmostVisible */
1289 if(!infoPtr
->needsScrolling
)
1290 infoPtr
->leftmostVisible
= 0;
1295 * No scrolling in Multiline or Vertical styles.
1297 infoPtr
->needsScrolling
= FALSE
;
1298 infoPtr
->leftmostVisible
= 0;
1300 TAB_SetupScrolling(infoPtr
, &clientRect
);
1302 /* Set the number of rows */
1303 infoPtr
->uNumRows
= curItemRowCount
;
1305 /* Arrange all tabs evenly if style says so */
1306 if (!(infoPtr
->dwStyle
& TCS_RAGGEDRIGHT
) &&
1307 ((infoPtr
->dwStyle
& TCS_MULTILINE
) || (infoPtr
->dwStyle
& TCS_VERTICAL
)) &&
1308 (infoPtr
->uNumItem
> 0) &&
1309 (infoPtr
->uNumRows
> 1))
1311 INT tabPerRow
,remTab
,iRow
;
1316 * Ok windows tries to even out the rows. place the same
1317 * number of tabs in each row. So lets give that a shot
1320 tabPerRow
= infoPtr
->uNumItem
/ (infoPtr
->uNumRows
);
1321 remTab
= infoPtr
->uNumItem
% (infoPtr
->uNumRows
);
1323 for (iItm
=0,iRow
=0,iCount
=0,curItemLeftPos
=0;
1324 iItm
<infoPtr
->uNumItem
;
1327 /* normalize the current rect */
1328 TAB_ITEM
*curr
= TAB_GetItem(infoPtr
, iItm
);
1330 /* shift the item to the left side of the clientRect */
1331 curr
->rect
.right
-= curr
->rect
.left
;
1332 curr
->rect
.left
= 0;
1334 TRACE("r=%d, cl=%d, cl.r=%d, iCount=%d, iRow=%d, uNumRows=%d, remTab=%d, tabPerRow=%d\n",
1335 curr
->rect
.right
, curItemLeftPos
, clientRect
.right
,
1336 iCount
, iRow
, infoPtr
->uNumRows
, remTab
, tabPerRow
);
1338 /* if we have reached the maximum number of tabs on this row */
1339 /* move to the next row, reset our current item left position and */
1340 /* the count of items on this row */
1342 if (infoPtr
->dwStyle
& TCS_VERTICAL
) {
1343 /* Vert: Add the remaining tabs in the *last* remainder rows */
1344 if (iCount
>= ((iRow
>=(INT
)infoPtr
->uNumRows
- remTab
)?tabPerRow
+ 1:tabPerRow
)) {
1350 /* Horz: Add the remaining tabs in the *first* remainder rows */
1351 if (iCount
>= ((iRow
<remTab
)?tabPerRow
+ 1:tabPerRow
)) {
1358 /* shift the item to the right to place it as the next item in this row */
1359 curr
->rect
.left
+= curItemLeftPos
;
1360 curr
->rect
.right
+= curItemLeftPos
;
1361 curr
->rect
.top
= iRow
;
1362 if (infoPtr
->dwStyle
& TCS_BUTTONS
)
1364 curItemLeftPos
= curr
->rect
.right
+ 1;
1365 if (infoPtr
->dwStyle
& TCS_FLATBUTTONS
)
1366 curItemLeftPos
+= FLAT_BTN_SPACINGX
;
1369 curItemLeftPos
= curr
->rect
.right
;
1371 TRACE("arranging <%s>, rect %s\n", debugstr_w(curr
->pszText
), wine_dbgstr_rect(&curr
->rect
));
1378 INT widthDiff
, iIndexStart
=0, iIndexEnd
=0;
1382 while(iIndexStart
< infoPtr
->uNumItem
)
1384 TAB_ITEM
*start
= TAB_GetItem(infoPtr
, iIndexStart
);
1387 * find the index of the row
1389 /* find the first item on the next row */
1390 for (iIndexEnd
=iIndexStart
;
1391 (iIndexEnd
< infoPtr
->uNumItem
) &&
1392 (TAB_GetItem(infoPtr
, iIndexEnd
)->rect
.top
==
1395 /* intentionally blank */;
1398 * we need to justify these tabs so they fill the whole given
1402 /* find the amount of space remaining on this row */
1403 widthDiff
= clientRect
.right
- (2 * SELECTED_TAB_OFFSET
) -
1404 TAB_GetItem(infoPtr
, iIndexEnd
- 1)->rect
.right
;
1406 /* iCount is the number of tab items on this row */
1407 iCount
= iIndexEnd
- iIndexStart
;
1411 remainder
= widthDiff
% iCount
;
1412 widthDiff
= widthDiff
/ iCount
;
1413 /* add widthDiff/iCount, or extra space/items on row, to each item on this row */
1414 for (iIndex
=iIndexStart
, iCount
=0; iIndex
< iIndexEnd
; iIndex
++, iCount
++)
1416 TAB_ITEM
*item
= TAB_GetItem(infoPtr
, iIndex
);
1418 item
->rect
.left
+= iCount
* widthDiff
;
1419 item
->rect
.right
+= (iCount
+ 1) * widthDiff
;
1421 TRACE("adjusting 1 <%s>, rect %s\n", debugstr_w(item
->pszText
), wine_dbgstr_rect(&item
->rect
));
1424 TAB_GetItem(infoPtr
, iIndex
- 1)->rect
.right
+= remainder
;
1426 else /* we have only one item on this row, make it take up the entire row */
1428 start
->rect
.left
= clientRect
.left
;
1429 start
->rect
.right
= clientRect
.right
- 4;
1431 TRACE("adjusting 2 <%s>, rect %s\n", debugstr_w(start
->pszText
), wine_dbgstr_rect(&start
->rect
));
1434 iIndexStart
= iIndexEnd
;
1439 /* if TCS_VERTICAL rotate the tabs so they are along the side of the clientRect */
1440 if(infoPtr
->dwStyle
& TCS_VERTICAL
)
1443 for(iIndex
= 0; iIndex
< infoPtr
->uNumItem
; iIndex
++)
1445 rcItem
= &TAB_GetItem(infoPtr
, iIndex
)->rect
;
1447 rcOriginal
= *rcItem
;
1449 /* this is rotating the items by 90 degrees clockwise around the center of the control */
1450 rcItem
->top
= (rcOriginal
.left
- clientRect
.left
);
1451 rcItem
->bottom
= rcItem
->top
+ (rcOriginal
.right
- rcOriginal
.left
);
1452 rcItem
->left
= rcOriginal
.top
;
1453 rcItem
->right
= rcOriginal
.bottom
;
1457 TAB_EnsureSelectionVisible(infoPtr
);
1458 TAB_RecalcHotTrack(infoPtr
, NULL
, NULL
, NULL
);
1461 SelectObject (hdc
, hOldFont
);
1462 ReleaseDC (infoPtr
->hwnd
, hdc
);
1467 TAB_EraseTabInterior(const TAB_INFO
*infoPtr
, HDC hdc
, INT iItem
, const RECT
*drawRect
)
1469 HBRUSH hbr
= CreateSolidBrush (comctl32_color
.clrBtnFace
);
1470 BOOL deleteBrush
= TRUE
;
1471 RECT rTemp
= *drawRect
;
1473 if (infoPtr
->dwStyle
& TCS_BUTTONS
)
1475 if (iItem
== infoPtr
->iSelected
)
1477 /* Background color */
1478 if (!(infoPtr
->dwStyle
& TCS_OWNERDRAWFIXED
))
1481 hbr
= GetSysColorBrush(COLOR_SCROLLBAR
);
1483 SetTextColor(hdc
, comctl32_color
.clr3dFace
);
1484 SetBkColor(hdc
, comctl32_color
.clr3dHilight
);
1486 /* if COLOR_WINDOW happens to be the same as COLOR_3DHILIGHT
1487 * we better use 0x55aa bitmap brush to make scrollbar's background
1488 * look different from the window background.
1490 if (comctl32_color
.clr3dHilight
== comctl32_color
.clrWindow
)
1491 hbr
= COMCTL32_hPattern55AABrush
;
1493 deleteBrush
= FALSE
;
1495 FillRect(hdc
, &rTemp
, hbr
);
1497 else /* ! selected */
1499 if (infoPtr
->dwStyle
& TCS_FLATBUTTONS
)
1501 InflateRect(&rTemp
, 2, 2);
1502 FillRect(hdc
, &rTemp
, hbr
);
1503 if (iItem
== infoPtr
->iHotTracked
||
1504 (iItem
!= infoPtr
->iSelected
&& iItem
== infoPtr
->uFocus
))
1505 DrawEdge(hdc
, &rTemp
, BDR_RAISEDINNER
, BF_RECT
);
1508 FillRect(hdc
, &rTemp
, hbr
);
1512 else /* !TCS_BUTTONS */
1514 InflateRect(&rTemp
, -2, -2);
1515 if (!GetWindowTheme (infoPtr
->hwnd
))
1516 FillRect(hdc
, &rTemp
, hbr
);
1519 /* highlighting is drawn on top of previous fills */
1520 if (TAB_GetItem(infoPtr
, iItem
)->dwState
& TCIS_HIGHLIGHTED
)
1525 deleteBrush
= FALSE
;
1527 hbr
= GetSysColorBrush(COLOR_HIGHLIGHT
);
1528 FillRect(hdc
, &rTemp
, hbr
);
1532 if (deleteBrush
) DeleteObject(hbr
);
1535 /******************************************************************************
1536 * TAB_DrawItemInterior
1538 * This method is used to draw the interior (text and icon) of a single tab
1539 * into the tab control.
1542 TAB_DrawItemInterior(const TAB_INFO
*infoPtr
, HDC hdc
, INT iItem
, RECT
*drawRect
)
1551 /* if (drawRect == NULL) */
1558 * Get the rectangle for the item.
1560 isVisible
= TAB_InternalGetItemRect(infoPtr
, iItem
, &itemRect
, &selectedRect
);
1565 * Make sure drawRect points to something valid; simplifies code.
1567 drawRect
= &localRect
;
1570 * This logic copied from the part of TAB_DrawItem which draws
1571 * the tab background. It's important to keep it in sync. I
1572 * would have liked to avoid code duplication, but couldn't figure
1573 * out how without making spaghetti of TAB_DrawItem.
1575 if (iItem
== infoPtr
->iSelected
)
1576 *drawRect
= selectedRect
;
1578 *drawRect
= itemRect
;
1580 if (infoPtr
->dwStyle
& TCS_BUTTONS
)
1582 if (iItem
== infoPtr
->iSelected
)
1584 drawRect
->left
+= 4;
1586 drawRect
->right
-= 4;
1588 if (infoPtr
->dwStyle
& TCS_VERTICAL
)
1590 if (!(infoPtr
->dwStyle
& TCS_BOTTOM
)) drawRect
->right
+= 1;
1591 drawRect
->bottom
-= 4;
1595 if (infoPtr
->dwStyle
& TCS_BOTTOM
)
1598 drawRect
->bottom
-= 4;
1601 drawRect
->bottom
-= 1;
1605 InflateRect(drawRect
, -2, -2);
1609 if ((infoPtr
->dwStyle
& TCS_VERTICAL
) && (infoPtr
->dwStyle
& TCS_BOTTOM
))
1611 if (iItem
!= infoPtr
->iSelected
)
1613 drawRect
->left
+= 2;
1614 InflateRect(drawRect
, 0, -2);
1617 else if (infoPtr
->dwStyle
& TCS_VERTICAL
)
1619 if (iItem
== infoPtr
->iSelected
)
1621 drawRect
->right
+= 1;
1625 drawRect
->right
-= 2;
1626 InflateRect(drawRect
, 0, -2);
1629 else if (infoPtr
->dwStyle
& TCS_BOTTOM
)
1631 if (iItem
== infoPtr
->iSelected
)
1637 InflateRect(drawRect
, -2, -2);
1638 drawRect
->bottom
+= 2;
1643 if (iItem
== infoPtr
->iSelected
)
1645 drawRect
->bottom
+= 3;
1649 drawRect
->bottom
-= 2;
1650 InflateRect(drawRect
, -2, 0);
1655 TRACE("drawRect=(%s)\n", wine_dbgstr_rect(drawRect
));
1657 /* Clear interior */
1658 TAB_EraseTabInterior (infoPtr
, hdc
, iItem
, drawRect
);
1660 /* Draw the focus rectangle */
1661 if (!(infoPtr
->dwStyle
& TCS_FOCUSNEVER
) &&
1662 (GetFocus() == infoPtr
->hwnd
) &&
1663 (iItem
== infoPtr
->uFocus
) )
1665 RECT rFocus
= *drawRect
;
1667 if (!(infoPtr
->dwStyle
& TCS_BUTTONS
)) InflateRect(&rFocus
, -3, -3);
1668 if (infoPtr
->dwStyle
& TCS_BOTTOM
&& !(infoPtr
->dwStyle
& TCS_VERTICAL
))
1671 /* focus should stay on selected item for TCS_BUTTONS style */
1672 if (!((infoPtr
->dwStyle
& TCS_BUTTONS
) && (infoPtr
->iSelected
!= iItem
)))
1673 DrawFocusRect(hdc
, &rFocus
);
1679 htextPen
= CreatePen( PS_SOLID
, 1, comctl32_color
.clrBtnText
);
1680 holdPen
= SelectObject(hdc
, htextPen
);
1681 hOldFont
= SelectObject(hdc
, infoPtr
->hFont
);
1684 * Setup for text output
1686 oldBkMode
= SetBkMode(hdc
, TRANSPARENT
);
1687 if (!GetWindowTheme (infoPtr
->hwnd
) || (infoPtr
->dwStyle
& TCS_BUTTONS
))
1689 if ((infoPtr
->dwStyle
& TCS_HOTTRACK
) && (iItem
== infoPtr
->iHotTracked
) &&
1690 !(infoPtr
->dwStyle
& TCS_FLATBUTTONS
))
1691 SetTextColor(hdc
, comctl32_color
.clrHighlight
);
1692 else if (TAB_GetItem(infoPtr
, iItem
)->dwState
& TCIS_HIGHLIGHTED
)
1693 SetTextColor(hdc
, comctl32_color
.clrHighlightText
);
1695 SetTextColor(hdc
, comctl32_color
.clrBtnText
);
1699 * if owner draw, tell the owner to draw
1701 if ((infoPtr
->dwStyle
& TCS_OWNERDRAWFIXED
) && IsWindow(infoPtr
->hwndNotify
))
1707 drawRect
->right
-= 1;
1708 if ( iItem
== infoPtr
->iSelected
)
1709 InflateRect(drawRect
, -1, 0);
1711 id
= (UINT
)GetWindowLongPtrW( infoPtr
->hwnd
, GWLP_ID
);
1713 /* fill DRAWITEMSTRUCT */
1714 dis
.CtlType
= ODT_TAB
;
1717 dis
.itemAction
= ODA_DRAWENTIRE
;
1719 if ( iItem
== infoPtr
->iSelected
)
1720 dis
.itemState
|= ODS_SELECTED
;
1721 if (infoPtr
->uFocus
== iItem
)
1722 dis
.itemState
|= ODS_FOCUS
;
1723 dis
.hwndItem
= infoPtr
->hwnd
;
1725 dis
.rcItem
= *drawRect
;
1727 /* when extra data fits ULONG_PTR, store it directly */
1728 if (infoPtr
->cbInfo
> sizeof(LPARAM
))
1729 dis
.itemData
= (ULONG_PTR
) TAB_GetItem(infoPtr
, iItem
)->extra
;
1732 /* this could be considered broken on 64 bit, but that's how it works -
1733 only first 4 bytes are copied */
1735 memcpy(&dis
.itemData
, (ULONG_PTR
*)TAB_GetItem(infoPtr
, iItem
)->extra
, 4);
1738 /* draw notification */
1739 SendMessageW( infoPtr
->hwndNotify
, WM_DRAWITEM
, id
, (LPARAM
)&dis
);
1743 TAB_ITEM
*item
= TAB_GetItem(infoPtr
, iItem
);
1747 /* used to center the icon and text in the tab */
1749 INT center_offset_h
, center_offset_v
;
1751 /* set rcImage to drawRect, we will use top & left in our ImageList_Draw call */
1752 rcImage
= *drawRect
;
1755 SetRectEmpty(&rcText
);
1757 /* get the rectangle that the text fits in */
1760 DrawTextW(hdc
, item
->pszText
, -1, &rcText
, DT_CALCRECT
);
1763 * If not owner draw, then do the drawing ourselves.
1767 if (infoPtr
->himl
&& item
->iImage
!= -1)
1772 ImageList_GetIconSize(infoPtr
->himl
, &cx
, &cy
);
1774 if(infoPtr
->dwStyle
& TCS_VERTICAL
)
1776 center_offset_h
= ((drawRect
->bottom
- drawRect
->top
) - (cy
+ infoPtr
->uHItemPadding
+ (rcText
.right
- rcText
.left
))) / 2;
1777 center_offset_v
= ((drawRect
->right
- drawRect
->left
) - cx
) / 2;
1781 center_offset_h
= ((drawRect
->right
- drawRect
->left
) - (cx
+ infoPtr
->uHItemPadding
+ (rcText
.right
- rcText
.left
))) / 2;
1782 center_offset_v
= ((drawRect
->bottom
- drawRect
->top
) - cy
) / 2;
1785 /* if an item is selected, the icon is shifted up instead of down */
1786 if (iItem
== infoPtr
->iSelected
)
1787 center_offset_v
-= infoPtr
->uVItemPadding
/ 2;
1789 center_offset_v
+= infoPtr
->uVItemPadding
/ 2;
1791 if (infoPtr
->dwStyle
& TCS_FIXEDWIDTH
&& infoPtr
->dwStyle
& (TCS_FORCELABELLEFT
| TCS_FORCEICONLEFT
))
1792 center_offset_h
= infoPtr
->uHItemPadding
;
1794 if (center_offset_h
< 2)
1795 center_offset_h
= 2;
1797 if (center_offset_v
< 0)
1798 center_offset_v
= 0;
1800 TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%s), textlen=%d\n",
1801 debugstr_w(item
->pszText
), center_offset_h
, center_offset_v
,
1802 wine_dbgstr_rect(drawRect
), (rcText
.right
-rcText
.left
));
1804 if((infoPtr
->dwStyle
& TCS_VERTICAL
) && (infoPtr
->dwStyle
& TCS_BOTTOM
))
1806 rcImage
.top
= drawRect
->top
+ center_offset_h
;
1807 /* if tab is TCS_VERTICAL and TCS_BOTTOM, the text is drawn from the */
1808 /* right side of the tab, but the image still uses the left as its x position */
1809 /* this keeps the image always drawn off of the same side of the tab */
1810 rcImage
.left
= drawRect
->right
- cx
- center_offset_v
;
1811 drawRect
->top
+= cy
+ infoPtr
->uHItemPadding
;
1813 else if(infoPtr
->dwStyle
& TCS_VERTICAL
)
1815 rcImage
.top
= drawRect
->bottom
- cy
- center_offset_h
;
1816 rcImage
.left
= drawRect
->left
+ center_offset_v
;
1817 drawRect
->bottom
-= cy
+ infoPtr
->uHItemPadding
;
1819 else /* normal style, whether TCS_BOTTOM or not */
1821 rcImage
.left
= drawRect
->left
+ center_offset_h
;
1822 rcImage
.top
= drawRect
->top
+ center_offset_v
;
1823 drawRect
->left
+= cx
+ infoPtr
->uHItemPadding
;
1826 TRACE("drawing image=%d, left=%d, top=%d\n",
1827 item
->iImage
, rcImage
.left
, rcImage
.top
-1);
1839 /* Now position text */
1840 if (infoPtr
->dwStyle
& TCS_FIXEDWIDTH
&& infoPtr
->dwStyle
& TCS_FORCELABELLEFT
)
1841 center_offset_h
= infoPtr
->uHItemPadding
;
1843 if(infoPtr
->dwStyle
& TCS_VERTICAL
)
1844 center_offset_h
= ((drawRect
->bottom
- drawRect
->top
) - (rcText
.right
- rcText
.left
)) / 2;
1846 center_offset_h
= ((drawRect
->right
- drawRect
->left
) - (rcText
.right
- rcText
.left
)) / 2;
1848 if(infoPtr
->dwStyle
& TCS_VERTICAL
)
1850 if(infoPtr
->dwStyle
& TCS_BOTTOM
)
1851 drawRect
->top
+=center_offset_h
;
1853 drawRect
->bottom
-=center_offset_h
;
1855 center_offset_v
= ((drawRect
->right
- drawRect
->left
) - (rcText
.bottom
- rcText
.top
)) / 2;
1859 drawRect
->left
+= center_offset_h
;
1860 center_offset_v
= ((drawRect
->bottom
- drawRect
->top
) - (rcText
.bottom
- rcText
.top
)) / 2;
1863 /* if an item is selected, the text is shifted up instead of down */
1864 if (iItem
== infoPtr
->iSelected
)
1865 center_offset_v
-= infoPtr
->uVItemPadding
/ 2;
1867 center_offset_v
+= infoPtr
->uVItemPadding
/ 2;
1869 if (center_offset_v
< 0)
1870 center_offset_v
= 0;
1872 if(infoPtr
->dwStyle
& TCS_VERTICAL
)
1873 drawRect
->left
+= center_offset_v
;
1875 drawRect
->top
+= center_offset_v
;
1878 if(infoPtr
->dwStyle
& TCS_VERTICAL
) /* if we are vertical rotate the text and each character */
1882 INT nEscapement
= 900;
1883 INT nOrientation
= 900;
1885 if(infoPtr
->dwStyle
& TCS_BOTTOM
)
1888 nOrientation
= -900;
1891 /* to get a font with the escapement and orientation we are looking for, we need to */
1892 /* call CreateFontIndirect, which requires us to set the values of the logfont we pass in */
1893 if (!GetObjectW(infoPtr
->hFont
, sizeof(logfont
), &logfont
))
1894 GetObjectW(GetStockObject(DEFAULT_GUI_FONT
), sizeof(logfont
), &logfont
);
1896 logfont
.lfEscapement
= nEscapement
;
1897 logfont
.lfOrientation
= nOrientation
;
1898 hFont
= CreateFontIndirectW(&logfont
);
1899 SelectObject(hdc
, hFont
);
1904 (infoPtr
->dwStyle
& TCS_BOTTOM
) ? drawRect
->right
: drawRect
->left
,
1905 (!(infoPtr
->dwStyle
& TCS_BOTTOM
)) ? drawRect
->bottom
: drawRect
->top
,
1909 lstrlenW(item
->pszText
),
1913 DeleteObject(hFont
);
1917 TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%s), textlen=%d\n",
1918 debugstr_w(item
->pszText
), center_offset_h
, center_offset_v
,
1919 wine_dbgstr_rect(drawRect
), (rcText
.right
-rcText
.left
));
1926 lstrlenW(item
->pszText
),
1928 DT_LEFT
| DT_SINGLELINE
1933 *drawRect
= rcTemp
; /* restore drawRect */
1939 SelectObject(hdc
, hOldFont
);
1940 SetBkMode(hdc
, oldBkMode
);
1941 SelectObject(hdc
, holdPen
);
1942 DeleteObject( htextPen
);
1945 /******************************************************************************
1948 * This method is used to draw a single tab into the tab control.
1950 static void TAB_DrawItem(const TAB_INFO
*infoPtr
, HDC hdc
, INT iItem
)
1955 RECT r
, fillRect
, r1
;
1958 COLORREF bkgnd
, corner
;
1962 * Get the rectangle for the item.
1964 isVisible
= TAB_InternalGetItemRect(infoPtr
,
1973 /* Clip UpDown control to not draw over it */
1974 if (infoPtr
->needsScrolling
)
1976 GetWindowRect(infoPtr
->hwnd
, &rC
);
1977 GetWindowRect(infoPtr
->hwndUpDown
, &rUD
);
1978 ExcludeClipRect(hdc
, rUD
.left
- rC
.left
, rUD
.top
- rC
.top
, rUD
.right
- rC
.left
, rUD
.bottom
- rC
.top
);
1981 /* If you need to see what the control is doing,
1982 * then override these variables. They will change what
1983 * fill colors are used for filling the tabs, and the
1984 * corners when drawing the edge.
1986 bkgnd
= comctl32_color
.clrBtnFace
;
1987 corner
= comctl32_color
.clrBtnFace
;
1989 if (infoPtr
->dwStyle
& TCS_BUTTONS
)
1991 /* Get item rectangle */
1994 /* Separators between flat buttons */
1995 if ((infoPtr
->dwStyle
& TCS_FLATBUTTONS
) && (infoPtr
->exStyle
& TCS_EX_FLATSEPARATORS
))
1998 r1
.right
+= (FLAT_BTN_SPACINGX
-2);
1999 DrawEdge(hdc
, &r1
, EDGE_ETCHED
, BF_RIGHT
);
2002 if (iItem
== infoPtr
->iSelected
)
2004 DrawEdge(hdc
, &r
, EDGE_SUNKEN
, BF_SOFT
|BF_RECT
);
2006 OffsetRect(&r
, 1, 1);
2008 else /* ! selected */
2010 DWORD state
= TAB_GetItem(infoPtr
, iItem
)->dwState
;
2012 if ((state
& TCIS_BUTTONPRESSED
) || (iItem
== infoPtr
->uFocus
))
2013 DrawEdge(hdc
, &r
, EDGE_SUNKEN
, BF_SOFT
|BF_RECT
);
2015 if (!(infoPtr
->dwStyle
& TCS_FLATBUTTONS
))
2016 DrawEdge(hdc
, &r
, EDGE_RAISED
, BF_SOFT
|BF_RECT
);
2019 else /* !TCS_BUTTONS */
2021 /* We draw a rectangle of different sizes depending on the selection
2023 if (iItem
== infoPtr
->iSelected
) {
2025 GetClientRect (infoPtr
->hwnd
, &rect
);
2026 clRight
= rect
.right
;
2027 clBottom
= rect
.bottom
;
2034 * Erase the background. (Delay it but setup rectangle.)
2035 * This is necessary when drawing the selected item since it is larger
2036 * than the others, it might overlap with stuff already drawn by the
2041 /* Draw themed tabs - but only if they are at the top.
2042 * Windows draws even side or bottom tabs themed, with wacky results.
2043 * However, since in Wine apps may get themed that did not opt in via
2044 * a manifest avoid theming when we know the result will be wrong */
2045 if ((theme
= GetWindowTheme (infoPtr
->hwnd
))
2046 && ((infoPtr
->dwStyle
& (TCS_VERTICAL
| TCS_BOTTOM
)) == 0))
2048 static const int partIds
[8] = {
2051 TABP_TABITEMLEFTEDGE
,
2052 TABP_TABITEMRIGHTEDGE
,
2053 TABP_TABITEMBOTHEDGE
,
2056 TABP_TOPTABITEMLEFTEDGE
,
2057 TABP_TOPTABITEMRIGHTEDGE
,
2058 TABP_TOPTABITEMBOTHEDGE
,
2061 int stateId
= TIS_NORMAL
;
2063 /* selected and unselected tabs have different parts */
2064 if (iItem
== infoPtr
->iSelected
)
2066 /* The part also differs on the position of a tab on a line.
2067 * "Visually" determining the position works well enough. */
2068 GetClientRect(infoPtr
->hwnd
, &r1
);
2069 if(selectedRect
.left
== 0)
2071 if(selectedRect
.right
== r1
.right
)
2074 if (iItem
== infoPtr
->iSelected
)
2075 stateId
= TIS_SELECTED
;
2076 else if (iItem
== infoPtr
->iHotTracked
)
2078 else if (iItem
== infoPtr
->uFocus
)
2079 stateId
= TIS_FOCUSED
;
2081 /* Adjust rectangle for bottommost row */
2082 if (TAB_GetItem(infoPtr
, iItem
)->rect
.top
== infoPtr
->uNumRows
-1)
2085 DrawThemeBackground (theme
, hdc
, partIds
[partIndex
], stateId
, &r
, NULL
);
2086 GetThemeBackgroundContentRect (theme
, hdc
, partIds
[partIndex
], stateId
, &r
, &r
);
2088 else if(infoPtr
->dwStyle
& TCS_VERTICAL
)
2090 /* These are for adjusting the drawing of a Selected tab */
2091 /* The initial values are for the normal case of non-Selected */
2092 int ZZ
= 1; /* Do not stretch if selected */
2093 if (iItem
== infoPtr
->iSelected
) {
2096 /* if leftmost draw the line longer */
2097 if(selectedRect
.top
== 0)
2098 fillRect
.top
+= CONTROL_BORDER_SIZEY
;
2099 /* if rightmost draw the line longer */
2100 if(selectedRect
.bottom
== clBottom
)
2101 fillRect
.bottom
-= CONTROL_BORDER_SIZEY
;
2104 if (infoPtr
->dwStyle
& TCS_BOTTOM
)
2106 /* Adjust both rectangles to match native */
2109 TRACE("<right> item=%d, fill=(%s), edge=(%s)\n",
2110 iItem
, wine_dbgstr_rect(&fillRect
), wine_dbgstr_rect(&r
));
2112 /* Clear interior */
2113 SetBkColor(hdc
, bkgnd
);
2114 ExtTextOutW(hdc
, 0, 0, 2, &fillRect
, NULL
, 0, 0);
2116 /* Draw rectangular edge around tab */
2117 DrawEdge(hdc
, &r
, EDGE_RAISED
, BF_SOFT
|BF_RIGHT
|BF_TOP
|BF_BOTTOM
);
2119 /* Now erase the top corner and draw diagonal edge */
2120 SetBkColor(hdc
, corner
);
2121 r1
.left
= r
.right
- ROUND_CORNER_SIZE
- 1;
2124 r1
.bottom
= r1
.top
+ ROUND_CORNER_SIZE
;
2125 ExtTextOutW(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2127 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_DIAGONAL_ENDTOPLEFT
);
2129 /* Now erase the bottom corner and draw diagonal edge */
2130 r1
.left
= r
.right
- ROUND_CORNER_SIZE
- 1;
2131 r1
.bottom
= r
.bottom
;
2133 r1
.top
= r1
.bottom
- ROUND_CORNER_SIZE
;
2134 ExtTextOutW(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2136 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_DIAGONAL_ENDBOTTOMLEFT
);
2138 if ((iItem
== infoPtr
->iSelected
) && (selectedRect
.top
== 0)) {
2142 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_TOP
);
2148 TRACE("<left> item=%d, fill=(%s), edge=(%s)\n",
2149 iItem
, wine_dbgstr_rect(&fillRect
), wine_dbgstr_rect(&r
));
2151 /* Clear interior */
2152 SetBkColor(hdc
, bkgnd
);
2153 ExtTextOutW(hdc
, 0, 0, 2, &fillRect
, NULL
, 0, 0);
2155 /* Draw rectangular edge around tab */
2156 DrawEdge(hdc
, &r
, EDGE_RAISED
, BF_SOFT
|BF_LEFT
|BF_TOP
|BF_BOTTOM
);
2158 /* Now erase the top corner and draw diagonal edge */
2159 SetBkColor(hdc
, corner
);
2162 r1
.right
= r1
.left
+ ROUND_CORNER_SIZE
+ 1;
2163 r1
.bottom
= r1
.top
+ ROUND_CORNER_SIZE
;
2164 ExtTextOutW(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2166 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_DIAGONAL_ENDTOPRIGHT
);
2168 /* Now erase the bottom corner and draw diagonal edge */
2170 r1
.bottom
= r
.bottom
;
2171 r1
.right
= r1
.left
+ ROUND_CORNER_SIZE
+ 1;
2172 r1
.top
= r1
.bottom
- ROUND_CORNER_SIZE
;
2173 ExtTextOutW(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2175 DrawEdge(hdc
, &r1
, EDGE_SUNKEN
, BF_DIAGONAL_ENDTOPLEFT
);
2178 else /* ! TCS_VERTICAL */
2180 /* These are for adjusting the drawing of a Selected tab */
2181 /* The initial values are for the normal case of non-Selected */
2182 if (iItem
== infoPtr
->iSelected
) {
2183 /* if leftmost draw the line longer */
2184 if(selectedRect
.left
== 0)
2185 fillRect
.left
+= CONTROL_BORDER_SIZEX
;
2186 /* if rightmost draw the line longer */
2187 if(selectedRect
.right
== clRight
)
2188 fillRect
.right
-= CONTROL_BORDER_SIZEX
;
2191 if (infoPtr
->dwStyle
& TCS_BOTTOM
)
2193 /* Adjust both rectangles for topmost row */
2194 if (TAB_GetItem(infoPtr
, iItem
)->rect
.top
== infoPtr
->uNumRows
-1)
2200 TRACE("<bottom> item=%d, fill=(%s), edge=(%s)\n",
2201 iItem
, wine_dbgstr_rect(&fillRect
), wine_dbgstr_rect(&r
));
2203 /* Clear interior */
2204 SetBkColor(hdc
, bkgnd
);
2205 ExtTextOutW(hdc
, 0, 0, 2, &fillRect
, NULL
, 0, 0);
2207 /* Draw rectangular edge around tab */
2208 DrawEdge(hdc
, &r
, EDGE_RAISED
, BF_SOFT
|BF_LEFT
|BF_BOTTOM
|BF_RIGHT
);
2210 /* Now erase the righthand corner and draw diagonal edge */
2211 SetBkColor(hdc
, corner
);
2212 r1
.left
= r
.right
- ROUND_CORNER_SIZE
;
2213 r1
.bottom
= r
.bottom
;
2215 r1
.top
= r1
.bottom
- ROUND_CORNER_SIZE
- 1;
2216 ExtTextOutW(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2218 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_DIAGONAL_ENDBOTTOMLEFT
);
2220 /* Now erase the lefthand corner and draw diagonal edge */
2222 r1
.bottom
= r
.bottom
;
2223 r1
.right
= r1
.left
+ ROUND_CORNER_SIZE
;
2224 r1
.top
= r1
.bottom
- ROUND_CORNER_SIZE
- 1;
2225 ExtTextOutW(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2227 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_DIAGONAL_ENDTOPLEFT
);
2229 if (iItem
== infoPtr
->iSelected
)
2233 if (selectedRect
.left
== 0)
2238 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_LEFT
);
2245 /* Adjust both rectangles for bottommost row */
2246 if (TAB_GetItem(infoPtr
, iItem
)->rect
.top
== infoPtr
->uNumRows
-1)
2248 fillRect
.bottom
+= 3;
2252 TRACE("<top> item=%d, fill=(%s), edge=(%s)\n",
2253 iItem
, wine_dbgstr_rect(&fillRect
), wine_dbgstr_rect(&r
));
2255 /* Clear interior */
2256 SetBkColor(hdc
, bkgnd
);
2257 ExtTextOutW(hdc
, 0, 0, 2, &fillRect
, NULL
, 0, 0);
2259 /* Draw rectangular edge around tab */
2260 DrawEdge(hdc
, &r
, EDGE_RAISED
, BF_SOFT
|BF_LEFT
|BF_TOP
|BF_RIGHT
);
2262 /* Now erase the righthand corner and draw diagonal edge */
2263 SetBkColor(hdc
, corner
);
2264 r1
.left
= r
.right
- ROUND_CORNER_SIZE
;
2267 r1
.bottom
= r1
.top
+ ROUND_CORNER_SIZE
+ 1;
2268 ExtTextOutW(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2270 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_DIAGONAL_ENDBOTTOMRIGHT
);
2272 /* Now erase the lefthand corner and draw diagonal edge */
2275 r1
.right
= r1
.left
+ ROUND_CORNER_SIZE
;
2276 r1
.bottom
= r1
.top
+ ROUND_CORNER_SIZE
+ 1;
2277 ExtTextOutW(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2279 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_DIAGONAL_ENDTOPRIGHT
);
2284 TAB_DumpItemInternal(infoPtr
, iItem
);
2286 /* This modifies r to be the text rectangle. */
2287 TAB_DrawItemInterior(infoPtr
, hdc
, iItem
, &r
);
2291 /******************************************************************************
2294 * This method is used to draw the raised border around the tab control
2297 static void TAB_DrawBorder(const TAB_INFO
*infoPtr
, HDC hdc
)
2300 HTHEME theme
= GetWindowTheme (infoPtr
->hwnd
);
2302 GetClientRect (infoPtr
->hwnd
, &rect
);
2305 * Adjust for the style
2308 if (infoPtr
->uNumItem
)
2310 if ((infoPtr
->dwStyle
& TCS_BOTTOM
) && !(infoPtr
->dwStyle
& TCS_VERTICAL
))
2311 rect
.bottom
-= infoPtr
->tabHeight
* infoPtr
->uNumRows
+ CONTROL_BORDER_SIZEX
;
2312 else if((infoPtr
->dwStyle
& TCS_BOTTOM
) && (infoPtr
->dwStyle
& TCS_VERTICAL
))
2313 rect
.right
-= infoPtr
->tabHeight
* infoPtr
->uNumRows
+ CONTROL_BORDER_SIZEX
;
2314 else if(infoPtr
->dwStyle
& TCS_VERTICAL
)
2315 rect
.left
+= infoPtr
->tabHeight
* infoPtr
->uNumRows
+ CONTROL_BORDER_SIZEX
;
2316 else /* not TCS_VERTICAL and not TCS_BOTTOM */
2317 rect
.top
+= infoPtr
->tabHeight
* infoPtr
->uNumRows
+ CONTROL_BORDER_SIZEX
;
2320 TRACE("border=(%s)\n", wine_dbgstr_rect(&rect
));
2323 DrawThemeBackground (theme
, hdc
, TABP_PANE
, 0, &rect
, NULL
);
2325 DrawEdge(hdc
, &rect
, EDGE_RAISED
, BF_SOFT
|BF_RECT
);
2328 /******************************************************************************
2331 * This method repaints the tab control..
2333 static void TAB_Refresh (const TAB_INFO
*infoPtr
, HDC hdc
)
2338 if (!infoPtr
->DoRedraw
)
2341 hOldFont
= SelectObject (hdc
, infoPtr
->hFont
);
2343 if (infoPtr
->dwStyle
& TCS_BUTTONS
)
2345 for (i
= 0; i
< infoPtr
->uNumItem
; i
++)
2346 TAB_DrawItem (infoPtr
, hdc
, i
);
2350 /* Draw all the non selected item first */
2351 for (i
= 0; i
< infoPtr
->uNumItem
; i
++)
2353 if (i
!= infoPtr
->iSelected
)
2354 TAB_DrawItem (infoPtr
, hdc
, i
);
2357 /* Now, draw the border, draw it before the selected item
2358 * since the selected item overwrites part of the border. */
2359 TAB_DrawBorder (infoPtr
, hdc
);
2361 /* Then, draw the selected item */
2362 TAB_DrawItem (infoPtr
, hdc
, infoPtr
->iSelected
);
2365 SelectObject (hdc
, hOldFont
);
2368 static inline DWORD
TAB_GetRowCount (const TAB_INFO
*infoPtr
)
2370 TRACE("(%p)\n", infoPtr
);
2371 return infoPtr
->uNumRows
;
2374 static inline LRESULT
TAB_SetRedraw (TAB_INFO
*infoPtr
, BOOL doRedraw
)
2376 infoPtr
->DoRedraw
= doRedraw
;
2380 /******************************************************************************
2381 * TAB_EnsureSelectionVisible
2383 * This method will make sure that the current selection is completely
2384 * visible by scrolling until it is.
2386 static void TAB_EnsureSelectionVisible(
2389 INT iSelected
= infoPtr
->iSelected
;
2390 INT iOrigLeftmostVisible
= infoPtr
->leftmostVisible
;
2395 /* set the items row to the bottommost row or topmost row depending on
2397 if ((infoPtr
->uNumRows
> 1) && !(infoPtr
->dwStyle
& TCS_BUTTONS
))
2399 TAB_ITEM
*selected
= TAB_GetItem(infoPtr
, iSelected
);
2403 if(infoPtr
->dwStyle
& TCS_VERTICAL
)
2404 newselected
= selected
->rect
.left
;
2406 newselected
= selected
->rect
.top
;
2408 /* the target row is always (number of rows - 1)
2409 as row 0 is furthest from the clientRect */
2410 iTargetRow
= infoPtr
->uNumRows
- 1;
2412 if (newselected
!= iTargetRow
)
2415 if(infoPtr
->dwStyle
& TCS_VERTICAL
)
2417 for (i
=0; i
< infoPtr
->uNumItem
; i
++)
2419 /* move everything in the row of the selected item to the iTargetRow */
2420 TAB_ITEM
*item
= TAB_GetItem(infoPtr
, i
);
2422 if (item
->rect
.left
== newselected
)
2423 item
->rect
.left
= iTargetRow
;
2426 if (item
->rect
.left
> newselected
)
2433 for (i
=0; i
< infoPtr
->uNumItem
; i
++)
2435 TAB_ITEM
*item
= TAB_GetItem(infoPtr
, i
);
2437 if (item
->rect
.top
== newselected
)
2438 item
->rect
.top
= iTargetRow
;
2441 if (item
->rect
.top
> newselected
)
2446 TAB_RecalcHotTrack(infoPtr
, NULL
, NULL
, NULL
);
2451 * Do the trivial cases first.
2453 if ( (!infoPtr
->needsScrolling
) ||
2454 (infoPtr
->hwndUpDown
==0) || (infoPtr
->dwStyle
& TCS_VERTICAL
))
2457 if (infoPtr
->leftmostVisible
>= iSelected
)
2459 infoPtr
->leftmostVisible
= iSelected
;
2463 TAB_ITEM
*selected
= TAB_GetItem(infoPtr
, iSelected
);
2468 /* Calculate the part of the client area that is visible */
2469 GetClientRect(infoPtr
->hwnd
, &r
);
2472 GetClientRect(infoPtr
->hwndUpDown
, &r
);
2475 if ((selected
->rect
.right
-
2476 selected
->rect
.left
) >= width
)
2478 /* Special case: width of selected item is greater than visible
2481 infoPtr
->leftmostVisible
= iSelected
;
2485 for (i
= infoPtr
->leftmostVisible
; i
< infoPtr
->uNumItem
; i
++)
2487 if ((selected
->rect
.right
- TAB_GetItem(infoPtr
, i
)->rect
.left
) < width
)
2490 infoPtr
->leftmostVisible
= i
;
2494 if (infoPtr
->leftmostVisible
!= iOrigLeftmostVisible
)
2495 TAB_RecalcHotTrack(infoPtr
, NULL
, NULL
, NULL
);
2497 SendMessageW(infoPtr
->hwndUpDown
, UDM_SETPOS
, 0,
2498 MAKELONG(infoPtr
->leftmostVisible
, 0));
2501 /******************************************************************************
2502 * TAB_InvalidateTabArea
2504 * This method will invalidate the portion of the control that contains the
2505 * tabs. It is called when the state of the control changes and needs
2508 static void TAB_InvalidateTabArea(const TAB_INFO
*infoPtr
)
2510 RECT clientRect
, rInvalidate
, rAdjClient
;
2511 INT lastRow
= infoPtr
->uNumRows
- 1;
2514 if (lastRow
< 0) return;
2516 GetClientRect(infoPtr
->hwnd
, &clientRect
);
2517 rInvalidate
= clientRect
;
2518 rAdjClient
= clientRect
;
2520 TAB_AdjustRect(infoPtr
, 0, &rAdjClient
);
2522 TAB_InternalGetItemRect(infoPtr
, infoPtr
->uNumItem
-1 , &rect
, NULL
);
2523 if ((infoPtr
->dwStyle
& TCS_BOTTOM
) && (infoPtr
->dwStyle
& TCS_VERTICAL
))
2525 rInvalidate
.left
= rAdjClient
.right
;
2526 if (infoPtr
->uNumRows
== 1)
2527 rInvalidate
.bottom
= clientRect
.top
+ rect
.bottom
+ 2 * SELECTED_TAB_OFFSET
;
2529 else if(infoPtr
->dwStyle
& TCS_VERTICAL
)
2531 rInvalidate
.right
= rAdjClient
.left
;
2532 if (infoPtr
->uNumRows
== 1)
2533 rInvalidate
.bottom
= clientRect
.top
+ rect
.bottom
+ 2 * SELECTED_TAB_OFFSET
;
2535 else if (infoPtr
->dwStyle
& TCS_BOTTOM
)
2537 rInvalidate
.top
= rAdjClient
.bottom
;
2538 if (infoPtr
->uNumRows
== 1)
2539 rInvalidate
.right
= clientRect
.left
+ rect
.right
+ 2 * SELECTED_TAB_OFFSET
;
2543 rInvalidate
.bottom
= rAdjClient
.top
;
2544 if (infoPtr
->uNumRows
== 1)
2545 rInvalidate
.right
= clientRect
.left
+ rect
.right
+ 2 * SELECTED_TAB_OFFSET
;
2548 /* Punch out the updown control */
2549 if (infoPtr
->needsScrolling
&& (rInvalidate
.right
> 0)) {
2551 GetClientRect(infoPtr
->hwndUpDown
, &r
);
2552 if (rInvalidate
.right
> clientRect
.right
- r
.left
)
2553 rInvalidate
.right
= rInvalidate
.right
- (r
.right
- r
.left
);
2555 rInvalidate
.right
= clientRect
.right
- r
.left
;
2558 TRACE("invalidate (%s)\n", wine_dbgstr_rect(&rInvalidate
));
2560 InvalidateRect(infoPtr
->hwnd
, &rInvalidate
, TRUE
);
2563 static inline LRESULT
TAB_Paint (TAB_INFO
*infoPtr
, HDC hdcPaint
)
2572 hdc
= BeginPaint (infoPtr
->hwnd
, &ps
);
2573 TRACE("erase %d, rect=(%s)\n", ps
.fErase
, wine_dbgstr_rect(&ps
.rcPaint
));
2576 TAB_Refresh (infoPtr
, hdc
);
2579 EndPaint (infoPtr
->hwnd
, &ps
);
2585 TAB_InsertItemT (TAB_INFO
*infoPtr
, INT iItem
, const TCITEMW
*pti
, BOOL bUnicode
)
2590 GetClientRect (infoPtr
->hwnd
, &rect
);
2591 TRACE("Rect: %p %s\n", infoPtr
->hwnd
, wine_dbgstr_rect(&rect
));
2593 if (iItem
< 0) return -1;
2594 if (iItem
> infoPtr
->uNumItem
)
2595 iItem
= infoPtr
->uNumItem
;
2597 TAB_DumpItemExternalT(pti
, iItem
, bUnicode
);
2599 if (!(item
= Alloc(TAB_ITEM_SIZE(infoPtr
)))) return FALSE
;
2600 if (DPA_InsertPtr(infoPtr
->items
, iItem
, item
) == -1)
2606 if (infoPtr
->uNumItem
== 0)
2607 infoPtr
->iSelected
= 0;
2608 else if (iItem
<= infoPtr
->iSelected
)
2609 infoPtr
->iSelected
++;
2611 infoPtr
->uNumItem
++;
2613 item
->pszText
= NULL
;
2614 if (pti
->mask
& TCIF_TEXT
)
2617 Str_SetPtrW (&item
->pszText
, pti
->pszText
);
2619 Str_SetPtrAtoW (&item
->pszText
, (LPSTR
)pti
->pszText
);
2622 if (pti
->mask
& TCIF_IMAGE
)
2623 item
->iImage
= pti
->iImage
;
2627 if (pti
->mask
& TCIF_PARAM
)
2628 memcpy(item
->extra
, &pti
->lParam
, EXTRA_ITEM_SIZE(infoPtr
));
2630 memset(item
->extra
, 0, EXTRA_ITEM_SIZE(infoPtr
));
2632 TAB_SetItemBounds(infoPtr
);
2633 if (infoPtr
->uNumItem
> 1)
2634 TAB_InvalidateTabArea(infoPtr
);
2636 InvalidateRect(infoPtr
->hwnd
, NULL
, TRUE
);
2638 TRACE("[%p]: added item %d %s\n",
2639 infoPtr
->hwnd
, iItem
, debugstr_w(item
->pszText
));
2641 /* If we haven't set the current focus yet, set it now. */
2642 if (infoPtr
->uFocus
== -1)
2643 TAB_SetCurFocus(infoPtr
, iItem
);
2649 TAB_SetItemSize (TAB_INFO
*infoPtr
, INT cx
, INT cy
)
2652 BOOL bNeedPaint
= FALSE
;
2654 lResult
= MAKELONG(infoPtr
->tabWidth
, infoPtr
->tabHeight
);
2656 /* UNDOCUMENTED: If requested Width or Height is 0 this means that program wants to use auto size. */
2657 if (infoPtr
->dwStyle
& TCS_FIXEDWIDTH
&& (infoPtr
->tabWidth
!= cx
))
2659 infoPtr
->tabWidth
= cx
;
2663 if (infoPtr
->tabHeight
!= cy
)
2665 if ((infoPtr
->fHeightSet
= (cy
!= 0)))
2666 infoPtr
->tabHeight
= cy
;
2670 TRACE("was h=%d,w=%d, now h=%d,w=%d\n",
2671 HIWORD(lResult
), LOWORD(lResult
),
2672 infoPtr
->tabHeight
, infoPtr
->tabWidth
);
2676 TAB_SetItemBounds(infoPtr
);
2677 RedrawWindow(infoPtr
->hwnd
, NULL
, NULL
, RDW_ERASE
| RDW_INVALIDATE
| RDW_UPDATENOW
);
2683 static inline LRESULT
TAB_SetMinTabWidth (TAB_INFO
*infoPtr
, INT cx
)
2687 TRACE("(%p,%d)\n", infoPtr
, cx
);
2689 if (infoPtr
->tabMinWidth
< 0)
2690 oldcx
= DEFAULT_MIN_TAB_WIDTH
;
2692 oldcx
= infoPtr
->tabMinWidth
;
2693 infoPtr
->tabMinWidth
= cx
;
2694 TAB_SetItemBounds(infoPtr
);
2698 static inline LRESULT
2699 TAB_HighlightItem (TAB_INFO
*infoPtr
, INT iItem
, BOOL fHighlight
)
2705 TRACE("(%p,%d,%s)\n", infoPtr
, iItem
, fHighlight
? "true" : "false");
2707 if (iItem
< 0 || iItem
>= infoPtr
->uNumItem
)
2710 lpState
= &TAB_GetItem(infoPtr
, iItem
)->dwState
;
2711 oldState
= *lpState
;
2714 *lpState
|= TCIS_HIGHLIGHTED
;
2716 *lpState
&= ~TCIS_HIGHLIGHTED
;
2718 if ((oldState
!= *lpState
) && TAB_InternalGetItemRect (infoPtr
, iItem
, &r
, NULL
))
2719 InvalidateRect (infoPtr
->hwnd
, &r
, TRUE
);
2725 TAB_SetItemT (TAB_INFO
*infoPtr
, INT iItem
, LPTCITEMW tabItem
, BOOL bUnicode
)
2729 TRACE("(%p,%d,%p,%s)\n", infoPtr
, iItem
, tabItem
, bUnicode
? "true" : "false");
2731 if (iItem
< 0 || iItem
>= infoPtr
->uNumItem
)
2734 TAB_DumpItemExternalT(tabItem
, iItem
, bUnicode
);
2736 wineItem
= TAB_GetItem(infoPtr
, iItem
);
2738 if (tabItem
->mask
& TCIF_IMAGE
)
2739 wineItem
->iImage
= tabItem
->iImage
;
2741 if (tabItem
->mask
& TCIF_PARAM
)
2742 memcpy(wineItem
->extra
, &tabItem
->lParam
, infoPtr
->cbInfo
);
2744 if (tabItem
->mask
& TCIF_RTLREADING
)
2745 FIXME("TCIF_RTLREADING\n");
2747 if (tabItem
->mask
& TCIF_STATE
)
2748 wineItem
->dwState
= (wineItem
->dwState
& ~tabItem
->dwStateMask
) |
2749 ( tabItem
->dwState
& tabItem
->dwStateMask
);
2751 if (tabItem
->mask
& TCIF_TEXT
)
2753 Free(wineItem
->pszText
);
2754 wineItem
->pszText
= NULL
;
2756 Str_SetPtrW(&wineItem
->pszText
, tabItem
->pszText
);
2758 Str_SetPtrAtoW(&wineItem
->pszText
, (LPSTR
)tabItem
->pszText
);
2761 /* Update and repaint tabs */
2762 TAB_SetItemBounds(infoPtr
);
2763 TAB_InvalidateTabArea(infoPtr
);
2768 static inline LRESULT
TAB_GetItemCount (const TAB_INFO
*infoPtr
)
2771 return infoPtr
->uNumItem
;
2776 TAB_GetItemT (TAB_INFO
*infoPtr
, INT iItem
, LPTCITEMW tabItem
, BOOL bUnicode
)
2780 TRACE("(%p,%d,%p,%s)\n", infoPtr
, iItem
, tabItem
, bUnicode
? "true" : "false");
2782 if (!tabItem
) return FALSE
;
2784 if (iItem
< 0 || iItem
>= infoPtr
->uNumItem
)
2786 /* init requested fields */
2787 if (tabItem
->mask
& TCIF_IMAGE
) tabItem
->iImage
= 0;
2788 if (tabItem
->mask
& TCIF_PARAM
) tabItem
->lParam
= 0;
2789 if (tabItem
->mask
& TCIF_STATE
) tabItem
->dwState
= 0;
2793 wineItem
= TAB_GetItem(infoPtr
, iItem
);
2795 if (tabItem
->mask
& TCIF_IMAGE
)
2796 tabItem
->iImage
= wineItem
->iImage
;
2798 if (tabItem
->mask
& TCIF_PARAM
)
2799 memcpy(&tabItem
->lParam
, wineItem
->extra
, infoPtr
->cbInfo
);
2801 if (tabItem
->mask
& TCIF_RTLREADING
)
2802 FIXME("TCIF_RTLREADING\n");
2804 if (tabItem
->mask
& TCIF_STATE
)
2805 tabItem
->dwState
= wineItem
->dwState
& tabItem
->dwStateMask
;
2807 if (tabItem
->mask
& TCIF_TEXT
)
2810 Str_GetPtrW (wineItem
->pszText
, tabItem
->pszText
, tabItem
->cchTextMax
);
2812 Str_GetPtrWtoA (wineItem
->pszText
, (LPSTR
)tabItem
->pszText
, tabItem
->cchTextMax
);
2815 TAB_DumpItemExternalT(tabItem
, iItem
, bUnicode
);
2821 static LRESULT
TAB_DeleteItem (TAB_INFO
*infoPtr
, INT iItem
)
2825 TRACE("(%p, %d)\n", infoPtr
, iItem
);
2827 if (iItem
< 0 || iItem
>= infoPtr
->uNumItem
) return FALSE
;
2829 TAB_InvalidateTabArea(infoPtr
);
2830 item
= TAB_GetItem(infoPtr
, iItem
);
2831 Free(item
->pszText
);
2833 infoPtr
->uNumItem
--;
2834 DPA_DeletePtr(infoPtr
->items
, iItem
);
2836 if (infoPtr
->uNumItem
== 0)
2838 if (infoPtr
->iHotTracked
>= 0)
2840 KillTimer(infoPtr
->hwnd
, TAB_HOTTRACK_TIMER
);
2841 infoPtr
->iHotTracked
= -1;
2844 infoPtr
->iSelected
= -1;
2848 if (iItem
<= infoPtr
->iHotTracked
)
2850 /* When tabs move left/up, the hot track item may change */
2851 FIXME("Recalc hot track\n");
2855 /* adjust the selected index */
2856 if (iItem
== infoPtr
->iSelected
)
2857 infoPtr
->iSelected
= -1;
2858 else if (iItem
< infoPtr
->iSelected
)
2859 infoPtr
->iSelected
--;
2861 /* reposition and repaint tabs */
2862 TAB_SetItemBounds(infoPtr
);
2867 static inline LRESULT
TAB_DeleteAllItems (TAB_INFO
*infoPtr
)
2869 TRACE("(%p)\n", infoPtr
);
2870 while (infoPtr
->uNumItem
)
2871 TAB_DeleteItem (infoPtr
, 0);
2876 static inline LRESULT
TAB_GetFont (const TAB_INFO
*infoPtr
)
2878 TRACE("(%p) returning %p\n", infoPtr
, infoPtr
->hFont
);
2879 return (LRESULT
)infoPtr
->hFont
;
2882 static inline LRESULT
TAB_SetFont (TAB_INFO
*infoPtr
, HFONT hNewFont
)
2884 TRACE("(%p,%p)\n", infoPtr
, hNewFont
);
2886 infoPtr
->hFont
= hNewFont
;
2888 TAB_SetItemBounds(infoPtr
);
2890 TAB_InvalidateTabArea(infoPtr
);
2896 static inline LRESULT
TAB_GetImageList (const TAB_INFO
*infoPtr
)
2899 return (LRESULT
)infoPtr
->himl
;
2902 static inline LRESULT
TAB_SetImageList (TAB_INFO
*infoPtr
, HIMAGELIST himlNew
)
2904 HIMAGELIST himlPrev
= infoPtr
->himl
;
2905 TRACE("himl=%p\n", himlNew
);
2906 infoPtr
->himl
= himlNew
;
2907 TAB_SetItemBounds(infoPtr
);
2908 InvalidateRect(infoPtr
->hwnd
, NULL
, TRUE
);
2909 return (LRESULT
)himlPrev
;
2912 static inline LRESULT
TAB_GetUnicodeFormat (const TAB_INFO
*infoPtr
)
2914 TRACE("(%p)\n", infoPtr
);
2915 return infoPtr
->bUnicode
;
2918 static inline LRESULT
TAB_SetUnicodeFormat (TAB_INFO
*infoPtr
, BOOL bUnicode
)
2920 BOOL bTemp
= infoPtr
->bUnicode
;
2922 TRACE("(%p %d)\n", infoPtr
, bUnicode
);
2923 infoPtr
->bUnicode
= bUnicode
;
2928 static inline LRESULT
TAB_Size (TAB_INFO
*infoPtr
)
2930 /* I'm not really sure what the following code was meant to do.
2931 This is what it is doing:
2932 When WM_SIZE is sent with SIZE_RESTORED, the control
2933 gets positioned in the top left corner.
2937 UINT uPosFlags,cx,cy;
2941 parent = GetParent (hwnd);
2942 GetClientRect(parent, &parent_rect);
2945 if (GetWindowLongW(hwnd, GWL_STYLE) & CCS_NORESIZE)
2946 uPosFlags |= (SWP_NOSIZE | SWP_NOMOVE);
2948 SetWindowPos (hwnd, 0, parent_rect.left, parent_rect.top,
2949 cx, cy, uPosFlags | SWP_NOZORDER);
2951 FIXME("WM_SIZE flag %x %lx not handled\n", wParam, lParam);
2954 /* Recompute the size/position of the tabs. */
2955 TAB_SetItemBounds (infoPtr
);
2957 /* Force a repaint of the control. */
2958 InvalidateRect(infoPtr
->hwnd
, NULL
, TRUE
);
2964 static LRESULT
TAB_Create (HWND hwnd
, LPARAM lParam
)
2967 TEXTMETRICW fontMetrics
;
2972 infoPtr
= Alloc (sizeof(TAB_INFO
));
2974 SetWindowLongPtrW(hwnd
, 0, (DWORD_PTR
)infoPtr
);
2976 infoPtr
->hwnd
= hwnd
;
2977 infoPtr
->hwndNotify
= ((LPCREATESTRUCTW
)lParam
)->hwndParent
;
2978 infoPtr
->uNumItem
= 0;
2979 infoPtr
->uNumRows
= 0;
2980 infoPtr
->uHItemPadding
= 6;
2981 infoPtr
->uVItemPadding
= 3;
2982 infoPtr
->uHItemPadding_s
= 6;
2983 infoPtr
->uVItemPadding_s
= 3;
2985 infoPtr
->items
= DPA_Create(8);
2986 infoPtr
->hcurArrow
= LoadCursorW (0, (LPWSTR
)IDC_ARROW
);
2987 infoPtr
->iSelected
= -1;
2988 infoPtr
->iHotTracked
= -1;
2989 infoPtr
->uFocus
= -1;
2990 infoPtr
->hwndToolTip
= 0;
2991 infoPtr
->DoRedraw
= TRUE
;
2992 infoPtr
->needsScrolling
= FALSE
;
2993 infoPtr
->hwndUpDown
= 0;
2994 infoPtr
->leftmostVisible
= 0;
2995 infoPtr
->fHeightSet
= FALSE
;
2996 infoPtr
->bUnicode
= IsWindowUnicode (hwnd
);
2997 infoPtr
->cbInfo
= sizeof(LPARAM
);
2999 TRACE("Created tab control, hwnd [%p]\n", hwnd
);
3001 /* The tab control always has the WS_CLIPSIBLINGS style. Even
3002 if you don't specify it in CreateWindow. This is necessary in
3003 order for paint to work correctly. This follows windows behaviour. */
3004 style
= GetWindowLongW(hwnd
, GWL_STYLE
);
3005 if (style
& TCS_VERTICAL
) style
|= TCS_MULTILINE
;
3006 style
|= WS_CLIPSIBLINGS
;
3007 SetWindowLongW(hwnd
, GWL_STYLE
, style
);
3009 infoPtr
->dwStyle
= style
;
3010 infoPtr
->exStyle
= (style
& TCS_FLATBUTTONS
) ? TCS_EX_FLATSEPARATORS
: 0;
3012 if (infoPtr
->dwStyle
& TCS_TOOLTIPS
) {
3013 /* Create tooltip control */
3014 infoPtr
->hwndToolTip
=
3015 CreateWindowExW (0, TOOLTIPS_CLASSW
, NULL
, WS_POPUP
,
3016 CW_USEDEFAULT
, CW_USEDEFAULT
,
3017 CW_USEDEFAULT
, CW_USEDEFAULT
,
3020 /* Send NM_TOOLTIPSCREATED notification */
3021 if (infoPtr
->hwndToolTip
) {
3022 NMTOOLTIPSCREATED nmttc
;
3024 nmttc
.hdr
.hwndFrom
= hwnd
;
3025 nmttc
.hdr
.idFrom
= GetWindowLongPtrW(hwnd
, GWLP_ID
);
3026 nmttc
.hdr
.code
= NM_TOOLTIPSCREATED
;
3027 nmttc
.hwndToolTips
= infoPtr
->hwndToolTip
;
3029 SendMessageW (infoPtr
->hwndNotify
, WM_NOTIFY
,
3030 GetWindowLongPtrW(hwnd
, GWLP_ID
), (LPARAM
)&nmttc
);
3034 OpenThemeData (infoPtr
->hwnd
, themeClass
);
3037 * We need to get text information so we need a DC and we need to select
3041 hOldFont
= SelectObject (hdc
, GetStockObject (SYSTEM_FONT
));
3043 /* Use the system font to determine the initial height of a tab. */
3044 GetTextMetricsW(hdc
, &fontMetrics
);
3047 * Make sure there is enough space for the letters + growing the
3048 * selected item + extra space for the selected item.
3050 infoPtr
->tabHeight
= fontMetrics
.tmHeight
+ SELECTED_TAB_OFFSET
+
3051 ((infoPtr
->dwStyle
& TCS_BUTTONS
) ? 2 : 1) *
3052 infoPtr
->uVItemPadding
;
3054 /* Initialize the width of a tab. */
3055 if (infoPtr
->dwStyle
& TCS_FIXEDWIDTH
)
3056 infoPtr
->tabWidth
= GetDeviceCaps(hdc
, LOGPIXELSX
);
3058 infoPtr
->tabMinWidth
= -1;
3060 TRACE("tabH=%d, tabW=%d\n", infoPtr
->tabHeight
, infoPtr
->tabWidth
);
3062 SelectObject (hdc
, hOldFont
);
3063 ReleaseDC(hwnd
, hdc
);
3069 TAB_Destroy (TAB_INFO
*infoPtr
)
3073 SetWindowLongPtrW(infoPtr
->hwnd
, 0, 0);
3075 for (iItem
= infoPtr
->uNumItem
- 1; iItem
>= 0; iItem
--)
3077 TAB_ITEM
*tab
= TAB_GetItem(infoPtr
, iItem
);
3079 DPA_DeletePtr(infoPtr
->items
, iItem
);
3080 infoPtr
->uNumItem
--;
3085 DPA_Destroy(infoPtr
->items
);
3086 infoPtr
->items
= NULL
;
3088 if (infoPtr
->hwndToolTip
)
3089 DestroyWindow (infoPtr
->hwndToolTip
);
3091 if (infoPtr
->hwndUpDown
)
3092 DestroyWindow(infoPtr
->hwndUpDown
);
3094 if (infoPtr
->iHotTracked
>= 0)
3095 KillTimer(infoPtr
->hwnd
, TAB_HOTTRACK_TIMER
);
3097 CloseThemeData (GetWindowTheme (infoPtr
->hwnd
));
3103 /* update theme after a WM_THEMECHANGED message */
3104 static LRESULT
theme_changed(const TAB_INFO
*infoPtr
)
3106 HTHEME theme
= GetWindowTheme (infoPtr
->hwnd
);
3107 CloseThemeData (theme
);
3108 OpenThemeData (infoPtr
->hwnd
, themeClass
);
3112 static LRESULT
TAB_NCCalcSize(WPARAM wParam
)
3116 return WVR_ALIGNTOP
;
3119 static inline LRESULT
3120 TAB_SetItemExtra (TAB_INFO
*infoPtr
, INT cbInfo
)
3122 TRACE("(%p %d)\n", infoPtr
, cbInfo
);
3124 if (cbInfo
< 0 || infoPtr
->uNumItem
) return FALSE
;
3126 infoPtr
->cbInfo
= cbInfo
;
3130 static LRESULT
TAB_RemoveImage (TAB_INFO
*infoPtr
, INT image
)
3132 TRACE("%p %d\n", infoPtr
, image
);
3134 if (ImageList_Remove (infoPtr
->himl
, image
))
3139 /* shift indices, repaint items if needed */
3140 for (i
= 0; i
< infoPtr
->uNumItem
; i
++)
3142 idx
= &TAB_GetItem(infoPtr
, i
)->iImage
;
3151 if (TAB_InternalGetItemRect (infoPtr
, i
, &r
, NULL
))
3152 InvalidateRect (infoPtr
->hwnd
, &r
, TRUE
);
3161 TAB_SetExtendedStyle (TAB_INFO
*infoPtr
, DWORD exMask
, DWORD exStyle
)
3163 DWORD prevstyle
= infoPtr
->exStyle
;
3165 /* zero mask means all styles */
3166 if (exMask
== 0) exMask
= ~0;
3168 if (exMask
& TCS_EX_REGISTERDROP
)
3170 FIXME("TCS_EX_REGISTERDROP style unimplemented\n");
3171 exMask
&= ~TCS_EX_REGISTERDROP
;
3172 exStyle
&= ~TCS_EX_REGISTERDROP
;
3175 if (exMask
& TCS_EX_FLATSEPARATORS
)
3177 if ((prevstyle
^ exStyle
) & TCS_EX_FLATSEPARATORS
)
3179 infoPtr
->exStyle
^= TCS_EX_FLATSEPARATORS
;
3180 TAB_InvalidateTabArea(infoPtr
);
3187 static inline LRESULT
3188 TAB_GetExtendedStyle (const TAB_INFO
*infoPtr
)
3190 return infoPtr
->exStyle
;
3194 TAB_DeselectAll (TAB_INFO
*infoPtr
, BOOL excludesel
)
3197 INT i
, selected
= infoPtr
->iSelected
;
3199 TRACE("(%p, %d)\n", infoPtr
, excludesel
);
3201 if (!(infoPtr
->dwStyle
& TCS_BUTTONS
))
3204 for (i
= 0; i
< infoPtr
->uNumItem
; i
++)
3206 if ((TAB_GetItem(infoPtr
, i
)->dwState
& TCIS_BUTTONPRESSED
) &&
3209 TAB_GetItem(infoPtr
, i
)->dwState
&= ~TCIS_BUTTONPRESSED
;
3214 if (!excludesel
&& (selected
!= -1))
3216 TAB_GetItem(infoPtr
, selected
)->dwState
&= ~TCIS_BUTTONPRESSED
;
3217 infoPtr
->iSelected
= -1;
3222 TAB_InvalidateTabArea (infoPtr
);
3229 * Processes WM_STYLECHANGED messages.
3232 * [I] infoPtr : valid pointer to the tab data structure
3233 * [I] wStyleType : window style type (normal or extended)
3234 * [I] lpss : window style information
3239 static INT
TAB_StyleChanged(TAB_INFO
*infoPtr
, WPARAM wStyleType
,
3240 const STYLESTRUCT
*lpss
)
3242 TRACE("(styletype=%lx, styleOld=0x%08x, styleNew=0x%08x)\n",
3243 wStyleType
, lpss
->styleOld
, lpss
->styleNew
);
3245 if (wStyleType
!= GWL_STYLE
) return 0;
3247 infoPtr
->dwStyle
= lpss
->styleNew
;
3249 TAB_SetItemBounds (infoPtr
);
3250 InvalidateRect(infoPtr
->hwnd
, NULL
, TRUE
);
3255 static LRESULT WINAPI
3256 TAB_WindowProc (HWND hwnd
, UINT uMsg
, WPARAM wParam
, LPARAM lParam
)
3258 TAB_INFO
*infoPtr
= TAB_GetInfoPtr(hwnd
);
3260 TRACE("hwnd=%p msg=%x wParam=%lx lParam=%lx\n", hwnd
, uMsg
, wParam
, lParam
);
3261 if (!infoPtr
&& (uMsg
!= WM_CREATE
))
3262 return DefWindowProcW (hwnd
, uMsg
, wParam
, lParam
);
3266 case TCM_GETIMAGELIST
:
3267 return TAB_GetImageList (infoPtr
);
3269 case TCM_SETIMAGELIST
:
3270 return TAB_SetImageList (infoPtr
, (HIMAGELIST
)lParam
);
3272 case TCM_GETITEMCOUNT
:
3273 return TAB_GetItemCount (infoPtr
);
3277 return TAB_GetItemT (infoPtr
, (INT
)wParam
, (LPTCITEMW
)lParam
, uMsg
== TCM_GETITEMW
);
3281 return TAB_SetItemT (infoPtr
, (INT
)wParam
, (LPTCITEMW
)lParam
, uMsg
== TCM_SETITEMW
);
3283 case TCM_DELETEITEM
:
3284 return TAB_DeleteItem (infoPtr
, (INT
)wParam
);
3286 case TCM_DELETEALLITEMS
:
3287 return TAB_DeleteAllItems (infoPtr
);
3289 case TCM_GETITEMRECT
:
3290 return TAB_GetItemRect (infoPtr
, (INT
)wParam
, (LPRECT
)lParam
);
3293 return TAB_GetCurSel (infoPtr
);
3296 return TAB_HitTest (infoPtr
, (LPTCHITTESTINFO
)lParam
);
3299 return TAB_SetCurSel (infoPtr
, (INT
)wParam
);
3301 case TCM_INSERTITEMA
:
3302 case TCM_INSERTITEMW
:
3303 return TAB_InsertItemT (infoPtr
, (INT
)wParam
, (TCITEMW
*)lParam
, uMsg
== TCM_INSERTITEMW
);
3305 case TCM_SETITEMEXTRA
:
3306 return TAB_SetItemExtra (infoPtr
, (INT
)wParam
);
3308 case TCM_ADJUSTRECT
:
3309 return TAB_AdjustRect (infoPtr
, (BOOL
)wParam
, (LPRECT
)lParam
);
3311 case TCM_SETITEMSIZE
:
3312 return TAB_SetItemSize (infoPtr
, (INT
)LOWORD(lParam
), (INT
)HIWORD(lParam
));
3314 case TCM_REMOVEIMAGE
:
3315 return TAB_RemoveImage (infoPtr
, (INT
)wParam
);
3317 case TCM_SETPADDING
:
3318 return TAB_SetPadding (infoPtr
, lParam
);
3320 case TCM_GETROWCOUNT
:
3321 return TAB_GetRowCount(infoPtr
);
3323 case TCM_GETUNICODEFORMAT
:
3324 return TAB_GetUnicodeFormat (infoPtr
);
3326 case TCM_SETUNICODEFORMAT
:
3327 return TAB_SetUnicodeFormat (infoPtr
, (BOOL
)wParam
);
3329 case TCM_HIGHLIGHTITEM
:
3330 return TAB_HighlightItem (infoPtr
, (INT
)wParam
, (BOOL
)LOWORD(lParam
));
3332 case TCM_GETTOOLTIPS
:
3333 return TAB_GetToolTips (infoPtr
);
3335 case TCM_SETTOOLTIPS
:
3336 return TAB_SetToolTips (infoPtr
, (HWND
)wParam
);
3338 case TCM_GETCURFOCUS
:
3339 return TAB_GetCurFocus (infoPtr
);
3341 case TCM_SETCURFOCUS
:
3342 return TAB_SetCurFocus (infoPtr
, (INT
)wParam
);
3344 case TCM_SETMINTABWIDTH
:
3345 return TAB_SetMinTabWidth(infoPtr
, (INT
)lParam
);
3347 case TCM_DESELECTALL
:
3348 return TAB_DeselectAll (infoPtr
, (BOOL
)wParam
);
3350 case TCM_GETEXTENDEDSTYLE
:
3351 return TAB_GetExtendedStyle (infoPtr
);
3353 case TCM_SETEXTENDEDSTYLE
:
3354 return TAB_SetExtendedStyle (infoPtr
, wParam
, lParam
);
3357 return TAB_GetFont (infoPtr
);
3360 return TAB_SetFont (infoPtr
, (HFONT
)wParam
);
3363 return TAB_Create (hwnd
, lParam
);
3366 return TAB_Destroy (infoPtr
);
3369 return DLGC_WANTARROWS
| DLGC_WANTCHARS
;
3371 case WM_LBUTTONDOWN
:
3372 return TAB_LButtonDown (infoPtr
, wParam
, lParam
);
3375 return TAB_LButtonUp (infoPtr
);
3378 return SendMessageW(infoPtr
->hwndNotify
, WM_NOTIFY
, wParam
, lParam
);
3381 TAB_RButtonUp (infoPtr
);
3382 return DefWindowProcW (hwnd
, uMsg
, wParam
, lParam
);
3385 return TAB_MouseMove (infoPtr
, wParam
, lParam
);
3387 case WM_PRINTCLIENT
:
3389 return TAB_Paint (infoPtr
, (HDC
)wParam
);
3392 return TAB_Size (infoPtr
);
3395 return TAB_SetRedraw (infoPtr
, (BOOL
)wParam
);
3398 return TAB_OnHScroll(infoPtr
, (int)LOWORD(wParam
), (int)HIWORD(wParam
));
3400 case WM_STYLECHANGED
:
3401 return TAB_StyleChanged(infoPtr
, wParam
, (LPSTYLESTRUCT
)lParam
);
3403 case WM_SYSCOLORCHANGE
:
3404 COMCTL32_RefreshSysColors();
3407 case WM_THEMECHANGED
:
3408 return theme_changed (infoPtr
);
3411 TAB_KillFocus(infoPtr
);
3413 TAB_FocusChanging(infoPtr
);
3414 break; /* Don't disturb normal focus behavior */
3417 return TAB_KeyDown(infoPtr
, wParam
, lParam
);
3420 return TAB_NCHitTest(infoPtr
, lParam
);
3423 return TAB_NCCalcSize(wParam
);
3426 if (uMsg
>= WM_USER
&& uMsg
< WM_APP
&& !COMCTL32_IsReflectedMessage(uMsg
))
3427 WARN("unknown msg %04x wp=%08lx lp=%08lx\n",
3428 uMsg
, wParam
, lParam
);
3431 return DefWindowProcW(hwnd
, uMsg
, wParam
, lParam
);
3440 ZeroMemory (&wndClass
, sizeof(WNDCLASSW
));
3441 wndClass
.style
= CS_GLOBALCLASS
| CS_DBLCLKS
| CS_HREDRAW
| CS_VREDRAW
;
3442 wndClass
.lpfnWndProc
= TAB_WindowProc
;
3443 wndClass
.cbClsExtra
= 0;
3444 wndClass
.cbWndExtra
= sizeof(TAB_INFO
*);
3445 wndClass
.hCursor
= LoadCursorW (0, (LPWSTR
)IDC_ARROW
);
3446 wndClass
.hbrBackground
= (HBRUSH
)(COLOR_BTNFACE
+1);
3447 wndClass
.lpszClassName
= WC_TABCONTROLW
;
3449 RegisterClassW (&wndClass
);
3454 TAB_Unregister (void)
3456 UnregisterClassW (WC_TABCONTROLW
, NULL
);