4 * Copyright 1998 Anders Carlsson
5 * Copyright 1999 Alex Priem <alexp@sci.kun.nl>
6 * Copyright 1999 Francis Beaudet
7 * Copyright 2003 Vitaliy Margolen
9 * This library is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Lesser General Public
11 * License as published by the Free Software Foundation; either
12 * version 2.1 of the License, or (at your option) any later version.
14 * This library is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Lesser General Public License for more details.
19 * You should have received a copy of the GNU Lesser General Public
20 * License along with this library; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
25 * This code was audited for completeness against the documented features
26 * of Comctl32.dll version 6.0 on May. 20, 2005, by James Hawkins.
28 * Unless otherwise noted, we believe this code to be complete, as per
29 * the specification mentioned above.
30 * If you discover missing features, or bugs, please note them below.
35 * TCS_MULTISELECT - implement for VK_SPACE selection
68 #include "wine/debug.h"
71 WINE_DEFAULT_DEBUG_CHANNEL(tab
);
78 RECT rect
; /* bounding rectangle of the item relative to the
79 * leftmost item (the leftmost item, 0, would have a
80 * "left" member of 0 in this rectangle)
82 * additionally the top member holds the row number
83 * and bottom is unused and should be 0 */
84 BYTE extra
[1]; /* Space for caller supplied info, variable size */
87 /* The size of a tab item depends on how much extra data is requested.
88 TCM_INSERTITEM always stores at least LPARAM sized data. */
89 #define EXTRA_ITEM_SIZE(infoPtr) (max((infoPtr)->cbInfo, sizeof(LPARAM)))
90 #define TAB_ITEM_SIZE(infoPtr) FIELD_OFFSET(TAB_ITEM, extra[EXTRA_ITEM_SIZE(infoPtr)])
94 HWND hwnd
; /* Tab control window */
95 HWND hwndNotify
; /* notification window (parent) */
96 UINT uNumItem
; /* number of tab items */
97 UINT uNumRows
; /* number of tab rows */
98 INT tabHeight
; /* height of the tab row */
99 INT tabWidth
; /* width of tabs */
100 INT tabMinWidth
; /* minimum width of items */
101 USHORT uHItemPadding
; /* amount of horizontal padding, in pixels */
102 USHORT uVItemPadding
; /* amount of vertical padding, in pixels */
103 USHORT uHItemPadding_s
; /* Set amount of horizontal padding, in pixels */
104 USHORT uVItemPadding_s
; /* Set amount of vertical padding, in pixels */
105 HFONT hFont
; /* handle to the current font */
106 HCURSOR hcurArrow
; /* handle to the current cursor */
107 HIMAGELIST himl
; /* handle to an image list (may be 0) */
108 HWND hwndToolTip
; /* handle to tab's tooltip */
109 INT leftmostVisible
; /* Used for scrolling, this member contains
110 * the index of the first visible item */
111 INT iSelected
; /* the currently selected item */
112 INT iHotTracked
; /* the highlighted item under the mouse */
113 INT uFocus
; /* item which has the focus */
114 BOOL DoRedraw
; /* flag for redrawing when tab contents is changed*/
115 BOOL needsScrolling
; /* TRUE if the size of the tabs is greater than
116 * the size of the control */
117 BOOL fHeightSet
; /* was the height of the tabs explicitly set? */
118 BOOL bUnicode
; /* Unicode control? */
119 HWND hwndUpDown
; /* Updown control used for scrolling */
120 INT cbInfo
; /* Number of bytes of caller supplied info per tab */
122 DWORD exStyle
; /* Extended style used, currently:
123 TCS_EX_FLATSEPARATORS, TCS_EX_REGISTERDROP */
124 DWORD dwStyle
; /* the cached window GWL_STYLE */
126 HDPA items
; /* dynamic array of TAB_ITEM* pointers */
129 /******************************************************************************
130 * Positioning constants
132 #define SELECTED_TAB_OFFSET 2
133 #define ROUND_CORNER_SIZE 2
134 #define DISPLAY_AREA_PADDINGX 2
135 #define DISPLAY_AREA_PADDINGY 2
136 #define CONTROL_BORDER_SIZEX 2
137 #define CONTROL_BORDER_SIZEY 2
138 #define BUTTON_SPACINGX 3
139 #define BUTTON_SPACINGY 3
140 #define FLAT_BTN_SPACINGX 8
141 #define DEFAULT_MIN_TAB_WIDTH 54
142 #define DEFAULT_PADDING_X 6
143 #define EXTRA_ICON_PADDING 3
145 #define TAB_GetInfoPtr(hwnd) ((TAB_INFO *)GetWindowLongPtrW(hwnd,0))
147 #define GET_DEFAULT_MIN_TAB_WIDTH(infoPtr) (DEFAULT_MIN_TAB_WIDTH - (DEFAULT_PADDING_X - (infoPtr)->uHItemPadding) * 2)
149 /******************************************************************************
150 * Hot-tracking timer constants
152 #define TAB_HOTTRACK_TIMER 1
153 #define TAB_HOTTRACK_TIMER_INTERVAL 100 /* milliseconds */
155 static const WCHAR themeClass
[] = { 'T','a','b',0 };
157 static inline TAB_ITEM
* TAB_GetItem(const TAB_INFO
*infoPtr
, INT i
)
159 assert(i
>= 0 && i
< infoPtr
->uNumItem
);
160 return DPA_GetPtr(infoPtr
->items
, i
);
163 /******************************************************************************
166 static void TAB_InvalidateTabArea(const TAB_INFO
*);
167 static void TAB_EnsureSelectionVisible(TAB_INFO
*);
168 static void TAB_DrawItemInterior(const TAB_INFO
*, HDC
, INT
, RECT
*);
169 static LRESULT
TAB_DeselectAll(TAB_INFO
*, BOOL
);
170 static BOOL
TAB_InternalGetItemRect(const TAB_INFO
*, INT
, RECT
*, RECT
*);
173 TAB_SendSimpleNotify (const TAB_INFO
*infoPtr
, UINT code
)
177 nmhdr
.hwndFrom
= infoPtr
->hwnd
;
178 nmhdr
.idFrom
= GetWindowLongPtrW(infoPtr
->hwnd
, GWLP_ID
);
181 return (BOOL
) SendMessageW (infoPtr
->hwndNotify
, WM_NOTIFY
,
182 nmhdr
.idFrom
, (LPARAM
) &nmhdr
);
186 TAB_RelayEvent (HWND hwndTip
, HWND hwndMsg
, UINT uMsg
,
187 WPARAM wParam
, LPARAM lParam
)
195 msg
.time
= GetMessageTime ();
196 msg
.pt
.x
= (short)LOWORD(GetMessagePos ());
197 msg
.pt
.y
= (short)HIWORD(GetMessagePos ());
199 SendMessageW (hwndTip
, TTM_RELAYEVENT
, 0, (LPARAM
)&msg
);
203 TAB_DumpItemExternalT(const TCITEMW
*pti
, UINT iItem
, BOOL isW
)
206 TRACE("external tab %d, mask=0x%08x, dwState=0x%08x, dwStateMask=0x%08x, cchTextMax=0x%08x\n",
207 iItem
, pti
->mask
, pti
->dwState
, pti
->dwStateMask
, pti
->cchTextMax
);
208 TRACE("external tab %d, iImage=%d, lParam=0x%08lx, pszTextW=%s\n",
209 iItem
, pti
->iImage
, pti
->lParam
, isW
? debugstr_w(pti
->pszText
) : debugstr_a((LPSTR
)pti
->pszText
));
214 TAB_DumpItemInternal(const TAB_INFO
*infoPtr
, UINT iItem
)
217 TAB_ITEM
*ti
= TAB_GetItem(infoPtr
, iItem
);
219 TRACE("tab %d, dwState=0x%08x, pszText=%s, iImage=%d\n",
220 iItem
, ti
->dwState
, debugstr_w(ti
->pszText
), ti
->iImage
);
221 TRACE("tab %d, rect.left=%d, rect.top(row)=%d\n",
222 iItem
, ti
->rect
.left
, ti
->rect
.top
);
227 * the index of the selected tab, or -1 if no tab is selected. */
228 static inline LRESULT
TAB_GetCurSel (const TAB_INFO
*infoPtr
)
230 TRACE("(%p)\n", infoPtr
);
231 return infoPtr
->iSelected
;
235 * the index of the tab item that has the focus. */
236 static inline LRESULT
237 TAB_GetCurFocus (const TAB_INFO
*infoPtr
)
239 TRACE("(%p)\n", infoPtr
);
240 return infoPtr
->uFocus
;
243 static inline LRESULT
TAB_GetToolTips (const TAB_INFO
*infoPtr
)
245 TRACE("(%p)\n", infoPtr
);
246 return (LRESULT
)infoPtr
->hwndToolTip
;
249 static inline LRESULT
TAB_SetCurSel (TAB_INFO
*infoPtr
, INT iItem
)
251 INT prevItem
= infoPtr
->iSelected
;
253 TRACE("(%p %d)\n", infoPtr
, iItem
);
256 infoPtr
->iSelected
= -1;
257 else if (iItem
>= infoPtr
->uNumItem
)
260 if (prevItem
!= iItem
) {
262 TAB_GetItem(infoPtr
, prevItem
)->dwState
&= ~TCIS_BUTTONPRESSED
;
263 TAB_GetItem(infoPtr
, iItem
)->dwState
|= TCIS_BUTTONPRESSED
;
265 infoPtr
->iSelected
= iItem
;
266 infoPtr
->uFocus
= iItem
;
267 TAB_EnsureSelectionVisible(infoPtr
);
268 TAB_InvalidateTabArea(infoPtr
);
274 static LRESULT
TAB_SetCurFocus (TAB_INFO
*infoPtr
, INT iItem
)
276 TRACE("(%p %d)\n", infoPtr
, iItem
);
279 infoPtr
->uFocus
= -1;
280 if (infoPtr
->iSelected
!= -1) {
281 infoPtr
->iSelected
= -1;
282 TAB_SendSimpleNotify(infoPtr
, TCN_SELCHANGE
);
283 TAB_InvalidateTabArea(infoPtr
);
286 else if (iItem
< infoPtr
->uNumItem
) {
287 if (infoPtr
->dwStyle
& TCS_BUTTONS
) {
288 /* set focus to new item, leave selection as is */
289 if (infoPtr
->uFocus
!= iItem
) {
290 INT prev_focus
= infoPtr
->uFocus
;
293 infoPtr
->uFocus
= iItem
;
295 if (prev_focus
!= infoPtr
->iSelected
) {
296 if (TAB_InternalGetItemRect(infoPtr
, prev_focus
, &r
, NULL
))
297 InvalidateRect(infoPtr
->hwnd
, &r
, FALSE
);
300 if (TAB_InternalGetItemRect(infoPtr
, iItem
, &r
, NULL
))
301 InvalidateRect(infoPtr
->hwnd
, &r
, FALSE
);
303 TAB_SendSimpleNotify(infoPtr
, TCN_FOCUSCHANGE
);
306 INT oldFocus
= infoPtr
->uFocus
;
307 if (infoPtr
->iSelected
!= iItem
|| oldFocus
== -1 ) {
308 infoPtr
->uFocus
= iItem
;
309 if (oldFocus
!= -1) {
310 if (!TAB_SendSimpleNotify(infoPtr
, TCN_SELCHANGING
)) {
311 infoPtr
->iSelected
= iItem
;
312 TAB_SendSimpleNotify(infoPtr
, TCN_SELCHANGE
);
315 infoPtr
->iSelected
= iItem
;
316 TAB_EnsureSelectionVisible(infoPtr
);
317 TAB_InvalidateTabArea(infoPtr
);
325 static inline LRESULT
326 TAB_SetToolTips (TAB_INFO
*infoPtr
, HWND hwndToolTip
)
328 TRACE("%p %p\n", infoPtr
, hwndToolTip
);
329 infoPtr
->hwndToolTip
= hwndToolTip
;
333 static inline LRESULT
334 TAB_SetPadding (TAB_INFO
*infoPtr
, LPARAM lParam
)
336 TRACE("(%p %d %d)\n", infoPtr
, LOWORD(lParam
), HIWORD(lParam
));
337 infoPtr
->uHItemPadding_s
= LOWORD(lParam
);
338 infoPtr
->uVItemPadding_s
= HIWORD(lParam
);
343 /******************************************************************************
344 * TAB_InternalGetItemRect
346 * This method will calculate the rectangle representing a given tab item in
347 * client coordinates. This method takes scrolling into account.
349 * This method returns TRUE if the item is visible in the window and FALSE
350 * if it is completely outside the client area.
352 static BOOL
TAB_InternalGetItemRect(
353 const TAB_INFO
* infoPtr
,
358 RECT tmpItemRect
,clientRect
;
360 /* Perform a sanity check and a trivial visibility check. */
361 if ( (infoPtr
->uNumItem
<= 0) ||
362 (itemIndex
>= infoPtr
->uNumItem
) ||
363 (!(((infoPtr
->dwStyle
& TCS_MULTILINE
) || (infoPtr
->dwStyle
& TCS_VERTICAL
))) &&
364 (itemIndex
< infoPtr
->leftmostVisible
)))
366 TRACE("Not Visible\n");
367 /* need to initialize these to empty rects */
370 memset(itemRect
,0,sizeof(RECT
));
371 itemRect
->bottom
= infoPtr
->tabHeight
;
374 memset(selectedRect
,0,sizeof(RECT
));
379 * Avoid special cases in this procedure by assigning the "out"
380 * parameters if the caller didn't supply them
382 if (itemRect
== NULL
)
383 itemRect
= &tmpItemRect
;
385 /* Retrieve the unmodified item rect. */
386 *itemRect
= TAB_GetItem(infoPtr
,itemIndex
)->rect
;
388 /* calculate the times bottom and top based on the row */
389 GetClientRect(infoPtr
->hwnd
, &clientRect
);
391 if ((infoPtr
->dwStyle
& TCS_BOTTOM
) && (infoPtr
->dwStyle
& TCS_VERTICAL
))
393 itemRect
->right
= clientRect
.right
- SELECTED_TAB_OFFSET
- itemRect
->left
* infoPtr
->tabHeight
-
394 ((infoPtr
->dwStyle
& TCS_BUTTONS
) ? itemRect
->left
* BUTTON_SPACINGX
: 0);
395 itemRect
->left
= itemRect
->right
- infoPtr
->tabHeight
;
397 else if (infoPtr
->dwStyle
& TCS_VERTICAL
)
399 itemRect
->left
= clientRect
.left
+ SELECTED_TAB_OFFSET
+ itemRect
->left
* infoPtr
->tabHeight
+
400 ((infoPtr
->dwStyle
& TCS_BUTTONS
) ? itemRect
->left
* BUTTON_SPACINGX
: 0);
401 itemRect
->right
= itemRect
->left
+ infoPtr
->tabHeight
;
403 else if (infoPtr
->dwStyle
& TCS_BOTTOM
)
405 itemRect
->bottom
= clientRect
.bottom
- itemRect
->top
* infoPtr
->tabHeight
-
406 ((infoPtr
->dwStyle
& TCS_BUTTONS
) ? itemRect
->top
* BUTTON_SPACINGY
: SELECTED_TAB_OFFSET
);
407 itemRect
->top
= itemRect
->bottom
- infoPtr
->tabHeight
;
409 else /* not TCS_BOTTOM and not TCS_VERTICAL */
411 itemRect
->top
= clientRect
.top
+ itemRect
->top
* infoPtr
->tabHeight
+
412 ((infoPtr
->dwStyle
& TCS_BUTTONS
) ? itemRect
->top
* BUTTON_SPACINGY
: SELECTED_TAB_OFFSET
);
413 itemRect
->bottom
= itemRect
->top
+ infoPtr
->tabHeight
;
417 * "scroll" it to make sure the item at the very left of the
418 * tab control is the leftmost visible tab.
420 if(infoPtr
->dwStyle
& TCS_VERTICAL
)
424 -TAB_GetItem(infoPtr
, infoPtr
->leftmostVisible
)->rect
.top
);
427 * Move the rectangle so the first item is slightly offset from
428 * the bottom of the tab control.
432 SELECTED_TAB_OFFSET
);
437 -TAB_GetItem(infoPtr
, infoPtr
->leftmostVisible
)->rect
.left
,
441 * Move the rectangle so the first item is slightly offset from
442 * the left of the tab control.
448 TRACE("item %d tab h=%d, rect=(%s)\n",
449 itemIndex
, infoPtr
->tabHeight
, wine_dbgstr_rect(itemRect
));
451 /* Now, calculate the position of the item as if it were selected. */
452 if (selectedRect
!=NULL
)
454 CopyRect(selectedRect
, itemRect
);
456 /* The rectangle of a selected item is a bit wider. */
457 if(infoPtr
->dwStyle
& TCS_VERTICAL
)
458 InflateRect(selectedRect
, 0, SELECTED_TAB_OFFSET
);
460 InflateRect(selectedRect
, SELECTED_TAB_OFFSET
, 0);
462 /* If it also a bit higher. */
463 if ((infoPtr
->dwStyle
& TCS_BOTTOM
) && (infoPtr
->dwStyle
& TCS_VERTICAL
))
465 selectedRect
->left
-= 2; /* the border is thicker on the right */
466 selectedRect
->right
+= SELECTED_TAB_OFFSET
;
468 else if (infoPtr
->dwStyle
& TCS_VERTICAL
)
470 selectedRect
->left
-= SELECTED_TAB_OFFSET
;
471 selectedRect
->right
+= 1;
473 else if (infoPtr
->dwStyle
& TCS_BOTTOM
)
475 selectedRect
->bottom
+= SELECTED_TAB_OFFSET
;
477 else /* not TCS_BOTTOM and not TCS_VERTICAL */
479 selectedRect
->top
-= SELECTED_TAB_OFFSET
;
480 selectedRect
->bottom
-= 1;
484 /* Check for visibility */
485 if (infoPtr
->dwStyle
& TCS_VERTICAL
)
486 return (itemRect
->top
< clientRect
.bottom
) && (itemRect
->bottom
> clientRect
.top
);
488 return (itemRect
->left
< clientRect
.right
) && (itemRect
->right
> clientRect
.left
);
492 TAB_GetItemRect(const TAB_INFO
*infoPtr
, INT item
, RECT
*rect
)
494 TRACE("(%p, %d, %p)\n", infoPtr
, item
, rect
);
495 return TAB_InternalGetItemRect(infoPtr
, item
, rect
, NULL
);
498 /******************************************************************************
501 * This method is called to handle keyboard input
503 static LRESULT
TAB_KeyDown(TAB_INFO
* infoPtr
, WPARAM keyCode
, LPARAM lParam
)
508 /* TCN_KEYDOWN notification sent always */
509 nm
.hdr
.hwndFrom
= infoPtr
->hwnd
;
510 nm
.hdr
.idFrom
= GetWindowLongPtrW(infoPtr
->hwnd
, GWLP_ID
);
511 nm
.hdr
.code
= TCN_KEYDOWN
;
514 SendMessageW(infoPtr
->hwndNotify
, WM_NOTIFY
, nm
.hdr
.idFrom
, (LPARAM
)&nm
);
519 newItem
= infoPtr
->uFocus
- 1;
522 newItem
= infoPtr
->uFocus
+ 1;
526 /* If we changed to a valid item, change focused item */
527 if (newItem
>= 0 && newItem
< infoPtr
->uNumItem
&& infoPtr
->uFocus
!= newItem
)
528 TAB_SetCurFocus(infoPtr
, newItem
);
534 * WM_KILLFOCUS handler
536 static void TAB_KillFocus(TAB_INFO
*infoPtr
)
538 /* clear current focused item back to selected for TCS_BUTTONS */
539 if ((infoPtr
->dwStyle
& TCS_BUTTONS
) && (infoPtr
->uFocus
!= infoPtr
->iSelected
))
543 if (TAB_InternalGetItemRect(infoPtr
, infoPtr
->uFocus
, &r
, NULL
))
544 InvalidateRect(infoPtr
->hwnd
, &r
, FALSE
);
546 infoPtr
->uFocus
= infoPtr
->iSelected
;
550 /******************************************************************************
553 * This method is called whenever the focus goes in or out of this control
554 * it is used to update the visual state of the control.
556 static void TAB_FocusChanging(const TAB_INFO
*infoPtr
)
562 * Get the rectangle for the item.
564 isVisible
= TAB_InternalGetItemRect(infoPtr
,
570 * If the rectangle is not completely invisible, invalidate that
571 * portion of the window.
575 TRACE("invalidate (%s)\n", wine_dbgstr_rect(&selectedRect
));
576 InvalidateRect(infoPtr
->hwnd
, &selectedRect
, TRUE
);
580 static INT
TAB_InternalHitTest (const TAB_INFO
*infoPtr
, POINT pt
, UINT
*flags
)
585 for (iCount
= 0; iCount
< infoPtr
->uNumItem
; iCount
++)
587 TAB_InternalGetItemRect(infoPtr
, iCount
, &rect
, NULL
);
589 if (PtInRect(&rect
, pt
))
591 *flags
= TCHT_ONITEM
;
596 *flags
= TCHT_NOWHERE
;
600 static inline LRESULT
601 TAB_HitTest (const TAB_INFO
*infoPtr
, LPTCHITTESTINFO lptest
)
603 TRACE("(%p, %p)\n", infoPtr
, lptest
);
604 return TAB_InternalHitTest (infoPtr
, lptest
->pt
, &lptest
->flags
);
607 /******************************************************************************
610 * Napster v2b5 has a tab control for its main navigation which has a client
611 * area that covers the whole area of the dialog pages.
612 * That's why it receives all msgs for that area and the underlying dialog ctrls
614 * So I decided that we should handle WM_NCHITTEST here and return
615 * HTTRANSPARENT if we don't hit the tab control buttons.
616 * FIXME: WM_NCHITTEST handling correct ? Fix it if you know that Windows
617 * doesn't do it that way. Maybe depends on tab control styles ?
619 static inline LRESULT
620 TAB_NCHitTest (const TAB_INFO
*infoPtr
, LPARAM lParam
)
625 pt
.x
= (short)LOWORD(lParam
);
626 pt
.y
= (short)HIWORD(lParam
);
627 ScreenToClient(infoPtr
->hwnd
, &pt
);
629 if (TAB_InternalHitTest(infoPtr
, pt
, &dummyflag
) == -1)
630 return HTTRANSPARENT
;
636 TAB_LButtonDown (TAB_INFO
*infoPtr
, WPARAM wParam
, LPARAM lParam
)
642 if (infoPtr
->hwndToolTip
)
643 TAB_RelayEvent (infoPtr
->hwndToolTip
, infoPtr
->hwnd
,
644 WM_LBUTTONDOWN
, wParam
, lParam
);
646 if (!(infoPtr
->dwStyle
& TCS_FOCUSNEVER
)) {
647 SetFocus (infoPtr
->hwnd
);
650 if (infoPtr
->hwndToolTip
)
651 TAB_RelayEvent (infoPtr
->hwndToolTip
, infoPtr
->hwnd
,
652 WM_LBUTTONDOWN
, wParam
, lParam
);
654 pt
.x
= (short)LOWORD(lParam
);
655 pt
.y
= (short)HIWORD(lParam
);
657 newItem
= TAB_InternalHitTest (infoPtr
, pt
, &dummy
);
659 TRACE("On Tab, item %d\n", newItem
);
661 if ((newItem
!= -1) && (infoPtr
->iSelected
!= newItem
))
663 if ((infoPtr
->dwStyle
& TCS_BUTTONS
) && (infoPtr
->dwStyle
& TCS_MULTISELECT
) &&
664 (wParam
& MK_CONTROL
))
668 /* toggle multiselection */
669 TAB_GetItem(infoPtr
, newItem
)->dwState
^= TCIS_BUTTONPRESSED
;
670 if (TAB_InternalGetItemRect (infoPtr
, newItem
, &r
, NULL
))
671 InvalidateRect (infoPtr
->hwnd
, &r
, TRUE
);
676 BOOL pressed
= FALSE
;
678 /* any button pressed ? */
679 for (i
= 0; i
< infoPtr
->uNumItem
; i
++)
680 if ((TAB_GetItem (infoPtr
, i
)->dwState
& TCIS_BUTTONPRESSED
) &&
681 (infoPtr
->iSelected
!= i
))
687 if (TAB_SendSimpleNotify(infoPtr
, TCN_SELCHANGING
))
691 TAB_DeselectAll (infoPtr
, FALSE
);
693 TAB_SetCurSel(infoPtr
, newItem
);
695 TAB_SendSimpleNotify(infoPtr
, TCN_SELCHANGE
);
702 static inline LRESULT
703 TAB_LButtonUp (const TAB_INFO
*infoPtr
)
705 TAB_SendSimpleNotify(infoPtr
, NM_CLICK
);
711 TAB_RButtonUp (const TAB_INFO
*infoPtr
)
713 TAB_SendSimpleNotify(infoPtr
, NM_RCLICK
);
716 /******************************************************************************
717 * TAB_DrawLoneItemInterior
719 * This calls TAB_DrawItemInterior. However, TAB_DrawItemInterior is normally
720 * called by TAB_DrawItem which is normally called by TAB_Refresh which sets
721 * up the device context and font. This routine does the same setup but
722 * only calls TAB_DrawItemInterior for the single specified item.
725 TAB_DrawLoneItemInterior(const TAB_INFO
* infoPtr
, int iItem
)
727 HDC hdc
= GetDC(infoPtr
->hwnd
);
730 /* Clip UpDown control to not draw over it */
731 if (infoPtr
->needsScrolling
)
733 GetWindowRect(infoPtr
->hwnd
, &rC
);
734 GetWindowRect(infoPtr
->hwndUpDown
, &r
);
735 ExcludeClipRect(hdc
, r
.left
- rC
.left
, r
.top
- rC
.top
, r
.right
- rC
.left
, r
.bottom
- rC
.top
);
737 TAB_DrawItemInterior(infoPtr
, hdc
, iItem
, NULL
);
738 ReleaseDC(infoPtr
->hwnd
, hdc
);
741 /* update a tab after hottracking - invalidate it or just redraw the interior,
742 * based on whether theming is used or not */
743 static inline void hottrack_refresh(const TAB_INFO
*infoPtr
, int tabIndex
)
745 if (tabIndex
== -1) return;
747 if (GetWindowTheme (infoPtr
->hwnd
))
750 TAB_InternalGetItemRect(infoPtr
, tabIndex
, &rect
, NULL
);
751 InvalidateRect (infoPtr
->hwnd
, &rect
, FALSE
);
754 TAB_DrawLoneItemInterior(infoPtr
, tabIndex
);
757 /******************************************************************************
758 * TAB_HotTrackTimerProc
760 * When a mouse-move event causes a tab to be highlighted (hot-tracking), a
761 * timer is setup so we can check if the mouse is moved out of our window.
762 * (We don't get an event when the mouse leaves, the mouse-move events just
763 * stop being delivered to our window and just start being delivered to
764 * another window.) This function is called when the timer triggers so
765 * we can check if the mouse has left our window. If so, we un-highlight
766 * the hot-tracked tab.
769 TAB_HotTrackTimerProc
771 HWND hwnd
, /* handle of window for timer messages */
772 UINT uMsg
, /* WM_TIMER message */
773 UINT_PTR idEvent
, /* timer identifier */
774 DWORD dwTime
/* current system time */
777 TAB_INFO
* infoPtr
= TAB_GetInfoPtr(hwnd
);
779 if (infoPtr
!= NULL
&& infoPtr
->iHotTracked
>= 0)
784 ** If we can't get the cursor position, or if the cursor is outside our
785 ** window, we un-highlight the hot-tracked tab. Note that the cursor is
786 ** "outside" even if it is within our bounding rect if another window
787 ** overlaps. Note also that the case where the cursor stayed within our
788 ** window but has moved off the hot-tracked tab will be handled by the
789 ** WM_MOUSEMOVE event.
791 if (!GetCursorPos(&pt
) || WindowFromPoint(pt
) != hwnd
)
793 /* Redraw iHotTracked to look normal */
794 INT iRedraw
= infoPtr
->iHotTracked
;
795 infoPtr
->iHotTracked
= -1;
796 hottrack_refresh (infoPtr
, iRedraw
);
798 /* Kill this timer */
799 KillTimer(hwnd
, TAB_HOTTRACK_TIMER
);
804 /******************************************************************************
807 * If a tab control has the TCS_HOTTRACK style, then the tab under the mouse
808 * should be highlighted. This function determines which tab in a tab control,
809 * if any, is under the mouse and records that information. The caller may
810 * supply output parameters to receive the item number of the tab item which
811 * was highlighted but isn't any longer and of the tab item which is now
812 * highlighted but wasn't previously. The caller can use this information to
813 * selectively redraw those tab items.
815 * If the caller has a mouse position, it can supply it through the pos
816 * parameter. For example, TAB_MouseMove does this. Otherwise, the caller
817 * supplies NULL and this function determines the current mouse position
825 int* out_redrawLeave
,
832 if (out_redrawLeave
!= NULL
)
833 *out_redrawLeave
= -1;
834 if (out_redrawEnter
!= NULL
)
835 *out_redrawEnter
= -1;
837 if ((infoPtr
->dwStyle
& TCS_HOTTRACK
) || GetWindowTheme(infoPtr
->hwnd
))
845 ScreenToClient(infoPtr
->hwnd
, &pt
);
849 pt
.x
= (short)LOWORD(*pos
);
850 pt
.y
= (short)HIWORD(*pos
);
853 item
= TAB_InternalHitTest(infoPtr
, pt
, &flags
);
856 if (item
!= infoPtr
->iHotTracked
)
858 if (infoPtr
->iHotTracked
>= 0)
860 /* Mark currently hot-tracked to be redrawn to look normal */
861 if (out_redrawLeave
!= NULL
)
862 *out_redrawLeave
= infoPtr
->iHotTracked
;
866 /* Kill timer which forces recheck of mouse pos */
867 KillTimer(infoPtr
->hwnd
, TAB_HOTTRACK_TIMER
);
872 /* Start timer so we recheck mouse pos */
873 UINT timerID
= SetTimer
877 TAB_HOTTRACK_TIMER_INTERVAL
,
878 TAB_HotTrackTimerProc
882 return; /* Hot tracking not available */
885 infoPtr
->iHotTracked
= item
;
889 /* Mark new hot-tracked to be redrawn to look highlighted */
890 if (out_redrawEnter
!= NULL
)
891 *out_redrawEnter
= item
;
896 /******************************************************************************
899 * Handles the mouse-move event. Updates tooltips. Updates hot-tracking.
902 TAB_MouseMove (TAB_INFO
*infoPtr
, WPARAM wParam
, LPARAM lParam
)
907 if (infoPtr
->hwndToolTip
)
908 TAB_RelayEvent (infoPtr
->hwndToolTip
, infoPtr
->hwnd
,
909 WM_LBUTTONDOWN
, wParam
, lParam
);
911 /* Determine which tab to highlight. Redraw tabs which change highlight
913 TAB_RecalcHotTrack(infoPtr
, &lParam
, &redrawLeave
, &redrawEnter
);
915 hottrack_refresh (infoPtr
, redrawLeave
);
916 hottrack_refresh (infoPtr
, redrawEnter
);
921 /******************************************************************************
924 * Calculates the tab control's display area given the window rectangle or
925 * the window rectangle given the requested display rectangle.
927 static LRESULT
TAB_AdjustRect(const TAB_INFO
*infoPtr
, WPARAM fLarger
, LPRECT prc
)
929 LONG
*iRightBottom
, *iLeftTop
;
931 TRACE ("hwnd=%p fLarger=%ld (%s)\n", infoPtr
->hwnd
, fLarger
,
932 wine_dbgstr_rect(prc
));
936 if(infoPtr
->dwStyle
& TCS_VERTICAL
)
938 iRightBottom
= &(prc
->right
);
939 iLeftTop
= &(prc
->left
);
943 iRightBottom
= &(prc
->bottom
);
944 iLeftTop
= &(prc
->top
);
947 if (fLarger
) /* Go from display rectangle */
949 /* Add the height of the tabs. */
950 if (infoPtr
->dwStyle
& TCS_BOTTOM
)
951 *iRightBottom
+= infoPtr
->tabHeight
* infoPtr
->uNumRows
;
953 *iLeftTop
-= infoPtr
->tabHeight
* infoPtr
->uNumRows
+
954 ((infoPtr
->dwStyle
& TCS_BUTTONS
)? 3 * (infoPtr
->uNumRows
- 1) : 0);
956 /* Inflate the rectangle for the padding */
957 InflateRect(prc
, DISPLAY_AREA_PADDINGX
, DISPLAY_AREA_PADDINGY
);
959 /* Inflate for the border */
960 InflateRect(prc
, CONTROL_BORDER_SIZEX
, CONTROL_BORDER_SIZEY
);
962 else /* Go from window rectangle. */
964 /* Deflate the rectangle for the border */
965 InflateRect(prc
, -CONTROL_BORDER_SIZEX
, -CONTROL_BORDER_SIZEY
);
967 /* Deflate the rectangle for the padding */
968 InflateRect(prc
, -DISPLAY_AREA_PADDINGX
, -DISPLAY_AREA_PADDINGY
);
970 /* Remove the height of the tabs. */
971 if (infoPtr
->dwStyle
& TCS_BOTTOM
)
972 *iRightBottom
-= infoPtr
->tabHeight
* infoPtr
->uNumRows
;
974 *iLeftTop
+= (infoPtr
->tabHeight
) * infoPtr
->uNumRows
+
975 ((infoPtr
->dwStyle
& TCS_BUTTONS
)? 3 * (infoPtr
->uNumRows
- 1) : 0);
981 /******************************************************************************
984 * This method will handle the notification from the scroll control and
985 * perform the scrolling operation on the tab control.
987 static LRESULT
TAB_OnHScroll(TAB_INFO
*infoPtr
, int nScrollCode
, int nPos
)
989 if(nScrollCode
== SB_THUMBPOSITION
&& nPos
!= infoPtr
->leftmostVisible
)
991 if(nPos
< infoPtr
->leftmostVisible
)
992 infoPtr
->leftmostVisible
--;
994 infoPtr
->leftmostVisible
++;
996 TAB_RecalcHotTrack(infoPtr
, NULL
, NULL
, NULL
);
997 TAB_InvalidateTabArea(infoPtr
);
998 SendMessageW(infoPtr
->hwndUpDown
, UDM_SETPOS
, 0,
999 MAKELONG(infoPtr
->leftmostVisible
, 0));
1005 /******************************************************************************
1006 * TAB_SetupScrolling
1008 * This method will check the current scrolling state and make sure the
1009 * scrolling control is displayed (or not).
1011 static void TAB_SetupScrolling(
1013 const RECT
* clientRect
)
1015 static const WCHAR emptyW
[] = { 0 };
1018 if (infoPtr
->needsScrolling
)
1021 INT vsize
, tabwidth
;
1024 * Calculate the position of the scroll control.
1026 controlPos
.right
= clientRect
->right
;
1027 controlPos
.left
= controlPos
.right
- 2 * GetSystemMetrics(SM_CXHSCROLL
);
1029 if (infoPtr
->dwStyle
& TCS_BOTTOM
)
1031 controlPos
.top
= clientRect
->bottom
- infoPtr
->tabHeight
;
1032 controlPos
.bottom
= controlPos
.top
+ GetSystemMetrics(SM_CYHSCROLL
);
1036 controlPos
.bottom
= clientRect
->top
+ infoPtr
->tabHeight
;
1037 controlPos
.top
= controlPos
.bottom
- GetSystemMetrics(SM_CYHSCROLL
);
1041 * If we don't have a scroll control yet, we want to create one.
1042 * If we have one, we want to make sure it's positioned properly.
1044 if (infoPtr
->hwndUpDown
==0)
1046 infoPtr
->hwndUpDown
= CreateWindowW(UPDOWN_CLASSW
, emptyW
,
1047 WS_VISIBLE
| WS_CHILD
| UDS_HORZ
,
1048 controlPos
.left
, controlPos
.top
,
1049 controlPos
.right
- controlPos
.left
,
1050 controlPos
.bottom
- controlPos
.top
,
1051 infoPtr
->hwnd
, NULL
, NULL
, NULL
);
1055 SetWindowPos(infoPtr
->hwndUpDown
,
1057 controlPos
.left
, controlPos
.top
,
1058 controlPos
.right
- controlPos
.left
,
1059 controlPos
.bottom
- controlPos
.top
,
1060 SWP_SHOWWINDOW
| SWP_NOZORDER
);
1063 /* Now calculate upper limit of the updown control range.
1064 * We do this by calculating how many tabs will be offscreen when the
1065 * last tab is visible.
1067 if(infoPtr
->uNumItem
)
1069 vsize
= clientRect
->right
- (controlPos
.right
- controlPos
.left
+ 1);
1070 maxRange
= infoPtr
->uNumItem
;
1071 tabwidth
= TAB_GetItem(infoPtr
, infoPtr
->uNumItem
- 1)->rect
.right
;
1073 for(; maxRange
> 0; maxRange
--)
1075 if(tabwidth
- TAB_GetItem(infoPtr
,maxRange
- 1)->rect
.left
> vsize
)
1079 if(maxRange
== infoPtr
->uNumItem
)
1085 /* If we once had a scroll control... hide it */
1086 if (infoPtr
->hwndUpDown
)
1087 ShowWindow(infoPtr
->hwndUpDown
, SW_HIDE
);
1089 if (infoPtr
->hwndUpDown
)
1090 SendMessageW(infoPtr
->hwndUpDown
, UDM_SETRANGE32
, 0, maxRange
);
1093 /******************************************************************************
1096 * This method will calculate the position rectangles of all the items in the
1097 * control. The rectangle calculated starts at 0 for the first item in the
1098 * list and ignores scrolling and selection.
1099 * It also uses the current font to determine the height of the tab row and
1100 * it checks if all the tabs fit in the client area of the window. If they
1101 * don't, a scrolling control is added.
1103 static void TAB_SetItemBounds (TAB_INFO
*infoPtr
)
1105 TEXTMETRICW fontMetrics
;
1108 INT curItemRowCount
;
1109 HFONT hFont
, hOldFont
;
1118 * We need to get text information so we need a DC and we need to select
1121 hdc
= GetDC(infoPtr
->hwnd
);
1123 hFont
= infoPtr
->hFont
? infoPtr
->hFont
: GetStockObject (SYSTEM_FONT
);
1124 hOldFont
= SelectObject (hdc
, hFont
);
1127 * We will base the rectangle calculations on the client rectangle
1130 GetClientRect(infoPtr
->hwnd
, &clientRect
);
1132 /* if TCS_VERTICAL then swap the height and width so this code places the
1133 tabs along the top of the rectangle and we can just rotate them after
1134 rather than duplicate all of the below code */
1135 if(infoPtr
->dwStyle
& TCS_VERTICAL
)
1137 iTemp
= clientRect
.bottom
;
1138 clientRect
.bottom
= clientRect
.right
;
1139 clientRect
.right
= iTemp
;
1142 /* Now use hPadding and vPadding */
1143 infoPtr
->uHItemPadding
= infoPtr
->uHItemPadding_s
;
1144 infoPtr
->uVItemPadding
= infoPtr
->uVItemPadding_s
;
1146 /* The leftmost item will be "0" aligned */
1148 curItemRowCount
= infoPtr
->uNumItem
? 1 : 0;
1150 if (!(infoPtr
->fHeightSet
))
1153 INT icon_height
= 0, cx
;
1155 /* Use the current font to determine the height of a tab. */
1156 GetTextMetricsW(hdc
, &fontMetrics
);
1158 /* Get the icon height */
1160 ImageList_GetIconSize(infoPtr
->himl
, &cx
, &icon_height
);
1162 /* Take the highest between font or icon */
1163 if (fontMetrics
.tmHeight
> icon_height
)
1164 item_height
= fontMetrics
.tmHeight
+ 2;
1166 item_height
= icon_height
;
1169 * Make sure there is enough space for the letters + icon + growing the
1170 * selected item + extra space for the selected item.
1172 infoPtr
->tabHeight
= item_height
+
1173 ((infoPtr
->dwStyle
& TCS_BUTTONS
) ? 2 : 1) *
1174 infoPtr
->uVItemPadding
;
1176 TRACE("tabH=%d, tmH=%d, iconh=%d\n",
1177 infoPtr
->tabHeight
, fontMetrics
.tmHeight
, icon_height
);
1180 TRACE("client right=%d\n", clientRect
.right
);
1182 /* Get the icon width */
1187 ImageList_GetIconSize(infoPtr
->himl
, &icon_width
, &cy
);
1189 if (infoPtr
->dwStyle
& TCS_FIXEDWIDTH
)
1192 /* Add padding if icon is present */
1193 icon_width
+= infoPtr
->uHItemPadding
;
1196 for (curItem
= 0; curItem
< infoPtr
->uNumItem
; curItem
++)
1198 TAB_ITEM
*curr
= TAB_GetItem(infoPtr
, curItem
);
1200 /* Set the leftmost position of the tab. */
1201 curr
->rect
.left
= curItemLeftPos
;
1203 if (infoPtr
->dwStyle
& TCS_FIXEDWIDTH
)
1205 curr
->rect
.right
= curr
->rect
.left
+
1206 max(infoPtr
->tabWidth
, icon_width
);
1208 else if (!curr
->pszText
)
1210 /* If no text use minimum tab width including padding. */
1211 if (infoPtr
->tabMinWidth
< 0)
1212 curr
->rect
.right
= curr
->rect
.left
+ GET_DEFAULT_MIN_TAB_WIDTH(infoPtr
);
1215 curr
->rect
.right
= curr
->rect
.left
+ infoPtr
->tabMinWidth
;
1217 /* Add extra padding if icon is present */
1218 if (infoPtr
->himl
&& infoPtr
->tabMinWidth
> 0 && infoPtr
->tabMinWidth
< DEFAULT_MIN_TAB_WIDTH
1219 && infoPtr
->uHItemPadding
> 1)
1220 curr
->rect
.right
+= EXTRA_ICON_PADDING
* (infoPtr
->uHItemPadding
-1);
1227 /* Calculate how wide the tab is depending on the text it contains */
1228 GetTextExtentPoint32W(hdc
, curr
->pszText
,
1229 lstrlenW(curr
->pszText
), &size
);
1231 tabwidth
= size
.cx
+ icon_width
+ 2 * infoPtr
->uHItemPadding
;
1233 if (infoPtr
->tabMinWidth
< 0)
1234 tabwidth
= max(tabwidth
, GET_DEFAULT_MIN_TAB_WIDTH(infoPtr
));
1236 tabwidth
= max(tabwidth
, infoPtr
->tabMinWidth
);
1238 curr
->rect
.right
= curr
->rect
.left
+ tabwidth
;
1239 TRACE("for <%s>, l,r=%d,%d\n",
1240 debugstr_w(curr
->pszText
), curr
->rect
.left
, curr
->rect
.right
);
1244 * Check if this is a multiline tab control and if so
1245 * check to see if we should wrap the tabs
1247 * Wrap all these tabs. We will arrange them evenly later.
1251 if (((infoPtr
->dwStyle
& TCS_MULTILINE
) || (infoPtr
->dwStyle
& TCS_VERTICAL
)) &&
1253 (clientRect
.right
- CONTROL_BORDER_SIZEX
- DISPLAY_AREA_PADDINGX
)))
1255 curr
->rect
.right
-= curr
->rect
.left
;
1257 curr
->rect
.left
= 0;
1259 TRACE("wrapping <%s>, l,r=%d,%d\n", debugstr_w(curr
->pszText
),
1260 curr
->rect
.left
, curr
->rect
.right
);
1263 curr
->rect
.bottom
= 0;
1264 curr
->rect
.top
= curItemRowCount
- 1;
1266 TRACE("Rect: %s\n", wine_dbgstr_rect(&curr
->rect
));
1269 * The leftmost position of the next item is the rightmost position
1272 if (infoPtr
->dwStyle
& TCS_BUTTONS
)
1274 curItemLeftPos
= curr
->rect
.right
+ BUTTON_SPACINGX
;
1275 if (infoPtr
->dwStyle
& TCS_FLATBUTTONS
)
1276 curItemLeftPos
+= FLAT_BTN_SPACINGX
;
1279 curItemLeftPos
= curr
->rect
.right
;
1282 if (!((infoPtr
->dwStyle
& TCS_MULTILINE
) || (infoPtr
->dwStyle
& TCS_VERTICAL
)))
1285 * Check if we need a scrolling control.
1287 infoPtr
->needsScrolling
= (curItemLeftPos
+ (2 * SELECTED_TAB_OFFSET
) >
1290 /* Don't need scrolling, then update infoPtr->leftmostVisible */
1291 if(!infoPtr
->needsScrolling
)
1292 infoPtr
->leftmostVisible
= 0;
1297 * No scrolling in Multiline or Vertical styles.
1299 infoPtr
->needsScrolling
= FALSE
;
1300 infoPtr
->leftmostVisible
= 0;
1302 TAB_SetupScrolling(infoPtr
, &clientRect
);
1304 /* Set the number of rows */
1305 infoPtr
->uNumRows
= curItemRowCount
;
1307 /* Arrange all tabs evenly if style says so */
1308 if (!(infoPtr
->dwStyle
& TCS_RAGGEDRIGHT
) &&
1309 ((infoPtr
->dwStyle
& TCS_MULTILINE
) || (infoPtr
->dwStyle
& TCS_VERTICAL
)) &&
1310 (infoPtr
->uNumItem
> 0) &&
1311 (infoPtr
->uNumRows
> 1))
1313 INT tabPerRow
,remTab
,iRow
;
1318 * Ok windows tries to even out the rows. place the same
1319 * number of tabs in each row. So lets give that a shot
1322 tabPerRow
= infoPtr
->uNumItem
/ (infoPtr
->uNumRows
);
1323 remTab
= infoPtr
->uNumItem
% (infoPtr
->uNumRows
);
1325 for (iItm
=0,iRow
=0,iCount
=0,curItemLeftPos
=0;
1326 iItm
<infoPtr
->uNumItem
;
1329 /* normalize the current rect */
1330 TAB_ITEM
*curr
= TAB_GetItem(infoPtr
, iItm
);
1332 /* shift the item to the left side of the clientRect */
1333 curr
->rect
.right
-= curr
->rect
.left
;
1334 curr
->rect
.left
= 0;
1336 TRACE("r=%d, cl=%d, cl.r=%d, iCount=%d, iRow=%d, uNumRows=%d, remTab=%d, tabPerRow=%d\n",
1337 curr
->rect
.right
, curItemLeftPos
, clientRect
.right
,
1338 iCount
, iRow
, infoPtr
->uNumRows
, remTab
, tabPerRow
);
1340 /* if we have reached the maximum number of tabs on this row */
1341 /* move to the next row, reset our current item left position and */
1342 /* the count of items on this row */
1344 if (infoPtr
->dwStyle
& TCS_VERTICAL
) {
1345 /* Vert: Add the remaining tabs in the *last* remainder rows */
1346 if (iCount
>= ((iRow
>=(INT
)infoPtr
->uNumRows
- remTab
)?tabPerRow
+ 1:tabPerRow
)) {
1352 /* Horz: Add the remaining tabs in the *first* remainder rows */
1353 if (iCount
>= ((iRow
<remTab
)?tabPerRow
+ 1:tabPerRow
)) {
1360 /* shift the item to the right to place it as the next item in this row */
1361 curr
->rect
.left
+= curItemLeftPos
;
1362 curr
->rect
.right
+= curItemLeftPos
;
1363 curr
->rect
.top
= iRow
;
1364 if (infoPtr
->dwStyle
& TCS_BUTTONS
)
1366 curItemLeftPos
= curr
->rect
.right
+ 1;
1367 if (infoPtr
->dwStyle
& TCS_FLATBUTTONS
)
1368 curItemLeftPos
+= FLAT_BTN_SPACINGX
;
1371 curItemLeftPos
= curr
->rect
.right
;
1373 TRACE("arranging <%s>, l,r=%d,%d, row=%d\n",
1374 debugstr_w(curr
->pszText
), curr
->rect
.left
,
1375 curr
->rect
.right
, curr
->rect
.top
);
1382 INT widthDiff
, iIndexStart
=0, iIndexEnd
=0;
1386 while(iIndexStart
< infoPtr
->uNumItem
)
1388 TAB_ITEM
*start
= TAB_GetItem(infoPtr
, iIndexStart
);
1391 * find the index of the row
1393 /* find the first item on the next row */
1394 for (iIndexEnd
=iIndexStart
;
1395 (iIndexEnd
< infoPtr
->uNumItem
) &&
1396 (TAB_GetItem(infoPtr
, iIndexEnd
)->rect
.top
==
1399 /* intentionally blank */;
1402 * we need to justify these tabs so they fill the whole given
1406 /* find the amount of space remaining on this row */
1407 widthDiff
= clientRect
.right
- (2 * SELECTED_TAB_OFFSET
) -
1408 TAB_GetItem(infoPtr
, iIndexEnd
- 1)->rect
.right
;
1410 /* iCount is the number of tab items on this row */
1411 iCount
= iIndexEnd
- iIndexStart
;
1415 remainder
= widthDiff
% iCount
;
1416 widthDiff
= widthDiff
/ iCount
;
1417 /* add widthDiff/iCount, or extra space/items on row, to each item on this row */
1418 for (iIndex
=iIndexStart
, iCount
=0; iIndex
< iIndexEnd
; iIndex
++, iCount
++)
1420 TAB_ITEM
*item
= TAB_GetItem(infoPtr
, iIndex
);
1422 item
->rect
.left
+= iCount
* widthDiff
;
1423 item
->rect
.right
+= (iCount
+ 1) * widthDiff
;
1425 TRACE("adjusting 1 <%s>, l,r=%d,%d\n",
1426 debugstr_w(item
->pszText
),
1427 item
->rect
.left
, item
->rect
.right
);
1430 TAB_GetItem(infoPtr
, iIndex
- 1)->rect
.right
+= remainder
;
1432 else /* we have only one item on this row, make it take up the entire row */
1434 start
->rect
.left
= clientRect
.left
;
1435 start
->rect
.right
= clientRect
.right
- 4;
1437 TRACE("adjusting 2 <%s>, l,r=%d,%d\n",
1438 debugstr_w(start
->pszText
),
1439 start
->rect
.left
, start
->rect
.right
);
1444 iIndexStart
= iIndexEnd
;
1449 /* if TCS_VERTICAL rotate the tabs so they are along the side of the clientRect */
1450 if(infoPtr
->dwStyle
& TCS_VERTICAL
)
1453 for(iIndex
= 0; iIndex
< infoPtr
->uNumItem
; iIndex
++)
1455 rcItem
= &TAB_GetItem(infoPtr
, iIndex
)->rect
;
1457 rcOriginal
= *rcItem
;
1459 /* this is rotating the items by 90 degrees clockwise around the center of the control */
1460 rcItem
->top
= (rcOriginal
.left
- clientRect
.left
);
1461 rcItem
->bottom
= rcItem
->top
+ (rcOriginal
.right
- rcOriginal
.left
);
1462 rcItem
->left
= rcOriginal
.top
;
1463 rcItem
->right
= rcOriginal
.bottom
;
1467 TAB_EnsureSelectionVisible(infoPtr
);
1468 TAB_RecalcHotTrack(infoPtr
, NULL
, NULL
, NULL
);
1471 SelectObject (hdc
, hOldFont
);
1472 ReleaseDC (infoPtr
->hwnd
, hdc
);
1477 TAB_EraseTabInterior(const TAB_INFO
*infoPtr
, HDC hdc
, INT iItem
, const RECT
*drawRect
)
1479 HBRUSH hbr
= CreateSolidBrush (comctl32_color
.clrBtnFace
);
1480 BOOL deleteBrush
= TRUE
;
1481 RECT rTemp
= *drawRect
;
1483 if (infoPtr
->dwStyle
& TCS_BUTTONS
)
1485 if (iItem
== infoPtr
->iSelected
)
1487 /* Background color */
1488 if (!(infoPtr
->dwStyle
& TCS_OWNERDRAWFIXED
))
1491 hbr
= GetSysColorBrush(COLOR_SCROLLBAR
);
1493 SetTextColor(hdc
, comctl32_color
.clr3dFace
);
1494 SetBkColor(hdc
, comctl32_color
.clr3dHilight
);
1496 /* if COLOR_WINDOW happens to be the same as COLOR_3DHILIGHT
1497 * we better use 0x55aa bitmap brush to make scrollbar's background
1498 * look different from the window background.
1500 if (comctl32_color
.clr3dHilight
== comctl32_color
.clrWindow
)
1501 hbr
= COMCTL32_hPattern55AABrush
;
1503 deleteBrush
= FALSE
;
1505 FillRect(hdc
, &rTemp
, hbr
);
1507 else /* ! selected */
1509 if (infoPtr
->dwStyle
& TCS_FLATBUTTONS
)
1511 InflateRect(&rTemp
, 2, 2);
1512 FillRect(hdc
, &rTemp
, hbr
);
1513 if (iItem
== infoPtr
->iHotTracked
||
1514 (iItem
!= infoPtr
->iSelected
&& iItem
== infoPtr
->uFocus
))
1515 DrawEdge(hdc
, &rTemp
, BDR_RAISEDINNER
, BF_RECT
);
1518 FillRect(hdc
, &rTemp
, hbr
);
1522 else /* !TCS_BUTTONS */
1524 InflateRect(&rTemp
, -2, -2);
1525 if (!GetWindowTheme (infoPtr
->hwnd
))
1526 FillRect(hdc
, &rTemp
, hbr
);
1529 /* highlighting is drawn on top of previous fills */
1530 if (TAB_GetItem(infoPtr
, iItem
)->dwState
& TCIS_HIGHLIGHTED
)
1535 deleteBrush
= FALSE
;
1537 hbr
= GetSysColorBrush(COLOR_HIGHLIGHT
);
1538 FillRect(hdc
, &rTemp
, hbr
);
1542 if (deleteBrush
) DeleteObject(hbr
);
1545 /******************************************************************************
1546 * TAB_DrawItemInterior
1548 * This method is used to draw the interior (text and icon) of a single tab
1549 * into the tab control.
1552 TAB_DrawItemInterior(const TAB_INFO
*infoPtr
, HDC hdc
, INT iItem
, RECT
*drawRect
)
1561 /* if (drawRect == NULL) */
1568 * Get the rectangle for the item.
1570 isVisible
= TAB_InternalGetItemRect(infoPtr
, iItem
, &itemRect
, &selectedRect
);
1575 * Make sure drawRect points to something valid; simplifies code.
1577 drawRect
= &localRect
;
1580 * This logic copied from the part of TAB_DrawItem which draws
1581 * the tab background. It's important to keep it in sync. I
1582 * would have liked to avoid code duplication, but couldn't figure
1583 * out how without making spaghetti of TAB_DrawItem.
1585 if (iItem
== infoPtr
->iSelected
)
1586 *drawRect
= selectedRect
;
1588 *drawRect
= itemRect
;
1590 if (infoPtr
->dwStyle
& TCS_BUTTONS
)
1592 if (iItem
== infoPtr
->iSelected
)
1594 drawRect
->left
+= 4;
1596 drawRect
->right
-= 4;
1598 if (infoPtr
->dwStyle
& TCS_VERTICAL
)
1600 if (!(infoPtr
->dwStyle
& TCS_BOTTOM
)) drawRect
->right
+= 1;
1601 drawRect
->bottom
-= 4;
1605 if (infoPtr
->dwStyle
& TCS_BOTTOM
)
1608 drawRect
->bottom
-= 4;
1611 drawRect
->bottom
-= 1;
1616 drawRect
->left
+= 2;
1618 drawRect
->right
-= 2;
1619 drawRect
->bottom
-= 2;
1624 if ((infoPtr
->dwStyle
& TCS_VERTICAL
) && (infoPtr
->dwStyle
& TCS_BOTTOM
))
1626 if (iItem
!= infoPtr
->iSelected
)
1628 drawRect
->left
+= 2;
1630 drawRect
->bottom
-= 2;
1633 else if (infoPtr
->dwStyle
& TCS_VERTICAL
)
1635 if (iItem
== infoPtr
->iSelected
)
1637 drawRect
->right
+= 1;
1642 drawRect
->right
-= 2;
1643 drawRect
->bottom
-= 2;
1646 else if (infoPtr
->dwStyle
& TCS_BOTTOM
)
1648 if (iItem
== infoPtr
->iSelected
)
1654 InflateRect(drawRect
, -2, -2);
1655 drawRect
->bottom
+= 2;
1660 if (iItem
== infoPtr
->iSelected
)
1662 drawRect
->bottom
+= 3;
1666 drawRect
->bottom
-= 2;
1667 InflateRect(drawRect
, -2, 0);
1672 TRACE("drawRect=(%s)\n", wine_dbgstr_rect(drawRect
));
1674 /* Clear interior */
1675 TAB_EraseTabInterior (infoPtr
, hdc
, iItem
, drawRect
);
1677 /* Draw the focus rectangle */
1678 if (!(infoPtr
->dwStyle
& TCS_FOCUSNEVER
) &&
1679 (GetFocus() == infoPtr
->hwnd
) &&
1680 (iItem
== infoPtr
->uFocus
) )
1682 RECT rFocus
= *drawRect
;
1684 if (!(infoPtr
->dwStyle
& TCS_BUTTONS
)) InflateRect(&rFocus
, -3, -3);
1685 if (infoPtr
->dwStyle
& TCS_BOTTOM
&& !(infoPtr
->dwStyle
& TCS_VERTICAL
))
1688 /* focus should stay on selected item for TCS_BUTTONS style */
1689 if (!((infoPtr
->dwStyle
& TCS_BUTTONS
) && (infoPtr
->iSelected
!= iItem
)))
1690 DrawFocusRect(hdc
, &rFocus
);
1696 htextPen
= CreatePen( PS_SOLID
, 1, comctl32_color
.clrBtnText
);
1697 holdPen
= SelectObject(hdc
, htextPen
);
1698 hOldFont
= SelectObject(hdc
, infoPtr
->hFont
);
1701 * Setup for text output
1703 oldBkMode
= SetBkMode(hdc
, TRANSPARENT
);
1704 if (!GetWindowTheme (infoPtr
->hwnd
) || (infoPtr
->dwStyle
& TCS_BUTTONS
))
1706 if ((infoPtr
->dwStyle
& TCS_HOTTRACK
) && (iItem
== infoPtr
->iHotTracked
) &&
1707 !(infoPtr
->dwStyle
& TCS_FLATBUTTONS
))
1708 SetTextColor(hdc
, comctl32_color
.clrHighlight
);
1709 else if (TAB_GetItem(infoPtr
, iItem
)->dwState
& TCIS_HIGHLIGHTED
)
1710 SetTextColor(hdc
, comctl32_color
.clrHighlightText
);
1712 SetTextColor(hdc
, comctl32_color
.clrBtnText
);
1716 * if owner draw, tell the owner to draw
1718 if ((infoPtr
->dwStyle
& TCS_OWNERDRAWFIXED
) && IsWindow(infoPtr
->hwndNotify
))
1724 drawRect
->right
-= 1;
1725 if ( iItem
== infoPtr
->iSelected
)
1727 drawRect
->right
-= 1;
1728 drawRect
->left
+= 1;
1731 id
= (UINT
)GetWindowLongPtrW( infoPtr
->hwnd
, GWLP_ID
);
1733 /* fill DRAWITEMSTRUCT */
1734 dis
.CtlType
= ODT_TAB
;
1737 dis
.itemAction
= ODA_DRAWENTIRE
;
1739 if ( iItem
== infoPtr
->iSelected
)
1740 dis
.itemState
|= ODS_SELECTED
;
1741 if (infoPtr
->uFocus
== iItem
)
1742 dis
.itemState
|= ODS_FOCUS
;
1743 dis
.hwndItem
= infoPtr
->hwnd
;
1745 CopyRect(&dis
.rcItem
,drawRect
);
1747 /* when extra data fits ULONG_PTR, store it directly */
1748 if (infoPtr
->cbInfo
> sizeof(LPARAM
))
1749 dis
.itemData
= (ULONG_PTR
) TAB_GetItem(infoPtr
, iItem
)->extra
;
1752 /* this could be considered broken on 64 bit, but that's how it works -
1753 only first 4 bytes are copied */
1755 memcpy(&dis
.itemData
, (ULONG_PTR
*)TAB_GetItem(infoPtr
, iItem
)->extra
, 4);
1758 /* draw notification */
1759 SendMessageW( infoPtr
->hwndNotify
, WM_DRAWITEM
, id
, (LPARAM
)&dis
);
1763 TAB_ITEM
*item
= TAB_GetItem(infoPtr
, iItem
);
1767 /* used to center the icon and text in the tab */
1769 INT center_offset_h
, center_offset_v
;
1771 /* set rcImage to drawRect, we will use top & left in our ImageList_Draw call */
1772 rcImage
= *drawRect
;
1776 rcText
.left
= rcText
.top
= rcText
.right
= rcText
.bottom
= 0;
1778 /* get the rectangle that the text fits in */
1781 DrawTextW(hdc
, item
->pszText
, -1, &rcText
, DT_CALCRECT
);
1784 * If not owner draw, then do the drawing ourselves.
1788 if (infoPtr
->himl
&& item
->iImage
!= -1)
1793 ImageList_GetIconSize(infoPtr
->himl
, &cx
, &cy
);
1795 if(infoPtr
->dwStyle
& TCS_VERTICAL
)
1797 center_offset_h
= ((drawRect
->bottom
- drawRect
->top
) - (cy
+ infoPtr
->uHItemPadding
+ (rcText
.right
- rcText
.left
))) / 2;
1798 center_offset_v
= ((drawRect
->right
- drawRect
->left
) - cx
) / 2;
1802 center_offset_h
= ((drawRect
->right
- drawRect
->left
) - (cx
+ infoPtr
->uHItemPadding
+ (rcText
.right
- rcText
.left
))) / 2;
1803 center_offset_v
= ((drawRect
->bottom
- drawRect
->top
) - cy
) / 2;
1806 /* if an item is selected, the icon is shifted up instead of down */
1807 if (iItem
== infoPtr
->iSelected
)
1808 center_offset_v
-= infoPtr
->uVItemPadding
/ 2;
1810 center_offset_v
+= infoPtr
->uVItemPadding
/ 2;
1812 if (infoPtr
->dwStyle
& TCS_FIXEDWIDTH
&& infoPtr
->dwStyle
& (TCS_FORCELABELLEFT
| TCS_FORCEICONLEFT
))
1813 center_offset_h
= infoPtr
->uHItemPadding
;
1815 if (center_offset_h
< 2)
1816 center_offset_h
= 2;
1818 if (center_offset_v
< 0)
1819 center_offset_v
= 0;
1821 TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%s), textlen=%d\n",
1822 debugstr_w(item
->pszText
), center_offset_h
, center_offset_v
,
1823 wine_dbgstr_rect(drawRect
), (rcText
.right
-rcText
.left
));
1825 if((infoPtr
->dwStyle
& TCS_VERTICAL
) && (infoPtr
->dwStyle
& TCS_BOTTOM
))
1827 rcImage
.top
= drawRect
->top
+ center_offset_h
;
1828 /* if tab is TCS_VERTICAL and TCS_BOTTOM, the text is drawn from the */
1829 /* right side of the tab, but the image still uses the left as its x position */
1830 /* this keeps the image always drawn off of the same side of the tab */
1831 rcImage
.left
= drawRect
->right
- cx
- center_offset_v
;
1832 drawRect
->top
+= cy
+ infoPtr
->uHItemPadding
;
1834 else if(infoPtr
->dwStyle
& TCS_VERTICAL
)
1836 rcImage
.top
= drawRect
->bottom
- cy
- center_offset_h
;
1837 rcImage
.left
= drawRect
->left
+ center_offset_v
;
1838 drawRect
->bottom
-= cy
+ infoPtr
->uHItemPadding
;
1840 else /* normal style, whether TCS_BOTTOM or not */
1842 rcImage
.left
= drawRect
->left
+ center_offset_h
;
1843 rcImage
.top
= drawRect
->top
+ center_offset_v
;
1844 drawRect
->left
+= cx
+ infoPtr
->uHItemPadding
;
1847 TRACE("drawing image=%d, left=%d, top=%d\n",
1848 item
->iImage
, rcImage
.left
, rcImage
.top
-1);
1860 /* Now position text */
1861 if (infoPtr
->dwStyle
& TCS_FIXEDWIDTH
&& infoPtr
->dwStyle
& TCS_FORCELABELLEFT
)
1862 center_offset_h
= infoPtr
->uHItemPadding
;
1864 if(infoPtr
->dwStyle
& TCS_VERTICAL
)
1865 center_offset_h
= ((drawRect
->bottom
- drawRect
->top
) - (rcText
.right
- rcText
.left
)) / 2;
1867 center_offset_h
= ((drawRect
->right
- drawRect
->left
) - (rcText
.right
- rcText
.left
)) / 2;
1869 if(infoPtr
->dwStyle
& TCS_VERTICAL
)
1871 if(infoPtr
->dwStyle
& TCS_BOTTOM
)
1872 drawRect
->top
+=center_offset_h
;
1874 drawRect
->bottom
-=center_offset_h
;
1876 center_offset_v
= ((drawRect
->right
- drawRect
->left
) - (rcText
.bottom
- rcText
.top
)) / 2;
1880 drawRect
->left
+= center_offset_h
;
1881 center_offset_v
= ((drawRect
->bottom
- drawRect
->top
) - (rcText
.bottom
- rcText
.top
)) / 2;
1884 /* if an item is selected, the text is shifted up instead of down */
1885 if (iItem
== infoPtr
->iSelected
)
1886 center_offset_v
-= infoPtr
->uVItemPadding
/ 2;
1888 center_offset_v
+= infoPtr
->uVItemPadding
/ 2;
1890 if (center_offset_v
< 0)
1891 center_offset_v
= 0;
1893 if(infoPtr
->dwStyle
& TCS_VERTICAL
)
1894 drawRect
->left
+= center_offset_v
;
1896 drawRect
->top
+= center_offset_v
;
1899 if(infoPtr
->dwStyle
& TCS_VERTICAL
) /* if we are vertical rotate the text and each character */
1903 INT nEscapement
= 900;
1904 INT nOrientation
= 900;
1906 if(infoPtr
->dwStyle
& TCS_BOTTOM
)
1909 nOrientation
= -900;
1912 /* to get a font with the escapement and orientation we are looking for, we need to */
1913 /* call CreateFontIndirect, which requires us to set the values of the logfont we pass in */
1914 if (!GetObjectW(infoPtr
->hFont
, sizeof(logfont
), &logfont
))
1915 GetObjectW(GetStockObject(DEFAULT_GUI_FONT
), sizeof(logfont
), &logfont
);
1917 logfont
.lfEscapement
= nEscapement
;
1918 logfont
.lfOrientation
= nOrientation
;
1919 hFont
= CreateFontIndirectW(&logfont
);
1920 SelectObject(hdc
, hFont
);
1925 (infoPtr
->dwStyle
& TCS_BOTTOM
) ? drawRect
->right
: drawRect
->left
,
1926 (!(infoPtr
->dwStyle
& TCS_BOTTOM
)) ? drawRect
->bottom
: drawRect
->top
,
1930 lstrlenW(item
->pszText
),
1934 DeleteObject(hFont
);
1938 TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%s), textlen=%d\n",
1939 debugstr_w(item
->pszText
), center_offset_h
, center_offset_v
,
1940 wine_dbgstr_rect(drawRect
), (rcText
.right
-rcText
.left
));
1947 lstrlenW(item
->pszText
),
1949 DT_LEFT
| DT_SINGLELINE
1954 *drawRect
= rcTemp
; /* restore drawRect */
1960 SelectObject(hdc
, hOldFont
);
1961 SetBkMode(hdc
, oldBkMode
);
1962 SelectObject(hdc
, holdPen
);
1963 DeleteObject( htextPen
);
1966 /******************************************************************************
1969 * This method is used to draw a single tab into the tab control.
1971 static void TAB_DrawItem(const TAB_INFO
*infoPtr
, HDC hdc
, INT iItem
)
1976 RECT r
, fillRect
, r1
;
1979 COLORREF bkgnd
, corner
;
1983 * Get the rectangle for the item.
1985 isVisible
= TAB_InternalGetItemRect(infoPtr
,
1994 /* Clip UpDown control to not draw over it */
1995 if (infoPtr
->needsScrolling
)
1997 GetWindowRect(infoPtr
->hwnd
, &rC
);
1998 GetWindowRect(infoPtr
->hwndUpDown
, &rUD
);
1999 ExcludeClipRect(hdc
, rUD
.left
- rC
.left
, rUD
.top
- rC
.top
, rUD
.right
- rC
.left
, rUD
.bottom
- rC
.top
);
2002 /* If you need to see what the control is doing,
2003 * then override these variables. They will change what
2004 * fill colors are used for filling the tabs, and the
2005 * corners when drawing the edge.
2007 bkgnd
= comctl32_color
.clrBtnFace
;
2008 corner
= comctl32_color
.clrBtnFace
;
2010 if (infoPtr
->dwStyle
& TCS_BUTTONS
)
2012 /* Get item rectangle */
2015 /* Separators between flat buttons */
2016 if ((infoPtr
->dwStyle
& TCS_FLATBUTTONS
) && (infoPtr
->exStyle
& TCS_EX_FLATSEPARATORS
))
2019 r1
.right
+= (FLAT_BTN_SPACINGX
-2);
2020 DrawEdge(hdc
, &r1
, EDGE_ETCHED
, BF_RIGHT
);
2023 if (iItem
== infoPtr
->iSelected
)
2025 DrawEdge(hdc
, &r
, EDGE_SUNKEN
, BF_SOFT
|BF_RECT
);
2027 OffsetRect(&r
, 1, 1);
2029 else /* ! selected */
2031 DWORD state
= TAB_GetItem(infoPtr
, iItem
)->dwState
;
2033 if ((state
& TCIS_BUTTONPRESSED
) || (iItem
== infoPtr
->uFocus
))
2034 DrawEdge(hdc
, &r
, EDGE_SUNKEN
, BF_SOFT
|BF_RECT
);
2036 if (!(infoPtr
->dwStyle
& TCS_FLATBUTTONS
))
2037 DrawEdge(hdc
, &r
, EDGE_RAISED
, BF_SOFT
|BF_RECT
);
2040 else /* !TCS_BUTTONS */
2042 /* We draw a rectangle of different sizes depending on the selection
2044 if (iItem
== infoPtr
->iSelected
) {
2046 GetClientRect (infoPtr
->hwnd
, &rect
);
2047 clRight
= rect
.right
;
2048 clBottom
= rect
.bottom
;
2055 * Erase the background. (Delay it but setup rectangle.)
2056 * This is necessary when drawing the selected item since it is larger
2057 * than the others, it might overlap with stuff already drawn by the
2062 /* Draw themed tabs - but only if they are at the top.
2063 * Windows draws even side or bottom tabs themed, with wacky results.
2064 * However, since in Wine apps may get themed that did not opt in via
2065 * a manifest avoid theming when we know the result will be wrong */
2066 if ((theme
= GetWindowTheme (infoPtr
->hwnd
))
2067 && ((infoPtr
->dwStyle
& (TCS_VERTICAL
| TCS_BOTTOM
)) == 0))
2069 static const int partIds
[8] = {
2072 TABP_TABITEMLEFTEDGE
,
2073 TABP_TABITEMRIGHTEDGE
,
2074 TABP_TABITEMBOTHEDGE
,
2077 TABP_TOPTABITEMLEFTEDGE
,
2078 TABP_TOPTABITEMRIGHTEDGE
,
2079 TABP_TOPTABITEMBOTHEDGE
,
2082 int stateId
= TIS_NORMAL
;
2084 /* selected and unselected tabs have different parts */
2085 if (iItem
== infoPtr
->iSelected
)
2087 /* The part also differs on the position of a tab on a line.
2088 * "Visually" determining the position works well enough. */
2089 GetClientRect(infoPtr
->hwnd
, &r1
);
2090 if(selectedRect
.left
== 0)
2092 if(selectedRect
.right
== r1
.right
)
2095 if (iItem
== infoPtr
->iSelected
)
2096 stateId
= TIS_SELECTED
;
2097 else if (iItem
== infoPtr
->iHotTracked
)
2099 else if (iItem
== infoPtr
->uFocus
)
2100 stateId
= TIS_FOCUSED
;
2102 /* Adjust rectangle for bottommost row */
2103 if (TAB_GetItem(infoPtr
, iItem
)->rect
.top
== infoPtr
->uNumRows
-1)
2106 DrawThemeBackground (theme
, hdc
, partIds
[partIndex
], stateId
, &r
, NULL
);
2107 GetThemeBackgroundContentRect (theme
, hdc
, partIds
[partIndex
], stateId
, &r
, &r
);
2109 else if(infoPtr
->dwStyle
& TCS_VERTICAL
)
2111 /* These are for adjusting the drawing of a Selected tab */
2112 /* The initial values are for the normal case of non-Selected */
2113 int ZZ
= 1; /* Do not stretch if selected */
2114 if (iItem
== infoPtr
->iSelected
) {
2117 /* if leftmost draw the line longer */
2118 if(selectedRect
.top
== 0)
2119 fillRect
.top
+= CONTROL_BORDER_SIZEY
;
2120 /* if rightmost draw the line longer */
2121 if(selectedRect
.bottom
== clBottom
)
2122 fillRect
.bottom
-= CONTROL_BORDER_SIZEY
;
2125 if (infoPtr
->dwStyle
& TCS_BOTTOM
)
2127 /* Adjust both rectangles to match native */
2130 TRACE("<right> item=%d, fill=(%s), edge=(%s)\n",
2131 iItem
, wine_dbgstr_rect(&fillRect
), wine_dbgstr_rect(&r
));
2133 /* Clear interior */
2134 SetBkColor(hdc
, bkgnd
);
2135 ExtTextOutW(hdc
, 0, 0, 2, &fillRect
, NULL
, 0, 0);
2137 /* Draw rectangular edge around tab */
2138 DrawEdge(hdc
, &r
, EDGE_RAISED
, BF_SOFT
|BF_RIGHT
|BF_TOP
|BF_BOTTOM
);
2140 /* Now erase the top corner and draw diagonal edge */
2141 SetBkColor(hdc
, corner
);
2142 r1
.left
= r
.right
- ROUND_CORNER_SIZE
- 1;
2145 r1
.bottom
= r1
.top
+ ROUND_CORNER_SIZE
;
2146 ExtTextOutW(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2148 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_DIAGONAL_ENDTOPLEFT
);
2150 /* Now erase the bottom corner and draw diagonal edge */
2151 r1
.left
= r
.right
- ROUND_CORNER_SIZE
- 1;
2152 r1
.bottom
= r
.bottom
;
2154 r1
.top
= r1
.bottom
- ROUND_CORNER_SIZE
;
2155 ExtTextOutW(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2157 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_DIAGONAL_ENDBOTTOMLEFT
);
2159 if ((iItem
== infoPtr
->iSelected
) && (selectedRect
.top
== 0)) {
2163 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_TOP
);
2169 TRACE("<left> item=%d, fill=(%s), edge=(%s)\n",
2170 iItem
, wine_dbgstr_rect(&fillRect
), wine_dbgstr_rect(&r
));
2172 /* Clear interior */
2173 SetBkColor(hdc
, bkgnd
);
2174 ExtTextOutW(hdc
, 0, 0, 2, &fillRect
, NULL
, 0, 0);
2176 /* Draw rectangular edge around tab */
2177 DrawEdge(hdc
, &r
, EDGE_RAISED
, BF_SOFT
|BF_LEFT
|BF_TOP
|BF_BOTTOM
);
2179 /* Now erase the top corner and draw diagonal edge */
2180 SetBkColor(hdc
, corner
);
2183 r1
.right
= r1
.left
+ ROUND_CORNER_SIZE
+ 1;
2184 r1
.bottom
= r1
.top
+ ROUND_CORNER_SIZE
;
2185 ExtTextOutW(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2187 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_DIAGONAL_ENDTOPRIGHT
);
2189 /* Now erase the bottom corner and draw diagonal edge */
2191 r1
.bottom
= r
.bottom
;
2192 r1
.right
= r1
.left
+ ROUND_CORNER_SIZE
+ 1;
2193 r1
.top
= r1
.bottom
- ROUND_CORNER_SIZE
;
2194 ExtTextOutW(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2196 DrawEdge(hdc
, &r1
, EDGE_SUNKEN
, BF_DIAGONAL_ENDTOPLEFT
);
2199 else /* ! TCS_VERTICAL */
2201 /* These are for adjusting the drawing of a Selected tab */
2202 /* The initial values are for the normal case of non-Selected */
2203 if (iItem
== infoPtr
->iSelected
) {
2204 /* if leftmost draw the line longer */
2205 if(selectedRect
.left
== 0)
2206 fillRect
.left
+= CONTROL_BORDER_SIZEX
;
2207 /* if rightmost draw the line longer */
2208 if(selectedRect
.right
== clRight
)
2209 fillRect
.right
-= CONTROL_BORDER_SIZEX
;
2212 if (infoPtr
->dwStyle
& TCS_BOTTOM
)
2214 /* Adjust both rectangles for topmost row */
2215 if (TAB_GetItem(infoPtr
, iItem
)->rect
.top
== infoPtr
->uNumRows
-1)
2221 TRACE("<bottom> item=%d, fill=(%s), edge=(%s)\n",
2222 iItem
, wine_dbgstr_rect(&fillRect
), wine_dbgstr_rect(&r
));
2224 /* Clear interior */
2225 SetBkColor(hdc
, bkgnd
);
2226 ExtTextOutW(hdc
, 0, 0, 2, &fillRect
, NULL
, 0, 0);
2228 /* Draw rectangular edge around tab */
2229 DrawEdge(hdc
, &r
, EDGE_RAISED
, BF_SOFT
|BF_LEFT
|BF_BOTTOM
|BF_RIGHT
);
2231 /* Now erase the righthand corner and draw diagonal edge */
2232 SetBkColor(hdc
, corner
);
2233 r1
.left
= r
.right
- ROUND_CORNER_SIZE
;
2234 r1
.bottom
= r
.bottom
;
2236 r1
.top
= r1
.bottom
- ROUND_CORNER_SIZE
- 1;
2237 ExtTextOutW(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2239 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_DIAGONAL_ENDBOTTOMLEFT
);
2241 /* Now erase the lefthand corner and draw diagonal edge */
2243 r1
.bottom
= r
.bottom
;
2244 r1
.right
= r1
.left
+ ROUND_CORNER_SIZE
;
2245 r1
.top
= r1
.bottom
- ROUND_CORNER_SIZE
- 1;
2246 ExtTextOutW(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2248 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_DIAGONAL_ENDTOPLEFT
);
2250 if (iItem
== infoPtr
->iSelected
)
2254 if (selectedRect
.left
== 0)
2259 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_LEFT
);
2266 /* Adjust both rectangles for bottommost row */
2267 if (TAB_GetItem(infoPtr
, iItem
)->rect
.top
== infoPtr
->uNumRows
-1)
2269 fillRect
.bottom
+= 3;
2273 TRACE("<top> item=%d, fill=(%s), edge=(%s)\n",
2274 iItem
, wine_dbgstr_rect(&fillRect
), wine_dbgstr_rect(&r
));
2276 /* Clear interior */
2277 SetBkColor(hdc
, bkgnd
);
2278 ExtTextOutW(hdc
, 0, 0, 2, &fillRect
, NULL
, 0, 0);
2280 /* Draw rectangular edge around tab */
2281 DrawEdge(hdc
, &r
, EDGE_RAISED
, BF_SOFT
|BF_LEFT
|BF_TOP
|BF_RIGHT
);
2283 /* Now erase the righthand corner and draw diagonal edge */
2284 SetBkColor(hdc
, corner
);
2285 r1
.left
= r
.right
- ROUND_CORNER_SIZE
;
2288 r1
.bottom
= r1
.top
+ ROUND_CORNER_SIZE
+ 1;
2289 ExtTextOutW(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2291 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_DIAGONAL_ENDBOTTOMRIGHT
);
2293 /* Now erase the lefthand corner and draw diagonal edge */
2296 r1
.right
= r1
.left
+ ROUND_CORNER_SIZE
;
2297 r1
.bottom
= r1
.top
+ ROUND_CORNER_SIZE
+ 1;
2298 ExtTextOutW(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2300 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_DIAGONAL_ENDTOPRIGHT
);
2305 TAB_DumpItemInternal(infoPtr
, iItem
);
2307 /* This modifies r to be the text rectangle. */
2308 TAB_DrawItemInterior(infoPtr
, hdc
, iItem
, &r
);
2312 /******************************************************************************
2315 * This method is used to draw the raised border around the tab control
2318 static void TAB_DrawBorder(const TAB_INFO
*infoPtr
, HDC hdc
)
2321 HTHEME theme
= GetWindowTheme (infoPtr
->hwnd
);
2323 GetClientRect (infoPtr
->hwnd
, &rect
);
2326 * Adjust for the style
2329 if (infoPtr
->uNumItem
)
2331 if ((infoPtr
->dwStyle
& TCS_BOTTOM
) && !(infoPtr
->dwStyle
& TCS_VERTICAL
))
2332 rect
.bottom
-= infoPtr
->tabHeight
* infoPtr
->uNumRows
+ CONTROL_BORDER_SIZEX
;
2333 else if((infoPtr
->dwStyle
& TCS_BOTTOM
) && (infoPtr
->dwStyle
& TCS_VERTICAL
))
2334 rect
.right
-= infoPtr
->tabHeight
* infoPtr
->uNumRows
+ CONTROL_BORDER_SIZEX
;
2335 else if(infoPtr
->dwStyle
& TCS_VERTICAL
)
2336 rect
.left
+= infoPtr
->tabHeight
* infoPtr
->uNumRows
+ CONTROL_BORDER_SIZEX
;
2337 else /* not TCS_VERTICAL and not TCS_BOTTOM */
2338 rect
.top
+= infoPtr
->tabHeight
* infoPtr
->uNumRows
+ CONTROL_BORDER_SIZEX
;
2341 TRACE("border=(%s)\n", wine_dbgstr_rect(&rect
));
2344 DrawThemeBackground (theme
, hdc
, TABP_PANE
, 0, &rect
, NULL
);
2346 DrawEdge(hdc
, &rect
, EDGE_RAISED
, BF_SOFT
|BF_RECT
);
2349 /******************************************************************************
2352 * This method repaints the tab control..
2354 static void TAB_Refresh (const TAB_INFO
*infoPtr
, HDC hdc
)
2359 if (!infoPtr
->DoRedraw
)
2362 hOldFont
= SelectObject (hdc
, infoPtr
->hFont
);
2364 if (infoPtr
->dwStyle
& TCS_BUTTONS
)
2366 for (i
= 0; i
< infoPtr
->uNumItem
; i
++)
2367 TAB_DrawItem (infoPtr
, hdc
, i
);
2371 /* Draw all the non selected item first */
2372 for (i
= 0; i
< infoPtr
->uNumItem
; i
++)
2374 if (i
!= infoPtr
->iSelected
)
2375 TAB_DrawItem (infoPtr
, hdc
, i
);
2378 /* Now, draw the border, draw it before the selected item
2379 * since the selected item overwrites part of the border. */
2380 TAB_DrawBorder (infoPtr
, hdc
);
2382 /* Then, draw the selected item */
2383 TAB_DrawItem (infoPtr
, hdc
, infoPtr
->iSelected
);
2386 SelectObject (hdc
, hOldFont
);
2389 static inline DWORD
TAB_GetRowCount (const TAB_INFO
*infoPtr
)
2391 TRACE("(%p)\n", infoPtr
);
2392 return infoPtr
->uNumRows
;
2395 static inline LRESULT
TAB_SetRedraw (TAB_INFO
*infoPtr
, BOOL doRedraw
)
2397 infoPtr
->DoRedraw
= doRedraw
;
2401 /******************************************************************************
2402 * TAB_EnsureSelectionVisible
2404 * This method will make sure that the current selection is completely
2405 * visible by scrolling until it is.
2407 static void TAB_EnsureSelectionVisible(
2410 INT iSelected
= infoPtr
->iSelected
;
2411 INT iOrigLeftmostVisible
= infoPtr
->leftmostVisible
;
2416 /* set the items row to the bottommost row or topmost row depending on
2418 if ((infoPtr
->uNumRows
> 1) && !(infoPtr
->dwStyle
& TCS_BUTTONS
))
2420 TAB_ITEM
*selected
= TAB_GetItem(infoPtr
, iSelected
);
2424 if(infoPtr
->dwStyle
& TCS_VERTICAL
)
2425 newselected
= selected
->rect
.left
;
2427 newselected
= selected
->rect
.top
;
2429 /* the target row is always (number of rows - 1)
2430 as row 0 is furthest from the clientRect */
2431 iTargetRow
= infoPtr
->uNumRows
- 1;
2433 if (newselected
!= iTargetRow
)
2436 if(infoPtr
->dwStyle
& TCS_VERTICAL
)
2438 for (i
=0; i
< infoPtr
->uNumItem
; i
++)
2440 /* move everything in the row of the selected item to the iTargetRow */
2441 TAB_ITEM
*item
= TAB_GetItem(infoPtr
, i
);
2443 if (item
->rect
.left
== newselected
)
2444 item
->rect
.left
= iTargetRow
;
2447 if (item
->rect
.left
> newselected
)
2454 for (i
=0; i
< infoPtr
->uNumItem
; i
++)
2456 TAB_ITEM
*item
= TAB_GetItem(infoPtr
, i
);
2458 if (item
->rect
.top
== newselected
)
2459 item
->rect
.top
= iTargetRow
;
2462 if (item
->rect
.top
> newselected
)
2467 TAB_RecalcHotTrack(infoPtr
, NULL
, NULL
, NULL
);
2472 * Do the trivial cases first.
2474 if ( (!infoPtr
->needsScrolling
) ||
2475 (infoPtr
->hwndUpDown
==0) || (infoPtr
->dwStyle
& TCS_VERTICAL
))
2478 if (infoPtr
->leftmostVisible
>= iSelected
)
2480 infoPtr
->leftmostVisible
= iSelected
;
2484 TAB_ITEM
*selected
= TAB_GetItem(infoPtr
, iSelected
);
2489 /* Calculate the part of the client area that is visible */
2490 GetClientRect(infoPtr
->hwnd
, &r
);
2493 GetClientRect(infoPtr
->hwndUpDown
, &r
);
2496 if ((selected
->rect
.right
-
2497 selected
->rect
.left
) >= width
)
2499 /* Special case: width of selected item is greater than visible
2502 infoPtr
->leftmostVisible
= iSelected
;
2506 for (i
= infoPtr
->leftmostVisible
; i
< infoPtr
->uNumItem
; i
++)
2508 if ((selected
->rect
.right
- TAB_GetItem(infoPtr
, i
)->rect
.left
) < width
)
2511 infoPtr
->leftmostVisible
= i
;
2515 if (infoPtr
->leftmostVisible
!= iOrigLeftmostVisible
)
2516 TAB_RecalcHotTrack(infoPtr
, NULL
, NULL
, NULL
);
2518 SendMessageW(infoPtr
->hwndUpDown
, UDM_SETPOS
, 0,
2519 MAKELONG(infoPtr
->leftmostVisible
, 0));
2522 /******************************************************************************
2523 * TAB_InvalidateTabArea
2525 * This method will invalidate the portion of the control that contains the
2526 * tabs. It is called when the state of the control changes and needs
2529 static void TAB_InvalidateTabArea(const TAB_INFO
*infoPtr
)
2531 RECT clientRect
, rInvalidate
, rAdjClient
;
2532 INT lastRow
= infoPtr
->uNumRows
- 1;
2535 if (lastRow
< 0) return;
2537 GetClientRect(infoPtr
->hwnd
, &clientRect
);
2538 rInvalidate
= clientRect
;
2539 rAdjClient
= clientRect
;
2541 TAB_AdjustRect(infoPtr
, 0, &rAdjClient
);
2543 TAB_InternalGetItemRect(infoPtr
, infoPtr
->uNumItem
-1 , &rect
, NULL
);
2544 if ((infoPtr
->dwStyle
& TCS_BOTTOM
) && (infoPtr
->dwStyle
& TCS_VERTICAL
))
2546 rInvalidate
.left
= rAdjClient
.right
;
2547 if (infoPtr
->uNumRows
== 1)
2548 rInvalidate
.bottom
= clientRect
.top
+ rect
.bottom
+ 2 * SELECTED_TAB_OFFSET
;
2550 else if(infoPtr
->dwStyle
& TCS_VERTICAL
)
2552 rInvalidate
.right
= rAdjClient
.left
;
2553 if (infoPtr
->uNumRows
== 1)
2554 rInvalidate
.bottom
= clientRect
.top
+ rect
.bottom
+ 2 * SELECTED_TAB_OFFSET
;
2556 else if (infoPtr
->dwStyle
& TCS_BOTTOM
)
2558 rInvalidate
.top
= rAdjClient
.bottom
;
2559 if (infoPtr
->uNumRows
== 1)
2560 rInvalidate
.right
= clientRect
.left
+ rect
.right
+ 2 * SELECTED_TAB_OFFSET
;
2564 rInvalidate
.bottom
= rAdjClient
.top
;
2565 if (infoPtr
->uNumRows
== 1)
2566 rInvalidate
.right
= clientRect
.left
+ rect
.right
+ 2 * SELECTED_TAB_OFFSET
;
2569 /* Punch out the updown control */
2570 if (infoPtr
->needsScrolling
&& (rInvalidate
.right
> 0)) {
2572 GetClientRect(infoPtr
->hwndUpDown
, &r
);
2573 if (rInvalidate
.right
> clientRect
.right
- r
.left
)
2574 rInvalidate
.right
= rInvalidate
.right
- (r
.right
- r
.left
);
2576 rInvalidate
.right
= clientRect
.right
- r
.left
;
2579 TRACE("invalidate (%s)\n", wine_dbgstr_rect(&rInvalidate
));
2581 InvalidateRect(infoPtr
->hwnd
, &rInvalidate
, TRUE
);
2584 static inline LRESULT
TAB_Paint (TAB_INFO
*infoPtr
, HDC hdcPaint
)
2593 hdc
= BeginPaint (infoPtr
->hwnd
, &ps
);
2594 TRACE("erase %d, rect=(%s)\n", ps
.fErase
, wine_dbgstr_rect(&ps
.rcPaint
));
2597 TAB_Refresh (infoPtr
, hdc
);
2600 EndPaint (infoPtr
->hwnd
, &ps
);
2606 TAB_InsertItemT (TAB_INFO
*infoPtr
, INT iItem
, const TCITEMW
*pti
, BOOL bUnicode
)
2611 GetClientRect (infoPtr
->hwnd
, &rect
);
2612 TRACE("Rect: %p %s\n", infoPtr
->hwnd
, wine_dbgstr_rect(&rect
));
2614 if (iItem
< 0) return -1;
2615 if (iItem
> infoPtr
->uNumItem
)
2616 iItem
= infoPtr
->uNumItem
;
2618 TAB_DumpItemExternalT(pti
, iItem
, bUnicode
);
2620 if (!(item
= Alloc(TAB_ITEM_SIZE(infoPtr
)))) return FALSE
;
2621 if (DPA_InsertPtr(infoPtr
->items
, iItem
, item
) == -1)
2627 if (infoPtr
->uNumItem
== 0)
2628 infoPtr
->iSelected
= 0;
2629 else if (iItem
<= infoPtr
->iSelected
)
2630 infoPtr
->iSelected
++;
2632 infoPtr
->uNumItem
++;
2634 item
->pszText
= NULL
;
2635 if (pti
->mask
& TCIF_TEXT
)
2638 Str_SetPtrW (&item
->pszText
, pti
->pszText
);
2640 Str_SetPtrAtoW (&item
->pszText
, (LPSTR
)pti
->pszText
);
2643 if (pti
->mask
& TCIF_IMAGE
)
2644 item
->iImage
= pti
->iImage
;
2648 if (pti
->mask
& TCIF_PARAM
)
2649 memcpy(item
->extra
, &pti
->lParam
, EXTRA_ITEM_SIZE(infoPtr
));
2651 memset(item
->extra
, 0, EXTRA_ITEM_SIZE(infoPtr
));
2653 TAB_SetItemBounds(infoPtr
);
2654 if (infoPtr
->uNumItem
> 1)
2655 TAB_InvalidateTabArea(infoPtr
);
2657 InvalidateRect(infoPtr
->hwnd
, NULL
, TRUE
);
2659 TRACE("[%p]: added item %d %s\n",
2660 infoPtr
->hwnd
, iItem
, debugstr_w(item
->pszText
));
2662 /* If we haven't set the current focus yet, set it now. */
2663 if (infoPtr
->uFocus
== -1)
2664 TAB_SetCurFocus(infoPtr
, iItem
);
2670 TAB_SetItemSize (TAB_INFO
*infoPtr
, INT cx
, INT cy
)
2673 BOOL bNeedPaint
= FALSE
;
2675 lResult
= MAKELONG(infoPtr
->tabWidth
, infoPtr
->tabHeight
);
2677 /* UNDOCUMENTED: If requested Width or Height is 0 this means that program wants to use auto size. */
2678 if (infoPtr
->dwStyle
& TCS_FIXEDWIDTH
&& (infoPtr
->tabWidth
!= cx
))
2680 infoPtr
->tabWidth
= cx
;
2684 if (infoPtr
->tabHeight
!= cy
)
2686 if ((infoPtr
->fHeightSet
= (cy
!= 0)))
2687 infoPtr
->tabHeight
= cy
;
2691 TRACE("was h=%d,w=%d, now h=%d,w=%d\n",
2692 HIWORD(lResult
), LOWORD(lResult
),
2693 infoPtr
->tabHeight
, infoPtr
->tabWidth
);
2697 TAB_SetItemBounds(infoPtr
);
2698 RedrawWindow(infoPtr
->hwnd
, NULL
, NULL
, RDW_ERASE
| RDW_INVALIDATE
| RDW_UPDATENOW
);
2704 static inline LRESULT
TAB_SetMinTabWidth (TAB_INFO
*infoPtr
, INT cx
)
2708 TRACE("(%p,%d)\n", infoPtr
, cx
);
2710 if (infoPtr
->tabMinWidth
< 0)
2711 oldcx
= DEFAULT_MIN_TAB_WIDTH
;
2713 oldcx
= infoPtr
->tabMinWidth
;
2714 infoPtr
->tabMinWidth
= cx
;
2715 TAB_SetItemBounds(infoPtr
);
2719 static inline LRESULT
2720 TAB_HighlightItem (TAB_INFO
*infoPtr
, INT iItem
, BOOL fHighlight
)
2726 TRACE("(%p,%d,%s)\n", infoPtr
, iItem
, fHighlight
? "true" : "false");
2728 if (iItem
< 0 || iItem
>= infoPtr
->uNumItem
)
2731 lpState
= &TAB_GetItem(infoPtr
, iItem
)->dwState
;
2732 oldState
= *lpState
;
2735 *lpState
|= TCIS_HIGHLIGHTED
;
2737 *lpState
&= ~TCIS_HIGHLIGHTED
;
2739 if ((oldState
!= *lpState
) && TAB_InternalGetItemRect (infoPtr
, iItem
, &r
, NULL
))
2740 InvalidateRect (infoPtr
->hwnd
, &r
, TRUE
);
2746 TAB_SetItemT (TAB_INFO
*infoPtr
, INT iItem
, LPTCITEMW tabItem
, BOOL bUnicode
)
2750 TRACE("(%p,%d,%p,%s)\n", infoPtr
, iItem
, tabItem
, bUnicode
? "true" : "false");
2752 if (iItem
< 0 || iItem
>= infoPtr
->uNumItem
)
2755 TAB_DumpItemExternalT(tabItem
, iItem
, bUnicode
);
2757 wineItem
= TAB_GetItem(infoPtr
, iItem
);
2759 if (tabItem
->mask
& TCIF_IMAGE
)
2760 wineItem
->iImage
= tabItem
->iImage
;
2762 if (tabItem
->mask
& TCIF_PARAM
)
2763 memcpy(wineItem
->extra
, &tabItem
->lParam
, infoPtr
->cbInfo
);
2765 if (tabItem
->mask
& TCIF_RTLREADING
)
2766 FIXME("TCIF_RTLREADING\n");
2768 if (tabItem
->mask
& TCIF_STATE
)
2769 wineItem
->dwState
= (wineItem
->dwState
& ~tabItem
->dwStateMask
) |
2770 ( tabItem
->dwState
& tabItem
->dwStateMask
);
2772 if (tabItem
->mask
& TCIF_TEXT
)
2774 Free(wineItem
->pszText
);
2775 wineItem
->pszText
= NULL
;
2777 Str_SetPtrW(&wineItem
->pszText
, tabItem
->pszText
);
2779 Str_SetPtrAtoW(&wineItem
->pszText
, (LPSTR
)tabItem
->pszText
);
2782 /* Update and repaint tabs */
2783 TAB_SetItemBounds(infoPtr
);
2784 TAB_InvalidateTabArea(infoPtr
);
2789 static inline LRESULT
TAB_GetItemCount (const TAB_INFO
*infoPtr
)
2792 return infoPtr
->uNumItem
;
2797 TAB_GetItemT (TAB_INFO
*infoPtr
, INT iItem
, LPTCITEMW tabItem
, BOOL bUnicode
)
2801 TRACE("(%p,%d,%p,%s)\n", infoPtr
, iItem
, tabItem
, bUnicode
? "true" : "false");
2803 if (!tabItem
) return FALSE
;
2805 if (iItem
< 0 || iItem
>= infoPtr
->uNumItem
)
2807 /* init requested fields */
2808 if (tabItem
->mask
& TCIF_IMAGE
) tabItem
->iImage
= 0;
2809 if (tabItem
->mask
& TCIF_PARAM
) tabItem
->lParam
= 0;
2810 if (tabItem
->mask
& TCIF_STATE
) tabItem
->dwState
= 0;
2814 wineItem
= TAB_GetItem(infoPtr
, iItem
);
2816 if (tabItem
->mask
& TCIF_IMAGE
)
2817 tabItem
->iImage
= wineItem
->iImage
;
2819 if (tabItem
->mask
& TCIF_PARAM
)
2820 memcpy(&tabItem
->lParam
, wineItem
->extra
, infoPtr
->cbInfo
);
2822 if (tabItem
->mask
& TCIF_RTLREADING
)
2823 FIXME("TCIF_RTLREADING\n");
2825 if (tabItem
->mask
& TCIF_STATE
)
2826 tabItem
->dwState
= wineItem
->dwState
& tabItem
->dwStateMask
;
2828 if (tabItem
->mask
& TCIF_TEXT
)
2831 Str_GetPtrW (wineItem
->pszText
, tabItem
->pszText
, tabItem
->cchTextMax
);
2833 Str_GetPtrWtoA (wineItem
->pszText
, (LPSTR
)tabItem
->pszText
, tabItem
->cchTextMax
);
2836 TAB_DumpItemExternalT(tabItem
, iItem
, bUnicode
);
2842 static LRESULT
TAB_DeleteItem (TAB_INFO
*infoPtr
, INT iItem
)
2846 TRACE("(%p, %d)\n", infoPtr
, iItem
);
2848 if (iItem
< 0 || iItem
>= infoPtr
->uNumItem
) return FALSE
;
2850 TAB_InvalidateTabArea(infoPtr
);
2851 item
= TAB_GetItem(infoPtr
, iItem
);
2852 Free(item
->pszText
);
2854 infoPtr
->uNumItem
--;
2855 DPA_DeletePtr(infoPtr
->items
, iItem
);
2857 if (infoPtr
->uNumItem
== 0)
2859 if (infoPtr
->iHotTracked
>= 0)
2861 KillTimer(infoPtr
->hwnd
, TAB_HOTTRACK_TIMER
);
2862 infoPtr
->iHotTracked
= -1;
2865 infoPtr
->iSelected
= -1;
2869 if (iItem
<= infoPtr
->iHotTracked
)
2871 /* When tabs move left/up, the hot track item may change */
2872 FIXME("Recalc hot track\n");
2876 /* adjust the selected index */
2877 if (iItem
== infoPtr
->iSelected
)
2878 infoPtr
->iSelected
= -1;
2879 else if (iItem
< infoPtr
->iSelected
)
2880 infoPtr
->iSelected
--;
2882 /* reposition and repaint tabs */
2883 TAB_SetItemBounds(infoPtr
);
2888 static inline LRESULT
TAB_DeleteAllItems (TAB_INFO
*infoPtr
)
2890 TRACE("(%p)\n", infoPtr
);
2891 while (infoPtr
->uNumItem
)
2892 TAB_DeleteItem (infoPtr
, 0);
2897 static inline LRESULT
TAB_GetFont (const TAB_INFO
*infoPtr
)
2899 TRACE("(%p) returning %p\n", infoPtr
, infoPtr
->hFont
);
2900 return (LRESULT
)infoPtr
->hFont
;
2903 static inline LRESULT
TAB_SetFont (TAB_INFO
*infoPtr
, HFONT hNewFont
)
2905 TRACE("(%p,%p)\n", infoPtr
, hNewFont
);
2907 infoPtr
->hFont
= hNewFont
;
2909 TAB_SetItemBounds(infoPtr
);
2911 TAB_InvalidateTabArea(infoPtr
);
2917 static inline LRESULT
TAB_GetImageList (const TAB_INFO
*infoPtr
)
2920 return (LRESULT
)infoPtr
->himl
;
2923 static inline LRESULT
TAB_SetImageList (TAB_INFO
*infoPtr
, HIMAGELIST himlNew
)
2925 HIMAGELIST himlPrev
= infoPtr
->himl
;
2926 TRACE("himl=%p\n", himlNew
);
2927 infoPtr
->himl
= himlNew
;
2928 TAB_SetItemBounds(infoPtr
);
2929 InvalidateRect(infoPtr
->hwnd
, NULL
, TRUE
);
2930 return (LRESULT
)himlPrev
;
2933 static inline LRESULT
TAB_GetUnicodeFormat (const TAB_INFO
*infoPtr
)
2935 TRACE("(%p)\n", infoPtr
);
2936 return infoPtr
->bUnicode
;
2939 static inline LRESULT
TAB_SetUnicodeFormat (TAB_INFO
*infoPtr
, BOOL bUnicode
)
2941 BOOL bTemp
= infoPtr
->bUnicode
;
2943 TRACE("(%p %d)\n", infoPtr
, bUnicode
);
2944 infoPtr
->bUnicode
= bUnicode
;
2949 static inline LRESULT
TAB_Size (TAB_INFO
*infoPtr
)
2951 /* I'm not really sure what the following code was meant to do.
2952 This is what it is doing:
2953 When WM_SIZE is sent with SIZE_RESTORED, the control
2954 gets positioned in the top left corner.
2958 UINT uPosFlags,cx,cy;
2962 parent = GetParent (hwnd);
2963 GetClientRect(parent, &parent_rect);
2966 if (GetWindowLongW(hwnd, GWL_STYLE) & CCS_NORESIZE)
2967 uPosFlags |= (SWP_NOSIZE | SWP_NOMOVE);
2969 SetWindowPos (hwnd, 0, parent_rect.left, parent_rect.top,
2970 cx, cy, uPosFlags | SWP_NOZORDER);
2972 FIXME("WM_SIZE flag %x %lx not handled\n", wParam, lParam);
2975 /* Recompute the size/position of the tabs. */
2976 TAB_SetItemBounds (infoPtr
);
2978 /* Force a repaint of the control. */
2979 InvalidateRect(infoPtr
->hwnd
, NULL
, TRUE
);
2985 static LRESULT
TAB_Create (HWND hwnd
, LPARAM lParam
)
2988 TEXTMETRICW fontMetrics
;
2993 infoPtr
= Alloc (sizeof(TAB_INFO
));
2995 SetWindowLongPtrW(hwnd
, 0, (DWORD_PTR
)infoPtr
);
2997 infoPtr
->hwnd
= hwnd
;
2998 infoPtr
->hwndNotify
= ((LPCREATESTRUCTW
)lParam
)->hwndParent
;
2999 infoPtr
->uNumItem
= 0;
3000 infoPtr
->uNumRows
= 0;
3001 infoPtr
->uHItemPadding
= 6;
3002 infoPtr
->uVItemPadding
= 3;
3003 infoPtr
->uHItemPadding_s
= 6;
3004 infoPtr
->uVItemPadding_s
= 3;
3006 infoPtr
->items
= DPA_Create(8);
3007 infoPtr
->hcurArrow
= LoadCursorW (0, (LPWSTR
)IDC_ARROW
);
3008 infoPtr
->iSelected
= -1;
3009 infoPtr
->iHotTracked
= -1;
3010 infoPtr
->uFocus
= -1;
3011 infoPtr
->hwndToolTip
= 0;
3012 infoPtr
->DoRedraw
= TRUE
;
3013 infoPtr
->needsScrolling
= FALSE
;
3014 infoPtr
->hwndUpDown
= 0;
3015 infoPtr
->leftmostVisible
= 0;
3016 infoPtr
->fHeightSet
= FALSE
;
3017 infoPtr
->bUnicode
= IsWindowUnicode (hwnd
);
3018 infoPtr
->cbInfo
= sizeof(LPARAM
);
3020 TRACE("Created tab control, hwnd [%p]\n", hwnd
);
3022 /* The tab control always has the WS_CLIPSIBLINGS style. Even
3023 if you don't specify it in CreateWindow. This is necessary in
3024 order for paint to work correctly. This follows windows behaviour. */
3025 style
= GetWindowLongW(hwnd
, GWL_STYLE
);
3026 if (style
& TCS_VERTICAL
) style
|= TCS_MULTILINE
;
3027 style
|= WS_CLIPSIBLINGS
;
3028 SetWindowLongW(hwnd
, GWL_STYLE
, style
);
3030 infoPtr
->dwStyle
= style
;
3031 infoPtr
->exStyle
= (style
& TCS_FLATBUTTONS
) ? TCS_EX_FLATSEPARATORS
: 0;
3033 if (infoPtr
->dwStyle
& TCS_TOOLTIPS
) {
3034 /* Create tooltip control */
3035 infoPtr
->hwndToolTip
=
3036 CreateWindowExW (0, TOOLTIPS_CLASSW
, NULL
, WS_POPUP
,
3037 CW_USEDEFAULT
, CW_USEDEFAULT
,
3038 CW_USEDEFAULT
, CW_USEDEFAULT
,
3041 /* Send NM_TOOLTIPSCREATED notification */
3042 if (infoPtr
->hwndToolTip
) {
3043 NMTOOLTIPSCREATED nmttc
;
3045 nmttc
.hdr
.hwndFrom
= hwnd
;
3046 nmttc
.hdr
.idFrom
= GetWindowLongPtrW(hwnd
, GWLP_ID
);
3047 nmttc
.hdr
.code
= NM_TOOLTIPSCREATED
;
3048 nmttc
.hwndToolTips
= infoPtr
->hwndToolTip
;
3050 SendMessageW (infoPtr
->hwndNotify
, WM_NOTIFY
,
3051 GetWindowLongPtrW(hwnd
, GWLP_ID
), (LPARAM
)&nmttc
);
3055 OpenThemeData (infoPtr
->hwnd
, themeClass
);
3058 * We need to get text information so we need a DC and we need to select
3062 hOldFont
= SelectObject (hdc
, GetStockObject (SYSTEM_FONT
));
3064 /* Use the system font to determine the initial height of a tab. */
3065 GetTextMetricsW(hdc
, &fontMetrics
);
3068 * Make sure there is enough space for the letters + growing the
3069 * selected item + extra space for the selected item.
3071 infoPtr
->tabHeight
= fontMetrics
.tmHeight
+ SELECTED_TAB_OFFSET
+
3072 ((infoPtr
->dwStyle
& TCS_BUTTONS
) ? 2 : 1) *
3073 infoPtr
->uVItemPadding
;
3075 /* Initialize the width of a tab. */
3076 if (infoPtr
->dwStyle
& TCS_FIXEDWIDTH
)
3077 infoPtr
->tabWidth
= GetDeviceCaps(hdc
, LOGPIXELSX
);
3079 infoPtr
->tabMinWidth
= -1;
3081 TRACE("tabH=%d, tabW=%d\n", infoPtr
->tabHeight
, infoPtr
->tabWidth
);
3083 SelectObject (hdc
, hOldFont
);
3084 ReleaseDC(hwnd
, hdc
);
3090 TAB_Destroy (TAB_INFO
*infoPtr
)
3094 SetWindowLongPtrW(infoPtr
->hwnd
, 0, 0);
3096 for (iItem
= infoPtr
->uNumItem
- 1; iItem
>= 0; iItem
--)
3098 TAB_ITEM
*tab
= TAB_GetItem(infoPtr
, iItem
);
3100 DPA_DeletePtr(infoPtr
->items
, iItem
);
3101 infoPtr
->uNumItem
--;
3106 DPA_Destroy(infoPtr
->items
);
3107 infoPtr
->items
= NULL
;
3109 if (infoPtr
->hwndToolTip
)
3110 DestroyWindow (infoPtr
->hwndToolTip
);
3112 if (infoPtr
->hwndUpDown
)
3113 DestroyWindow(infoPtr
->hwndUpDown
);
3115 if (infoPtr
->iHotTracked
>= 0)
3116 KillTimer(infoPtr
->hwnd
, TAB_HOTTRACK_TIMER
);
3118 CloseThemeData (GetWindowTheme (infoPtr
->hwnd
));
3124 /* update theme after a WM_THEMECHANGED message */
3125 static LRESULT
theme_changed(const TAB_INFO
*infoPtr
)
3127 HTHEME theme
= GetWindowTheme (infoPtr
->hwnd
);
3128 CloseThemeData (theme
);
3129 OpenThemeData (infoPtr
->hwnd
, themeClass
);
3133 static LRESULT
TAB_NCCalcSize(WPARAM wParam
)
3137 return WVR_ALIGNTOP
;
3140 static inline LRESULT
3141 TAB_SetItemExtra (TAB_INFO
*infoPtr
, INT cbInfo
)
3143 TRACE("(%p %d)\n", infoPtr
, cbInfo
);
3145 if (cbInfo
< 0 || infoPtr
->uNumItem
) return FALSE
;
3147 infoPtr
->cbInfo
= cbInfo
;
3151 static LRESULT
TAB_RemoveImage (TAB_INFO
*infoPtr
, INT image
)
3153 TRACE("%p %d\n", infoPtr
, image
);
3155 if (ImageList_Remove (infoPtr
->himl
, image
))
3160 /* shift indices, repaint items if needed */
3161 for (i
= 0; i
< infoPtr
->uNumItem
; i
++)
3163 idx
= &TAB_GetItem(infoPtr
, i
)->iImage
;
3172 if (TAB_InternalGetItemRect (infoPtr
, i
, &r
, NULL
))
3173 InvalidateRect (infoPtr
->hwnd
, &r
, TRUE
);
3182 TAB_SetExtendedStyle (TAB_INFO
*infoPtr
, DWORD exMask
, DWORD exStyle
)
3184 DWORD prevstyle
= infoPtr
->exStyle
;
3186 /* zero mask means all styles */
3187 if (exMask
== 0) exMask
= ~0;
3189 if (exMask
& TCS_EX_REGISTERDROP
)
3191 FIXME("TCS_EX_REGISTERDROP style unimplemented\n");
3192 exMask
&= ~TCS_EX_REGISTERDROP
;
3193 exStyle
&= ~TCS_EX_REGISTERDROP
;
3196 if (exMask
& TCS_EX_FLATSEPARATORS
)
3198 if ((prevstyle
^ exStyle
) & TCS_EX_FLATSEPARATORS
)
3200 infoPtr
->exStyle
^= TCS_EX_FLATSEPARATORS
;
3201 TAB_InvalidateTabArea(infoPtr
);
3208 static inline LRESULT
3209 TAB_GetExtendedStyle (const TAB_INFO
*infoPtr
)
3211 return infoPtr
->exStyle
;
3215 TAB_DeselectAll (TAB_INFO
*infoPtr
, BOOL excludesel
)
3218 INT i
, selected
= infoPtr
->iSelected
;
3220 TRACE("(%p, %d)\n", infoPtr
, excludesel
);
3222 if (!(infoPtr
->dwStyle
& TCS_BUTTONS
))
3225 for (i
= 0; i
< infoPtr
->uNumItem
; i
++)
3227 if ((TAB_GetItem(infoPtr
, i
)->dwState
& TCIS_BUTTONPRESSED
) &&
3230 TAB_GetItem(infoPtr
, i
)->dwState
&= ~TCIS_BUTTONPRESSED
;
3235 if (!excludesel
&& (selected
!= -1))
3237 TAB_GetItem(infoPtr
, selected
)->dwState
&= ~TCIS_BUTTONPRESSED
;
3238 infoPtr
->iSelected
= -1;
3243 TAB_InvalidateTabArea (infoPtr
);
3250 * Processes WM_STYLECHANGED messages.
3253 * [I] infoPtr : valid pointer to the tab data structure
3254 * [I] wStyleType : window style type (normal or extended)
3255 * [I] lpss : window style information
3260 static INT
TAB_StyleChanged(TAB_INFO
*infoPtr
, WPARAM wStyleType
,
3261 const STYLESTRUCT
*lpss
)
3263 TRACE("(styletype=%lx, styleOld=0x%08x, styleNew=0x%08x)\n",
3264 wStyleType
, lpss
->styleOld
, lpss
->styleNew
);
3266 if (wStyleType
!= GWL_STYLE
) return 0;
3268 infoPtr
->dwStyle
= lpss
->styleNew
;
3270 TAB_SetItemBounds (infoPtr
);
3271 InvalidateRect(infoPtr
->hwnd
, NULL
, TRUE
);
3276 static LRESULT WINAPI
3277 TAB_WindowProc (HWND hwnd
, UINT uMsg
, WPARAM wParam
, LPARAM lParam
)
3279 TAB_INFO
*infoPtr
= TAB_GetInfoPtr(hwnd
);
3281 TRACE("hwnd=%p msg=%x wParam=%lx lParam=%lx\n", hwnd
, uMsg
, wParam
, lParam
);
3282 if (!infoPtr
&& (uMsg
!= WM_CREATE
))
3283 return DefWindowProcW (hwnd
, uMsg
, wParam
, lParam
);
3287 case TCM_GETIMAGELIST
:
3288 return TAB_GetImageList (infoPtr
);
3290 case TCM_SETIMAGELIST
:
3291 return TAB_SetImageList (infoPtr
, (HIMAGELIST
)lParam
);
3293 case TCM_GETITEMCOUNT
:
3294 return TAB_GetItemCount (infoPtr
);
3298 return TAB_GetItemT (infoPtr
, (INT
)wParam
, (LPTCITEMW
)lParam
, uMsg
== TCM_GETITEMW
);
3302 return TAB_SetItemT (infoPtr
, (INT
)wParam
, (LPTCITEMW
)lParam
, uMsg
== TCM_SETITEMW
);
3304 case TCM_DELETEITEM
:
3305 return TAB_DeleteItem (infoPtr
, (INT
)wParam
);
3307 case TCM_DELETEALLITEMS
:
3308 return TAB_DeleteAllItems (infoPtr
);
3310 case TCM_GETITEMRECT
:
3311 return TAB_GetItemRect (infoPtr
, (INT
)wParam
, (LPRECT
)lParam
);
3314 return TAB_GetCurSel (infoPtr
);
3317 return TAB_HitTest (infoPtr
, (LPTCHITTESTINFO
)lParam
);
3320 return TAB_SetCurSel (infoPtr
, (INT
)wParam
);
3322 case TCM_INSERTITEMA
:
3323 case TCM_INSERTITEMW
:
3324 return TAB_InsertItemT (infoPtr
, (INT
)wParam
, (TCITEMW
*)lParam
, uMsg
== TCM_INSERTITEMW
);
3326 case TCM_SETITEMEXTRA
:
3327 return TAB_SetItemExtra (infoPtr
, (INT
)wParam
);
3329 case TCM_ADJUSTRECT
:
3330 return TAB_AdjustRect (infoPtr
, (BOOL
)wParam
, (LPRECT
)lParam
);
3332 case TCM_SETITEMSIZE
:
3333 return TAB_SetItemSize (infoPtr
, (INT
)LOWORD(lParam
), (INT
)HIWORD(lParam
));
3335 case TCM_REMOVEIMAGE
:
3336 return TAB_RemoveImage (infoPtr
, (INT
)wParam
);
3338 case TCM_SETPADDING
:
3339 return TAB_SetPadding (infoPtr
, lParam
);
3341 case TCM_GETROWCOUNT
:
3342 return TAB_GetRowCount(infoPtr
);
3344 case TCM_GETUNICODEFORMAT
:
3345 return TAB_GetUnicodeFormat (infoPtr
);
3347 case TCM_SETUNICODEFORMAT
:
3348 return TAB_SetUnicodeFormat (infoPtr
, (BOOL
)wParam
);
3350 case TCM_HIGHLIGHTITEM
:
3351 return TAB_HighlightItem (infoPtr
, (INT
)wParam
, (BOOL
)LOWORD(lParam
));
3353 case TCM_GETTOOLTIPS
:
3354 return TAB_GetToolTips (infoPtr
);
3356 case TCM_SETTOOLTIPS
:
3357 return TAB_SetToolTips (infoPtr
, (HWND
)wParam
);
3359 case TCM_GETCURFOCUS
:
3360 return TAB_GetCurFocus (infoPtr
);
3362 case TCM_SETCURFOCUS
:
3363 return TAB_SetCurFocus (infoPtr
, (INT
)wParam
);
3365 case TCM_SETMINTABWIDTH
:
3366 return TAB_SetMinTabWidth(infoPtr
, (INT
)lParam
);
3368 case TCM_DESELECTALL
:
3369 return TAB_DeselectAll (infoPtr
, (BOOL
)wParam
);
3371 case TCM_GETEXTENDEDSTYLE
:
3372 return TAB_GetExtendedStyle (infoPtr
);
3374 case TCM_SETEXTENDEDSTYLE
:
3375 return TAB_SetExtendedStyle (infoPtr
, wParam
, lParam
);
3378 return TAB_GetFont (infoPtr
);
3381 return TAB_SetFont (infoPtr
, (HFONT
)wParam
);
3384 return TAB_Create (hwnd
, lParam
);
3387 return TAB_Destroy (infoPtr
);
3390 return DLGC_WANTARROWS
| DLGC_WANTCHARS
;
3392 case WM_LBUTTONDOWN
:
3393 return TAB_LButtonDown (infoPtr
, wParam
, lParam
);
3396 return TAB_LButtonUp (infoPtr
);
3399 return SendMessageW(infoPtr
->hwndNotify
, WM_NOTIFY
, wParam
, lParam
);
3402 TAB_RButtonUp (infoPtr
);
3403 return DefWindowProcW (hwnd
, uMsg
, wParam
, lParam
);
3406 return TAB_MouseMove (infoPtr
, wParam
, lParam
);
3408 case WM_PRINTCLIENT
:
3410 return TAB_Paint (infoPtr
, (HDC
)wParam
);
3413 return TAB_Size (infoPtr
);
3416 return TAB_SetRedraw (infoPtr
, (BOOL
)wParam
);
3419 return TAB_OnHScroll(infoPtr
, (int)LOWORD(wParam
), (int)HIWORD(wParam
));
3421 case WM_STYLECHANGED
:
3422 return TAB_StyleChanged(infoPtr
, wParam
, (LPSTYLESTRUCT
)lParam
);
3424 case WM_SYSCOLORCHANGE
:
3425 COMCTL32_RefreshSysColors();
3428 case WM_THEMECHANGED
:
3429 return theme_changed (infoPtr
);
3432 TAB_KillFocus(infoPtr
);
3434 TAB_FocusChanging(infoPtr
);
3435 break; /* Don't disturb normal focus behavior */
3438 return TAB_KeyDown(infoPtr
, wParam
, lParam
);
3441 return TAB_NCHitTest(infoPtr
, lParam
);
3444 return TAB_NCCalcSize(wParam
);
3447 if (uMsg
>= WM_USER
&& uMsg
< WM_APP
&& !COMCTL32_IsReflectedMessage(uMsg
))
3448 WARN("unknown msg %04x wp=%08lx lp=%08lx\n",
3449 uMsg
, wParam
, lParam
);
3452 return DefWindowProcW(hwnd
, uMsg
, wParam
, lParam
);
3461 ZeroMemory (&wndClass
, sizeof(WNDCLASSW
));
3462 wndClass
.style
= CS_GLOBALCLASS
| CS_DBLCLKS
| CS_HREDRAW
| CS_VREDRAW
;
3463 wndClass
.lpfnWndProc
= TAB_WindowProc
;
3464 wndClass
.cbClsExtra
= 0;
3465 wndClass
.cbWndExtra
= sizeof(TAB_INFO
*);
3466 wndClass
.hCursor
= LoadCursorW (0, (LPWSTR
)IDC_ARROW
);
3467 wndClass
.hbrBackground
= (HBRUSH
)(COLOR_BTNFACE
+1);
3468 wndClass
.lpszClassName
= WC_TABCONTROLW
;
3470 RegisterClassW (&wndClass
);
3475 TAB_Unregister (void)
3477 UnregisterClassW (WC_TABCONTROLW
, NULL
);