4 * Copyright 1998 Anders Carlsson
5 * Copyright 1999 Alex Priem <alexp@sci.kun.nl>
6 * Copyright 1999 Francis Beaudet
7 * Copyright 2003 Vitaliy Margolen
9 * This library is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Lesser General Public
11 * License as published by the Free Software Foundation; either
12 * version 2.1 of the License, or (at your option) any later version.
14 * This library is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Lesser General Public License for more details.
19 * You should have received a copy of the GNU Lesser General Public
20 * License along with this library; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
25 * This code was audited for completeness against the documented features
26 * of Comctl32.dll version 6.0 on May. 20, 2005, by James Hawkins.
28 * Unless otherwise noted, we believe this code to be complete, as per
29 * the specification mentioned above.
30 * If you discover missing features, or bugs, please note them below.
35 * TCS_MULTISELECT - implement for VK_SPACE selection
68 #include "wine/debug.h"
71 WINE_DEFAULT_DEBUG_CHANNEL(tab
);
78 RECT rect
; /* bounding rectangle of the item relative to the
79 * leftmost item (the leftmost item, 0, would have a
80 * "left" member of 0 in this rectangle)
82 * additionally the top member holds the row number
83 * and bottom is unused and should be 0 */
84 BYTE extra
[1]; /* Space for caller supplied info, variable size */
87 /* The size of a tab item depends on how much extra data is requested.
88 TCM_INSERTITEM always stores at least LPARAM sized data. */
89 #define EXTRA_ITEM_SIZE(infoPtr) (max((infoPtr)->cbInfo, sizeof(LPARAM)))
90 #define TAB_ITEM_SIZE(infoPtr) FIELD_OFFSET(TAB_ITEM, extra[EXTRA_ITEM_SIZE(infoPtr)])
94 HWND hwnd
; /* Tab control window */
95 HWND hwndNotify
; /* notification window (parent) */
96 UINT uNumItem
; /* number of tab items */
97 UINT uNumRows
; /* number of tab rows */
98 INT tabHeight
; /* height of the tab row */
99 INT tabWidth
; /* width of tabs */
100 INT tabMinWidth
; /* minimum width of items */
101 USHORT uHItemPadding
; /* amount of horizontal padding, in pixels */
102 USHORT uVItemPadding
; /* amount of vertical padding, in pixels */
103 USHORT uHItemPadding_s
; /* Set amount of horizontal padding, in pixels */
104 USHORT uVItemPadding_s
; /* Set amount of vertical padding, in pixels */
105 HFONT hFont
; /* handle to the current font */
106 HCURSOR hcurArrow
; /* handle to the current cursor */
107 HIMAGELIST himl
; /* handle to an image list (may be 0) */
108 HWND hwndToolTip
; /* handle to tab's tooltip */
109 INT leftmostVisible
; /* Used for scrolling, this member contains
110 * the index of the first visible item */
111 INT iSelected
; /* the currently selected item */
112 INT iHotTracked
; /* the highlighted item under the mouse */
113 INT uFocus
; /* item which has the focus */
114 BOOL DoRedraw
; /* flag for redrawing when tab contents is changed*/
115 BOOL needsScrolling
; /* TRUE if the size of the tabs is greater than
116 * the size of the control */
117 BOOL fHeightSet
; /* was the height of the tabs explicitly set? */
118 BOOL bUnicode
; /* Unicode control? */
119 HWND hwndUpDown
; /* Updown control used for scrolling */
120 INT cbInfo
; /* Number of bytes of caller supplied info per tab */
122 DWORD exStyle
; /* Extended style used, currently:
123 TCS_EX_FLATSEPARATORS, TCS_EX_REGISTERDROP */
124 DWORD dwStyle
; /* the cached window GWL_STYLE */
126 HDPA items
; /* dynamic array of TAB_ITEM* pointers */
129 /******************************************************************************
130 * Positioning constants
132 #define SELECTED_TAB_OFFSET 2
133 #define ROUND_CORNER_SIZE 2
134 #define DISPLAY_AREA_PADDINGX 2
135 #define DISPLAY_AREA_PADDINGY 2
136 #define CONTROL_BORDER_SIZEX 2
137 #define CONTROL_BORDER_SIZEY 2
138 #define BUTTON_SPACINGX 3
139 #define BUTTON_SPACINGY 3
140 #define FLAT_BTN_SPACINGX 8
141 #define DEFAULT_MIN_TAB_WIDTH 54
142 #define DEFAULT_PADDING_X 6
143 #define EXTRA_ICON_PADDING 3
145 #define TAB_GetInfoPtr(hwnd) ((TAB_INFO *)GetWindowLongPtrW(hwnd,0))
147 #define GET_DEFAULT_MIN_TAB_WIDTH(infoPtr) (DEFAULT_MIN_TAB_WIDTH - (DEFAULT_PADDING_X - (infoPtr)->uHItemPadding) * 2)
149 /******************************************************************************
150 * Hot-tracking timer constants
152 #define TAB_HOTTRACK_TIMER 1
153 #define TAB_HOTTRACK_TIMER_INTERVAL 100 /* milliseconds */
155 static const WCHAR themeClass
[] = L
"Tab";
157 static inline TAB_ITEM
* TAB_GetItem(const TAB_INFO
*infoPtr
, INT i
)
159 assert(i
>= 0 && i
< infoPtr
->uNumItem
);
160 return DPA_GetPtr(infoPtr
->items
, i
);
163 /******************************************************************************
166 static void TAB_InvalidateTabArea(const TAB_INFO
*);
167 static void TAB_EnsureSelectionVisible(TAB_INFO
*);
168 static void TAB_DrawItemInterior(const TAB_INFO
*, HDC
, INT
, RECT
*);
169 static LRESULT
TAB_DeselectAll(TAB_INFO
*, BOOL
);
170 static BOOL
TAB_InternalGetItemRect(const TAB_INFO
*, INT
, RECT
*, RECT
*);
173 TAB_SendSimpleNotify (const TAB_INFO
*infoPtr
, UINT code
)
177 nmhdr
.hwndFrom
= infoPtr
->hwnd
;
178 nmhdr
.idFrom
= GetWindowLongPtrW(infoPtr
->hwnd
, GWLP_ID
);
181 return (BOOL
) SendMessageW (infoPtr
->hwndNotify
, WM_NOTIFY
,
182 nmhdr
.idFrom
, (LPARAM
) &nmhdr
);
186 TAB_RelayEvent (HWND hwndTip
, HWND hwndMsg
, UINT uMsg
,
187 WPARAM wParam
, LPARAM lParam
)
195 msg
.time
= GetMessageTime ();
196 msg
.pt
.x
= (short)LOWORD(GetMessagePos ());
197 msg
.pt
.y
= (short)HIWORD(GetMessagePos ());
199 SendMessageW (hwndTip
, TTM_RELAYEVENT
, 0, (LPARAM
)&msg
);
203 TAB_DumpItemExternalT(const TCITEMW
*pti
, UINT iItem
, BOOL isW
)
206 TRACE("external tab %d, mask=0x%08x, dwState %#lx, dwStateMask %#lx, cchTextMax=0x%08x\n",
207 iItem
, pti
->mask
, pti
->dwState
, pti
->dwStateMask
, pti
->cchTextMax
);
208 TRACE("external tab %d, iImage=%d, lParam %Ix, 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 %#lx, pszText %s, iImage %d\n", iItem
, ti
->dwState
, debugstr_w(ti
->pszText
), ti
->iImage
);
220 TRACE("tab %d, rect.left=%ld, rect.top(row)=%ld\n", iItem
, ti
->rect
.left
, ti
->rect
.top
);
225 * the index of the selected tab, or -1 if no tab is selected. */
226 static inline LRESULT
TAB_GetCurSel (const TAB_INFO
*infoPtr
)
228 TRACE("(%p)\n", infoPtr
);
229 return infoPtr
->iSelected
;
233 * the index of the tab item that has the focus. */
234 static inline LRESULT
235 TAB_GetCurFocus (const TAB_INFO
*infoPtr
)
237 TRACE("(%p)\n", infoPtr
);
238 return infoPtr
->uFocus
;
241 static inline LRESULT
TAB_GetToolTips (const TAB_INFO
*infoPtr
)
243 TRACE("(%p)\n", infoPtr
);
244 return (LRESULT
)infoPtr
->hwndToolTip
;
247 static inline LRESULT
TAB_SetCurSel (TAB_INFO
*infoPtr
, INT iItem
)
249 INT prevItem
= infoPtr
->iSelected
;
251 TRACE("(%p %d)\n", infoPtr
, iItem
);
253 if (iItem
>= (INT
)infoPtr
->uNumItem
)
256 if (prevItem
!= iItem
) {
258 TAB_GetItem(infoPtr
, prevItem
)->dwState
&= ~TCIS_BUTTONPRESSED
;
262 TAB_GetItem(infoPtr
, iItem
)->dwState
|= TCIS_BUTTONPRESSED
;
263 infoPtr
->iSelected
= iItem
;
264 infoPtr
->uFocus
= iItem
;
268 infoPtr
->iSelected
= -1;
269 infoPtr
->uFocus
= -1;
272 TAB_EnsureSelectionVisible(infoPtr
);
273 TAB_InvalidateTabArea(infoPtr
);
279 static LRESULT
TAB_SetCurFocus (TAB_INFO
*infoPtr
, INT iItem
)
281 TRACE("(%p %d)\n", infoPtr
, iItem
);
284 infoPtr
->uFocus
= -1;
285 if (infoPtr
->iSelected
!= -1) {
286 infoPtr
->iSelected
= -1;
287 TAB_SendSimpleNotify(infoPtr
, TCN_SELCHANGE
);
288 TAB_InvalidateTabArea(infoPtr
);
291 else if (iItem
< infoPtr
->uNumItem
) {
292 if (infoPtr
->dwStyle
& TCS_BUTTONS
) {
293 /* set focus to new item, leave selection as is */
294 if (infoPtr
->uFocus
!= iItem
) {
295 INT prev_focus
= infoPtr
->uFocus
;
298 infoPtr
->uFocus
= iItem
;
300 if (prev_focus
!= infoPtr
->iSelected
) {
301 if (TAB_InternalGetItemRect(infoPtr
, prev_focus
, &r
, NULL
))
302 InvalidateRect(infoPtr
->hwnd
, &r
, FALSE
);
305 if (TAB_InternalGetItemRect(infoPtr
, iItem
, &r
, NULL
))
306 InvalidateRect(infoPtr
->hwnd
, &r
, FALSE
);
308 TAB_SendSimpleNotify(infoPtr
, TCN_FOCUSCHANGE
);
311 INT oldFocus
= infoPtr
->uFocus
;
312 if (infoPtr
->iSelected
!= iItem
|| oldFocus
== -1 ) {
313 infoPtr
->uFocus
= iItem
;
314 if (oldFocus
!= -1) {
315 if (!TAB_SendSimpleNotify(infoPtr
, TCN_SELCHANGING
)) {
316 infoPtr
->iSelected
= iItem
;
317 TAB_SendSimpleNotify(infoPtr
, TCN_SELCHANGE
);
320 infoPtr
->iSelected
= iItem
;
321 TAB_EnsureSelectionVisible(infoPtr
);
322 TAB_InvalidateTabArea(infoPtr
);
330 static inline LRESULT
331 TAB_SetToolTips (TAB_INFO
*infoPtr
, HWND hwndToolTip
)
333 TRACE("%p %p\n", infoPtr
, hwndToolTip
);
334 infoPtr
->hwndToolTip
= hwndToolTip
;
338 static inline LRESULT
339 TAB_SetPadding (TAB_INFO
*infoPtr
, LPARAM lParam
)
341 TRACE("(%p %d %d)\n", infoPtr
, LOWORD(lParam
), HIWORD(lParam
));
342 infoPtr
->uHItemPadding_s
= LOWORD(lParam
);
343 infoPtr
->uVItemPadding_s
= HIWORD(lParam
);
348 /******************************************************************************
349 * TAB_InternalGetItemRect
351 * This method will calculate the rectangle representing a given tab item in
352 * client coordinates. This method takes scrolling into account.
354 * This method returns TRUE if the item is visible in the window and FALSE
355 * if it is completely outside the client area.
357 static BOOL
TAB_InternalGetItemRect(
358 const TAB_INFO
* infoPtr
,
363 RECT tmpItemRect
,clientRect
;
365 /* Perform a sanity check and a trivial visibility check. */
366 if ( (infoPtr
->uNumItem
<= 0) ||
367 (itemIndex
>= infoPtr
->uNumItem
) ||
368 (!(((infoPtr
->dwStyle
& TCS_MULTILINE
) || (infoPtr
->dwStyle
& TCS_VERTICAL
))) &&
369 (itemIndex
< infoPtr
->leftmostVisible
)))
371 TRACE("Not Visible\n");
372 SetRect(itemRect
, 0, 0, 0, infoPtr
->tabHeight
);
373 SetRectEmpty(selectedRect
);
378 * Avoid special cases in this procedure by assigning the "out"
379 * parameters if the caller didn't supply them
381 if (itemRect
== NULL
)
382 itemRect
= &tmpItemRect
;
384 /* Retrieve the unmodified item rect. */
385 *itemRect
= TAB_GetItem(infoPtr
,itemIndex
)->rect
;
387 /* calculate the times bottom and top based on the row */
388 GetClientRect(infoPtr
->hwnd
, &clientRect
);
390 if ((infoPtr
->dwStyle
& TCS_BOTTOM
) && (infoPtr
->dwStyle
& TCS_VERTICAL
))
392 itemRect
->right
= clientRect
.right
- SELECTED_TAB_OFFSET
- itemRect
->left
* infoPtr
->tabHeight
-
393 ((infoPtr
->dwStyle
& TCS_BUTTONS
) ? itemRect
->left
* BUTTON_SPACINGX
: 0);
394 itemRect
->left
= itemRect
->right
- infoPtr
->tabHeight
;
396 else if (infoPtr
->dwStyle
& TCS_VERTICAL
)
398 itemRect
->left
= clientRect
.left
+ SELECTED_TAB_OFFSET
+ itemRect
->left
* infoPtr
->tabHeight
+
399 ((infoPtr
->dwStyle
& TCS_BUTTONS
) ? itemRect
->left
* BUTTON_SPACINGX
: 0);
400 itemRect
->right
= itemRect
->left
+ infoPtr
->tabHeight
;
402 else if (infoPtr
->dwStyle
& TCS_BOTTOM
)
404 itemRect
->bottom
= clientRect
.bottom
- itemRect
->top
* infoPtr
->tabHeight
-
405 ((infoPtr
->dwStyle
& TCS_BUTTONS
) ? itemRect
->top
* BUTTON_SPACINGY
: SELECTED_TAB_OFFSET
);
406 itemRect
->top
= itemRect
->bottom
- infoPtr
->tabHeight
;
408 else /* not TCS_BOTTOM and not TCS_VERTICAL */
410 itemRect
->top
= clientRect
.top
+ itemRect
->top
* infoPtr
->tabHeight
+
411 ((infoPtr
->dwStyle
& TCS_BUTTONS
) ? itemRect
->top
* BUTTON_SPACINGY
: SELECTED_TAB_OFFSET
);
412 itemRect
->bottom
= itemRect
->top
+ infoPtr
->tabHeight
;
416 * "scroll" it to make sure the item at the very left of the
417 * tab control is the leftmost visible tab.
419 if(infoPtr
->dwStyle
& TCS_VERTICAL
)
423 -TAB_GetItem(infoPtr
, infoPtr
->leftmostVisible
)->rect
.top
);
426 * Move the rectangle so the first item is slightly offset from
427 * the bottom of the tab control.
431 SELECTED_TAB_OFFSET
);
436 -TAB_GetItem(infoPtr
, infoPtr
->leftmostVisible
)->rect
.left
,
440 * Move the rectangle so the first item is slightly offset from
441 * the left of the tab control.
447 TRACE("item %d tab h=%d, rect=(%s)\n",
448 itemIndex
, infoPtr
->tabHeight
, wine_dbgstr_rect(itemRect
));
450 /* Now, calculate the position of the item as if it were selected. */
451 if (selectedRect
!=NULL
)
453 *selectedRect
= *itemRect
;
455 /* The rectangle of a selected item is a bit wider. */
456 if(infoPtr
->dwStyle
& TCS_VERTICAL
)
457 InflateRect(selectedRect
, 0, SELECTED_TAB_OFFSET
);
459 InflateRect(selectedRect
, SELECTED_TAB_OFFSET
, 0);
461 /* If it also a bit higher. */
462 if ((infoPtr
->dwStyle
& TCS_BOTTOM
) && (infoPtr
->dwStyle
& TCS_VERTICAL
))
464 selectedRect
->left
-= 2; /* the border is thicker on the right */
465 selectedRect
->right
+= SELECTED_TAB_OFFSET
;
467 else if (infoPtr
->dwStyle
& TCS_VERTICAL
)
469 selectedRect
->left
-= SELECTED_TAB_OFFSET
;
470 selectedRect
->right
+= 1;
472 else if (infoPtr
->dwStyle
& TCS_BOTTOM
)
474 selectedRect
->bottom
+= SELECTED_TAB_OFFSET
;
476 else /* not TCS_BOTTOM and not TCS_VERTICAL */
478 selectedRect
->top
-= SELECTED_TAB_OFFSET
;
479 selectedRect
->bottom
-= 1;
483 /* Check for visibility */
484 if (infoPtr
->dwStyle
& TCS_VERTICAL
)
485 return (itemRect
->top
< clientRect
.bottom
) && (itemRect
->bottom
> clientRect
.top
);
487 return (itemRect
->left
< clientRect
.right
) && (itemRect
->right
> clientRect
.left
);
491 TAB_GetItemRect(const TAB_INFO
*infoPtr
, INT item
, RECT
*rect
)
493 TRACE("(%p, %d, %p)\n", infoPtr
, item
, rect
);
494 return TAB_InternalGetItemRect(infoPtr
, item
, rect
, NULL
);
497 /******************************************************************************
500 * This method is called to handle keyboard input
502 static LRESULT
TAB_KeyDown(TAB_INFO
* infoPtr
, WPARAM keyCode
, LPARAM lParam
)
507 /* TCN_KEYDOWN notification sent always */
508 nm
.hdr
.hwndFrom
= infoPtr
->hwnd
;
509 nm
.hdr
.idFrom
= GetWindowLongPtrW(infoPtr
->hwnd
, GWLP_ID
);
510 nm
.hdr
.code
= TCN_KEYDOWN
;
513 SendMessageW(infoPtr
->hwndNotify
, WM_NOTIFY
, nm
.hdr
.idFrom
, (LPARAM
)&nm
);
518 newItem
= infoPtr
->uFocus
- 1;
521 newItem
= infoPtr
->uFocus
+ 1;
525 /* If we changed to a valid item, change focused item */
526 if (newItem
>= 0 && newItem
< infoPtr
->uNumItem
&& infoPtr
->uFocus
!= newItem
)
527 TAB_SetCurFocus(infoPtr
, newItem
);
533 * WM_KILLFOCUS handler
535 static void TAB_KillFocus(TAB_INFO
*infoPtr
)
537 /* clear current focused item back to selected for TCS_BUTTONS */
538 if ((infoPtr
->dwStyle
& TCS_BUTTONS
) && (infoPtr
->uFocus
!= infoPtr
->iSelected
))
542 if (TAB_InternalGetItemRect(infoPtr
, infoPtr
->uFocus
, &r
, NULL
))
543 InvalidateRect(infoPtr
->hwnd
, &r
, FALSE
);
545 infoPtr
->uFocus
= infoPtr
->iSelected
;
549 /******************************************************************************
552 * This method is called whenever the focus goes in or out of this control
553 * it is used to update the visual state of the control.
555 static void TAB_FocusChanging(const TAB_INFO
*infoPtr
)
561 * Get the rectangle for the item.
563 isVisible
= TAB_InternalGetItemRect(infoPtr
,
569 * If the rectangle is not completely invisible, invalidate that
570 * portion of the window.
574 TRACE("invalidate (%s)\n", wine_dbgstr_rect(&selectedRect
));
575 InvalidateRect(infoPtr
->hwnd
, &selectedRect
, TRUE
);
579 static INT
TAB_InternalHitTest (const TAB_INFO
*infoPtr
, POINT pt
, UINT
*flags
)
584 for (iCount
= 0; iCount
< infoPtr
->uNumItem
; iCount
++)
586 TAB_InternalGetItemRect(infoPtr
, iCount
, &rect
, NULL
);
588 if (PtInRect(&rect
, pt
))
590 *flags
= TCHT_ONITEM
;
595 *flags
= TCHT_NOWHERE
;
599 static inline LRESULT
600 TAB_HitTest (const TAB_INFO
*infoPtr
, LPTCHITTESTINFO lptest
)
602 TRACE("(%p, %p)\n", infoPtr
, lptest
);
603 return TAB_InternalHitTest (infoPtr
, lptest
->pt
, &lptest
->flags
);
606 /******************************************************************************
609 * Napster v2b5 has a tab control for its main navigation which has a client
610 * area that covers the whole area of the dialog pages.
611 * That's why it receives all msgs for that area and the underlying dialog ctrls
613 * So I decided that we should handle WM_NCHITTEST here and return
614 * HTTRANSPARENT if we don't hit the tab control buttons.
615 * FIXME: WM_NCHITTEST handling correct ? Fix it if you know that Windows
616 * doesn't do it that way. Maybe depends on tab control styles ?
618 static inline LRESULT
619 TAB_NCHitTest (const TAB_INFO
*infoPtr
, LPARAM lParam
)
624 pt
.x
= (short)LOWORD(lParam
);
625 pt
.y
= (short)HIWORD(lParam
);
626 ScreenToClient(infoPtr
->hwnd
, &pt
);
628 if (TAB_InternalHitTest(infoPtr
, pt
, &dummyflag
) == -1)
629 return HTTRANSPARENT
;
635 TAB_LButtonDown (TAB_INFO
*infoPtr
, WPARAM wParam
, LPARAM lParam
)
641 if (infoPtr
->hwndToolTip
)
642 TAB_RelayEvent (infoPtr
->hwndToolTip
, infoPtr
->hwnd
,
643 WM_LBUTTONDOWN
, wParam
, lParam
);
645 if (!(infoPtr
->dwStyle
& TCS_FOCUSNEVER
)) {
646 SetFocus (infoPtr
->hwnd
);
649 if (infoPtr
->hwndToolTip
)
650 TAB_RelayEvent (infoPtr
->hwndToolTip
, infoPtr
->hwnd
,
651 WM_LBUTTONDOWN
, wParam
, lParam
);
653 pt
.x
= (short)LOWORD(lParam
);
654 pt
.y
= (short)HIWORD(lParam
);
656 newItem
= TAB_InternalHitTest (infoPtr
, pt
, &dummy
);
658 TRACE("On Tab, item %d\n", newItem
);
660 if ((newItem
!= -1) && (infoPtr
->iSelected
!= newItem
))
662 if ((infoPtr
->dwStyle
& TCS_BUTTONS
) && (infoPtr
->dwStyle
& TCS_MULTISELECT
) &&
663 (wParam
& MK_CONTROL
))
667 /* toggle multiselection */
668 TAB_GetItem(infoPtr
, newItem
)->dwState
^= TCIS_BUTTONPRESSED
;
669 if (TAB_InternalGetItemRect (infoPtr
, newItem
, &r
, NULL
))
670 InvalidateRect (infoPtr
->hwnd
, &r
, TRUE
);
675 BOOL pressed
= FALSE
;
677 /* any button pressed ? */
678 for (i
= 0; i
< infoPtr
->uNumItem
; i
++)
679 if ((TAB_GetItem (infoPtr
, i
)->dwState
& TCIS_BUTTONPRESSED
) &&
680 (infoPtr
->iSelected
!= i
))
686 if (TAB_SendSimpleNotify(infoPtr
, TCN_SELCHANGING
))
690 TAB_DeselectAll (infoPtr
, FALSE
);
692 TAB_SetCurSel(infoPtr
, newItem
);
694 TAB_SendSimpleNotify(infoPtr
, TCN_SELCHANGE
);
701 static inline LRESULT
702 TAB_LButtonUp (const TAB_INFO
*infoPtr
)
704 TAB_SendSimpleNotify(infoPtr
, NM_CLICK
);
710 TAB_RButtonUp (const TAB_INFO
*infoPtr
)
712 TAB_SendSimpleNotify(infoPtr
, NM_RCLICK
);
715 /******************************************************************************
716 * TAB_DrawLoneItemInterior
718 * This calls TAB_DrawItemInterior. However, TAB_DrawItemInterior is normally
719 * called by TAB_DrawItem which is normally called by TAB_Refresh which sets
720 * up the device context and font. This routine does the same setup but
721 * only calls TAB_DrawItemInterior for the single specified item.
724 TAB_DrawLoneItemInterior(const TAB_INFO
* infoPtr
, int iItem
)
726 HDC hdc
= GetDC(infoPtr
->hwnd
);
729 /* Clip UpDown control to not draw over it */
730 if (infoPtr
->needsScrolling
)
732 GetWindowRect(infoPtr
->hwnd
, &rC
);
733 GetWindowRect(infoPtr
->hwndUpDown
, &r
);
734 ExcludeClipRect(hdc
, r
.left
- rC
.left
, r
.top
- rC
.top
, r
.right
- rC
.left
, r
.bottom
- rC
.top
);
736 TAB_DrawItemInterior(infoPtr
, hdc
, iItem
, NULL
);
737 ReleaseDC(infoPtr
->hwnd
, hdc
);
740 /* update a tab after hottracking - invalidate it or just redraw the interior,
741 * based on whether theming is used or not */
742 static inline void hottrack_refresh(const TAB_INFO
*infoPtr
, int tabIndex
)
744 if (tabIndex
== -1) return;
746 if (GetWindowTheme (infoPtr
->hwnd
))
749 TAB_InternalGetItemRect(infoPtr
, tabIndex
, &rect
, NULL
);
750 InvalidateRect (infoPtr
->hwnd
, &rect
, FALSE
);
753 TAB_DrawLoneItemInterior(infoPtr
, tabIndex
);
756 /******************************************************************************
757 * TAB_HotTrackTimerProc
759 * When a mouse-move event causes a tab to be highlighted (hot-tracking), a
760 * timer is setup so we can check if the mouse is moved out of our window.
761 * (We don't get an event when the mouse leaves, the mouse-move events just
762 * stop being delivered to our window and just start being delivered to
763 * another window.) This function is called when the timer triggers so
764 * we can check if the mouse has left our window. If so, we un-highlight
765 * the hot-tracked tab.
768 TAB_HotTrackTimerProc
770 HWND hwnd
, /* handle of window for timer messages */
771 UINT uMsg
, /* WM_TIMER message */
772 UINT_PTR idEvent
, /* timer identifier */
773 DWORD dwTime
/* current system time */
776 TAB_INFO
* infoPtr
= TAB_GetInfoPtr(hwnd
);
778 if (infoPtr
!= NULL
&& infoPtr
->iHotTracked
>= 0)
783 ** If we can't get the cursor position, or if the cursor is outside our
784 ** window, we un-highlight the hot-tracked tab. Note that the cursor is
785 ** "outside" even if it is within our bounding rect if another window
786 ** overlaps. Note also that the case where the cursor stayed within our
787 ** window but has moved off the hot-tracked tab will be handled by the
788 ** WM_MOUSEMOVE event.
790 if (!GetCursorPos(&pt
) || WindowFromPoint(pt
) != hwnd
)
792 /* Redraw iHotTracked to look normal */
793 INT iRedraw
= infoPtr
->iHotTracked
;
794 infoPtr
->iHotTracked
= -1;
795 hottrack_refresh (infoPtr
, iRedraw
);
797 /* Kill this timer */
798 KillTimer(hwnd
, TAB_HOTTRACK_TIMER
);
803 /******************************************************************************
806 * If a tab control has the TCS_HOTTRACK style, then the tab under the mouse
807 * should be highlighted. This function determines which tab in a tab control,
808 * if any, is under the mouse and records that information. The caller may
809 * supply output parameters to receive the item number of the tab item which
810 * was highlighted but isn't any longer and of the tab item which is now
811 * highlighted but wasn't previously. The caller can use this information to
812 * selectively redraw those tab items.
814 * If the caller has a mouse position, it can supply it through the pos
815 * parameter. For example, TAB_MouseMove does this. Otherwise, the caller
816 * supplies NULL and this function determines the current mouse position
824 int* out_redrawLeave
,
831 if (out_redrawLeave
!= NULL
)
832 *out_redrawLeave
= -1;
833 if (out_redrawEnter
!= NULL
)
834 *out_redrawEnter
= -1;
836 if ((infoPtr
->dwStyle
& TCS_HOTTRACK
) || GetWindowTheme(infoPtr
->hwnd
))
844 ScreenToClient(infoPtr
->hwnd
, &pt
);
848 pt
.x
= (short)LOWORD(*pos
);
849 pt
.y
= (short)HIWORD(*pos
);
852 item
= TAB_InternalHitTest(infoPtr
, pt
, &flags
);
855 if (item
!= infoPtr
->iHotTracked
)
857 if (infoPtr
->iHotTracked
>= 0)
859 /* Mark currently hot-tracked to be redrawn to look normal */
860 if (out_redrawLeave
!= NULL
)
861 *out_redrawLeave
= infoPtr
->iHotTracked
;
865 /* Kill timer which forces recheck of mouse pos */
866 KillTimer(infoPtr
->hwnd
, TAB_HOTTRACK_TIMER
);
871 /* Start timer so we recheck mouse pos */
872 UINT timerID
= SetTimer
876 TAB_HOTTRACK_TIMER_INTERVAL
,
877 TAB_HotTrackTimerProc
881 return; /* Hot tracking not available */
884 infoPtr
->iHotTracked
= item
;
888 /* Mark new hot-tracked to be redrawn to look highlighted */
889 if (out_redrawEnter
!= NULL
)
890 *out_redrawEnter
= item
;
895 /******************************************************************************
898 * Handles the mouse-move event. Updates tooltips. Updates hot-tracking.
901 TAB_MouseMove (TAB_INFO
*infoPtr
, WPARAM wParam
, LPARAM lParam
)
906 if (infoPtr
->hwndToolTip
)
907 TAB_RelayEvent (infoPtr
->hwndToolTip
, infoPtr
->hwnd
,
908 WM_LBUTTONDOWN
, wParam
, lParam
);
910 /* Determine which tab to highlight. Redraw tabs which change highlight
912 TAB_RecalcHotTrack(infoPtr
, &lParam
, &redrawLeave
, &redrawEnter
);
914 hottrack_refresh (infoPtr
, redrawLeave
);
915 hottrack_refresh (infoPtr
, redrawEnter
);
920 /******************************************************************************
923 * Calculates the tab control's display area given the window rectangle or
924 * the window rectangle given the requested display rectangle.
926 static LRESULT
TAB_AdjustRect(const TAB_INFO
*infoPtr
, WPARAM fLarger
, LPRECT prc
)
928 LONG
*iRightBottom
, *iLeftTop
;
930 TRACE("hwnd %p, fLarger %Id, (%s)\n", infoPtr
->hwnd
, fLarger
, wine_dbgstr_rect(prc
));
934 if(infoPtr
->dwStyle
& TCS_VERTICAL
)
936 iRightBottom
= &(prc
->right
);
937 iLeftTop
= &(prc
->left
);
941 iRightBottom
= &(prc
->bottom
);
942 iLeftTop
= &(prc
->top
);
945 if (fLarger
) /* Go from display rectangle */
947 /* Add the height of the tabs. */
948 if (infoPtr
->dwStyle
& TCS_BOTTOM
)
949 *iRightBottom
+= infoPtr
->tabHeight
* infoPtr
->uNumRows
;
951 *iLeftTop
-= infoPtr
->tabHeight
* infoPtr
->uNumRows
+
952 ((infoPtr
->dwStyle
& TCS_BUTTONS
)? 3 * (infoPtr
->uNumRows
- 1) : 0);
954 /* Inflate the rectangle for the padding */
955 InflateRect(prc
, DISPLAY_AREA_PADDINGX
, DISPLAY_AREA_PADDINGY
);
957 /* Inflate for the border */
958 InflateRect(prc
, CONTROL_BORDER_SIZEX
, CONTROL_BORDER_SIZEY
);
960 else /* Go from window rectangle. */
962 /* Deflate the rectangle for the border */
963 InflateRect(prc
, -CONTROL_BORDER_SIZEX
, -CONTROL_BORDER_SIZEY
);
965 /* Deflate the rectangle for the padding */
966 InflateRect(prc
, -DISPLAY_AREA_PADDINGX
, -DISPLAY_AREA_PADDINGY
);
968 /* Remove the height of the tabs. */
969 if (infoPtr
->dwStyle
& TCS_BOTTOM
)
970 *iRightBottom
-= infoPtr
->tabHeight
* infoPtr
->uNumRows
;
972 *iLeftTop
+= (infoPtr
->tabHeight
) * infoPtr
->uNumRows
+
973 ((infoPtr
->dwStyle
& TCS_BUTTONS
)? 3 * (infoPtr
->uNumRows
- 1) : 0);
979 /******************************************************************************
982 * This method will handle the notification from the scroll control and
983 * perform the scrolling operation on the tab control.
985 static LRESULT
TAB_OnHScroll(TAB_INFO
*infoPtr
, int nScrollCode
, int nPos
)
987 if(nScrollCode
== SB_THUMBPOSITION
&& nPos
!= infoPtr
->leftmostVisible
)
989 if(nPos
< infoPtr
->leftmostVisible
)
990 infoPtr
->leftmostVisible
--;
992 infoPtr
->leftmostVisible
++;
994 TAB_RecalcHotTrack(infoPtr
, NULL
, NULL
, NULL
);
995 TAB_InvalidateTabArea(infoPtr
);
996 SendMessageW(infoPtr
->hwndUpDown
, UDM_SETPOS
, 0,
997 MAKELONG(infoPtr
->leftmostVisible
, 0));
1003 /******************************************************************************
1004 * TAB_SetupScrolling
1006 * This method will check the current scrolling state and make sure the
1007 * scrolling control is displayed (or not).
1009 static void TAB_SetupScrolling(
1011 const RECT
* clientRect
)
1015 if (infoPtr
->needsScrolling
)
1018 INT vsize
, tabwidth
;
1021 * Calculate the position of the scroll control.
1023 controlPos
.right
= clientRect
->right
;
1024 controlPos
.left
= controlPos
.right
- 2 * GetSystemMetrics(SM_CXHSCROLL
);
1026 if (infoPtr
->dwStyle
& TCS_BOTTOM
)
1028 controlPos
.top
= clientRect
->bottom
- infoPtr
->tabHeight
;
1029 controlPos
.bottom
= controlPos
.top
+ GetSystemMetrics(SM_CYHSCROLL
);
1033 controlPos
.bottom
= clientRect
->top
+ infoPtr
->tabHeight
;
1034 controlPos
.top
= controlPos
.bottom
- GetSystemMetrics(SM_CYHSCROLL
);
1038 * If we don't have a scroll control yet, we want to create one.
1039 * If we have one, we want to make sure it's positioned properly.
1041 if (infoPtr
->hwndUpDown
==0)
1043 infoPtr
->hwndUpDown
= CreateWindowW(UPDOWN_CLASSW
, L
"",
1044 WS_VISIBLE
| WS_CHILD
| UDS_HORZ
,
1045 controlPos
.left
, controlPos
.top
,
1046 controlPos
.right
- controlPos
.left
,
1047 controlPos
.bottom
- controlPos
.top
,
1048 infoPtr
->hwnd
, NULL
, NULL
, NULL
);
1052 SetWindowPos(infoPtr
->hwndUpDown
,
1054 controlPos
.left
, controlPos
.top
,
1055 controlPos
.right
- controlPos
.left
,
1056 controlPos
.bottom
- controlPos
.top
,
1057 SWP_SHOWWINDOW
| SWP_NOZORDER
);
1060 /* Now calculate upper limit of the updown control range.
1061 * We do this by calculating how many tabs will be offscreen when the
1062 * last tab is visible.
1064 if(infoPtr
->uNumItem
)
1066 vsize
= clientRect
->right
- (controlPos
.right
- controlPos
.left
+ 1);
1067 maxRange
= infoPtr
->uNumItem
;
1068 tabwidth
= TAB_GetItem(infoPtr
, infoPtr
->uNumItem
- 1)->rect
.right
;
1070 for(; maxRange
> 0; maxRange
--)
1072 if(tabwidth
- TAB_GetItem(infoPtr
,maxRange
- 1)->rect
.left
> vsize
)
1076 if(maxRange
== infoPtr
->uNumItem
)
1082 /* If we once had a scroll control... hide it */
1083 if (infoPtr
->hwndUpDown
)
1084 ShowWindow(infoPtr
->hwndUpDown
, SW_HIDE
);
1086 if (infoPtr
->hwndUpDown
)
1087 SendMessageW(infoPtr
->hwndUpDown
, UDM_SETRANGE32
, 0, maxRange
);
1090 /******************************************************************************
1093 * This method will calculate the position rectangles of all the items in the
1094 * control. The rectangle calculated starts at 0 for the first item in the
1095 * list and ignores scrolling and selection.
1096 * It also uses the current font to determine the height of the tab row and
1097 * it checks if all the tabs fit in the client area of the window. If they
1098 * don't, a scrolling control is added.
1100 static void TAB_SetItemBounds (TAB_INFO
*infoPtr
)
1102 TEXTMETRICW fontMetrics
;
1105 INT curItemRowCount
;
1106 HFONT hFont
, hOldFont
;
1115 * We need to get text information so we need a DC and we need to select
1118 hdc
= GetDC(infoPtr
->hwnd
);
1120 hFont
= infoPtr
->hFont
? infoPtr
->hFont
: GetStockObject (SYSTEM_FONT
);
1121 hOldFont
= SelectObject (hdc
, hFont
);
1124 * We will base the rectangle calculations on the client rectangle
1127 GetClientRect(infoPtr
->hwnd
, &clientRect
);
1129 /* if TCS_VERTICAL then swap the height and width so this code places the
1130 tabs along the top of the rectangle and we can just rotate them after
1131 rather than duplicate all of the below code */
1132 if(infoPtr
->dwStyle
& TCS_VERTICAL
)
1134 iTemp
= clientRect
.bottom
;
1135 clientRect
.bottom
= clientRect
.right
;
1136 clientRect
.right
= iTemp
;
1139 /* Now use hPadding and vPadding */
1140 infoPtr
->uHItemPadding
= infoPtr
->uHItemPadding_s
;
1141 infoPtr
->uVItemPadding
= infoPtr
->uVItemPadding_s
;
1143 /* The leftmost item will be "0" aligned */
1145 curItemRowCount
= infoPtr
->uNumItem
? 1 : 0;
1147 if (!(infoPtr
->fHeightSet
))
1150 INT icon_height
= 0, cx
;
1152 /* Use the current font to determine the height of a tab. */
1153 GetTextMetricsW(hdc
, &fontMetrics
);
1155 /* Get the icon height */
1157 ImageList_GetIconSize(infoPtr
->himl
, &cx
, &icon_height
);
1159 /* Take the highest between font or icon */
1160 if (fontMetrics
.tmHeight
> icon_height
)
1161 item_height
= fontMetrics
.tmHeight
+ 2;
1163 item_height
= icon_height
;
1166 * Make sure there is enough space for the letters + icon + growing the
1167 * selected item + extra space for the selected item.
1169 infoPtr
->tabHeight
= item_height
+
1170 ((infoPtr
->dwStyle
& TCS_BUTTONS
) ? 2 : 1) *
1171 infoPtr
->uVItemPadding
;
1173 TRACE("tabH=%d, tmH %ld, iconh %d\n", infoPtr
->tabHeight
, fontMetrics
.tmHeight
, icon_height
);
1176 TRACE("client right %ld\n", clientRect
.right
);
1178 /* Get the icon width */
1183 ImageList_GetIconSize(infoPtr
->himl
, &icon_width
, &cy
);
1185 if (infoPtr
->dwStyle
& TCS_FIXEDWIDTH
)
1188 /* Add padding if icon is present */
1189 icon_width
+= infoPtr
->uHItemPadding
;
1192 for (curItem
= 0; curItem
< infoPtr
->uNumItem
; curItem
++)
1194 TAB_ITEM
*curr
= TAB_GetItem(infoPtr
, curItem
);
1196 /* Set the leftmost position of the tab. */
1197 curr
->rect
.left
= curItemLeftPos
;
1199 if (infoPtr
->dwStyle
& TCS_FIXEDWIDTH
)
1201 curr
->rect
.right
= curr
->rect
.left
+
1202 max(infoPtr
->tabWidth
, icon_width
);
1204 else if (!curr
->pszText
)
1206 /* If no text use minimum tab width including padding. */
1207 if (infoPtr
->tabMinWidth
< 0)
1208 curr
->rect
.right
= curr
->rect
.left
+ GET_DEFAULT_MIN_TAB_WIDTH(infoPtr
);
1211 curr
->rect
.right
= curr
->rect
.left
+ infoPtr
->tabMinWidth
;
1213 /* Add extra padding if icon is present */
1214 if (infoPtr
->himl
&& infoPtr
->tabMinWidth
> 0 && infoPtr
->tabMinWidth
< DEFAULT_MIN_TAB_WIDTH
1215 && infoPtr
->uHItemPadding
> 1)
1216 curr
->rect
.right
+= EXTRA_ICON_PADDING
* (infoPtr
->uHItemPadding
-1);
1223 /* Calculate how wide the tab is depending on the text it contains */
1224 GetTextExtentPoint32W(hdc
, curr
->pszText
,
1225 lstrlenW(curr
->pszText
), &size
);
1227 tabwidth
= size
.cx
+ icon_width
+ 2 * infoPtr
->uHItemPadding
;
1229 if (infoPtr
->tabMinWidth
< 0)
1230 tabwidth
= max(tabwidth
, GET_DEFAULT_MIN_TAB_WIDTH(infoPtr
));
1232 tabwidth
= max(tabwidth
, infoPtr
->tabMinWidth
);
1234 curr
->rect
.right
= curr
->rect
.left
+ tabwidth
;
1235 TRACE("for <%s>, rect %s\n", debugstr_w(curr
->pszText
), wine_dbgstr_rect(&curr
->rect
));
1239 * Check if this is a multiline tab control and if so
1240 * check to see if we should wrap the tabs
1242 * Wrap all these tabs. We will arrange them evenly later.
1246 if (((infoPtr
->dwStyle
& TCS_MULTILINE
) || (infoPtr
->dwStyle
& TCS_VERTICAL
)) &&
1248 (clientRect
.right
- CONTROL_BORDER_SIZEX
- DISPLAY_AREA_PADDINGX
)))
1250 curr
->rect
.right
-= curr
->rect
.left
;
1252 curr
->rect
.left
= 0;
1254 TRACE("wrapping <%s>, rect %s\n", debugstr_w(curr
->pszText
), wine_dbgstr_rect(&curr
->rect
));
1257 curr
->rect
.bottom
= 0;
1258 curr
->rect
.top
= curItemRowCount
- 1;
1260 TRACE("Rect: %s\n", wine_dbgstr_rect(&curr
->rect
));
1263 * The leftmost position of the next item is the rightmost position
1266 if (infoPtr
->dwStyle
& TCS_BUTTONS
)
1268 curItemLeftPos
= curr
->rect
.right
+ BUTTON_SPACINGX
;
1269 if (infoPtr
->dwStyle
& TCS_FLATBUTTONS
)
1270 curItemLeftPos
+= FLAT_BTN_SPACINGX
;
1273 curItemLeftPos
= curr
->rect
.right
;
1276 if (!((infoPtr
->dwStyle
& TCS_MULTILINE
) || (infoPtr
->dwStyle
& TCS_VERTICAL
)))
1279 * Check if we need a scrolling control.
1281 infoPtr
->needsScrolling
= (curItemLeftPos
+ (2 * SELECTED_TAB_OFFSET
) >
1284 /* Don't need scrolling, then update infoPtr->leftmostVisible */
1285 if(!infoPtr
->needsScrolling
)
1286 infoPtr
->leftmostVisible
= 0;
1291 * No scrolling in Multiline or Vertical styles.
1293 infoPtr
->needsScrolling
= FALSE
;
1294 infoPtr
->leftmostVisible
= 0;
1296 TAB_SetupScrolling(infoPtr
, &clientRect
);
1298 /* Set the number of rows */
1299 infoPtr
->uNumRows
= curItemRowCount
;
1301 /* Arrange all tabs evenly if style says so */
1302 if (!(infoPtr
->dwStyle
& TCS_RAGGEDRIGHT
) &&
1303 ((infoPtr
->dwStyle
& TCS_MULTILINE
) || (infoPtr
->dwStyle
& TCS_VERTICAL
)) &&
1304 (infoPtr
->uNumItem
> 0) &&
1305 (infoPtr
->uNumRows
> 1))
1307 INT tabPerRow
,remTab
,iRow
;
1312 * Ok windows tries to even out the rows. place the same
1313 * number of tabs in each row. So lets give that a shot
1316 tabPerRow
= infoPtr
->uNumItem
/ (infoPtr
->uNumRows
);
1317 remTab
= infoPtr
->uNumItem
% (infoPtr
->uNumRows
);
1319 for (iItm
=0,iRow
=0,iCount
=0,curItemLeftPos
=0;
1320 iItm
<infoPtr
->uNumItem
;
1323 /* normalize the current rect */
1324 TAB_ITEM
*curr
= TAB_GetItem(infoPtr
, iItm
);
1326 /* shift the item to the left side of the clientRect */
1327 curr
->rect
.right
-= curr
->rect
.left
;
1328 curr
->rect
.left
= 0;
1330 TRACE("r=%ld, cl=%d, cl.r=%ld, iCount=%d, iRow=%d, uNumRows=%d, remTab=%d, tabPerRow=%d\n",
1331 curr
->rect
.right
, curItemLeftPos
, clientRect
.right
,
1332 iCount
, iRow
, infoPtr
->uNumRows
, remTab
, tabPerRow
);
1334 /* if we have reached the maximum number of tabs on this row */
1335 /* move to the next row, reset our current item left position and */
1336 /* the count of items on this row */
1338 if (infoPtr
->dwStyle
& TCS_VERTICAL
) {
1339 /* Vert: Add the remaining tabs in the *last* remainder rows */
1340 if (iCount
>= ((iRow
>=(INT
)infoPtr
->uNumRows
- remTab
)?tabPerRow
+ 1:tabPerRow
)) {
1346 /* Horz: Add the remaining tabs in the *first* remainder rows */
1347 if (iCount
>= ((iRow
<remTab
)?tabPerRow
+ 1:tabPerRow
)) {
1354 /* shift the item to the right to place it as the next item in this row */
1355 curr
->rect
.left
+= curItemLeftPos
;
1356 curr
->rect
.right
+= curItemLeftPos
;
1357 curr
->rect
.top
= iRow
;
1358 if (infoPtr
->dwStyle
& TCS_BUTTONS
)
1360 curItemLeftPos
= curr
->rect
.right
+ 1;
1361 if (infoPtr
->dwStyle
& TCS_FLATBUTTONS
)
1362 curItemLeftPos
+= FLAT_BTN_SPACINGX
;
1365 curItemLeftPos
= curr
->rect
.right
;
1367 TRACE("arranging <%s>, rect %s\n", debugstr_w(curr
->pszText
), wine_dbgstr_rect(&curr
->rect
));
1374 INT widthDiff
, iIndexStart
=0, iIndexEnd
=0;
1378 while(iIndexStart
< infoPtr
->uNumItem
)
1380 TAB_ITEM
*start
= TAB_GetItem(infoPtr
, iIndexStart
);
1383 * find the index of the row
1385 /* find the first item on the next row */
1386 for (iIndexEnd
=iIndexStart
;
1387 (iIndexEnd
< infoPtr
->uNumItem
) &&
1388 (TAB_GetItem(infoPtr
, iIndexEnd
)->rect
.top
==
1391 /* intentionally blank */;
1394 * we need to justify these tabs so they fill the whole given
1398 /* find the amount of space remaining on this row */
1399 widthDiff
= clientRect
.right
- (2 * SELECTED_TAB_OFFSET
) -
1400 TAB_GetItem(infoPtr
, iIndexEnd
- 1)->rect
.right
;
1402 /* iCount is the number of tab items on this row */
1403 iCount
= iIndexEnd
- iIndexStart
;
1407 remainder
= widthDiff
% iCount
;
1408 widthDiff
= widthDiff
/ iCount
;
1409 /* add widthDiff/iCount, or extra space/items on row, to each item on this row */
1410 for (iIndex
=iIndexStart
, iCount
=0; iIndex
< iIndexEnd
; iIndex
++, iCount
++)
1412 TAB_ITEM
*item
= TAB_GetItem(infoPtr
, iIndex
);
1414 item
->rect
.left
+= iCount
* widthDiff
;
1415 item
->rect
.right
+= (iCount
+ 1) * widthDiff
;
1417 TRACE("adjusting 1 <%s>, rect %s\n", debugstr_w(item
->pszText
), wine_dbgstr_rect(&item
->rect
));
1420 TAB_GetItem(infoPtr
, iIndex
- 1)->rect
.right
+= remainder
;
1422 else /* we have only one item on this row, make it take up the entire row */
1424 start
->rect
.left
= clientRect
.left
;
1425 start
->rect
.right
= clientRect
.right
- 4;
1427 TRACE("adjusting 2 <%s>, rect %s\n", debugstr_w(start
->pszText
), wine_dbgstr_rect(&start
->rect
));
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;
1601 InflateRect(drawRect
, -2, -2);
1605 if ((infoPtr
->dwStyle
& TCS_VERTICAL
) && (infoPtr
->dwStyle
& TCS_BOTTOM
))
1607 if (iItem
!= infoPtr
->iSelected
)
1609 drawRect
->left
+= 2;
1610 InflateRect(drawRect
, 0, -2);
1613 else if (infoPtr
->dwStyle
& TCS_VERTICAL
)
1615 if (iItem
== infoPtr
->iSelected
)
1617 drawRect
->right
+= 1;
1621 drawRect
->right
-= 2;
1622 InflateRect(drawRect
, 0, -2);
1625 else if (infoPtr
->dwStyle
& TCS_BOTTOM
)
1627 if (iItem
== infoPtr
->iSelected
)
1633 InflateRect(drawRect
, -2, -2);
1634 drawRect
->bottom
+= 2;
1639 if (iItem
== infoPtr
->iSelected
)
1641 drawRect
->bottom
+= 3;
1645 drawRect
->bottom
-= 2;
1646 InflateRect(drawRect
, -2, 0);
1651 TRACE("drawRect=(%s)\n", wine_dbgstr_rect(drawRect
));
1653 /* Clear interior */
1654 TAB_EraseTabInterior (infoPtr
, hdc
, iItem
, drawRect
);
1656 /* Draw the focus rectangle */
1657 if (!(infoPtr
->dwStyle
& TCS_FOCUSNEVER
) &&
1658 (GetFocus() == infoPtr
->hwnd
) &&
1659 (iItem
== infoPtr
->uFocus
) )
1661 RECT rFocus
= *drawRect
;
1663 if (!(infoPtr
->dwStyle
& TCS_BUTTONS
)) InflateRect(&rFocus
, -3, -3);
1664 if (infoPtr
->dwStyle
& TCS_BOTTOM
&& !(infoPtr
->dwStyle
& TCS_VERTICAL
))
1667 /* focus should stay on selected item for TCS_BUTTONS style */
1668 if (!((infoPtr
->dwStyle
& TCS_BUTTONS
) && (infoPtr
->iSelected
!= iItem
)))
1669 DrawFocusRect(hdc
, &rFocus
);
1675 htextPen
= CreatePen( PS_SOLID
, 1, comctl32_color
.clrBtnText
);
1676 holdPen
= SelectObject(hdc
, htextPen
);
1677 hOldFont
= SelectObject(hdc
, infoPtr
->hFont
);
1680 * Setup for text output
1682 oldBkMode
= SetBkMode(hdc
, TRANSPARENT
);
1683 if (!GetWindowTheme (infoPtr
->hwnd
) || (infoPtr
->dwStyle
& TCS_BUTTONS
))
1685 if ((infoPtr
->dwStyle
& TCS_HOTTRACK
) && (iItem
== infoPtr
->iHotTracked
) &&
1686 !(infoPtr
->dwStyle
& TCS_FLATBUTTONS
))
1687 SetTextColor(hdc
, comctl32_color
.clrHighlight
);
1688 else if (TAB_GetItem(infoPtr
, iItem
)->dwState
& TCIS_HIGHLIGHTED
)
1689 SetTextColor(hdc
, comctl32_color
.clrHighlightText
);
1691 SetTextColor(hdc
, comctl32_color
.clrBtnText
);
1695 * if owner draw, tell the owner to draw
1697 if ((infoPtr
->dwStyle
& TCS_OWNERDRAWFIXED
) && IsWindow(infoPtr
->hwndNotify
))
1703 drawRect
->right
-= 1;
1704 if ( iItem
== infoPtr
->iSelected
)
1705 InflateRect(drawRect
, -1, 0);
1707 id
= (UINT
)GetWindowLongPtrW( infoPtr
->hwnd
, GWLP_ID
);
1709 /* fill DRAWITEMSTRUCT */
1710 dis
.CtlType
= ODT_TAB
;
1713 dis
.itemAction
= ODA_DRAWENTIRE
;
1715 if ( iItem
== infoPtr
->iSelected
)
1716 dis
.itemState
|= ODS_SELECTED
;
1717 if (infoPtr
->uFocus
== iItem
)
1718 dis
.itemState
|= ODS_FOCUS
;
1719 dis
.hwndItem
= infoPtr
->hwnd
;
1721 dis
.rcItem
= *drawRect
;
1723 /* when extra data fits ULONG_PTR, store it directly */
1724 if (infoPtr
->cbInfo
> sizeof(LPARAM
))
1725 dis
.itemData
= (ULONG_PTR
) TAB_GetItem(infoPtr
, iItem
)->extra
;
1728 /* this could be considered broken on 64 bit, but that's how it works -
1729 only first 4 bytes are copied */
1731 memcpy(&dis
.itemData
, (ULONG_PTR
*)TAB_GetItem(infoPtr
, iItem
)->extra
, 4);
1734 /* draw notification */
1735 SendMessageW( infoPtr
->hwndNotify
, WM_DRAWITEM
, id
, (LPARAM
)&dis
);
1739 TAB_ITEM
*item
= TAB_GetItem(infoPtr
, iItem
);
1743 /* used to center the icon and text in the tab */
1745 INT center_offset_h
, center_offset_v
;
1747 /* set rcImage to drawRect, we will use top & left in our ImageList_Draw call */
1748 rcImage
= *drawRect
;
1751 SetRectEmpty(&rcText
);
1753 /* get the rectangle that the text fits in */
1756 DrawTextW(hdc
, item
->pszText
, -1, &rcText
, DT_CALCRECT
);
1759 * If not owner draw, then do the drawing ourselves.
1763 if (infoPtr
->himl
&& item
->iImage
!= -1)
1768 ImageList_GetIconSize(infoPtr
->himl
, &cx
, &cy
);
1770 if(infoPtr
->dwStyle
& TCS_VERTICAL
)
1772 center_offset_h
= ((drawRect
->bottom
- drawRect
->top
) - (cy
+ infoPtr
->uHItemPadding
+ (rcText
.right
- rcText
.left
))) / 2;
1773 center_offset_v
= ((drawRect
->right
- drawRect
->left
) - cx
) / 2;
1777 center_offset_h
= ((drawRect
->right
- drawRect
->left
) - (cx
+ infoPtr
->uHItemPadding
+ (rcText
.right
- rcText
.left
))) / 2;
1778 center_offset_v
= ((drawRect
->bottom
- drawRect
->top
) - cy
) / 2;
1781 /* if an item is selected, the icon is shifted up instead of down */
1782 if (iItem
== infoPtr
->iSelected
)
1783 center_offset_v
-= infoPtr
->uVItemPadding
/ 2;
1785 center_offset_v
+= infoPtr
->uVItemPadding
/ 2;
1787 if (infoPtr
->dwStyle
& TCS_FIXEDWIDTH
&& infoPtr
->dwStyle
& (TCS_FORCELABELLEFT
| TCS_FORCEICONLEFT
))
1788 center_offset_h
= infoPtr
->uHItemPadding
;
1790 if (center_offset_h
< 2)
1791 center_offset_h
= 2;
1793 if (center_offset_v
< 0)
1794 center_offset_v
= 0;
1796 TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%s), textlen=%ld\n",
1797 debugstr_w(item
->pszText
), center_offset_h
, center_offset_v
,
1798 wine_dbgstr_rect(drawRect
), (rcText
.right
-rcText
.left
));
1800 if((infoPtr
->dwStyle
& TCS_VERTICAL
) && (infoPtr
->dwStyle
& TCS_BOTTOM
))
1802 rcImage
.top
= drawRect
->top
+ center_offset_h
;
1803 /* if tab is TCS_VERTICAL and TCS_BOTTOM, the text is drawn from the */
1804 /* right side of the tab, but the image still uses the left as its x position */
1805 /* this keeps the image always drawn off of the same side of the tab */
1806 rcImage
.left
= drawRect
->right
- cx
- center_offset_v
;
1807 drawRect
->top
+= cy
+ infoPtr
->uHItemPadding
;
1809 else if(infoPtr
->dwStyle
& TCS_VERTICAL
)
1811 rcImage
.top
= drawRect
->bottom
- cy
- center_offset_h
;
1812 rcImage
.left
= drawRect
->left
+ center_offset_v
;
1813 drawRect
->bottom
-= cy
+ infoPtr
->uHItemPadding
;
1815 else /* normal style, whether TCS_BOTTOM or not */
1817 rcImage
.left
= drawRect
->left
+ center_offset_h
;
1818 rcImage
.top
= drawRect
->top
+ center_offset_v
;
1819 drawRect
->left
+= cx
+ infoPtr
->uHItemPadding
;
1822 TRACE("drawing image %d, left %ld, top %ld\n", item
->iImage
, rcImage
.left
, rcImage
.top
-1);
1834 /* Now position text */
1835 if (infoPtr
->dwStyle
& TCS_FIXEDWIDTH
&& infoPtr
->dwStyle
& TCS_FORCELABELLEFT
)
1836 center_offset_h
= infoPtr
->uHItemPadding
;
1838 if(infoPtr
->dwStyle
& TCS_VERTICAL
)
1839 center_offset_h
= ((drawRect
->bottom
- drawRect
->top
) - (rcText
.right
- rcText
.left
)) / 2;
1841 center_offset_h
= ((drawRect
->right
- drawRect
->left
) - (rcText
.right
- rcText
.left
)) / 2;
1843 if(infoPtr
->dwStyle
& TCS_VERTICAL
)
1845 if(infoPtr
->dwStyle
& TCS_BOTTOM
)
1846 drawRect
->top
+=center_offset_h
;
1848 drawRect
->bottom
-=center_offset_h
;
1850 center_offset_v
= ((drawRect
->right
- drawRect
->left
) - (rcText
.bottom
- rcText
.top
)) / 2;
1854 drawRect
->left
+= center_offset_h
;
1855 center_offset_v
= ((drawRect
->bottom
- drawRect
->top
) - (rcText
.bottom
- rcText
.top
)) / 2;
1858 /* if an item is selected, the text is shifted up instead of down */
1859 if (iItem
== infoPtr
->iSelected
)
1860 center_offset_v
-= infoPtr
->uVItemPadding
/ 2;
1862 center_offset_v
+= infoPtr
->uVItemPadding
/ 2;
1864 if (center_offset_v
< 0)
1865 center_offset_v
= 0;
1867 if(infoPtr
->dwStyle
& TCS_VERTICAL
)
1868 drawRect
->left
+= center_offset_v
;
1870 drawRect
->top
+= center_offset_v
;
1873 if(infoPtr
->dwStyle
& TCS_VERTICAL
) /* if we are vertical rotate the text and each character */
1877 INT nEscapement
= 900;
1878 INT nOrientation
= 900;
1880 if(infoPtr
->dwStyle
& TCS_BOTTOM
)
1883 nOrientation
= -900;
1886 /* to get a font with the escapement and orientation we are looking for, we need to */
1887 /* call CreateFontIndirect, which requires us to set the values of the logfont we pass in */
1888 if (!GetObjectW(infoPtr
->hFont
, sizeof(logfont
), &logfont
))
1889 GetObjectW(GetStockObject(DEFAULT_GUI_FONT
), sizeof(logfont
), &logfont
);
1891 logfont
.lfEscapement
= nEscapement
;
1892 logfont
.lfOrientation
= nOrientation
;
1893 hFont
= CreateFontIndirectW(&logfont
);
1894 SelectObject(hdc
, hFont
);
1899 (infoPtr
->dwStyle
& TCS_BOTTOM
) ? drawRect
->right
: drawRect
->left
,
1900 (!(infoPtr
->dwStyle
& TCS_BOTTOM
)) ? drawRect
->bottom
: drawRect
->top
,
1904 lstrlenW(item
->pszText
),
1908 DeleteObject(hFont
);
1912 TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%s), textlen=%ld\n",
1913 debugstr_w(item
->pszText
), center_offset_h
, center_offset_v
,
1914 wine_dbgstr_rect(drawRect
), (rcText
.right
-rcText
.left
));
1921 lstrlenW(item
->pszText
),
1923 DT_LEFT
| DT_SINGLELINE
1928 *drawRect
= rcTemp
; /* restore drawRect */
1934 SelectObject(hdc
, hOldFont
);
1935 SetBkMode(hdc
, oldBkMode
);
1936 SelectObject(hdc
, holdPen
);
1937 DeleteObject( htextPen
);
1940 /******************************************************************************
1943 * This method is used to draw a single tab into the tab control.
1945 static void TAB_DrawItem(const TAB_INFO
*infoPtr
, HDC hdc
, INT iItem
)
1950 RECT r
, fillRect
, r1
;
1953 COLORREF bkgnd
, corner
;
1957 * Get the rectangle for the item.
1959 isVisible
= TAB_InternalGetItemRect(infoPtr
,
1968 /* Clip UpDown control to not draw over it */
1969 if (infoPtr
->needsScrolling
)
1971 GetWindowRect(infoPtr
->hwnd
, &rC
);
1972 GetWindowRect(infoPtr
->hwndUpDown
, &rUD
);
1973 ExcludeClipRect(hdc
, rUD
.left
- rC
.left
, rUD
.top
- rC
.top
, rUD
.right
- rC
.left
, rUD
.bottom
- rC
.top
);
1976 /* If you need to see what the control is doing,
1977 * then override these variables. They will change what
1978 * fill colors are used for filling the tabs, and the
1979 * corners when drawing the edge.
1981 bkgnd
= comctl32_color
.clrBtnFace
;
1982 corner
= comctl32_color
.clrBtnFace
;
1984 if (infoPtr
->dwStyle
& TCS_BUTTONS
)
1986 /* Get item rectangle */
1989 /* Separators between flat buttons */
1990 if ((infoPtr
->dwStyle
& TCS_FLATBUTTONS
) && (infoPtr
->exStyle
& TCS_EX_FLATSEPARATORS
))
1993 r1
.right
+= (FLAT_BTN_SPACINGX
-2);
1994 DrawEdge(hdc
, &r1
, EDGE_ETCHED
, BF_RIGHT
);
1997 if (iItem
== infoPtr
->iSelected
)
1999 DrawEdge(hdc
, &r
, EDGE_SUNKEN
, BF_SOFT
|BF_RECT
);
2001 OffsetRect(&r
, 1, 1);
2003 else /* ! selected */
2005 DWORD state
= TAB_GetItem(infoPtr
, iItem
)->dwState
;
2007 if ((state
& TCIS_BUTTONPRESSED
) || (iItem
== infoPtr
->uFocus
))
2008 DrawEdge(hdc
, &r
, EDGE_SUNKEN
, BF_SOFT
|BF_RECT
);
2010 if (!(infoPtr
->dwStyle
& TCS_FLATBUTTONS
))
2011 DrawEdge(hdc
, &r
, EDGE_RAISED
, BF_SOFT
|BF_RECT
);
2014 else /* !TCS_BUTTONS */
2016 /* We draw a rectangle of different sizes depending on the selection
2018 if (iItem
== infoPtr
->iSelected
) {
2020 GetClientRect (infoPtr
->hwnd
, &rect
);
2021 clRight
= rect
.right
;
2022 clBottom
= rect
.bottom
;
2029 * Erase the background. (Delay it but setup rectangle.)
2030 * This is necessary when drawing the selected item since it is larger
2031 * than the others, it might overlap with stuff already drawn by the
2036 /* Draw themed tabs - but only if they are at the top.
2037 * Windows draws even side or bottom tabs themed, with wacky results.
2038 * However, since in Wine apps may get themed that did not opt in via
2039 * a manifest avoid theming when we know the result will be wrong */
2040 if ((theme
= GetWindowTheme (infoPtr
->hwnd
))
2041 && ((infoPtr
->dwStyle
& (TCS_VERTICAL
| TCS_BOTTOM
)) == 0))
2043 static const int partIds
[8] = {
2046 TABP_TABITEMLEFTEDGE
,
2047 TABP_TABITEMRIGHTEDGE
,
2048 TABP_TABITEMBOTHEDGE
,
2051 TABP_TOPTABITEMLEFTEDGE
,
2052 TABP_TOPTABITEMRIGHTEDGE
,
2053 TABP_TOPTABITEMBOTHEDGE
,
2056 int stateId
= TIS_NORMAL
;
2058 /* selected and unselected tabs have different parts */
2059 if (iItem
== infoPtr
->iSelected
)
2061 /* The part also differs on the position of a tab on a line.
2062 * "Visually" determining the position works well enough. */
2063 GetClientRect(infoPtr
->hwnd
, &r1
);
2064 if(selectedRect
.left
== 0)
2066 if(selectedRect
.right
== r1
.right
)
2069 if (iItem
== infoPtr
->iSelected
)
2070 stateId
= TIS_SELECTED
;
2071 else if (iItem
== infoPtr
->iHotTracked
)
2073 else if (iItem
== infoPtr
->uFocus
)
2074 stateId
= TIS_FOCUSED
;
2076 /* Adjust rectangle for bottommost row */
2077 if (TAB_GetItem(infoPtr
, iItem
)->rect
.top
== infoPtr
->uNumRows
-1)
2080 DrawThemeBackground (theme
, hdc
, partIds
[partIndex
], stateId
, &r
, NULL
);
2081 GetThemeBackgroundContentRect (theme
, hdc
, partIds
[partIndex
], stateId
, &r
, &r
);
2083 else if(infoPtr
->dwStyle
& TCS_VERTICAL
)
2085 /* These are for adjusting the drawing of a Selected tab */
2086 /* The initial values are for the normal case of non-Selected */
2087 int ZZ
= 1; /* Do not stretch if selected */
2088 if (iItem
== infoPtr
->iSelected
) {
2091 /* if leftmost draw the line longer */
2092 if(selectedRect
.top
== 0)
2093 fillRect
.top
+= CONTROL_BORDER_SIZEY
;
2094 /* if rightmost draw the line longer */
2095 if(selectedRect
.bottom
== clBottom
)
2096 fillRect
.bottom
-= CONTROL_BORDER_SIZEY
;
2099 if (infoPtr
->dwStyle
& TCS_BOTTOM
)
2101 /* Adjust both rectangles to match native */
2104 TRACE("<right> item=%d, fill=(%s), edge=(%s)\n",
2105 iItem
, wine_dbgstr_rect(&fillRect
), wine_dbgstr_rect(&r
));
2107 /* Clear interior */
2108 SetBkColor(hdc
, bkgnd
);
2109 ExtTextOutW(hdc
, 0, 0, 2, &fillRect
, NULL
, 0, 0);
2111 /* Draw rectangular edge around tab */
2112 DrawEdge(hdc
, &r
, EDGE_RAISED
, BF_SOFT
|BF_RIGHT
|BF_TOP
|BF_BOTTOM
);
2114 /* Now erase the top corner and draw diagonal edge */
2115 SetBkColor(hdc
, corner
);
2116 r1
.left
= r
.right
- ROUND_CORNER_SIZE
- 1;
2119 r1
.bottom
= r1
.top
+ ROUND_CORNER_SIZE
;
2120 ExtTextOutW(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2122 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_DIAGONAL_ENDTOPLEFT
);
2124 /* Now erase the bottom corner and draw diagonal edge */
2125 r1
.left
= r
.right
- ROUND_CORNER_SIZE
- 1;
2126 r1
.bottom
= r
.bottom
;
2128 r1
.top
= r1
.bottom
- ROUND_CORNER_SIZE
;
2129 ExtTextOutW(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2131 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_DIAGONAL_ENDBOTTOMLEFT
);
2133 if ((iItem
== infoPtr
->iSelected
) && (selectedRect
.top
== 0)) {
2137 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_TOP
);
2143 TRACE("<left> item=%d, fill=(%s), edge=(%s)\n",
2144 iItem
, wine_dbgstr_rect(&fillRect
), wine_dbgstr_rect(&r
));
2146 /* Clear interior */
2147 SetBkColor(hdc
, bkgnd
);
2148 ExtTextOutW(hdc
, 0, 0, 2, &fillRect
, NULL
, 0, 0);
2150 /* Draw rectangular edge around tab */
2151 DrawEdge(hdc
, &r
, EDGE_RAISED
, BF_SOFT
|BF_LEFT
|BF_TOP
|BF_BOTTOM
);
2153 /* Now erase the top corner and draw diagonal edge */
2154 SetBkColor(hdc
, corner
);
2157 r1
.right
= r1
.left
+ ROUND_CORNER_SIZE
+ 1;
2158 r1
.bottom
= r1
.top
+ ROUND_CORNER_SIZE
;
2159 ExtTextOutW(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2161 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_DIAGONAL_ENDTOPRIGHT
);
2163 /* Now erase the bottom corner and draw diagonal edge */
2165 r1
.bottom
= r
.bottom
;
2166 r1
.right
= r1
.left
+ ROUND_CORNER_SIZE
+ 1;
2167 r1
.top
= r1
.bottom
- ROUND_CORNER_SIZE
;
2168 ExtTextOutW(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2170 DrawEdge(hdc
, &r1
, EDGE_SUNKEN
, BF_DIAGONAL_ENDTOPLEFT
);
2173 else /* ! TCS_VERTICAL */
2175 /* These are for adjusting the drawing of a Selected tab */
2176 /* The initial values are for the normal case of non-Selected */
2177 if (iItem
== infoPtr
->iSelected
) {
2178 /* if leftmost draw the line longer */
2179 if(selectedRect
.left
== 0)
2180 fillRect
.left
+= CONTROL_BORDER_SIZEX
;
2181 /* if rightmost draw the line longer */
2182 if(selectedRect
.right
== clRight
)
2183 fillRect
.right
-= CONTROL_BORDER_SIZEX
;
2186 if (infoPtr
->dwStyle
& TCS_BOTTOM
)
2188 /* Adjust both rectangles for topmost row */
2189 if (TAB_GetItem(infoPtr
, iItem
)->rect
.top
== infoPtr
->uNumRows
-1)
2195 TRACE("<bottom> item=%d, fill=(%s), edge=(%s)\n",
2196 iItem
, wine_dbgstr_rect(&fillRect
), wine_dbgstr_rect(&r
));
2198 /* Clear interior */
2199 SetBkColor(hdc
, bkgnd
);
2200 ExtTextOutW(hdc
, 0, 0, 2, &fillRect
, NULL
, 0, 0);
2202 /* Draw rectangular edge around tab */
2203 DrawEdge(hdc
, &r
, EDGE_RAISED
, BF_SOFT
|BF_LEFT
|BF_BOTTOM
|BF_RIGHT
);
2205 /* Now erase the righthand corner and draw diagonal edge */
2206 SetBkColor(hdc
, corner
);
2207 r1
.left
= r
.right
- ROUND_CORNER_SIZE
;
2208 r1
.bottom
= r
.bottom
;
2210 r1
.top
= r1
.bottom
- ROUND_CORNER_SIZE
- 1;
2211 ExtTextOutW(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2213 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_DIAGONAL_ENDBOTTOMLEFT
);
2215 /* Now erase the lefthand corner and draw diagonal edge */
2217 r1
.bottom
= r
.bottom
;
2218 r1
.right
= r1
.left
+ ROUND_CORNER_SIZE
;
2219 r1
.top
= r1
.bottom
- ROUND_CORNER_SIZE
- 1;
2220 ExtTextOutW(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2222 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_DIAGONAL_ENDTOPLEFT
);
2224 if (iItem
== infoPtr
->iSelected
)
2228 if (selectedRect
.left
== 0)
2233 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_LEFT
);
2240 /* Adjust both rectangles for bottommost row */
2241 if (TAB_GetItem(infoPtr
, iItem
)->rect
.top
== infoPtr
->uNumRows
-1)
2243 fillRect
.bottom
+= 3;
2247 TRACE("<top> item=%d, fill=(%s), edge=(%s)\n",
2248 iItem
, wine_dbgstr_rect(&fillRect
), wine_dbgstr_rect(&r
));
2250 /* Clear interior */
2251 SetBkColor(hdc
, bkgnd
);
2252 ExtTextOutW(hdc
, 0, 0, 2, &fillRect
, NULL
, 0, 0);
2254 /* Draw rectangular edge around tab */
2255 DrawEdge(hdc
, &r
, EDGE_RAISED
, BF_SOFT
|BF_LEFT
|BF_TOP
|BF_RIGHT
);
2257 /* Now erase the righthand corner and draw diagonal edge */
2258 SetBkColor(hdc
, corner
);
2259 r1
.left
= r
.right
- ROUND_CORNER_SIZE
;
2262 r1
.bottom
= r1
.top
+ ROUND_CORNER_SIZE
+ 1;
2263 ExtTextOutW(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2265 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_DIAGONAL_ENDBOTTOMRIGHT
);
2267 /* Now erase the lefthand corner and draw diagonal edge */
2270 r1
.right
= r1
.left
+ ROUND_CORNER_SIZE
;
2271 r1
.bottom
= r1
.top
+ ROUND_CORNER_SIZE
+ 1;
2272 ExtTextOutW(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2274 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_DIAGONAL_ENDTOPRIGHT
);
2279 TAB_DumpItemInternal(infoPtr
, iItem
);
2281 /* This modifies r to be the text rectangle. */
2282 TAB_DrawItemInterior(infoPtr
, hdc
, iItem
, &r
);
2286 /******************************************************************************
2289 * This method is used to draw the raised border around the tab control
2292 static void TAB_DrawBorder(const TAB_INFO
*infoPtr
, HDC hdc
)
2295 HTHEME theme
= GetWindowTheme (infoPtr
->hwnd
);
2297 GetClientRect (infoPtr
->hwnd
, &rect
);
2300 * Adjust for the style
2303 if (infoPtr
->uNumItem
)
2305 if ((infoPtr
->dwStyle
& TCS_BOTTOM
) && !(infoPtr
->dwStyle
& TCS_VERTICAL
))
2306 rect
.bottom
-= infoPtr
->tabHeight
* infoPtr
->uNumRows
+ CONTROL_BORDER_SIZEX
;
2307 else if((infoPtr
->dwStyle
& TCS_BOTTOM
) && (infoPtr
->dwStyle
& TCS_VERTICAL
))
2308 rect
.right
-= infoPtr
->tabHeight
* infoPtr
->uNumRows
+ CONTROL_BORDER_SIZEX
;
2309 else if(infoPtr
->dwStyle
& TCS_VERTICAL
)
2310 rect
.left
+= infoPtr
->tabHeight
* infoPtr
->uNumRows
+ CONTROL_BORDER_SIZEX
;
2311 else /* not TCS_VERTICAL and not TCS_BOTTOM */
2312 rect
.top
+= infoPtr
->tabHeight
* infoPtr
->uNumRows
+ CONTROL_BORDER_SIZEX
;
2315 TRACE("border=(%s)\n", wine_dbgstr_rect(&rect
));
2319 DrawThemeParentBackground(infoPtr
->hwnd
, hdc
, &rect
);
2320 DrawThemeBackground (theme
, hdc
, TABP_PANE
, 0, &rect
, NULL
);
2324 DrawEdge(hdc
, &rect
, EDGE_RAISED
, BF_SOFT
|BF_RECT
);
2328 /******************************************************************************
2331 * This method repaints the tab control..
2333 static void TAB_Refresh (const TAB_INFO
*infoPtr
, HDC hdc
)
2338 if (!infoPtr
->DoRedraw
)
2341 hOldFont
= SelectObject (hdc
, infoPtr
->hFont
);
2343 if (infoPtr
->dwStyle
& TCS_BUTTONS
)
2345 for (i
= 0; i
< infoPtr
->uNumItem
; i
++)
2346 TAB_DrawItem (infoPtr
, hdc
, i
);
2350 /* Draw all the non selected item first */
2351 for (i
= 0; i
< infoPtr
->uNumItem
; i
++)
2353 if (i
!= infoPtr
->iSelected
)
2354 TAB_DrawItem (infoPtr
, hdc
, i
);
2357 /* Now, draw the border, draw it before the selected item
2358 * since the selected item overwrites part of the border. */
2359 TAB_DrawBorder (infoPtr
, hdc
);
2361 /* Then, draw the selected item */
2362 TAB_DrawItem (infoPtr
, hdc
, infoPtr
->iSelected
);
2365 SelectObject (hdc
, hOldFont
);
2368 static inline DWORD
TAB_GetRowCount (const TAB_INFO
*infoPtr
)
2370 TRACE("(%p)\n", infoPtr
);
2371 return infoPtr
->uNumRows
;
2374 static inline LRESULT
TAB_SetRedraw (TAB_INFO
*infoPtr
, BOOL doRedraw
)
2376 infoPtr
->DoRedraw
= doRedraw
;
2380 /******************************************************************************
2381 * TAB_EnsureSelectionVisible
2383 * This method will make sure that the current selection is completely
2384 * visible by scrolling until it is.
2386 static void TAB_EnsureSelectionVisible(
2389 INT iSelected
= infoPtr
->iSelected
;
2390 INT iOrigLeftmostVisible
= infoPtr
->leftmostVisible
;
2395 /* set the items row to the bottommost row or topmost row depending on
2397 if ((infoPtr
->uNumRows
> 1) && !(infoPtr
->dwStyle
& TCS_BUTTONS
))
2399 TAB_ITEM
*selected
= TAB_GetItem(infoPtr
, iSelected
);
2403 if(infoPtr
->dwStyle
& TCS_VERTICAL
)
2404 newselected
= selected
->rect
.left
;
2406 newselected
= selected
->rect
.top
;
2408 /* the target row is always (number of rows - 1)
2409 as row 0 is furthest from the clientRect */
2410 iTargetRow
= infoPtr
->uNumRows
- 1;
2412 if (newselected
!= iTargetRow
)
2415 if(infoPtr
->dwStyle
& TCS_VERTICAL
)
2417 for (i
=0; i
< infoPtr
->uNumItem
; i
++)
2419 /* move everything in the row of the selected item to the iTargetRow */
2420 TAB_ITEM
*item
= TAB_GetItem(infoPtr
, i
);
2422 if (item
->rect
.left
== newselected
)
2423 item
->rect
.left
= iTargetRow
;
2426 if (item
->rect
.left
> newselected
)
2433 for (i
=0; i
< infoPtr
->uNumItem
; i
++)
2435 TAB_ITEM
*item
= TAB_GetItem(infoPtr
, i
);
2437 if (item
->rect
.top
== newselected
)
2438 item
->rect
.top
= iTargetRow
;
2441 if (item
->rect
.top
> newselected
)
2446 TAB_RecalcHotTrack(infoPtr
, NULL
, NULL
, NULL
);
2451 * Do the trivial cases first.
2453 if ( (!infoPtr
->needsScrolling
) ||
2454 (infoPtr
->hwndUpDown
==0) || (infoPtr
->dwStyle
& TCS_VERTICAL
))
2457 if (infoPtr
->leftmostVisible
>= iSelected
)
2459 infoPtr
->leftmostVisible
= iSelected
;
2463 TAB_ITEM
*selected
= TAB_GetItem(infoPtr
, iSelected
);
2468 /* Calculate the part of the client area that is visible */
2469 GetClientRect(infoPtr
->hwnd
, &r
);
2472 GetClientRect(infoPtr
->hwndUpDown
, &r
);
2475 if ((selected
->rect
.right
-
2476 selected
->rect
.left
) >= width
)
2478 /* Special case: width of selected item is greater than visible
2481 infoPtr
->leftmostVisible
= iSelected
;
2485 for (i
= infoPtr
->leftmostVisible
; i
< infoPtr
->uNumItem
; i
++)
2487 if ((selected
->rect
.right
- TAB_GetItem(infoPtr
, i
)->rect
.left
) < width
)
2490 infoPtr
->leftmostVisible
= i
;
2494 if (infoPtr
->leftmostVisible
!= iOrigLeftmostVisible
)
2495 TAB_RecalcHotTrack(infoPtr
, NULL
, NULL
, NULL
);
2497 SendMessageW(infoPtr
->hwndUpDown
, UDM_SETPOS
, 0,
2498 MAKELONG(infoPtr
->leftmostVisible
, 0));
2501 /******************************************************************************
2502 * TAB_InvalidateTabArea
2504 * This method will invalidate the portion of the control that contains the
2505 * tabs. It is called when the state of the control changes and needs
2508 static void TAB_InvalidateTabArea(const TAB_INFO
*infoPtr
)
2510 RECT clientRect
, rInvalidate
, rAdjClient
;
2511 INT lastRow
= infoPtr
->uNumRows
- 1;
2514 if (lastRow
< 0) return;
2516 GetClientRect(infoPtr
->hwnd
, &clientRect
);
2517 rInvalidate
= clientRect
;
2518 rAdjClient
= clientRect
;
2520 TAB_AdjustRect(infoPtr
, 0, &rAdjClient
);
2522 TAB_InternalGetItemRect(infoPtr
, infoPtr
->uNumItem
-1 , &rect
, NULL
);
2523 if ((infoPtr
->dwStyle
& TCS_BOTTOM
) && (infoPtr
->dwStyle
& TCS_VERTICAL
))
2525 rInvalidate
.left
= rAdjClient
.right
;
2526 if (infoPtr
->uNumRows
== 1)
2527 rInvalidate
.bottom
= clientRect
.top
+ rect
.bottom
+ 2 * SELECTED_TAB_OFFSET
;
2529 else if(infoPtr
->dwStyle
& TCS_VERTICAL
)
2531 rInvalidate
.right
= rAdjClient
.left
;
2532 if (infoPtr
->uNumRows
== 1)
2533 rInvalidate
.bottom
= clientRect
.top
+ rect
.bottom
+ 2 * SELECTED_TAB_OFFSET
;
2535 else if (infoPtr
->dwStyle
& TCS_BOTTOM
)
2537 rInvalidate
.top
= rAdjClient
.bottom
;
2538 if (infoPtr
->uNumRows
== 1)
2539 rInvalidate
.right
= clientRect
.left
+ rect
.right
+ 2 * SELECTED_TAB_OFFSET
;
2543 rInvalidate
.bottom
= rAdjClient
.top
;
2544 if (infoPtr
->uNumRows
== 1)
2545 rInvalidate
.right
= clientRect
.left
+ rect
.right
+ 2 * SELECTED_TAB_OFFSET
;
2548 /* Punch out the updown control */
2549 if (infoPtr
->needsScrolling
&& (rInvalidate
.right
> 0)) {
2551 GetClientRect(infoPtr
->hwndUpDown
, &r
);
2552 if (rInvalidate
.right
> clientRect
.right
- r
.left
)
2553 rInvalidate
.right
= rInvalidate
.right
- (r
.right
- r
.left
);
2555 rInvalidate
.right
= clientRect
.right
- r
.left
;
2558 TRACE("invalidate (%s)\n", wine_dbgstr_rect(&rInvalidate
));
2560 InvalidateRect(infoPtr
->hwnd
, &rInvalidate
, TRUE
);
2563 static inline LRESULT
TAB_Paint (TAB_INFO
*infoPtr
, HDC hdcPaint
)
2572 hdc
= BeginPaint (infoPtr
->hwnd
, &ps
);
2573 TRACE("erase %d, rect=(%s)\n", ps
.fErase
, wine_dbgstr_rect(&ps
.rcPaint
));
2576 TAB_Refresh (infoPtr
, hdc
);
2579 EndPaint (infoPtr
->hwnd
, &ps
);
2585 TAB_InsertItemT (TAB_INFO
*infoPtr
, INT iItem
, const TCITEMW
*pti
, BOOL bUnicode
)
2590 GetClientRect (infoPtr
->hwnd
, &rect
);
2591 TRACE("Rect: %p %s\n", infoPtr
->hwnd
, wine_dbgstr_rect(&rect
));
2593 if (iItem
< 0) return -1;
2594 if (iItem
> infoPtr
->uNumItem
)
2595 iItem
= infoPtr
->uNumItem
;
2597 TAB_DumpItemExternalT(pti
, iItem
, bUnicode
);
2599 if (!(item
= Alloc(TAB_ITEM_SIZE(infoPtr
)))) return FALSE
;
2600 if (DPA_InsertPtr(infoPtr
->items
, iItem
, item
) == -1)
2606 if (infoPtr
->uNumItem
== 0)
2607 infoPtr
->iSelected
= 0;
2608 else if (iItem
<= infoPtr
->iSelected
)
2609 infoPtr
->iSelected
++;
2611 infoPtr
->uNumItem
++;
2613 item
->pszText
= NULL
;
2614 if (pti
->mask
& TCIF_TEXT
)
2617 Str_SetPtrW (&item
->pszText
, pti
->pszText
);
2619 Str_SetPtrAtoW (&item
->pszText
, (LPSTR
)pti
->pszText
);
2622 if (pti
->mask
& TCIF_IMAGE
)
2623 item
->iImage
= pti
->iImage
;
2627 if (pti
->mask
& TCIF_PARAM
)
2628 memcpy(item
->extra
, &pti
->lParam
, EXTRA_ITEM_SIZE(infoPtr
));
2630 memset(item
->extra
, 0, EXTRA_ITEM_SIZE(infoPtr
));
2632 TAB_SetItemBounds(infoPtr
);
2633 if (infoPtr
->uNumItem
> 1)
2634 TAB_InvalidateTabArea(infoPtr
);
2636 InvalidateRect(infoPtr
->hwnd
, NULL
, TRUE
);
2638 TRACE("[%p]: added item %d %s\n",
2639 infoPtr
->hwnd
, iItem
, debugstr_w(item
->pszText
));
2641 /* If we haven't set the current focus yet, set it now. */
2642 if (infoPtr
->uFocus
== -1)
2643 TAB_SetCurFocus(infoPtr
, iItem
);
2649 TAB_SetItemSize (TAB_INFO
*infoPtr
, INT cx
, INT cy
)
2652 BOOL bNeedPaint
= FALSE
;
2654 lResult
= MAKELONG(infoPtr
->tabWidth
, infoPtr
->tabHeight
);
2656 /* UNDOCUMENTED: If requested Width or Height is 0 this means that program wants to use auto size. */
2657 if (infoPtr
->dwStyle
& TCS_FIXEDWIDTH
&& (infoPtr
->tabWidth
!= cx
))
2659 infoPtr
->tabWidth
= cx
;
2663 if (infoPtr
->tabHeight
!= cy
)
2665 if ((infoPtr
->fHeightSet
= (cy
!= 0)))
2666 infoPtr
->tabHeight
= cy
;
2670 TRACE("was h=%d,w=%d, now h=%d,w=%d\n",
2671 HIWORD(lResult
), LOWORD(lResult
),
2672 infoPtr
->tabHeight
, infoPtr
->tabWidth
);
2676 TAB_SetItemBounds(infoPtr
);
2677 RedrawWindow(infoPtr
->hwnd
, NULL
, NULL
, RDW_ERASE
| RDW_INVALIDATE
| RDW_UPDATENOW
);
2683 static inline LRESULT
TAB_SetMinTabWidth (TAB_INFO
*infoPtr
, INT cx
)
2687 TRACE("(%p,%d)\n", infoPtr
, cx
);
2689 if (infoPtr
->tabMinWidth
< 0)
2690 oldcx
= DEFAULT_MIN_TAB_WIDTH
;
2692 oldcx
= infoPtr
->tabMinWidth
;
2693 infoPtr
->tabMinWidth
= cx
;
2694 TAB_SetItemBounds(infoPtr
);
2698 static inline LRESULT
2699 TAB_HighlightItem (TAB_INFO
*infoPtr
, INT iItem
, BOOL fHighlight
)
2705 TRACE("(%p,%d,%s)\n", infoPtr
, iItem
, fHighlight
? "true" : "false");
2707 if (iItem
< 0 || iItem
>= infoPtr
->uNumItem
)
2710 lpState
= &TAB_GetItem(infoPtr
, iItem
)->dwState
;
2711 oldState
= *lpState
;
2714 *lpState
|= TCIS_HIGHLIGHTED
;
2716 *lpState
&= ~TCIS_HIGHLIGHTED
;
2718 if ((oldState
!= *lpState
) && TAB_InternalGetItemRect (infoPtr
, iItem
, &r
, NULL
))
2719 InvalidateRect (infoPtr
->hwnd
, &r
, TRUE
);
2725 TAB_SetItemT (TAB_INFO
*infoPtr
, INT iItem
, LPTCITEMW tabItem
, BOOL bUnicode
)
2729 TRACE("(%p,%d,%p,%s)\n", infoPtr
, iItem
, tabItem
, bUnicode
? "true" : "false");
2731 if (iItem
< 0 || iItem
>= infoPtr
->uNumItem
)
2734 TAB_DumpItemExternalT(tabItem
, iItem
, bUnicode
);
2736 wineItem
= TAB_GetItem(infoPtr
, iItem
);
2738 if (tabItem
->mask
& TCIF_IMAGE
)
2739 wineItem
->iImage
= tabItem
->iImage
;
2741 if (tabItem
->mask
& TCIF_PARAM
)
2742 memcpy(wineItem
->extra
, &tabItem
->lParam
, infoPtr
->cbInfo
);
2744 if (tabItem
->mask
& TCIF_RTLREADING
)
2745 FIXME("TCIF_RTLREADING\n");
2747 if (tabItem
->mask
& TCIF_STATE
)
2748 wineItem
->dwState
= (wineItem
->dwState
& ~tabItem
->dwStateMask
) |
2749 ( tabItem
->dwState
& tabItem
->dwStateMask
);
2751 if (tabItem
->mask
& TCIF_TEXT
)
2753 Free(wineItem
->pszText
);
2754 wineItem
->pszText
= NULL
;
2756 Str_SetPtrW(&wineItem
->pszText
, tabItem
->pszText
);
2758 Str_SetPtrAtoW(&wineItem
->pszText
, (LPSTR
)tabItem
->pszText
);
2761 /* Update and repaint tabs */
2762 TAB_SetItemBounds(infoPtr
);
2763 TAB_InvalidateTabArea(infoPtr
);
2768 static inline LRESULT
TAB_GetItemCount (const TAB_INFO
*infoPtr
)
2771 return infoPtr
->uNumItem
;
2776 TAB_GetItemT (TAB_INFO
*infoPtr
, INT iItem
, LPTCITEMW tabItem
, BOOL bUnicode
)
2780 TRACE("(%p,%d,%p,%s)\n", infoPtr
, iItem
, tabItem
, bUnicode
? "true" : "false");
2782 if (!tabItem
) return FALSE
;
2784 if (iItem
< 0 || iItem
>= infoPtr
->uNumItem
)
2786 /* init requested fields */
2787 if (tabItem
->mask
& TCIF_IMAGE
) tabItem
->iImage
= 0;
2788 if (tabItem
->mask
& TCIF_PARAM
) tabItem
->lParam
= 0;
2789 if (tabItem
->mask
& TCIF_STATE
) tabItem
->dwState
= 0;
2793 wineItem
= TAB_GetItem(infoPtr
, iItem
);
2795 if (tabItem
->mask
& TCIF_IMAGE
)
2796 tabItem
->iImage
= wineItem
->iImage
;
2798 if (tabItem
->mask
& TCIF_PARAM
)
2799 memcpy(&tabItem
->lParam
, wineItem
->extra
, infoPtr
->cbInfo
);
2801 if (tabItem
->mask
& TCIF_RTLREADING
)
2802 FIXME("TCIF_RTLREADING\n");
2804 if (tabItem
->mask
& TCIF_STATE
)
2805 tabItem
->dwState
= wineItem
->dwState
& tabItem
->dwStateMask
;
2807 if (tabItem
->mask
& TCIF_TEXT
)
2810 Str_GetPtrW (wineItem
->pszText
, tabItem
->pszText
, tabItem
->cchTextMax
);
2812 Str_GetPtrWtoA (wineItem
->pszText
, (LPSTR
)tabItem
->pszText
, tabItem
->cchTextMax
);
2815 TAB_DumpItemExternalT(tabItem
, iItem
, bUnicode
);
2821 static LRESULT
TAB_DeleteItem (TAB_INFO
*infoPtr
, INT iItem
)
2825 TRACE("(%p, %d)\n", infoPtr
, iItem
);
2827 if (iItem
< 0 || iItem
>= infoPtr
->uNumItem
) return FALSE
;
2829 TAB_InvalidateTabArea(infoPtr
);
2830 item
= TAB_GetItem(infoPtr
, iItem
);
2831 Free(item
->pszText
);
2833 infoPtr
->uNumItem
--;
2834 DPA_DeletePtr(infoPtr
->items
, iItem
);
2836 if (infoPtr
->uNumItem
== 0)
2838 if (infoPtr
->iHotTracked
>= 0)
2840 KillTimer(infoPtr
->hwnd
, TAB_HOTTRACK_TIMER
);
2841 infoPtr
->iHotTracked
= -1;
2844 infoPtr
->iSelected
= -1;
2848 if (iItem
<= infoPtr
->iHotTracked
)
2850 /* When tabs move left/up, the hot track item may change */
2851 FIXME("Recalc hot track\n");
2855 /* adjust the selected index */
2856 if (iItem
== infoPtr
->iSelected
)
2857 infoPtr
->iSelected
= -1;
2858 else if (iItem
< infoPtr
->iSelected
)
2859 infoPtr
->iSelected
--;
2861 /* reposition and repaint tabs */
2862 TAB_SetItemBounds(infoPtr
);
2867 static inline LRESULT
TAB_DeleteAllItems (TAB_INFO
*infoPtr
)
2869 TRACE("(%p)\n", infoPtr
);
2870 while (infoPtr
->uNumItem
)
2871 TAB_DeleteItem (infoPtr
, 0);
2876 static inline LRESULT
TAB_GetFont (const TAB_INFO
*infoPtr
)
2878 TRACE("(%p) returning %p\n", infoPtr
, infoPtr
->hFont
);
2879 return (LRESULT
)infoPtr
->hFont
;
2882 static inline LRESULT
TAB_SetFont (TAB_INFO
*infoPtr
, HFONT hNewFont
)
2884 TRACE("(%p,%p)\n", infoPtr
, hNewFont
);
2886 infoPtr
->hFont
= hNewFont
;
2888 TAB_SetItemBounds(infoPtr
);
2890 TAB_InvalidateTabArea(infoPtr
);
2896 static inline LRESULT
TAB_GetImageList (const TAB_INFO
*infoPtr
)
2899 return (LRESULT
)infoPtr
->himl
;
2902 static inline LRESULT
TAB_SetImageList (TAB_INFO
*infoPtr
, HIMAGELIST himlNew
)
2904 HIMAGELIST himlPrev
= infoPtr
->himl
;
2905 TRACE("himl=%p\n", himlNew
);
2906 infoPtr
->himl
= himlNew
;
2907 TAB_SetItemBounds(infoPtr
);
2908 InvalidateRect(infoPtr
->hwnd
, NULL
, TRUE
);
2909 return (LRESULT
)himlPrev
;
2912 static inline LRESULT
TAB_GetUnicodeFormat (const TAB_INFO
*infoPtr
)
2914 TRACE("(%p)\n", infoPtr
);
2915 return infoPtr
->bUnicode
;
2918 static inline LRESULT
TAB_SetUnicodeFormat (TAB_INFO
*infoPtr
, BOOL bUnicode
)
2920 BOOL bTemp
= infoPtr
->bUnicode
;
2922 TRACE("(%p %d)\n", infoPtr
, bUnicode
);
2923 infoPtr
->bUnicode
= bUnicode
;
2928 static inline LRESULT
TAB_Size (TAB_INFO
*infoPtr
)
2930 /* I'm not really sure what the following code was meant to do.
2931 This is what it is doing:
2932 When WM_SIZE is sent with SIZE_RESTORED, the control
2933 gets positioned in the top left corner.
2937 UINT uPosFlags,cx,cy;
2941 parent = GetParent (hwnd);
2942 GetClientRect(parent, &parent_rect);
2945 if (GetWindowLongW(hwnd, GWL_STYLE) & CCS_NORESIZE)
2946 uPosFlags |= (SWP_NOSIZE | SWP_NOMOVE);
2948 SetWindowPos (hwnd, 0, parent_rect.left, parent_rect.top,
2949 cx, cy, uPosFlags | SWP_NOZORDER);
2951 FIXME("WM_SIZE flag %x %lx not handled\n", wParam, lParam);
2954 /* Recompute the size/position of the tabs. */
2955 TAB_SetItemBounds (infoPtr
);
2957 /* Force a repaint of the control. */
2958 InvalidateRect(infoPtr
->hwnd
, NULL
, TRUE
);
2964 static LRESULT
TAB_Create (HWND hwnd
, LPARAM lParam
)
2967 TEXTMETRICW fontMetrics
;
2972 infoPtr
= Alloc (sizeof(TAB_INFO
));
2974 SetWindowLongPtrW(hwnd
, 0, (DWORD_PTR
)infoPtr
);
2976 infoPtr
->hwnd
= hwnd
;
2977 infoPtr
->hwndNotify
= ((LPCREATESTRUCTW
)lParam
)->hwndParent
;
2978 infoPtr
->uNumItem
= 0;
2979 infoPtr
->uNumRows
= 0;
2980 infoPtr
->uHItemPadding
= 6;
2981 infoPtr
->uVItemPadding
= 3;
2982 infoPtr
->uHItemPadding_s
= 6;
2983 infoPtr
->uVItemPadding_s
= 3;
2985 infoPtr
->items
= DPA_Create(8);
2986 infoPtr
->hcurArrow
= LoadCursorW (0, (LPWSTR
)IDC_ARROW
);
2987 infoPtr
->iSelected
= -1;
2988 infoPtr
->iHotTracked
= -1;
2989 infoPtr
->uFocus
= -1;
2990 infoPtr
->hwndToolTip
= 0;
2991 infoPtr
->DoRedraw
= TRUE
;
2992 infoPtr
->needsScrolling
= FALSE
;
2993 infoPtr
->hwndUpDown
= 0;
2994 infoPtr
->leftmostVisible
= 0;
2995 infoPtr
->fHeightSet
= FALSE
;
2996 infoPtr
->bUnicode
= IsWindowUnicode (hwnd
);
2997 infoPtr
->cbInfo
= sizeof(LPARAM
);
2999 TRACE("Created tab control, hwnd [%p]\n", hwnd
);
3001 /* The tab control always has the WS_CLIPSIBLINGS style. Even
3002 if you don't specify it in CreateWindow. This is necessary in
3003 order for paint to work correctly. This follows windows behaviour. */
3004 style
= GetWindowLongW(hwnd
, GWL_STYLE
);
3005 if (style
& TCS_VERTICAL
) style
|= TCS_MULTILINE
;
3006 style
|= WS_CLIPSIBLINGS
;
3007 SetWindowLongW(hwnd
, GWL_STYLE
, style
);
3009 infoPtr
->dwStyle
= style
;
3010 infoPtr
->exStyle
= (style
& TCS_FLATBUTTONS
) ? TCS_EX_FLATSEPARATORS
: 0;
3012 if (infoPtr
->dwStyle
& TCS_TOOLTIPS
) {
3013 /* Create tooltip control */
3014 infoPtr
->hwndToolTip
=
3015 CreateWindowExW (0, TOOLTIPS_CLASSW
, NULL
, WS_POPUP
,
3016 CW_USEDEFAULT
, CW_USEDEFAULT
,
3017 CW_USEDEFAULT
, CW_USEDEFAULT
,
3020 /* Send NM_TOOLTIPSCREATED notification */
3021 if (infoPtr
->hwndToolTip
) {
3022 NMTOOLTIPSCREATED nmttc
;
3024 nmttc
.hdr
.hwndFrom
= hwnd
;
3025 nmttc
.hdr
.idFrom
= GetWindowLongPtrW(hwnd
, GWLP_ID
);
3026 nmttc
.hdr
.code
= NM_TOOLTIPSCREATED
;
3027 nmttc
.hwndToolTips
= infoPtr
->hwndToolTip
;
3029 SendMessageW (infoPtr
->hwndNotify
, WM_NOTIFY
,
3030 GetWindowLongPtrW(hwnd
, GWLP_ID
), (LPARAM
)&nmttc
);
3034 OpenThemeData (infoPtr
->hwnd
, themeClass
);
3037 * We need to get text information so we need a DC and we need to select
3041 hOldFont
= SelectObject (hdc
, GetStockObject (SYSTEM_FONT
));
3043 /* Use the system font to determine the initial height of a tab. */
3044 GetTextMetricsW(hdc
, &fontMetrics
);
3047 * Make sure there is enough space for the letters + growing the
3048 * selected item + extra space for the selected item.
3050 infoPtr
->tabHeight
= fontMetrics
.tmHeight
+ SELECTED_TAB_OFFSET
+
3051 ((infoPtr
->dwStyle
& TCS_BUTTONS
) ? 2 : 1) *
3052 infoPtr
->uVItemPadding
;
3054 /* Initialize the width of a tab. */
3055 if (infoPtr
->dwStyle
& TCS_FIXEDWIDTH
)
3056 infoPtr
->tabWidth
= GetDeviceCaps(hdc
, LOGPIXELSX
);
3058 infoPtr
->tabMinWidth
= -1;
3060 TRACE("tabH=%d, tabW=%d\n", infoPtr
->tabHeight
, infoPtr
->tabWidth
);
3062 SelectObject (hdc
, hOldFont
);
3063 ReleaseDC(hwnd
, hdc
);
3069 TAB_Destroy (TAB_INFO
*infoPtr
)
3073 SetWindowLongPtrW(infoPtr
->hwnd
, 0, 0);
3075 for (iItem
= infoPtr
->uNumItem
- 1; iItem
>= 0; iItem
--)
3077 TAB_ITEM
*tab
= TAB_GetItem(infoPtr
, iItem
);
3079 DPA_DeletePtr(infoPtr
->items
, iItem
);
3080 infoPtr
->uNumItem
--;
3085 DPA_Destroy(infoPtr
->items
);
3086 infoPtr
->items
= NULL
;
3088 if (infoPtr
->hwndToolTip
)
3089 DestroyWindow (infoPtr
->hwndToolTip
);
3091 if (infoPtr
->hwndUpDown
)
3092 DestroyWindow(infoPtr
->hwndUpDown
);
3094 if (infoPtr
->iHotTracked
>= 0)
3095 KillTimer(infoPtr
->hwnd
, TAB_HOTTRACK_TIMER
);
3097 CloseThemeData (GetWindowTheme (infoPtr
->hwnd
));
3103 /* update theme after a WM_THEMECHANGED message */
3104 static LRESULT
theme_changed(const TAB_INFO
*infoPtr
)
3106 HTHEME theme
= GetWindowTheme (infoPtr
->hwnd
);
3107 CloseThemeData (theme
);
3108 OpenThemeData (infoPtr
->hwnd
, themeClass
);
3109 InvalidateRect (infoPtr
->hwnd
, NULL
, TRUE
);
3113 static LRESULT
TAB_NCCalcSize(WPARAM wParam
)
3117 return WVR_ALIGNTOP
;
3120 static inline LRESULT
3121 TAB_SetItemExtra (TAB_INFO
*infoPtr
, INT cbInfo
)
3123 TRACE("(%p %d)\n", infoPtr
, cbInfo
);
3125 if (cbInfo
< 0 || infoPtr
->uNumItem
) return FALSE
;
3127 infoPtr
->cbInfo
= cbInfo
;
3131 static LRESULT
TAB_RemoveImage (TAB_INFO
*infoPtr
, INT image
)
3133 TRACE("%p %d\n", infoPtr
, image
);
3135 if (ImageList_Remove (infoPtr
->himl
, image
))
3140 /* shift indices, repaint items if needed */
3141 for (i
= 0; i
< infoPtr
->uNumItem
; i
++)
3143 idx
= &TAB_GetItem(infoPtr
, i
)->iImage
;
3152 if (TAB_InternalGetItemRect (infoPtr
, i
, &r
, NULL
))
3153 InvalidateRect (infoPtr
->hwnd
, &r
, TRUE
);
3162 TAB_SetExtendedStyle (TAB_INFO
*infoPtr
, DWORD exMask
, DWORD exStyle
)
3164 DWORD prevstyle
= infoPtr
->exStyle
;
3166 /* zero mask means all styles */
3167 if (exMask
== 0) exMask
= ~0;
3169 if (exMask
& TCS_EX_REGISTERDROP
)
3171 FIXME("TCS_EX_REGISTERDROP style unimplemented\n");
3172 exMask
&= ~TCS_EX_REGISTERDROP
;
3173 exStyle
&= ~TCS_EX_REGISTERDROP
;
3176 if (exMask
& TCS_EX_FLATSEPARATORS
)
3178 if ((prevstyle
^ exStyle
) & TCS_EX_FLATSEPARATORS
)
3180 infoPtr
->exStyle
^= TCS_EX_FLATSEPARATORS
;
3181 TAB_InvalidateTabArea(infoPtr
);
3188 static inline LRESULT
3189 TAB_GetExtendedStyle (const TAB_INFO
*infoPtr
)
3191 return infoPtr
->exStyle
;
3195 TAB_DeselectAll (TAB_INFO
*infoPtr
, BOOL excludesel
)
3198 INT i
, selected
= infoPtr
->iSelected
;
3200 TRACE("(%p, %d)\n", infoPtr
, excludesel
);
3202 if (!(infoPtr
->dwStyle
& TCS_BUTTONS
))
3205 for (i
= 0; i
< infoPtr
->uNumItem
; i
++)
3207 if ((TAB_GetItem(infoPtr
, i
)->dwState
& TCIS_BUTTONPRESSED
) &&
3210 TAB_GetItem(infoPtr
, i
)->dwState
&= ~TCIS_BUTTONPRESSED
;
3215 if (!excludesel
&& (selected
!= -1))
3217 TAB_GetItem(infoPtr
, selected
)->dwState
&= ~TCIS_BUTTONPRESSED
;
3218 infoPtr
->iSelected
= -1;
3223 TAB_InvalidateTabArea (infoPtr
);
3230 * Processes WM_STYLECHANGED messages.
3233 * [I] infoPtr : valid pointer to the tab data structure
3234 * [I] wStyleType : window style type (normal or extended)
3235 * [I] lpss : window style information
3240 static INT
TAB_StyleChanged(TAB_INFO
*infoPtr
, WPARAM wStyleType
,
3241 const STYLESTRUCT
*lpss
)
3243 TRACE("style type %Ix, styleOld %#lx, styleNew %#lx\n", wStyleType
, lpss
->styleOld
, lpss
->styleNew
);
3245 if (wStyleType
!= GWL_STYLE
) return 0;
3247 infoPtr
->dwStyle
= lpss
->styleNew
;
3249 TAB_SetItemBounds (infoPtr
);
3254 static LRESULT WINAPI
3255 TAB_WindowProc (HWND hwnd
, UINT uMsg
, WPARAM wParam
, LPARAM lParam
)
3257 TAB_INFO
*infoPtr
= TAB_GetInfoPtr(hwnd
);
3259 TRACE("hwnd %p, msg %x, wParam %Ix, lParam %Ix\n", hwnd
, uMsg
, wParam
, lParam
);
3261 if (!infoPtr
&& (uMsg
!= WM_CREATE
))
3262 return DefWindowProcW (hwnd
, uMsg
, wParam
, lParam
);
3266 case TCM_GETIMAGELIST
:
3267 return TAB_GetImageList (infoPtr
);
3269 case TCM_SETIMAGELIST
:
3270 return TAB_SetImageList (infoPtr
, (HIMAGELIST
)lParam
);
3272 case TCM_GETITEMCOUNT
:
3273 return TAB_GetItemCount (infoPtr
);
3277 return TAB_GetItemT (infoPtr
, (INT
)wParam
, (LPTCITEMW
)lParam
, uMsg
== TCM_GETITEMW
);
3281 return TAB_SetItemT (infoPtr
, (INT
)wParam
, (LPTCITEMW
)lParam
, uMsg
== TCM_SETITEMW
);
3283 case TCM_DELETEITEM
:
3284 return TAB_DeleteItem (infoPtr
, (INT
)wParam
);
3286 case TCM_DELETEALLITEMS
:
3287 return TAB_DeleteAllItems (infoPtr
);
3289 case TCM_GETITEMRECT
:
3290 return TAB_GetItemRect (infoPtr
, (INT
)wParam
, (LPRECT
)lParam
);
3293 return TAB_GetCurSel (infoPtr
);
3296 return TAB_HitTest (infoPtr
, (LPTCHITTESTINFO
)lParam
);
3299 return TAB_SetCurSel (infoPtr
, (INT
)wParam
);
3301 case TCM_INSERTITEMA
:
3302 case TCM_INSERTITEMW
:
3303 return TAB_InsertItemT (infoPtr
, (INT
)wParam
, (TCITEMW
*)lParam
, uMsg
== TCM_INSERTITEMW
);
3305 case TCM_SETITEMEXTRA
:
3306 return TAB_SetItemExtra (infoPtr
, (INT
)wParam
);
3308 case TCM_ADJUSTRECT
:
3309 return TAB_AdjustRect (infoPtr
, (BOOL
)wParam
, (LPRECT
)lParam
);
3311 case TCM_SETITEMSIZE
:
3312 return TAB_SetItemSize (infoPtr
, (INT
)LOWORD(lParam
), (INT
)HIWORD(lParam
));
3314 case TCM_REMOVEIMAGE
:
3315 return TAB_RemoveImage (infoPtr
, (INT
)wParam
);
3317 case TCM_SETPADDING
:
3318 return TAB_SetPadding (infoPtr
, lParam
);
3320 case TCM_GETROWCOUNT
:
3321 return TAB_GetRowCount(infoPtr
);
3323 case TCM_GETUNICODEFORMAT
:
3324 return TAB_GetUnicodeFormat (infoPtr
);
3326 case TCM_SETUNICODEFORMAT
:
3327 return TAB_SetUnicodeFormat (infoPtr
, (BOOL
)wParam
);
3329 case TCM_HIGHLIGHTITEM
:
3330 return TAB_HighlightItem (infoPtr
, (INT
)wParam
, (BOOL
)LOWORD(lParam
));
3332 case TCM_GETTOOLTIPS
:
3333 return TAB_GetToolTips (infoPtr
);
3335 case TCM_SETTOOLTIPS
:
3336 return TAB_SetToolTips (infoPtr
, (HWND
)wParam
);
3338 case TCM_GETCURFOCUS
:
3339 return TAB_GetCurFocus (infoPtr
);
3341 case TCM_SETCURFOCUS
:
3342 return TAB_SetCurFocus (infoPtr
, (INT
)wParam
);
3344 case TCM_SETMINTABWIDTH
:
3345 return TAB_SetMinTabWidth(infoPtr
, (INT
)lParam
);
3347 case TCM_DESELECTALL
:
3348 return TAB_DeselectAll (infoPtr
, (BOOL
)wParam
);
3350 case TCM_GETEXTENDEDSTYLE
:
3351 return TAB_GetExtendedStyle (infoPtr
);
3353 case TCM_SETEXTENDEDSTYLE
:
3354 return TAB_SetExtendedStyle (infoPtr
, wParam
, lParam
);
3357 return TAB_GetFont (infoPtr
);
3360 return TAB_SetFont (infoPtr
, (HFONT
)wParam
);
3363 return TAB_Create (hwnd
, lParam
);
3366 return TAB_Destroy (infoPtr
);
3369 return DLGC_WANTARROWS
| DLGC_WANTCHARS
;
3371 case WM_LBUTTONDOWN
:
3372 return TAB_LButtonDown (infoPtr
, wParam
, lParam
);
3375 return TAB_LButtonUp (infoPtr
);
3378 return SendMessageW(infoPtr
->hwndNotify
, WM_NOTIFY
, wParam
, lParam
);
3381 TAB_RButtonUp (infoPtr
);
3382 return DefWindowProcW (hwnd
, uMsg
, wParam
, lParam
);
3385 return TAB_MouseMove (infoPtr
, wParam
, lParam
);
3387 case WM_PRINTCLIENT
:
3389 return TAB_Paint (infoPtr
, (HDC
)wParam
);
3392 return TAB_Size (infoPtr
);
3395 return TAB_SetRedraw (infoPtr
, (BOOL
)wParam
);
3398 return TAB_OnHScroll(infoPtr
, (int)LOWORD(wParam
), (int)HIWORD(wParam
));
3400 case WM_STYLECHANGED
:
3401 return TAB_StyleChanged(infoPtr
, wParam
, (LPSTYLESTRUCT
)lParam
);
3403 case WM_SYSCOLORCHANGE
:
3404 COMCTL32_RefreshSysColors();
3407 case WM_THEMECHANGED
:
3408 return theme_changed (infoPtr
);
3411 TAB_KillFocus(infoPtr
);
3413 TAB_FocusChanging(infoPtr
);
3414 break; /* Don't disturb normal focus behavior */
3417 return TAB_KeyDown(infoPtr
, wParam
, lParam
);
3420 return TAB_NCHitTest(infoPtr
, lParam
);
3423 return TAB_NCCalcSize(wParam
);
3426 if (uMsg
>= WM_USER
&& uMsg
< WM_APP
&& !COMCTL32_IsReflectedMessage(uMsg
))
3427 WARN("unknown msg %04x wp %Ix, lp %Ix\n", uMsg
, wParam
, lParam
);
3430 return DefWindowProcW(hwnd
, uMsg
, wParam
, lParam
);
3439 ZeroMemory (&wndClass
, sizeof(WNDCLASSW
));
3440 wndClass
.style
= CS_GLOBALCLASS
| CS_DBLCLKS
| CS_HREDRAW
| CS_VREDRAW
;
3441 wndClass
.lpfnWndProc
= TAB_WindowProc
;
3442 wndClass
.cbClsExtra
= 0;
3443 wndClass
.cbWndExtra
= sizeof(TAB_INFO
*);
3444 wndClass
.hCursor
= LoadCursorW (0, (LPWSTR
)IDC_ARROW
);
3445 wndClass
.hbrBackground
= (HBRUSH
)(COLOR_BTNFACE
+1);
3446 wndClass
.lpszClassName
= WC_TABCONTROLW
;
3448 RegisterClassW (&wndClass
);
3453 TAB_Unregister (void)
3455 UnregisterClassW (WC_TABCONTROLW
, NULL
);