4 * Copyright 1998 Anders Carlsson
5 * Copyright 1999 Alex Priem <alexp@sci.kun.nl>
6 * Copyright 1999 Francis Beaudet
7 * Copyright 2003 Vitaliy Margolen
9 * This library is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Lesser General Public
11 * License as published by the Free Software Foundation; either
12 * version 2.1 of the License, or (at your option) any later version.
14 * This library is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Lesser General Public License for more details.
19 * You should have received a copy of the GNU Lesser General Public
20 * License along with this library; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
25 * This code was audited for completeness against the documented features
26 * of Comctl32.dll version 6.0 on May. 20, 2005, by James Hawkins.
28 * Unless otherwise noted, we believe this code to be complete, as per
29 * the specification mentioned above.
30 * If you discover missing features, or bugs, please note them below.
35 * TCS_MULTISELECT - implement for VK_SPACE selection
57 WINE_DEFAULT_DEBUG_CHANNEL(tab
);
64 RECT rect
; /* bounding rectangle of the item relative to the
65 * leftmost item (the leftmost item, 0, would have a
66 * "left" member of 0 in this rectangle)
68 * additionally the top member holds the row number
69 * and bottom is unused and should be 0 */
70 BYTE extra
[1]; /* Space for caller supplied info, variable size */
73 /* The size of a tab item depends on how much extra data is requested.
74 TCM_INSERTITEM always stores at least LPARAM sized data. */
75 #define EXTRA_ITEM_SIZE(infoPtr) (max((infoPtr)->cbInfo, sizeof(LPARAM)))
76 #define TAB_ITEM_SIZE(infoPtr) FIELD_OFFSET(TAB_ITEM, extra[EXTRA_ITEM_SIZE(infoPtr)])
80 HWND hwnd
; /* Tab control window */
81 HWND hwndNotify
; /* notification window (parent) */
82 UINT uNumItem
; /* number of tab items */
83 UINT uNumRows
; /* number of tab rows */
84 INT tabHeight
; /* height of the tab row */
85 INT tabWidth
; /* width of tabs */
86 INT tabMinWidth
; /* minimum width of items */
87 USHORT uHItemPadding
; /* amount of horizontal padding, in pixels */
88 USHORT uVItemPadding
; /* amount of vertical padding, in pixels */
89 USHORT uHItemPadding_s
; /* Set amount of horizontal padding, in pixels */
90 USHORT uVItemPadding_s
; /* Set amount of vertical padding, in pixels */
91 HFONT hFont
; /* handle to the current font */
92 HCURSOR hcurArrow
; /* handle to the current cursor */
93 HIMAGELIST himl
; /* handle to an image list (may be 0) */
94 HWND hwndToolTip
; /* handle to tab's tooltip */
95 INT leftmostVisible
; /* Used for scrolling, this member contains
96 * the index of the first visible item */
97 INT iSelected
; /* the currently selected item */
98 INT iHotTracked
; /* the highlighted item under the mouse */
99 INT uFocus
; /* item which has the focus */
100 BOOL DoRedraw
; /* flag for redrawing when tab contents is changed*/
101 BOOL needsScrolling
; /* TRUE if the size of the tabs is greater than
102 * the size of the control */
103 BOOL fHeightSet
; /* was the height of the tabs explicitly set? */
104 BOOL bUnicode
; /* Unicode control? */
105 HWND hwndUpDown
; /* Updown control used for scrolling */
106 INT cbInfo
; /* Number of bytes of caller supplied info per tab */
108 DWORD exStyle
; /* Extended style used, currently:
109 TCS_EX_FLATSEPARATORS, TCS_EX_REGISTERDROP */
110 DWORD dwStyle
; /* the cached window GWL_STYLE */
112 HDPA items
; /* dynamic array of TAB_ITEM* pointers */
115 /******************************************************************************
116 * Positioning constants
118 #define SELECTED_TAB_OFFSET 2
119 #define ROUND_CORNER_SIZE 2
120 #define DISPLAY_AREA_PADDINGX 2
121 #define DISPLAY_AREA_PADDINGY 2
122 #define CONTROL_BORDER_SIZEX 2
123 #define CONTROL_BORDER_SIZEY 2
124 #define BUTTON_SPACINGX 3
125 #define BUTTON_SPACINGY 3
126 #define FLAT_BTN_SPACINGX 8
127 #define DEFAULT_MIN_TAB_WIDTH 54
128 #define DEFAULT_PADDING_X 6
129 #define EXTRA_ICON_PADDING 3
131 #define TAB_GetInfoPtr(hwnd) ((TAB_INFO *)GetWindowLongPtrW(hwnd,0))
133 #define GET_DEFAULT_MIN_TAB_WIDTH(infoPtr) (DEFAULT_MIN_TAB_WIDTH - (DEFAULT_PADDING_X - (infoPtr)->uHItemPadding) * 2)
135 /******************************************************************************
136 * Hot-tracking timer constants
138 #define TAB_HOTTRACK_TIMER 1
139 #define TAB_HOTTRACK_TIMER_INTERVAL 100 /* milliseconds */
141 static const WCHAR themeClass
[] = { 'T','a','b',0 };
143 static inline TAB_ITEM
* TAB_GetItem(const TAB_INFO
*infoPtr
, INT i
)
145 assert(i
>= 0 && i
< infoPtr
->uNumItem
);
146 return DPA_GetPtr(infoPtr
->items
, i
);
149 /******************************************************************************
152 static void TAB_InvalidateTabArea(const TAB_INFO
*);
153 static void TAB_EnsureSelectionVisible(TAB_INFO
*);
154 static void TAB_DrawItemInterior(const TAB_INFO
*, HDC
, INT
, RECT
*);
155 static LRESULT
TAB_DeselectAll(TAB_INFO
*, BOOL
);
156 static BOOL
TAB_InternalGetItemRect(const TAB_INFO
*, INT
, RECT
*, RECT
*);
159 TAB_SendSimpleNotify (const TAB_INFO
*infoPtr
, UINT code
)
163 nmhdr
.hwndFrom
= infoPtr
->hwnd
;
164 nmhdr
.idFrom
= GetWindowLongPtrW(infoPtr
->hwnd
, GWLP_ID
);
167 return (BOOL
) SendMessageW (infoPtr
->hwndNotify
, WM_NOTIFY
,
168 nmhdr
.idFrom
, (LPARAM
) &nmhdr
);
172 TAB_RelayEvent (HWND hwndTip
, HWND hwndMsg
, UINT uMsg
,
173 WPARAM wParam
, LPARAM lParam
)
181 msg
.time
= GetMessageTime ();
182 msg
.pt
.x
= (short)LOWORD(GetMessagePos ());
183 msg
.pt
.y
= (short)HIWORD(GetMessagePos ());
185 SendMessageW (hwndTip
, TTM_RELAYEVENT
, 0, (LPARAM
)&msg
);
189 TAB_DumpItemExternalT(const TCITEMW
*pti
, UINT iItem
, BOOL isW
)
192 TRACE("external tab %d, mask=0x%08x, dwState=0x%08x, dwStateMask=0x%08x, cchTextMax=0x%08x\n",
193 iItem
, pti
->mask
, pti
->dwState
, pti
->dwStateMask
, pti
->cchTextMax
);
194 TRACE("external tab %d, iImage=%d, lParam=0x%08lx, pszTextW=%s\n",
195 iItem
, pti
->iImage
, pti
->lParam
, isW
? debugstr_w(pti
->pszText
) : debugstr_a((LPSTR
)pti
->pszText
));
200 TAB_DumpItemInternal(const TAB_INFO
*infoPtr
, UINT iItem
)
203 TAB_ITEM
*ti
= TAB_GetItem(infoPtr
, iItem
);
205 TRACE("tab %d, dwState=0x%08x, pszText=%s, iImage=%d\n",
206 iItem
, ti
->dwState
, debugstr_w(ti
->pszText
), ti
->iImage
);
207 TRACE("tab %d, rect.left=%d, rect.top(row)=%d\n",
208 iItem
, ti
->rect
.left
, ti
->rect
.top
);
213 * the index of the selected tab, or -1 if no tab is selected. */
214 static inline LRESULT
TAB_GetCurSel (const TAB_INFO
*infoPtr
)
216 TRACE("(%p)\n", infoPtr
);
217 return infoPtr
->iSelected
;
221 * the index of the tab item that has the focus. */
222 static inline LRESULT
223 TAB_GetCurFocus (const TAB_INFO
*infoPtr
)
225 TRACE("(%p)\n", infoPtr
);
226 return infoPtr
->uFocus
;
229 static inline LRESULT
TAB_GetToolTips (const TAB_INFO
*infoPtr
)
231 TRACE("(%p)\n", infoPtr
);
232 return (LRESULT
)infoPtr
->hwndToolTip
;
235 static inline LRESULT
TAB_SetCurSel (TAB_INFO
*infoPtr
, INT iItem
)
237 INT prevItem
= infoPtr
->iSelected
;
239 TRACE("(%p %d)\n", infoPtr
, iItem
);
241 if (iItem
>= (INT
)infoPtr
->uNumItem
)
244 if (prevItem
!= iItem
) {
246 TAB_GetItem(infoPtr
, prevItem
)->dwState
&= ~TCIS_BUTTONPRESSED
;
250 TAB_GetItem(infoPtr
, iItem
)->dwState
|= TCIS_BUTTONPRESSED
;
251 infoPtr
->iSelected
= iItem
;
252 infoPtr
->uFocus
= iItem
;
256 infoPtr
->iSelected
= -1;
257 infoPtr
->uFocus
= -1;
260 TAB_EnsureSelectionVisible(infoPtr
);
261 TAB_InvalidateTabArea(infoPtr
);
267 static LRESULT
TAB_SetCurFocus (TAB_INFO
*infoPtr
, INT iItem
)
269 TRACE("(%p %d)\n", infoPtr
, iItem
);
272 infoPtr
->uFocus
= -1;
273 if (infoPtr
->iSelected
!= -1) {
274 infoPtr
->iSelected
= -1;
275 TAB_SendSimpleNotify(infoPtr
, TCN_SELCHANGE
);
276 TAB_InvalidateTabArea(infoPtr
);
279 else if (iItem
< infoPtr
->uNumItem
) {
280 if (infoPtr
->dwStyle
& TCS_BUTTONS
) {
281 /* set focus to new item, leave selection as is */
282 if (infoPtr
->uFocus
!= iItem
) {
283 INT prev_focus
= infoPtr
->uFocus
;
286 infoPtr
->uFocus
= iItem
;
288 if (prev_focus
!= infoPtr
->iSelected
) {
289 if (TAB_InternalGetItemRect(infoPtr
, prev_focus
, &r
, NULL
))
290 InvalidateRect(infoPtr
->hwnd
, &r
, FALSE
);
293 if (TAB_InternalGetItemRect(infoPtr
, iItem
, &r
, NULL
))
294 InvalidateRect(infoPtr
->hwnd
, &r
, FALSE
);
296 TAB_SendSimpleNotify(infoPtr
, TCN_FOCUSCHANGE
);
299 INT oldFocus
= infoPtr
->uFocus
;
300 if (infoPtr
->iSelected
!= iItem
|| oldFocus
== -1 ) {
301 infoPtr
->uFocus
= iItem
;
302 if (oldFocus
!= -1) {
303 if (!TAB_SendSimpleNotify(infoPtr
, TCN_SELCHANGING
)) {
304 infoPtr
->iSelected
= iItem
;
305 TAB_SendSimpleNotify(infoPtr
, TCN_SELCHANGE
);
308 infoPtr
->iSelected
= iItem
;
309 TAB_EnsureSelectionVisible(infoPtr
);
310 TAB_InvalidateTabArea(infoPtr
);
318 static inline LRESULT
319 TAB_SetToolTips (TAB_INFO
*infoPtr
, HWND hwndToolTip
)
321 TRACE("%p %p\n", infoPtr
, hwndToolTip
);
322 infoPtr
->hwndToolTip
= hwndToolTip
;
326 static inline LRESULT
327 TAB_SetPadding (TAB_INFO
*infoPtr
, LPARAM lParam
)
329 TRACE("(%p %d %d)\n", infoPtr
, LOWORD(lParam
), HIWORD(lParam
));
330 infoPtr
->uHItemPadding_s
= LOWORD(lParam
);
331 infoPtr
->uVItemPadding_s
= HIWORD(lParam
);
336 /******************************************************************************
337 * TAB_InternalGetItemRect
339 * This method will calculate the rectangle representing a given tab item in
340 * client coordinates. This method takes scrolling into account.
342 * This method returns TRUE if the item is visible in the window and FALSE
343 * if it is completely outside the client area.
345 static BOOL
TAB_InternalGetItemRect(
346 const TAB_INFO
* infoPtr
,
351 RECT tmpItemRect
,clientRect
;
353 /* Perform a sanity check and a trivial visibility check. */
354 if ( (infoPtr
->uNumItem
<= 0) ||
355 (itemIndex
>= infoPtr
->uNumItem
) ||
356 (!(((infoPtr
->dwStyle
& TCS_MULTILINE
) || (infoPtr
->dwStyle
& TCS_VERTICAL
))) &&
357 (itemIndex
< infoPtr
->leftmostVisible
)))
359 TRACE("Not Visible\n");
360 SetRect(itemRect
, 0, 0, 0, infoPtr
->tabHeight
);
361 SetRectEmpty(selectedRect
);
366 * Avoid special cases in this procedure by assigning the "out"
367 * parameters if the caller didn't supply them
369 if (itemRect
== NULL
)
370 itemRect
= &tmpItemRect
;
372 /* Retrieve the unmodified item rect. */
373 *itemRect
= TAB_GetItem(infoPtr
,itemIndex
)->rect
;
375 /* calculate the times bottom and top based on the row */
376 GetClientRect(infoPtr
->hwnd
, &clientRect
);
378 if ((infoPtr
->dwStyle
& TCS_BOTTOM
) && (infoPtr
->dwStyle
& TCS_VERTICAL
))
380 itemRect
->right
= clientRect
.right
- SELECTED_TAB_OFFSET
- itemRect
->left
* infoPtr
->tabHeight
-
381 ((infoPtr
->dwStyle
& TCS_BUTTONS
) ? itemRect
->left
* BUTTON_SPACINGX
: 0);
382 itemRect
->left
= itemRect
->right
- infoPtr
->tabHeight
;
384 else if (infoPtr
->dwStyle
& TCS_VERTICAL
)
386 itemRect
->left
= clientRect
.left
+ SELECTED_TAB_OFFSET
+ itemRect
->left
* infoPtr
->tabHeight
+
387 ((infoPtr
->dwStyle
& TCS_BUTTONS
) ? itemRect
->left
* BUTTON_SPACINGX
: 0);
388 itemRect
->right
= itemRect
->left
+ infoPtr
->tabHeight
;
390 else if (infoPtr
->dwStyle
& TCS_BOTTOM
)
392 itemRect
->bottom
= clientRect
.bottom
- itemRect
->top
* infoPtr
->tabHeight
-
393 ((infoPtr
->dwStyle
& TCS_BUTTONS
) ? itemRect
->top
* BUTTON_SPACINGY
: SELECTED_TAB_OFFSET
);
394 itemRect
->top
= itemRect
->bottom
- infoPtr
->tabHeight
;
396 else /* not TCS_BOTTOM and not TCS_VERTICAL */
398 itemRect
->top
= clientRect
.top
+ itemRect
->top
* infoPtr
->tabHeight
+
399 ((infoPtr
->dwStyle
& TCS_BUTTONS
) ? itemRect
->top
* BUTTON_SPACINGY
: SELECTED_TAB_OFFSET
);
400 itemRect
->bottom
= itemRect
->top
+ infoPtr
->tabHeight
;
404 * "scroll" it to make sure the item at the very left of the
405 * tab control is the leftmost visible tab.
407 if(infoPtr
->dwStyle
& TCS_VERTICAL
)
411 -TAB_GetItem(infoPtr
, infoPtr
->leftmostVisible
)->rect
.top
);
414 * Move the rectangle so the first item is slightly offset from
415 * the bottom of the tab control.
419 SELECTED_TAB_OFFSET
);
424 -TAB_GetItem(infoPtr
, infoPtr
->leftmostVisible
)->rect
.left
,
428 * Move the rectangle so the first item is slightly offset from
429 * the left of the tab control.
435 TRACE("item %d tab h=%d, rect=(%s)\n",
436 itemIndex
, infoPtr
->tabHeight
, wine_dbgstr_rect(itemRect
));
438 /* Now, calculate the position of the item as if it were selected. */
439 if (selectedRect
!=NULL
)
441 *selectedRect
= *itemRect
;
443 /* The rectangle of a selected item is a bit wider. */
444 if(infoPtr
->dwStyle
& TCS_VERTICAL
)
445 InflateRect(selectedRect
, 0, SELECTED_TAB_OFFSET
);
447 InflateRect(selectedRect
, SELECTED_TAB_OFFSET
, 0);
449 /* If it also a bit higher. */
450 if ((infoPtr
->dwStyle
& TCS_BOTTOM
) && (infoPtr
->dwStyle
& TCS_VERTICAL
))
452 selectedRect
->left
-= 2; /* the border is thicker on the right */
453 selectedRect
->right
+= SELECTED_TAB_OFFSET
;
455 else if (infoPtr
->dwStyle
& TCS_VERTICAL
)
457 selectedRect
->left
-= SELECTED_TAB_OFFSET
;
458 selectedRect
->right
+= 1;
460 else if (infoPtr
->dwStyle
& TCS_BOTTOM
)
462 selectedRect
->bottom
+= SELECTED_TAB_OFFSET
;
464 else /* not TCS_BOTTOM and not TCS_VERTICAL */
466 selectedRect
->top
-= SELECTED_TAB_OFFSET
;
467 selectedRect
->bottom
-= 1;
471 /* Check for visibility */
472 if (infoPtr
->dwStyle
& TCS_VERTICAL
)
473 return (itemRect
->top
< clientRect
.bottom
) && (itemRect
->bottom
> clientRect
.top
);
475 return (itemRect
->left
< clientRect
.right
) && (itemRect
->right
> clientRect
.left
);
479 TAB_GetItemRect(const TAB_INFO
*infoPtr
, INT item
, RECT
*rect
)
481 TRACE("(%p, %d, %p)\n", infoPtr
, item
, rect
);
482 return TAB_InternalGetItemRect(infoPtr
, item
, rect
, NULL
);
485 /******************************************************************************
488 * This method is called to handle keyboard input
490 static LRESULT
TAB_KeyDown(TAB_INFO
* infoPtr
, WPARAM keyCode
, LPARAM lParam
)
495 /* TCN_KEYDOWN notification sent always */
496 nm
.hdr
.hwndFrom
= infoPtr
->hwnd
;
497 nm
.hdr
.idFrom
= GetWindowLongPtrW(infoPtr
->hwnd
, GWLP_ID
);
498 nm
.hdr
.code
= TCN_KEYDOWN
;
501 SendMessageW(infoPtr
->hwndNotify
, WM_NOTIFY
, nm
.hdr
.idFrom
, (LPARAM
)&nm
);
506 newItem
= infoPtr
->uFocus
- 1;
509 newItem
= infoPtr
->uFocus
+ 1;
513 /* If we changed to a valid item, change focused item */
514 if (newItem
>= 0 && newItem
< infoPtr
->uNumItem
&& infoPtr
->uFocus
!= newItem
)
515 TAB_SetCurFocus(infoPtr
, newItem
);
521 * WM_KILLFOCUS handler
523 static void TAB_KillFocus(TAB_INFO
*infoPtr
)
525 /* clear current focused item back to selected for TCS_BUTTONS */
526 if ((infoPtr
->dwStyle
& TCS_BUTTONS
) && (infoPtr
->uFocus
!= infoPtr
->iSelected
))
530 if (TAB_InternalGetItemRect(infoPtr
, infoPtr
->uFocus
, &r
, NULL
))
531 InvalidateRect(infoPtr
->hwnd
, &r
, FALSE
);
533 infoPtr
->uFocus
= infoPtr
->iSelected
;
537 /******************************************************************************
540 * This method is called whenever the focus goes in or out of this control
541 * it is used to update the visual state of the control.
543 static void TAB_FocusChanging(const TAB_INFO
*infoPtr
)
549 * Get the rectangle for the item.
551 isVisible
= TAB_InternalGetItemRect(infoPtr
,
557 * If the rectangle is not completely invisible, invalidate that
558 * portion of the window.
562 TRACE("invalidate (%s)\n", wine_dbgstr_rect(&selectedRect
));
563 InvalidateRect(infoPtr
->hwnd
, &selectedRect
, TRUE
);
567 static INT
TAB_InternalHitTest (const TAB_INFO
*infoPtr
, POINT pt
, UINT
*flags
)
572 for (iCount
= 0; iCount
< infoPtr
->uNumItem
; iCount
++)
574 TAB_InternalGetItemRect(infoPtr
, iCount
, &rect
, NULL
);
576 if (PtInRect(&rect
, pt
))
578 *flags
= TCHT_ONITEM
;
583 *flags
= TCHT_NOWHERE
;
587 static inline LRESULT
588 TAB_HitTest (const TAB_INFO
*infoPtr
, LPTCHITTESTINFO lptest
)
590 TRACE("(%p, %p)\n", infoPtr
, lptest
);
591 return TAB_InternalHitTest (infoPtr
, lptest
->pt
, &lptest
->flags
);
594 /******************************************************************************
597 * Napster v2b5 has a tab control for its main navigation which has a client
598 * area that covers the whole area of the dialog pages.
599 * That's why it receives all msgs for that area and the underlying dialog ctrls
601 * So I decided that we should handle WM_NCHITTEST here and return
602 * HTTRANSPARENT if we don't hit the tab control buttons.
603 * FIXME: WM_NCHITTEST handling correct ? Fix it if you know that Windows
604 * doesn't do it that way. Maybe depends on tab control styles ?
606 static inline LRESULT
607 TAB_NCHitTest (const TAB_INFO
*infoPtr
, LPARAM lParam
)
612 pt
.x
= (short)LOWORD(lParam
);
613 pt
.y
= (short)HIWORD(lParam
);
614 ScreenToClient(infoPtr
->hwnd
, &pt
);
616 if (TAB_InternalHitTest(infoPtr
, pt
, &dummyflag
) == -1)
617 return HTTRANSPARENT
;
623 TAB_LButtonDown (TAB_INFO
*infoPtr
, WPARAM wParam
, LPARAM lParam
)
629 if (infoPtr
->hwndToolTip
)
630 TAB_RelayEvent (infoPtr
->hwndToolTip
, infoPtr
->hwnd
,
631 WM_LBUTTONDOWN
, wParam
, lParam
);
633 if (!(infoPtr
->dwStyle
& TCS_FOCUSNEVER
)) {
634 SetFocus (infoPtr
->hwnd
);
637 if (infoPtr
->hwndToolTip
)
638 TAB_RelayEvent (infoPtr
->hwndToolTip
, infoPtr
->hwnd
,
639 WM_LBUTTONDOWN
, wParam
, lParam
);
641 pt
.x
= (short)LOWORD(lParam
);
642 pt
.y
= (short)HIWORD(lParam
);
644 newItem
= TAB_InternalHitTest (infoPtr
, pt
, &dummy
);
646 TRACE("On Tab, item %d\n", newItem
);
648 if ((newItem
!= -1) && (infoPtr
->iSelected
!= newItem
))
650 if ((infoPtr
->dwStyle
& TCS_BUTTONS
) && (infoPtr
->dwStyle
& TCS_MULTISELECT
) &&
651 (wParam
& MK_CONTROL
))
655 /* toggle multiselection */
656 TAB_GetItem(infoPtr
, newItem
)->dwState
^= TCIS_BUTTONPRESSED
;
657 if (TAB_InternalGetItemRect (infoPtr
, newItem
, &r
, NULL
))
658 InvalidateRect (infoPtr
->hwnd
, &r
, TRUE
);
663 BOOL pressed
= FALSE
;
665 /* any button pressed ? */
666 for (i
= 0; i
< infoPtr
->uNumItem
; i
++)
667 if ((TAB_GetItem (infoPtr
, i
)->dwState
& TCIS_BUTTONPRESSED
) &&
668 (infoPtr
->iSelected
!= i
))
674 if (TAB_SendSimpleNotify(infoPtr
, TCN_SELCHANGING
))
678 TAB_DeselectAll (infoPtr
, FALSE
);
680 TAB_SetCurSel(infoPtr
, newItem
);
682 TAB_SendSimpleNotify(infoPtr
, TCN_SELCHANGE
);
689 static inline LRESULT
690 TAB_LButtonUp (const TAB_INFO
*infoPtr
)
692 TAB_SendSimpleNotify(infoPtr
, NM_CLICK
);
698 TAB_RButtonUp (const TAB_INFO
*infoPtr
)
700 TAB_SendSimpleNotify(infoPtr
, NM_RCLICK
);
703 /******************************************************************************
704 * TAB_DrawLoneItemInterior
706 * This calls TAB_DrawItemInterior. However, TAB_DrawItemInterior is normally
707 * called by TAB_DrawItem which is normally called by TAB_Refresh which sets
708 * up the device context and font. This routine does the same setup but
709 * only calls TAB_DrawItemInterior for the single specified item.
712 TAB_DrawLoneItemInterior(const TAB_INFO
* infoPtr
, int iItem
)
714 HDC hdc
= GetDC(infoPtr
->hwnd
);
717 /* Clip UpDown control to not draw over it */
718 if (infoPtr
->needsScrolling
)
720 GetWindowRect(infoPtr
->hwnd
, &rC
);
721 GetWindowRect(infoPtr
->hwndUpDown
, &r
);
722 ExcludeClipRect(hdc
, r
.left
- rC
.left
, r
.top
- rC
.top
, r
.right
- rC
.left
, r
.bottom
- rC
.top
);
724 TAB_DrawItemInterior(infoPtr
, hdc
, iItem
, NULL
);
725 ReleaseDC(infoPtr
->hwnd
, hdc
);
728 /* update a tab after hottracking - invalidate it or just redraw the interior,
729 * based on whether theming is used or not */
730 static inline void hottrack_refresh(const TAB_INFO
*infoPtr
, int tabIndex
)
732 if (tabIndex
== -1) return;
734 if (GetWindowTheme (infoPtr
->hwnd
))
737 TAB_InternalGetItemRect(infoPtr
, tabIndex
, &rect
, NULL
);
738 InvalidateRect (infoPtr
->hwnd
, &rect
, FALSE
);
741 TAB_DrawLoneItemInterior(infoPtr
, tabIndex
);
744 /******************************************************************************
745 * TAB_HotTrackTimerProc
747 * When a mouse-move event causes a tab to be highlighted (hot-tracking), a
748 * timer is setup so we can check if the mouse is moved out of our window.
749 * (We don't get an event when the mouse leaves, the mouse-move events just
750 * stop being delivered to our window and just start being delivered to
751 * another window.) This function is called when the timer triggers so
752 * we can check if the mouse has left our window. If so, we un-highlight
753 * the hot-tracked tab.
756 TAB_HotTrackTimerProc
758 HWND hwnd
, /* handle of window for timer messages */
759 UINT uMsg
, /* WM_TIMER message */
760 UINT_PTR idEvent
, /* timer identifier */
761 DWORD dwTime
/* current system time */
764 TAB_INFO
* infoPtr
= TAB_GetInfoPtr(hwnd
);
766 if (infoPtr
!= NULL
&& infoPtr
->iHotTracked
>= 0)
771 ** If we can't get the cursor position, or if the cursor is outside our
772 ** window, we un-highlight the hot-tracked tab. Note that the cursor is
773 ** "outside" even if it is within our bounding rect if another window
774 ** overlaps. Note also that the case where the cursor stayed within our
775 ** window but has moved off the hot-tracked tab will be handled by the
776 ** WM_MOUSEMOVE event.
778 if (!GetCursorPos(&pt
) || WindowFromPoint(pt
) != hwnd
)
780 /* Redraw iHotTracked to look normal */
781 INT iRedraw
= infoPtr
->iHotTracked
;
782 infoPtr
->iHotTracked
= -1;
783 hottrack_refresh (infoPtr
, iRedraw
);
785 /* Kill this timer */
786 KillTimer(hwnd
, TAB_HOTTRACK_TIMER
);
791 /******************************************************************************
794 * If a tab control has the TCS_HOTTRACK style, then the tab under the mouse
795 * should be highlighted. This function determines which tab in a tab control,
796 * if any, is under the mouse and records that information. The caller may
797 * supply output parameters to receive the item number of the tab item which
798 * was highlighted but isn't any longer and of the tab item which is now
799 * highlighted but wasn't previously. The caller can use this information to
800 * selectively redraw those tab items.
802 * If the caller has a mouse position, it can supply it through the pos
803 * parameter. For example, TAB_MouseMove does this. Otherwise, the caller
804 * supplies NULL and this function determines the current mouse position
812 int* out_redrawLeave
,
819 if (out_redrawLeave
!= NULL
)
820 *out_redrawLeave
= -1;
821 if (out_redrawEnter
!= NULL
)
822 *out_redrawEnter
= -1;
824 if ((infoPtr
->dwStyle
& TCS_HOTTRACK
) || GetWindowTheme(infoPtr
->hwnd
))
832 ScreenToClient(infoPtr
->hwnd
, &pt
);
836 pt
.x
= (short)LOWORD(*pos
);
837 pt
.y
= (short)HIWORD(*pos
);
840 item
= TAB_InternalHitTest(infoPtr
, pt
, &flags
);
843 if (item
!= infoPtr
->iHotTracked
)
845 if (infoPtr
->iHotTracked
>= 0)
847 /* Mark currently hot-tracked to be redrawn to look normal */
848 if (out_redrawLeave
!= NULL
)
849 *out_redrawLeave
= infoPtr
->iHotTracked
;
853 /* Kill timer which forces recheck of mouse pos */
854 KillTimer(infoPtr
->hwnd
, TAB_HOTTRACK_TIMER
);
859 /* Start timer so we recheck mouse pos */
860 UINT timerID
= SetTimer
864 TAB_HOTTRACK_TIMER_INTERVAL
,
865 TAB_HotTrackTimerProc
869 return; /* Hot tracking not available */
872 infoPtr
->iHotTracked
= item
;
876 /* Mark new hot-tracked to be redrawn to look highlighted */
877 if (out_redrawEnter
!= NULL
)
878 *out_redrawEnter
= item
;
883 /******************************************************************************
886 * Handles the mouse-move event. Updates tooltips. Updates hot-tracking.
889 TAB_MouseMove (TAB_INFO
*infoPtr
, WPARAM wParam
, LPARAM lParam
)
894 if (infoPtr
->hwndToolTip
)
895 TAB_RelayEvent (infoPtr
->hwndToolTip
, infoPtr
->hwnd
,
896 WM_LBUTTONDOWN
, wParam
, lParam
);
898 /* Determine which tab to highlight. Redraw tabs which change highlight
900 TAB_RecalcHotTrack(infoPtr
, &lParam
, &redrawLeave
, &redrawEnter
);
902 hottrack_refresh (infoPtr
, redrawLeave
);
903 hottrack_refresh (infoPtr
, redrawEnter
);
908 /******************************************************************************
911 * Calculates the tab control's display area given the window rectangle or
912 * the window rectangle given the requested display rectangle.
914 static LRESULT
TAB_AdjustRect(const TAB_INFO
*infoPtr
, WPARAM fLarger
, LPRECT prc
)
916 LONG
*iRightBottom
, *iLeftTop
;
918 TRACE ("hwnd=%p fLarger=%ld (%s)\n", infoPtr
->hwnd
, fLarger
,
919 wine_dbgstr_rect(prc
));
923 if(infoPtr
->dwStyle
& TCS_VERTICAL
)
925 iRightBottom
= &(prc
->right
);
926 iLeftTop
= &(prc
->left
);
930 iRightBottom
= &(prc
->bottom
);
931 iLeftTop
= &(prc
->top
);
934 if (fLarger
) /* Go from display rectangle */
936 /* Add the height of the tabs. */
937 if (infoPtr
->dwStyle
& TCS_BOTTOM
)
938 *iRightBottom
+= infoPtr
->tabHeight
* infoPtr
->uNumRows
;
940 *iLeftTop
-= infoPtr
->tabHeight
* infoPtr
->uNumRows
+
941 ((infoPtr
->dwStyle
& TCS_BUTTONS
)? 3 * (infoPtr
->uNumRows
- 1) : 0);
943 /* Inflate the rectangle for the padding */
944 InflateRect(prc
, DISPLAY_AREA_PADDINGX
, DISPLAY_AREA_PADDINGY
);
946 /* Inflate for the border */
947 InflateRect(prc
, CONTROL_BORDER_SIZEX
, CONTROL_BORDER_SIZEY
);
949 else /* Go from window rectangle. */
951 /* Deflate the rectangle for the border */
952 InflateRect(prc
, -CONTROL_BORDER_SIZEX
, -CONTROL_BORDER_SIZEY
);
954 /* Deflate the rectangle for the padding */
955 InflateRect(prc
, -DISPLAY_AREA_PADDINGX
, -DISPLAY_AREA_PADDINGY
);
957 /* Remove the height of the tabs. */
958 if (infoPtr
->dwStyle
& TCS_BOTTOM
)
959 *iRightBottom
-= infoPtr
->tabHeight
* infoPtr
->uNumRows
;
961 *iLeftTop
+= (infoPtr
->tabHeight
) * infoPtr
->uNumRows
+
962 ((infoPtr
->dwStyle
& TCS_BUTTONS
)? 3 * (infoPtr
->uNumRows
- 1) : 0);
968 /******************************************************************************
971 * This method will handle the notification from the scroll control and
972 * perform the scrolling operation on the tab control.
974 static LRESULT
TAB_OnHScroll(TAB_INFO
*infoPtr
, int nScrollCode
, int nPos
)
976 if(nScrollCode
== SB_THUMBPOSITION
&& nPos
!= infoPtr
->leftmostVisible
)
978 if(nPos
< infoPtr
->leftmostVisible
)
979 infoPtr
->leftmostVisible
--;
981 infoPtr
->leftmostVisible
++;
983 TAB_RecalcHotTrack(infoPtr
, NULL
, NULL
, NULL
);
984 TAB_InvalidateTabArea(infoPtr
);
985 SendMessageW(infoPtr
->hwndUpDown
, UDM_SETPOS
, 0,
986 MAKELONG(infoPtr
->leftmostVisible
, 0));
992 /******************************************************************************
995 * This method will check the current scrolling state and make sure the
996 * scrolling control is displayed (or not).
998 static void TAB_SetupScrolling(
1000 const RECT
* clientRect
)
1002 static const WCHAR emptyW
[] = { 0 };
1005 if (infoPtr
->needsScrolling
)
1008 INT vsize
, tabwidth
;
1011 * Calculate the position of the scroll control.
1013 controlPos
.right
= clientRect
->right
;
1014 controlPos
.left
= controlPos
.right
- 2 * GetSystemMetrics(SM_CXHSCROLL
);
1016 if (infoPtr
->dwStyle
& TCS_BOTTOM
)
1018 controlPos
.top
= clientRect
->bottom
- infoPtr
->tabHeight
;
1019 controlPos
.bottom
= controlPos
.top
+ GetSystemMetrics(SM_CYHSCROLL
);
1023 controlPos
.bottom
= clientRect
->top
+ infoPtr
->tabHeight
;
1024 controlPos
.top
= controlPos
.bottom
- GetSystemMetrics(SM_CYHSCROLL
);
1028 * If we don't have a scroll control yet, we want to create one.
1029 * If we have one, we want to make sure it's positioned properly.
1031 if (infoPtr
->hwndUpDown
==0)
1033 infoPtr
->hwndUpDown
= CreateWindowW(UPDOWN_CLASSW
, emptyW
,
1034 WS_VISIBLE
| WS_CHILD
| UDS_HORZ
,
1035 controlPos
.left
, controlPos
.top
,
1036 controlPos
.right
- controlPos
.left
,
1037 controlPos
.bottom
- controlPos
.top
,
1038 infoPtr
->hwnd
, NULL
, NULL
, NULL
);
1042 SetWindowPos(infoPtr
->hwndUpDown
,
1044 controlPos
.left
, controlPos
.top
,
1045 controlPos
.right
- controlPos
.left
,
1046 controlPos
.bottom
- controlPos
.top
,
1047 SWP_SHOWWINDOW
| SWP_NOZORDER
);
1050 /* Now calculate upper limit of the updown control range.
1051 * We do this by calculating how many tabs will be offscreen when the
1052 * last tab is visible.
1054 if(infoPtr
->uNumItem
)
1056 vsize
= clientRect
->right
- (controlPos
.right
- controlPos
.left
+ 1);
1057 maxRange
= infoPtr
->uNumItem
;
1058 tabwidth
= TAB_GetItem(infoPtr
, infoPtr
->uNumItem
- 1)->rect
.right
;
1060 for(; maxRange
> 0; maxRange
--)
1062 if(tabwidth
- TAB_GetItem(infoPtr
,maxRange
- 1)->rect
.left
> vsize
)
1066 if(maxRange
== infoPtr
->uNumItem
)
1072 /* If we once had a scroll control... hide it */
1073 if (infoPtr
->hwndUpDown
)
1074 ShowWindow(infoPtr
->hwndUpDown
, SW_HIDE
);
1076 if (infoPtr
->hwndUpDown
)
1077 SendMessageW(infoPtr
->hwndUpDown
, UDM_SETRANGE32
, 0, maxRange
);
1080 /******************************************************************************
1083 * This method will calculate the position rectangles of all the items in the
1084 * control. The rectangle calculated starts at 0 for the first item in the
1085 * list and ignores scrolling and selection.
1086 * It also uses the current font to determine the height of the tab row and
1087 * it checks if all the tabs fit in the client area of the window. If they
1088 * don't, a scrolling control is added.
1090 static void TAB_SetItemBounds (TAB_INFO
*infoPtr
)
1092 TEXTMETRICW fontMetrics
;
1095 INT curItemRowCount
;
1096 HFONT hFont
, hOldFont
;
1105 * We need to get text information so we need a DC and we need to select
1108 hdc
= GetDC(infoPtr
->hwnd
);
1110 hFont
= infoPtr
->hFont
? infoPtr
->hFont
: GetStockObject (SYSTEM_FONT
);
1111 hOldFont
= SelectObject (hdc
, hFont
);
1114 * We will base the rectangle calculations on the client rectangle
1117 GetClientRect(infoPtr
->hwnd
, &clientRect
);
1119 /* if TCS_VERTICAL then swap the height and width so this code places the
1120 tabs along the top of the rectangle and we can just rotate them after
1121 rather than duplicate all of the below code */
1122 if(infoPtr
->dwStyle
& TCS_VERTICAL
)
1124 iTemp
= clientRect
.bottom
;
1125 clientRect
.bottom
= clientRect
.right
;
1126 clientRect
.right
= iTemp
;
1129 /* Now use hPadding and vPadding */
1130 infoPtr
->uHItemPadding
= infoPtr
->uHItemPadding_s
;
1131 infoPtr
->uVItemPadding
= infoPtr
->uVItemPadding_s
;
1133 /* The leftmost item will be "0" aligned */
1135 curItemRowCount
= infoPtr
->uNumItem
? 1 : 0;
1137 if (!(infoPtr
->fHeightSet
))
1140 INT icon_height
= 0, cx
;
1142 /* Use the current font to determine the height of a tab. */
1143 GetTextMetricsW(hdc
, &fontMetrics
);
1145 /* Get the icon height */
1147 ImageList_GetIconSize(infoPtr
->himl
, &cx
, &icon_height
);
1149 /* Take the highest between font or icon */
1150 if (fontMetrics
.tmHeight
> icon_height
)
1151 item_height
= fontMetrics
.tmHeight
+ 2;
1153 item_height
= icon_height
;
1156 * Make sure there is enough space for the letters + icon + growing the
1157 * selected item + extra space for the selected item.
1159 infoPtr
->tabHeight
= item_height
+
1160 ((infoPtr
->dwStyle
& TCS_BUTTONS
) ? 2 : 1) *
1161 infoPtr
->uVItemPadding
;
1163 TRACE("tabH=%d, tmH=%d, iconh=%d\n",
1164 infoPtr
->tabHeight
, fontMetrics
.tmHeight
, icon_height
);
1167 TRACE("client right=%d\n", clientRect
.right
);
1169 /* Get the icon width */
1174 ImageList_GetIconSize(infoPtr
->himl
, &icon_width
, &cy
);
1176 if (infoPtr
->dwStyle
& TCS_FIXEDWIDTH
)
1179 /* Add padding if icon is present */
1180 icon_width
+= infoPtr
->uHItemPadding
;
1183 for (curItem
= 0; curItem
< infoPtr
->uNumItem
; curItem
++)
1185 TAB_ITEM
*curr
= TAB_GetItem(infoPtr
, curItem
);
1187 /* Set the leftmost position of the tab. */
1188 curr
->rect
.left
= curItemLeftPos
;
1190 if (infoPtr
->dwStyle
& TCS_FIXEDWIDTH
)
1192 curr
->rect
.right
= curr
->rect
.left
+
1193 max(infoPtr
->tabWidth
, icon_width
);
1195 else if (!curr
->pszText
)
1197 /* If no text use minimum tab width including padding. */
1198 if (infoPtr
->tabMinWidth
< 0)
1199 curr
->rect
.right
= curr
->rect
.left
+ GET_DEFAULT_MIN_TAB_WIDTH(infoPtr
);
1202 curr
->rect
.right
= curr
->rect
.left
+ infoPtr
->tabMinWidth
;
1204 /* Add extra padding if icon is present */
1205 if (infoPtr
->himl
&& infoPtr
->tabMinWidth
> 0 && infoPtr
->tabMinWidth
< DEFAULT_MIN_TAB_WIDTH
1206 && infoPtr
->uHItemPadding
> 1)
1207 curr
->rect
.right
+= EXTRA_ICON_PADDING
* (infoPtr
->uHItemPadding
-1);
1214 /* Calculate how wide the tab is depending on the text it contains */
1215 GetTextExtentPoint32W(hdc
, curr
->pszText
,
1216 lstrlenW(curr
->pszText
), &size
);
1218 tabwidth
= size
.cx
+ icon_width
+ 2 * infoPtr
->uHItemPadding
;
1220 if (infoPtr
->tabMinWidth
< 0)
1221 tabwidth
= max(tabwidth
, GET_DEFAULT_MIN_TAB_WIDTH(infoPtr
));
1223 tabwidth
= max(tabwidth
, infoPtr
->tabMinWidth
);
1225 curr
->rect
.right
= curr
->rect
.left
+ tabwidth
;
1226 TRACE("for <%s>, rect %s\n", debugstr_w(curr
->pszText
), wine_dbgstr_rect(&curr
->rect
));
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>, rect %s\n", debugstr_w(curr
->pszText
), wine_dbgstr_rect(&curr
->rect
));
1248 curr
->rect
.bottom
= 0;
1249 curr
->rect
.top
= curItemRowCount
- 1;
1251 TRACE("Rect: %s\n", wine_dbgstr_rect(&curr
->rect
));
1254 * The leftmost position of the next item is the rightmost position
1257 if (infoPtr
->dwStyle
& TCS_BUTTONS
)
1259 curItemLeftPos
= curr
->rect
.right
+ BUTTON_SPACINGX
;
1260 if (infoPtr
->dwStyle
& TCS_FLATBUTTONS
)
1261 curItemLeftPos
+= FLAT_BTN_SPACINGX
;
1264 curItemLeftPos
= curr
->rect
.right
;
1267 if (!((infoPtr
->dwStyle
& TCS_MULTILINE
) || (infoPtr
->dwStyle
& TCS_VERTICAL
)))
1270 * Check if we need a scrolling control.
1272 infoPtr
->needsScrolling
= (curItemLeftPos
+ (2 * SELECTED_TAB_OFFSET
) >
1275 /* Don't need scrolling, then update infoPtr->leftmostVisible */
1276 if(!infoPtr
->needsScrolling
)
1277 infoPtr
->leftmostVisible
= 0;
1282 * No scrolling in Multiline or Vertical styles.
1284 infoPtr
->needsScrolling
= FALSE
;
1285 infoPtr
->leftmostVisible
= 0;
1287 TAB_SetupScrolling(infoPtr
, &clientRect
);
1289 /* Set the number of rows */
1290 infoPtr
->uNumRows
= curItemRowCount
;
1292 /* Arrange all tabs evenly if style says so */
1293 if (!(infoPtr
->dwStyle
& TCS_RAGGEDRIGHT
) &&
1294 ((infoPtr
->dwStyle
& TCS_MULTILINE
) || (infoPtr
->dwStyle
& TCS_VERTICAL
)) &&
1295 (infoPtr
->uNumItem
> 0) &&
1296 (infoPtr
->uNumRows
> 1))
1298 INT tabPerRow
,remTab
,iRow
;
1303 * Ok windows tries to even out the rows. place the same
1304 * number of tabs in each row. So lets give that a shot
1307 tabPerRow
= infoPtr
->uNumItem
/ (infoPtr
->uNumRows
);
1308 remTab
= infoPtr
->uNumItem
% (infoPtr
->uNumRows
);
1310 for (iItm
=0,iRow
=0,iCount
=0,curItemLeftPos
=0;
1311 iItm
<infoPtr
->uNumItem
;
1314 /* normalize the current rect */
1315 TAB_ITEM
*curr
= TAB_GetItem(infoPtr
, iItm
);
1317 /* shift the item to the left side of the clientRect */
1318 curr
->rect
.right
-= curr
->rect
.left
;
1319 curr
->rect
.left
= 0;
1321 TRACE("r=%d, cl=%d, cl.r=%d, iCount=%d, iRow=%d, uNumRows=%d, remTab=%d, tabPerRow=%d\n",
1322 curr
->rect
.right
, curItemLeftPos
, clientRect
.right
,
1323 iCount
, iRow
, infoPtr
->uNumRows
, remTab
, tabPerRow
);
1325 /* if we have reached the maximum number of tabs on this row */
1326 /* move to the next row, reset our current item left position and */
1327 /* the count of items on this row */
1329 if (infoPtr
->dwStyle
& TCS_VERTICAL
) {
1330 /* Vert: Add the remaining tabs in the *last* remainder rows */
1331 if (iCount
>= ((iRow
>=(INT
)infoPtr
->uNumRows
- remTab
)?tabPerRow
+ 1:tabPerRow
)) {
1337 /* Horz: Add the remaining tabs in the *first* remainder rows */
1338 if (iCount
>= ((iRow
<remTab
)?tabPerRow
+ 1:tabPerRow
)) {
1345 /* shift the item to the right to place it as the next item in this row */
1346 curr
->rect
.left
+= curItemLeftPos
;
1347 curr
->rect
.right
+= curItemLeftPos
;
1348 curr
->rect
.top
= iRow
;
1349 if (infoPtr
->dwStyle
& TCS_BUTTONS
)
1351 curItemLeftPos
= curr
->rect
.right
+ 1;
1352 if (infoPtr
->dwStyle
& TCS_FLATBUTTONS
)
1353 curItemLeftPos
+= FLAT_BTN_SPACINGX
;
1356 curItemLeftPos
= curr
->rect
.right
;
1358 TRACE("arranging <%s>, rect %s\n", debugstr_w(curr
->pszText
), wine_dbgstr_rect(&curr
->rect
));
1365 INT widthDiff
, iIndexStart
=0, iIndexEnd
=0;
1369 while(iIndexStart
< infoPtr
->uNumItem
)
1371 TAB_ITEM
*start
= TAB_GetItem(infoPtr
, iIndexStart
);
1374 * find the index of the row
1376 /* find the first item on the next row */
1377 for (iIndexEnd
=iIndexStart
;
1378 (iIndexEnd
< infoPtr
->uNumItem
) &&
1379 (TAB_GetItem(infoPtr
, iIndexEnd
)->rect
.top
==
1382 /* intentionally blank */;
1385 * we need to justify these tabs so they fill the whole given
1389 /* find the amount of space remaining on this row */
1390 widthDiff
= clientRect
.right
- (2 * SELECTED_TAB_OFFSET
) -
1391 TAB_GetItem(infoPtr
, iIndexEnd
- 1)->rect
.right
;
1393 /* iCount is the number of tab items on this row */
1394 iCount
= iIndexEnd
- iIndexStart
;
1398 remainder
= widthDiff
% iCount
;
1399 widthDiff
= widthDiff
/ iCount
;
1400 /* add widthDiff/iCount, or extra space/items on row, to each item on this row */
1401 for (iIndex
=iIndexStart
, iCount
=0; iIndex
< iIndexEnd
; iIndex
++, iCount
++)
1403 TAB_ITEM
*item
= TAB_GetItem(infoPtr
, iIndex
);
1405 item
->rect
.left
+= iCount
* widthDiff
;
1406 item
->rect
.right
+= (iCount
+ 1) * widthDiff
;
1408 TRACE("adjusting 1 <%s>, rect %s\n", debugstr_w(item
->pszText
), wine_dbgstr_rect(&item
->rect
));
1411 TAB_GetItem(infoPtr
, iIndex
- 1)->rect
.right
+= remainder
;
1413 else /* we have only one item on this row, make it take up the entire row */
1415 start
->rect
.left
= clientRect
.left
;
1416 start
->rect
.right
= clientRect
.right
- 4;
1418 TRACE("adjusting 2 <%s>, rect %s\n", debugstr_w(start
->pszText
), wine_dbgstr_rect(&start
->rect
));
1421 iIndexStart
= iIndexEnd
;
1426 /* if TCS_VERTICAL rotate the tabs so they are along the side of the clientRect */
1427 if(infoPtr
->dwStyle
& TCS_VERTICAL
)
1430 for(iIndex
= 0; iIndex
< infoPtr
->uNumItem
; iIndex
++)
1432 rcItem
= &TAB_GetItem(infoPtr
, iIndex
)->rect
;
1434 rcOriginal
= *rcItem
;
1436 /* this is rotating the items by 90 degrees clockwise around the center of the control */
1437 rcItem
->top
= (rcOriginal
.left
- clientRect
.left
);
1438 rcItem
->bottom
= rcItem
->top
+ (rcOriginal
.right
- rcOriginal
.left
);
1439 rcItem
->left
= rcOriginal
.top
;
1440 rcItem
->right
= rcOriginal
.bottom
;
1444 TAB_EnsureSelectionVisible(infoPtr
);
1445 TAB_RecalcHotTrack(infoPtr
, NULL
, NULL
, NULL
);
1448 SelectObject (hdc
, hOldFont
);
1449 ReleaseDC (infoPtr
->hwnd
, hdc
);
1454 TAB_EraseTabInterior(const TAB_INFO
*infoPtr
, HDC hdc
, INT iItem
, const RECT
*drawRect
)
1456 HBRUSH hbr
= CreateSolidBrush (comctl32_color
.clrBtnFace
);
1457 BOOL deleteBrush
= TRUE
;
1458 RECT rTemp
= *drawRect
;
1460 if (infoPtr
->dwStyle
& TCS_BUTTONS
)
1462 if (iItem
== infoPtr
->iSelected
)
1464 /* Background color */
1465 if (!(infoPtr
->dwStyle
& TCS_OWNERDRAWFIXED
))
1468 hbr
= GetSysColorBrush(COLOR_SCROLLBAR
);
1470 SetTextColor(hdc
, comctl32_color
.clr3dFace
);
1471 SetBkColor(hdc
, comctl32_color
.clr3dHilight
);
1473 /* if COLOR_WINDOW happens to be the same as COLOR_3DHILIGHT
1474 * we better use 0x55aa bitmap brush to make scrollbar's background
1475 * look different from the window background.
1477 if (comctl32_color
.clr3dHilight
== comctl32_color
.clrWindow
)
1478 hbr
= COMCTL32_hPattern55AABrush
;
1480 deleteBrush
= FALSE
;
1482 FillRect(hdc
, &rTemp
, hbr
);
1484 else /* ! selected */
1486 if (infoPtr
->dwStyle
& TCS_FLATBUTTONS
)
1488 InflateRect(&rTemp
, 2, 2);
1489 FillRect(hdc
, &rTemp
, hbr
);
1490 if (iItem
== infoPtr
->iHotTracked
||
1491 (iItem
!= infoPtr
->iSelected
&& iItem
== infoPtr
->uFocus
))
1492 DrawEdge(hdc
, &rTemp
, BDR_RAISEDINNER
, BF_RECT
);
1495 FillRect(hdc
, &rTemp
, hbr
);
1499 else /* !TCS_BUTTONS */
1501 InflateRect(&rTemp
, -2, -2);
1502 if (!GetWindowTheme (infoPtr
->hwnd
))
1503 FillRect(hdc
, &rTemp
, hbr
);
1506 /* highlighting is drawn on top of previous fills */
1507 if (TAB_GetItem(infoPtr
, iItem
)->dwState
& TCIS_HIGHLIGHTED
)
1512 deleteBrush
= FALSE
;
1514 hbr
= GetSysColorBrush(COLOR_HIGHLIGHT
);
1515 FillRect(hdc
, &rTemp
, hbr
);
1519 if (deleteBrush
) DeleteObject(hbr
);
1522 /******************************************************************************
1523 * TAB_DrawItemInterior
1525 * This method is used to draw the interior (text and icon) of a single tab
1526 * into the tab control.
1529 TAB_DrawItemInterior(const TAB_INFO
*infoPtr
, HDC hdc
, INT iItem
, RECT
*drawRect
)
1538 /* if (drawRect == NULL) */
1545 * Get the rectangle for the item.
1547 isVisible
= TAB_InternalGetItemRect(infoPtr
, iItem
, &itemRect
, &selectedRect
);
1552 * Make sure drawRect points to something valid; simplifies code.
1554 drawRect
= &localRect
;
1557 * This logic copied from the part of TAB_DrawItem which draws
1558 * the tab background. It's important to keep it in sync. I
1559 * would have liked to avoid code duplication, but couldn't figure
1560 * out how without making spaghetti of TAB_DrawItem.
1562 if (iItem
== infoPtr
->iSelected
)
1563 *drawRect
= selectedRect
;
1565 *drawRect
= itemRect
;
1567 if (infoPtr
->dwStyle
& TCS_BUTTONS
)
1569 if (iItem
== infoPtr
->iSelected
)
1571 drawRect
->left
+= 4;
1573 drawRect
->right
-= 4;
1575 if (infoPtr
->dwStyle
& TCS_VERTICAL
)
1577 if (!(infoPtr
->dwStyle
& TCS_BOTTOM
)) drawRect
->right
+= 1;
1578 drawRect
->bottom
-= 4;
1582 if (infoPtr
->dwStyle
& TCS_BOTTOM
)
1585 drawRect
->bottom
-= 4;
1588 drawRect
->bottom
-= 1;
1592 InflateRect(drawRect
, -2, -2);
1596 if ((infoPtr
->dwStyle
& TCS_VERTICAL
) && (infoPtr
->dwStyle
& TCS_BOTTOM
))
1598 if (iItem
!= infoPtr
->iSelected
)
1600 drawRect
->left
+= 2;
1601 InflateRect(drawRect
, 0, -2);
1604 else if (infoPtr
->dwStyle
& TCS_VERTICAL
)
1606 if (iItem
== infoPtr
->iSelected
)
1608 drawRect
->right
+= 1;
1612 drawRect
->right
-= 2;
1613 InflateRect(drawRect
, 0, -2);
1616 else if (infoPtr
->dwStyle
& TCS_BOTTOM
)
1618 if (iItem
== infoPtr
->iSelected
)
1624 InflateRect(drawRect
, -2, -2);
1625 drawRect
->bottom
+= 2;
1630 if (iItem
== infoPtr
->iSelected
)
1632 drawRect
->bottom
+= 3;
1636 drawRect
->bottom
-= 2;
1637 InflateRect(drawRect
, -2, 0);
1642 TRACE("drawRect=(%s)\n", wine_dbgstr_rect(drawRect
));
1644 /* Clear interior */
1645 TAB_EraseTabInterior (infoPtr
, hdc
, iItem
, drawRect
);
1647 /* Draw the focus rectangle */
1648 if (!(infoPtr
->dwStyle
& TCS_FOCUSNEVER
) &&
1649 (GetFocus() == infoPtr
->hwnd
) &&
1650 (iItem
== infoPtr
->uFocus
) )
1652 RECT rFocus
= *drawRect
;
1654 if (!(infoPtr
->dwStyle
& TCS_BUTTONS
)) InflateRect(&rFocus
, -3, -3);
1655 if (infoPtr
->dwStyle
& TCS_BOTTOM
&& !(infoPtr
->dwStyle
& TCS_VERTICAL
))
1658 /* focus should stay on selected item for TCS_BUTTONS style */
1659 if (!((infoPtr
->dwStyle
& TCS_BUTTONS
) && (infoPtr
->iSelected
!= iItem
)))
1660 DrawFocusRect(hdc
, &rFocus
);
1666 htextPen
= CreatePen( PS_SOLID
, 1, comctl32_color
.clrBtnText
);
1667 holdPen
= SelectObject(hdc
, htextPen
);
1668 hOldFont
= SelectObject(hdc
, infoPtr
->hFont
);
1671 * Setup for text output
1673 oldBkMode
= SetBkMode(hdc
, TRANSPARENT
);
1674 if (!GetWindowTheme (infoPtr
->hwnd
) || (infoPtr
->dwStyle
& TCS_BUTTONS
))
1676 if ((infoPtr
->dwStyle
& TCS_HOTTRACK
) && (iItem
== infoPtr
->iHotTracked
) &&
1677 !(infoPtr
->dwStyle
& TCS_FLATBUTTONS
))
1678 SetTextColor(hdc
, comctl32_color
.clrHighlight
);
1679 else if (TAB_GetItem(infoPtr
, iItem
)->dwState
& TCIS_HIGHLIGHTED
)
1680 SetTextColor(hdc
, comctl32_color
.clrHighlightText
);
1682 SetTextColor(hdc
, comctl32_color
.clrBtnText
);
1686 * if owner draw, tell the owner to draw
1688 if ((infoPtr
->dwStyle
& TCS_OWNERDRAWFIXED
) && IsWindow(infoPtr
->hwndNotify
))
1694 drawRect
->right
-= 1;
1695 if ( iItem
== infoPtr
->iSelected
)
1696 InflateRect(drawRect
, -1, 0);
1698 id
= (UINT
)GetWindowLongPtrW( infoPtr
->hwnd
, GWLP_ID
);
1700 /* fill DRAWITEMSTRUCT */
1701 dis
.CtlType
= ODT_TAB
;
1704 dis
.itemAction
= ODA_DRAWENTIRE
;
1706 if ( iItem
== infoPtr
->iSelected
)
1707 dis
.itemState
|= ODS_SELECTED
;
1708 if (infoPtr
->uFocus
== iItem
)
1709 dis
.itemState
|= ODS_FOCUS
;
1710 dis
.hwndItem
= infoPtr
->hwnd
;
1712 dis
.rcItem
= *drawRect
;
1714 /* when extra data fits ULONG_PTR, store it directly */
1715 if (infoPtr
->cbInfo
> sizeof(LPARAM
))
1716 dis
.itemData
= (ULONG_PTR
) TAB_GetItem(infoPtr
, iItem
)->extra
;
1719 /* this could be considered broken on 64 bit, but that's how it works -
1720 only first 4 bytes are copied */
1722 memcpy(&dis
.itemData
, (ULONG_PTR
*)TAB_GetItem(infoPtr
, iItem
)->extra
, 4);
1725 /* draw notification */
1726 SendMessageW( infoPtr
->hwndNotify
, WM_DRAWITEM
, id
, (LPARAM
)&dis
);
1730 TAB_ITEM
*item
= TAB_GetItem(infoPtr
, iItem
);
1734 /* used to center the icon and text in the tab */
1736 INT center_offset_h
, center_offset_v
;
1738 /* set rcImage to drawRect, we will use top & left in our ImageList_Draw call */
1739 rcImage
= *drawRect
;
1742 SetRectEmpty(&rcText
);
1744 /* get the rectangle that the text fits in */
1747 DrawTextW(hdc
, item
->pszText
, -1, &rcText
, DT_CALCRECT
);
1750 * If not owner draw, then do the drawing ourselves.
1754 if (infoPtr
->himl
&& item
->iImage
!= -1)
1759 ImageList_GetIconSize(infoPtr
->himl
, &cx
, &cy
);
1761 if(infoPtr
->dwStyle
& TCS_VERTICAL
)
1763 center_offset_h
= ((drawRect
->bottom
- drawRect
->top
) - (cy
+ infoPtr
->uHItemPadding
+ (rcText
.right
- rcText
.left
))) / 2;
1764 center_offset_v
= ((drawRect
->right
- drawRect
->left
) - cx
) / 2;
1768 center_offset_h
= ((drawRect
->right
- drawRect
->left
) - (cx
+ infoPtr
->uHItemPadding
+ (rcText
.right
- rcText
.left
))) / 2;
1769 center_offset_v
= ((drawRect
->bottom
- drawRect
->top
) - cy
) / 2;
1772 /* if an item is selected, the icon is shifted up instead of down */
1773 if (iItem
== infoPtr
->iSelected
)
1774 center_offset_v
-= infoPtr
->uVItemPadding
/ 2;
1776 center_offset_v
+= infoPtr
->uVItemPadding
/ 2;
1778 if (infoPtr
->dwStyle
& TCS_FIXEDWIDTH
&& infoPtr
->dwStyle
& (TCS_FORCELABELLEFT
| TCS_FORCEICONLEFT
))
1779 center_offset_h
= infoPtr
->uHItemPadding
;
1781 if (center_offset_h
< 2)
1782 center_offset_h
= 2;
1784 if (center_offset_v
< 0)
1785 center_offset_v
= 0;
1787 TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%s), textlen=%d\n",
1788 debugstr_w(item
->pszText
), center_offset_h
, center_offset_v
,
1789 wine_dbgstr_rect(drawRect
), (rcText
.right
-rcText
.left
));
1791 if((infoPtr
->dwStyle
& TCS_VERTICAL
) && (infoPtr
->dwStyle
& TCS_BOTTOM
))
1793 rcImage
.top
= drawRect
->top
+ center_offset_h
;
1794 /* if tab is TCS_VERTICAL and TCS_BOTTOM, the text is drawn from the */
1795 /* right side of the tab, but the image still uses the left as its x position */
1796 /* this keeps the image always drawn off of the same side of the tab */
1797 rcImage
.left
= drawRect
->right
- cx
- center_offset_v
;
1798 drawRect
->top
+= cy
+ infoPtr
->uHItemPadding
;
1800 else if(infoPtr
->dwStyle
& TCS_VERTICAL
)
1802 rcImage
.top
= drawRect
->bottom
- cy
- center_offset_h
;
1803 rcImage
.left
= drawRect
->left
+ center_offset_v
;
1804 drawRect
->bottom
-= cy
+ infoPtr
->uHItemPadding
;
1806 else /* normal style, whether TCS_BOTTOM or not */
1808 rcImage
.left
= drawRect
->left
+ center_offset_h
;
1809 rcImage
.top
= drawRect
->top
+ center_offset_v
;
1810 drawRect
->left
+= cx
+ infoPtr
->uHItemPadding
;
1813 TRACE("drawing image=%d, left=%d, top=%d\n",
1814 item
->iImage
, rcImage
.left
, rcImage
.top
-1);
1826 /* Now position text */
1827 if (infoPtr
->dwStyle
& TCS_FIXEDWIDTH
&& infoPtr
->dwStyle
& TCS_FORCELABELLEFT
)
1828 center_offset_h
= infoPtr
->uHItemPadding
;
1830 if(infoPtr
->dwStyle
& TCS_VERTICAL
)
1831 center_offset_h
= ((drawRect
->bottom
- drawRect
->top
) - (rcText
.right
- rcText
.left
)) / 2;
1833 center_offset_h
= ((drawRect
->right
- drawRect
->left
) - (rcText
.right
- rcText
.left
)) / 2;
1835 if(infoPtr
->dwStyle
& TCS_VERTICAL
)
1837 if(infoPtr
->dwStyle
& TCS_BOTTOM
)
1838 drawRect
->top
+=center_offset_h
;
1840 drawRect
->bottom
-=center_offset_h
;
1842 center_offset_v
= ((drawRect
->right
- drawRect
->left
) - (rcText
.bottom
- rcText
.top
)) / 2;
1846 drawRect
->left
+= center_offset_h
;
1847 center_offset_v
= ((drawRect
->bottom
- drawRect
->top
) - (rcText
.bottom
- rcText
.top
)) / 2;
1850 /* if an item is selected, the text is shifted up instead of down */
1851 if (iItem
== infoPtr
->iSelected
)
1852 center_offset_v
-= infoPtr
->uVItemPadding
/ 2;
1854 center_offset_v
+= infoPtr
->uVItemPadding
/ 2;
1856 if (center_offset_v
< 0)
1857 center_offset_v
= 0;
1859 if(infoPtr
->dwStyle
& TCS_VERTICAL
)
1860 drawRect
->left
+= center_offset_v
;
1862 drawRect
->top
+= center_offset_v
;
1865 if(infoPtr
->dwStyle
& TCS_VERTICAL
) /* if we are vertical rotate the text and each character */
1869 INT nEscapement
= 900;
1870 INT nOrientation
= 900;
1872 if(infoPtr
->dwStyle
& TCS_BOTTOM
)
1875 nOrientation
= -900;
1878 /* to get a font with the escapement and orientation we are looking for, we need to */
1879 /* call CreateFontIndirect, which requires us to set the values of the logfont we pass in */
1880 if (!GetObjectW(infoPtr
->hFont
, sizeof(logfont
), &logfont
))
1881 GetObjectW(GetStockObject(DEFAULT_GUI_FONT
), sizeof(logfont
), &logfont
);
1883 logfont
.lfEscapement
= nEscapement
;
1884 logfont
.lfOrientation
= nOrientation
;
1885 hFont
= CreateFontIndirectW(&logfont
);
1886 SelectObject(hdc
, hFont
);
1891 (infoPtr
->dwStyle
& TCS_BOTTOM
) ? drawRect
->right
: drawRect
->left
,
1892 (!(infoPtr
->dwStyle
& TCS_BOTTOM
)) ? drawRect
->bottom
: drawRect
->top
,
1896 lstrlenW(item
->pszText
),
1900 DeleteObject(hFont
);
1904 TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%s), textlen=%d\n",
1905 debugstr_w(item
->pszText
), center_offset_h
, center_offset_v
,
1906 wine_dbgstr_rect(drawRect
), (rcText
.right
-rcText
.left
));
1913 lstrlenW(item
->pszText
),
1915 DT_LEFT
| DT_SINGLELINE
1920 *drawRect
= rcTemp
; /* restore drawRect */
1926 SelectObject(hdc
, hOldFont
);
1927 SetBkMode(hdc
, oldBkMode
);
1928 SelectObject(hdc
, holdPen
);
1929 DeleteObject( htextPen
);
1932 /******************************************************************************
1935 * This method is used to draw a single tab into the tab control.
1937 static void TAB_DrawItem(const TAB_INFO
*infoPtr
, HDC hdc
, INT iItem
)
1942 RECT r
, fillRect
, r1
;
1945 COLORREF bkgnd
, corner
;
1949 * Get the rectangle for the item.
1951 isVisible
= TAB_InternalGetItemRect(infoPtr
,
1960 /* Clip UpDown control to not draw over it */
1961 if (infoPtr
->needsScrolling
)
1963 GetWindowRect(infoPtr
->hwnd
, &rC
);
1964 GetWindowRect(infoPtr
->hwndUpDown
, &rUD
);
1965 ExcludeClipRect(hdc
, rUD
.left
- rC
.left
, rUD
.top
- rC
.top
, rUD
.right
- rC
.left
, rUD
.bottom
- rC
.top
);
1968 /* If you need to see what the control is doing,
1969 * then override these variables. They will change what
1970 * fill colors are used for filling the tabs, and the
1971 * corners when drawing the edge.
1973 bkgnd
= comctl32_color
.clrBtnFace
;
1974 corner
= comctl32_color
.clrBtnFace
;
1976 if (infoPtr
->dwStyle
& TCS_BUTTONS
)
1978 /* Get item rectangle */
1981 /* Separators between flat buttons */
1982 if ((infoPtr
->dwStyle
& TCS_FLATBUTTONS
) && (infoPtr
->exStyle
& TCS_EX_FLATSEPARATORS
))
1985 r1
.right
+= (FLAT_BTN_SPACINGX
-2);
1986 DrawEdge(hdc
, &r1
, EDGE_ETCHED
, BF_RIGHT
);
1989 if (iItem
== infoPtr
->iSelected
)
1991 DrawEdge(hdc
, &r
, EDGE_SUNKEN
, BF_SOFT
|BF_RECT
);
1993 OffsetRect(&r
, 1, 1);
1995 else /* ! selected */
1997 DWORD state
= TAB_GetItem(infoPtr
, iItem
)->dwState
;
1999 if ((state
& TCIS_BUTTONPRESSED
) || (iItem
== infoPtr
->uFocus
))
2000 DrawEdge(hdc
, &r
, EDGE_SUNKEN
, BF_SOFT
|BF_RECT
);
2002 if (!(infoPtr
->dwStyle
& TCS_FLATBUTTONS
))
2003 DrawEdge(hdc
, &r
, EDGE_RAISED
, BF_SOFT
|BF_RECT
);
2006 else /* !TCS_BUTTONS */
2008 /* We draw a rectangle of different sizes depending on the selection
2010 if (iItem
== infoPtr
->iSelected
) {
2012 GetClientRect (infoPtr
->hwnd
, &rect
);
2013 clRight
= rect
.right
;
2014 clBottom
= rect
.bottom
;
2021 * Erase the background. (Delay it but setup rectangle.)
2022 * This is necessary when drawing the selected item since it is larger
2023 * than the others, it might overlap with stuff already drawn by the
2028 /* Draw themed tabs - but only if they are at the top.
2029 * Windows draws even side or bottom tabs themed, with wacky results.
2030 * However, since in Wine apps may get themed that did not opt in via
2031 * a manifest avoid theming when we know the result will be wrong */
2032 if ((theme
= GetWindowTheme (infoPtr
->hwnd
))
2033 && ((infoPtr
->dwStyle
& (TCS_VERTICAL
| TCS_BOTTOM
)) == 0))
2035 static const int partIds
[8] = {
2038 TABP_TABITEMLEFTEDGE
,
2039 TABP_TABITEMRIGHTEDGE
,
2040 TABP_TABITEMBOTHEDGE
,
2043 TABP_TOPTABITEMLEFTEDGE
,
2044 TABP_TOPTABITEMRIGHTEDGE
,
2045 TABP_TOPTABITEMBOTHEDGE
,
2048 int stateId
= TIS_NORMAL
;
2050 /* selected and unselected tabs have different parts */
2051 if (iItem
== infoPtr
->iSelected
)
2053 /* The part also differs on the position of a tab on a line.
2054 * "Visually" determining the position works well enough. */
2055 GetClientRect(infoPtr
->hwnd
, &r1
);
2056 if(selectedRect
.left
== 0)
2058 if(selectedRect
.right
== r1
.right
)
2061 if (iItem
== infoPtr
->iSelected
)
2062 stateId
= TIS_SELECTED
;
2063 else if (iItem
== infoPtr
->iHotTracked
)
2065 else if (iItem
== infoPtr
->uFocus
)
2066 stateId
= TIS_FOCUSED
;
2068 /* Adjust rectangle for bottommost row */
2069 if (TAB_GetItem(infoPtr
, iItem
)->rect
.top
== infoPtr
->uNumRows
-1)
2072 DrawThemeBackground (theme
, hdc
, partIds
[partIndex
], stateId
, &r
, NULL
);
2073 GetThemeBackgroundContentRect (theme
, hdc
, partIds
[partIndex
], stateId
, &r
, &r
);
2075 else if(infoPtr
->dwStyle
& TCS_VERTICAL
)
2077 /* These are for adjusting the drawing of a Selected tab */
2078 /* The initial values are for the normal case of non-Selected */
2079 int ZZ
= 1; /* Do not stretch if selected */
2080 if (iItem
== infoPtr
->iSelected
) {
2083 /* if leftmost draw the line longer */
2084 if(selectedRect
.top
== 0)
2085 fillRect
.top
+= CONTROL_BORDER_SIZEY
;
2086 /* if rightmost draw the line longer */
2087 if(selectedRect
.bottom
== clBottom
)
2088 fillRect
.bottom
-= CONTROL_BORDER_SIZEY
;
2091 if (infoPtr
->dwStyle
& TCS_BOTTOM
)
2093 /* Adjust both rectangles to match native */
2096 TRACE("<right> item=%d, fill=(%s), edge=(%s)\n",
2097 iItem
, wine_dbgstr_rect(&fillRect
), wine_dbgstr_rect(&r
));
2099 /* Clear interior */
2100 SetBkColor(hdc
, bkgnd
);
2101 ExtTextOutW(hdc
, 0, 0, 2, &fillRect
, NULL
, 0, 0);
2103 /* Draw rectangular edge around tab */
2104 DrawEdge(hdc
, &r
, EDGE_RAISED
, BF_SOFT
|BF_RIGHT
|BF_TOP
|BF_BOTTOM
);
2106 /* Now erase the top corner and draw diagonal edge */
2107 SetBkColor(hdc
, corner
);
2108 r1
.left
= r
.right
- ROUND_CORNER_SIZE
- 1;
2111 r1
.bottom
= r1
.top
+ ROUND_CORNER_SIZE
;
2112 ExtTextOutW(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2114 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_DIAGONAL_ENDTOPLEFT
);
2116 /* Now erase the bottom corner and draw diagonal edge */
2117 r1
.left
= r
.right
- ROUND_CORNER_SIZE
- 1;
2118 r1
.bottom
= r
.bottom
;
2120 r1
.top
= r1
.bottom
- ROUND_CORNER_SIZE
;
2121 ExtTextOutW(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2123 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_DIAGONAL_ENDBOTTOMLEFT
);
2125 if ((iItem
== infoPtr
->iSelected
) && (selectedRect
.top
== 0)) {
2129 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_TOP
);
2135 TRACE("<left> item=%d, fill=(%s), edge=(%s)\n",
2136 iItem
, wine_dbgstr_rect(&fillRect
), wine_dbgstr_rect(&r
));
2138 /* Clear interior */
2139 SetBkColor(hdc
, bkgnd
);
2140 ExtTextOutW(hdc
, 0, 0, 2, &fillRect
, NULL
, 0, 0);
2142 /* Draw rectangular edge around tab */
2143 DrawEdge(hdc
, &r
, EDGE_RAISED
, BF_SOFT
|BF_LEFT
|BF_TOP
|BF_BOTTOM
);
2145 /* Now erase the top corner and draw diagonal edge */
2146 SetBkColor(hdc
, corner
);
2149 r1
.right
= r1
.left
+ ROUND_CORNER_SIZE
+ 1;
2150 r1
.bottom
= r1
.top
+ ROUND_CORNER_SIZE
;
2151 ExtTextOutW(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2153 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_DIAGONAL_ENDTOPRIGHT
);
2155 /* Now erase the bottom corner and draw diagonal edge */
2157 r1
.bottom
= r
.bottom
;
2158 r1
.right
= r1
.left
+ ROUND_CORNER_SIZE
+ 1;
2159 r1
.top
= r1
.bottom
- ROUND_CORNER_SIZE
;
2160 ExtTextOutW(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2162 DrawEdge(hdc
, &r1
, EDGE_SUNKEN
, BF_DIAGONAL_ENDTOPLEFT
);
2165 else /* ! TCS_VERTICAL */
2167 /* These are for adjusting the drawing of a Selected tab */
2168 /* The initial values are for the normal case of non-Selected */
2169 if (iItem
== infoPtr
->iSelected
) {
2170 /* if leftmost draw the line longer */
2171 if(selectedRect
.left
== 0)
2172 fillRect
.left
+= CONTROL_BORDER_SIZEX
;
2173 /* if rightmost draw the line longer */
2174 if(selectedRect
.right
== clRight
)
2175 fillRect
.right
-= CONTROL_BORDER_SIZEX
;
2178 if (infoPtr
->dwStyle
& TCS_BOTTOM
)
2180 /* Adjust both rectangles for topmost row */
2181 if (TAB_GetItem(infoPtr
, iItem
)->rect
.top
== infoPtr
->uNumRows
-1)
2187 TRACE("<bottom> item=%d, fill=(%s), edge=(%s)\n",
2188 iItem
, wine_dbgstr_rect(&fillRect
), wine_dbgstr_rect(&r
));
2190 /* Clear interior */
2191 SetBkColor(hdc
, bkgnd
);
2192 ExtTextOutW(hdc
, 0, 0, 2, &fillRect
, NULL
, 0, 0);
2194 /* Draw rectangular edge around tab */
2195 DrawEdge(hdc
, &r
, EDGE_RAISED
, BF_SOFT
|BF_LEFT
|BF_BOTTOM
|BF_RIGHT
);
2197 /* Now erase the righthand corner and draw diagonal edge */
2198 SetBkColor(hdc
, corner
);
2199 r1
.left
= r
.right
- ROUND_CORNER_SIZE
;
2200 r1
.bottom
= r
.bottom
;
2202 r1
.top
= r1
.bottom
- ROUND_CORNER_SIZE
- 1;
2203 ExtTextOutW(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2205 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_DIAGONAL_ENDBOTTOMLEFT
);
2207 /* Now erase the lefthand corner and draw diagonal edge */
2209 r1
.bottom
= r
.bottom
;
2210 r1
.right
= r1
.left
+ ROUND_CORNER_SIZE
;
2211 r1
.top
= r1
.bottom
- ROUND_CORNER_SIZE
- 1;
2212 ExtTextOutW(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2214 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_DIAGONAL_ENDTOPLEFT
);
2216 if (iItem
== infoPtr
->iSelected
)
2220 if (selectedRect
.left
== 0)
2225 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_LEFT
);
2232 /* Adjust both rectangles for bottommost row */
2233 if (TAB_GetItem(infoPtr
, iItem
)->rect
.top
== infoPtr
->uNumRows
-1)
2235 fillRect
.bottom
+= 3;
2239 TRACE("<top> item=%d, fill=(%s), edge=(%s)\n",
2240 iItem
, wine_dbgstr_rect(&fillRect
), wine_dbgstr_rect(&r
));
2242 /* Clear interior */
2243 SetBkColor(hdc
, bkgnd
);
2244 ExtTextOutW(hdc
, 0, 0, 2, &fillRect
, NULL
, 0, 0);
2246 /* Draw rectangular edge around tab */
2247 DrawEdge(hdc
, &r
, EDGE_RAISED
, BF_SOFT
|BF_LEFT
|BF_TOP
|BF_RIGHT
);
2249 /* Now erase the righthand corner and draw diagonal edge */
2250 SetBkColor(hdc
, corner
);
2251 r1
.left
= r
.right
- ROUND_CORNER_SIZE
;
2254 r1
.bottom
= r1
.top
+ ROUND_CORNER_SIZE
+ 1;
2255 ExtTextOutW(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2257 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_DIAGONAL_ENDBOTTOMRIGHT
);
2259 /* Now erase the lefthand corner and draw diagonal edge */
2262 r1
.right
= r1
.left
+ ROUND_CORNER_SIZE
;
2263 r1
.bottom
= r1
.top
+ ROUND_CORNER_SIZE
+ 1;
2264 ExtTextOutW(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2266 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_DIAGONAL_ENDTOPRIGHT
);
2271 TAB_DumpItemInternal(infoPtr
, iItem
);
2273 /* This modifies r to be the text rectangle. */
2274 TAB_DrawItemInterior(infoPtr
, hdc
, iItem
, &r
);
2278 /******************************************************************************
2281 * This method is used to draw the raised border around the tab control
2284 static void TAB_DrawBorder(const TAB_INFO
*infoPtr
, HDC hdc
)
2287 HTHEME theme
= GetWindowTheme (infoPtr
->hwnd
);
2289 GetClientRect (infoPtr
->hwnd
, &rect
);
2292 * Adjust for the style
2295 if (infoPtr
->uNumItem
)
2297 if ((infoPtr
->dwStyle
& TCS_BOTTOM
) && !(infoPtr
->dwStyle
& TCS_VERTICAL
))
2298 rect
.bottom
-= infoPtr
->tabHeight
* infoPtr
->uNumRows
+ CONTROL_BORDER_SIZEX
;
2299 else if((infoPtr
->dwStyle
& TCS_BOTTOM
) && (infoPtr
->dwStyle
& TCS_VERTICAL
))
2300 rect
.right
-= infoPtr
->tabHeight
* infoPtr
->uNumRows
+ CONTROL_BORDER_SIZEX
;
2301 else if(infoPtr
->dwStyle
& TCS_VERTICAL
)
2302 rect
.left
+= infoPtr
->tabHeight
* infoPtr
->uNumRows
+ CONTROL_BORDER_SIZEX
;
2303 else /* not TCS_VERTICAL and not TCS_BOTTOM */
2304 rect
.top
+= infoPtr
->tabHeight
* infoPtr
->uNumRows
+ CONTROL_BORDER_SIZEX
;
2307 TRACE("border=(%s)\n", wine_dbgstr_rect(&rect
));
2310 DrawThemeBackground (theme
, hdc
, TABP_PANE
, 0, &rect
, NULL
);
2312 DrawEdge(hdc
, &rect
, EDGE_RAISED
, BF_SOFT
|BF_RECT
);
2315 /******************************************************************************
2318 * This method repaints the tab control..
2320 static void TAB_Refresh (const TAB_INFO
*infoPtr
, HDC hdc
)
2325 if (!infoPtr
->DoRedraw
)
2328 hOldFont
= SelectObject (hdc
, infoPtr
->hFont
);
2330 if (infoPtr
->dwStyle
& TCS_BUTTONS
)
2332 for (i
= 0; i
< infoPtr
->uNumItem
; i
++)
2333 TAB_DrawItem (infoPtr
, hdc
, i
);
2337 /* Draw all the non selected item first */
2338 for (i
= 0; i
< infoPtr
->uNumItem
; i
++)
2340 if (i
!= infoPtr
->iSelected
)
2341 TAB_DrawItem (infoPtr
, hdc
, i
);
2344 /* Now, draw the border, draw it before the selected item
2345 * since the selected item overwrites part of the border. */
2346 TAB_DrawBorder (infoPtr
, hdc
);
2348 /* Then, draw the selected item */
2349 TAB_DrawItem (infoPtr
, hdc
, infoPtr
->iSelected
);
2352 SelectObject (hdc
, hOldFont
);
2355 static inline DWORD
TAB_GetRowCount (const TAB_INFO
*infoPtr
)
2357 TRACE("(%p)\n", infoPtr
);
2358 return infoPtr
->uNumRows
;
2361 static inline LRESULT
TAB_SetRedraw (TAB_INFO
*infoPtr
, BOOL doRedraw
)
2363 infoPtr
->DoRedraw
= doRedraw
;
2367 /******************************************************************************
2368 * TAB_EnsureSelectionVisible
2370 * This method will make sure that the current selection is completely
2371 * visible by scrolling until it is.
2373 static void TAB_EnsureSelectionVisible(
2376 INT iSelected
= infoPtr
->iSelected
;
2377 INT iOrigLeftmostVisible
= infoPtr
->leftmostVisible
;
2382 /* set the items row to the bottommost row or topmost row depending on
2384 if ((infoPtr
->uNumRows
> 1) && !(infoPtr
->dwStyle
& TCS_BUTTONS
))
2386 TAB_ITEM
*selected
= TAB_GetItem(infoPtr
, iSelected
);
2390 if(infoPtr
->dwStyle
& TCS_VERTICAL
)
2391 newselected
= selected
->rect
.left
;
2393 newselected
= selected
->rect
.top
;
2395 /* the target row is always (number of rows - 1)
2396 as row 0 is furthest from the clientRect */
2397 iTargetRow
= infoPtr
->uNumRows
- 1;
2399 if (newselected
!= iTargetRow
)
2402 if(infoPtr
->dwStyle
& TCS_VERTICAL
)
2404 for (i
=0; i
< infoPtr
->uNumItem
; i
++)
2406 /* move everything in the row of the selected item to the iTargetRow */
2407 TAB_ITEM
*item
= TAB_GetItem(infoPtr
, i
);
2409 if (item
->rect
.left
== newselected
)
2410 item
->rect
.left
= iTargetRow
;
2413 if (item
->rect
.left
> newselected
)
2420 for (i
=0; i
< infoPtr
->uNumItem
; i
++)
2422 TAB_ITEM
*item
= TAB_GetItem(infoPtr
, i
);
2424 if (item
->rect
.top
== newselected
)
2425 item
->rect
.top
= iTargetRow
;
2428 if (item
->rect
.top
> newselected
)
2433 TAB_RecalcHotTrack(infoPtr
, NULL
, NULL
, NULL
);
2438 * Do the trivial cases first.
2440 if ( (!infoPtr
->needsScrolling
) ||
2441 (infoPtr
->hwndUpDown
==0) || (infoPtr
->dwStyle
& TCS_VERTICAL
))
2444 if (infoPtr
->leftmostVisible
>= iSelected
)
2446 infoPtr
->leftmostVisible
= iSelected
;
2450 TAB_ITEM
*selected
= TAB_GetItem(infoPtr
, iSelected
);
2455 /* Calculate the part of the client area that is visible */
2456 GetClientRect(infoPtr
->hwnd
, &r
);
2459 GetClientRect(infoPtr
->hwndUpDown
, &r
);
2462 if ((selected
->rect
.right
-
2463 selected
->rect
.left
) >= width
)
2465 /* Special case: width of selected item is greater than visible
2468 infoPtr
->leftmostVisible
= iSelected
;
2472 for (i
= infoPtr
->leftmostVisible
; i
< infoPtr
->uNumItem
; i
++)
2474 if ((selected
->rect
.right
- TAB_GetItem(infoPtr
, i
)->rect
.left
) < width
)
2477 infoPtr
->leftmostVisible
= i
;
2481 if (infoPtr
->leftmostVisible
!= iOrigLeftmostVisible
)
2482 TAB_RecalcHotTrack(infoPtr
, NULL
, NULL
, NULL
);
2484 SendMessageW(infoPtr
->hwndUpDown
, UDM_SETPOS
, 0,
2485 MAKELONG(infoPtr
->leftmostVisible
, 0));
2488 /******************************************************************************
2489 * TAB_InvalidateTabArea
2491 * This method will invalidate the portion of the control that contains the
2492 * tabs. It is called when the state of the control changes and needs
2495 static void TAB_InvalidateTabArea(const TAB_INFO
*infoPtr
)
2497 RECT clientRect
, rInvalidate
, rAdjClient
;
2498 INT lastRow
= infoPtr
->uNumRows
- 1;
2501 if (lastRow
< 0) return;
2503 GetClientRect(infoPtr
->hwnd
, &clientRect
);
2504 rInvalidate
= clientRect
;
2505 rAdjClient
= clientRect
;
2507 TAB_AdjustRect(infoPtr
, 0, &rAdjClient
);
2509 TAB_InternalGetItemRect(infoPtr
, infoPtr
->uNumItem
-1 , &rect
, NULL
);
2510 if ((infoPtr
->dwStyle
& TCS_BOTTOM
) && (infoPtr
->dwStyle
& TCS_VERTICAL
))
2512 rInvalidate
.left
= rAdjClient
.right
;
2513 if (infoPtr
->uNumRows
== 1)
2514 rInvalidate
.bottom
= clientRect
.top
+ rect
.bottom
+ 2 * SELECTED_TAB_OFFSET
;
2516 else if(infoPtr
->dwStyle
& TCS_VERTICAL
)
2518 rInvalidate
.right
= rAdjClient
.left
;
2519 if (infoPtr
->uNumRows
== 1)
2520 rInvalidate
.bottom
= clientRect
.top
+ rect
.bottom
+ 2 * SELECTED_TAB_OFFSET
;
2522 else if (infoPtr
->dwStyle
& TCS_BOTTOM
)
2524 rInvalidate
.top
= rAdjClient
.bottom
;
2525 if (infoPtr
->uNumRows
== 1)
2526 rInvalidate
.right
= clientRect
.left
+ rect
.right
+ 2 * SELECTED_TAB_OFFSET
;
2530 rInvalidate
.bottom
= rAdjClient
.top
;
2531 if (infoPtr
->uNumRows
== 1)
2532 rInvalidate
.right
= clientRect
.left
+ rect
.right
+ 2 * SELECTED_TAB_OFFSET
;
2535 /* Punch out the updown control */
2536 if (infoPtr
->needsScrolling
&& (rInvalidate
.right
> 0)) {
2538 GetClientRect(infoPtr
->hwndUpDown
, &r
);
2539 if (rInvalidate
.right
> clientRect
.right
- r
.left
)
2540 rInvalidate
.right
= rInvalidate
.right
- (r
.right
- r
.left
);
2542 rInvalidate
.right
= clientRect
.right
- r
.left
;
2545 TRACE("invalidate (%s)\n", wine_dbgstr_rect(&rInvalidate
));
2547 InvalidateRect(infoPtr
->hwnd
, &rInvalidate
, TRUE
);
2550 static inline LRESULT
TAB_Paint (TAB_INFO
*infoPtr
, HDC hdcPaint
)
2559 hdc
= BeginPaint (infoPtr
->hwnd
, &ps
);
2560 TRACE("erase %d, rect=(%s)\n", ps
.fErase
, wine_dbgstr_rect(&ps
.rcPaint
));
2563 TAB_Refresh (infoPtr
, hdc
);
2566 EndPaint (infoPtr
->hwnd
, &ps
);
2572 TAB_InsertItemT (TAB_INFO
*infoPtr
, INT iItem
, const TCITEMW
*pti
, BOOL bUnicode
)
2577 GetClientRect (infoPtr
->hwnd
, &rect
);
2578 TRACE("Rect: %p %s\n", infoPtr
->hwnd
, wine_dbgstr_rect(&rect
));
2580 if (iItem
< 0) return -1;
2581 if (iItem
> infoPtr
->uNumItem
)
2582 iItem
= infoPtr
->uNumItem
;
2584 TAB_DumpItemExternalT(pti
, iItem
, bUnicode
);
2586 if (!(item
= Alloc(TAB_ITEM_SIZE(infoPtr
)))) return FALSE
;
2587 if (DPA_InsertPtr(infoPtr
->items
, iItem
, item
) == -1)
2593 if (infoPtr
->uNumItem
== 0)
2594 infoPtr
->iSelected
= 0;
2595 else if (iItem
<= infoPtr
->iSelected
)
2596 infoPtr
->iSelected
++;
2598 infoPtr
->uNumItem
++;
2600 item
->pszText
= NULL
;
2601 if (pti
->mask
& TCIF_TEXT
)
2604 Str_SetPtrW (&item
->pszText
, pti
->pszText
);
2606 Str_SetPtrAtoW (&item
->pszText
, (LPSTR
)pti
->pszText
);
2609 if (pti
->mask
& TCIF_IMAGE
)
2610 item
->iImage
= pti
->iImage
;
2614 if (pti
->mask
& TCIF_PARAM
)
2615 memcpy(item
->extra
, &pti
->lParam
, EXTRA_ITEM_SIZE(infoPtr
));
2617 memset(item
->extra
, 0, EXTRA_ITEM_SIZE(infoPtr
));
2619 TAB_SetItemBounds(infoPtr
);
2620 if (infoPtr
->uNumItem
> 1)
2621 TAB_InvalidateTabArea(infoPtr
);
2623 InvalidateRect(infoPtr
->hwnd
, NULL
, TRUE
);
2625 TRACE("[%p]: added item %d %s\n",
2626 infoPtr
->hwnd
, iItem
, debugstr_w(item
->pszText
));
2628 /* If we haven't set the current focus yet, set it now. */
2629 if (infoPtr
->uFocus
== -1)
2630 TAB_SetCurFocus(infoPtr
, iItem
);
2636 TAB_SetItemSize (TAB_INFO
*infoPtr
, INT cx
, INT cy
)
2639 BOOL bNeedPaint
= FALSE
;
2641 lResult
= MAKELONG(infoPtr
->tabWidth
, infoPtr
->tabHeight
);
2643 /* UNDOCUMENTED: If requested Width or Height is 0 this means that program wants to use auto size. */
2644 if (infoPtr
->dwStyle
& TCS_FIXEDWIDTH
&& (infoPtr
->tabWidth
!= cx
))
2646 infoPtr
->tabWidth
= cx
;
2650 if (infoPtr
->tabHeight
!= cy
)
2652 if ((infoPtr
->fHeightSet
= (cy
!= 0)))
2653 infoPtr
->tabHeight
= cy
;
2657 TRACE("was h=%d,w=%d, now h=%d,w=%d\n",
2658 HIWORD(lResult
), LOWORD(lResult
),
2659 infoPtr
->tabHeight
, infoPtr
->tabWidth
);
2663 TAB_SetItemBounds(infoPtr
);
2664 RedrawWindow(infoPtr
->hwnd
, NULL
, NULL
, RDW_ERASE
| RDW_INVALIDATE
| RDW_UPDATENOW
);
2670 static inline LRESULT
TAB_SetMinTabWidth (TAB_INFO
*infoPtr
, INT cx
)
2674 TRACE("(%p,%d)\n", infoPtr
, cx
);
2676 if (infoPtr
->tabMinWidth
< 0)
2677 oldcx
= DEFAULT_MIN_TAB_WIDTH
;
2679 oldcx
= infoPtr
->tabMinWidth
;
2680 infoPtr
->tabMinWidth
= cx
;
2681 TAB_SetItemBounds(infoPtr
);
2685 static inline LRESULT
2686 TAB_HighlightItem (TAB_INFO
*infoPtr
, INT iItem
, BOOL fHighlight
)
2692 TRACE("(%p,%d,%s)\n", infoPtr
, iItem
, fHighlight
? "true" : "false");
2694 if (iItem
< 0 || iItem
>= infoPtr
->uNumItem
)
2697 lpState
= &TAB_GetItem(infoPtr
, iItem
)->dwState
;
2698 oldState
= *lpState
;
2701 *lpState
|= TCIS_HIGHLIGHTED
;
2703 *lpState
&= ~TCIS_HIGHLIGHTED
;
2705 if ((oldState
!= *lpState
) && TAB_InternalGetItemRect (infoPtr
, iItem
, &r
, NULL
))
2706 InvalidateRect (infoPtr
->hwnd
, &r
, TRUE
);
2712 TAB_SetItemT (TAB_INFO
*infoPtr
, INT iItem
, LPTCITEMW tabItem
, BOOL bUnicode
)
2716 TRACE("(%p,%d,%p,%s)\n", infoPtr
, iItem
, tabItem
, bUnicode
? "true" : "false");
2718 if (iItem
< 0 || iItem
>= infoPtr
->uNumItem
)
2721 TAB_DumpItemExternalT(tabItem
, iItem
, bUnicode
);
2723 wineItem
= TAB_GetItem(infoPtr
, iItem
);
2725 if (tabItem
->mask
& TCIF_IMAGE
)
2726 wineItem
->iImage
= tabItem
->iImage
;
2728 if (tabItem
->mask
& TCIF_PARAM
)
2729 memcpy(wineItem
->extra
, &tabItem
->lParam
, infoPtr
->cbInfo
);
2731 if (tabItem
->mask
& TCIF_RTLREADING
)
2732 FIXME("TCIF_RTLREADING\n");
2734 if (tabItem
->mask
& TCIF_STATE
)
2735 wineItem
->dwState
= (wineItem
->dwState
& ~tabItem
->dwStateMask
) |
2736 ( tabItem
->dwState
& tabItem
->dwStateMask
);
2738 if (tabItem
->mask
& TCIF_TEXT
)
2740 Free(wineItem
->pszText
);
2741 wineItem
->pszText
= NULL
;
2743 Str_SetPtrW(&wineItem
->pszText
, tabItem
->pszText
);
2745 Str_SetPtrAtoW(&wineItem
->pszText
, (LPSTR
)tabItem
->pszText
);
2748 /* Update and repaint tabs */
2749 TAB_SetItemBounds(infoPtr
);
2750 TAB_InvalidateTabArea(infoPtr
);
2755 static inline LRESULT
TAB_GetItemCount (const TAB_INFO
*infoPtr
)
2758 return infoPtr
->uNumItem
;
2763 TAB_GetItemT (TAB_INFO
*infoPtr
, INT iItem
, LPTCITEMW tabItem
, BOOL bUnicode
)
2767 TRACE("(%p,%d,%p,%s)\n", infoPtr
, iItem
, tabItem
, bUnicode
? "true" : "false");
2769 if (!tabItem
) return FALSE
;
2771 if (iItem
< 0 || iItem
>= infoPtr
->uNumItem
)
2773 /* init requested fields */
2774 if (tabItem
->mask
& TCIF_IMAGE
) tabItem
->iImage
= 0;
2775 if (tabItem
->mask
& TCIF_PARAM
) tabItem
->lParam
= 0;
2776 if (tabItem
->mask
& TCIF_STATE
) tabItem
->dwState
= 0;
2780 wineItem
= TAB_GetItem(infoPtr
, iItem
);
2782 if (tabItem
->mask
& TCIF_IMAGE
)
2783 tabItem
->iImage
= wineItem
->iImage
;
2785 if (tabItem
->mask
& TCIF_PARAM
)
2786 memcpy(&tabItem
->lParam
, wineItem
->extra
, infoPtr
->cbInfo
);
2788 if (tabItem
->mask
& TCIF_RTLREADING
)
2789 FIXME("TCIF_RTLREADING\n");
2791 if (tabItem
->mask
& TCIF_STATE
)
2792 tabItem
->dwState
= wineItem
->dwState
& tabItem
->dwStateMask
;
2794 if (tabItem
->mask
& TCIF_TEXT
)
2797 Str_GetPtrW (wineItem
->pszText
, tabItem
->pszText
, tabItem
->cchTextMax
);
2799 Str_GetPtrWtoA (wineItem
->pszText
, (LPSTR
)tabItem
->pszText
, tabItem
->cchTextMax
);
2802 TAB_DumpItemExternalT(tabItem
, iItem
, bUnicode
);
2808 static LRESULT
TAB_DeleteItem (TAB_INFO
*infoPtr
, INT iItem
)
2812 TRACE("(%p, %d)\n", infoPtr
, iItem
);
2814 if (iItem
< 0 || iItem
>= infoPtr
->uNumItem
) return FALSE
;
2816 TAB_InvalidateTabArea(infoPtr
);
2817 item
= TAB_GetItem(infoPtr
, iItem
);
2818 Free(item
->pszText
);
2820 infoPtr
->uNumItem
--;
2821 DPA_DeletePtr(infoPtr
->items
, iItem
);
2823 if (infoPtr
->uNumItem
== 0)
2825 if (infoPtr
->iHotTracked
>= 0)
2827 KillTimer(infoPtr
->hwnd
, TAB_HOTTRACK_TIMER
);
2828 infoPtr
->iHotTracked
= -1;
2831 infoPtr
->iSelected
= -1;
2835 if (iItem
<= infoPtr
->iHotTracked
)
2837 /* When tabs move left/up, the hot track item may change */
2838 FIXME("Recalc hot track\n");
2842 /* adjust the selected index */
2843 if (iItem
== infoPtr
->iSelected
)
2844 infoPtr
->iSelected
= -1;
2845 else if (iItem
< infoPtr
->iSelected
)
2846 infoPtr
->iSelected
--;
2848 /* reposition and repaint tabs */
2849 TAB_SetItemBounds(infoPtr
);
2854 static inline LRESULT
TAB_DeleteAllItems (TAB_INFO
*infoPtr
)
2856 TRACE("(%p)\n", infoPtr
);
2857 while (infoPtr
->uNumItem
)
2858 TAB_DeleteItem (infoPtr
, 0);
2863 static inline LRESULT
TAB_GetFont (const TAB_INFO
*infoPtr
)
2865 TRACE("(%p) returning %p\n", infoPtr
, infoPtr
->hFont
);
2866 return (LRESULT
)infoPtr
->hFont
;
2869 static inline LRESULT
TAB_SetFont (TAB_INFO
*infoPtr
, HFONT hNewFont
)
2871 TRACE("(%p,%p)\n", infoPtr
, hNewFont
);
2873 infoPtr
->hFont
= hNewFont
;
2875 TAB_SetItemBounds(infoPtr
);
2877 TAB_InvalidateTabArea(infoPtr
);
2883 static inline LRESULT
TAB_GetImageList (const TAB_INFO
*infoPtr
)
2886 return (LRESULT
)infoPtr
->himl
;
2889 static inline LRESULT
TAB_SetImageList (TAB_INFO
*infoPtr
, HIMAGELIST himlNew
)
2891 HIMAGELIST himlPrev
= infoPtr
->himl
;
2892 TRACE("himl=%p\n", himlNew
);
2893 infoPtr
->himl
= himlNew
;
2894 TAB_SetItemBounds(infoPtr
);
2895 InvalidateRect(infoPtr
->hwnd
, NULL
, TRUE
);
2896 return (LRESULT
)himlPrev
;
2899 static inline LRESULT
TAB_GetUnicodeFormat (const TAB_INFO
*infoPtr
)
2901 TRACE("(%p)\n", infoPtr
);
2902 return infoPtr
->bUnicode
;
2905 static inline LRESULT
TAB_SetUnicodeFormat (TAB_INFO
*infoPtr
, BOOL bUnicode
)
2907 BOOL bTemp
= infoPtr
->bUnicode
;
2909 TRACE("(%p %d)\n", infoPtr
, bUnicode
);
2910 infoPtr
->bUnicode
= bUnicode
;
2915 static inline LRESULT
TAB_Size (TAB_INFO
*infoPtr
)
2917 /* I'm not really sure what the following code was meant to do.
2918 This is what it is doing:
2919 When WM_SIZE is sent with SIZE_RESTORED, the control
2920 gets positioned in the top left corner.
2924 UINT uPosFlags,cx,cy;
2928 parent = GetParent (hwnd);
2929 GetClientRect(parent, &parent_rect);
2932 if (GetWindowLongW(hwnd, GWL_STYLE) & CCS_NORESIZE)
2933 uPosFlags |= (SWP_NOSIZE | SWP_NOMOVE);
2935 SetWindowPos (hwnd, 0, parent_rect.left, parent_rect.top,
2936 cx, cy, uPosFlags | SWP_NOZORDER);
2938 FIXME("WM_SIZE flag %x %lx not handled\n", wParam, lParam);
2941 /* Recompute the size/position of the tabs. */
2942 TAB_SetItemBounds (infoPtr
);
2944 /* Force a repaint of the control. */
2945 InvalidateRect(infoPtr
->hwnd
, NULL
, TRUE
);
2951 static LRESULT
TAB_Create (HWND hwnd
, LPARAM lParam
)
2954 TEXTMETRICW fontMetrics
;
2959 infoPtr
= Alloc (sizeof(TAB_INFO
));
2961 SetWindowLongPtrW(hwnd
, 0, (DWORD_PTR
)infoPtr
);
2963 infoPtr
->hwnd
= hwnd
;
2964 infoPtr
->hwndNotify
= ((LPCREATESTRUCTW
)lParam
)->hwndParent
;
2965 infoPtr
->uNumItem
= 0;
2966 infoPtr
->uNumRows
= 0;
2967 infoPtr
->uHItemPadding
= 6;
2968 infoPtr
->uVItemPadding
= 3;
2969 infoPtr
->uHItemPadding_s
= 6;
2970 infoPtr
->uVItemPadding_s
= 3;
2972 infoPtr
->items
= DPA_Create(8);
2973 infoPtr
->hcurArrow
= LoadCursorW (0, (LPWSTR
)IDC_ARROW
);
2974 infoPtr
->iSelected
= -1;
2975 infoPtr
->iHotTracked
= -1;
2976 infoPtr
->uFocus
= -1;
2977 infoPtr
->hwndToolTip
= 0;
2978 infoPtr
->DoRedraw
= TRUE
;
2979 infoPtr
->needsScrolling
= FALSE
;
2980 infoPtr
->hwndUpDown
= 0;
2981 infoPtr
->leftmostVisible
= 0;
2982 infoPtr
->fHeightSet
= FALSE
;
2983 infoPtr
->bUnicode
= IsWindowUnicode (hwnd
);
2984 infoPtr
->cbInfo
= sizeof(LPARAM
);
2986 TRACE("Created tab control, hwnd [%p]\n", hwnd
);
2988 /* The tab control always has the WS_CLIPSIBLINGS style. Even
2989 if you don't specify it in CreateWindow. This is necessary in
2990 order for paint to work correctly. This follows windows behaviour. */
2991 style
= GetWindowLongW(hwnd
, GWL_STYLE
);
2992 if (style
& TCS_VERTICAL
) style
|= TCS_MULTILINE
;
2993 style
|= WS_CLIPSIBLINGS
;
2994 SetWindowLongW(hwnd
, GWL_STYLE
, style
);
2996 infoPtr
->dwStyle
= style
;
2997 infoPtr
->exStyle
= (style
& TCS_FLATBUTTONS
) ? TCS_EX_FLATSEPARATORS
: 0;
2999 if (infoPtr
->dwStyle
& TCS_TOOLTIPS
) {
3000 /* Create tooltip control */
3001 infoPtr
->hwndToolTip
=
3002 CreateWindowExW (0, TOOLTIPS_CLASSW
, NULL
, WS_POPUP
,
3003 CW_USEDEFAULT
, CW_USEDEFAULT
,
3004 CW_USEDEFAULT
, CW_USEDEFAULT
,
3007 /* Send NM_TOOLTIPSCREATED notification */
3008 if (infoPtr
->hwndToolTip
) {
3009 NMTOOLTIPSCREATED nmttc
;
3011 nmttc
.hdr
.hwndFrom
= hwnd
;
3012 nmttc
.hdr
.idFrom
= GetWindowLongPtrW(hwnd
, GWLP_ID
);
3013 nmttc
.hdr
.code
= NM_TOOLTIPSCREATED
;
3014 nmttc
.hwndToolTips
= infoPtr
->hwndToolTip
;
3016 SendMessageW (infoPtr
->hwndNotify
, WM_NOTIFY
,
3017 GetWindowLongPtrW(hwnd
, GWLP_ID
), (LPARAM
)&nmttc
);
3021 OpenThemeData (infoPtr
->hwnd
, themeClass
);
3024 * We need to get text information so we need a DC and we need to select
3028 hOldFont
= SelectObject (hdc
, GetStockObject (SYSTEM_FONT
));
3030 /* Use the system font to determine the initial height of a tab. */
3031 GetTextMetricsW(hdc
, &fontMetrics
);
3034 * Make sure there is enough space for the letters + growing the
3035 * selected item + extra space for the selected item.
3037 infoPtr
->tabHeight
= fontMetrics
.tmHeight
+ SELECTED_TAB_OFFSET
+
3038 ((infoPtr
->dwStyle
& TCS_BUTTONS
) ? 2 : 1) *
3039 infoPtr
->uVItemPadding
;
3041 /* Initialize the width of a tab. */
3042 if (infoPtr
->dwStyle
& TCS_FIXEDWIDTH
)
3043 infoPtr
->tabWidth
= GetDeviceCaps(hdc
, LOGPIXELSX
);
3045 infoPtr
->tabMinWidth
= -1;
3047 TRACE("tabH=%d, tabW=%d\n", infoPtr
->tabHeight
, infoPtr
->tabWidth
);
3049 SelectObject (hdc
, hOldFont
);
3050 ReleaseDC(hwnd
, hdc
);
3056 TAB_Destroy (TAB_INFO
*infoPtr
)
3060 SetWindowLongPtrW(infoPtr
->hwnd
, 0, 0);
3062 for (iItem
= infoPtr
->uNumItem
- 1; iItem
>= 0; iItem
--)
3064 TAB_ITEM
*tab
= TAB_GetItem(infoPtr
, iItem
);
3066 DPA_DeletePtr(infoPtr
->items
, iItem
);
3067 infoPtr
->uNumItem
--;
3072 DPA_Destroy(infoPtr
->items
);
3073 infoPtr
->items
= NULL
;
3075 if (infoPtr
->hwndToolTip
)
3076 DestroyWindow (infoPtr
->hwndToolTip
);
3078 if (infoPtr
->hwndUpDown
)
3079 DestroyWindow(infoPtr
->hwndUpDown
);
3081 if (infoPtr
->iHotTracked
>= 0)
3082 KillTimer(infoPtr
->hwnd
, TAB_HOTTRACK_TIMER
);
3084 CloseThemeData (GetWindowTheme (infoPtr
->hwnd
));
3090 /* update theme after a WM_THEMECHANGED message */
3091 static LRESULT
theme_changed(const TAB_INFO
*infoPtr
)
3093 HTHEME theme
= GetWindowTheme (infoPtr
->hwnd
);
3094 CloseThemeData (theme
);
3095 OpenThemeData (infoPtr
->hwnd
, themeClass
);
3099 static LRESULT
TAB_NCCalcSize(WPARAM wParam
)
3103 return WVR_ALIGNTOP
;
3106 static inline LRESULT
3107 TAB_SetItemExtra (TAB_INFO
*infoPtr
, INT cbInfo
)
3109 TRACE("(%p %d)\n", infoPtr
, cbInfo
);
3111 if (cbInfo
< 0 || infoPtr
->uNumItem
) return FALSE
;
3113 infoPtr
->cbInfo
= cbInfo
;
3117 static LRESULT
TAB_RemoveImage (TAB_INFO
*infoPtr
, INT image
)
3119 TRACE("%p %d\n", infoPtr
, image
);
3121 if (ImageList_Remove (infoPtr
->himl
, image
))
3126 /* shift indices, repaint items if needed */
3127 for (i
= 0; i
< infoPtr
->uNumItem
; i
++)
3129 idx
= &TAB_GetItem(infoPtr
, i
)->iImage
;
3138 if (TAB_InternalGetItemRect (infoPtr
, i
, &r
, NULL
))
3139 InvalidateRect (infoPtr
->hwnd
, &r
, TRUE
);
3148 TAB_SetExtendedStyle (TAB_INFO
*infoPtr
, DWORD exMask
, DWORD exStyle
)
3150 DWORD prevstyle
= infoPtr
->exStyle
;
3152 /* zero mask means all styles */
3153 if (exMask
== 0) exMask
= ~0;
3155 if (exMask
& TCS_EX_REGISTERDROP
)
3157 FIXME("TCS_EX_REGISTERDROP style unimplemented\n");
3158 exMask
&= ~TCS_EX_REGISTERDROP
;
3159 exStyle
&= ~TCS_EX_REGISTERDROP
;
3162 if (exMask
& TCS_EX_FLATSEPARATORS
)
3164 if ((prevstyle
^ exStyle
) & TCS_EX_FLATSEPARATORS
)
3166 infoPtr
->exStyle
^= TCS_EX_FLATSEPARATORS
;
3167 TAB_InvalidateTabArea(infoPtr
);
3174 static inline LRESULT
3175 TAB_GetExtendedStyle (const TAB_INFO
*infoPtr
)
3177 return infoPtr
->exStyle
;
3181 TAB_DeselectAll (TAB_INFO
*infoPtr
, BOOL excludesel
)
3184 INT i
, selected
= infoPtr
->iSelected
;
3186 TRACE("(%p, %d)\n", infoPtr
, excludesel
);
3188 if (!(infoPtr
->dwStyle
& TCS_BUTTONS
))
3191 for (i
= 0; i
< infoPtr
->uNumItem
; i
++)
3193 if ((TAB_GetItem(infoPtr
, i
)->dwState
& TCIS_BUTTONPRESSED
) &&
3196 TAB_GetItem(infoPtr
, i
)->dwState
&= ~TCIS_BUTTONPRESSED
;
3201 if (!excludesel
&& (selected
!= -1))
3203 TAB_GetItem(infoPtr
, selected
)->dwState
&= ~TCIS_BUTTONPRESSED
;
3204 infoPtr
->iSelected
= -1;
3209 TAB_InvalidateTabArea (infoPtr
);
3216 * Processes WM_STYLECHANGED messages.
3219 * [I] infoPtr : valid pointer to the tab data structure
3220 * [I] wStyleType : window style type (normal or extended)
3221 * [I] lpss : window style information
3226 static INT
TAB_StyleChanged(TAB_INFO
*infoPtr
, WPARAM wStyleType
,
3227 const STYLESTRUCT
*lpss
)
3229 TRACE("(styletype=%lx, styleOld=0x%08x, styleNew=0x%08x)\n",
3230 wStyleType
, lpss
->styleOld
, lpss
->styleNew
);
3232 if (wStyleType
!= GWL_STYLE
) return 0;
3234 infoPtr
->dwStyle
= lpss
->styleNew
;
3236 TAB_SetItemBounds (infoPtr
);
3237 InvalidateRect(infoPtr
->hwnd
, NULL
, TRUE
);
3242 static LRESULT WINAPI
3243 TAB_WindowProc (HWND hwnd
, UINT uMsg
, WPARAM wParam
, LPARAM lParam
)
3245 TAB_INFO
*infoPtr
= TAB_GetInfoPtr(hwnd
);
3247 TRACE("hwnd=%p msg=%x wParam=%lx lParam=%lx\n", hwnd
, uMsg
, wParam
, lParam
);
3248 if (!infoPtr
&& (uMsg
!= WM_CREATE
))
3249 return DefWindowProcW (hwnd
, uMsg
, wParam
, lParam
);
3253 case TCM_GETIMAGELIST
:
3254 return TAB_GetImageList (infoPtr
);
3256 case TCM_SETIMAGELIST
:
3257 return TAB_SetImageList (infoPtr
, (HIMAGELIST
)lParam
);
3259 case TCM_GETITEMCOUNT
:
3260 return TAB_GetItemCount (infoPtr
);
3264 return TAB_GetItemT (infoPtr
, (INT
)wParam
, (LPTCITEMW
)lParam
, uMsg
== TCM_GETITEMW
);
3268 return TAB_SetItemT (infoPtr
, (INT
)wParam
, (LPTCITEMW
)lParam
, uMsg
== TCM_SETITEMW
);
3270 case TCM_DELETEITEM
:
3271 return TAB_DeleteItem (infoPtr
, (INT
)wParam
);
3273 case TCM_DELETEALLITEMS
:
3274 return TAB_DeleteAllItems (infoPtr
);
3276 case TCM_GETITEMRECT
:
3277 return TAB_GetItemRect (infoPtr
, (INT
)wParam
, (LPRECT
)lParam
);
3280 return TAB_GetCurSel (infoPtr
);
3283 return TAB_HitTest (infoPtr
, (LPTCHITTESTINFO
)lParam
);
3286 return TAB_SetCurSel (infoPtr
, (INT
)wParam
);
3288 case TCM_INSERTITEMA
:
3289 case TCM_INSERTITEMW
:
3290 return TAB_InsertItemT (infoPtr
, (INT
)wParam
, (TCITEMW
*)lParam
, uMsg
== TCM_INSERTITEMW
);
3292 case TCM_SETITEMEXTRA
:
3293 return TAB_SetItemExtra (infoPtr
, (INT
)wParam
);
3295 case TCM_ADJUSTRECT
:
3296 return TAB_AdjustRect (infoPtr
, (BOOL
)wParam
, (LPRECT
)lParam
);
3298 case TCM_SETITEMSIZE
:
3299 return TAB_SetItemSize (infoPtr
, (INT
)LOWORD(lParam
), (INT
)HIWORD(lParam
));
3301 case TCM_REMOVEIMAGE
:
3302 return TAB_RemoveImage (infoPtr
, (INT
)wParam
);
3304 case TCM_SETPADDING
:
3305 return TAB_SetPadding (infoPtr
, lParam
);
3307 case TCM_GETROWCOUNT
:
3308 return TAB_GetRowCount(infoPtr
);
3310 case TCM_GETUNICODEFORMAT
:
3311 return TAB_GetUnicodeFormat (infoPtr
);
3313 case TCM_SETUNICODEFORMAT
:
3314 return TAB_SetUnicodeFormat (infoPtr
, (BOOL
)wParam
);
3316 case TCM_HIGHLIGHTITEM
:
3317 return TAB_HighlightItem (infoPtr
, (INT
)wParam
, (BOOL
)LOWORD(lParam
));
3319 case TCM_GETTOOLTIPS
:
3320 return TAB_GetToolTips (infoPtr
);
3322 case TCM_SETTOOLTIPS
:
3323 return TAB_SetToolTips (infoPtr
, (HWND
)wParam
);
3325 case TCM_GETCURFOCUS
:
3326 return TAB_GetCurFocus (infoPtr
);
3328 case TCM_SETCURFOCUS
:
3329 return TAB_SetCurFocus (infoPtr
, (INT
)wParam
);
3331 case TCM_SETMINTABWIDTH
:
3332 return TAB_SetMinTabWidth(infoPtr
, (INT
)lParam
);
3334 case TCM_DESELECTALL
:
3335 return TAB_DeselectAll (infoPtr
, (BOOL
)wParam
);
3337 case TCM_GETEXTENDEDSTYLE
:
3338 return TAB_GetExtendedStyle (infoPtr
);
3340 case TCM_SETEXTENDEDSTYLE
:
3341 return TAB_SetExtendedStyle (infoPtr
, wParam
, lParam
);
3344 return TAB_GetFont (infoPtr
);
3347 return TAB_SetFont (infoPtr
, (HFONT
)wParam
);
3350 return TAB_Create (hwnd
, lParam
);
3353 return TAB_Destroy (infoPtr
);
3356 return DLGC_WANTARROWS
| DLGC_WANTCHARS
;
3358 case WM_LBUTTONDOWN
:
3359 return TAB_LButtonDown (infoPtr
, wParam
, lParam
);
3362 return TAB_LButtonUp (infoPtr
);
3365 return SendMessageW(infoPtr
->hwndNotify
, WM_NOTIFY
, wParam
, lParam
);
3368 TAB_RButtonUp (infoPtr
);
3369 return DefWindowProcW (hwnd
, uMsg
, wParam
, lParam
);
3372 return TAB_MouseMove (infoPtr
, wParam
, lParam
);
3374 case WM_PRINTCLIENT
:
3376 return TAB_Paint (infoPtr
, (HDC
)wParam
);
3379 return TAB_Size (infoPtr
);
3382 return TAB_SetRedraw (infoPtr
, (BOOL
)wParam
);
3385 return TAB_OnHScroll(infoPtr
, (int)LOWORD(wParam
), (int)HIWORD(wParam
));
3387 case WM_STYLECHANGED
:
3388 return TAB_StyleChanged(infoPtr
, wParam
, (LPSTYLESTRUCT
)lParam
);
3390 case WM_SYSCOLORCHANGE
:
3391 COMCTL32_RefreshSysColors();
3394 case WM_THEMECHANGED
:
3395 return theme_changed (infoPtr
);
3398 TAB_KillFocus(infoPtr
);
3400 TAB_FocusChanging(infoPtr
);
3401 break; /* Don't disturb normal focus behavior */
3404 return TAB_KeyDown(infoPtr
, wParam
, lParam
);
3407 return TAB_NCHitTest(infoPtr
, lParam
);
3410 return TAB_NCCalcSize(wParam
);
3413 if (uMsg
>= WM_USER
&& uMsg
< WM_APP
&& !COMCTL32_IsReflectedMessage(uMsg
))
3414 WARN("unknown msg %04x wp=%08lx lp=%08lx\n",
3415 uMsg
, wParam
, lParam
);
3418 return DefWindowProcW(hwnd
, uMsg
, wParam
, lParam
);
3427 ZeroMemory (&wndClass
, sizeof(WNDCLASSW
));
3428 wndClass
.style
= CS_GLOBALCLASS
| CS_DBLCLKS
| CS_HREDRAW
| CS_VREDRAW
;
3429 wndClass
.lpfnWndProc
= TAB_WindowProc
;
3430 wndClass
.cbClsExtra
= 0;
3431 wndClass
.cbWndExtra
= sizeof(TAB_INFO
*);
3432 wndClass
.hCursor
= LoadCursorW (0, (LPWSTR
)IDC_ARROW
);
3433 wndClass
.hbrBackground
= (HBRUSH
)(COLOR_BTNFACE
+1);
3434 wndClass
.lpszClassName
= WC_TABCONTROLW
;
3436 RegisterClassW (&wndClass
);
3441 TAB_Unregister (void)
3443 UnregisterClassW (WC_TABCONTROLW
, NULL
);