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
[] = { 'T','a','b',0 };
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 /* need to initialize these to empty rects */
377 memset(itemRect
,0,sizeof(RECT
));
378 itemRect
->bottom
= infoPtr
->tabHeight
;
381 memset(selectedRect
,0,sizeof(RECT
));
386 * Avoid special cases in this procedure by assigning the "out"
387 * parameters if the caller didn't supply them
389 if (itemRect
== NULL
)
390 itemRect
= &tmpItemRect
;
392 /* Retrieve the unmodified item rect. */
393 *itemRect
= TAB_GetItem(infoPtr
,itemIndex
)->rect
;
395 /* calculate the times bottom and top based on the row */
396 GetClientRect(infoPtr
->hwnd
, &clientRect
);
398 if ((infoPtr
->dwStyle
& TCS_BOTTOM
) && (infoPtr
->dwStyle
& TCS_VERTICAL
))
400 itemRect
->right
= clientRect
.right
- SELECTED_TAB_OFFSET
- itemRect
->left
* infoPtr
->tabHeight
-
401 ((infoPtr
->dwStyle
& TCS_BUTTONS
) ? itemRect
->left
* BUTTON_SPACINGX
: 0);
402 itemRect
->left
= itemRect
->right
- infoPtr
->tabHeight
;
404 else if (infoPtr
->dwStyle
& TCS_VERTICAL
)
406 itemRect
->left
= clientRect
.left
+ SELECTED_TAB_OFFSET
+ itemRect
->left
* infoPtr
->tabHeight
+
407 ((infoPtr
->dwStyle
& TCS_BUTTONS
) ? itemRect
->left
* BUTTON_SPACINGX
: 0);
408 itemRect
->right
= itemRect
->left
+ infoPtr
->tabHeight
;
410 else if (infoPtr
->dwStyle
& TCS_BOTTOM
)
412 itemRect
->bottom
= clientRect
.bottom
- itemRect
->top
* infoPtr
->tabHeight
-
413 ((infoPtr
->dwStyle
& TCS_BUTTONS
) ? itemRect
->top
* BUTTON_SPACINGY
: SELECTED_TAB_OFFSET
);
414 itemRect
->top
= itemRect
->bottom
- infoPtr
->tabHeight
;
416 else /* not TCS_BOTTOM and not TCS_VERTICAL */
418 itemRect
->top
= clientRect
.top
+ itemRect
->top
* infoPtr
->tabHeight
+
419 ((infoPtr
->dwStyle
& TCS_BUTTONS
) ? itemRect
->top
* BUTTON_SPACINGY
: SELECTED_TAB_OFFSET
);
420 itemRect
->bottom
= itemRect
->top
+ infoPtr
->tabHeight
;
424 * "scroll" it to make sure the item at the very left of the
425 * tab control is the leftmost visible tab.
427 if(infoPtr
->dwStyle
& TCS_VERTICAL
)
431 -TAB_GetItem(infoPtr
, infoPtr
->leftmostVisible
)->rect
.top
);
434 * Move the rectangle so the first item is slightly offset from
435 * the bottom of the tab control.
439 SELECTED_TAB_OFFSET
);
444 -TAB_GetItem(infoPtr
, infoPtr
->leftmostVisible
)->rect
.left
,
448 * Move the rectangle so the first item is slightly offset from
449 * the left of the tab control.
455 TRACE("item %d tab h=%d, rect=(%s)\n",
456 itemIndex
, infoPtr
->tabHeight
, wine_dbgstr_rect(itemRect
));
458 /* Now, calculate the position of the item as if it were selected. */
459 if (selectedRect
!=NULL
)
461 CopyRect(selectedRect
, itemRect
);
463 /* The rectangle of a selected item is a bit wider. */
464 if(infoPtr
->dwStyle
& TCS_VERTICAL
)
465 InflateRect(selectedRect
, 0, SELECTED_TAB_OFFSET
);
467 InflateRect(selectedRect
, SELECTED_TAB_OFFSET
, 0);
469 /* If it also a bit higher. */
470 if ((infoPtr
->dwStyle
& TCS_BOTTOM
) && (infoPtr
->dwStyle
& TCS_VERTICAL
))
472 selectedRect
->left
-= 2; /* the border is thicker on the right */
473 selectedRect
->right
+= SELECTED_TAB_OFFSET
;
475 else if (infoPtr
->dwStyle
& TCS_VERTICAL
)
477 selectedRect
->left
-= SELECTED_TAB_OFFSET
;
478 selectedRect
->right
+= 1;
480 else if (infoPtr
->dwStyle
& TCS_BOTTOM
)
482 selectedRect
->bottom
+= SELECTED_TAB_OFFSET
;
484 else /* not TCS_BOTTOM and not TCS_VERTICAL */
486 selectedRect
->top
-= SELECTED_TAB_OFFSET
;
487 selectedRect
->bottom
-= 1;
491 /* Check for visibility */
492 if (infoPtr
->dwStyle
& TCS_VERTICAL
)
493 return (itemRect
->top
< clientRect
.bottom
) && (itemRect
->bottom
> clientRect
.top
);
495 return (itemRect
->left
< clientRect
.right
) && (itemRect
->right
> clientRect
.left
);
499 TAB_GetItemRect(const TAB_INFO
*infoPtr
, INT item
, RECT
*rect
)
501 TRACE("(%p, %d, %p)\n", infoPtr
, item
, rect
);
502 return TAB_InternalGetItemRect(infoPtr
, item
, rect
, NULL
);
505 /******************************************************************************
508 * This method is called to handle keyboard input
510 static LRESULT
TAB_KeyDown(TAB_INFO
* infoPtr
, WPARAM keyCode
, LPARAM lParam
)
515 /* TCN_KEYDOWN notification sent always */
516 nm
.hdr
.hwndFrom
= infoPtr
->hwnd
;
517 nm
.hdr
.idFrom
= GetWindowLongPtrW(infoPtr
->hwnd
, GWLP_ID
);
518 nm
.hdr
.code
= TCN_KEYDOWN
;
521 SendMessageW(infoPtr
->hwndNotify
, WM_NOTIFY
, nm
.hdr
.idFrom
, (LPARAM
)&nm
);
526 newItem
= infoPtr
->uFocus
- 1;
529 newItem
= infoPtr
->uFocus
+ 1;
533 /* If we changed to a valid item, change focused item */
534 if (newItem
>= 0 && newItem
< infoPtr
->uNumItem
&& infoPtr
->uFocus
!= newItem
)
535 TAB_SetCurFocus(infoPtr
, newItem
);
541 * WM_KILLFOCUS handler
543 static void TAB_KillFocus(TAB_INFO
*infoPtr
)
545 /* clear current focused item back to selected for TCS_BUTTONS */
546 if ((infoPtr
->dwStyle
& TCS_BUTTONS
) && (infoPtr
->uFocus
!= infoPtr
->iSelected
))
550 if (TAB_InternalGetItemRect(infoPtr
, infoPtr
->uFocus
, &r
, NULL
))
551 InvalidateRect(infoPtr
->hwnd
, &r
, FALSE
);
553 infoPtr
->uFocus
= infoPtr
->iSelected
;
557 /******************************************************************************
560 * This method is called whenever the focus goes in or out of this control
561 * it is used to update the visual state of the control.
563 static void TAB_FocusChanging(const TAB_INFO
*infoPtr
)
569 * Get the rectangle for the item.
571 isVisible
= TAB_InternalGetItemRect(infoPtr
,
577 * If the rectangle is not completely invisible, invalidate that
578 * portion of the window.
582 TRACE("invalidate (%s)\n", wine_dbgstr_rect(&selectedRect
));
583 InvalidateRect(infoPtr
->hwnd
, &selectedRect
, TRUE
);
587 static INT
TAB_InternalHitTest (const TAB_INFO
*infoPtr
, POINT pt
, UINT
*flags
)
592 for (iCount
= 0; iCount
< infoPtr
->uNumItem
; iCount
++)
594 TAB_InternalGetItemRect(infoPtr
, iCount
, &rect
, NULL
);
596 if (PtInRect(&rect
, pt
))
598 *flags
= TCHT_ONITEM
;
603 *flags
= TCHT_NOWHERE
;
607 static inline LRESULT
608 TAB_HitTest (const TAB_INFO
*infoPtr
, LPTCHITTESTINFO lptest
)
610 TRACE("(%p, %p)\n", infoPtr
, lptest
);
611 return TAB_InternalHitTest (infoPtr
, lptest
->pt
, &lptest
->flags
);
614 /******************************************************************************
617 * Napster v2b5 has a tab control for its main navigation which has a client
618 * area that covers the whole area of the dialog pages.
619 * That's why it receives all msgs for that area and the underlying dialog ctrls
621 * So I decided that we should handle WM_NCHITTEST here and return
622 * HTTRANSPARENT if we don't hit the tab control buttons.
623 * FIXME: WM_NCHITTEST handling correct ? Fix it if you know that Windows
624 * doesn't do it that way. Maybe depends on tab control styles ?
626 static inline LRESULT
627 TAB_NCHitTest (const TAB_INFO
*infoPtr
, LPARAM lParam
)
632 pt
.x
= (short)LOWORD(lParam
);
633 pt
.y
= (short)HIWORD(lParam
);
634 ScreenToClient(infoPtr
->hwnd
, &pt
);
636 if (TAB_InternalHitTest(infoPtr
, pt
, &dummyflag
) == -1)
637 return HTTRANSPARENT
;
643 TAB_LButtonDown (TAB_INFO
*infoPtr
, WPARAM wParam
, LPARAM lParam
)
649 if (infoPtr
->hwndToolTip
)
650 TAB_RelayEvent (infoPtr
->hwndToolTip
, infoPtr
->hwnd
,
651 WM_LBUTTONDOWN
, wParam
, lParam
);
653 if (!(infoPtr
->dwStyle
& TCS_FOCUSNEVER
)) {
654 SetFocus (infoPtr
->hwnd
);
657 if (infoPtr
->hwndToolTip
)
658 TAB_RelayEvent (infoPtr
->hwndToolTip
, infoPtr
->hwnd
,
659 WM_LBUTTONDOWN
, wParam
, lParam
);
661 pt
.x
= (short)LOWORD(lParam
);
662 pt
.y
= (short)HIWORD(lParam
);
664 newItem
= TAB_InternalHitTest (infoPtr
, pt
, &dummy
);
666 TRACE("On Tab, item %d\n", newItem
);
668 if ((newItem
!= -1) && (infoPtr
->iSelected
!= newItem
))
670 if ((infoPtr
->dwStyle
& TCS_BUTTONS
) && (infoPtr
->dwStyle
& TCS_MULTISELECT
) &&
671 (wParam
& MK_CONTROL
))
675 /* toggle multiselection */
676 TAB_GetItem(infoPtr
, newItem
)->dwState
^= TCIS_BUTTONPRESSED
;
677 if (TAB_InternalGetItemRect (infoPtr
, newItem
, &r
, NULL
))
678 InvalidateRect (infoPtr
->hwnd
, &r
, TRUE
);
683 BOOL pressed
= FALSE
;
685 /* any button pressed ? */
686 for (i
= 0; i
< infoPtr
->uNumItem
; i
++)
687 if ((TAB_GetItem (infoPtr
, i
)->dwState
& TCIS_BUTTONPRESSED
) &&
688 (infoPtr
->iSelected
!= i
))
694 if (TAB_SendSimpleNotify(infoPtr
, TCN_SELCHANGING
))
698 TAB_DeselectAll (infoPtr
, FALSE
);
700 TAB_SetCurSel(infoPtr
, newItem
);
702 TAB_SendSimpleNotify(infoPtr
, TCN_SELCHANGE
);
709 static inline LRESULT
710 TAB_LButtonUp (const TAB_INFO
*infoPtr
)
712 TAB_SendSimpleNotify(infoPtr
, NM_CLICK
);
718 TAB_RButtonUp (const TAB_INFO
*infoPtr
)
720 TAB_SendSimpleNotify(infoPtr
, NM_RCLICK
);
723 /******************************************************************************
724 * TAB_DrawLoneItemInterior
726 * This calls TAB_DrawItemInterior. However, TAB_DrawItemInterior is normally
727 * called by TAB_DrawItem which is normally called by TAB_Refresh which sets
728 * up the device context and font. This routine does the same setup but
729 * only calls TAB_DrawItemInterior for the single specified item.
732 TAB_DrawLoneItemInterior(const TAB_INFO
* infoPtr
, int iItem
)
734 HDC hdc
= GetDC(infoPtr
->hwnd
);
737 /* Clip UpDown control to not draw over it */
738 if (infoPtr
->needsScrolling
)
740 GetWindowRect(infoPtr
->hwnd
, &rC
);
741 GetWindowRect(infoPtr
->hwndUpDown
, &r
);
742 ExcludeClipRect(hdc
, r
.left
- rC
.left
, r
.top
- rC
.top
, r
.right
- rC
.left
, r
.bottom
- rC
.top
);
744 TAB_DrawItemInterior(infoPtr
, hdc
, iItem
, NULL
);
745 ReleaseDC(infoPtr
->hwnd
, hdc
);
748 /* update a tab after hottracking - invalidate it or just redraw the interior,
749 * based on whether theming is used or not */
750 static inline void hottrack_refresh(const TAB_INFO
*infoPtr
, int tabIndex
)
752 if (tabIndex
== -1) return;
754 if (GetWindowTheme (infoPtr
->hwnd
))
757 TAB_InternalGetItemRect(infoPtr
, tabIndex
, &rect
, NULL
);
758 InvalidateRect (infoPtr
->hwnd
, &rect
, FALSE
);
761 TAB_DrawLoneItemInterior(infoPtr
, tabIndex
);
764 /******************************************************************************
765 * TAB_HotTrackTimerProc
767 * When a mouse-move event causes a tab to be highlighted (hot-tracking), a
768 * timer is setup so we can check if the mouse is moved out of our window.
769 * (We don't get an event when the mouse leaves, the mouse-move events just
770 * stop being delivered to our window and just start being delivered to
771 * another window.) This function is called when the timer triggers so
772 * we can check if the mouse has left our window. If so, we un-highlight
773 * the hot-tracked tab.
776 TAB_HotTrackTimerProc
778 HWND hwnd
, /* handle of window for timer messages */
779 UINT uMsg
, /* WM_TIMER message */
780 UINT_PTR idEvent
, /* timer identifier */
781 DWORD dwTime
/* current system time */
784 TAB_INFO
* infoPtr
= TAB_GetInfoPtr(hwnd
);
786 if (infoPtr
!= NULL
&& infoPtr
->iHotTracked
>= 0)
791 ** If we can't get the cursor position, or if the cursor is outside our
792 ** window, we un-highlight the hot-tracked tab. Note that the cursor is
793 ** "outside" even if it is within our bounding rect if another window
794 ** overlaps. Note also that the case where the cursor stayed within our
795 ** window but has moved off the hot-tracked tab will be handled by the
796 ** WM_MOUSEMOVE event.
798 if (!GetCursorPos(&pt
) || WindowFromPoint(pt
) != hwnd
)
800 /* Redraw iHotTracked to look normal */
801 INT iRedraw
= infoPtr
->iHotTracked
;
802 infoPtr
->iHotTracked
= -1;
803 hottrack_refresh (infoPtr
, iRedraw
);
805 /* Kill this timer */
806 KillTimer(hwnd
, TAB_HOTTRACK_TIMER
);
811 /******************************************************************************
814 * If a tab control has the TCS_HOTTRACK style, then the tab under the mouse
815 * should be highlighted. This function determines which tab in a tab control,
816 * if any, is under the mouse and records that information. The caller may
817 * supply output parameters to receive the item number of the tab item which
818 * was highlighted but isn't any longer and of the tab item which is now
819 * highlighted but wasn't previously. The caller can use this information to
820 * selectively redraw those tab items.
822 * If the caller has a mouse position, it can supply it through the pos
823 * parameter. For example, TAB_MouseMove does this. Otherwise, the caller
824 * supplies NULL and this function determines the current mouse position
832 int* out_redrawLeave
,
839 if (out_redrawLeave
!= NULL
)
840 *out_redrawLeave
= -1;
841 if (out_redrawEnter
!= NULL
)
842 *out_redrawEnter
= -1;
844 if ((infoPtr
->dwStyle
& TCS_HOTTRACK
) || GetWindowTheme(infoPtr
->hwnd
))
852 ScreenToClient(infoPtr
->hwnd
, &pt
);
856 pt
.x
= (short)LOWORD(*pos
);
857 pt
.y
= (short)HIWORD(*pos
);
860 item
= TAB_InternalHitTest(infoPtr
, pt
, &flags
);
863 if (item
!= infoPtr
->iHotTracked
)
865 if (infoPtr
->iHotTracked
>= 0)
867 /* Mark currently hot-tracked to be redrawn to look normal */
868 if (out_redrawLeave
!= NULL
)
869 *out_redrawLeave
= infoPtr
->iHotTracked
;
873 /* Kill timer which forces recheck of mouse pos */
874 KillTimer(infoPtr
->hwnd
, TAB_HOTTRACK_TIMER
);
879 /* Start timer so we recheck mouse pos */
880 UINT timerID
= SetTimer
884 TAB_HOTTRACK_TIMER_INTERVAL
,
885 TAB_HotTrackTimerProc
889 return; /* Hot tracking not available */
892 infoPtr
->iHotTracked
= item
;
896 /* Mark new hot-tracked to be redrawn to look highlighted */
897 if (out_redrawEnter
!= NULL
)
898 *out_redrawEnter
= item
;
903 /******************************************************************************
906 * Handles the mouse-move event. Updates tooltips. Updates hot-tracking.
909 TAB_MouseMove (TAB_INFO
*infoPtr
, WPARAM wParam
, LPARAM lParam
)
914 if (infoPtr
->hwndToolTip
)
915 TAB_RelayEvent (infoPtr
->hwndToolTip
, infoPtr
->hwnd
,
916 WM_LBUTTONDOWN
, wParam
, lParam
);
918 /* Determine which tab to highlight. Redraw tabs which change highlight
920 TAB_RecalcHotTrack(infoPtr
, &lParam
, &redrawLeave
, &redrawEnter
);
922 hottrack_refresh (infoPtr
, redrawLeave
);
923 hottrack_refresh (infoPtr
, redrawEnter
);
928 /******************************************************************************
931 * Calculates the tab control's display area given the window rectangle or
932 * the window rectangle given the requested display rectangle.
934 static LRESULT
TAB_AdjustRect(const TAB_INFO
*infoPtr
, WPARAM fLarger
, LPRECT prc
)
936 LONG
*iRightBottom
, *iLeftTop
;
938 TRACE ("hwnd=%p fLarger=%ld (%s)\n", infoPtr
->hwnd
, fLarger
,
939 wine_dbgstr_rect(prc
));
943 if(infoPtr
->dwStyle
& TCS_VERTICAL
)
945 iRightBottom
= &(prc
->right
);
946 iLeftTop
= &(prc
->left
);
950 iRightBottom
= &(prc
->bottom
);
951 iLeftTop
= &(prc
->top
);
954 if (fLarger
) /* Go from display rectangle */
956 /* Add the height of the tabs. */
957 if (infoPtr
->dwStyle
& TCS_BOTTOM
)
958 *iRightBottom
+= infoPtr
->tabHeight
* infoPtr
->uNumRows
;
960 *iLeftTop
-= infoPtr
->tabHeight
* infoPtr
->uNumRows
+
961 ((infoPtr
->dwStyle
& TCS_BUTTONS
)? 3 * (infoPtr
->uNumRows
- 1) : 0);
963 /* Inflate the rectangle for the padding */
964 InflateRect(prc
, DISPLAY_AREA_PADDINGX
, DISPLAY_AREA_PADDINGY
);
966 /* Inflate for the border */
967 InflateRect(prc
, CONTROL_BORDER_SIZEX
, CONTROL_BORDER_SIZEY
);
969 else /* Go from window rectangle. */
971 /* Deflate the rectangle for the border */
972 InflateRect(prc
, -CONTROL_BORDER_SIZEX
, -CONTROL_BORDER_SIZEY
);
974 /* Deflate the rectangle for the padding */
975 InflateRect(prc
, -DISPLAY_AREA_PADDINGX
, -DISPLAY_AREA_PADDINGY
);
977 /* Remove the height of the tabs. */
978 if (infoPtr
->dwStyle
& TCS_BOTTOM
)
979 *iRightBottom
-= infoPtr
->tabHeight
* infoPtr
->uNumRows
;
981 *iLeftTop
+= (infoPtr
->tabHeight
) * infoPtr
->uNumRows
+
982 ((infoPtr
->dwStyle
& TCS_BUTTONS
)? 3 * (infoPtr
->uNumRows
- 1) : 0);
988 /******************************************************************************
991 * This method will handle the notification from the scroll control and
992 * perform the scrolling operation on the tab control.
994 static LRESULT
TAB_OnHScroll(TAB_INFO
*infoPtr
, int nScrollCode
, int nPos
)
996 if(nScrollCode
== SB_THUMBPOSITION
&& nPos
!= infoPtr
->leftmostVisible
)
998 if(nPos
< infoPtr
->leftmostVisible
)
999 infoPtr
->leftmostVisible
--;
1001 infoPtr
->leftmostVisible
++;
1003 TAB_RecalcHotTrack(infoPtr
, NULL
, NULL
, NULL
);
1004 TAB_InvalidateTabArea(infoPtr
);
1005 SendMessageW(infoPtr
->hwndUpDown
, UDM_SETPOS
, 0,
1006 MAKELONG(infoPtr
->leftmostVisible
, 0));
1012 /******************************************************************************
1013 * TAB_SetupScrolling
1015 * This method will check the current scrolling state and make sure the
1016 * scrolling control is displayed (or not).
1018 static void TAB_SetupScrolling(
1020 const RECT
* clientRect
)
1022 static const WCHAR emptyW
[] = { 0 };
1025 if (infoPtr
->needsScrolling
)
1028 INT vsize
, tabwidth
;
1031 * Calculate the position of the scroll control.
1033 controlPos
.right
= clientRect
->right
;
1034 controlPos
.left
= controlPos
.right
- 2 * GetSystemMetrics(SM_CXHSCROLL
);
1036 if (infoPtr
->dwStyle
& TCS_BOTTOM
)
1038 controlPos
.top
= clientRect
->bottom
- infoPtr
->tabHeight
;
1039 controlPos
.bottom
= controlPos
.top
+ GetSystemMetrics(SM_CYHSCROLL
);
1043 controlPos
.bottom
= clientRect
->top
+ infoPtr
->tabHeight
;
1044 controlPos
.top
= controlPos
.bottom
- GetSystemMetrics(SM_CYHSCROLL
);
1048 * If we don't have a scroll control yet, we want to create one.
1049 * If we have one, we want to make sure it's positioned properly.
1051 if (infoPtr
->hwndUpDown
==0)
1053 infoPtr
->hwndUpDown
= CreateWindowW(UPDOWN_CLASSW
, emptyW
,
1054 WS_VISIBLE
| WS_CHILD
| UDS_HORZ
,
1055 controlPos
.left
, controlPos
.top
,
1056 controlPos
.right
- controlPos
.left
,
1057 controlPos
.bottom
- controlPos
.top
,
1058 infoPtr
->hwnd
, NULL
, NULL
, NULL
);
1062 SetWindowPos(infoPtr
->hwndUpDown
,
1064 controlPos
.left
, controlPos
.top
,
1065 controlPos
.right
- controlPos
.left
,
1066 controlPos
.bottom
- controlPos
.top
,
1067 SWP_SHOWWINDOW
| SWP_NOZORDER
);
1070 /* Now calculate upper limit of the updown control range.
1071 * We do this by calculating how many tabs will be offscreen when the
1072 * last tab is visible.
1074 if(infoPtr
->uNumItem
)
1076 vsize
= clientRect
->right
- (controlPos
.right
- controlPos
.left
+ 1);
1077 maxRange
= infoPtr
->uNumItem
;
1078 tabwidth
= TAB_GetItem(infoPtr
, infoPtr
->uNumItem
- 1)->rect
.right
;
1080 for(; maxRange
> 0; maxRange
--)
1082 if(tabwidth
- TAB_GetItem(infoPtr
,maxRange
- 1)->rect
.left
> vsize
)
1086 if(maxRange
== infoPtr
->uNumItem
)
1092 /* If we once had a scroll control... hide it */
1093 if (infoPtr
->hwndUpDown
)
1094 ShowWindow(infoPtr
->hwndUpDown
, SW_HIDE
);
1096 if (infoPtr
->hwndUpDown
)
1097 SendMessageW(infoPtr
->hwndUpDown
, UDM_SETRANGE32
, 0, maxRange
);
1100 /******************************************************************************
1103 * This method will calculate the position rectangles of all the items in the
1104 * control. The rectangle calculated starts at 0 for the first item in the
1105 * list and ignores scrolling and selection.
1106 * It also uses the current font to determine the height of the tab row and
1107 * it checks if all the tabs fit in the client area of the window. If they
1108 * don't, a scrolling control is added.
1110 static void TAB_SetItemBounds (TAB_INFO
*infoPtr
)
1112 TEXTMETRICW fontMetrics
;
1115 INT curItemRowCount
;
1116 HFONT hFont
, hOldFont
;
1125 * We need to get text information so we need a DC and we need to select
1128 hdc
= GetDC(infoPtr
->hwnd
);
1130 hFont
= infoPtr
->hFont
? infoPtr
->hFont
: GetStockObject (SYSTEM_FONT
);
1131 hOldFont
= SelectObject (hdc
, hFont
);
1134 * We will base the rectangle calculations on the client rectangle
1137 GetClientRect(infoPtr
->hwnd
, &clientRect
);
1139 /* if TCS_VERTICAL then swap the height and width so this code places the
1140 tabs along the top of the rectangle and we can just rotate them after
1141 rather than duplicate all of the below code */
1142 if(infoPtr
->dwStyle
& TCS_VERTICAL
)
1144 iTemp
= clientRect
.bottom
;
1145 clientRect
.bottom
= clientRect
.right
;
1146 clientRect
.right
= iTemp
;
1149 /* Now use hPadding and vPadding */
1150 infoPtr
->uHItemPadding
= infoPtr
->uHItemPadding_s
;
1151 infoPtr
->uVItemPadding
= infoPtr
->uVItemPadding_s
;
1153 /* The leftmost item will be "0" aligned */
1155 curItemRowCount
= infoPtr
->uNumItem
? 1 : 0;
1157 if (!(infoPtr
->fHeightSet
))
1160 INT icon_height
= 0, cx
;
1162 /* Use the current font to determine the height of a tab. */
1163 GetTextMetricsW(hdc
, &fontMetrics
);
1165 /* Get the icon height */
1167 ImageList_GetIconSize(infoPtr
->himl
, &cx
, &icon_height
);
1169 /* Take the highest between font or icon */
1170 if (fontMetrics
.tmHeight
> icon_height
)
1171 item_height
= fontMetrics
.tmHeight
+ 2;
1173 item_height
= icon_height
;
1176 * Make sure there is enough space for the letters + icon + growing the
1177 * selected item + extra space for the selected item.
1179 infoPtr
->tabHeight
= item_height
+
1180 ((infoPtr
->dwStyle
& TCS_BUTTONS
) ? 2 : 1) *
1181 infoPtr
->uVItemPadding
;
1183 TRACE("tabH=%d, tmH=%d, iconh=%d\n",
1184 infoPtr
->tabHeight
, fontMetrics
.tmHeight
, icon_height
);
1187 TRACE("client right=%d\n", clientRect
.right
);
1189 /* Get the icon width */
1194 ImageList_GetIconSize(infoPtr
->himl
, &icon_width
, &cy
);
1196 if (infoPtr
->dwStyle
& TCS_FIXEDWIDTH
)
1199 /* Add padding if icon is present */
1200 icon_width
+= infoPtr
->uHItemPadding
;
1203 for (curItem
= 0; curItem
< infoPtr
->uNumItem
; curItem
++)
1205 TAB_ITEM
*curr
= TAB_GetItem(infoPtr
, curItem
);
1207 /* Set the leftmost position of the tab. */
1208 curr
->rect
.left
= curItemLeftPos
;
1210 if (infoPtr
->dwStyle
& TCS_FIXEDWIDTH
)
1212 curr
->rect
.right
= curr
->rect
.left
+
1213 max(infoPtr
->tabWidth
, icon_width
);
1215 else if (!curr
->pszText
)
1217 /* If no text use minimum tab width including padding. */
1218 if (infoPtr
->tabMinWidth
< 0)
1219 curr
->rect
.right
= curr
->rect
.left
+ GET_DEFAULT_MIN_TAB_WIDTH(infoPtr
);
1222 curr
->rect
.right
= curr
->rect
.left
+ infoPtr
->tabMinWidth
;
1224 /* Add extra padding if icon is present */
1225 if (infoPtr
->himl
&& infoPtr
->tabMinWidth
> 0 && infoPtr
->tabMinWidth
< DEFAULT_MIN_TAB_WIDTH
1226 && infoPtr
->uHItemPadding
> 1)
1227 curr
->rect
.right
+= EXTRA_ICON_PADDING
* (infoPtr
->uHItemPadding
-1);
1234 /* Calculate how wide the tab is depending on the text it contains */
1235 GetTextExtentPoint32W(hdc
, curr
->pszText
,
1236 lstrlenW(curr
->pszText
), &size
);
1238 tabwidth
= size
.cx
+ icon_width
+ 2 * infoPtr
->uHItemPadding
;
1240 if (infoPtr
->tabMinWidth
< 0)
1241 tabwidth
= max(tabwidth
, GET_DEFAULT_MIN_TAB_WIDTH(infoPtr
));
1243 tabwidth
= max(tabwidth
, infoPtr
->tabMinWidth
);
1245 curr
->rect
.right
= curr
->rect
.left
+ tabwidth
;
1246 TRACE("for <%s>, l,r=%d,%d\n",
1247 debugstr_w(curr
->pszText
), curr
->rect
.left
, curr
->rect
.right
);
1251 * Check if this is a multiline tab control and if so
1252 * check to see if we should wrap the tabs
1254 * Wrap all these tabs. We will arrange them evenly later.
1258 if (((infoPtr
->dwStyle
& TCS_MULTILINE
) || (infoPtr
->dwStyle
& TCS_VERTICAL
)) &&
1260 (clientRect
.right
- CONTROL_BORDER_SIZEX
- DISPLAY_AREA_PADDINGX
)))
1262 curr
->rect
.right
-= curr
->rect
.left
;
1264 curr
->rect
.left
= 0;
1266 TRACE("wrapping <%s>, l,r=%d,%d\n", debugstr_w(curr
->pszText
),
1267 curr
->rect
.left
, curr
->rect
.right
);
1270 curr
->rect
.bottom
= 0;
1271 curr
->rect
.top
= curItemRowCount
- 1;
1273 TRACE("Rect: %s\n", wine_dbgstr_rect(&curr
->rect
));
1276 * The leftmost position of the next item is the rightmost position
1279 if (infoPtr
->dwStyle
& TCS_BUTTONS
)
1281 curItemLeftPos
= curr
->rect
.right
+ BUTTON_SPACINGX
;
1282 if (infoPtr
->dwStyle
& TCS_FLATBUTTONS
)
1283 curItemLeftPos
+= FLAT_BTN_SPACINGX
;
1286 curItemLeftPos
= curr
->rect
.right
;
1289 if (!((infoPtr
->dwStyle
& TCS_MULTILINE
) || (infoPtr
->dwStyle
& TCS_VERTICAL
)))
1292 * Check if we need a scrolling control.
1294 infoPtr
->needsScrolling
= (curItemLeftPos
+ (2 * SELECTED_TAB_OFFSET
) >
1297 /* Don't need scrolling, then update infoPtr->leftmostVisible */
1298 if(!infoPtr
->needsScrolling
)
1299 infoPtr
->leftmostVisible
= 0;
1304 * No scrolling in Multiline or Vertical styles.
1306 infoPtr
->needsScrolling
= FALSE
;
1307 infoPtr
->leftmostVisible
= 0;
1309 TAB_SetupScrolling(infoPtr
, &clientRect
);
1311 /* Set the number of rows */
1312 infoPtr
->uNumRows
= curItemRowCount
;
1314 /* Arrange all tabs evenly if style says so */
1315 if (!(infoPtr
->dwStyle
& TCS_RAGGEDRIGHT
) &&
1316 ((infoPtr
->dwStyle
& TCS_MULTILINE
) || (infoPtr
->dwStyle
& TCS_VERTICAL
)) &&
1317 (infoPtr
->uNumItem
> 0) &&
1318 (infoPtr
->uNumRows
> 1))
1320 INT tabPerRow
,remTab
,iRow
;
1325 * Ok windows tries to even out the rows. place the same
1326 * number of tabs in each row. So lets give that a shot
1329 tabPerRow
= infoPtr
->uNumItem
/ (infoPtr
->uNumRows
);
1330 remTab
= infoPtr
->uNumItem
% (infoPtr
->uNumRows
);
1332 for (iItm
=0,iRow
=0,iCount
=0,curItemLeftPos
=0;
1333 iItm
<infoPtr
->uNumItem
;
1336 /* normalize the current rect */
1337 TAB_ITEM
*curr
= TAB_GetItem(infoPtr
, iItm
);
1339 /* shift the item to the left side of the clientRect */
1340 curr
->rect
.right
-= curr
->rect
.left
;
1341 curr
->rect
.left
= 0;
1343 TRACE("r=%d, cl=%d, cl.r=%d, iCount=%d, iRow=%d, uNumRows=%d, remTab=%d, tabPerRow=%d\n",
1344 curr
->rect
.right
, curItemLeftPos
, clientRect
.right
,
1345 iCount
, iRow
, infoPtr
->uNumRows
, remTab
, tabPerRow
);
1347 /* if we have reached the maximum number of tabs on this row */
1348 /* move to the next row, reset our current item left position and */
1349 /* the count of items on this row */
1351 if (infoPtr
->dwStyle
& TCS_VERTICAL
) {
1352 /* Vert: Add the remaining tabs in the *last* remainder rows */
1353 if (iCount
>= ((iRow
>=(INT
)infoPtr
->uNumRows
- remTab
)?tabPerRow
+ 1:tabPerRow
)) {
1359 /* Horz: Add the remaining tabs in the *first* remainder rows */
1360 if (iCount
>= ((iRow
<remTab
)?tabPerRow
+ 1:tabPerRow
)) {
1367 /* shift the item to the right to place it as the next item in this row */
1368 curr
->rect
.left
+= curItemLeftPos
;
1369 curr
->rect
.right
+= curItemLeftPos
;
1370 curr
->rect
.top
= iRow
;
1371 if (infoPtr
->dwStyle
& TCS_BUTTONS
)
1373 curItemLeftPos
= curr
->rect
.right
+ 1;
1374 if (infoPtr
->dwStyle
& TCS_FLATBUTTONS
)
1375 curItemLeftPos
+= FLAT_BTN_SPACINGX
;
1378 curItemLeftPos
= curr
->rect
.right
;
1380 TRACE("arranging <%s>, l,r=%d,%d, row=%d\n",
1381 debugstr_w(curr
->pszText
), curr
->rect
.left
,
1382 curr
->rect
.right
, curr
->rect
.top
);
1389 INT widthDiff
, iIndexStart
=0, iIndexEnd
=0;
1393 while(iIndexStart
< infoPtr
->uNumItem
)
1395 TAB_ITEM
*start
= TAB_GetItem(infoPtr
, iIndexStart
);
1398 * find the index of the row
1400 /* find the first item on the next row */
1401 for (iIndexEnd
=iIndexStart
;
1402 (iIndexEnd
< infoPtr
->uNumItem
) &&
1403 (TAB_GetItem(infoPtr
, iIndexEnd
)->rect
.top
==
1406 /* intentionally blank */;
1409 * we need to justify these tabs so they fill the whole given
1413 /* find the amount of space remaining on this row */
1414 widthDiff
= clientRect
.right
- (2 * SELECTED_TAB_OFFSET
) -
1415 TAB_GetItem(infoPtr
, iIndexEnd
- 1)->rect
.right
;
1417 /* iCount is the number of tab items on this row */
1418 iCount
= iIndexEnd
- iIndexStart
;
1422 remainder
= widthDiff
% iCount
;
1423 widthDiff
= widthDiff
/ iCount
;
1424 /* add widthDiff/iCount, or extra space/items on row, to each item on this row */
1425 for (iIndex
=iIndexStart
, iCount
=0; iIndex
< iIndexEnd
; iIndex
++, iCount
++)
1427 TAB_ITEM
*item
= TAB_GetItem(infoPtr
, iIndex
);
1429 item
->rect
.left
+= iCount
* widthDiff
;
1430 item
->rect
.right
+= (iCount
+ 1) * widthDiff
;
1432 TRACE("adjusting 1 <%s>, l,r=%d,%d\n",
1433 debugstr_w(item
->pszText
),
1434 item
->rect
.left
, item
->rect
.right
);
1437 TAB_GetItem(infoPtr
, iIndex
- 1)->rect
.right
+= remainder
;
1439 else /* we have only one item on this row, make it take up the entire row */
1441 start
->rect
.left
= clientRect
.left
;
1442 start
->rect
.right
= clientRect
.right
- 4;
1444 TRACE("adjusting 2 <%s>, l,r=%d,%d\n",
1445 debugstr_w(start
->pszText
),
1446 start
->rect
.left
, start
->rect
.right
);
1451 iIndexStart
= iIndexEnd
;
1456 /* if TCS_VERTICAL rotate the tabs so they are along the side of the clientRect */
1457 if(infoPtr
->dwStyle
& TCS_VERTICAL
)
1460 for(iIndex
= 0; iIndex
< infoPtr
->uNumItem
; iIndex
++)
1462 rcItem
= &TAB_GetItem(infoPtr
, iIndex
)->rect
;
1464 rcOriginal
= *rcItem
;
1466 /* this is rotating the items by 90 degrees clockwise around the center of the control */
1467 rcItem
->top
= (rcOriginal
.left
- clientRect
.left
);
1468 rcItem
->bottom
= rcItem
->top
+ (rcOriginal
.right
- rcOriginal
.left
);
1469 rcItem
->left
= rcOriginal
.top
;
1470 rcItem
->right
= rcOriginal
.bottom
;
1474 TAB_EnsureSelectionVisible(infoPtr
);
1475 TAB_RecalcHotTrack(infoPtr
, NULL
, NULL
, NULL
);
1478 SelectObject (hdc
, hOldFont
);
1479 ReleaseDC (infoPtr
->hwnd
, hdc
);
1484 TAB_EraseTabInterior(const TAB_INFO
*infoPtr
, HDC hdc
, INT iItem
, const RECT
*drawRect
)
1486 HBRUSH hbr
= CreateSolidBrush (comctl32_color
.clrBtnFace
);
1487 BOOL deleteBrush
= TRUE
;
1488 RECT rTemp
= *drawRect
;
1490 if (infoPtr
->dwStyle
& TCS_BUTTONS
)
1492 if (iItem
== infoPtr
->iSelected
)
1494 /* Background color */
1495 if (!(infoPtr
->dwStyle
& TCS_OWNERDRAWFIXED
))
1498 hbr
= GetSysColorBrush(COLOR_SCROLLBAR
);
1500 SetTextColor(hdc
, comctl32_color
.clr3dFace
);
1501 SetBkColor(hdc
, comctl32_color
.clr3dHilight
);
1503 /* if COLOR_WINDOW happens to be the same as COLOR_3DHILIGHT
1504 * we better use 0x55aa bitmap brush to make scrollbar's background
1505 * look different from the window background.
1507 if (comctl32_color
.clr3dHilight
== comctl32_color
.clrWindow
)
1508 hbr
= COMCTL32_hPattern55AABrush
;
1510 deleteBrush
= FALSE
;
1512 FillRect(hdc
, &rTemp
, hbr
);
1514 else /* ! selected */
1516 if (infoPtr
->dwStyle
& TCS_FLATBUTTONS
)
1518 InflateRect(&rTemp
, 2, 2);
1519 FillRect(hdc
, &rTemp
, hbr
);
1520 if (iItem
== infoPtr
->iHotTracked
||
1521 (iItem
!= infoPtr
->iSelected
&& iItem
== infoPtr
->uFocus
))
1522 DrawEdge(hdc
, &rTemp
, BDR_RAISEDINNER
, BF_RECT
);
1525 FillRect(hdc
, &rTemp
, hbr
);
1529 else /* !TCS_BUTTONS */
1531 InflateRect(&rTemp
, -2, -2);
1532 if (!GetWindowTheme (infoPtr
->hwnd
))
1533 FillRect(hdc
, &rTemp
, hbr
);
1536 /* highlighting is drawn on top of previous fills */
1537 if (TAB_GetItem(infoPtr
, iItem
)->dwState
& TCIS_HIGHLIGHTED
)
1542 deleteBrush
= FALSE
;
1544 hbr
= GetSysColorBrush(COLOR_HIGHLIGHT
);
1545 FillRect(hdc
, &rTemp
, hbr
);
1549 if (deleteBrush
) DeleteObject(hbr
);
1552 /******************************************************************************
1553 * TAB_DrawItemInterior
1555 * This method is used to draw the interior (text and icon) of a single tab
1556 * into the tab control.
1559 TAB_DrawItemInterior(const TAB_INFO
*infoPtr
, HDC hdc
, INT iItem
, RECT
*drawRect
)
1568 /* if (drawRect == NULL) */
1575 * Get the rectangle for the item.
1577 isVisible
= TAB_InternalGetItemRect(infoPtr
, iItem
, &itemRect
, &selectedRect
);
1582 * Make sure drawRect points to something valid; simplifies code.
1584 drawRect
= &localRect
;
1587 * This logic copied from the part of TAB_DrawItem which draws
1588 * the tab background. It's important to keep it in sync. I
1589 * would have liked to avoid code duplication, but couldn't figure
1590 * out how without making spaghetti of TAB_DrawItem.
1592 if (iItem
== infoPtr
->iSelected
)
1593 *drawRect
= selectedRect
;
1595 *drawRect
= itemRect
;
1597 if (infoPtr
->dwStyle
& TCS_BUTTONS
)
1599 if (iItem
== infoPtr
->iSelected
)
1601 drawRect
->left
+= 4;
1603 drawRect
->right
-= 4;
1605 if (infoPtr
->dwStyle
& TCS_VERTICAL
)
1607 if (!(infoPtr
->dwStyle
& TCS_BOTTOM
)) drawRect
->right
+= 1;
1608 drawRect
->bottom
-= 4;
1612 if (infoPtr
->dwStyle
& TCS_BOTTOM
)
1615 drawRect
->bottom
-= 4;
1618 drawRect
->bottom
-= 1;
1623 drawRect
->left
+= 2;
1625 drawRect
->right
-= 2;
1626 drawRect
->bottom
-= 2;
1631 if ((infoPtr
->dwStyle
& TCS_VERTICAL
) && (infoPtr
->dwStyle
& TCS_BOTTOM
))
1633 if (iItem
!= infoPtr
->iSelected
)
1635 drawRect
->left
+= 2;
1637 drawRect
->bottom
-= 2;
1640 else if (infoPtr
->dwStyle
& TCS_VERTICAL
)
1642 if (iItem
== infoPtr
->iSelected
)
1644 drawRect
->right
+= 1;
1649 drawRect
->right
-= 2;
1650 drawRect
->bottom
-= 2;
1653 else if (infoPtr
->dwStyle
& TCS_BOTTOM
)
1655 if (iItem
== infoPtr
->iSelected
)
1661 InflateRect(drawRect
, -2, -2);
1662 drawRect
->bottom
+= 2;
1667 if (iItem
== infoPtr
->iSelected
)
1669 drawRect
->bottom
+= 3;
1673 drawRect
->bottom
-= 2;
1674 InflateRect(drawRect
, -2, 0);
1679 TRACE("drawRect=(%s)\n", wine_dbgstr_rect(drawRect
));
1681 /* Clear interior */
1682 TAB_EraseTabInterior (infoPtr
, hdc
, iItem
, drawRect
);
1684 /* Draw the focus rectangle */
1685 if (!(infoPtr
->dwStyle
& TCS_FOCUSNEVER
) &&
1686 (GetFocus() == infoPtr
->hwnd
) &&
1687 (iItem
== infoPtr
->uFocus
) )
1689 RECT rFocus
= *drawRect
;
1691 if (!(infoPtr
->dwStyle
& TCS_BUTTONS
)) InflateRect(&rFocus
, -3, -3);
1692 if (infoPtr
->dwStyle
& TCS_BOTTOM
&& !(infoPtr
->dwStyle
& TCS_VERTICAL
))
1695 /* focus should stay on selected item for TCS_BUTTONS style */
1696 if (!((infoPtr
->dwStyle
& TCS_BUTTONS
) && (infoPtr
->iSelected
!= iItem
)))
1697 DrawFocusRect(hdc
, &rFocus
);
1703 htextPen
= CreatePen( PS_SOLID
, 1, comctl32_color
.clrBtnText
);
1704 holdPen
= SelectObject(hdc
, htextPen
);
1705 hOldFont
= SelectObject(hdc
, infoPtr
->hFont
);
1708 * Setup for text output
1710 oldBkMode
= SetBkMode(hdc
, TRANSPARENT
);
1711 if (!GetWindowTheme (infoPtr
->hwnd
) || (infoPtr
->dwStyle
& TCS_BUTTONS
))
1713 if ((infoPtr
->dwStyle
& TCS_HOTTRACK
) && (iItem
== infoPtr
->iHotTracked
) &&
1714 !(infoPtr
->dwStyle
& TCS_FLATBUTTONS
))
1715 SetTextColor(hdc
, comctl32_color
.clrHighlight
);
1716 else if (TAB_GetItem(infoPtr
, iItem
)->dwState
& TCIS_HIGHLIGHTED
)
1717 SetTextColor(hdc
, comctl32_color
.clrHighlightText
);
1719 SetTextColor(hdc
, comctl32_color
.clrBtnText
);
1723 * if owner draw, tell the owner to draw
1725 if ((infoPtr
->dwStyle
& TCS_OWNERDRAWFIXED
) && IsWindow(infoPtr
->hwndNotify
))
1731 drawRect
->right
-= 1;
1732 if ( iItem
== infoPtr
->iSelected
)
1734 drawRect
->right
-= 1;
1735 drawRect
->left
+= 1;
1738 id
= (UINT
)GetWindowLongPtrW( infoPtr
->hwnd
, GWLP_ID
);
1740 /* fill DRAWITEMSTRUCT */
1741 dis
.CtlType
= ODT_TAB
;
1744 dis
.itemAction
= ODA_DRAWENTIRE
;
1746 if ( iItem
== infoPtr
->iSelected
)
1747 dis
.itemState
|= ODS_SELECTED
;
1748 if (infoPtr
->uFocus
== iItem
)
1749 dis
.itemState
|= ODS_FOCUS
;
1750 dis
.hwndItem
= infoPtr
->hwnd
;
1752 CopyRect(&dis
.rcItem
,drawRect
);
1754 /* when extra data fits ULONG_PTR, store it directly */
1755 if (infoPtr
->cbInfo
> sizeof(LPARAM
))
1756 dis
.itemData
= (ULONG_PTR
) TAB_GetItem(infoPtr
, iItem
)->extra
;
1759 /* this could be considered broken on 64 bit, but that's how it works -
1760 only first 4 bytes are copied */
1762 memcpy(&dis
.itemData
, (ULONG_PTR
*)TAB_GetItem(infoPtr
, iItem
)->extra
, 4);
1765 /* draw notification */
1766 SendMessageW( infoPtr
->hwndNotify
, WM_DRAWITEM
, id
, (LPARAM
)&dis
);
1770 TAB_ITEM
*item
= TAB_GetItem(infoPtr
, iItem
);
1774 /* used to center the icon and text in the tab */
1776 INT center_offset_h
, center_offset_v
;
1778 /* set rcImage to drawRect, we will use top & left in our ImageList_Draw call */
1779 rcImage
= *drawRect
;
1783 rcText
.left
= rcText
.top
= rcText
.right
= rcText
.bottom
= 0;
1785 /* get the rectangle that the text fits in */
1788 DrawTextW(hdc
, item
->pszText
, -1, &rcText
, DT_CALCRECT
);
1791 * If not owner draw, then do the drawing ourselves.
1795 if (infoPtr
->himl
&& item
->iImage
!= -1)
1800 ImageList_GetIconSize(infoPtr
->himl
, &cx
, &cy
);
1802 if(infoPtr
->dwStyle
& TCS_VERTICAL
)
1804 center_offset_h
= ((drawRect
->bottom
- drawRect
->top
) - (cy
+ infoPtr
->uHItemPadding
+ (rcText
.right
- rcText
.left
))) / 2;
1805 center_offset_v
= ((drawRect
->right
- drawRect
->left
) - cx
) / 2;
1809 center_offset_h
= ((drawRect
->right
- drawRect
->left
) - (cx
+ infoPtr
->uHItemPadding
+ (rcText
.right
- rcText
.left
))) / 2;
1810 center_offset_v
= ((drawRect
->bottom
- drawRect
->top
) - cy
) / 2;
1813 /* if an item is selected, the icon is shifted up instead of down */
1814 if (iItem
== infoPtr
->iSelected
)
1815 center_offset_v
-= infoPtr
->uVItemPadding
/ 2;
1817 center_offset_v
+= infoPtr
->uVItemPadding
/ 2;
1819 if (infoPtr
->dwStyle
& TCS_FIXEDWIDTH
&& infoPtr
->dwStyle
& (TCS_FORCELABELLEFT
| TCS_FORCEICONLEFT
))
1820 center_offset_h
= infoPtr
->uHItemPadding
;
1822 if (center_offset_h
< 2)
1823 center_offset_h
= 2;
1825 if (center_offset_v
< 0)
1826 center_offset_v
= 0;
1828 TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%s), textlen=%d\n",
1829 debugstr_w(item
->pszText
), center_offset_h
, center_offset_v
,
1830 wine_dbgstr_rect(drawRect
), (rcText
.right
-rcText
.left
));
1832 if((infoPtr
->dwStyle
& TCS_VERTICAL
) && (infoPtr
->dwStyle
& TCS_BOTTOM
))
1834 rcImage
.top
= drawRect
->top
+ center_offset_h
;
1835 /* if tab is TCS_VERTICAL and TCS_BOTTOM, the text is drawn from the */
1836 /* right side of the tab, but the image still uses the left as its x position */
1837 /* this keeps the image always drawn off of the same side of the tab */
1838 rcImage
.left
= drawRect
->right
- cx
- center_offset_v
;
1839 drawRect
->top
+= cy
+ infoPtr
->uHItemPadding
;
1841 else if(infoPtr
->dwStyle
& TCS_VERTICAL
)
1843 rcImage
.top
= drawRect
->bottom
- cy
- center_offset_h
;
1844 rcImage
.left
= drawRect
->left
+ center_offset_v
;
1845 drawRect
->bottom
-= cy
+ infoPtr
->uHItemPadding
;
1847 else /* normal style, whether TCS_BOTTOM or not */
1849 rcImage
.left
= drawRect
->left
+ center_offset_h
;
1850 rcImage
.top
= drawRect
->top
+ center_offset_v
;
1851 drawRect
->left
+= cx
+ infoPtr
->uHItemPadding
;
1854 TRACE("drawing image=%d, left=%d, top=%d\n",
1855 item
->iImage
, rcImage
.left
, rcImage
.top
-1);
1867 /* Now position text */
1868 if (infoPtr
->dwStyle
& TCS_FIXEDWIDTH
&& infoPtr
->dwStyle
& TCS_FORCELABELLEFT
)
1869 center_offset_h
= infoPtr
->uHItemPadding
;
1871 if(infoPtr
->dwStyle
& TCS_VERTICAL
)
1872 center_offset_h
= ((drawRect
->bottom
- drawRect
->top
) - (rcText
.right
- rcText
.left
)) / 2;
1874 center_offset_h
= ((drawRect
->right
- drawRect
->left
) - (rcText
.right
- rcText
.left
)) / 2;
1876 if(infoPtr
->dwStyle
& TCS_VERTICAL
)
1878 if(infoPtr
->dwStyle
& TCS_BOTTOM
)
1879 drawRect
->top
+=center_offset_h
;
1881 drawRect
->bottom
-=center_offset_h
;
1883 center_offset_v
= ((drawRect
->right
- drawRect
->left
) - (rcText
.bottom
- rcText
.top
)) / 2;
1887 drawRect
->left
+= center_offset_h
;
1888 center_offset_v
= ((drawRect
->bottom
- drawRect
->top
) - (rcText
.bottom
- rcText
.top
)) / 2;
1891 /* if an item is selected, the text is shifted up instead of down */
1892 if (iItem
== infoPtr
->iSelected
)
1893 center_offset_v
-= infoPtr
->uVItemPadding
/ 2;
1895 center_offset_v
+= infoPtr
->uVItemPadding
/ 2;
1897 if (center_offset_v
< 0)
1898 center_offset_v
= 0;
1900 if(infoPtr
->dwStyle
& TCS_VERTICAL
)
1901 drawRect
->left
+= center_offset_v
;
1903 drawRect
->top
+= center_offset_v
;
1906 if(infoPtr
->dwStyle
& TCS_VERTICAL
) /* if we are vertical rotate the text and each character */
1910 INT nEscapement
= 900;
1911 INT nOrientation
= 900;
1913 if(infoPtr
->dwStyle
& TCS_BOTTOM
)
1916 nOrientation
= -900;
1919 /* to get a font with the escapement and orientation we are looking for, we need to */
1920 /* call CreateFontIndirect, which requires us to set the values of the logfont we pass in */
1921 if (!GetObjectW(infoPtr
->hFont
, sizeof(logfont
), &logfont
))
1922 GetObjectW(GetStockObject(DEFAULT_GUI_FONT
), sizeof(logfont
), &logfont
);
1924 logfont
.lfEscapement
= nEscapement
;
1925 logfont
.lfOrientation
= nOrientation
;
1926 hFont
= CreateFontIndirectW(&logfont
);
1927 SelectObject(hdc
, hFont
);
1932 (infoPtr
->dwStyle
& TCS_BOTTOM
) ? drawRect
->right
: drawRect
->left
,
1933 (!(infoPtr
->dwStyle
& TCS_BOTTOM
)) ? drawRect
->bottom
: drawRect
->top
,
1937 lstrlenW(item
->pszText
),
1941 DeleteObject(hFont
);
1945 TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%s), textlen=%d\n",
1946 debugstr_w(item
->pszText
), center_offset_h
, center_offset_v
,
1947 wine_dbgstr_rect(drawRect
), (rcText
.right
-rcText
.left
));
1954 lstrlenW(item
->pszText
),
1956 DT_LEFT
| DT_SINGLELINE
1961 *drawRect
= rcTemp
; /* restore drawRect */
1967 SelectObject(hdc
, hOldFont
);
1968 SetBkMode(hdc
, oldBkMode
);
1969 SelectObject(hdc
, holdPen
);
1970 DeleteObject( htextPen
);
1973 /******************************************************************************
1976 * This method is used to draw a single tab into the tab control.
1978 static void TAB_DrawItem(const TAB_INFO
*infoPtr
, HDC hdc
, INT iItem
)
1983 RECT r
, fillRect
, r1
;
1986 COLORREF bkgnd
, corner
;
1990 * Get the rectangle for the item.
1992 isVisible
= TAB_InternalGetItemRect(infoPtr
,
2001 /* Clip UpDown control to not draw over it */
2002 if (infoPtr
->needsScrolling
)
2004 GetWindowRect(infoPtr
->hwnd
, &rC
);
2005 GetWindowRect(infoPtr
->hwndUpDown
, &rUD
);
2006 ExcludeClipRect(hdc
, rUD
.left
- rC
.left
, rUD
.top
- rC
.top
, rUD
.right
- rC
.left
, rUD
.bottom
- rC
.top
);
2009 /* If you need to see what the control is doing,
2010 * then override these variables. They will change what
2011 * fill colors are used for filling the tabs, and the
2012 * corners when drawing the edge.
2014 bkgnd
= comctl32_color
.clrBtnFace
;
2015 corner
= comctl32_color
.clrBtnFace
;
2017 if (infoPtr
->dwStyle
& TCS_BUTTONS
)
2019 /* Get item rectangle */
2022 /* Separators between flat buttons */
2023 if ((infoPtr
->dwStyle
& TCS_FLATBUTTONS
) && (infoPtr
->exStyle
& TCS_EX_FLATSEPARATORS
))
2026 r1
.right
+= (FLAT_BTN_SPACINGX
-2);
2027 DrawEdge(hdc
, &r1
, EDGE_ETCHED
, BF_RIGHT
);
2030 if (iItem
== infoPtr
->iSelected
)
2032 DrawEdge(hdc
, &r
, EDGE_SUNKEN
, BF_SOFT
|BF_RECT
);
2034 OffsetRect(&r
, 1, 1);
2036 else /* ! selected */
2038 DWORD state
= TAB_GetItem(infoPtr
, iItem
)->dwState
;
2040 if ((state
& TCIS_BUTTONPRESSED
) || (iItem
== infoPtr
->uFocus
))
2041 DrawEdge(hdc
, &r
, EDGE_SUNKEN
, BF_SOFT
|BF_RECT
);
2043 if (!(infoPtr
->dwStyle
& TCS_FLATBUTTONS
))
2044 DrawEdge(hdc
, &r
, EDGE_RAISED
, BF_SOFT
|BF_RECT
);
2047 else /* !TCS_BUTTONS */
2049 /* We draw a rectangle of different sizes depending on the selection
2051 if (iItem
== infoPtr
->iSelected
) {
2053 GetClientRect (infoPtr
->hwnd
, &rect
);
2054 clRight
= rect
.right
;
2055 clBottom
= rect
.bottom
;
2062 * Erase the background. (Delay it but setup rectangle.)
2063 * This is necessary when drawing the selected item since it is larger
2064 * than the others, it might overlap with stuff already drawn by the
2069 /* Draw themed tabs - but only if they are at the top.
2070 * Windows draws even side or bottom tabs themed, with wacky results.
2071 * However, since in Wine apps may get themed that did not opt in via
2072 * a manifest avoid theming when we know the result will be wrong */
2073 if ((theme
= GetWindowTheme (infoPtr
->hwnd
))
2074 && ((infoPtr
->dwStyle
& (TCS_VERTICAL
| TCS_BOTTOM
)) == 0))
2076 static const int partIds
[8] = {
2079 TABP_TABITEMLEFTEDGE
,
2080 TABP_TABITEMRIGHTEDGE
,
2081 TABP_TABITEMBOTHEDGE
,
2084 TABP_TOPTABITEMLEFTEDGE
,
2085 TABP_TOPTABITEMRIGHTEDGE
,
2086 TABP_TOPTABITEMBOTHEDGE
,
2089 int stateId
= TIS_NORMAL
;
2091 /* selected and unselected tabs have different parts */
2092 if (iItem
== infoPtr
->iSelected
)
2094 /* The part also differs on the position of a tab on a line.
2095 * "Visually" determining the position works well enough. */
2096 GetClientRect(infoPtr
->hwnd
, &r1
);
2097 if(selectedRect
.left
== 0)
2099 if(selectedRect
.right
== r1
.right
)
2102 if (iItem
== infoPtr
->iSelected
)
2103 stateId
= TIS_SELECTED
;
2104 else if (iItem
== infoPtr
->iHotTracked
)
2106 else if (iItem
== infoPtr
->uFocus
)
2107 stateId
= TIS_FOCUSED
;
2109 /* Adjust rectangle for bottommost row */
2110 if (TAB_GetItem(infoPtr
, iItem
)->rect
.top
== infoPtr
->uNumRows
-1)
2113 DrawThemeBackground (theme
, hdc
, partIds
[partIndex
], stateId
, &r
, NULL
);
2114 GetThemeBackgroundContentRect (theme
, hdc
, partIds
[partIndex
], stateId
, &r
, &r
);
2116 else if(infoPtr
->dwStyle
& TCS_VERTICAL
)
2118 /* These are for adjusting the drawing of a Selected tab */
2119 /* The initial values are for the normal case of non-Selected */
2120 int ZZ
= 1; /* Do not stretch if selected */
2121 if (iItem
== infoPtr
->iSelected
) {
2124 /* if leftmost draw the line longer */
2125 if(selectedRect
.top
== 0)
2126 fillRect
.top
+= CONTROL_BORDER_SIZEY
;
2127 /* if rightmost draw the line longer */
2128 if(selectedRect
.bottom
== clBottom
)
2129 fillRect
.bottom
-= CONTROL_BORDER_SIZEY
;
2132 if (infoPtr
->dwStyle
& TCS_BOTTOM
)
2134 /* Adjust both rectangles to match native */
2137 TRACE("<right> item=%d, fill=(%s), edge=(%s)\n",
2138 iItem
, wine_dbgstr_rect(&fillRect
), wine_dbgstr_rect(&r
));
2140 /* Clear interior */
2141 SetBkColor(hdc
, bkgnd
);
2142 ExtTextOutW(hdc
, 0, 0, 2, &fillRect
, NULL
, 0, 0);
2144 /* Draw rectangular edge around tab */
2145 DrawEdge(hdc
, &r
, EDGE_RAISED
, BF_SOFT
|BF_RIGHT
|BF_TOP
|BF_BOTTOM
);
2147 /* Now erase the top corner and draw diagonal edge */
2148 SetBkColor(hdc
, corner
);
2149 r1
.left
= r
.right
- ROUND_CORNER_SIZE
- 1;
2152 r1
.bottom
= r1
.top
+ ROUND_CORNER_SIZE
;
2153 ExtTextOutW(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2155 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_DIAGONAL_ENDTOPLEFT
);
2157 /* Now erase the bottom corner and draw diagonal edge */
2158 r1
.left
= r
.right
- ROUND_CORNER_SIZE
- 1;
2159 r1
.bottom
= r
.bottom
;
2161 r1
.top
= r1
.bottom
- ROUND_CORNER_SIZE
;
2162 ExtTextOutW(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2164 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_DIAGONAL_ENDBOTTOMLEFT
);
2166 if ((iItem
== infoPtr
->iSelected
) && (selectedRect
.top
== 0)) {
2170 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_TOP
);
2176 TRACE("<left> item=%d, fill=(%s), edge=(%s)\n",
2177 iItem
, wine_dbgstr_rect(&fillRect
), wine_dbgstr_rect(&r
));
2179 /* Clear interior */
2180 SetBkColor(hdc
, bkgnd
);
2181 ExtTextOutW(hdc
, 0, 0, 2, &fillRect
, NULL
, 0, 0);
2183 /* Draw rectangular edge around tab */
2184 DrawEdge(hdc
, &r
, EDGE_RAISED
, BF_SOFT
|BF_LEFT
|BF_TOP
|BF_BOTTOM
);
2186 /* Now erase the top corner and draw diagonal edge */
2187 SetBkColor(hdc
, corner
);
2190 r1
.right
= r1
.left
+ ROUND_CORNER_SIZE
+ 1;
2191 r1
.bottom
= r1
.top
+ ROUND_CORNER_SIZE
;
2192 ExtTextOutW(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2194 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_DIAGONAL_ENDTOPRIGHT
);
2196 /* Now erase the bottom corner and draw diagonal edge */
2198 r1
.bottom
= r
.bottom
;
2199 r1
.right
= r1
.left
+ ROUND_CORNER_SIZE
+ 1;
2200 r1
.top
= r1
.bottom
- ROUND_CORNER_SIZE
;
2201 ExtTextOutW(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2203 DrawEdge(hdc
, &r1
, EDGE_SUNKEN
, BF_DIAGONAL_ENDTOPLEFT
);
2206 else /* ! TCS_VERTICAL */
2208 /* These are for adjusting the drawing of a Selected tab */
2209 /* The initial values are for the normal case of non-Selected */
2210 if (iItem
== infoPtr
->iSelected
) {
2211 /* if leftmost draw the line longer */
2212 if(selectedRect
.left
== 0)
2213 fillRect
.left
+= CONTROL_BORDER_SIZEX
;
2214 /* if rightmost draw the line longer */
2215 if(selectedRect
.right
== clRight
)
2216 fillRect
.right
-= CONTROL_BORDER_SIZEX
;
2219 if (infoPtr
->dwStyle
& TCS_BOTTOM
)
2221 /* Adjust both rectangles for topmost row */
2222 if (TAB_GetItem(infoPtr
, iItem
)->rect
.top
== infoPtr
->uNumRows
-1)
2228 TRACE("<bottom> item=%d, fill=(%s), edge=(%s)\n",
2229 iItem
, wine_dbgstr_rect(&fillRect
), wine_dbgstr_rect(&r
));
2231 /* Clear interior */
2232 SetBkColor(hdc
, bkgnd
);
2233 ExtTextOutW(hdc
, 0, 0, 2, &fillRect
, NULL
, 0, 0);
2235 /* Draw rectangular edge around tab */
2236 DrawEdge(hdc
, &r
, EDGE_RAISED
, BF_SOFT
|BF_LEFT
|BF_BOTTOM
|BF_RIGHT
);
2238 /* Now erase the righthand corner and draw diagonal edge */
2239 SetBkColor(hdc
, corner
);
2240 r1
.left
= r
.right
- ROUND_CORNER_SIZE
;
2241 r1
.bottom
= r
.bottom
;
2243 r1
.top
= r1
.bottom
- ROUND_CORNER_SIZE
- 1;
2244 ExtTextOutW(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2246 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_DIAGONAL_ENDBOTTOMLEFT
);
2248 /* Now erase the lefthand corner and draw diagonal edge */
2250 r1
.bottom
= r
.bottom
;
2251 r1
.right
= r1
.left
+ ROUND_CORNER_SIZE
;
2252 r1
.top
= r1
.bottom
- ROUND_CORNER_SIZE
- 1;
2253 ExtTextOutW(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2255 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_DIAGONAL_ENDTOPLEFT
);
2257 if (iItem
== infoPtr
->iSelected
)
2261 if (selectedRect
.left
== 0)
2266 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_LEFT
);
2273 /* Adjust both rectangles for bottommost row */
2274 if (TAB_GetItem(infoPtr
, iItem
)->rect
.top
== infoPtr
->uNumRows
-1)
2276 fillRect
.bottom
+= 3;
2280 TRACE("<top> item=%d, fill=(%s), edge=(%s)\n",
2281 iItem
, wine_dbgstr_rect(&fillRect
), wine_dbgstr_rect(&r
));
2283 /* Clear interior */
2284 SetBkColor(hdc
, bkgnd
);
2285 ExtTextOutW(hdc
, 0, 0, 2, &fillRect
, NULL
, 0, 0);
2287 /* Draw rectangular edge around tab */
2288 DrawEdge(hdc
, &r
, EDGE_RAISED
, BF_SOFT
|BF_LEFT
|BF_TOP
|BF_RIGHT
);
2290 /* Now erase the righthand corner and draw diagonal edge */
2291 SetBkColor(hdc
, corner
);
2292 r1
.left
= r
.right
- ROUND_CORNER_SIZE
;
2295 r1
.bottom
= r1
.top
+ ROUND_CORNER_SIZE
+ 1;
2296 ExtTextOutW(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2298 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_DIAGONAL_ENDBOTTOMRIGHT
);
2300 /* Now erase the lefthand corner and draw diagonal edge */
2303 r1
.right
= r1
.left
+ ROUND_CORNER_SIZE
;
2304 r1
.bottom
= r1
.top
+ ROUND_CORNER_SIZE
+ 1;
2305 ExtTextOutW(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2307 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_DIAGONAL_ENDTOPRIGHT
);
2312 TAB_DumpItemInternal(infoPtr
, iItem
);
2314 /* This modifies r to be the text rectangle. */
2315 TAB_DrawItemInterior(infoPtr
, hdc
, iItem
, &r
);
2319 /******************************************************************************
2322 * This method is used to draw the raised border around the tab control
2325 static void TAB_DrawBorder(const TAB_INFO
*infoPtr
, HDC hdc
)
2328 HTHEME theme
= GetWindowTheme (infoPtr
->hwnd
);
2330 GetClientRect (infoPtr
->hwnd
, &rect
);
2333 * Adjust for the style
2336 if (infoPtr
->uNumItem
)
2338 if ((infoPtr
->dwStyle
& TCS_BOTTOM
) && !(infoPtr
->dwStyle
& TCS_VERTICAL
))
2339 rect
.bottom
-= infoPtr
->tabHeight
* infoPtr
->uNumRows
+ CONTROL_BORDER_SIZEX
;
2340 else if((infoPtr
->dwStyle
& TCS_BOTTOM
) && (infoPtr
->dwStyle
& TCS_VERTICAL
))
2341 rect
.right
-= infoPtr
->tabHeight
* infoPtr
->uNumRows
+ CONTROL_BORDER_SIZEX
;
2342 else if(infoPtr
->dwStyle
& TCS_VERTICAL
)
2343 rect
.left
+= infoPtr
->tabHeight
* infoPtr
->uNumRows
+ CONTROL_BORDER_SIZEX
;
2344 else /* not TCS_VERTICAL and not TCS_BOTTOM */
2345 rect
.top
+= infoPtr
->tabHeight
* infoPtr
->uNumRows
+ CONTROL_BORDER_SIZEX
;
2348 TRACE("border=(%s)\n", wine_dbgstr_rect(&rect
));
2351 DrawThemeBackground (theme
, hdc
, TABP_PANE
, 0, &rect
, NULL
);
2353 DrawEdge(hdc
, &rect
, EDGE_RAISED
, BF_SOFT
|BF_RECT
);
2356 /******************************************************************************
2359 * This method repaints the tab control..
2361 static void TAB_Refresh (const TAB_INFO
*infoPtr
, HDC hdc
)
2366 if (!infoPtr
->DoRedraw
)
2369 hOldFont
= SelectObject (hdc
, infoPtr
->hFont
);
2371 if (infoPtr
->dwStyle
& TCS_BUTTONS
)
2373 for (i
= 0; i
< infoPtr
->uNumItem
; i
++)
2374 TAB_DrawItem (infoPtr
, hdc
, i
);
2378 /* Draw all the non selected item first */
2379 for (i
= 0; i
< infoPtr
->uNumItem
; i
++)
2381 if (i
!= infoPtr
->iSelected
)
2382 TAB_DrawItem (infoPtr
, hdc
, i
);
2385 /* Now, draw the border, draw it before the selected item
2386 * since the selected item overwrites part of the border. */
2387 TAB_DrawBorder (infoPtr
, hdc
);
2389 /* Then, draw the selected item */
2390 TAB_DrawItem (infoPtr
, hdc
, infoPtr
->iSelected
);
2393 SelectObject (hdc
, hOldFont
);
2396 static inline DWORD
TAB_GetRowCount (const TAB_INFO
*infoPtr
)
2398 TRACE("(%p)\n", infoPtr
);
2399 return infoPtr
->uNumRows
;
2402 static inline LRESULT
TAB_SetRedraw (TAB_INFO
*infoPtr
, BOOL doRedraw
)
2404 infoPtr
->DoRedraw
= doRedraw
;
2408 /******************************************************************************
2409 * TAB_EnsureSelectionVisible
2411 * This method will make sure that the current selection is completely
2412 * visible by scrolling until it is.
2414 static void TAB_EnsureSelectionVisible(
2417 INT iSelected
= infoPtr
->iSelected
;
2418 INT iOrigLeftmostVisible
= infoPtr
->leftmostVisible
;
2423 /* set the items row to the bottommost row or topmost row depending on
2425 if ((infoPtr
->uNumRows
> 1) && !(infoPtr
->dwStyle
& TCS_BUTTONS
))
2427 TAB_ITEM
*selected
= TAB_GetItem(infoPtr
, iSelected
);
2431 if(infoPtr
->dwStyle
& TCS_VERTICAL
)
2432 newselected
= selected
->rect
.left
;
2434 newselected
= selected
->rect
.top
;
2436 /* the target row is always (number of rows - 1)
2437 as row 0 is furthest from the clientRect */
2438 iTargetRow
= infoPtr
->uNumRows
- 1;
2440 if (newselected
!= iTargetRow
)
2443 if(infoPtr
->dwStyle
& TCS_VERTICAL
)
2445 for (i
=0; i
< infoPtr
->uNumItem
; i
++)
2447 /* move everything in the row of the selected item to the iTargetRow */
2448 TAB_ITEM
*item
= TAB_GetItem(infoPtr
, i
);
2450 if (item
->rect
.left
== newselected
)
2451 item
->rect
.left
= iTargetRow
;
2454 if (item
->rect
.left
> newselected
)
2461 for (i
=0; i
< infoPtr
->uNumItem
; i
++)
2463 TAB_ITEM
*item
= TAB_GetItem(infoPtr
, i
);
2465 if (item
->rect
.top
== newselected
)
2466 item
->rect
.top
= iTargetRow
;
2469 if (item
->rect
.top
> newselected
)
2474 TAB_RecalcHotTrack(infoPtr
, NULL
, NULL
, NULL
);
2479 * Do the trivial cases first.
2481 if ( (!infoPtr
->needsScrolling
) ||
2482 (infoPtr
->hwndUpDown
==0) || (infoPtr
->dwStyle
& TCS_VERTICAL
))
2485 if (infoPtr
->leftmostVisible
>= iSelected
)
2487 infoPtr
->leftmostVisible
= iSelected
;
2491 TAB_ITEM
*selected
= TAB_GetItem(infoPtr
, iSelected
);
2496 /* Calculate the part of the client area that is visible */
2497 GetClientRect(infoPtr
->hwnd
, &r
);
2500 GetClientRect(infoPtr
->hwndUpDown
, &r
);
2503 if ((selected
->rect
.right
-
2504 selected
->rect
.left
) >= width
)
2506 /* Special case: width of selected item is greater than visible
2509 infoPtr
->leftmostVisible
= iSelected
;
2513 for (i
= infoPtr
->leftmostVisible
; i
< infoPtr
->uNumItem
; i
++)
2515 if ((selected
->rect
.right
- TAB_GetItem(infoPtr
, i
)->rect
.left
) < width
)
2518 infoPtr
->leftmostVisible
= i
;
2522 if (infoPtr
->leftmostVisible
!= iOrigLeftmostVisible
)
2523 TAB_RecalcHotTrack(infoPtr
, NULL
, NULL
, NULL
);
2525 SendMessageW(infoPtr
->hwndUpDown
, UDM_SETPOS
, 0,
2526 MAKELONG(infoPtr
->leftmostVisible
, 0));
2529 /******************************************************************************
2530 * TAB_InvalidateTabArea
2532 * This method will invalidate the portion of the control that contains the
2533 * tabs. It is called when the state of the control changes and needs
2536 static void TAB_InvalidateTabArea(const TAB_INFO
*infoPtr
)
2538 RECT clientRect
, rInvalidate
, rAdjClient
;
2539 INT lastRow
= infoPtr
->uNumRows
- 1;
2542 if (lastRow
< 0) return;
2544 GetClientRect(infoPtr
->hwnd
, &clientRect
);
2545 rInvalidate
= clientRect
;
2546 rAdjClient
= clientRect
;
2548 TAB_AdjustRect(infoPtr
, 0, &rAdjClient
);
2550 TAB_InternalGetItemRect(infoPtr
, infoPtr
->uNumItem
-1 , &rect
, NULL
);
2551 if ((infoPtr
->dwStyle
& TCS_BOTTOM
) && (infoPtr
->dwStyle
& TCS_VERTICAL
))
2553 rInvalidate
.left
= rAdjClient
.right
;
2554 if (infoPtr
->uNumRows
== 1)
2555 rInvalidate
.bottom
= clientRect
.top
+ rect
.bottom
+ 2 * SELECTED_TAB_OFFSET
;
2557 else if(infoPtr
->dwStyle
& TCS_VERTICAL
)
2559 rInvalidate
.right
= rAdjClient
.left
;
2560 if (infoPtr
->uNumRows
== 1)
2561 rInvalidate
.bottom
= clientRect
.top
+ rect
.bottom
+ 2 * SELECTED_TAB_OFFSET
;
2563 else if (infoPtr
->dwStyle
& TCS_BOTTOM
)
2565 rInvalidate
.top
= rAdjClient
.bottom
;
2566 if (infoPtr
->uNumRows
== 1)
2567 rInvalidate
.right
= clientRect
.left
+ rect
.right
+ 2 * SELECTED_TAB_OFFSET
;
2571 rInvalidate
.bottom
= rAdjClient
.top
;
2572 if (infoPtr
->uNumRows
== 1)
2573 rInvalidate
.right
= clientRect
.left
+ rect
.right
+ 2 * SELECTED_TAB_OFFSET
;
2576 /* Punch out the updown control */
2577 if (infoPtr
->needsScrolling
&& (rInvalidate
.right
> 0)) {
2579 GetClientRect(infoPtr
->hwndUpDown
, &r
);
2580 if (rInvalidate
.right
> clientRect
.right
- r
.left
)
2581 rInvalidate
.right
= rInvalidate
.right
- (r
.right
- r
.left
);
2583 rInvalidate
.right
= clientRect
.right
- r
.left
;
2586 TRACE("invalidate (%s)\n", wine_dbgstr_rect(&rInvalidate
));
2588 InvalidateRect(infoPtr
->hwnd
, &rInvalidate
, TRUE
);
2591 static inline LRESULT
TAB_Paint (TAB_INFO
*infoPtr
, HDC hdcPaint
)
2600 hdc
= BeginPaint (infoPtr
->hwnd
, &ps
);
2601 TRACE("erase %d, rect=(%s)\n", ps
.fErase
, wine_dbgstr_rect(&ps
.rcPaint
));
2604 TAB_Refresh (infoPtr
, hdc
);
2607 EndPaint (infoPtr
->hwnd
, &ps
);
2613 TAB_InsertItemT (TAB_INFO
*infoPtr
, INT iItem
, const TCITEMW
*pti
, BOOL bUnicode
)
2618 GetClientRect (infoPtr
->hwnd
, &rect
);
2619 TRACE("Rect: %p %s\n", infoPtr
->hwnd
, wine_dbgstr_rect(&rect
));
2621 if (iItem
< 0) return -1;
2622 if (iItem
> infoPtr
->uNumItem
)
2623 iItem
= infoPtr
->uNumItem
;
2625 TAB_DumpItemExternalT(pti
, iItem
, bUnicode
);
2627 if (!(item
= Alloc(TAB_ITEM_SIZE(infoPtr
)))) return FALSE
;
2628 if (DPA_InsertPtr(infoPtr
->items
, iItem
, item
) == -1)
2634 if (infoPtr
->uNumItem
== 0)
2635 infoPtr
->iSelected
= 0;
2636 else if (iItem
<= infoPtr
->iSelected
)
2637 infoPtr
->iSelected
++;
2639 infoPtr
->uNumItem
++;
2641 item
->pszText
= NULL
;
2642 if (pti
->mask
& TCIF_TEXT
)
2645 Str_SetPtrW (&item
->pszText
, pti
->pszText
);
2647 Str_SetPtrAtoW (&item
->pszText
, (LPSTR
)pti
->pszText
);
2650 if (pti
->mask
& TCIF_IMAGE
)
2651 item
->iImage
= pti
->iImage
;
2655 if (pti
->mask
& TCIF_PARAM
)
2656 memcpy(item
->extra
, &pti
->lParam
, EXTRA_ITEM_SIZE(infoPtr
));
2658 memset(item
->extra
, 0, EXTRA_ITEM_SIZE(infoPtr
));
2660 TAB_SetItemBounds(infoPtr
);
2661 if (infoPtr
->uNumItem
> 1)
2662 TAB_InvalidateTabArea(infoPtr
);
2664 InvalidateRect(infoPtr
->hwnd
, NULL
, TRUE
);
2666 TRACE("[%p]: added item %d %s\n",
2667 infoPtr
->hwnd
, iItem
, debugstr_w(item
->pszText
));
2669 /* If we haven't set the current focus yet, set it now. */
2670 if (infoPtr
->uFocus
== -1)
2671 TAB_SetCurFocus(infoPtr
, iItem
);
2677 TAB_SetItemSize (TAB_INFO
*infoPtr
, INT cx
, INT cy
)
2680 BOOL bNeedPaint
= FALSE
;
2682 lResult
= MAKELONG(infoPtr
->tabWidth
, infoPtr
->tabHeight
);
2684 /* UNDOCUMENTED: If requested Width or Height is 0 this means that program wants to use auto size. */
2685 if (infoPtr
->dwStyle
& TCS_FIXEDWIDTH
&& (infoPtr
->tabWidth
!= cx
))
2687 infoPtr
->tabWidth
= cx
;
2691 if (infoPtr
->tabHeight
!= cy
)
2693 if ((infoPtr
->fHeightSet
= (cy
!= 0)))
2694 infoPtr
->tabHeight
= cy
;
2698 TRACE("was h=%d,w=%d, now h=%d,w=%d\n",
2699 HIWORD(lResult
), LOWORD(lResult
),
2700 infoPtr
->tabHeight
, infoPtr
->tabWidth
);
2704 TAB_SetItemBounds(infoPtr
);
2705 RedrawWindow(infoPtr
->hwnd
, NULL
, NULL
, RDW_ERASE
| RDW_INVALIDATE
| RDW_UPDATENOW
);
2711 static inline LRESULT
TAB_SetMinTabWidth (TAB_INFO
*infoPtr
, INT cx
)
2715 TRACE("(%p,%d)\n", infoPtr
, cx
);
2717 if (infoPtr
->tabMinWidth
< 0)
2718 oldcx
= DEFAULT_MIN_TAB_WIDTH
;
2720 oldcx
= infoPtr
->tabMinWidth
;
2721 infoPtr
->tabMinWidth
= cx
;
2722 TAB_SetItemBounds(infoPtr
);
2726 static inline LRESULT
2727 TAB_HighlightItem (TAB_INFO
*infoPtr
, INT iItem
, BOOL fHighlight
)
2733 TRACE("(%p,%d,%s)\n", infoPtr
, iItem
, fHighlight
? "true" : "false");
2735 if (iItem
< 0 || iItem
>= infoPtr
->uNumItem
)
2738 lpState
= &TAB_GetItem(infoPtr
, iItem
)->dwState
;
2739 oldState
= *lpState
;
2742 *lpState
|= TCIS_HIGHLIGHTED
;
2744 *lpState
&= ~TCIS_HIGHLIGHTED
;
2746 if ((oldState
!= *lpState
) && TAB_InternalGetItemRect (infoPtr
, iItem
, &r
, NULL
))
2747 InvalidateRect (infoPtr
->hwnd
, &r
, TRUE
);
2753 TAB_SetItemT (TAB_INFO
*infoPtr
, INT iItem
, LPTCITEMW tabItem
, BOOL bUnicode
)
2757 TRACE("(%p,%d,%p,%s)\n", infoPtr
, iItem
, tabItem
, bUnicode
? "true" : "false");
2759 if (iItem
< 0 || iItem
>= infoPtr
->uNumItem
)
2762 TAB_DumpItemExternalT(tabItem
, iItem
, bUnicode
);
2764 wineItem
= TAB_GetItem(infoPtr
, iItem
);
2766 if (tabItem
->mask
& TCIF_IMAGE
)
2767 wineItem
->iImage
= tabItem
->iImage
;
2769 if (tabItem
->mask
& TCIF_PARAM
)
2770 memcpy(wineItem
->extra
, &tabItem
->lParam
, infoPtr
->cbInfo
);
2772 if (tabItem
->mask
& TCIF_RTLREADING
)
2773 FIXME("TCIF_RTLREADING\n");
2775 if (tabItem
->mask
& TCIF_STATE
)
2776 wineItem
->dwState
= (wineItem
->dwState
& ~tabItem
->dwStateMask
) |
2777 ( tabItem
->dwState
& tabItem
->dwStateMask
);
2779 if (tabItem
->mask
& TCIF_TEXT
)
2781 Free(wineItem
->pszText
);
2782 wineItem
->pszText
= NULL
;
2784 Str_SetPtrW(&wineItem
->pszText
, tabItem
->pszText
);
2786 Str_SetPtrAtoW(&wineItem
->pszText
, (LPSTR
)tabItem
->pszText
);
2789 /* Update and repaint tabs */
2790 TAB_SetItemBounds(infoPtr
);
2791 TAB_InvalidateTabArea(infoPtr
);
2796 static inline LRESULT
TAB_GetItemCount (const TAB_INFO
*infoPtr
)
2799 return infoPtr
->uNumItem
;
2804 TAB_GetItemT (TAB_INFO
*infoPtr
, INT iItem
, LPTCITEMW tabItem
, BOOL bUnicode
)
2808 TRACE("(%p,%d,%p,%s)\n", infoPtr
, iItem
, tabItem
, bUnicode
? "true" : "false");
2810 if (!tabItem
) return FALSE
;
2812 if (iItem
< 0 || iItem
>= infoPtr
->uNumItem
)
2814 /* init requested fields */
2815 if (tabItem
->mask
& TCIF_IMAGE
) tabItem
->iImage
= 0;
2816 if (tabItem
->mask
& TCIF_PARAM
) tabItem
->lParam
= 0;
2817 if (tabItem
->mask
& TCIF_STATE
) tabItem
->dwState
= 0;
2821 wineItem
= TAB_GetItem(infoPtr
, iItem
);
2823 if (tabItem
->mask
& TCIF_IMAGE
)
2824 tabItem
->iImage
= wineItem
->iImage
;
2826 if (tabItem
->mask
& TCIF_PARAM
)
2827 memcpy(&tabItem
->lParam
, wineItem
->extra
, infoPtr
->cbInfo
);
2829 if (tabItem
->mask
& TCIF_RTLREADING
)
2830 FIXME("TCIF_RTLREADING\n");
2832 if (tabItem
->mask
& TCIF_STATE
)
2833 tabItem
->dwState
= wineItem
->dwState
& tabItem
->dwStateMask
;
2835 if (tabItem
->mask
& TCIF_TEXT
)
2838 Str_GetPtrW (wineItem
->pszText
, tabItem
->pszText
, tabItem
->cchTextMax
);
2840 Str_GetPtrWtoA (wineItem
->pszText
, (LPSTR
)tabItem
->pszText
, tabItem
->cchTextMax
);
2843 TAB_DumpItemExternalT(tabItem
, iItem
, bUnicode
);
2849 static LRESULT
TAB_DeleteItem (TAB_INFO
*infoPtr
, INT iItem
)
2853 TRACE("(%p, %d)\n", infoPtr
, iItem
);
2855 if (iItem
< 0 || iItem
>= infoPtr
->uNumItem
) return FALSE
;
2857 TAB_InvalidateTabArea(infoPtr
);
2858 item
= TAB_GetItem(infoPtr
, iItem
);
2859 Free(item
->pszText
);
2861 infoPtr
->uNumItem
--;
2862 DPA_DeletePtr(infoPtr
->items
, iItem
);
2864 if (infoPtr
->uNumItem
== 0)
2866 if (infoPtr
->iHotTracked
>= 0)
2868 KillTimer(infoPtr
->hwnd
, TAB_HOTTRACK_TIMER
);
2869 infoPtr
->iHotTracked
= -1;
2872 infoPtr
->iSelected
= -1;
2876 if (iItem
<= infoPtr
->iHotTracked
)
2878 /* When tabs move left/up, the hot track item may change */
2879 FIXME("Recalc hot track\n");
2883 /* adjust the selected index */
2884 if (iItem
== infoPtr
->iSelected
)
2885 infoPtr
->iSelected
= -1;
2886 else if (iItem
< infoPtr
->iSelected
)
2887 infoPtr
->iSelected
--;
2889 /* reposition and repaint tabs */
2890 TAB_SetItemBounds(infoPtr
);
2895 static inline LRESULT
TAB_DeleteAllItems (TAB_INFO
*infoPtr
)
2897 TRACE("(%p)\n", infoPtr
);
2898 while (infoPtr
->uNumItem
)
2899 TAB_DeleteItem (infoPtr
, 0);
2904 static inline LRESULT
TAB_GetFont (const TAB_INFO
*infoPtr
)
2906 TRACE("(%p) returning %p\n", infoPtr
, infoPtr
->hFont
);
2907 return (LRESULT
)infoPtr
->hFont
;
2910 static inline LRESULT
TAB_SetFont (TAB_INFO
*infoPtr
, HFONT hNewFont
)
2912 TRACE("(%p,%p)\n", infoPtr
, hNewFont
);
2914 infoPtr
->hFont
= hNewFont
;
2916 TAB_SetItemBounds(infoPtr
);
2918 TAB_InvalidateTabArea(infoPtr
);
2924 static inline LRESULT
TAB_GetImageList (const TAB_INFO
*infoPtr
)
2927 return (LRESULT
)infoPtr
->himl
;
2930 static inline LRESULT
TAB_SetImageList (TAB_INFO
*infoPtr
, HIMAGELIST himlNew
)
2932 HIMAGELIST himlPrev
= infoPtr
->himl
;
2933 TRACE("himl=%p\n", himlNew
);
2934 infoPtr
->himl
= himlNew
;
2935 TAB_SetItemBounds(infoPtr
);
2936 InvalidateRect(infoPtr
->hwnd
, NULL
, TRUE
);
2937 return (LRESULT
)himlPrev
;
2940 static inline LRESULT
TAB_GetUnicodeFormat (const TAB_INFO
*infoPtr
)
2942 TRACE("(%p)\n", infoPtr
);
2943 return infoPtr
->bUnicode
;
2946 static inline LRESULT
TAB_SetUnicodeFormat (TAB_INFO
*infoPtr
, BOOL bUnicode
)
2948 BOOL bTemp
= infoPtr
->bUnicode
;
2950 TRACE("(%p %d)\n", infoPtr
, bUnicode
);
2951 infoPtr
->bUnicode
= bUnicode
;
2956 static inline LRESULT
TAB_Size (TAB_INFO
*infoPtr
)
2958 /* I'm not really sure what the following code was meant to do.
2959 This is what it is doing:
2960 When WM_SIZE is sent with SIZE_RESTORED, the control
2961 gets positioned in the top left corner.
2965 UINT uPosFlags,cx,cy;
2969 parent = GetParent (hwnd);
2970 GetClientRect(parent, &parent_rect);
2973 if (GetWindowLongW(hwnd, GWL_STYLE) & CCS_NORESIZE)
2974 uPosFlags |= (SWP_NOSIZE | SWP_NOMOVE);
2976 SetWindowPos (hwnd, 0, parent_rect.left, parent_rect.top,
2977 cx, cy, uPosFlags | SWP_NOZORDER);
2979 FIXME("WM_SIZE flag %x %lx not handled\n", wParam, lParam);
2982 /* Recompute the size/position of the tabs. */
2983 TAB_SetItemBounds (infoPtr
);
2985 /* Force a repaint of the control. */
2986 InvalidateRect(infoPtr
->hwnd
, NULL
, TRUE
);
2992 static LRESULT
TAB_Create (HWND hwnd
, LPARAM lParam
)
2995 TEXTMETRICW fontMetrics
;
3000 infoPtr
= Alloc (sizeof(TAB_INFO
));
3002 SetWindowLongPtrW(hwnd
, 0, (DWORD_PTR
)infoPtr
);
3004 infoPtr
->hwnd
= hwnd
;
3005 infoPtr
->hwndNotify
= ((LPCREATESTRUCTW
)lParam
)->hwndParent
;
3006 infoPtr
->uNumItem
= 0;
3007 infoPtr
->uNumRows
= 0;
3008 infoPtr
->uHItemPadding
= 6;
3009 infoPtr
->uVItemPadding
= 3;
3010 infoPtr
->uHItemPadding_s
= 6;
3011 infoPtr
->uVItemPadding_s
= 3;
3013 infoPtr
->items
= DPA_Create(8);
3014 infoPtr
->hcurArrow
= LoadCursorW (0, (LPWSTR
)IDC_ARROW
);
3015 infoPtr
->iSelected
= -1;
3016 infoPtr
->iHotTracked
= -1;
3017 infoPtr
->uFocus
= -1;
3018 infoPtr
->hwndToolTip
= 0;
3019 infoPtr
->DoRedraw
= TRUE
;
3020 infoPtr
->needsScrolling
= FALSE
;
3021 infoPtr
->hwndUpDown
= 0;
3022 infoPtr
->leftmostVisible
= 0;
3023 infoPtr
->fHeightSet
= FALSE
;
3024 infoPtr
->bUnicode
= IsWindowUnicode (hwnd
);
3025 infoPtr
->cbInfo
= sizeof(LPARAM
);
3027 TRACE("Created tab control, hwnd [%p]\n", hwnd
);
3029 /* The tab control always has the WS_CLIPSIBLINGS style. Even
3030 if you don't specify it in CreateWindow. This is necessary in
3031 order for paint to work correctly. This follows windows behaviour. */
3032 style
= GetWindowLongW(hwnd
, GWL_STYLE
);
3033 if (style
& TCS_VERTICAL
) style
|= TCS_MULTILINE
;
3034 style
|= WS_CLIPSIBLINGS
;
3035 SetWindowLongW(hwnd
, GWL_STYLE
, style
);
3037 infoPtr
->dwStyle
= style
;
3038 infoPtr
->exStyle
= (style
& TCS_FLATBUTTONS
) ? TCS_EX_FLATSEPARATORS
: 0;
3040 if (infoPtr
->dwStyle
& TCS_TOOLTIPS
) {
3041 /* Create tooltip control */
3042 infoPtr
->hwndToolTip
=
3043 CreateWindowExW (0, TOOLTIPS_CLASSW
, NULL
, WS_POPUP
,
3044 CW_USEDEFAULT
, CW_USEDEFAULT
,
3045 CW_USEDEFAULT
, CW_USEDEFAULT
,
3048 /* Send NM_TOOLTIPSCREATED notification */
3049 if (infoPtr
->hwndToolTip
) {
3050 NMTOOLTIPSCREATED nmttc
;
3052 nmttc
.hdr
.hwndFrom
= hwnd
;
3053 nmttc
.hdr
.idFrom
= GetWindowLongPtrW(hwnd
, GWLP_ID
);
3054 nmttc
.hdr
.code
= NM_TOOLTIPSCREATED
;
3055 nmttc
.hwndToolTips
= infoPtr
->hwndToolTip
;
3057 SendMessageW (infoPtr
->hwndNotify
, WM_NOTIFY
,
3058 GetWindowLongPtrW(hwnd
, GWLP_ID
), (LPARAM
)&nmttc
);
3062 OpenThemeData (infoPtr
->hwnd
, themeClass
);
3065 * We need to get text information so we need a DC and we need to select
3069 hOldFont
= SelectObject (hdc
, GetStockObject (SYSTEM_FONT
));
3071 /* Use the system font to determine the initial height of a tab. */
3072 GetTextMetricsW(hdc
, &fontMetrics
);
3075 * Make sure there is enough space for the letters + growing the
3076 * selected item + extra space for the selected item.
3078 infoPtr
->tabHeight
= fontMetrics
.tmHeight
+ SELECTED_TAB_OFFSET
+
3079 ((infoPtr
->dwStyle
& TCS_BUTTONS
) ? 2 : 1) *
3080 infoPtr
->uVItemPadding
;
3082 /* Initialize the width of a tab. */
3083 if (infoPtr
->dwStyle
& TCS_FIXEDWIDTH
)
3084 infoPtr
->tabWidth
= GetDeviceCaps(hdc
, LOGPIXELSX
);
3086 infoPtr
->tabMinWidth
= -1;
3088 TRACE("tabH=%d, tabW=%d\n", infoPtr
->tabHeight
, infoPtr
->tabWidth
);
3090 SelectObject (hdc
, hOldFont
);
3091 ReleaseDC(hwnd
, hdc
);
3097 TAB_Destroy (TAB_INFO
*infoPtr
)
3101 SetWindowLongPtrW(infoPtr
->hwnd
, 0, 0);
3103 for (iItem
= infoPtr
->uNumItem
- 1; iItem
>= 0; iItem
--)
3105 TAB_ITEM
*tab
= TAB_GetItem(infoPtr
, iItem
);
3107 DPA_DeletePtr(infoPtr
->items
, iItem
);
3108 infoPtr
->uNumItem
--;
3113 DPA_Destroy(infoPtr
->items
);
3114 infoPtr
->items
= NULL
;
3116 if (infoPtr
->hwndToolTip
)
3117 DestroyWindow (infoPtr
->hwndToolTip
);
3119 if (infoPtr
->hwndUpDown
)
3120 DestroyWindow(infoPtr
->hwndUpDown
);
3122 if (infoPtr
->iHotTracked
>= 0)
3123 KillTimer(infoPtr
->hwnd
, TAB_HOTTRACK_TIMER
);
3125 CloseThemeData (GetWindowTheme (infoPtr
->hwnd
));
3131 /* update theme after a WM_THEMECHANGED message */
3132 static LRESULT
theme_changed(const TAB_INFO
*infoPtr
)
3134 HTHEME theme
= GetWindowTheme (infoPtr
->hwnd
);
3135 CloseThemeData (theme
);
3136 OpenThemeData (infoPtr
->hwnd
, themeClass
);
3140 static LRESULT
TAB_NCCalcSize(WPARAM wParam
)
3144 return WVR_ALIGNTOP
;
3147 static inline LRESULT
3148 TAB_SetItemExtra (TAB_INFO
*infoPtr
, INT cbInfo
)
3150 TRACE("(%p %d)\n", infoPtr
, cbInfo
);
3152 if (cbInfo
< 0 || infoPtr
->uNumItem
) return FALSE
;
3154 infoPtr
->cbInfo
= cbInfo
;
3158 static LRESULT
TAB_RemoveImage (TAB_INFO
*infoPtr
, INT image
)
3160 TRACE("%p %d\n", infoPtr
, image
);
3162 if (ImageList_Remove (infoPtr
->himl
, image
))
3167 /* shift indices, repaint items if needed */
3168 for (i
= 0; i
< infoPtr
->uNumItem
; i
++)
3170 idx
= &TAB_GetItem(infoPtr
, i
)->iImage
;
3179 if (TAB_InternalGetItemRect (infoPtr
, i
, &r
, NULL
))
3180 InvalidateRect (infoPtr
->hwnd
, &r
, TRUE
);
3189 TAB_SetExtendedStyle (TAB_INFO
*infoPtr
, DWORD exMask
, DWORD exStyle
)
3191 DWORD prevstyle
= infoPtr
->exStyle
;
3193 /* zero mask means all styles */
3194 if (exMask
== 0) exMask
= ~0;
3196 if (exMask
& TCS_EX_REGISTERDROP
)
3198 FIXME("TCS_EX_REGISTERDROP style unimplemented\n");
3199 exMask
&= ~TCS_EX_REGISTERDROP
;
3200 exStyle
&= ~TCS_EX_REGISTERDROP
;
3203 if (exMask
& TCS_EX_FLATSEPARATORS
)
3205 if ((prevstyle
^ exStyle
) & TCS_EX_FLATSEPARATORS
)
3207 infoPtr
->exStyle
^= TCS_EX_FLATSEPARATORS
;
3208 TAB_InvalidateTabArea(infoPtr
);
3215 static inline LRESULT
3216 TAB_GetExtendedStyle (const TAB_INFO
*infoPtr
)
3218 return infoPtr
->exStyle
;
3222 TAB_DeselectAll (TAB_INFO
*infoPtr
, BOOL excludesel
)
3225 INT i
, selected
= infoPtr
->iSelected
;
3227 TRACE("(%p, %d)\n", infoPtr
, excludesel
);
3229 if (!(infoPtr
->dwStyle
& TCS_BUTTONS
))
3232 for (i
= 0; i
< infoPtr
->uNumItem
; i
++)
3234 if ((TAB_GetItem(infoPtr
, i
)->dwState
& TCIS_BUTTONPRESSED
) &&
3237 TAB_GetItem(infoPtr
, i
)->dwState
&= ~TCIS_BUTTONPRESSED
;
3242 if (!excludesel
&& (selected
!= -1))
3244 TAB_GetItem(infoPtr
, selected
)->dwState
&= ~TCIS_BUTTONPRESSED
;
3245 infoPtr
->iSelected
= -1;
3250 TAB_InvalidateTabArea (infoPtr
);
3257 * Processes WM_STYLECHANGED messages.
3260 * [I] infoPtr : valid pointer to the tab data structure
3261 * [I] wStyleType : window style type (normal or extended)
3262 * [I] lpss : window style information
3267 static INT
TAB_StyleChanged(TAB_INFO
*infoPtr
, WPARAM wStyleType
,
3268 const STYLESTRUCT
*lpss
)
3270 TRACE("(styletype=%lx, styleOld=0x%08x, styleNew=0x%08x)\n",
3271 wStyleType
, lpss
->styleOld
, lpss
->styleNew
);
3273 if (wStyleType
!= GWL_STYLE
) return 0;
3275 infoPtr
->dwStyle
= lpss
->styleNew
;
3277 TAB_SetItemBounds (infoPtr
);
3278 InvalidateRect(infoPtr
->hwnd
, NULL
, TRUE
);
3283 static LRESULT WINAPI
3284 TAB_WindowProc (HWND hwnd
, UINT uMsg
, WPARAM wParam
, LPARAM lParam
)
3286 TAB_INFO
*infoPtr
= TAB_GetInfoPtr(hwnd
);
3288 TRACE("hwnd=%p msg=%x wParam=%lx lParam=%lx\n", hwnd
, uMsg
, wParam
, lParam
);
3289 if (!infoPtr
&& (uMsg
!= WM_CREATE
))
3290 return DefWindowProcW (hwnd
, uMsg
, wParam
, lParam
);
3294 case TCM_GETIMAGELIST
:
3295 return TAB_GetImageList (infoPtr
);
3297 case TCM_SETIMAGELIST
:
3298 return TAB_SetImageList (infoPtr
, (HIMAGELIST
)lParam
);
3300 case TCM_GETITEMCOUNT
:
3301 return TAB_GetItemCount (infoPtr
);
3305 return TAB_GetItemT (infoPtr
, (INT
)wParam
, (LPTCITEMW
)lParam
, uMsg
== TCM_GETITEMW
);
3309 return TAB_SetItemT (infoPtr
, (INT
)wParam
, (LPTCITEMW
)lParam
, uMsg
== TCM_SETITEMW
);
3311 case TCM_DELETEITEM
:
3312 return TAB_DeleteItem (infoPtr
, (INT
)wParam
);
3314 case TCM_DELETEALLITEMS
:
3315 return TAB_DeleteAllItems (infoPtr
);
3317 case TCM_GETITEMRECT
:
3318 return TAB_GetItemRect (infoPtr
, (INT
)wParam
, (LPRECT
)lParam
);
3321 return TAB_GetCurSel (infoPtr
);
3324 return TAB_HitTest (infoPtr
, (LPTCHITTESTINFO
)lParam
);
3327 return TAB_SetCurSel (infoPtr
, (INT
)wParam
);
3329 case TCM_INSERTITEMA
:
3330 case TCM_INSERTITEMW
:
3331 return TAB_InsertItemT (infoPtr
, (INT
)wParam
, (TCITEMW
*)lParam
, uMsg
== TCM_INSERTITEMW
);
3333 case TCM_SETITEMEXTRA
:
3334 return TAB_SetItemExtra (infoPtr
, (INT
)wParam
);
3336 case TCM_ADJUSTRECT
:
3337 return TAB_AdjustRect (infoPtr
, (BOOL
)wParam
, (LPRECT
)lParam
);
3339 case TCM_SETITEMSIZE
:
3340 return TAB_SetItemSize (infoPtr
, (INT
)LOWORD(lParam
), (INT
)HIWORD(lParam
));
3342 case TCM_REMOVEIMAGE
:
3343 return TAB_RemoveImage (infoPtr
, (INT
)wParam
);
3345 case TCM_SETPADDING
:
3346 return TAB_SetPadding (infoPtr
, lParam
);
3348 case TCM_GETROWCOUNT
:
3349 return TAB_GetRowCount(infoPtr
);
3351 case TCM_GETUNICODEFORMAT
:
3352 return TAB_GetUnicodeFormat (infoPtr
);
3354 case TCM_SETUNICODEFORMAT
:
3355 return TAB_SetUnicodeFormat (infoPtr
, (BOOL
)wParam
);
3357 case TCM_HIGHLIGHTITEM
:
3358 return TAB_HighlightItem (infoPtr
, (INT
)wParam
, (BOOL
)LOWORD(lParam
));
3360 case TCM_GETTOOLTIPS
:
3361 return TAB_GetToolTips (infoPtr
);
3363 case TCM_SETTOOLTIPS
:
3364 return TAB_SetToolTips (infoPtr
, (HWND
)wParam
);
3366 case TCM_GETCURFOCUS
:
3367 return TAB_GetCurFocus (infoPtr
);
3369 case TCM_SETCURFOCUS
:
3370 return TAB_SetCurFocus (infoPtr
, (INT
)wParam
);
3372 case TCM_SETMINTABWIDTH
:
3373 return TAB_SetMinTabWidth(infoPtr
, (INT
)lParam
);
3375 case TCM_DESELECTALL
:
3376 return TAB_DeselectAll (infoPtr
, (BOOL
)wParam
);
3378 case TCM_GETEXTENDEDSTYLE
:
3379 return TAB_GetExtendedStyle (infoPtr
);
3381 case TCM_SETEXTENDEDSTYLE
:
3382 return TAB_SetExtendedStyle (infoPtr
, wParam
, lParam
);
3385 return TAB_GetFont (infoPtr
);
3388 return TAB_SetFont (infoPtr
, (HFONT
)wParam
);
3391 return TAB_Create (hwnd
, lParam
);
3394 return TAB_Destroy (infoPtr
);
3397 return DLGC_WANTARROWS
| DLGC_WANTCHARS
;
3399 case WM_LBUTTONDOWN
:
3400 return TAB_LButtonDown (infoPtr
, wParam
, lParam
);
3403 return TAB_LButtonUp (infoPtr
);
3406 return SendMessageW(infoPtr
->hwndNotify
, WM_NOTIFY
, wParam
, lParam
);
3409 TAB_RButtonUp (infoPtr
);
3410 return DefWindowProcW (hwnd
, uMsg
, wParam
, lParam
);
3413 return TAB_MouseMove (infoPtr
, wParam
, lParam
);
3415 case WM_PRINTCLIENT
:
3417 return TAB_Paint (infoPtr
, (HDC
)wParam
);
3420 return TAB_Size (infoPtr
);
3423 return TAB_SetRedraw (infoPtr
, (BOOL
)wParam
);
3426 return TAB_OnHScroll(infoPtr
, (int)LOWORD(wParam
), (int)HIWORD(wParam
));
3428 case WM_STYLECHANGED
:
3429 return TAB_StyleChanged(infoPtr
, wParam
, (LPSTYLESTRUCT
)lParam
);
3431 case WM_SYSCOLORCHANGE
:
3432 COMCTL32_RefreshSysColors();
3435 case WM_THEMECHANGED
:
3436 return theme_changed (infoPtr
);
3439 TAB_KillFocus(infoPtr
);
3441 TAB_FocusChanging(infoPtr
);
3442 break; /* Don't disturb normal focus behavior */
3445 return TAB_KeyDown(infoPtr
, wParam
, lParam
);
3448 return TAB_NCHitTest(infoPtr
, lParam
);
3451 return TAB_NCCalcSize(wParam
);
3454 if (uMsg
>= WM_USER
&& uMsg
< WM_APP
&& !COMCTL32_IsReflectedMessage(uMsg
))
3455 WARN("unknown msg %04x wp=%08lx lp=%08lx\n",
3456 uMsg
, wParam
, lParam
);
3459 return DefWindowProcW(hwnd
, uMsg
, wParam
, lParam
);
3468 ZeroMemory (&wndClass
, sizeof(WNDCLASSW
));
3469 wndClass
.style
= CS_GLOBALCLASS
| CS_DBLCLKS
| CS_HREDRAW
| CS_VREDRAW
;
3470 wndClass
.lpfnWndProc
= TAB_WindowProc
;
3471 wndClass
.cbClsExtra
= 0;
3472 wndClass
.cbWndExtra
= sizeof(TAB_INFO
*);
3473 wndClass
.hCursor
= LoadCursorW (0, (LPWSTR
)IDC_ARROW
);
3474 wndClass
.hbrBackground
= (HBRUSH
)(COLOR_BTNFACE
+1);
3475 wndClass
.lpszClassName
= WC_TABCONTROLW
;
3477 RegisterClassW (&wndClass
);
3482 TAB_Unregister (void)
3484 UnregisterClassW (WC_TABCONTROLW
, NULL
);