4 * Copyright 1998 Anders Carlsson
5 * Copyright 1999 Alex Priem <alexp@sci.kun.nl>
6 * Copyright 1999 Francis Beaudet
7 * Copyright 2003 Vitaliy Margolen
9 * This library is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Lesser General Public
11 * License as published by the Free Software Foundation; either
12 * version 2.1 of the License, or (at your option) any later version.
14 * This library is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Lesser General Public License for more details.
19 * You should have received a copy of the GNU Lesser General Public
20 * License along with this library; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
25 * This code was audited for completeness against the documented features
26 * of Comctl32.dll version 6.0 on May. 20, 2005, by James Hawkins.
28 * Unless otherwise noted, we believe this code to be complete, as per
29 * the specification mentioned above.
30 * If you discover missing features, or bugs, please note them below.
35 * TCS_MULTISELECT - implement for VK_SPACE selection
57 WINE_DEFAULT_DEBUG_CHANNEL(tab
);
64 RECT rect
; /* bounding rectangle of the item relative to the
65 * leftmost item (the leftmost item, 0, would have a
66 * "left" member of 0 in this rectangle)
68 * additionally the top member holds the row number
69 * and bottom is unused and should be 0 */
70 BYTE extra
[1]; /* Space for caller supplied info, variable size */
73 /* The size of a tab item depends on how much extra data is requested.
74 TCM_INSERTITEM always stores at least LPARAM sized data. */
75 #define EXTRA_ITEM_SIZE(infoPtr) (max((infoPtr)->cbInfo, sizeof(LPARAM)))
76 #define TAB_ITEM_SIZE(infoPtr) FIELD_OFFSET(TAB_ITEM, extra[EXTRA_ITEM_SIZE(infoPtr)])
80 HWND hwnd
; /* Tab control window */
81 HWND hwndNotify
; /* notification window (parent) */
82 UINT uNumItem
; /* number of tab items */
83 UINT uNumRows
; /* number of tab rows */
84 INT tabHeight
; /* height of the tab row */
85 INT tabWidth
; /* width of tabs */
86 INT tabMinWidth
; /* minimum width of items */
87 USHORT uHItemPadding
; /* amount of horizontal padding, in pixels */
88 USHORT uVItemPadding
; /* amount of vertical padding, in pixels */
89 USHORT uHItemPadding_s
; /* Set amount of horizontal padding, in pixels */
90 USHORT uVItemPadding_s
; /* Set amount of vertical padding, in pixels */
91 HFONT hFont
; /* handle to the current font */
92 HCURSOR hcurArrow
; /* handle to the current cursor */
93 HIMAGELIST himl
; /* handle to an image list (may be 0) */
94 HWND hwndToolTip
; /* handle to tab's tooltip */
95 INT leftmostVisible
; /* Used for scrolling, this member contains
96 * the index of the first visible item */
97 INT iSelected
; /* the currently selected item */
98 INT iHotTracked
; /* the highlighted item under the mouse */
99 INT uFocus
; /* item which has the focus */
100 BOOL DoRedraw
; /* flag for redrawing when tab contents is changed*/
101 BOOL needsScrolling
; /* TRUE if the size of the tabs is greater than
102 * the size of the control */
103 BOOL fHeightSet
; /* was the height of the tabs explicitly set? */
104 BOOL bUnicode
; /* Unicode control? */
105 HWND hwndUpDown
; /* Updown control used for scrolling */
106 INT cbInfo
; /* Number of bytes of caller supplied info per tab */
108 DWORD exStyle
; /* Extended style used, currently:
109 TCS_EX_FLATSEPARATORS, TCS_EX_REGISTERDROP */
110 DWORD dwStyle
; /* the cached window GWL_STYLE */
112 HDPA items
; /* dynamic array of TAB_ITEM* pointers */
115 /******************************************************************************
116 * Positioning constants
118 #define SELECTED_TAB_OFFSET 2
119 #define ROUND_CORNER_SIZE 2
120 #define DISPLAY_AREA_PADDINGX 2
121 #define DISPLAY_AREA_PADDINGY 2
122 #define CONTROL_BORDER_SIZEX 2
123 #define CONTROL_BORDER_SIZEY 2
124 #define BUTTON_SPACINGX 3
125 #define BUTTON_SPACINGY 3
126 #define FLAT_BTN_SPACINGX 8
127 #define DEFAULT_MIN_TAB_WIDTH 54
128 #define DEFAULT_PADDING_X 6
129 #define EXTRA_ICON_PADDING 3
131 #define TAB_GetInfoPtr(hwnd) ((TAB_INFO *)GetWindowLongPtrW(hwnd,0))
133 #define GET_DEFAULT_MIN_TAB_WIDTH(infoPtr) (DEFAULT_MIN_TAB_WIDTH - (DEFAULT_PADDING_X - (infoPtr)->uHItemPadding) * 2)
135 /******************************************************************************
136 * Hot-tracking timer constants
138 #define TAB_HOTTRACK_TIMER 1
139 #define TAB_HOTTRACK_TIMER_INTERVAL 100 /* milliseconds */
141 static const WCHAR themeClass
[] = { 'T','a','b',0 };
143 static inline TAB_ITEM
* TAB_GetItem(const TAB_INFO
*infoPtr
, INT i
)
145 assert(i
>= 0 && i
< infoPtr
->uNumItem
);
146 return DPA_GetPtr(infoPtr
->items
, i
);
149 /******************************************************************************
152 static void TAB_InvalidateTabArea(const TAB_INFO
*);
153 static void TAB_EnsureSelectionVisible(TAB_INFO
*);
154 static void TAB_DrawItemInterior(const TAB_INFO
*, HDC
, INT
, RECT
*);
155 static LRESULT
TAB_DeselectAll(TAB_INFO
*, BOOL
);
156 static BOOL
TAB_InternalGetItemRect(const TAB_INFO
*, INT
, RECT
*, RECT
*);
159 TAB_SendSimpleNotify (const TAB_INFO
*infoPtr
, UINT code
)
163 nmhdr
.hwndFrom
= infoPtr
->hwnd
;
164 nmhdr
.idFrom
= GetWindowLongPtrW(infoPtr
->hwnd
, GWLP_ID
);
167 return (BOOL
) SendMessageW (infoPtr
->hwndNotify
, WM_NOTIFY
,
168 nmhdr
.idFrom
, (LPARAM
) &nmhdr
);
172 TAB_RelayEvent (HWND hwndTip
, HWND hwndMsg
, UINT uMsg
,
173 WPARAM wParam
, LPARAM lParam
)
181 msg
.time
= GetMessageTime ();
182 msg
.pt
.x
= (short)LOWORD(GetMessagePos ());
183 msg
.pt
.y
= (short)HIWORD(GetMessagePos ());
185 SendMessageW (hwndTip
, TTM_RELAYEVENT
, 0, (LPARAM
)&msg
);
189 TAB_DumpItemExternalT(const TCITEMW
*pti
, UINT iItem
, BOOL isW
)
192 TRACE("external tab %d, mask=0x%08x, dwState=0x%08x, dwStateMask=0x%08x, cchTextMax=0x%08x\n",
193 iItem
, pti
->mask
, pti
->dwState
, pti
->dwStateMask
, pti
->cchTextMax
);
194 TRACE("external tab %d, iImage=%d, lParam=0x%08lx, pszTextW=%s\n",
195 iItem
, pti
->iImage
, pti
->lParam
, isW
? debugstr_w(pti
->pszText
) : debugstr_a((LPSTR
)pti
->pszText
));
200 TAB_DumpItemInternal(const TAB_INFO
*infoPtr
, UINT iItem
)
203 TAB_ITEM
*ti
= TAB_GetItem(infoPtr
, iItem
);
205 TRACE("tab %d, dwState=0x%08x, pszText=%s, iImage=%d\n",
206 iItem
, ti
->dwState
, debugstr_w(ti
->pszText
), ti
->iImage
);
207 TRACE("tab %d, rect.left=%d, rect.top(row)=%d\n",
208 iItem
, ti
->rect
.left
, ti
->rect
.top
);
213 * the index of the selected tab, or -1 if no tab is selected. */
214 static inline LRESULT
TAB_GetCurSel (const TAB_INFO
*infoPtr
)
216 TRACE("(%p)\n", infoPtr
);
217 return infoPtr
->iSelected
;
221 * the index of the tab item that has the focus. */
222 static inline LRESULT
223 TAB_GetCurFocus (const TAB_INFO
*infoPtr
)
225 TRACE("(%p)\n", infoPtr
);
226 return infoPtr
->uFocus
;
229 static inline LRESULT
TAB_GetToolTips (const TAB_INFO
*infoPtr
)
231 TRACE("(%p)\n", infoPtr
);
232 return (LRESULT
)infoPtr
->hwndToolTip
;
235 static inline LRESULT
TAB_SetCurSel (TAB_INFO
*infoPtr
, INT iItem
)
237 INT prevItem
= infoPtr
->iSelected
;
239 TRACE("(%p %d)\n", infoPtr
, iItem
);
242 infoPtr
->iSelected
= -1;
243 else if (iItem
>= infoPtr
->uNumItem
)
246 if (prevItem
!= iItem
) {
248 TAB_GetItem(infoPtr
, prevItem
)->dwState
&= ~TCIS_BUTTONPRESSED
;
249 TAB_GetItem(infoPtr
, iItem
)->dwState
|= TCIS_BUTTONPRESSED
;
251 infoPtr
->iSelected
= iItem
;
252 infoPtr
->uFocus
= iItem
;
253 TAB_EnsureSelectionVisible(infoPtr
);
254 TAB_InvalidateTabArea(infoPtr
);
260 static LRESULT
TAB_SetCurFocus (TAB_INFO
*infoPtr
, INT iItem
)
262 TRACE("(%p %d)\n", infoPtr
, iItem
);
265 infoPtr
->uFocus
= -1;
266 if (infoPtr
->iSelected
!= -1) {
267 infoPtr
->iSelected
= -1;
268 TAB_SendSimpleNotify(infoPtr
, TCN_SELCHANGE
);
269 TAB_InvalidateTabArea(infoPtr
);
272 else if (iItem
< infoPtr
->uNumItem
) {
273 if (infoPtr
->dwStyle
& TCS_BUTTONS
) {
274 /* set focus to new item, leave selection as is */
275 if (infoPtr
->uFocus
!= iItem
) {
276 INT prev_focus
= infoPtr
->uFocus
;
279 infoPtr
->uFocus
= iItem
;
281 if (prev_focus
!= infoPtr
->iSelected
) {
282 if (TAB_InternalGetItemRect(infoPtr
, prev_focus
, &r
, NULL
))
283 InvalidateRect(infoPtr
->hwnd
, &r
, FALSE
);
286 if (TAB_InternalGetItemRect(infoPtr
, iItem
, &r
, NULL
))
287 InvalidateRect(infoPtr
->hwnd
, &r
, FALSE
);
289 TAB_SendSimpleNotify(infoPtr
, TCN_FOCUSCHANGE
);
292 INT oldFocus
= infoPtr
->uFocus
;
293 if (infoPtr
->iSelected
!= iItem
|| oldFocus
== -1 ) {
294 infoPtr
->uFocus
= iItem
;
295 if (oldFocus
!= -1) {
296 if (!TAB_SendSimpleNotify(infoPtr
, TCN_SELCHANGING
)) {
297 infoPtr
->iSelected
= iItem
;
298 TAB_SendSimpleNotify(infoPtr
, TCN_SELCHANGE
);
301 infoPtr
->iSelected
= iItem
;
302 TAB_EnsureSelectionVisible(infoPtr
);
303 TAB_InvalidateTabArea(infoPtr
);
311 static inline LRESULT
312 TAB_SetToolTips (TAB_INFO
*infoPtr
, HWND hwndToolTip
)
314 TRACE("%p %p\n", infoPtr
, hwndToolTip
);
315 infoPtr
->hwndToolTip
= hwndToolTip
;
319 static inline LRESULT
320 TAB_SetPadding (TAB_INFO
*infoPtr
, LPARAM lParam
)
322 TRACE("(%p %d %d)\n", infoPtr
, LOWORD(lParam
), HIWORD(lParam
));
323 infoPtr
->uHItemPadding_s
= LOWORD(lParam
);
324 infoPtr
->uVItemPadding_s
= HIWORD(lParam
);
329 /******************************************************************************
330 * TAB_InternalGetItemRect
332 * This method will calculate the rectangle representing a given tab item in
333 * client coordinates. This method takes scrolling into account.
335 * This method returns TRUE if the item is visible in the window and FALSE
336 * if it is completely outside the client area.
338 static BOOL
TAB_InternalGetItemRect(
339 const TAB_INFO
* infoPtr
,
344 RECT tmpItemRect
,clientRect
;
346 /* Perform a sanity check and a trivial visibility check. */
347 if ( (infoPtr
->uNumItem
<= 0) ||
348 (itemIndex
>= infoPtr
->uNumItem
) ||
349 (!(((infoPtr
->dwStyle
& TCS_MULTILINE
) || (infoPtr
->dwStyle
& TCS_VERTICAL
))) &&
350 (itemIndex
< infoPtr
->leftmostVisible
)))
352 TRACE("Not Visible\n");
353 /* need to initialize these to empty rects */
356 memset(itemRect
,0,sizeof(RECT
));
357 itemRect
->bottom
= infoPtr
->tabHeight
;
360 memset(selectedRect
,0,sizeof(RECT
));
365 * Avoid special cases in this procedure by assigning the "out"
366 * parameters if the caller didn't supply them
368 if (itemRect
== NULL
)
369 itemRect
= &tmpItemRect
;
371 /* Retrieve the unmodified item rect. */
372 *itemRect
= TAB_GetItem(infoPtr
,itemIndex
)->rect
;
374 /* calculate the times bottom and top based on the row */
375 GetClientRect(infoPtr
->hwnd
, &clientRect
);
377 if ((infoPtr
->dwStyle
& TCS_BOTTOM
) && (infoPtr
->dwStyle
& TCS_VERTICAL
))
379 itemRect
->right
= clientRect
.right
- SELECTED_TAB_OFFSET
- itemRect
->left
* infoPtr
->tabHeight
-
380 ((infoPtr
->dwStyle
& TCS_BUTTONS
) ? itemRect
->left
* BUTTON_SPACINGX
: 0);
381 itemRect
->left
= itemRect
->right
- infoPtr
->tabHeight
;
383 else if (infoPtr
->dwStyle
& TCS_VERTICAL
)
385 itemRect
->left
= clientRect
.left
+ SELECTED_TAB_OFFSET
+ itemRect
->left
* infoPtr
->tabHeight
+
386 ((infoPtr
->dwStyle
& TCS_BUTTONS
) ? itemRect
->left
* BUTTON_SPACINGX
: 0);
387 itemRect
->right
= itemRect
->left
+ infoPtr
->tabHeight
;
389 else if (infoPtr
->dwStyle
& TCS_BOTTOM
)
391 itemRect
->bottom
= clientRect
.bottom
- itemRect
->top
* infoPtr
->tabHeight
-
392 ((infoPtr
->dwStyle
& TCS_BUTTONS
) ? itemRect
->top
* BUTTON_SPACINGY
: SELECTED_TAB_OFFSET
);
393 itemRect
->top
= itemRect
->bottom
- infoPtr
->tabHeight
;
395 else /* not TCS_BOTTOM and not TCS_VERTICAL */
397 itemRect
->top
= clientRect
.top
+ itemRect
->top
* infoPtr
->tabHeight
+
398 ((infoPtr
->dwStyle
& TCS_BUTTONS
) ? itemRect
->top
* BUTTON_SPACINGY
: SELECTED_TAB_OFFSET
);
399 itemRect
->bottom
= itemRect
->top
+ infoPtr
->tabHeight
;
403 * "scroll" it to make sure the item at the very left of the
404 * tab control is the leftmost visible tab.
406 if(infoPtr
->dwStyle
& TCS_VERTICAL
)
410 -TAB_GetItem(infoPtr
, infoPtr
->leftmostVisible
)->rect
.top
);
413 * Move the rectangle so the first item is slightly offset from
414 * the bottom of the tab control.
418 SELECTED_TAB_OFFSET
);
423 -TAB_GetItem(infoPtr
, infoPtr
->leftmostVisible
)->rect
.left
,
427 * Move the rectangle so the first item is slightly offset from
428 * the left of the tab control.
434 TRACE("item %d tab h=%d, rect=(%s)\n",
435 itemIndex
, infoPtr
->tabHeight
, wine_dbgstr_rect(itemRect
));
437 /* Now, calculate the position of the item as if it were selected. */
438 if (selectedRect
!=NULL
)
440 CopyRect(selectedRect
, itemRect
);
442 /* The rectangle of a selected item is a bit wider. */
443 if(infoPtr
->dwStyle
& TCS_VERTICAL
)
444 InflateRect(selectedRect
, 0, SELECTED_TAB_OFFSET
);
446 InflateRect(selectedRect
, SELECTED_TAB_OFFSET
, 0);
448 /* If it also a bit higher. */
449 if ((infoPtr
->dwStyle
& TCS_BOTTOM
) && (infoPtr
->dwStyle
& TCS_VERTICAL
))
451 selectedRect
->left
-= 2; /* the border is thicker on the right */
452 selectedRect
->right
+= SELECTED_TAB_OFFSET
;
454 else if (infoPtr
->dwStyle
& TCS_VERTICAL
)
456 selectedRect
->left
-= SELECTED_TAB_OFFSET
;
457 selectedRect
->right
+= 1;
459 else if (infoPtr
->dwStyle
& TCS_BOTTOM
)
461 selectedRect
->bottom
+= SELECTED_TAB_OFFSET
;
463 else /* not TCS_BOTTOM and not TCS_VERTICAL */
465 selectedRect
->top
-= SELECTED_TAB_OFFSET
;
466 selectedRect
->bottom
-= 1;
470 /* Check for visibility */
471 if (infoPtr
->dwStyle
& TCS_VERTICAL
)
472 return (itemRect
->top
< clientRect
.bottom
) && (itemRect
->bottom
> clientRect
.top
);
474 return (itemRect
->left
< clientRect
.right
) && (itemRect
->right
> clientRect
.left
);
478 TAB_GetItemRect(const TAB_INFO
*infoPtr
, INT item
, RECT
*rect
)
480 TRACE("(%p, %d, %p)\n", infoPtr
, item
, rect
);
481 return TAB_InternalGetItemRect(infoPtr
, item
, rect
, NULL
);
484 /******************************************************************************
487 * This method is called to handle keyboard input
489 static LRESULT
TAB_KeyDown(TAB_INFO
* infoPtr
, WPARAM keyCode
, LPARAM lParam
)
494 /* TCN_KEYDOWN notification sent always */
495 nm
.hdr
.hwndFrom
= infoPtr
->hwnd
;
496 nm
.hdr
.idFrom
= GetWindowLongPtrW(infoPtr
->hwnd
, GWLP_ID
);
497 nm
.hdr
.code
= TCN_KEYDOWN
;
500 SendMessageW(infoPtr
->hwndNotify
, WM_NOTIFY
, nm
.hdr
.idFrom
, (LPARAM
)&nm
);
505 newItem
= infoPtr
->uFocus
- 1;
508 newItem
= infoPtr
->uFocus
+ 1;
512 /* If we changed to a valid item, change focused item */
513 if (newItem
>= 0 && newItem
< infoPtr
->uNumItem
&& infoPtr
->uFocus
!= newItem
)
514 TAB_SetCurFocus(infoPtr
, newItem
);
520 * WM_KILLFOCUS handler
522 static void TAB_KillFocus(TAB_INFO
*infoPtr
)
524 /* clear current focused item back to selected for TCS_BUTTONS */
525 if ((infoPtr
->dwStyle
& TCS_BUTTONS
) && (infoPtr
->uFocus
!= infoPtr
->iSelected
))
529 if (TAB_InternalGetItemRect(infoPtr
, infoPtr
->uFocus
, &r
, NULL
))
530 InvalidateRect(infoPtr
->hwnd
, &r
, FALSE
);
532 infoPtr
->uFocus
= infoPtr
->iSelected
;
536 /******************************************************************************
539 * This method is called whenever the focus goes in or out of this control
540 * it is used to update the visual state of the control.
542 static void TAB_FocusChanging(const TAB_INFO
*infoPtr
)
548 * Get the rectangle for the item.
550 isVisible
= TAB_InternalGetItemRect(infoPtr
,
556 * If the rectangle is not completely invisible, invalidate that
557 * portion of the window.
561 TRACE("invalidate (%s)\n", wine_dbgstr_rect(&selectedRect
));
562 InvalidateRect(infoPtr
->hwnd
, &selectedRect
, TRUE
);
566 static INT
TAB_InternalHitTest (const TAB_INFO
*infoPtr
, POINT pt
, UINT
*flags
)
571 for (iCount
= 0; iCount
< infoPtr
->uNumItem
; iCount
++)
573 TAB_InternalGetItemRect(infoPtr
, iCount
, &rect
, NULL
);
575 if (PtInRect(&rect
, pt
))
577 *flags
= TCHT_ONITEM
;
582 *flags
= TCHT_NOWHERE
;
586 static inline LRESULT
587 TAB_HitTest (const TAB_INFO
*infoPtr
, LPTCHITTESTINFO lptest
)
589 TRACE("(%p, %p)\n", infoPtr
, lptest
);
590 return TAB_InternalHitTest (infoPtr
, lptest
->pt
, &lptest
->flags
);
593 /******************************************************************************
596 * Napster v2b5 has a tab control for its main navigation which has a client
597 * area that covers the whole area of the dialog pages.
598 * That's why it receives all msgs for that area and the underlying dialog ctrls
600 * So I decided that we should handle WM_NCHITTEST here and return
601 * HTTRANSPARENT if we don't hit the tab control buttons.
602 * FIXME: WM_NCHITTEST handling correct ? Fix it if you know that Windows
603 * doesn't do it that way. Maybe depends on tab control styles ?
605 static inline LRESULT
606 TAB_NCHitTest (const TAB_INFO
*infoPtr
, LPARAM lParam
)
611 pt
.x
= (short)LOWORD(lParam
);
612 pt
.y
= (short)HIWORD(lParam
);
613 ScreenToClient(infoPtr
->hwnd
, &pt
);
615 if (TAB_InternalHitTest(infoPtr
, pt
, &dummyflag
) == -1)
616 return HTTRANSPARENT
;
622 TAB_LButtonDown (TAB_INFO
*infoPtr
, WPARAM wParam
, LPARAM lParam
)
628 if (infoPtr
->hwndToolTip
)
629 TAB_RelayEvent (infoPtr
->hwndToolTip
, infoPtr
->hwnd
,
630 WM_LBUTTONDOWN
, wParam
, lParam
);
632 if (!(infoPtr
->dwStyle
& TCS_FOCUSNEVER
)) {
633 SetFocus (infoPtr
->hwnd
);
636 if (infoPtr
->hwndToolTip
)
637 TAB_RelayEvent (infoPtr
->hwndToolTip
, infoPtr
->hwnd
,
638 WM_LBUTTONDOWN
, wParam
, lParam
);
640 pt
.x
= (short)LOWORD(lParam
);
641 pt
.y
= (short)HIWORD(lParam
);
643 newItem
= TAB_InternalHitTest (infoPtr
, pt
, &dummy
);
645 TRACE("On Tab, item %d\n", newItem
);
647 if ((newItem
!= -1) && (infoPtr
->iSelected
!= newItem
))
649 if ((infoPtr
->dwStyle
& TCS_BUTTONS
) && (infoPtr
->dwStyle
& TCS_MULTISELECT
) &&
650 (wParam
& MK_CONTROL
))
654 /* toggle multiselection */
655 TAB_GetItem(infoPtr
, newItem
)->dwState
^= TCIS_BUTTONPRESSED
;
656 if (TAB_InternalGetItemRect (infoPtr
, newItem
, &r
, NULL
))
657 InvalidateRect (infoPtr
->hwnd
, &r
, TRUE
);
662 BOOL pressed
= FALSE
;
664 /* any button pressed ? */
665 for (i
= 0; i
< infoPtr
->uNumItem
; i
++)
666 if ((TAB_GetItem (infoPtr
, i
)->dwState
& TCIS_BUTTONPRESSED
) &&
667 (infoPtr
->iSelected
!= i
))
673 if (TAB_SendSimpleNotify(infoPtr
, TCN_SELCHANGING
))
677 TAB_DeselectAll (infoPtr
, FALSE
);
679 TAB_SetCurSel(infoPtr
, newItem
);
681 TAB_SendSimpleNotify(infoPtr
, TCN_SELCHANGE
);
688 static inline LRESULT
689 TAB_LButtonUp (const TAB_INFO
*infoPtr
)
691 TAB_SendSimpleNotify(infoPtr
, NM_CLICK
);
697 TAB_RButtonUp (const TAB_INFO
*infoPtr
)
699 TAB_SendSimpleNotify(infoPtr
, NM_RCLICK
);
702 /******************************************************************************
703 * TAB_DrawLoneItemInterior
705 * This calls TAB_DrawItemInterior. However, TAB_DrawItemInterior is normally
706 * called by TAB_DrawItem which is normally called by TAB_Refresh which sets
707 * up the device context and font. This routine does the same setup but
708 * only calls TAB_DrawItemInterior for the single specified item.
711 TAB_DrawLoneItemInterior(const TAB_INFO
* infoPtr
, int iItem
)
713 HDC hdc
= GetDC(infoPtr
->hwnd
);
716 /* Clip UpDown control to not draw over it */
717 if (infoPtr
->needsScrolling
)
719 GetWindowRect(infoPtr
->hwnd
, &rC
);
720 GetWindowRect(infoPtr
->hwndUpDown
, &r
);
721 ExcludeClipRect(hdc
, r
.left
- rC
.left
, r
.top
- rC
.top
, r
.right
- rC
.left
, r
.bottom
- rC
.top
);
723 TAB_DrawItemInterior(infoPtr
, hdc
, iItem
, NULL
);
724 ReleaseDC(infoPtr
->hwnd
, hdc
);
727 /* update a tab after hottracking - invalidate it or just redraw the interior,
728 * based on whether theming is used or not */
729 static inline void hottrack_refresh(const TAB_INFO
*infoPtr
, int tabIndex
)
731 if (tabIndex
== -1) return;
733 if (GetWindowTheme (infoPtr
->hwnd
))
736 TAB_InternalGetItemRect(infoPtr
, tabIndex
, &rect
, NULL
);
737 InvalidateRect (infoPtr
->hwnd
, &rect
, FALSE
);
740 TAB_DrawLoneItemInterior(infoPtr
, tabIndex
);
743 /******************************************************************************
744 * TAB_HotTrackTimerProc
746 * When a mouse-move event causes a tab to be highlighted (hot-tracking), a
747 * timer is setup so we can check if the mouse is moved out of our window.
748 * (We don't get an event when the mouse leaves, the mouse-move events just
749 * stop being delivered to our window and just start being delivered to
750 * another window.) This function is called when the timer triggers so
751 * we can check if the mouse has left our window. If so, we un-highlight
752 * the hot-tracked tab.
755 TAB_HotTrackTimerProc
757 HWND hwnd
, /* handle of window for timer messages */
758 UINT uMsg
, /* WM_TIMER message */
759 UINT_PTR idEvent
, /* timer identifier */
760 DWORD dwTime
/* current system time */
763 TAB_INFO
* infoPtr
= TAB_GetInfoPtr(hwnd
);
765 if (infoPtr
!= NULL
&& infoPtr
->iHotTracked
>= 0)
770 ** If we can't get the cursor position, or if the cursor is outside our
771 ** window, we un-highlight the hot-tracked tab. Note that the cursor is
772 ** "outside" even if it is within our bounding rect if another window
773 ** overlaps. Note also that the case where the cursor stayed within our
774 ** window but has moved off the hot-tracked tab will be handled by the
775 ** WM_MOUSEMOVE event.
777 if (!GetCursorPos(&pt
) || WindowFromPoint(pt
) != hwnd
)
779 /* Redraw iHotTracked to look normal */
780 INT iRedraw
= infoPtr
->iHotTracked
;
781 infoPtr
->iHotTracked
= -1;
782 hottrack_refresh (infoPtr
, iRedraw
);
784 /* Kill this timer */
785 KillTimer(hwnd
, TAB_HOTTRACK_TIMER
);
790 /******************************************************************************
793 * If a tab control has the TCS_HOTTRACK style, then the tab under the mouse
794 * should be highlighted. This function determines which tab in a tab control,
795 * if any, is under the mouse and records that information. The caller may
796 * supply output parameters to receive the item number of the tab item which
797 * was highlighted but isn't any longer and of the tab item which is now
798 * highlighted but wasn't previously. The caller can use this information to
799 * selectively redraw those tab items.
801 * If the caller has a mouse position, it can supply it through the pos
802 * parameter. For example, TAB_MouseMove does this. Otherwise, the caller
803 * supplies NULL and this function determines the current mouse position
811 int* out_redrawLeave
,
818 if (out_redrawLeave
!= NULL
)
819 *out_redrawLeave
= -1;
820 if (out_redrawEnter
!= NULL
)
821 *out_redrawEnter
= -1;
823 if ((infoPtr
->dwStyle
& TCS_HOTTRACK
) || GetWindowTheme(infoPtr
->hwnd
))
831 ScreenToClient(infoPtr
->hwnd
, &pt
);
835 pt
.x
= (short)LOWORD(*pos
);
836 pt
.y
= (short)HIWORD(*pos
);
839 item
= TAB_InternalHitTest(infoPtr
, pt
, &flags
);
842 if (item
!= infoPtr
->iHotTracked
)
844 if (infoPtr
->iHotTracked
>= 0)
846 /* Mark currently hot-tracked to be redrawn to look normal */
847 if (out_redrawLeave
!= NULL
)
848 *out_redrawLeave
= infoPtr
->iHotTracked
;
852 /* Kill timer which forces recheck of mouse pos */
853 KillTimer(infoPtr
->hwnd
, TAB_HOTTRACK_TIMER
);
858 /* Start timer so we recheck mouse pos */
859 UINT timerID
= SetTimer
863 TAB_HOTTRACK_TIMER_INTERVAL
,
864 TAB_HotTrackTimerProc
868 return; /* Hot tracking not available */
871 infoPtr
->iHotTracked
= item
;
875 /* Mark new hot-tracked to be redrawn to look highlighted */
876 if (out_redrawEnter
!= NULL
)
877 *out_redrawEnter
= item
;
882 /******************************************************************************
885 * Handles the mouse-move event. Updates tooltips. Updates hot-tracking.
888 TAB_MouseMove (TAB_INFO
*infoPtr
, WPARAM wParam
, LPARAM lParam
)
893 if (infoPtr
->hwndToolTip
)
894 TAB_RelayEvent (infoPtr
->hwndToolTip
, infoPtr
->hwnd
,
895 WM_LBUTTONDOWN
, wParam
, lParam
);
897 /* Determine which tab to highlight. Redraw tabs which change highlight
899 TAB_RecalcHotTrack(infoPtr
, &lParam
, &redrawLeave
, &redrawEnter
);
901 hottrack_refresh (infoPtr
, redrawLeave
);
902 hottrack_refresh (infoPtr
, redrawEnter
);
907 /******************************************************************************
910 * Calculates the tab control's display area given the window rectangle or
911 * the window rectangle given the requested display rectangle.
913 static LRESULT
TAB_AdjustRect(const TAB_INFO
*infoPtr
, WPARAM fLarger
, LPRECT prc
)
915 LONG
*iRightBottom
, *iLeftTop
;
917 TRACE ("hwnd=%p fLarger=%ld (%s)\n", infoPtr
->hwnd
, fLarger
,
918 wine_dbgstr_rect(prc
));
922 if(infoPtr
->dwStyle
& TCS_VERTICAL
)
924 iRightBottom
= &(prc
->right
);
925 iLeftTop
= &(prc
->left
);
929 iRightBottom
= &(prc
->bottom
);
930 iLeftTop
= &(prc
->top
);
933 if (fLarger
) /* Go from display rectangle */
935 /* Add the height of the tabs. */
936 if (infoPtr
->dwStyle
& TCS_BOTTOM
)
937 *iRightBottom
+= infoPtr
->tabHeight
* infoPtr
->uNumRows
;
939 *iLeftTop
-= infoPtr
->tabHeight
* infoPtr
->uNumRows
+
940 ((infoPtr
->dwStyle
& TCS_BUTTONS
)? 3 * (infoPtr
->uNumRows
- 1) : 0);
942 /* Inflate the rectangle for the padding */
943 InflateRect(prc
, DISPLAY_AREA_PADDINGX
, DISPLAY_AREA_PADDINGY
);
945 /* Inflate for the border */
946 InflateRect(prc
, CONTROL_BORDER_SIZEX
, CONTROL_BORDER_SIZEY
);
948 else /* Go from window rectangle. */
950 /* Deflate the rectangle for the border */
951 InflateRect(prc
, -CONTROL_BORDER_SIZEX
, -CONTROL_BORDER_SIZEY
);
953 /* Deflate the rectangle for the padding */
954 InflateRect(prc
, -DISPLAY_AREA_PADDINGX
, -DISPLAY_AREA_PADDINGY
);
956 /* Remove 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);
967 /******************************************************************************
970 * This method will handle the notification from the scroll control and
971 * perform the scrolling operation on the tab control.
973 static LRESULT
TAB_OnHScroll(TAB_INFO
*infoPtr
, int nScrollCode
, int nPos
)
975 if(nScrollCode
== SB_THUMBPOSITION
&& nPos
!= infoPtr
->leftmostVisible
)
977 if(nPos
< infoPtr
->leftmostVisible
)
978 infoPtr
->leftmostVisible
--;
980 infoPtr
->leftmostVisible
++;
982 TAB_RecalcHotTrack(infoPtr
, NULL
, NULL
, NULL
);
983 TAB_InvalidateTabArea(infoPtr
);
984 SendMessageW(infoPtr
->hwndUpDown
, UDM_SETPOS
, 0,
985 MAKELONG(infoPtr
->leftmostVisible
, 0));
991 /******************************************************************************
994 * This method will check the current scrolling state and make sure the
995 * scrolling control is displayed (or not).
997 static void TAB_SetupScrolling(
999 const RECT
* clientRect
)
1001 static const WCHAR emptyW
[] = { 0 };
1004 if (infoPtr
->needsScrolling
)
1007 INT vsize
, tabwidth
;
1010 * Calculate the position of the scroll control.
1012 controlPos
.right
= clientRect
->right
;
1013 controlPos
.left
= controlPos
.right
- 2 * GetSystemMetrics(SM_CXHSCROLL
);
1015 if (infoPtr
->dwStyle
& TCS_BOTTOM
)
1017 controlPos
.top
= clientRect
->bottom
- infoPtr
->tabHeight
;
1018 controlPos
.bottom
= controlPos
.top
+ GetSystemMetrics(SM_CYHSCROLL
);
1022 controlPos
.bottom
= clientRect
->top
+ infoPtr
->tabHeight
;
1023 controlPos
.top
= controlPos
.bottom
- GetSystemMetrics(SM_CYHSCROLL
);
1027 * If we don't have a scroll control yet, we want to create one.
1028 * If we have one, we want to make sure it's positioned properly.
1030 if (infoPtr
->hwndUpDown
==0)
1032 infoPtr
->hwndUpDown
= CreateWindowW(UPDOWN_CLASSW
, emptyW
,
1033 WS_VISIBLE
| WS_CHILD
| UDS_HORZ
,
1034 controlPos
.left
, controlPos
.top
,
1035 controlPos
.right
- controlPos
.left
,
1036 controlPos
.bottom
- controlPos
.top
,
1037 infoPtr
->hwnd
, NULL
, NULL
, NULL
);
1041 SetWindowPos(infoPtr
->hwndUpDown
,
1043 controlPos
.left
, controlPos
.top
,
1044 controlPos
.right
- controlPos
.left
,
1045 controlPos
.bottom
- controlPos
.top
,
1046 SWP_SHOWWINDOW
| SWP_NOZORDER
);
1049 /* Now calculate upper limit of the updown control range.
1050 * We do this by calculating how many tabs will be offscreen when the
1051 * last tab is visible.
1053 if(infoPtr
->uNumItem
)
1055 vsize
= clientRect
->right
- (controlPos
.right
- controlPos
.left
+ 1);
1056 maxRange
= infoPtr
->uNumItem
;
1057 tabwidth
= TAB_GetItem(infoPtr
, infoPtr
->uNumItem
- 1)->rect
.right
;
1059 for(; maxRange
> 0; maxRange
--)
1061 if(tabwidth
- TAB_GetItem(infoPtr
,maxRange
- 1)->rect
.left
> vsize
)
1065 if(maxRange
== infoPtr
->uNumItem
)
1071 /* If we once had a scroll control... hide it */
1072 if (infoPtr
->hwndUpDown
)
1073 ShowWindow(infoPtr
->hwndUpDown
, SW_HIDE
);
1075 if (infoPtr
->hwndUpDown
)
1076 SendMessageW(infoPtr
->hwndUpDown
, UDM_SETRANGE32
, 0, maxRange
);
1079 /******************************************************************************
1082 * This method will calculate the position rectangles of all the items in the
1083 * control. The rectangle calculated starts at 0 for the first item in the
1084 * list and ignores scrolling and selection.
1085 * It also uses the current font to determine the height of the tab row and
1086 * it checks if all the tabs fit in the client area of the window. If they
1087 * don't, a scrolling control is added.
1089 static void TAB_SetItemBounds (TAB_INFO
*infoPtr
)
1091 TEXTMETRICW fontMetrics
;
1094 INT curItemRowCount
;
1095 HFONT hFont
, hOldFont
;
1104 * We need to get text information so we need a DC and we need to select
1107 hdc
= GetDC(infoPtr
->hwnd
);
1109 hFont
= infoPtr
->hFont
? infoPtr
->hFont
: GetStockObject (SYSTEM_FONT
);
1110 hOldFont
= SelectObject (hdc
, hFont
);
1113 * We will base the rectangle calculations on the client rectangle
1116 GetClientRect(infoPtr
->hwnd
, &clientRect
);
1118 /* if TCS_VERTICAL then swap the height and width so this code places the
1119 tabs along the top of the rectangle and we can just rotate them after
1120 rather than duplicate all of the below code */
1121 if(infoPtr
->dwStyle
& TCS_VERTICAL
)
1123 iTemp
= clientRect
.bottom
;
1124 clientRect
.bottom
= clientRect
.right
;
1125 clientRect
.right
= iTemp
;
1128 /* Now use hPadding and vPadding */
1129 infoPtr
->uHItemPadding
= infoPtr
->uHItemPadding_s
;
1130 infoPtr
->uVItemPadding
= infoPtr
->uVItemPadding_s
;
1132 /* The leftmost item will be "0" aligned */
1134 curItemRowCount
= infoPtr
->uNumItem
? 1 : 0;
1136 if (!(infoPtr
->fHeightSet
))
1139 INT icon_height
= 0, cx
;
1141 /* Use the current font to determine the height of a tab. */
1142 GetTextMetricsW(hdc
, &fontMetrics
);
1144 /* Get the icon height */
1146 ImageList_GetIconSize(infoPtr
->himl
, &cx
, &icon_height
);
1148 /* Take the highest between font or icon */
1149 if (fontMetrics
.tmHeight
> icon_height
)
1150 item_height
= fontMetrics
.tmHeight
+ 2;
1152 item_height
= icon_height
;
1155 * Make sure there is enough space for the letters + icon + growing the
1156 * selected item + extra space for the selected item.
1158 infoPtr
->tabHeight
= item_height
+
1159 ((infoPtr
->dwStyle
& TCS_BUTTONS
) ? 2 : 1) *
1160 infoPtr
->uVItemPadding
;
1162 TRACE("tabH=%d, tmH=%d, iconh=%d\n",
1163 infoPtr
->tabHeight
, fontMetrics
.tmHeight
, icon_height
);
1166 TRACE("client right=%d\n", clientRect
.right
);
1168 /* Get the icon width */
1173 ImageList_GetIconSize(infoPtr
->himl
, &icon_width
, &cy
);
1175 if (infoPtr
->dwStyle
& TCS_FIXEDWIDTH
)
1178 /* Add padding if icon is present */
1179 icon_width
+= infoPtr
->uHItemPadding
;
1182 for (curItem
= 0; curItem
< infoPtr
->uNumItem
; curItem
++)
1184 TAB_ITEM
*curr
= TAB_GetItem(infoPtr
, curItem
);
1186 /* Set the leftmost position of the tab. */
1187 curr
->rect
.left
= curItemLeftPos
;
1189 if (infoPtr
->dwStyle
& TCS_FIXEDWIDTH
)
1191 curr
->rect
.right
= curr
->rect
.left
+
1192 max(infoPtr
->tabWidth
, icon_width
);
1194 else if (!curr
->pszText
)
1196 /* If no text use minimum tab width including padding. */
1197 if (infoPtr
->tabMinWidth
< 0)
1198 curr
->rect
.right
= curr
->rect
.left
+ GET_DEFAULT_MIN_TAB_WIDTH(infoPtr
);
1201 curr
->rect
.right
= curr
->rect
.left
+ infoPtr
->tabMinWidth
;
1203 /* Add extra padding if icon is present */
1204 if (infoPtr
->himl
&& infoPtr
->tabMinWidth
> 0 && infoPtr
->tabMinWidth
< DEFAULT_MIN_TAB_WIDTH
1205 && infoPtr
->uHItemPadding
> 1)
1206 curr
->rect
.right
+= EXTRA_ICON_PADDING
* (infoPtr
->uHItemPadding
-1);
1213 /* Calculate how wide the tab is depending on the text it contains */
1214 GetTextExtentPoint32W(hdc
, curr
->pszText
,
1215 lstrlenW(curr
->pszText
), &size
);
1217 tabwidth
= size
.cx
+ icon_width
+ 2 * infoPtr
->uHItemPadding
;
1219 if (infoPtr
->tabMinWidth
< 0)
1220 tabwidth
= max(tabwidth
, GET_DEFAULT_MIN_TAB_WIDTH(infoPtr
));
1222 tabwidth
= max(tabwidth
, infoPtr
->tabMinWidth
);
1224 curr
->rect
.right
= curr
->rect
.left
+ tabwidth
;
1225 TRACE("for <%s>, l,r=%d,%d\n",
1226 debugstr_w(curr
->pszText
), curr
->rect
.left
, curr
->rect
.right
);
1230 * Check if this is a multiline tab control and if so
1231 * check to see if we should wrap the tabs
1233 * Wrap all these tabs. We will arrange them evenly later.
1237 if (((infoPtr
->dwStyle
& TCS_MULTILINE
) || (infoPtr
->dwStyle
& TCS_VERTICAL
)) &&
1239 (clientRect
.right
- CONTROL_BORDER_SIZEX
- DISPLAY_AREA_PADDINGX
)))
1241 curr
->rect
.right
-= curr
->rect
.left
;
1243 curr
->rect
.left
= 0;
1245 TRACE("wrapping <%s>, l,r=%d,%d\n", debugstr_w(curr
->pszText
),
1246 curr
->rect
.left
, curr
->rect
.right
);
1249 curr
->rect
.bottom
= 0;
1250 curr
->rect
.top
= curItemRowCount
- 1;
1252 TRACE("Rect: %s\n", wine_dbgstr_rect(&curr
->rect
));
1255 * The leftmost position of the next item is the rightmost position
1258 if (infoPtr
->dwStyle
& TCS_BUTTONS
)
1260 curItemLeftPos
= curr
->rect
.right
+ BUTTON_SPACINGX
;
1261 if (infoPtr
->dwStyle
& TCS_FLATBUTTONS
)
1262 curItemLeftPos
+= FLAT_BTN_SPACINGX
;
1265 curItemLeftPos
= curr
->rect
.right
;
1268 if (!((infoPtr
->dwStyle
& TCS_MULTILINE
) || (infoPtr
->dwStyle
& TCS_VERTICAL
)))
1271 * Check if we need a scrolling control.
1273 infoPtr
->needsScrolling
= (curItemLeftPos
+ (2 * SELECTED_TAB_OFFSET
) >
1276 /* Don't need scrolling, then update infoPtr->leftmostVisible */
1277 if(!infoPtr
->needsScrolling
)
1278 infoPtr
->leftmostVisible
= 0;
1283 * No scrolling in Multiline or Vertical styles.
1285 infoPtr
->needsScrolling
= FALSE
;
1286 infoPtr
->leftmostVisible
= 0;
1288 TAB_SetupScrolling(infoPtr
, &clientRect
);
1290 /* Set the number of rows */
1291 infoPtr
->uNumRows
= curItemRowCount
;
1293 /* Arrange all tabs evenly if style says so */
1294 if (!(infoPtr
->dwStyle
& TCS_RAGGEDRIGHT
) &&
1295 ((infoPtr
->dwStyle
& TCS_MULTILINE
) || (infoPtr
->dwStyle
& TCS_VERTICAL
)) &&
1296 (infoPtr
->uNumItem
> 0) &&
1297 (infoPtr
->uNumRows
> 1))
1299 INT tabPerRow
,remTab
,iRow
;
1304 * Ok windows tries to even out the rows. place the same
1305 * number of tabs in each row. So lets give that a shot
1308 tabPerRow
= infoPtr
->uNumItem
/ (infoPtr
->uNumRows
);
1309 remTab
= infoPtr
->uNumItem
% (infoPtr
->uNumRows
);
1311 for (iItm
=0,iRow
=0,iCount
=0,curItemLeftPos
=0;
1312 iItm
<infoPtr
->uNumItem
;
1315 /* normalize the current rect */
1316 TAB_ITEM
*curr
= TAB_GetItem(infoPtr
, iItm
);
1318 /* shift the item to the left side of the clientRect */
1319 curr
->rect
.right
-= curr
->rect
.left
;
1320 curr
->rect
.left
= 0;
1322 TRACE("r=%d, cl=%d, cl.r=%d, iCount=%d, iRow=%d, uNumRows=%d, remTab=%d, tabPerRow=%d\n",
1323 curr
->rect
.right
, curItemLeftPos
, clientRect
.right
,
1324 iCount
, iRow
, infoPtr
->uNumRows
, remTab
, tabPerRow
);
1326 /* if we have reached the maximum number of tabs on this row */
1327 /* move to the next row, reset our current item left position and */
1328 /* the count of items on this row */
1330 if (infoPtr
->dwStyle
& TCS_VERTICAL
) {
1331 /* Vert: Add the remaining tabs in the *last* remainder rows */
1332 if (iCount
>= ((iRow
>=(INT
)infoPtr
->uNumRows
- remTab
)?tabPerRow
+ 1:tabPerRow
)) {
1338 /* Horz: Add the remaining tabs in the *first* remainder rows */
1339 if (iCount
>= ((iRow
<remTab
)?tabPerRow
+ 1:tabPerRow
)) {
1346 /* shift the item to the right to place it as the next item in this row */
1347 curr
->rect
.left
+= curItemLeftPos
;
1348 curr
->rect
.right
+= curItemLeftPos
;
1349 curr
->rect
.top
= iRow
;
1350 if (infoPtr
->dwStyle
& TCS_BUTTONS
)
1352 curItemLeftPos
= curr
->rect
.right
+ 1;
1353 if (infoPtr
->dwStyle
& TCS_FLATBUTTONS
)
1354 curItemLeftPos
+= FLAT_BTN_SPACINGX
;
1357 curItemLeftPos
= curr
->rect
.right
;
1359 TRACE("arranging <%s>, l,r=%d,%d, row=%d\n",
1360 debugstr_w(curr
->pszText
), curr
->rect
.left
,
1361 curr
->rect
.right
, curr
->rect
.top
);
1368 INT widthDiff
, iIndexStart
=0, iIndexEnd
=0;
1372 while(iIndexStart
< infoPtr
->uNumItem
)
1374 TAB_ITEM
*start
= TAB_GetItem(infoPtr
, iIndexStart
);
1377 * find the index of the row
1379 /* find the first item on the next row */
1380 for (iIndexEnd
=iIndexStart
;
1381 (iIndexEnd
< infoPtr
->uNumItem
) &&
1382 (TAB_GetItem(infoPtr
, iIndexEnd
)->rect
.top
==
1385 /* intentionally blank */;
1388 * we need to justify these tabs so they fill the whole given
1392 /* find the amount of space remaining on this row */
1393 widthDiff
= clientRect
.right
- (2 * SELECTED_TAB_OFFSET
) -
1394 TAB_GetItem(infoPtr
, iIndexEnd
- 1)->rect
.right
;
1396 /* iCount is the number of tab items on this row */
1397 iCount
= iIndexEnd
- iIndexStart
;
1401 remainder
= widthDiff
% iCount
;
1402 widthDiff
= widthDiff
/ iCount
;
1403 /* add widthDiff/iCount, or extra space/items on row, to each item on this row */
1404 for (iIndex
=iIndexStart
, iCount
=0; iIndex
< iIndexEnd
; iIndex
++, iCount
++)
1406 TAB_ITEM
*item
= TAB_GetItem(infoPtr
, iIndex
);
1408 item
->rect
.left
+= iCount
* widthDiff
;
1409 item
->rect
.right
+= (iCount
+ 1) * widthDiff
;
1411 TRACE("adjusting 1 <%s>, l,r=%d,%d\n",
1412 debugstr_w(item
->pszText
),
1413 item
->rect
.left
, item
->rect
.right
);
1416 TAB_GetItem(infoPtr
, iIndex
- 1)->rect
.right
+= remainder
;
1418 else /* we have only one item on this row, make it take up the entire row */
1420 start
->rect
.left
= clientRect
.left
;
1421 start
->rect
.right
= clientRect
.right
- 4;
1423 TRACE("adjusting 2 <%s>, l,r=%d,%d\n",
1424 debugstr_w(start
->pszText
),
1425 start
->rect
.left
, start
->rect
.right
);
1430 iIndexStart
= iIndexEnd
;
1435 /* if TCS_VERTICAL rotate the tabs so they are along the side of the clientRect */
1436 if(infoPtr
->dwStyle
& TCS_VERTICAL
)
1439 for(iIndex
= 0; iIndex
< infoPtr
->uNumItem
; iIndex
++)
1441 rcItem
= &TAB_GetItem(infoPtr
, iIndex
)->rect
;
1443 rcOriginal
= *rcItem
;
1445 /* this is rotating the items by 90 degrees clockwise around the center of the control */
1446 rcItem
->top
= (rcOriginal
.left
- clientRect
.left
);
1447 rcItem
->bottom
= rcItem
->top
+ (rcOriginal
.right
- rcOriginal
.left
);
1448 rcItem
->left
= rcOriginal
.top
;
1449 rcItem
->right
= rcOriginal
.bottom
;
1453 TAB_EnsureSelectionVisible(infoPtr
);
1454 TAB_RecalcHotTrack(infoPtr
, NULL
, NULL
, NULL
);
1457 SelectObject (hdc
, hOldFont
);
1458 ReleaseDC (infoPtr
->hwnd
, hdc
);
1463 TAB_EraseTabInterior(const TAB_INFO
*infoPtr
, HDC hdc
, INT iItem
, const RECT
*drawRect
)
1465 HBRUSH hbr
= CreateSolidBrush (comctl32_color
.clrBtnFace
);
1466 BOOL deleteBrush
= TRUE
;
1467 RECT rTemp
= *drawRect
;
1469 if (infoPtr
->dwStyle
& TCS_BUTTONS
)
1471 if (iItem
== infoPtr
->iSelected
)
1473 /* Background color */
1474 if (!(infoPtr
->dwStyle
& TCS_OWNERDRAWFIXED
))
1477 hbr
= GetSysColorBrush(COLOR_SCROLLBAR
);
1479 SetTextColor(hdc
, comctl32_color
.clr3dFace
);
1480 SetBkColor(hdc
, comctl32_color
.clr3dHilight
);
1482 /* if COLOR_WINDOW happens to be the same as COLOR_3DHILIGHT
1483 * we better use 0x55aa bitmap brush to make scrollbar's background
1484 * look different from the window background.
1486 if (comctl32_color
.clr3dHilight
== comctl32_color
.clrWindow
)
1487 hbr
= COMCTL32_hPattern55AABrush
;
1489 deleteBrush
= FALSE
;
1491 FillRect(hdc
, &rTemp
, hbr
);
1493 else /* ! selected */
1495 if (infoPtr
->dwStyle
& TCS_FLATBUTTONS
)
1497 InflateRect(&rTemp
, 2, 2);
1498 FillRect(hdc
, &rTemp
, hbr
);
1499 if (iItem
== infoPtr
->iHotTracked
||
1500 (iItem
!= infoPtr
->iSelected
&& iItem
== infoPtr
->uFocus
))
1501 DrawEdge(hdc
, &rTemp
, BDR_RAISEDINNER
, BF_RECT
);
1504 FillRect(hdc
, &rTemp
, hbr
);
1508 else /* !TCS_BUTTONS */
1510 InflateRect(&rTemp
, -2, -2);
1511 if (!GetWindowTheme (infoPtr
->hwnd
))
1512 FillRect(hdc
, &rTemp
, hbr
);
1515 /* highlighting is drawn on top of previous fills */
1516 if (TAB_GetItem(infoPtr
, iItem
)->dwState
& TCIS_HIGHLIGHTED
)
1521 deleteBrush
= FALSE
;
1523 hbr
= GetSysColorBrush(COLOR_HIGHLIGHT
);
1524 FillRect(hdc
, &rTemp
, hbr
);
1528 if (deleteBrush
) DeleteObject(hbr
);
1531 /******************************************************************************
1532 * TAB_DrawItemInterior
1534 * This method is used to draw the interior (text and icon) of a single tab
1535 * into the tab control.
1538 TAB_DrawItemInterior(const TAB_INFO
*infoPtr
, HDC hdc
, INT iItem
, RECT
*drawRect
)
1547 /* if (drawRect == NULL) */
1554 * Get the rectangle for the item.
1556 isVisible
= TAB_InternalGetItemRect(infoPtr
, iItem
, &itemRect
, &selectedRect
);
1561 * Make sure drawRect points to something valid; simplifies code.
1563 drawRect
= &localRect
;
1566 * This logic copied from the part of TAB_DrawItem which draws
1567 * the tab background. It's important to keep it in sync. I
1568 * would have liked to avoid code duplication, but couldn't figure
1569 * out how without making spaghetti of TAB_DrawItem.
1571 if (iItem
== infoPtr
->iSelected
)
1572 *drawRect
= selectedRect
;
1574 *drawRect
= itemRect
;
1576 if (infoPtr
->dwStyle
& TCS_BUTTONS
)
1578 if (iItem
== infoPtr
->iSelected
)
1580 drawRect
->left
+= 4;
1582 drawRect
->right
-= 4;
1584 if (infoPtr
->dwStyle
& TCS_VERTICAL
)
1586 if (!(infoPtr
->dwStyle
& TCS_BOTTOM
)) drawRect
->right
+= 1;
1587 drawRect
->bottom
-= 4;
1591 if (infoPtr
->dwStyle
& TCS_BOTTOM
)
1594 drawRect
->bottom
-= 4;
1597 drawRect
->bottom
-= 1;
1602 drawRect
->left
+= 2;
1604 drawRect
->right
-= 2;
1605 drawRect
->bottom
-= 2;
1610 if ((infoPtr
->dwStyle
& TCS_VERTICAL
) && (infoPtr
->dwStyle
& TCS_BOTTOM
))
1612 if (iItem
!= infoPtr
->iSelected
)
1614 drawRect
->left
+= 2;
1616 drawRect
->bottom
-= 2;
1619 else if (infoPtr
->dwStyle
& TCS_VERTICAL
)
1621 if (iItem
== infoPtr
->iSelected
)
1623 drawRect
->right
+= 1;
1628 drawRect
->right
-= 2;
1629 drawRect
->bottom
-= 2;
1632 else if (infoPtr
->dwStyle
& TCS_BOTTOM
)
1634 if (iItem
== infoPtr
->iSelected
)
1640 InflateRect(drawRect
, -2, -2);
1641 drawRect
->bottom
+= 2;
1646 if (iItem
== infoPtr
->iSelected
)
1648 drawRect
->bottom
+= 3;
1652 drawRect
->bottom
-= 2;
1653 InflateRect(drawRect
, -2, 0);
1658 TRACE("drawRect=(%s)\n", wine_dbgstr_rect(drawRect
));
1660 /* Clear interior */
1661 TAB_EraseTabInterior (infoPtr
, hdc
, iItem
, drawRect
);
1663 /* Draw the focus rectangle */
1664 if (!(infoPtr
->dwStyle
& TCS_FOCUSNEVER
) &&
1665 (GetFocus() == infoPtr
->hwnd
) &&
1666 (iItem
== infoPtr
->uFocus
) )
1668 RECT rFocus
= *drawRect
;
1670 if (!(infoPtr
->dwStyle
& TCS_BUTTONS
)) InflateRect(&rFocus
, -3, -3);
1671 if (infoPtr
->dwStyle
& TCS_BOTTOM
&& !(infoPtr
->dwStyle
& TCS_VERTICAL
))
1674 /* focus should stay on selected item for TCS_BUTTONS style */
1675 if (!((infoPtr
->dwStyle
& TCS_BUTTONS
) && (infoPtr
->iSelected
!= iItem
)))
1676 DrawFocusRect(hdc
, &rFocus
);
1682 htextPen
= CreatePen( PS_SOLID
, 1, comctl32_color
.clrBtnText
);
1683 holdPen
= SelectObject(hdc
, htextPen
);
1684 hOldFont
= SelectObject(hdc
, infoPtr
->hFont
);
1687 * Setup for text output
1689 oldBkMode
= SetBkMode(hdc
, TRANSPARENT
);
1690 if (!GetWindowTheme (infoPtr
->hwnd
) || (infoPtr
->dwStyle
& TCS_BUTTONS
))
1692 if ((infoPtr
->dwStyle
& TCS_HOTTRACK
) && (iItem
== infoPtr
->iHotTracked
) &&
1693 !(infoPtr
->dwStyle
& TCS_FLATBUTTONS
))
1694 SetTextColor(hdc
, comctl32_color
.clrHighlight
);
1695 else if (TAB_GetItem(infoPtr
, iItem
)->dwState
& TCIS_HIGHLIGHTED
)
1696 SetTextColor(hdc
, comctl32_color
.clrHighlightText
);
1698 SetTextColor(hdc
, comctl32_color
.clrBtnText
);
1702 * if owner draw, tell the owner to draw
1704 if ((infoPtr
->dwStyle
& TCS_OWNERDRAWFIXED
) && IsWindow(infoPtr
->hwndNotify
))
1710 drawRect
->right
-= 1;
1711 if ( iItem
== infoPtr
->iSelected
)
1713 drawRect
->right
-= 1;
1714 drawRect
->left
+= 1;
1717 id
= (UINT
)GetWindowLongPtrW( infoPtr
->hwnd
, GWLP_ID
);
1719 /* fill DRAWITEMSTRUCT */
1720 dis
.CtlType
= ODT_TAB
;
1723 dis
.itemAction
= ODA_DRAWENTIRE
;
1725 if ( iItem
== infoPtr
->iSelected
)
1726 dis
.itemState
|= ODS_SELECTED
;
1727 if (infoPtr
->uFocus
== iItem
)
1728 dis
.itemState
|= ODS_FOCUS
;
1729 dis
.hwndItem
= infoPtr
->hwnd
;
1731 CopyRect(&dis
.rcItem
,drawRect
);
1733 /* when extra data fits ULONG_PTR, store it directly */
1734 if (infoPtr
->cbInfo
> sizeof(LPARAM
))
1735 dis
.itemData
= (ULONG_PTR
) TAB_GetItem(infoPtr
, iItem
)->extra
;
1738 /* this could be considered broken on 64 bit, but that's how it works -
1739 only first 4 bytes are copied */
1741 memcpy(&dis
.itemData
, (ULONG_PTR
*)TAB_GetItem(infoPtr
, iItem
)->extra
, 4);
1744 /* draw notification */
1745 SendMessageW( infoPtr
->hwndNotify
, WM_DRAWITEM
, id
, (LPARAM
)&dis
);
1749 TAB_ITEM
*item
= TAB_GetItem(infoPtr
, iItem
);
1753 /* used to center the icon and text in the tab */
1755 INT center_offset_h
, center_offset_v
;
1757 /* set rcImage to drawRect, we will use top & left in our ImageList_Draw call */
1758 rcImage
= *drawRect
;
1762 rcText
.left
= rcText
.top
= rcText
.right
= rcText
.bottom
= 0;
1764 /* get the rectangle that the text fits in */
1767 DrawTextW(hdc
, item
->pszText
, -1, &rcText
, DT_CALCRECT
);
1770 * If not owner draw, then do the drawing ourselves.
1774 if (infoPtr
->himl
&& item
->iImage
!= -1)
1779 ImageList_GetIconSize(infoPtr
->himl
, &cx
, &cy
);
1781 if(infoPtr
->dwStyle
& TCS_VERTICAL
)
1783 center_offset_h
= ((drawRect
->bottom
- drawRect
->top
) - (cy
+ infoPtr
->uHItemPadding
+ (rcText
.right
- rcText
.left
))) / 2;
1784 center_offset_v
= ((drawRect
->right
- drawRect
->left
) - cx
) / 2;
1788 center_offset_h
= ((drawRect
->right
- drawRect
->left
) - (cx
+ infoPtr
->uHItemPadding
+ (rcText
.right
- rcText
.left
))) / 2;
1789 center_offset_v
= ((drawRect
->bottom
- drawRect
->top
) - cy
) / 2;
1792 /* if an item is selected, the icon is shifted up instead of down */
1793 if (iItem
== infoPtr
->iSelected
)
1794 center_offset_v
-= infoPtr
->uVItemPadding
/ 2;
1796 center_offset_v
+= infoPtr
->uVItemPadding
/ 2;
1798 if (infoPtr
->dwStyle
& TCS_FIXEDWIDTH
&& infoPtr
->dwStyle
& (TCS_FORCELABELLEFT
| TCS_FORCEICONLEFT
))
1799 center_offset_h
= infoPtr
->uHItemPadding
;
1801 if (center_offset_h
< 2)
1802 center_offset_h
= 2;
1804 if (center_offset_v
< 0)
1805 center_offset_v
= 0;
1807 TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%s), textlen=%d\n",
1808 debugstr_w(item
->pszText
), center_offset_h
, center_offset_v
,
1809 wine_dbgstr_rect(drawRect
), (rcText
.right
-rcText
.left
));
1811 if((infoPtr
->dwStyle
& TCS_VERTICAL
) && (infoPtr
->dwStyle
& TCS_BOTTOM
))
1813 rcImage
.top
= drawRect
->top
+ center_offset_h
;
1814 /* if tab is TCS_VERTICAL and TCS_BOTTOM, the text is drawn from the */
1815 /* right side of the tab, but the image still uses the left as its x position */
1816 /* this keeps the image always drawn off of the same side of the tab */
1817 rcImage
.left
= drawRect
->right
- cx
- center_offset_v
;
1818 drawRect
->top
+= cy
+ infoPtr
->uHItemPadding
;
1820 else if(infoPtr
->dwStyle
& TCS_VERTICAL
)
1822 rcImage
.top
= drawRect
->bottom
- cy
- center_offset_h
;
1823 rcImage
.left
= drawRect
->left
+ center_offset_v
;
1824 drawRect
->bottom
-= cy
+ infoPtr
->uHItemPadding
;
1826 else /* normal style, whether TCS_BOTTOM or not */
1828 rcImage
.left
= drawRect
->left
+ center_offset_h
;
1829 rcImage
.top
= drawRect
->top
+ center_offset_v
;
1830 drawRect
->left
+= cx
+ infoPtr
->uHItemPadding
;
1833 TRACE("drawing image=%d, left=%d, top=%d\n",
1834 item
->iImage
, rcImage
.left
, rcImage
.top
-1);
1846 /* Now position text */
1847 if (infoPtr
->dwStyle
& TCS_FIXEDWIDTH
&& infoPtr
->dwStyle
& TCS_FORCELABELLEFT
)
1848 center_offset_h
= infoPtr
->uHItemPadding
;
1850 if(infoPtr
->dwStyle
& TCS_VERTICAL
)
1851 center_offset_h
= ((drawRect
->bottom
- drawRect
->top
) - (rcText
.right
- rcText
.left
)) / 2;
1853 center_offset_h
= ((drawRect
->right
- drawRect
->left
) - (rcText
.right
- rcText
.left
)) / 2;
1855 if(infoPtr
->dwStyle
& TCS_VERTICAL
)
1857 if(infoPtr
->dwStyle
& TCS_BOTTOM
)
1858 drawRect
->top
+=center_offset_h
;
1860 drawRect
->bottom
-=center_offset_h
;
1862 center_offset_v
= ((drawRect
->right
- drawRect
->left
) - (rcText
.bottom
- rcText
.top
)) / 2;
1866 drawRect
->left
+= center_offset_h
;
1867 center_offset_v
= ((drawRect
->bottom
- drawRect
->top
) - (rcText
.bottom
- rcText
.top
)) / 2;
1870 /* if an item is selected, the text is shifted up instead of down */
1871 if (iItem
== infoPtr
->iSelected
)
1872 center_offset_v
-= infoPtr
->uVItemPadding
/ 2;
1874 center_offset_v
+= infoPtr
->uVItemPadding
/ 2;
1876 if (center_offset_v
< 0)
1877 center_offset_v
= 0;
1879 if(infoPtr
->dwStyle
& TCS_VERTICAL
)
1880 drawRect
->left
+= center_offset_v
;
1882 drawRect
->top
+= center_offset_v
;
1885 if(infoPtr
->dwStyle
& TCS_VERTICAL
) /* if we are vertical rotate the text and each character */
1889 INT nEscapement
= 900;
1890 INT nOrientation
= 900;
1892 if(infoPtr
->dwStyle
& TCS_BOTTOM
)
1895 nOrientation
= -900;
1898 /* to get a font with the escapement and orientation we are looking for, we need to */
1899 /* call CreateFontIndirect, which requires us to set the values of the logfont we pass in */
1900 if (!GetObjectW(infoPtr
->hFont
, sizeof(logfont
), &logfont
))
1901 GetObjectW(GetStockObject(DEFAULT_GUI_FONT
), sizeof(logfont
), &logfont
);
1903 logfont
.lfEscapement
= nEscapement
;
1904 logfont
.lfOrientation
= nOrientation
;
1905 hFont
= CreateFontIndirectW(&logfont
);
1906 SelectObject(hdc
, hFont
);
1911 (infoPtr
->dwStyle
& TCS_BOTTOM
) ? drawRect
->right
: drawRect
->left
,
1912 (!(infoPtr
->dwStyle
& TCS_BOTTOM
)) ? drawRect
->bottom
: drawRect
->top
,
1916 lstrlenW(item
->pszText
),
1920 DeleteObject(hFont
);
1924 TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%s), textlen=%d\n",
1925 debugstr_w(item
->pszText
), center_offset_h
, center_offset_v
,
1926 wine_dbgstr_rect(drawRect
), (rcText
.right
-rcText
.left
));
1933 lstrlenW(item
->pszText
),
1935 DT_LEFT
| DT_SINGLELINE
1940 *drawRect
= rcTemp
; /* restore drawRect */
1946 SelectObject(hdc
, hOldFont
);
1947 SetBkMode(hdc
, oldBkMode
);
1948 SelectObject(hdc
, holdPen
);
1949 DeleteObject( htextPen
);
1952 /******************************************************************************
1955 * This method is used to draw a single tab into the tab control.
1957 static void TAB_DrawItem(const TAB_INFO
*infoPtr
, HDC hdc
, INT iItem
)
1962 RECT r
, fillRect
, r1
;
1965 COLORREF bkgnd
, corner
;
1969 * Get the rectangle for the item.
1971 isVisible
= TAB_InternalGetItemRect(infoPtr
,
1980 /* Clip UpDown control to not draw over it */
1981 if (infoPtr
->needsScrolling
)
1983 GetWindowRect(infoPtr
->hwnd
, &rC
);
1984 GetWindowRect(infoPtr
->hwndUpDown
, &rUD
);
1985 ExcludeClipRect(hdc
, rUD
.left
- rC
.left
, rUD
.top
- rC
.top
, rUD
.right
- rC
.left
, rUD
.bottom
- rC
.top
);
1988 /* If you need to see what the control is doing,
1989 * then override these variables. They will change what
1990 * fill colors are used for filling the tabs, and the
1991 * corners when drawing the edge.
1993 bkgnd
= comctl32_color
.clrBtnFace
;
1994 corner
= comctl32_color
.clrBtnFace
;
1996 if (infoPtr
->dwStyle
& TCS_BUTTONS
)
1998 /* Get item rectangle */
2001 /* Separators between flat buttons */
2002 if ((infoPtr
->dwStyle
& TCS_FLATBUTTONS
) && (infoPtr
->exStyle
& TCS_EX_FLATSEPARATORS
))
2005 r1
.right
+= (FLAT_BTN_SPACINGX
-2);
2006 DrawEdge(hdc
, &r1
, EDGE_ETCHED
, BF_RIGHT
);
2009 if (iItem
== infoPtr
->iSelected
)
2011 DrawEdge(hdc
, &r
, EDGE_SUNKEN
, BF_SOFT
|BF_RECT
);
2013 OffsetRect(&r
, 1, 1);
2015 else /* ! selected */
2017 DWORD state
= TAB_GetItem(infoPtr
, iItem
)->dwState
;
2019 if ((state
& TCIS_BUTTONPRESSED
) || (iItem
== infoPtr
->uFocus
))
2020 DrawEdge(hdc
, &r
, EDGE_SUNKEN
, BF_SOFT
|BF_RECT
);
2022 if (!(infoPtr
->dwStyle
& TCS_FLATBUTTONS
))
2023 DrawEdge(hdc
, &r
, EDGE_RAISED
, BF_SOFT
|BF_RECT
);
2026 else /* !TCS_BUTTONS */
2028 /* We draw a rectangle of different sizes depending on the selection
2030 if (iItem
== infoPtr
->iSelected
) {
2032 GetClientRect (infoPtr
->hwnd
, &rect
);
2033 clRight
= rect
.right
;
2034 clBottom
= rect
.bottom
;
2041 * Erase the background. (Delay it but setup rectangle.)
2042 * This is necessary when drawing the selected item since it is larger
2043 * than the others, it might overlap with stuff already drawn by the
2048 /* Draw themed tabs - but only if they are at the top.
2049 * Windows draws even side or bottom tabs themed, with wacky results.
2050 * However, since in Wine apps may get themed that did not opt in via
2051 * a manifest avoid theming when we know the result will be wrong */
2052 if ((theme
= GetWindowTheme (infoPtr
->hwnd
))
2053 && ((infoPtr
->dwStyle
& (TCS_VERTICAL
| TCS_BOTTOM
)) == 0))
2055 static const int partIds
[8] = {
2058 TABP_TABITEMLEFTEDGE
,
2059 TABP_TABITEMRIGHTEDGE
,
2060 TABP_TABITEMBOTHEDGE
,
2063 TABP_TOPTABITEMLEFTEDGE
,
2064 TABP_TOPTABITEMRIGHTEDGE
,
2065 TABP_TOPTABITEMBOTHEDGE
,
2068 int stateId
= TIS_NORMAL
;
2070 /* selected and unselected tabs have different parts */
2071 if (iItem
== infoPtr
->iSelected
)
2073 /* The part also differs on the position of a tab on a line.
2074 * "Visually" determining the position works well enough. */
2075 GetClientRect(infoPtr
->hwnd
, &r1
);
2076 if(selectedRect
.left
== 0)
2078 if(selectedRect
.right
== r1
.right
)
2081 if (iItem
== infoPtr
->iSelected
)
2082 stateId
= TIS_SELECTED
;
2083 else if (iItem
== infoPtr
->iHotTracked
)
2085 else if (iItem
== infoPtr
->uFocus
)
2086 stateId
= TIS_FOCUSED
;
2088 /* Adjust rectangle for bottommost row */
2089 if (TAB_GetItem(infoPtr
, iItem
)->rect
.top
== infoPtr
->uNumRows
-1)
2092 DrawThemeBackground (theme
, hdc
, partIds
[partIndex
], stateId
, &r
, NULL
);
2093 GetThemeBackgroundContentRect (theme
, hdc
, partIds
[partIndex
], stateId
, &r
, &r
);
2095 else if(infoPtr
->dwStyle
& TCS_VERTICAL
)
2097 /* These are for adjusting the drawing of a Selected tab */
2098 /* The initial values are for the normal case of non-Selected */
2099 int ZZ
= 1; /* Do not stretch if selected */
2100 if (iItem
== infoPtr
->iSelected
) {
2103 /* if leftmost draw the line longer */
2104 if(selectedRect
.top
== 0)
2105 fillRect
.top
+= CONTROL_BORDER_SIZEY
;
2106 /* if rightmost draw the line longer */
2107 if(selectedRect
.bottom
== clBottom
)
2108 fillRect
.bottom
-= CONTROL_BORDER_SIZEY
;
2111 if (infoPtr
->dwStyle
& TCS_BOTTOM
)
2113 /* Adjust both rectangles to match native */
2116 TRACE("<right> item=%d, fill=(%s), edge=(%s)\n",
2117 iItem
, wine_dbgstr_rect(&fillRect
), wine_dbgstr_rect(&r
));
2119 /* Clear interior */
2120 SetBkColor(hdc
, bkgnd
);
2121 ExtTextOutW(hdc
, 0, 0, 2, &fillRect
, NULL
, 0, 0);
2123 /* Draw rectangular edge around tab */
2124 DrawEdge(hdc
, &r
, EDGE_RAISED
, BF_SOFT
|BF_RIGHT
|BF_TOP
|BF_BOTTOM
);
2126 /* Now erase the top corner and draw diagonal edge */
2127 SetBkColor(hdc
, corner
);
2128 r1
.left
= r
.right
- ROUND_CORNER_SIZE
- 1;
2131 r1
.bottom
= r1
.top
+ ROUND_CORNER_SIZE
;
2132 ExtTextOutW(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2134 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_DIAGONAL_ENDTOPLEFT
);
2136 /* Now erase the bottom corner and draw diagonal edge */
2137 r1
.left
= r
.right
- ROUND_CORNER_SIZE
- 1;
2138 r1
.bottom
= r
.bottom
;
2140 r1
.top
= r1
.bottom
- ROUND_CORNER_SIZE
;
2141 ExtTextOutW(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2143 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_DIAGONAL_ENDBOTTOMLEFT
);
2145 if ((iItem
== infoPtr
->iSelected
) && (selectedRect
.top
== 0)) {
2149 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_TOP
);
2155 TRACE("<left> item=%d, fill=(%s), edge=(%s)\n",
2156 iItem
, wine_dbgstr_rect(&fillRect
), wine_dbgstr_rect(&r
));
2158 /* Clear interior */
2159 SetBkColor(hdc
, bkgnd
);
2160 ExtTextOutW(hdc
, 0, 0, 2, &fillRect
, NULL
, 0, 0);
2162 /* Draw rectangular edge around tab */
2163 DrawEdge(hdc
, &r
, EDGE_RAISED
, BF_SOFT
|BF_LEFT
|BF_TOP
|BF_BOTTOM
);
2165 /* Now erase the top corner and draw diagonal edge */
2166 SetBkColor(hdc
, corner
);
2169 r1
.right
= r1
.left
+ ROUND_CORNER_SIZE
+ 1;
2170 r1
.bottom
= r1
.top
+ ROUND_CORNER_SIZE
;
2171 ExtTextOutW(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2173 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_DIAGONAL_ENDTOPRIGHT
);
2175 /* Now erase the bottom corner and draw diagonal edge */
2177 r1
.bottom
= r
.bottom
;
2178 r1
.right
= r1
.left
+ ROUND_CORNER_SIZE
+ 1;
2179 r1
.top
= r1
.bottom
- ROUND_CORNER_SIZE
;
2180 ExtTextOutW(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2182 DrawEdge(hdc
, &r1
, EDGE_SUNKEN
, BF_DIAGONAL_ENDTOPLEFT
);
2185 else /* ! TCS_VERTICAL */
2187 /* These are for adjusting the drawing of a Selected tab */
2188 /* The initial values are for the normal case of non-Selected */
2189 if (iItem
== infoPtr
->iSelected
) {
2190 /* if leftmost draw the line longer */
2191 if(selectedRect
.left
== 0)
2192 fillRect
.left
+= CONTROL_BORDER_SIZEX
;
2193 /* if rightmost draw the line longer */
2194 if(selectedRect
.right
== clRight
)
2195 fillRect
.right
-= CONTROL_BORDER_SIZEX
;
2198 if (infoPtr
->dwStyle
& TCS_BOTTOM
)
2200 /* Adjust both rectangles for topmost row */
2201 if (TAB_GetItem(infoPtr
, iItem
)->rect
.top
== infoPtr
->uNumRows
-1)
2207 TRACE("<bottom> item=%d, fill=(%s), edge=(%s)\n",
2208 iItem
, wine_dbgstr_rect(&fillRect
), wine_dbgstr_rect(&r
));
2210 /* Clear interior */
2211 SetBkColor(hdc
, bkgnd
);
2212 ExtTextOutW(hdc
, 0, 0, 2, &fillRect
, NULL
, 0, 0);
2214 /* Draw rectangular edge around tab */
2215 DrawEdge(hdc
, &r
, EDGE_RAISED
, BF_SOFT
|BF_LEFT
|BF_BOTTOM
|BF_RIGHT
);
2217 /* Now erase the righthand corner and draw diagonal edge */
2218 SetBkColor(hdc
, corner
);
2219 r1
.left
= r
.right
- ROUND_CORNER_SIZE
;
2220 r1
.bottom
= r
.bottom
;
2222 r1
.top
= r1
.bottom
- ROUND_CORNER_SIZE
- 1;
2223 ExtTextOutW(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2225 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_DIAGONAL_ENDBOTTOMLEFT
);
2227 /* Now erase the lefthand corner and draw diagonal edge */
2229 r1
.bottom
= r
.bottom
;
2230 r1
.right
= r1
.left
+ ROUND_CORNER_SIZE
;
2231 r1
.top
= r1
.bottom
- ROUND_CORNER_SIZE
- 1;
2232 ExtTextOutW(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2234 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_DIAGONAL_ENDTOPLEFT
);
2236 if (iItem
== infoPtr
->iSelected
)
2240 if (selectedRect
.left
== 0)
2245 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_LEFT
);
2252 /* Adjust both rectangles for bottommost row */
2253 if (TAB_GetItem(infoPtr
, iItem
)->rect
.top
== infoPtr
->uNumRows
-1)
2255 fillRect
.bottom
+= 3;
2259 TRACE("<top> item=%d, fill=(%s), edge=(%s)\n",
2260 iItem
, wine_dbgstr_rect(&fillRect
), wine_dbgstr_rect(&r
));
2262 /* Clear interior */
2263 SetBkColor(hdc
, bkgnd
);
2264 ExtTextOutW(hdc
, 0, 0, 2, &fillRect
, NULL
, 0, 0);
2266 /* Draw rectangular edge around tab */
2267 DrawEdge(hdc
, &r
, EDGE_RAISED
, BF_SOFT
|BF_LEFT
|BF_TOP
|BF_RIGHT
);
2269 /* Now erase the righthand corner and draw diagonal edge */
2270 SetBkColor(hdc
, corner
);
2271 r1
.left
= r
.right
- ROUND_CORNER_SIZE
;
2274 r1
.bottom
= r1
.top
+ ROUND_CORNER_SIZE
+ 1;
2275 ExtTextOutW(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2277 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_DIAGONAL_ENDBOTTOMRIGHT
);
2279 /* Now erase the lefthand corner and draw diagonal edge */
2282 r1
.right
= r1
.left
+ ROUND_CORNER_SIZE
;
2283 r1
.bottom
= r1
.top
+ ROUND_CORNER_SIZE
+ 1;
2284 ExtTextOutW(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2286 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_DIAGONAL_ENDTOPRIGHT
);
2291 TAB_DumpItemInternal(infoPtr
, iItem
);
2293 /* This modifies r to be the text rectangle. */
2294 TAB_DrawItemInterior(infoPtr
, hdc
, iItem
, &r
);
2298 /******************************************************************************
2301 * This method is used to draw the raised border around the tab control
2304 static void TAB_DrawBorder(const TAB_INFO
*infoPtr
, HDC hdc
)
2307 HTHEME theme
= GetWindowTheme (infoPtr
->hwnd
);
2309 GetClientRect (infoPtr
->hwnd
, &rect
);
2312 * Adjust for the style
2315 if (infoPtr
->uNumItem
)
2317 if ((infoPtr
->dwStyle
& TCS_BOTTOM
) && !(infoPtr
->dwStyle
& TCS_VERTICAL
))
2318 rect
.bottom
-= infoPtr
->tabHeight
* infoPtr
->uNumRows
+ CONTROL_BORDER_SIZEX
;
2319 else if((infoPtr
->dwStyle
& TCS_BOTTOM
) && (infoPtr
->dwStyle
& TCS_VERTICAL
))
2320 rect
.right
-= infoPtr
->tabHeight
* infoPtr
->uNumRows
+ CONTROL_BORDER_SIZEX
;
2321 else if(infoPtr
->dwStyle
& TCS_VERTICAL
)
2322 rect
.left
+= infoPtr
->tabHeight
* infoPtr
->uNumRows
+ CONTROL_BORDER_SIZEX
;
2323 else /* not TCS_VERTICAL and not TCS_BOTTOM */
2324 rect
.top
+= infoPtr
->tabHeight
* infoPtr
->uNumRows
+ CONTROL_BORDER_SIZEX
;
2327 TRACE("border=(%s)\n", wine_dbgstr_rect(&rect
));
2330 DrawThemeBackground (theme
, hdc
, TABP_PANE
, 0, &rect
, NULL
);
2332 DrawEdge(hdc
, &rect
, EDGE_RAISED
, BF_SOFT
|BF_RECT
);
2335 /******************************************************************************
2338 * This method repaints the tab control..
2340 static void TAB_Refresh (const TAB_INFO
*infoPtr
, HDC hdc
)
2345 if (!infoPtr
->DoRedraw
)
2348 hOldFont
= SelectObject (hdc
, infoPtr
->hFont
);
2350 if (infoPtr
->dwStyle
& TCS_BUTTONS
)
2352 for (i
= 0; i
< infoPtr
->uNumItem
; i
++)
2353 TAB_DrawItem (infoPtr
, hdc
, i
);
2357 /* Draw all the non selected item first */
2358 for (i
= 0; i
< infoPtr
->uNumItem
; i
++)
2360 if (i
!= infoPtr
->iSelected
)
2361 TAB_DrawItem (infoPtr
, hdc
, i
);
2364 /* Now, draw the border, draw it before the selected item
2365 * since the selected item overwrites part of the border. */
2366 TAB_DrawBorder (infoPtr
, hdc
);
2368 /* Then, draw the selected item */
2369 TAB_DrawItem (infoPtr
, hdc
, infoPtr
->iSelected
);
2372 SelectObject (hdc
, hOldFont
);
2375 static inline DWORD
TAB_GetRowCount (const TAB_INFO
*infoPtr
)
2377 TRACE("(%p)\n", infoPtr
);
2378 return infoPtr
->uNumRows
;
2381 static inline LRESULT
TAB_SetRedraw (TAB_INFO
*infoPtr
, BOOL doRedraw
)
2383 infoPtr
->DoRedraw
= doRedraw
;
2387 /******************************************************************************
2388 * TAB_EnsureSelectionVisible
2390 * This method will make sure that the current selection is completely
2391 * visible by scrolling until it is.
2393 static void TAB_EnsureSelectionVisible(
2396 INT iSelected
= infoPtr
->iSelected
;
2397 INT iOrigLeftmostVisible
= infoPtr
->leftmostVisible
;
2402 /* set the items row to the bottommost row or topmost row depending on
2404 if ((infoPtr
->uNumRows
> 1) && !(infoPtr
->dwStyle
& TCS_BUTTONS
))
2406 TAB_ITEM
*selected
= TAB_GetItem(infoPtr
, iSelected
);
2410 if(infoPtr
->dwStyle
& TCS_VERTICAL
)
2411 newselected
= selected
->rect
.left
;
2413 newselected
= selected
->rect
.top
;
2415 /* the target row is always (number of rows - 1)
2416 as row 0 is furthest from the clientRect */
2417 iTargetRow
= infoPtr
->uNumRows
- 1;
2419 if (newselected
!= iTargetRow
)
2422 if(infoPtr
->dwStyle
& TCS_VERTICAL
)
2424 for (i
=0; i
< infoPtr
->uNumItem
; i
++)
2426 /* move everything in the row of the selected item to the iTargetRow */
2427 TAB_ITEM
*item
= TAB_GetItem(infoPtr
, i
);
2429 if (item
->rect
.left
== newselected
)
2430 item
->rect
.left
= iTargetRow
;
2433 if (item
->rect
.left
> newselected
)
2440 for (i
=0; i
< infoPtr
->uNumItem
; i
++)
2442 TAB_ITEM
*item
= TAB_GetItem(infoPtr
, i
);
2444 if (item
->rect
.top
== newselected
)
2445 item
->rect
.top
= iTargetRow
;
2448 if (item
->rect
.top
> newselected
)
2453 TAB_RecalcHotTrack(infoPtr
, NULL
, NULL
, NULL
);
2458 * Do the trivial cases first.
2460 if ( (!infoPtr
->needsScrolling
) ||
2461 (infoPtr
->hwndUpDown
==0) || (infoPtr
->dwStyle
& TCS_VERTICAL
))
2464 if (infoPtr
->leftmostVisible
>= iSelected
)
2466 infoPtr
->leftmostVisible
= iSelected
;
2470 TAB_ITEM
*selected
= TAB_GetItem(infoPtr
, iSelected
);
2475 /* Calculate the part of the client area that is visible */
2476 GetClientRect(infoPtr
->hwnd
, &r
);
2479 GetClientRect(infoPtr
->hwndUpDown
, &r
);
2482 if ((selected
->rect
.right
-
2483 selected
->rect
.left
) >= width
)
2485 /* Special case: width of selected item is greater than visible
2488 infoPtr
->leftmostVisible
= iSelected
;
2492 for (i
= infoPtr
->leftmostVisible
; i
< infoPtr
->uNumItem
; i
++)
2494 if ((selected
->rect
.right
- TAB_GetItem(infoPtr
, i
)->rect
.left
) < width
)
2497 infoPtr
->leftmostVisible
= i
;
2501 if (infoPtr
->leftmostVisible
!= iOrigLeftmostVisible
)
2502 TAB_RecalcHotTrack(infoPtr
, NULL
, NULL
, NULL
);
2504 SendMessageW(infoPtr
->hwndUpDown
, UDM_SETPOS
, 0,
2505 MAKELONG(infoPtr
->leftmostVisible
, 0));
2508 /******************************************************************************
2509 * TAB_InvalidateTabArea
2511 * This method will invalidate the portion of the control that contains the
2512 * tabs. It is called when the state of the control changes and needs
2515 static void TAB_InvalidateTabArea(const TAB_INFO
*infoPtr
)
2517 RECT clientRect
, rInvalidate
, rAdjClient
;
2518 INT lastRow
= infoPtr
->uNumRows
- 1;
2521 if (lastRow
< 0) return;
2523 GetClientRect(infoPtr
->hwnd
, &clientRect
);
2524 rInvalidate
= clientRect
;
2525 rAdjClient
= clientRect
;
2527 TAB_AdjustRect(infoPtr
, 0, &rAdjClient
);
2529 TAB_InternalGetItemRect(infoPtr
, infoPtr
->uNumItem
-1 , &rect
, NULL
);
2530 if ((infoPtr
->dwStyle
& TCS_BOTTOM
) && (infoPtr
->dwStyle
& TCS_VERTICAL
))
2532 rInvalidate
.left
= rAdjClient
.right
;
2533 if (infoPtr
->uNumRows
== 1)
2534 rInvalidate
.bottom
= clientRect
.top
+ rect
.bottom
+ 2 * SELECTED_TAB_OFFSET
;
2536 else if(infoPtr
->dwStyle
& TCS_VERTICAL
)
2538 rInvalidate
.right
= rAdjClient
.left
;
2539 if (infoPtr
->uNumRows
== 1)
2540 rInvalidate
.bottom
= clientRect
.top
+ rect
.bottom
+ 2 * SELECTED_TAB_OFFSET
;
2542 else if (infoPtr
->dwStyle
& TCS_BOTTOM
)
2544 rInvalidate
.top
= rAdjClient
.bottom
;
2545 if (infoPtr
->uNumRows
== 1)
2546 rInvalidate
.right
= clientRect
.left
+ rect
.right
+ 2 * SELECTED_TAB_OFFSET
;
2550 rInvalidate
.bottom
= rAdjClient
.top
;
2551 if (infoPtr
->uNumRows
== 1)
2552 rInvalidate
.right
= clientRect
.left
+ rect
.right
+ 2 * SELECTED_TAB_OFFSET
;
2555 /* Punch out the updown control */
2556 if (infoPtr
->needsScrolling
&& (rInvalidate
.right
> 0)) {
2558 GetClientRect(infoPtr
->hwndUpDown
, &r
);
2559 if (rInvalidate
.right
> clientRect
.right
- r
.left
)
2560 rInvalidate
.right
= rInvalidate
.right
- (r
.right
- r
.left
);
2562 rInvalidate
.right
= clientRect
.right
- r
.left
;
2565 TRACE("invalidate (%s)\n", wine_dbgstr_rect(&rInvalidate
));
2567 InvalidateRect(infoPtr
->hwnd
, &rInvalidate
, TRUE
);
2570 static inline LRESULT
TAB_Paint (TAB_INFO
*infoPtr
, HDC hdcPaint
)
2579 hdc
= BeginPaint (infoPtr
->hwnd
, &ps
);
2580 TRACE("erase %d, rect=(%s)\n", ps
.fErase
, wine_dbgstr_rect(&ps
.rcPaint
));
2583 TAB_Refresh (infoPtr
, hdc
);
2586 EndPaint (infoPtr
->hwnd
, &ps
);
2592 TAB_InsertItemT (TAB_INFO
*infoPtr
, INT iItem
, const TCITEMW
*pti
, BOOL bUnicode
)
2597 GetClientRect (infoPtr
->hwnd
, &rect
);
2598 TRACE("Rect: %p %s\n", infoPtr
->hwnd
, wine_dbgstr_rect(&rect
));
2600 if (iItem
< 0) return -1;
2601 if (iItem
> infoPtr
->uNumItem
)
2602 iItem
= infoPtr
->uNumItem
;
2604 TAB_DumpItemExternalT(pti
, iItem
, bUnicode
);
2606 if (!(item
= Alloc(TAB_ITEM_SIZE(infoPtr
)))) return FALSE
;
2607 if (DPA_InsertPtr(infoPtr
->items
, iItem
, item
) == -1)
2613 if (infoPtr
->uNumItem
== 0)
2614 infoPtr
->iSelected
= 0;
2615 else if (iItem
<= infoPtr
->iSelected
)
2616 infoPtr
->iSelected
++;
2618 infoPtr
->uNumItem
++;
2620 item
->pszText
= NULL
;
2621 if (pti
->mask
& TCIF_TEXT
)
2624 Str_SetPtrW (&item
->pszText
, pti
->pszText
);
2626 Str_SetPtrAtoW (&item
->pszText
, (LPSTR
)pti
->pszText
);
2629 if (pti
->mask
& TCIF_IMAGE
)
2630 item
->iImage
= pti
->iImage
;
2634 if (pti
->mask
& TCIF_PARAM
)
2635 memcpy(item
->extra
, &pti
->lParam
, EXTRA_ITEM_SIZE(infoPtr
));
2637 memset(item
->extra
, 0, EXTRA_ITEM_SIZE(infoPtr
));
2639 TAB_SetItemBounds(infoPtr
);
2640 if (infoPtr
->uNumItem
> 1)
2641 TAB_InvalidateTabArea(infoPtr
);
2643 InvalidateRect(infoPtr
->hwnd
, NULL
, TRUE
);
2645 TRACE("[%p]: added item %d %s\n",
2646 infoPtr
->hwnd
, iItem
, debugstr_w(item
->pszText
));
2648 /* If we haven't set the current focus yet, set it now. */
2649 if (infoPtr
->uFocus
== -1)
2650 TAB_SetCurFocus(infoPtr
, iItem
);
2656 TAB_SetItemSize (TAB_INFO
*infoPtr
, INT cx
, INT cy
)
2659 BOOL bNeedPaint
= FALSE
;
2661 lResult
= MAKELONG(infoPtr
->tabWidth
, infoPtr
->tabHeight
);
2663 /* UNDOCUMENTED: If requested Width or Height is 0 this means that program wants to use auto size. */
2664 if (infoPtr
->dwStyle
& TCS_FIXEDWIDTH
&& (infoPtr
->tabWidth
!= cx
))
2666 infoPtr
->tabWidth
= cx
;
2670 if (infoPtr
->tabHeight
!= cy
)
2672 if ((infoPtr
->fHeightSet
= (cy
!= 0)))
2673 infoPtr
->tabHeight
= cy
;
2677 TRACE("was h=%d,w=%d, now h=%d,w=%d\n",
2678 HIWORD(lResult
), LOWORD(lResult
),
2679 infoPtr
->tabHeight
, infoPtr
->tabWidth
);
2683 TAB_SetItemBounds(infoPtr
);
2684 RedrawWindow(infoPtr
->hwnd
, NULL
, NULL
, RDW_ERASE
| RDW_INVALIDATE
| RDW_UPDATENOW
);
2690 static inline LRESULT
TAB_SetMinTabWidth (TAB_INFO
*infoPtr
, INT cx
)
2694 TRACE("(%p,%d)\n", infoPtr
, cx
);
2696 if (infoPtr
->tabMinWidth
< 0)
2697 oldcx
= DEFAULT_MIN_TAB_WIDTH
;
2699 oldcx
= infoPtr
->tabMinWidth
;
2700 infoPtr
->tabMinWidth
= cx
;
2701 TAB_SetItemBounds(infoPtr
);
2705 static inline LRESULT
2706 TAB_HighlightItem (TAB_INFO
*infoPtr
, INT iItem
, BOOL fHighlight
)
2712 TRACE("(%p,%d,%s)\n", infoPtr
, iItem
, fHighlight
? "true" : "false");
2714 if (iItem
< 0 || iItem
>= infoPtr
->uNumItem
)
2717 lpState
= &TAB_GetItem(infoPtr
, iItem
)->dwState
;
2718 oldState
= *lpState
;
2721 *lpState
|= TCIS_HIGHLIGHTED
;
2723 *lpState
&= ~TCIS_HIGHLIGHTED
;
2725 if ((oldState
!= *lpState
) && TAB_InternalGetItemRect (infoPtr
, iItem
, &r
, NULL
))
2726 InvalidateRect (infoPtr
->hwnd
, &r
, TRUE
);
2732 TAB_SetItemT (TAB_INFO
*infoPtr
, INT iItem
, LPTCITEMW tabItem
, BOOL bUnicode
)
2736 TRACE("(%p,%d,%p,%s)\n", infoPtr
, iItem
, tabItem
, bUnicode
? "true" : "false");
2738 if (iItem
< 0 || iItem
>= infoPtr
->uNumItem
)
2741 TAB_DumpItemExternalT(tabItem
, iItem
, bUnicode
);
2743 wineItem
= TAB_GetItem(infoPtr
, iItem
);
2745 if (tabItem
->mask
& TCIF_IMAGE
)
2746 wineItem
->iImage
= tabItem
->iImage
;
2748 if (tabItem
->mask
& TCIF_PARAM
)
2749 memcpy(wineItem
->extra
, &tabItem
->lParam
, infoPtr
->cbInfo
);
2751 if (tabItem
->mask
& TCIF_RTLREADING
)
2752 FIXME("TCIF_RTLREADING\n");
2754 if (tabItem
->mask
& TCIF_STATE
)
2755 wineItem
->dwState
= (wineItem
->dwState
& ~tabItem
->dwStateMask
) |
2756 ( tabItem
->dwState
& tabItem
->dwStateMask
);
2758 if (tabItem
->mask
& TCIF_TEXT
)
2760 Free(wineItem
->pszText
);
2761 wineItem
->pszText
= NULL
;
2763 Str_SetPtrW(&wineItem
->pszText
, tabItem
->pszText
);
2765 Str_SetPtrAtoW(&wineItem
->pszText
, (LPSTR
)tabItem
->pszText
);
2768 /* Update and repaint tabs */
2769 TAB_SetItemBounds(infoPtr
);
2770 TAB_InvalidateTabArea(infoPtr
);
2775 static inline LRESULT
TAB_GetItemCount (const TAB_INFO
*infoPtr
)
2778 return infoPtr
->uNumItem
;
2783 TAB_GetItemT (TAB_INFO
*infoPtr
, INT iItem
, LPTCITEMW tabItem
, BOOL bUnicode
)
2787 TRACE("(%p,%d,%p,%s)\n", infoPtr
, iItem
, tabItem
, bUnicode
? "true" : "false");
2789 if (!tabItem
) return FALSE
;
2791 if (iItem
< 0 || iItem
>= infoPtr
->uNumItem
)
2793 /* init requested fields */
2794 if (tabItem
->mask
& TCIF_IMAGE
) tabItem
->iImage
= 0;
2795 if (tabItem
->mask
& TCIF_PARAM
) tabItem
->lParam
= 0;
2796 if (tabItem
->mask
& TCIF_STATE
) tabItem
->dwState
= 0;
2800 wineItem
= TAB_GetItem(infoPtr
, iItem
);
2802 if (tabItem
->mask
& TCIF_IMAGE
)
2803 tabItem
->iImage
= wineItem
->iImage
;
2805 if (tabItem
->mask
& TCIF_PARAM
)
2806 memcpy(&tabItem
->lParam
, wineItem
->extra
, infoPtr
->cbInfo
);
2808 if (tabItem
->mask
& TCIF_RTLREADING
)
2809 FIXME("TCIF_RTLREADING\n");
2811 if (tabItem
->mask
& TCIF_STATE
)
2812 tabItem
->dwState
= wineItem
->dwState
& tabItem
->dwStateMask
;
2814 if (tabItem
->mask
& TCIF_TEXT
)
2817 Str_GetPtrW (wineItem
->pszText
, tabItem
->pszText
, tabItem
->cchTextMax
);
2819 Str_GetPtrWtoA (wineItem
->pszText
, (LPSTR
)tabItem
->pszText
, tabItem
->cchTextMax
);
2822 TAB_DumpItemExternalT(tabItem
, iItem
, bUnicode
);
2828 static LRESULT
TAB_DeleteItem (TAB_INFO
*infoPtr
, INT iItem
)
2832 TRACE("(%p, %d)\n", infoPtr
, iItem
);
2834 if (iItem
< 0 || iItem
>= infoPtr
->uNumItem
) return FALSE
;
2836 TAB_InvalidateTabArea(infoPtr
);
2837 item
= TAB_GetItem(infoPtr
, iItem
);
2838 Free(item
->pszText
);
2840 infoPtr
->uNumItem
--;
2841 DPA_DeletePtr(infoPtr
->items
, iItem
);
2843 if (infoPtr
->uNumItem
== 0)
2845 if (infoPtr
->iHotTracked
>= 0)
2847 KillTimer(infoPtr
->hwnd
, TAB_HOTTRACK_TIMER
);
2848 infoPtr
->iHotTracked
= -1;
2851 infoPtr
->iSelected
= -1;
2855 if (iItem
<= infoPtr
->iHotTracked
)
2857 /* When tabs move left/up, the hot track item may change */
2858 FIXME("Recalc hot track\n");
2862 /* adjust the selected index */
2863 if (iItem
== infoPtr
->iSelected
)
2864 infoPtr
->iSelected
= -1;
2865 else if (iItem
< infoPtr
->iSelected
)
2866 infoPtr
->iSelected
--;
2868 /* reposition and repaint tabs */
2869 TAB_SetItemBounds(infoPtr
);
2874 static inline LRESULT
TAB_DeleteAllItems (TAB_INFO
*infoPtr
)
2876 TRACE("(%p)\n", infoPtr
);
2877 while (infoPtr
->uNumItem
)
2878 TAB_DeleteItem (infoPtr
, 0);
2883 static inline LRESULT
TAB_GetFont (const TAB_INFO
*infoPtr
)
2885 TRACE("(%p) returning %p\n", infoPtr
, infoPtr
->hFont
);
2886 return (LRESULT
)infoPtr
->hFont
;
2889 static inline LRESULT
TAB_SetFont (TAB_INFO
*infoPtr
, HFONT hNewFont
)
2891 TRACE("(%p,%p)\n", infoPtr
, hNewFont
);
2893 infoPtr
->hFont
= hNewFont
;
2895 TAB_SetItemBounds(infoPtr
);
2897 TAB_InvalidateTabArea(infoPtr
);
2903 static inline LRESULT
TAB_GetImageList (const TAB_INFO
*infoPtr
)
2906 return (LRESULT
)infoPtr
->himl
;
2909 static inline LRESULT
TAB_SetImageList (TAB_INFO
*infoPtr
, HIMAGELIST himlNew
)
2911 HIMAGELIST himlPrev
= infoPtr
->himl
;
2912 TRACE("himl=%p\n", himlNew
);
2913 infoPtr
->himl
= himlNew
;
2914 TAB_SetItemBounds(infoPtr
);
2915 InvalidateRect(infoPtr
->hwnd
, NULL
, TRUE
);
2916 return (LRESULT
)himlPrev
;
2919 static inline LRESULT
TAB_GetUnicodeFormat (const TAB_INFO
*infoPtr
)
2921 TRACE("(%p)\n", infoPtr
);
2922 return infoPtr
->bUnicode
;
2925 static inline LRESULT
TAB_SetUnicodeFormat (TAB_INFO
*infoPtr
, BOOL bUnicode
)
2927 BOOL bTemp
= infoPtr
->bUnicode
;
2929 TRACE("(%p %d)\n", infoPtr
, bUnicode
);
2930 infoPtr
->bUnicode
= bUnicode
;
2935 static inline LRESULT
TAB_Size (TAB_INFO
*infoPtr
)
2937 /* I'm not really sure what the following code was meant to do.
2938 This is what it is doing:
2939 When WM_SIZE is sent with SIZE_RESTORED, the control
2940 gets positioned in the top left corner.
2944 UINT uPosFlags,cx,cy;
2948 parent = GetParent (hwnd);
2949 GetClientRect(parent, &parent_rect);
2952 if (GetWindowLongW(hwnd, GWL_STYLE) & CCS_NORESIZE)
2953 uPosFlags |= (SWP_NOSIZE | SWP_NOMOVE);
2955 SetWindowPos (hwnd, 0, parent_rect.left, parent_rect.top,
2956 cx, cy, uPosFlags | SWP_NOZORDER);
2958 FIXME("WM_SIZE flag %x %lx not handled\n", wParam, lParam);
2961 /* Recompute the size/position of the tabs. */
2962 TAB_SetItemBounds (infoPtr
);
2964 /* Force a repaint of the control. */
2965 InvalidateRect(infoPtr
->hwnd
, NULL
, TRUE
);
2971 static LRESULT
TAB_Create (HWND hwnd
, LPARAM lParam
)
2974 TEXTMETRICW fontMetrics
;
2979 infoPtr
= Alloc (sizeof(TAB_INFO
));
2981 SetWindowLongPtrW(hwnd
, 0, (DWORD_PTR
)infoPtr
);
2983 infoPtr
->hwnd
= hwnd
;
2984 infoPtr
->hwndNotify
= ((LPCREATESTRUCTW
)lParam
)->hwndParent
;
2985 infoPtr
->uNumItem
= 0;
2986 infoPtr
->uNumRows
= 0;
2987 infoPtr
->uHItemPadding
= 6;
2988 infoPtr
->uVItemPadding
= 3;
2989 infoPtr
->uHItemPadding_s
= 6;
2990 infoPtr
->uVItemPadding_s
= 3;
2992 infoPtr
->items
= DPA_Create(8);
2993 infoPtr
->hcurArrow
= LoadCursorW (0, (LPWSTR
)IDC_ARROW
);
2994 infoPtr
->iSelected
= -1;
2995 infoPtr
->iHotTracked
= -1;
2996 infoPtr
->uFocus
= -1;
2997 infoPtr
->hwndToolTip
= 0;
2998 infoPtr
->DoRedraw
= TRUE
;
2999 infoPtr
->needsScrolling
= FALSE
;
3000 infoPtr
->hwndUpDown
= 0;
3001 infoPtr
->leftmostVisible
= 0;
3002 infoPtr
->fHeightSet
= FALSE
;
3003 infoPtr
->bUnicode
= IsWindowUnicode (hwnd
);
3004 infoPtr
->cbInfo
= sizeof(LPARAM
);
3006 TRACE("Created tab control, hwnd [%p]\n", hwnd
);
3008 /* The tab control always has the WS_CLIPSIBLINGS style. Even
3009 if you don't specify it in CreateWindow. This is necessary in
3010 order for paint to work correctly. This follows windows behaviour. */
3011 style
= GetWindowLongW(hwnd
, GWL_STYLE
);
3012 if (style
& TCS_VERTICAL
) style
|= TCS_MULTILINE
;
3013 style
|= WS_CLIPSIBLINGS
;
3014 SetWindowLongW(hwnd
, GWL_STYLE
, style
);
3016 infoPtr
->dwStyle
= style
;
3017 infoPtr
->exStyle
= (style
& TCS_FLATBUTTONS
) ? TCS_EX_FLATSEPARATORS
: 0;
3019 if (infoPtr
->dwStyle
& TCS_TOOLTIPS
) {
3020 /* Create tooltip control */
3021 infoPtr
->hwndToolTip
=
3022 CreateWindowExW (0, TOOLTIPS_CLASSW
, NULL
, WS_POPUP
,
3023 CW_USEDEFAULT
, CW_USEDEFAULT
,
3024 CW_USEDEFAULT
, CW_USEDEFAULT
,
3027 /* Send NM_TOOLTIPSCREATED notification */
3028 if (infoPtr
->hwndToolTip
) {
3029 NMTOOLTIPSCREATED nmttc
;
3031 nmttc
.hdr
.hwndFrom
= hwnd
;
3032 nmttc
.hdr
.idFrom
= GetWindowLongPtrW(hwnd
, GWLP_ID
);
3033 nmttc
.hdr
.code
= NM_TOOLTIPSCREATED
;
3034 nmttc
.hwndToolTips
= infoPtr
->hwndToolTip
;
3036 SendMessageW (infoPtr
->hwndNotify
, WM_NOTIFY
,
3037 GetWindowLongPtrW(hwnd
, GWLP_ID
), (LPARAM
)&nmttc
);
3041 OpenThemeData (infoPtr
->hwnd
, themeClass
);
3044 * We need to get text information so we need a DC and we need to select
3048 hOldFont
= SelectObject (hdc
, GetStockObject (SYSTEM_FONT
));
3050 /* Use the system font to determine the initial height of a tab. */
3051 GetTextMetricsW(hdc
, &fontMetrics
);
3054 * Make sure there is enough space for the letters + growing the
3055 * selected item + extra space for the selected item.
3057 infoPtr
->tabHeight
= fontMetrics
.tmHeight
+ SELECTED_TAB_OFFSET
+
3058 ((infoPtr
->dwStyle
& TCS_BUTTONS
) ? 2 : 1) *
3059 infoPtr
->uVItemPadding
;
3061 /* Initialize the width of a tab. */
3062 if (infoPtr
->dwStyle
& TCS_FIXEDWIDTH
)
3063 infoPtr
->tabWidth
= GetDeviceCaps(hdc
, LOGPIXELSX
);
3065 infoPtr
->tabMinWidth
= -1;
3067 TRACE("tabH=%d, tabW=%d\n", infoPtr
->tabHeight
, infoPtr
->tabWidth
);
3069 SelectObject (hdc
, hOldFont
);
3070 ReleaseDC(hwnd
, hdc
);
3076 TAB_Destroy (TAB_INFO
*infoPtr
)
3080 SetWindowLongPtrW(infoPtr
->hwnd
, 0, 0);
3082 for (iItem
= infoPtr
->uNumItem
- 1; iItem
>= 0; iItem
--)
3084 TAB_ITEM
*tab
= TAB_GetItem(infoPtr
, iItem
);
3086 DPA_DeletePtr(infoPtr
->items
, iItem
);
3087 infoPtr
->uNumItem
--;
3092 DPA_Destroy(infoPtr
->items
);
3093 infoPtr
->items
= NULL
;
3095 if (infoPtr
->hwndToolTip
)
3096 DestroyWindow (infoPtr
->hwndToolTip
);
3098 if (infoPtr
->hwndUpDown
)
3099 DestroyWindow(infoPtr
->hwndUpDown
);
3101 if (infoPtr
->iHotTracked
>= 0)
3102 KillTimer(infoPtr
->hwnd
, TAB_HOTTRACK_TIMER
);
3104 CloseThemeData (GetWindowTheme (infoPtr
->hwnd
));
3110 /* update theme after a WM_THEMECHANGED message */
3111 static LRESULT
theme_changed(const TAB_INFO
*infoPtr
)
3113 HTHEME theme
= GetWindowTheme (infoPtr
->hwnd
);
3114 CloseThemeData (theme
);
3115 OpenThemeData (infoPtr
->hwnd
, themeClass
);
3119 static LRESULT
TAB_NCCalcSize(WPARAM wParam
)
3123 return WVR_ALIGNTOP
;
3126 static inline LRESULT
3127 TAB_SetItemExtra (TAB_INFO
*infoPtr
, INT cbInfo
)
3129 TRACE("(%p %d)\n", infoPtr
, cbInfo
);
3131 if (cbInfo
< 0 || infoPtr
->uNumItem
) return FALSE
;
3133 infoPtr
->cbInfo
= cbInfo
;
3137 static LRESULT
TAB_RemoveImage (TAB_INFO
*infoPtr
, INT image
)
3139 TRACE("%p %d\n", infoPtr
, image
);
3141 if (ImageList_Remove (infoPtr
->himl
, image
))
3146 /* shift indices, repaint items if needed */
3147 for (i
= 0; i
< infoPtr
->uNumItem
; i
++)
3149 idx
= &TAB_GetItem(infoPtr
, i
)->iImage
;
3158 if (TAB_InternalGetItemRect (infoPtr
, i
, &r
, NULL
))
3159 InvalidateRect (infoPtr
->hwnd
, &r
, TRUE
);
3168 TAB_SetExtendedStyle (TAB_INFO
*infoPtr
, DWORD exMask
, DWORD exStyle
)
3170 DWORD prevstyle
= infoPtr
->exStyle
;
3172 /* zero mask means all styles */
3173 if (exMask
== 0) exMask
= ~0;
3175 if (exMask
& TCS_EX_REGISTERDROP
)
3177 FIXME("TCS_EX_REGISTERDROP style unimplemented\n");
3178 exMask
&= ~TCS_EX_REGISTERDROP
;
3179 exStyle
&= ~TCS_EX_REGISTERDROP
;
3182 if (exMask
& TCS_EX_FLATSEPARATORS
)
3184 if ((prevstyle
^ exStyle
) & TCS_EX_FLATSEPARATORS
)
3186 infoPtr
->exStyle
^= TCS_EX_FLATSEPARATORS
;
3187 TAB_InvalidateTabArea(infoPtr
);
3194 static inline LRESULT
3195 TAB_GetExtendedStyle (const TAB_INFO
*infoPtr
)
3197 return infoPtr
->exStyle
;
3201 TAB_DeselectAll (TAB_INFO
*infoPtr
, BOOL excludesel
)
3204 INT i
, selected
= infoPtr
->iSelected
;
3206 TRACE("(%p, %d)\n", infoPtr
, excludesel
);
3208 if (!(infoPtr
->dwStyle
& TCS_BUTTONS
))
3211 for (i
= 0; i
< infoPtr
->uNumItem
; i
++)
3213 if ((TAB_GetItem(infoPtr
, i
)->dwState
& TCIS_BUTTONPRESSED
) &&
3216 TAB_GetItem(infoPtr
, i
)->dwState
&= ~TCIS_BUTTONPRESSED
;
3221 if (!excludesel
&& (selected
!= -1))
3223 TAB_GetItem(infoPtr
, selected
)->dwState
&= ~TCIS_BUTTONPRESSED
;
3224 infoPtr
->iSelected
= -1;
3229 TAB_InvalidateTabArea (infoPtr
);
3236 * Processes WM_STYLECHANGED messages.
3239 * [I] infoPtr : valid pointer to the tab data structure
3240 * [I] wStyleType : window style type (normal or extended)
3241 * [I] lpss : window style information
3246 static INT
TAB_StyleChanged(TAB_INFO
*infoPtr
, WPARAM wStyleType
,
3247 const STYLESTRUCT
*lpss
)
3249 TRACE("(styletype=%lx, styleOld=0x%08x, styleNew=0x%08x)\n",
3250 wStyleType
, lpss
->styleOld
, lpss
->styleNew
);
3252 if (wStyleType
!= GWL_STYLE
) return 0;
3254 infoPtr
->dwStyle
= lpss
->styleNew
;
3256 TAB_SetItemBounds (infoPtr
);
3257 InvalidateRect(infoPtr
->hwnd
, NULL
, TRUE
);
3262 static LRESULT WINAPI
3263 TAB_WindowProc (HWND hwnd
, UINT uMsg
, WPARAM wParam
, LPARAM lParam
)
3265 TAB_INFO
*infoPtr
= TAB_GetInfoPtr(hwnd
);
3267 TRACE("hwnd=%p msg=%x wParam=%lx lParam=%lx\n", hwnd
, uMsg
, wParam
, lParam
);
3268 if (!infoPtr
&& (uMsg
!= WM_CREATE
))
3269 return DefWindowProcW (hwnd
, uMsg
, wParam
, lParam
);
3273 case TCM_GETIMAGELIST
:
3274 return TAB_GetImageList (infoPtr
);
3276 case TCM_SETIMAGELIST
:
3277 return TAB_SetImageList (infoPtr
, (HIMAGELIST
)lParam
);
3279 case TCM_GETITEMCOUNT
:
3280 return TAB_GetItemCount (infoPtr
);
3284 return TAB_GetItemT (infoPtr
, (INT
)wParam
, (LPTCITEMW
)lParam
, uMsg
== TCM_GETITEMW
);
3288 return TAB_SetItemT (infoPtr
, (INT
)wParam
, (LPTCITEMW
)lParam
, uMsg
== TCM_SETITEMW
);
3290 case TCM_DELETEITEM
:
3291 return TAB_DeleteItem (infoPtr
, (INT
)wParam
);
3293 case TCM_DELETEALLITEMS
:
3294 return TAB_DeleteAllItems (infoPtr
);
3296 case TCM_GETITEMRECT
:
3297 return TAB_GetItemRect (infoPtr
, (INT
)wParam
, (LPRECT
)lParam
);
3300 return TAB_GetCurSel (infoPtr
);
3303 return TAB_HitTest (infoPtr
, (LPTCHITTESTINFO
)lParam
);
3306 return TAB_SetCurSel (infoPtr
, (INT
)wParam
);
3308 case TCM_INSERTITEMA
:
3309 case TCM_INSERTITEMW
:
3310 return TAB_InsertItemT (infoPtr
, (INT
)wParam
, (TCITEMW
*)lParam
, uMsg
== TCM_INSERTITEMW
);
3312 case TCM_SETITEMEXTRA
:
3313 return TAB_SetItemExtra (infoPtr
, (INT
)wParam
);
3315 case TCM_ADJUSTRECT
:
3316 return TAB_AdjustRect (infoPtr
, (BOOL
)wParam
, (LPRECT
)lParam
);
3318 case TCM_SETITEMSIZE
:
3319 return TAB_SetItemSize (infoPtr
, (INT
)LOWORD(lParam
), (INT
)HIWORD(lParam
));
3321 case TCM_REMOVEIMAGE
:
3322 return TAB_RemoveImage (infoPtr
, (INT
)wParam
);
3324 case TCM_SETPADDING
:
3325 return TAB_SetPadding (infoPtr
, lParam
);
3327 case TCM_GETROWCOUNT
:
3328 return TAB_GetRowCount(infoPtr
);
3330 case TCM_GETUNICODEFORMAT
:
3331 return TAB_GetUnicodeFormat (infoPtr
);
3333 case TCM_SETUNICODEFORMAT
:
3334 return TAB_SetUnicodeFormat (infoPtr
, (BOOL
)wParam
);
3336 case TCM_HIGHLIGHTITEM
:
3337 return TAB_HighlightItem (infoPtr
, (INT
)wParam
, (BOOL
)LOWORD(lParam
));
3339 case TCM_GETTOOLTIPS
:
3340 return TAB_GetToolTips (infoPtr
);
3342 case TCM_SETTOOLTIPS
:
3343 return TAB_SetToolTips (infoPtr
, (HWND
)wParam
);
3345 case TCM_GETCURFOCUS
:
3346 return TAB_GetCurFocus (infoPtr
);
3348 case TCM_SETCURFOCUS
:
3349 return TAB_SetCurFocus (infoPtr
, (INT
)wParam
);
3351 case TCM_SETMINTABWIDTH
:
3352 return TAB_SetMinTabWidth(infoPtr
, (INT
)lParam
);
3354 case TCM_DESELECTALL
:
3355 return TAB_DeselectAll (infoPtr
, (BOOL
)wParam
);
3357 case TCM_GETEXTENDEDSTYLE
:
3358 return TAB_GetExtendedStyle (infoPtr
);
3360 case TCM_SETEXTENDEDSTYLE
:
3361 return TAB_SetExtendedStyle (infoPtr
, wParam
, lParam
);
3364 return TAB_GetFont (infoPtr
);
3367 return TAB_SetFont (infoPtr
, (HFONT
)wParam
);
3370 return TAB_Create (hwnd
, lParam
);
3373 return TAB_Destroy (infoPtr
);
3376 return DLGC_WANTARROWS
| DLGC_WANTCHARS
;
3378 case WM_LBUTTONDOWN
:
3379 return TAB_LButtonDown (infoPtr
, wParam
, lParam
);
3382 return TAB_LButtonUp (infoPtr
);
3385 return SendMessageW(infoPtr
->hwndNotify
, WM_NOTIFY
, wParam
, lParam
);
3388 TAB_RButtonUp (infoPtr
);
3389 return DefWindowProcW (hwnd
, uMsg
, wParam
, lParam
);
3392 return TAB_MouseMove (infoPtr
, wParam
, lParam
);
3394 case WM_PRINTCLIENT
:
3396 return TAB_Paint (infoPtr
, (HDC
)wParam
);
3399 return TAB_Size (infoPtr
);
3402 return TAB_SetRedraw (infoPtr
, (BOOL
)wParam
);
3405 return TAB_OnHScroll(infoPtr
, (int)LOWORD(wParam
), (int)HIWORD(wParam
));
3407 case WM_STYLECHANGED
:
3408 return TAB_StyleChanged(infoPtr
, wParam
, (LPSTYLESTRUCT
)lParam
);
3410 case WM_SYSCOLORCHANGE
:
3411 COMCTL32_RefreshSysColors();
3414 case WM_THEMECHANGED
:
3415 return theme_changed (infoPtr
);
3418 TAB_KillFocus(infoPtr
);
3420 TAB_FocusChanging(infoPtr
);
3421 break; /* Don't disturb normal focus behavior */
3424 return TAB_KeyDown(infoPtr
, wParam
, lParam
);
3427 return TAB_NCHitTest(infoPtr
, lParam
);
3430 return TAB_NCCalcSize(wParam
);
3433 if (uMsg
>= WM_USER
&& uMsg
< WM_APP
&& !COMCTL32_IsReflectedMessage(uMsg
))
3434 WARN("unknown msg %04x wp=%08lx lp=%08lx\n",
3435 uMsg
, wParam
, lParam
);
3438 return DefWindowProcW(hwnd
, uMsg
, wParam
, lParam
);
3447 ZeroMemory (&wndClass
, sizeof(WNDCLASSW
));
3448 wndClass
.style
= CS_GLOBALCLASS
| CS_DBLCLKS
| CS_HREDRAW
| CS_VREDRAW
;
3449 wndClass
.lpfnWndProc
= TAB_WindowProc
;
3450 wndClass
.cbClsExtra
= 0;
3451 wndClass
.cbWndExtra
= sizeof(TAB_INFO
*);
3452 wndClass
.hCursor
= LoadCursorW (0, (LPWSTR
)IDC_ARROW
);
3453 wndClass
.hbrBackground
= (HBRUSH
)(COLOR_BTNFACE
+1);
3454 wndClass
.lpszClassName
= WC_TABCONTROLW
;
3456 RegisterClassW (&wndClass
);
3461 TAB_Unregister (void)
3463 UnregisterClassW (WC_TABCONTROLW
, NULL
);