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.
43 * TCS_EX_FLATSEPARATORS
58 * TCM_GETEXTENDEDSTYLE
59 * TCM_SETEXTENDEDSTYLE
78 #include "wine/debug.h"
81 WINE_DEFAULT_DEBUG_CHANNEL(tab
);
88 RECT rect
; /* bounding rectangle of the item relative to the
89 * leftmost item (the leftmost item, 0, would have a
90 * "left" member of 0 in this rectangle)
92 * additionally the top member holds the row number
93 * and bottom is unused and should be 0 */
94 BYTE extra
[1]; /* Space for caller supplied info, variable size */
97 /* The size of a tab item depends on how much extra data is requested */
98 #define TAB_ITEM_SIZE(infoPtr) (FIELD_OFFSET(TAB_ITEM, extra[(infoPtr)->cbInfo]))
102 HWND hwnd
; /* Tab control window */
103 HWND hwndNotify
; /* notification window (parent) */
104 UINT uNumItem
; /* number of tab items */
105 UINT uNumRows
; /* number of tab rows */
106 INT tabHeight
; /* height of the tab row */
107 INT tabWidth
; /* width of tabs */
108 INT tabMinWidth
; /* minimum width of items */
109 USHORT uHItemPadding
; /* amount of horizontal padding, in pixels */
110 USHORT uVItemPadding
; /* amount of vertical padding, in pixels */
111 USHORT uHItemPadding_s
; /* Set amount of horizontal padding, in pixels */
112 USHORT uVItemPadding_s
; /* Set amount of vertical padding, in pixels */
113 HFONT hFont
; /* handle to the current font */
114 HCURSOR hcurArrow
; /* handle to the current cursor */
115 HIMAGELIST himl
; /* handle to an image list (may be 0) */
116 HWND hwndToolTip
; /* handle to tab's tooltip */
117 INT leftmostVisible
; /* Used for scrolling, this member contains
118 * the index of the first visible item */
119 INT iSelected
; /* the currently selected item */
120 INT iHotTracked
; /* the highlighted item under the mouse */
121 INT uFocus
; /* item which has the focus */
122 TAB_ITEM
* items
; /* pointer to an array of TAB_ITEM's */
123 BOOL DoRedraw
; /* flag for redrawing when tab contents is changed*/
124 BOOL needsScrolling
; /* TRUE if the size of the tabs is greater than
125 * the size of the control */
126 BOOL fHeightSet
; /* was the height of the tabs explicitly set? */
127 BOOL bUnicode
; /* Unicode control? */
128 HWND hwndUpDown
; /* Updown control used for scrolling */
129 INT cbInfo
; /* Number of bytes of caller supplied info per tab */
132 /******************************************************************************
133 * Positioning constants
135 #define SELECTED_TAB_OFFSET 2
136 #define ROUND_CORNER_SIZE 2
137 #define DISPLAY_AREA_PADDINGX 2
138 #define DISPLAY_AREA_PADDINGY 2
139 #define CONTROL_BORDER_SIZEX 2
140 #define CONTROL_BORDER_SIZEY 2
141 #define BUTTON_SPACINGX 3
142 #define BUTTON_SPACINGY 3
143 #define FLAT_BTN_SPACINGX 8
144 #define DEFAULT_MIN_TAB_WIDTH 54
145 #define DEFAULT_TAB_WIDTH_FIXED 96
146 #define DEFAULT_PADDING_X 6
147 #define EXTRA_ICON_PADDING 3
149 #define TAB_GetInfoPtr(hwnd) ((TAB_INFO *)GetWindowLongPtrW(hwnd,0))
150 /* Since items are variable sized, cannot directly access them */
151 #define TAB_GetItem(info,i) \
152 ((TAB_ITEM*)((LPBYTE)info->items + (i) * TAB_ITEM_SIZE(info)))
154 #define GET_DEFAULT_MIN_TAB_WIDTH(infoPtr) (DEFAULT_MIN_TAB_WIDTH - (DEFAULT_PADDING_X - (infoPtr)->uHItemPadding) * 2)
156 /******************************************************************************
157 * Hot-tracking timer constants
159 #define TAB_HOTTRACK_TIMER 1
160 #define TAB_HOTTRACK_TIMER_INTERVAL 100 /* milliseconds */
162 static const WCHAR themeClass
[] = { 'T','a','b',0 };
164 /******************************************************************************
167 static void TAB_InvalidateTabArea(const TAB_INFO
*);
168 static void TAB_EnsureSelectionVisible(TAB_INFO
*);
169 static void TAB_DrawItemInterior(const TAB_INFO
*, HDC
, INT
, RECT
*);
172 TAB_SendSimpleNotify (const TAB_INFO
*infoPtr
, UINT code
)
176 nmhdr
.hwndFrom
= infoPtr
->hwnd
;
177 nmhdr
.idFrom
= GetWindowLongPtrW(infoPtr
->hwnd
, GWLP_ID
);
180 return (BOOL
) SendMessageW (infoPtr
->hwndNotify
, WM_NOTIFY
,
181 nmhdr
.idFrom
, (LPARAM
) &nmhdr
);
185 TAB_RelayEvent (HWND hwndTip
, HWND hwndMsg
, UINT uMsg
,
186 WPARAM wParam
, LPARAM lParam
)
194 msg
.time
= GetMessageTime ();
195 msg
.pt
.x
= (short)LOWORD(GetMessagePos ());
196 msg
.pt
.y
= (short)HIWORD(GetMessagePos ());
198 SendMessageW (hwndTip
, TTM_RELAYEVENT
, 0, (LPARAM
)&msg
);
202 TAB_DumpItemExternalT(const TCITEMW
*pti
, UINT iItem
, BOOL isW
)
205 TRACE("external tab %d, mask=0x%08x, dwState=0x%08x, dwStateMask=0x%08x, cchTextMax=0x%08x\n",
206 iItem
, pti
->mask
, pti
->dwState
, pti
->dwStateMask
, pti
->cchTextMax
);
207 TRACE("external tab %d, iImage=%d, lParam=0x%08lx, pszTextW=%s\n",
208 iItem
, pti
->iImage
, pti
->lParam
, isW
? debugstr_w(pti
->pszText
) : debugstr_a((LPSTR
)pti
->pszText
));
213 TAB_DumpItemInternal(const TAB_INFO
*infoPtr
, UINT iItem
)
218 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 return infoPtr
->iSelected
;
234 * the index of the tab item that has the focus. */
235 static inline LRESULT
236 TAB_GetCurFocus (const TAB_INFO
*infoPtr
)
238 return infoPtr
->uFocus
;
241 static inline LRESULT
TAB_GetToolTips (const TAB_INFO
*infoPtr
)
243 if (infoPtr
== NULL
) return 0;
244 return (LRESULT
)infoPtr
->hwndToolTip
;
247 static inline LRESULT
TAB_SetCurSel (TAB_INFO
*infoPtr
, INT iItem
)
249 INT prevItem
= infoPtr
->iSelected
;
252 infoPtr
->iSelected
=-1;
253 else if (iItem
>= infoPtr
->uNumItem
)
256 if (infoPtr
->iSelected
!= iItem
) {
257 infoPtr
->iSelected
=iItem
;
258 infoPtr
->uFocus
=iItem
;
259 TAB_EnsureSelectionVisible(infoPtr
);
260 TAB_InvalidateTabArea(infoPtr
);
266 static LRESULT
TAB_SetCurFocus (TAB_INFO
*infoPtr
, INT iItem
)
269 infoPtr
->uFocus
= -1;
270 else if (iItem
< infoPtr
->uNumItem
) {
271 if (GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
) & TCS_BUTTONS
) {
272 FIXME("Should set input focus\n");
274 int oldFocus
= infoPtr
->uFocus
;
275 if (infoPtr
->iSelected
!= iItem
|| oldFocus
== -1 ) {
276 infoPtr
->uFocus
= iItem
;
277 if (oldFocus
!= -1) {
278 if (!TAB_SendSimpleNotify(infoPtr
, TCN_SELCHANGING
)) {
279 infoPtr
->iSelected
= iItem
;
280 TAB_SendSimpleNotify(infoPtr
, TCN_SELCHANGE
);
283 infoPtr
->iSelected
= iItem
;
284 TAB_EnsureSelectionVisible(infoPtr
);
285 TAB_InvalidateTabArea(infoPtr
);
293 static inline LRESULT
294 TAB_SetToolTips (TAB_INFO
*infoPtr
, HWND hwndToolTip
)
297 infoPtr
->hwndToolTip
= hwndToolTip
;
301 static inline LRESULT
302 TAB_SetPadding (TAB_INFO
*infoPtr
, LPARAM lParam
)
306 infoPtr
->uHItemPadding_s
=LOWORD(lParam
);
307 infoPtr
->uVItemPadding_s
=HIWORD(lParam
);
312 /******************************************************************************
313 * TAB_InternalGetItemRect
315 * This method will calculate the rectangle representing a given tab item in
316 * client coordinates. This method takes scrolling into account.
318 * This method returns TRUE if the item is visible in the window and FALSE
319 * if it is completely outside the client area.
321 static BOOL
TAB_InternalGetItemRect(
322 const TAB_INFO
* infoPtr
,
327 RECT tmpItemRect
,clientRect
;
328 LONG lStyle
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
330 /* Perform a sanity check and a trivial visibility check. */
331 if ( (infoPtr
->uNumItem
<= 0) ||
332 (itemIndex
>= infoPtr
->uNumItem
) ||
333 (!((lStyle
& TCS_MULTILINE
) || (lStyle
& TCS_VERTICAL
)) && (itemIndex
< infoPtr
->leftmostVisible
)) )
335 TRACE("Not Visible\n");
336 /* need to initialize these to empty rects */
339 memset(itemRect
,0,sizeof(RECT
));
340 itemRect
->bottom
= infoPtr
->tabHeight
;
343 memset(selectedRect
,0,sizeof(RECT
));
348 * Avoid special cases in this procedure by assigning the "out"
349 * parameters if the caller didn't supply them
351 if (itemRect
== NULL
)
352 itemRect
= &tmpItemRect
;
354 /* Retrieve the unmodified item rect. */
355 *itemRect
= TAB_GetItem(infoPtr
,itemIndex
)->rect
;
357 /* calculate the times bottom and top based on the row */
358 GetClientRect(infoPtr
->hwnd
, &clientRect
);
360 if ((lStyle
& TCS_BOTTOM
) && (lStyle
& TCS_VERTICAL
))
362 itemRect
->right
= clientRect
.right
- SELECTED_TAB_OFFSET
- itemRect
->left
* infoPtr
->tabHeight
-
363 ((lStyle
& TCS_BUTTONS
) ? itemRect
->left
* BUTTON_SPACINGX
: 0);
364 itemRect
->left
= itemRect
->right
- infoPtr
->tabHeight
;
366 else if (lStyle
& TCS_VERTICAL
)
368 itemRect
->left
= clientRect
.left
+ SELECTED_TAB_OFFSET
+ itemRect
->left
* infoPtr
->tabHeight
+
369 ((lStyle
& TCS_BUTTONS
) ? itemRect
->left
* BUTTON_SPACINGX
: 0);
370 itemRect
->right
= itemRect
->left
+ infoPtr
->tabHeight
;
372 else if (lStyle
& TCS_BOTTOM
)
374 itemRect
->bottom
= clientRect
.bottom
- itemRect
->top
* infoPtr
->tabHeight
-
375 ((lStyle
& TCS_BUTTONS
) ? itemRect
->top
* BUTTON_SPACINGY
: SELECTED_TAB_OFFSET
);
376 itemRect
->top
= itemRect
->bottom
- infoPtr
->tabHeight
;
378 else /* not TCS_BOTTOM and not TCS_VERTICAL */
380 itemRect
->top
= clientRect
.top
+ itemRect
->top
* infoPtr
->tabHeight
+
381 ((lStyle
& TCS_BUTTONS
) ? itemRect
->top
* BUTTON_SPACINGY
: SELECTED_TAB_OFFSET
);
382 itemRect
->bottom
= itemRect
->top
+ infoPtr
->tabHeight
;
386 * "scroll" it to make sure the item at the very left of the
387 * tab control is the leftmost visible tab.
389 if(lStyle
& TCS_VERTICAL
)
393 -TAB_GetItem(infoPtr
, infoPtr
->leftmostVisible
)->rect
.top
);
396 * Move the rectangle so the first item is slightly offset from
397 * the bottom of the tab control.
401 SELECTED_TAB_OFFSET
);
406 -TAB_GetItem(infoPtr
, infoPtr
->leftmostVisible
)->rect
.left
,
410 * Move the rectangle so the first item is slightly offset from
411 * the left of the tab control.
417 TRACE("item %d tab h=%d, rect=(%s)\n",
418 itemIndex
, infoPtr
->tabHeight
, wine_dbgstr_rect(itemRect
));
420 /* Now, calculate the position of the item as if it were selected. */
421 if (selectedRect
!=NULL
)
423 CopyRect(selectedRect
, itemRect
);
425 /* The rectangle of a selected item is a bit wider. */
426 if(lStyle
& TCS_VERTICAL
)
427 InflateRect(selectedRect
, 0, SELECTED_TAB_OFFSET
);
429 InflateRect(selectedRect
, SELECTED_TAB_OFFSET
, 0);
431 /* If it also a bit higher. */
432 if ((lStyle
& TCS_BOTTOM
) && (lStyle
& TCS_VERTICAL
))
434 selectedRect
->left
-= 2; /* the border is thicker on the right */
435 selectedRect
->right
+= SELECTED_TAB_OFFSET
;
437 else if (lStyle
& TCS_VERTICAL
)
439 selectedRect
->left
-= SELECTED_TAB_OFFSET
;
440 selectedRect
->right
+= 1;
442 else if (lStyle
& TCS_BOTTOM
)
444 selectedRect
->bottom
+= SELECTED_TAB_OFFSET
;
446 else /* not TCS_BOTTOM and not TCS_VERTICAL */
448 selectedRect
->top
-= SELECTED_TAB_OFFSET
;
449 selectedRect
->bottom
-= 1;
453 /* Check for visibility */
454 if (lStyle
& TCS_VERTICAL
)
455 return (itemRect
->top
< clientRect
.bottom
) && (itemRect
->bottom
> clientRect
.top
);
457 return (itemRect
->left
< clientRect
.right
) && (itemRect
->right
> clientRect
.left
);
461 TAB_GetItemRect(const TAB_INFO
*infoPtr
, WPARAM wParam
, LPARAM lParam
)
463 return TAB_InternalGetItemRect(infoPtr
, (INT
)wParam
, (LPRECT
)lParam
, (LPRECT
)NULL
);
466 /******************************************************************************
469 * This method is called to handle keyboard input
471 static LRESULT
TAB_KeyUp(TAB_INFO
* infoPtr
, WPARAM keyCode
)
478 newItem
= infoPtr
->uFocus
- 1;
481 newItem
= infoPtr
->uFocus
+ 1;
486 * If we changed to a valid item, change the selection
489 newItem
< infoPtr
->uNumItem
&&
490 infoPtr
->uFocus
!= newItem
)
492 if (!TAB_SendSimpleNotify(infoPtr
, TCN_SELCHANGING
))
494 infoPtr
->iSelected
= newItem
;
495 infoPtr
->uFocus
= newItem
;
496 TAB_SendSimpleNotify(infoPtr
, TCN_SELCHANGE
);
498 TAB_EnsureSelectionVisible(infoPtr
);
499 TAB_InvalidateTabArea(infoPtr
);
506 /******************************************************************************
509 * This method is called whenever the focus goes in or out of this control
510 * it is used to update the visual state of the control.
512 static void TAB_FocusChanging(const TAB_INFO
*infoPtr
)
518 * Get the rectangle for the item.
520 isVisible
= TAB_InternalGetItemRect(infoPtr
,
526 * If the rectangle is not completely invisible, invalidate that
527 * portion of the window.
531 TRACE("invalidate (%s)\n", wine_dbgstr_rect(&selectedRect
));
532 InvalidateRect(infoPtr
->hwnd
, &selectedRect
, TRUE
);
536 static INT
TAB_InternalHitTest (const TAB_INFO
*infoPtr
, POINT pt
, UINT
*flags
)
541 for (iCount
= 0; iCount
< infoPtr
->uNumItem
; iCount
++)
543 TAB_InternalGetItemRect(infoPtr
, iCount
, &rect
, NULL
);
545 if (PtInRect(&rect
, pt
))
547 *flags
= TCHT_ONITEM
;
552 *flags
= TCHT_NOWHERE
;
556 static inline LRESULT
557 TAB_HitTest (const TAB_INFO
*infoPtr
, LPTCHITTESTINFO lptest
)
559 return TAB_InternalHitTest (infoPtr
, lptest
->pt
, &lptest
->flags
);
562 /******************************************************************************
565 * Napster v2b5 has a tab control for its main navigation which has a client
566 * area that covers the whole area of the dialog pages.
567 * That's why it receives all msgs for that area and the underlying dialog ctrls
569 * So I decided that we should handle WM_NCHITTEST here and return
570 * HTTRANSPARENT if we don't hit the tab control buttons.
571 * FIXME: WM_NCHITTEST handling correct ? Fix it if you know that Windows
572 * doesn't do it that way. Maybe depends on tab control styles ?
574 static inline LRESULT
575 TAB_NCHitTest (const TAB_INFO
*infoPtr
, LPARAM lParam
)
580 pt
.x
= (short)LOWORD(lParam
);
581 pt
.y
= (short)HIWORD(lParam
);
582 ScreenToClient(infoPtr
->hwnd
, &pt
);
584 if (TAB_InternalHitTest(infoPtr
, pt
, &dummyflag
) == -1)
585 return HTTRANSPARENT
;
591 TAB_LButtonDown (TAB_INFO
*infoPtr
, WPARAM wParam
, LPARAM lParam
)
597 if (infoPtr
->hwndToolTip
)
598 TAB_RelayEvent (infoPtr
->hwndToolTip
, infoPtr
->hwnd
,
599 WM_LBUTTONDOWN
, wParam
, lParam
);
601 if (GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
) & TCS_FOCUSONBUTTONDOWN
) {
602 SetFocus (infoPtr
->hwnd
);
605 if (infoPtr
->hwndToolTip
)
606 TAB_RelayEvent (infoPtr
->hwndToolTip
, infoPtr
->hwnd
,
607 WM_LBUTTONDOWN
, wParam
, lParam
);
609 pt
.x
= (short)LOWORD(lParam
);
610 pt
.y
= (short)HIWORD(lParam
);
612 newItem
= TAB_InternalHitTest (infoPtr
, pt
, &dummy
);
614 TRACE("On Tab, item %d\n", newItem
);
616 if (newItem
!= -1 && infoPtr
->iSelected
!= newItem
)
618 if (!TAB_SendSimpleNotify(infoPtr
, TCN_SELCHANGING
))
620 infoPtr
->iSelected
= newItem
;
621 infoPtr
->uFocus
= newItem
;
622 TAB_SendSimpleNotify(infoPtr
, TCN_SELCHANGE
);
624 TAB_EnsureSelectionVisible(infoPtr
);
626 TAB_InvalidateTabArea(infoPtr
);
632 static inline LRESULT
633 TAB_LButtonUp (const TAB_INFO
*infoPtr
)
635 TAB_SendSimpleNotify(infoPtr
, NM_CLICK
);
640 static inline LRESULT
641 TAB_RButtonDown (const TAB_INFO
*infoPtr
)
643 TAB_SendSimpleNotify(infoPtr
, NM_RCLICK
);
647 /******************************************************************************
648 * TAB_DrawLoneItemInterior
650 * This calls TAB_DrawItemInterior. However, TAB_DrawItemInterior is normally
651 * called by TAB_DrawItem which is normally called by TAB_Refresh which sets
652 * up the device context and font. This routine does the same setup but
653 * only calls TAB_DrawItemInterior for the single specified item.
656 TAB_DrawLoneItemInterior(const TAB_INFO
* infoPtr
, int iItem
)
658 HDC hdc
= GetDC(infoPtr
->hwnd
);
661 /* Clip UpDown control to not draw over it */
662 if (infoPtr
->needsScrolling
)
664 GetWindowRect(infoPtr
->hwnd
, &rC
);
665 GetWindowRect(infoPtr
->hwndUpDown
, &r
);
666 ExcludeClipRect(hdc
, r
.left
- rC
.left
, r
.top
- rC
.top
, r
.right
- rC
.left
, r
.bottom
- rC
.top
);
668 TAB_DrawItemInterior(infoPtr
, hdc
, iItem
, NULL
);
669 ReleaseDC(infoPtr
->hwnd
, hdc
);
672 /* update a tab after hottracking - invalidate it or just redraw the interior,
673 * based on whether theming is used or not */
674 static inline void hottrack_refresh(const TAB_INFO
*infoPtr
, int tabIndex
)
676 if (tabIndex
== -1) return;
678 if (GetWindowTheme (infoPtr
->hwnd
))
681 TAB_InternalGetItemRect(infoPtr
, tabIndex
, &rect
, NULL
);
682 InvalidateRect (infoPtr
->hwnd
, &rect
, FALSE
);
685 TAB_DrawLoneItemInterior(infoPtr
, tabIndex
);
688 /******************************************************************************
689 * TAB_HotTrackTimerProc
691 * When a mouse-move event causes a tab to be highlighted (hot-tracking), a
692 * timer is setup so we can check if the mouse is moved out of our window.
693 * (We don't get an event when the mouse leaves, the mouse-move events just
694 * stop being delivered to our window and just start being delivered to
695 * another window.) This function is called when the timer triggers so
696 * we can check if the mouse has left our window. If so, we un-highlight
697 * the hot-tracked tab.
700 TAB_HotTrackTimerProc
702 HWND hwnd
, /* handle of window for timer messages */
703 UINT uMsg
, /* WM_TIMER message */
704 UINT_PTR idEvent
, /* timer identifier */
705 DWORD dwTime
/* current system time */
708 TAB_INFO
* infoPtr
= TAB_GetInfoPtr(hwnd
);
710 if (infoPtr
!= NULL
&& infoPtr
->iHotTracked
>= 0)
715 ** If we can't get the cursor position, or if the cursor is outside our
716 ** window, we un-highlight the hot-tracked tab. Note that the cursor is
717 ** "outside" even if it is within our bounding rect if another window
718 ** overlaps. Note also that the case where the cursor stayed within our
719 ** window but has moved off the hot-tracked tab will be handled by the
720 ** WM_MOUSEMOVE event.
722 if (!GetCursorPos(&pt
) || WindowFromPoint(pt
) != hwnd
)
724 /* Redraw iHotTracked to look normal */
725 INT iRedraw
= infoPtr
->iHotTracked
;
726 infoPtr
->iHotTracked
= -1;
727 hottrack_refresh (infoPtr
, iRedraw
);
729 /* Kill this timer */
730 KillTimer(hwnd
, TAB_HOTTRACK_TIMER
);
735 /******************************************************************************
738 * If a tab control has the TCS_HOTTRACK style, then the tab under the mouse
739 * should be highlighted. This function determines which tab in a tab control,
740 * if any, is under the mouse and records that information. The caller may
741 * supply output parameters to receive the item number of the tab item which
742 * was highlighted but isn't any longer and of the tab item which is now
743 * highlighted but wasn't previously. The caller can use this information to
744 * selectively redraw those tab items.
746 * If the caller has a mouse position, it can supply it through the pos
747 * parameter. For example, TAB_MouseMove does this. Otherwise, the caller
748 * supplies NULL and this function determines the current mouse position
756 int* out_redrawLeave
,
763 if (out_redrawLeave
!= NULL
)
764 *out_redrawLeave
= -1;
765 if (out_redrawEnter
!= NULL
)
766 *out_redrawEnter
= -1;
768 if ((GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
) & TCS_HOTTRACK
)
769 || GetWindowTheme (infoPtr
->hwnd
))
777 ScreenToClient(infoPtr
->hwnd
, &pt
);
781 pt
.x
= (short)LOWORD(*pos
);
782 pt
.y
= (short)HIWORD(*pos
);
785 item
= TAB_InternalHitTest(infoPtr
, pt
, &flags
);
788 if (item
!= infoPtr
->iHotTracked
)
790 if (infoPtr
->iHotTracked
>= 0)
792 /* Mark currently hot-tracked to be redrawn to look normal */
793 if (out_redrawLeave
!= NULL
)
794 *out_redrawLeave
= infoPtr
->iHotTracked
;
798 /* Kill timer which forces recheck of mouse pos */
799 KillTimer(infoPtr
->hwnd
, TAB_HOTTRACK_TIMER
);
804 /* Start timer so we recheck mouse pos */
805 UINT timerID
= SetTimer
809 TAB_HOTTRACK_TIMER_INTERVAL
,
810 TAB_HotTrackTimerProc
814 return; /* Hot tracking not available */
817 infoPtr
->iHotTracked
= item
;
821 /* Mark new hot-tracked to be redrawn to look highlighted */
822 if (out_redrawEnter
!= NULL
)
823 *out_redrawEnter
= item
;
828 /******************************************************************************
831 * Handles the mouse-move event. Updates tooltips. Updates hot-tracking.
834 TAB_MouseMove (TAB_INFO
*infoPtr
, WPARAM wParam
, LPARAM lParam
)
839 if (infoPtr
->hwndToolTip
)
840 TAB_RelayEvent (infoPtr
->hwndToolTip
, infoPtr
->hwnd
,
841 WM_LBUTTONDOWN
, wParam
, lParam
);
843 /* Determine which tab to highlight. Redraw tabs which change highlight
845 TAB_RecalcHotTrack(infoPtr
, &lParam
, &redrawLeave
, &redrawEnter
);
847 hottrack_refresh (infoPtr
, redrawLeave
);
848 hottrack_refresh (infoPtr
, redrawEnter
);
853 /******************************************************************************
856 * Calculates the tab control's display area given the window rectangle or
857 * the window rectangle given the requested display rectangle.
859 static LRESULT
TAB_AdjustRect(const TAB_INFO
*infoPtr
, WPARAM fLarger
, LPRECT prc
)
861 DWORD lStyle
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
862 LONG
*iRightBottom
, *iLeftTop
;
864 TRACE ("hwnd=%p fLarger=%ld (%s)\n", infoPtr
->hwnd
, fLarger
,
865 wine_dbgstr_rect(prc
));
867 if(lStyle
& TCS_VERTICAL
)
869 iRightBottom
= &(prc
->right
);
870 iLeftTop
= &(prc
->left
);
874 iRightBottom
= &(prc
->bottom
);
875 iLeftTop
= &(prc
->top
);
878 if (fLarger
) /* Go from display rectangle */
880 /* Add the height of the tabs. */
881 if (lStyle
& TCS_BOTTOM
)
882 *iRightBottom
+= infoPtr
->tabHeight
* infoPtr
->uNumRows
;
884 *iLeftTop
-= infoPtr
->tabHeight
* infoPtr
->uNumRows
+
885 ((lStyle
& TCS_BUTTONS
)? 3 * (infoPtr
->uNumRows
- 1) : 0);
887 /* Inflate the rectangle for the padding */
888 InflateRect(prc
, DISPLAY_AREA_PADDINGX
, DISPLAY_AREA_PADDINGY
);
890 /* Inflate for the border */
891 InflateRect(prc
, CONTROL_BORDER_SIZEX
, CONTROL_BORDER_SIZEY
);
893 else /* Go from window rectangle. */
895 /* Deflate the rectangle for the border */
896 InflateRect(prc
, -CONTROL_BORDER_SIZEX
, -CONTROL_BORDER_SIZEY
);
898 /* Deflate the rectangle for the padding */
899 InflateRect(prc
, -DISPLAY_AREA_PADDINGX
, -DISPLAY_AREA_PADDINGY
);
901 /* Remove the height of the tabs. */
902 if (lStyle
& TCS_BOTTOM
)
903 *iRightBottom
-= infoPtr
->tabHeight
* infoPtr
->uNumRows
;
905 *iLeftTop
+= (infoPtr
->tabHeight
) * infoPtr
->uNumRows
+
906 ((lStyle
& TCS_BUTTONS
)? 3 * (infoPtr
->uNumRows
- 1) : 0);
912 /******************************************************************************
915 * This method will handle the notification from the scroll control and
916 * perform the scrolling operation on the tab control.
918 static LRESULT
TAB_OnHScroll(TAB_INFO
*infoPtr
, int nScrollCode
, int nPos
, HWND hwndScroll
)
920 if(nScrollCode
== SB_THUMBPOSITION
&& nPos
!= infoPtr
->leftmostVisible
)
922 if(nPos
< infoPtr
->leftmostVisible
)
923 infoPtr
->leftmostVisible
--;
925 infoPtr
->leftmostVisible
++;
927 TAB_RecalcHotTrack(infoPtr
, NULL
, NULL
, NULL
);
928 TAB_InvalidateTabArea(infoPtr
);
929 SendMessageW(infoPtr
->hwndUpDown
, UDM_SETPOS
, 0,
930 MAKELONG(infoPtr
->leftmostVisible
, 0));
936 /******************************************************************************
939 * This method will check the current scrolling state and make sure the
940 * scrolling control is displayed (or not).
942 static void TAB_SetupScrolling(
945 const RECT
* clientRect
)
947 static const WCHAR msctls_updown32W
[] = { 'm','s','c','t','l','s','_','u','p','d','o','w','n','3','2',0 };
948 static const WCHAR emptyW
[] = { 0 };
950 DWORD lStyle
= GetWindowLongW(hwnd
, GWL_STYLE
);
952 if (infoPtr
->needsScrolling
)
958 * Calculate the position of the scroll control.
960 if(lStyle
& TCS_VERTICAL
)
962 controlPos
.right
= clientRect
->right
;
963 controlPos
.left
= controlPos
.right
- 2 * GetSystemMetrics(SM_CXHSCROLL
);
965 if (lStyle
& TCS_BOTTOM
)
967 controlPos
.top
= clientRect
->bottom
- infoPtr
->tabHeight
;
968 controlPos
.bottom
= controlPos
.top
+ GetSystemMetrics(SM_CYHSCROLL
);
972 controlPos
.bottom
= clientRect
->top
+ infoPtr
->tabHeight
;
973 controlPos
.top
= controlPos
.bottom
- GetSystemMetrics(SM_CYHSCROLL
);
978 controlPos
.right
= clientRect
->right
;
979 controlPos
.left
= controlPos
.right
- 2 * GetSystemMetrics(SM_CXHSCROLL
);
981 if (lStyle
& TCS_BOTTOM
)
983 controlPos
.top
= clientRect
->bottom
- infoPtr
->tabHeight
;
984 controlPos
.bottom
= controlPos
.top
+ GetSystemMetrics(SM_CYHSCROLL
);
988 controlPos
.bottom
= clientRect
->top
+ infoPtr
->tabHeight
;
989 controlPos
.top
= controlPos
.bottom
- GetSystemMetrics(SM_CYHSCROLL
);
994 * If we don't have a scroll control yet, we want to create one.
995 * If we have one, we want to make sure it's positioned properly.
997 if (infoPtr
->hwndUpDown
==0)
999 infoPtr
->hwndUpDown
= CreateWindowW(msctls_updown32W
, emptyW
,
1000 WS_VISIBLE
| WS_CHILD
| UDS_HORZ
,
1001 controlPos
.left
, controlPos
.top
,
1002 controlPos
.right
- controlPos
.left
,
1003 controlPos
.bottom
- controlPos
.top
,
1004 hwnd
, NULL
, NULL
, NULL
);
1008 SetWindowPos(infoPtr
->hwndUpDown
,
1010 controlPos
.left
, controlPos
.top
,
1011 controlPos
.right
- controlPos
.left
,
1012 controlPos
.bottom
- controlPos
.top
,
1013 SWP_SHOWWINDOW
| SWP_NOZORDER
);
1016 /* Now calculate upper limit of the updown control range.
1017 * We do this by calculating how many tabs will be offscreen when the
1018 * last tab is visible.
1020 if(infoPtr
->uNumItem
)
1022 vsize
= clientRect
->right
- (controlPos
.right
- controlPos
.left
+ 1);
1023 maxRange
= infoPtr
->uNumItem
;
1024 tabwidth
= TAB_GetItem(infoPtr
, infoPtr
->uNumItem
- 1)->rect
.right
;
1026 for(; maxRange
> 0; maxRange
--)
1028 if(tabwidth
- TAB_GetItem(infoPtr
,maxRange
- 1)->rect
.left
> vsize
)
1032 if(maxRange
== infoPtr
->uNumItem
)
1038 /* If we once had a scroll control... hide it */
1039 if (infoPtr
->hwndUpDown
!=0)
1040 ShowWindow(infoPtr
->hwndUpDown
, SW_HIDE
);
1042 if (infoPtr
->hwndUpDown
)
1043 SendMessageW(infoPtr
->hwndUpDown
, UDM_SETRANGE32
, 0, maxRange
);
1046 /******************************************************************************
1049 * This method will calculate the position rectangles of all the items in the
1050 * control. The rectangle calculated starts at 0 for the first item in the
1051 * list and ignores scrolling and selection.
1052 * It also uses the current font to determine the height of the tab row and
1053 * it checks if all the tabs fit in the client area of the window. If they
1054 * don't, a scrolling control is added.
1056 static void TAB_SetItemBounds (TAB_INFO
*infoPtr
)
1058 LONG lStyle
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
1059 TEXTMETRICW fontMetrics
;
1062 INT curItemRowCount
;
1063 HFONT hFont
, hOldFont
;
1072 * We need to get text information so we need a DC and we need to select
1075 hdc
= GetDC(infoPtr
->hwnd
);
1077 hFont
= infoPtr
->hFont
? infoPtr
->hFont
: GetStockObject (SYSTEM_FONT
);
1078 hOldFont
= SelectObject (hdc
, hFont
);
1081 * We will base the rectangle calculations on the client rectangle
1084 GetClientRect(infoPtr
->hwnd
, &clientRect
);
1086 /* if TCS_VERTICAL then swap the height and width so this code places the
1087 tabs along the top of the rectangle and we can just rotate them after
1088 rather than duplicate all of the below code */
1089 if(lStyle
& TCS_VERTICAL
)
1091 iTemp
= clientRect
.bottom
;
1092 clientRect
.bottom
= clientRect
.right
;
1093 clientRect
.right
= iTemp
;
1096 /* Now use hPadding and vPadding */
1097 infoPtr
->uHItemPadding
= infoPtr
->uHItemPadding_s
;
1098 infoPtr
->uVItemPadding
= infoPtr
->uVItemPadding_s
;
1100 /* The leftmost item will be "0" aligned */
1102 curItemRowCount
= infoPtr
->uNumItem
? 1 : 0;
1104 if (!(infoPtr
->fHeightSet
))
1107 int icon_height
= 0;
1109 /* Use the current font to determine the height of a tab. */
1110 GetTextMetricsW(hdc
, &fontMetrics
);
1112 /* Get the icon height */
1114 ImageList_GetIconSize(infoPtr
->himl
, 0, &icon_height
);
1116 /* Take the highest between font or icon */
1117 if (fontMetrics
.tmHeight
> icon_height
)
1118 item_height
= fontMetrics
.tmHeight
+ 2;
1120 item_height
= icon_height
;
1123 * Make sure there is enough space for the letters + icon + growing the
1124 * selected item + extra space for the selected item.
1126 infoPtr
->tabHeight
= item_height
+
1127 ((lStyle
& TCS_BUTTONS
) ? 2 : 1) *
1128 infoPtr
->uVItemPadding
;
1130 TRACE("tabH=%d, tmH=%d, iconh=%d\n",
1131 infoPtr
->tabHeight
, fontMetrics
.tmHeight
, icon_height
);
1134 TRACE("client right=%d\n", clientRect
.right
);
1136 /* Get the icon width */
1139 ImageList_GetIconSize(infoPtr
->himl
, &icon_width
, 0);
1141 if (lStyle
& TCS_FIXEDWIDTH
)
1144 /* Add padding if icon is present */
1145 icon_width
+= infoPtr
->uHItemPadding
;
1148 for (curItem
= 0; curItem
< infoPtr
->uNumItem
; curItem
++)
1150 TAB_ITEM
*curr
= TAB_GetItem(infoPtr
, curItem
);
1152 /* Set the leftmost position of the tab. */
1153 curr
->rect
.left
= curItemLeftPos
;
1155 if (lStyle
& TCS_FIXEDWIDTH
)
1157 curr
->rect
.right
= curr
->rect
.left
+
1158 max(infoPtr
->tabWidth
, icon_width
);
1160 else if (!curr
->pszText
)
1162 /* If no text use minimum tab width including padding. */
1163 if (infoPtr
->tabMinWidth
< 0)
1164 curr
->rect
.right
= curr
->rect
.left
+ GET_DEFAULT_MIN_TAB_WIDTH(infoPtr
);
1167 curr
->rect
.right
= curr
->rect
.left
+ infoPtr
->tabMinWidth
;
1169 /* Add extra padding if icon is present */
1170 if (infoPtr
->himl
&& infoPtr
->tabMinWidth
> 0 && infoPtr
->tabMinWidth
< DEFAULT_MIN_TAB_WIDTH
1171 && infoPtr
->uHItemPadding
> 1)
1172 curr
->rect
.right
+= EXTRA_ICON_PADDING
* (infoPtr
->uHItemPadding
-1);
1179 /* Calculate how wide the tab is depending on the text it contains */
1180 GetTextExtentPoint32W(hdc
, curr
->pszText
,
1181 lstrlenW(curr
->pszText
), &size
);
1183 tabwidth
= size
.cx
+ icon_width
+ 2 * infoPtr
->uHItemPadding
;
1185 if (infoPtr
->tabMinWidth
< 0)
1186 tabwidth
= max(tabwidth
, GET_DEFAULT_MIN_TAB_WIDTH(infoPtr
));
1188 tabwidth
= max(tabwidth
, infoPtr
->tabMinWidth
);
1190 curr
->rect
.right
= curr
->rect
.left
+ tabwidth
;
1191 TRACE("for <%s>, l,r=%d,%d\n",
1192 debugstr_w(curr
->pszText
), curr
->rect
.left
, curr
->rect
.right
);
1196 * Check if this is a multiline tab control and if so
1197 * check to see if we should wrap the tabs
1199 * Wrap all these tabs. We will arrange them evenly later.
1203 if (((lStyle
& TCS_MULTILINE
) || (lStyle
& TCS_VERTICAL
)) &&
1205 (clientRect
.right
- CONTROL_BORDER_SIZEX
- DISPLAY_AREA_PADDINGX
)))
1207 curr
->rect
.right
-= curr
->rect
.left
;
1209 curr
->rect
.left
= 0;
1211 TRACE("wrapping <%s>, l,r=%d,%d\n", debugstr_w(curr
->pszText
),
1212 curr
->rect
.left
, curr
->rect
.right
);
1215 curr
->rect
.bottom
= 0;
1216 curr
->rect
.top
= curItemRowCount
- 1;
1218 TRACE("Rect: %s\n", wine_dbgstr_rect(&curr
->rect
));
1221 * The leftmost position of the next item is the rightmost position
1224 if (lStyle
& TCS_BUTTONS
)
1226 curItemLeftPos
= curr
->rect
.right
+ BUTTON_SPACINGX
;
1227 if (lStyle
& TCS_FLATBUTTONS
)
1228 curItemLeftPos
+= FLAT_BTN_SPACINGX
;
1231 curItemLeftPos
= curr
->rect
.right
;
1234 if (!((lStyle
& TCS_MULTILINE
) || (lStyle
& TCS_VERTICAL
)))
1237 * Check if we need a scrolling control.
1239 infoPtr
->needsScrolling
= (curItemLeftPos
+ (2 * SELECTED_TAB_OFFSET
) >
1242 /* Don't need scrolling, then update infoPtr->leftmostVisible */
1243 if(!infoPtr
->needsScrolling
)
1244 infoPtr
->leftmostVisible
= 0;
1249 * No scrolling in Multiline or Vertical styles.
1251 infoPtr
->needsScrolling
= FALSE
;
1252 infoPtr
->leftmostVisible
= 0;
1254 TAB_SetupScrolling(infoPtr
->hwnd
, infoPtr
, &clientRect
);
1256 /* Set the number of rows */
1257 infoPtr
->uNumRows
= curItemRowCount
;
1259 /* Arrange all tabs evenly if style says so */
1260 if (!(lStyle
& TCS_RAGGEDRIGHT
) &&
1261 ((lStyle
& TCS_MULTILINE
) || (lStyle
& TCS_VERTICAL
)) &&
1262 (infoPtr
->uNumItem
> 0) &&
1263 (infoPtr
->uNumRows
> 1))
1265 INT tabPerRow
,remTab
,iRow
;
1270 * Ok windows tries to even out the rows. place the same
1271 * number of tabs in each row. So lets give that a shot
1274 tabPerRow
= infoPtr
->uNumItem
/ (infoPtr
->uNumRows
);
1275 remTab
= infoPtr
->uNumItem
% (infoPtr
->uNumRows
);
1277 for (iItm
=0,iRow
=0,iCount
=0,curItemLeftPos
=0;
1278 iItm
<infoPtr
->uNumItem
;
1281 /* normalize the current rect */
1282 TAB_ITEM
*curr
= TAB_GetItem(infoPtr
, iItm
);
1284 /* shift the item to the left side of the clientRect */
1285 curr
->rect
.right
-= curr
->rect
.left
;
1286 curr
->rect
.left
= 0;
1288 TRACE("r=%d, cl=%d, cl.r=%d, iCount=%d, iRow=%d, uNumRows=%d, remTab=%d, tabPerRow=%d\n",
1289 curr
->rect
.right
, curItemLeftPos
, clientRect
.right
,
1290 iCount
, iRow
, infoPtr
->uNumRows
, remTab
, tabPerRow
);
1292 /* if we have reached the maximum number of tabs on this row */
1293 /* move to the next row, reset our current item left position and */
1294 /* the count of items on this row */
1296 if (lStyle
& TCS_VERTICAL
) {
1297 /* Vert: Add the remaining tabs in the *last* remainder rows */
1298 if (iCount
>= ((iRow
>=(INT
)infoPtr
->uNumRows
- remTab
)?tabPerRow
+ 1:tabPerRow
)) {
1304 /* Horz: Add the remaining tabs in the *first* remainder rows */
1305 if (iCount
>= ((iRow
<remTab
)?tabPerRow
+ 1:tabPerRow
)) {
1312 /* shift the item to the right to place it as the next item in this row */
1313 curr
->rect
.left
+= curItemLeftPos
;
1314 curr
->rect
.right
+= curItemLeftPos
;
1315 curr
->rect
.top
= iRow
;
1316 if (lStyle
& TCS_BUTTONS
)
1318 curItemLeftPos
= curr
->rect
.right
+ 1;
1319 if (lStyle
& TCS_FLATBUTTONS
)
1320 curItemLeftPos
+= FLAT_BTN_SPACINGX
;
1323 curItemLeftPos
= curr
->rect
.right
;
1325 TRACE("arranging <%s>, l,r=%d,%d, row=%d\n",
1326 debugstr_w(curr
->pszText
), curr
->rect
.left
,
1327 curr
->rect
.right
, curr
->rect
.top
);
1334 INT widthDiff
, iIndexStart
=0, iIndexEnd
=0;
1338 while(iIndexStart
< infoPtr
->uNumItem
)
1340 TAB_ITEM
*start
= TAB_GetItem(infoPtr
, iIndexStart
);
1343 * find the index of the row
1345 /* find the first item on the next row */
1346 for (iIndexEnd
=iIndexStart
;
1347 (iIndexEnd
< infoPtr
->uNumItem
) &&
1348 (TAB_GetItem(infoPtr
, iIndexEnd
)->rect
.top
==
1351 /* intentionally blank */;
1354 * we need to justify these tabs so they fill the whole given
1358 /* find the amount of space remaining on this row */
1359 widthDiff
= clientRect
.right
- (2 * SELECTED_TAB_OFFSET
) -
1360 TAB_GetItem(infoPtr
, iIndexEnd
- 1)->rect
.right
;
1362 /* iCount is the number of tab items on this row */
1363 iCount
= iIndexEnd
- iIndexStart
;
1367 remainder
= widthDiff
% iCount
;
1368 widthDiff
= widthDiff
/ iCount
;
1369 /* add widthDiff/iCount, or extra space/items on row, to each item on this row */
1370 for (iIndex
=iIndexStart
, iCount
=0; iIndex
< iIndexEnd
; iIndex
++, iCount
++)
1372 TAB_ITEM
*item
= TAB_GetItem(infoPtr
, iIndex
);
1374 item
->rect
.left
+= iCount
* widthDiff
;
1375 item
->rect
.right
+= (iCount
+ 1) * widthDiff
;
1377 TRACE("adjusting 1 <%s>, l,r=%d,%d\n",
1378 debugstr_w(item
->pszText
),
1379 item
->rect
.left
, item
->rect
.right
);
1382 TAB_GetItem(infoPtr
, iIndex
- 1)->rect
.right
+= remainder
;
1384 else /* we have only one item on this row, make it take up the entire row */
1386 start
->rect
.left
= clientRect
.left
;
1387 start
->rect
.right
= clientRect
.right
- 4;
1389 TRACE("adjusting 2 <%s>, l,r=%d,%d\n",
1390 debugstr_w(start
->pszText
),
1391 start
->rect
.left
, start
->rect
.right
);
1396 iIndexStart
= iIndexEnd
;
1401 /* if TCS_VERTICAL rotate the tabs so they are along the side of the clientRect */
1402 if(lStyle
& TCS_VERTICAL
)
1405 for(iIndex
= 0; iIndex
< infoPtr
->uNumItem
; iIndex
++)
1407 rcItem
= &TAB_GetItem(infoPtr
, iIndex
)->rect
;
1409 rcOriginal
= *rcItem
;
1411 /* this is rotating the items by 90 degrees clockwise around the center of the control */
1412 rcItem
->top
= (rcOriginal
.left
- clientRect
.left
);
1413 rcItem
->bottom
= rcItem
->top
+ (rcOriginal
.right
- rcOriginal
.left
);
1414 rcItem
->left
= rcOriginal
.top
;
1415 rcItem
->right
= rcOriginal
.bottom
;
1419 TAB_EnsureSelectionVisible(infoPtr
);
1420 TAB_RecalcHotTrack(infoPtr
, NULL
, NULL
, NULL
);
1423 SelectObject (hdc
, hOldFont
);
1424 ReleaseDC (infoPtr
->hwnd
, hdc
);
1429 TAB_EraseTabInterior(const TAB_INFO
*infoPtr
, HDC hdc
, INT iItem
, RECT
*drawRect
)
1431 LONG lStyle
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
1432 HBRUSH hbr
= CreateSolidBrush (comctl32_color
.clrBtnFace
);
1433 BOOL deleteBrush
= TRUE
;
1434 RECT rTemp
= *drawRect
;
1436 InflateRect(&rTemp
, -2, -2);
1437 if (lStyle
& TCS_BUTTONS
)
1439 if (iItem
== infoPtr
->iSelected
)
1441 /* Background color */
1442 if (!(lStyle
& TCS_OWNERDRAWFIXED
))
1445 hbr
= GetSysColorBrush(COLOR_SCROLLBAR
);
1447 SetTextColor(hdc
, comctl32_color
.clr3dFace
);
1448 SetBkColor(hdc
, comctl32_color
.clr3dHilight
);
1450 /* if COLOR_WINDOW happens to be the same as COLOR_3DHILIGHT
1451 * we better use 0x55aa bitmap brush to make scrollbar's background
1452 * look different from the window background.
1454 if (comctl32_color
.clr3dHilight
== comctl32_color
.clrWindow
)
1455 hbr
= COMCTL32_hPattern55AABrush
;
1457 deleteBrush
= FALSE
;
1459 FillRect(hdc
, &rTemp
, hbr
);
1461 else /* ! selected */
1463 if (lStyle
& TCS_FLATBUTTONS
)
1465 FillRect(hdc
, drawRect
, hbr
);
1466 if (iItem
== infoPtr
->iHotTracked
)
1467 DrawEdge(hdc
, drawRect
, EDGE_RAISED
, BF_SOFT
|BF_RECT
);
1470 FillRect(hdc
, &rTemp
, hbr
);
1474 else /* !TCS_BUTTONS */
1476 if (!GetWindowTheme (infoPtr
->hwnd
))
1477 FillRect(hdc
, &rTemp
, hbr
);
1481 if (deleteBrush
) DeleteObject(hbr
);
1484 /******************************************************************************
1485 * TAB_DrawItemInterior
1487 * This method is used to draw the interior (text and icon) of a single tab
1488 * into the tab control.
1491 TAB_DrawItemInterior(const TAB_INFO
*infoPtr
, HDC hdc
, INT iItem
, RECT
*drawRect
)
1493 LONG lStyle
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
1502 /* if (drawRect == NULL) */
1509 * Get the rectangle for the item.
1511 isVisible
= TAB_InternalGetItemRect(infoPtr
, iItem
, &itemRect
, &selectedRect
);
1516 * Make sure drawRect points to something valid; simplifies code.
1518 drawRect
= &localRect
;
1521 * This logic copied from the part of TAB_DrawItem which draws
1522 * the tab background. It's important to keep it in sync. I
1523 * would have liked to avoid code duplication, but couldn't figure
1524 * out how without making spaghetti of TAB_DrawItem.
1526 if (iItem
== infoPtr
->iSelected
)
1527 *drawRect
= selectedRect
;
1529 *drawRect
= itemRect
;
1531 if (lStyle
& TCS_BUTTONS
)
1533 if (iItem
== infoPtr
->iSelected
)
1535 drawRect
->left
+= 4;
1537 drawRect
->right
-= 4;
1538 drawRect
->bottom
-= 1;
1542 drawRect
->left
+= 2;
1544 drawRect
->right
-= 2;
1545 drawRect
->bottom
-= 2;
1550 if ((lStyle
& TCS_VERTICAL
) && (lStyle
& TCS_BOTTOM
))
1552 if (iItem
!= infoPtr
->iSelected
)
1554 drawRect
->left
+= 2;
1556 drawRect
->bottom
-= 2;
1559 else if (lStyle
& TCS_VERTICAL
)
1561 if (iItem
== infoPtr
->iSelected
)
1563 drawRect
->right
+= 1;
1568 drawRect
->right
-= 2;
1569 drawRect
->bottom
-= 2;
1572 else if (lStyle
& TCS_BOTTOM
)
1574 if (iItem
== infoPtr
->iSelected
)
1580 InflateRect(drawRect
, -2, -2);
1581 drawRect
->bottom
+= 2;
1586 if (iItem
== infoPtr
->iSelected
)
1588 drawRect
->bottom
+= 3;
1592 drawRect
->bottom
-= 2;
1593 InflateRect(drawRect
, -2, 0);
1598 TRACE("drawRect=(%s)\n", wine_dbgstr_rect(drawRect
));
1600 /* Clear interior */
1601 TAB_EraseTabInterior (infoPtr
, hdc
, iItem
, drawRect
);
1603 /* Draw the focus rectangle */
1604 if (!(lStyle
& TCS_FOCUSNEVER
) &&
1605 (GetFocus() == infoPtr
->hwnd
) &&
1606 (iItem
== infoPtr
->uFocus
) )
1608 RECT rFocus
= *drawRect
;
1609 InflateRect(&rFocus
, -3, -3);
1610 if (lStyle
& TCS_BOTTOM
&& !(lStyle
& TCS_VERTICAL
))
1612 if (lStyle
& TCS_BUTTONS
)
1618 DrawFocusRect(hdc
, &rFocus
);
1624 htextPen
= CreatePen( PS_SOLID
, 1, GetSysColor(COLOR_BTNTEXT
) );
1625 holdPen
= SelectObject(hdc
, htextPen
);
1626 hOldFont
= SelectObject(hdc
, infoPtr
->hFont
);
1629 * Setup for text output
1631 oldBkMode
= SetBkMode(hdc
, TRANSPARENT
);
1632 if (!GetWindowTheme (infoPtr
->hwnd
) || (lStyle
& TCS_BUTTONS
))
1633 SetTextColor(hdc
, (((lStyle
& TCS_HOTTRACK
) && (iItem
== infoPtr
->iHotTracked
)
1634 && !(lStyle
& TCS_FLATBUTTONS
))
1635 | (TAB_GetItem(infoPtr
, iItem
)->dwState
& TCIS_HIGHLIGHTED
)) ?
1636 comctl32_color
.clrHighlight
: comctl32_color
.clrBtnText
);
1639 * if owner draw, tell the owner to draw
1641 if ((lStyle
& TCS_OWNERDRAWFIXED
) && GetParent(infoPtr
->hwnd
))
1647 drawRect
->right
-= 1;
1648 if ( iItem
== infoPtr
->iSelected
)
1650 drawRect
->right
-= 1;
1651 drawRect
->left
+= 1;
1655 * get the control id
1657 id
= (UINT
)GetWindowLongPtrW( infoPtr
->hwnd
, GWLP_ID
);
1660 * put together the DRAWITEMSTRUCT
1662 dis
.CtlType
= ODT_TAB
;
1665 dis
.itemAction
= ODA_DRAWENTIRE
;
1667 if ( iItem
== infoPtr
->iSelected
)
1668 dis
.itemState
|= ODS_SELECTED
;
1669 if (infoPtr
->uFocus
== iItem
)
1670 dis
.itemState
|= ODS_FOCUS
;
1671 dis
.hwndItem
= infoPtr
->hwnd
;
1673 CopyRect(&dis
.rcItem
,drawRect
);
1674 dis
.itemData
= (ULONG_PTR
)TAB_GetItem(infoPtr
, iItem
)->extra
;
1677 * send the draw message
1679 SendMessageW( infoPtr
->hwndNotify
, WM_DRAWITEM
, (WPARAM
)id
, (LPARAM
)&dis
);
1683 TAB_ITEM
*item
= TAB_GetItem(infoPtr
, iItem
);
1687 /* used to center the icon and text in the tab */
1689 INT center_offset_h
, center_offset_v
;
1691 /* set rcImage to drawRect, we will use top & left in our ImageList_Draw call */
1692 rcImage
= *drawRect
;
1696 rcText
.left
= rcText
.top
= rcText
.right
= rcText
.bottom
= 0;
1698 /* get the rectangle that the text fits in */
1701 DrawTextW(hdc
, item
->pszText
, -1, &rcText
, DT_CALCRECT
);
1704 * If not owner draw, then do the drawing ourselves.
1708 if (infoPtr
->himl
&& item
->iImage
!= -1)
1713 ImageList_GetIconSize(infoPtr
->himl
, &cx
, &cy
);
1715 if(lStyle
& TCS_VERTICAL
)
1717 center_offset_h
= ((drawRect
->bottom
- drawRect
->top
) - (cy
+ infoPtr
->uHItemPadding
+ (rcText
.right
- rcText
.left
))) / 2;
1718 center_offset_v
= (drawRect
->left
+ (drawRect
->right
- drawRect
->left
) - cx
) / 2;
1722 center_offset_h
= ((drawRect
->right
- drawRect
->left
) - (cx
+ infoPtr
->uHItemPadding
+ (rcText
.right
- rcText
.left
))) / 2;
1723 center_offset_v
= (drawRect
->top
+ (drawRect
->bottom
- drawRect
->top
) - cy
) / 2;
1726 /* if an item is selected, the icon is shifted up instead of down */
1727 if (iItem
== infoPtr
->iSelected
)
1728 center_offset_v
-= infoPtr
->uVItemPadding
/ 2;
1730 center_offset_v
+= infoPtr
->uVItemPadding
/ 2;
1732 if (lStyle
& TCS_FIXEDWIDTH
&& lStyle
& (TCS_FORCELABELLEFT
| TCS_FORCEICONLEFT
))
1733 center_offset_h
= infoPtr
->uHItemPadding
;
1735 if (center_offset_h
< 2)
1736 center_offset_h
= 2;
1738 if (center_offset_v
< 0)
1739 center_offset_v
= 0;
1741 TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%s), textlen=%d\n",
1742 debugstr_w(item
->pszText
), center_offset_h
, center_offset_v
,
1743 wine_dbgstr_rect(drawRect
), (rcText
.right
-rcText
.left
));
1745 if((lStyle
& TCS_VERTICAL
) && (lStyle
& TCS_BOTTOM
))
1747 rcImage
.top
= drawRect
->top
+ center_offset_h
;
1748 /* if tab is TCS_VERTICAL and TCS_BOTTOM, the text is drawn from the */
1749 /* right side of the tab, but the image still uses the left as its x position */
1750 /* this keeps the image always drawn off of the same side of the tab */
1751 rcImage
.left
= drawRect
->right
- cx
- center_offset_v
;
1752 drawRect
->top
+= cy
+ infoPtr
->uHItemPadding
;
1754 else if(lStyle
& TCS_VERTICAL
)
1756 rcImage
.top
= drawRect
->bottom
- cy
- center_offset_h
;
1757 rcImage
.left
= drawRect
->left
+ center_offset_v
;
1758 drawRect
->bottom
-= cy
+ infoPtr
->uHItemPadding
;
1760 else /* normal style, whether TCS_BOTTOM or not */
1762 rcImage
.left
= drawRect
->left
+ center_offset_h
;
1763 rcImage
.top
= drawRect
->top
+ center_offset_v
;
1764 drawRect
->left
+= cx
+ infoPtr
->uHItemPadding
;
1767 TRACE("drawing image=%d, left=%d, top=%d\n",
1768 item
->iImage
, rcImage
.left
, rcImage
.top
-1);
1780 /* Now position text */
1781 if (lStyle
& TCS_FIXEDWIDTH
&& lStyle
& TCS_FORCELABELLEFT
)
1782 center_offset_h
= infoPtr
->uHItemPadding
;
1784 if(lStyle
& TCS_VERTICAL
)
1785 center_offset_h
= ((drawRect
->bottom
- drawRect
->top
) - (rcText
.right
- rcText
.left
)) / 2;
1787 center_offset_h
= ((drawRect
->right
- drawRect
->left
) - (rcText
.right
- rcText
.left
)) / 2;
1789 if(lStyle
& TCS_VERTICAL
)
1791 if(lStyle
& TCS_BOTTOM
)
1792 drawRect
->top
+=center_offset_h
;
1794 drawRect
->bottom
-=center_offset_h
;
1796 center_offset_v
= ((drawRect
->right
- drawRect
->left
) - (rcText
.bottom
- rcText
.top
)) / 2;
1800 drawRect
->left
+= center_offset_h
;
1801 center_offset_v
= ((drawRect
->bottom
- drawRect
->top
) - (rcText
.bottom
- rcText
.top
)) / 2;
1804 /* if an item is selected, the text is shifted up instead of down */
1805 if (iItem
== infoPtr
->iSelected
)
1806 center_offset_v
-= infoPtr
->uVItemPadding
/ 2;
1808 center_offset_v
+= infoPtr
->uVItemPadding
/ 2;
1810 if (center_offset_v
< 0)
1811 center_offset_v
= 0;
1813 if(lStyle
& TCS_VERTICAL
)
1814 drawRect
->left
+= center_offset_v
;
1816 drawRect
->top
+= center_offset_v
;
1819 if(lStyle
& TCS_VERTICAL
) /* if we are vertical rotate the text and each character */
1821 static const WCHAR ArialW
[] = { 'A','r','i','a','l',0 };
1824 INT nEscapement
= 900;
1825 INT nOrientation
= 900;
1827 if(lStyle
& TCS_BOTTOM
)
1830 nOrientation
= -900;
1833 /* to get a font with the escapement and orientation we are looking for, we need to */
1834 /* call CreateFontIndirectA, which requires us to set the values of the logfont we pass in */
1835 if (!GetObjectW((infoPtr
->hFont
) ?
1836 infoPtr
->hFont
: GetStockObject(SYSTEM_FONT
),
1837 sizeof(LOGFONTW
),&logfont
))
1841 lstrcpyW(logfont
.lfFaceName
, ArialW
);
1842 logfont
.lfHeight
= -MulDiv(iPointSize
, GetDeviceCaps(hdc
, LOGPIXELSY
),
1844 logfont
.lfWeight
= FW_NORMAL
;
1845 logfont
.lfItalic
= 0;
1846 logfont
.lfUnderline
= 0;
1847 logfont
.lfStrikeOut
= 0;
1850 logfont
.lfEscapement
= nEscapement
;
1851 logfont
.lfOrientation
= nOrientation
;
1852 hFont
= CreateFontIndirectW(&logfont
);
1853 SelectObject(hdc
, hFont
);
1858 (lStyle
& TCS_BOTTOM
) ? drawRect
->right
: drawRect
->left
,
1859 (!(lStyle
& TCS_BOTTOM
)) ? drawRect
->bottom
: drawRect
->top
,
1863 lstrlenW(item
->pszText
),
1867 DeleteObject(hFont
);
1871 TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%s), textlen=%d\n",
1872 debugstr_w(item
->pszText
), center_offset_h
, center_offset_v
,
1873 wine_dbgstr_rect(drawRect
), (rcText
.right
-rcText
.left
));
1880 lstrlenW(item
->pszText
),
1882 DT_LEFT
| DT_SINGLELINE
1887 *drawRect
= rcTemp
; /* restore drawRect */
1893 SelectObject(hdc
, hOldFont
);
1894 SetBkMode(hdc
, oldBkMode
);
1895 SelectObject(hdc
, holdPen
);
1896 DeleteObject( htextPen
);
1899 /******************************************************************************
1902 * This method is used to draw a single tab into the tab control.
1904 static void TAB_DrawItem(const TAB_INFO
*infoPtr
, HDC hdc
, INT iItem
)
1906 LONG lStyle
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
1910 RECT r
, fillRect
, r1
;
1913 COLORREF bkgnd
, corner
;
1917 * Get the rectangle for the item.
1919 isVisible
= TAB_InternalGetItemRect(infoPtr
,
1928 /* Clip UpDown control to not draw over it */
1929 if (infoPtr
->needsScrolling
)
1931 GetWindowRect(infoPtr
->hwnd
, &rC
);
1932 GetWindowRect(infoPtr
->hwndUpDown
, &rUD
);
1933 ExcludeClipRect(hdc
, rUD
.left
- rC
.left
, rUD
.top
- rC
.top
, rUD
.right
- rC
.left
, rUD
.bottom
- rC
.top
);
1936 /* If you need to see what the control is doing,
1937 * then override these variables. They will change what
1938 * fill colors are used for filling the tabs, and the
1939 * corners when drawing the edge.
1941 bkgnd
= comctl32_color
.clrBtnFace
;
1942 corner
= comctl32_color
.clrBtnFace
;
1944 if (lStyle
& TCS_BUTTONS
)
1946 /* Get item rectangle */
1949 /* Separators between flat buttons */
1950 if (lStyle
& TCS_FLATBUTTONS
)
1953 r1
.right
+= (FLAT_BTN_SPACINGX
-2);
1954 DrawEdge(hdc
, &r1
, EDGE_ETCHED
, BF_RIGHT
);
1957 if (iItem
== infoPtr
->iSelected
)
1959 DrawEdge(hdc
, &r
, EDGE_SUNKEN
, BF_SOFT
|BF_RECT
);
1961 OffsetRect(&r
, 1, 1);
1963 else /* ! selected */
1965 if (!(lStyle
& TCS_FLATBUTTONS
))
1966 DrawEdge(hdc
, &r
, EDGE_RAISED
, BF_SOFT
|BF_RECT
);
1969 else /* !TCS_BUTTONS */
1971 /* We draw a rectangle of different sizes depending on the selection
1973 if (iItem
== infoPtr
->iSelected
) {
1975 GetClientRect (infoPtr
->hwnd
, &rect
);
1976 clRight
= rect
.right
;
1977 clBottom
= rect
.bottom
;
1984 * Erase the background. (Delay it but setup rectangle.)
1985 * This is necessary when drawing the selected item since it is larger
1986 * than the others, it might overlap with stuff already drawn by the
1991 /* Draw themed tabs - but only if they are at the top.
1992 * Windows draws even side or bottom tabs themed, with wacky results.
1993 * However, since in Wine apps may get themed that did not opt in via
1994 * a manifest avoid theming when we know the result will be wrong */
1995 if ((theme
= GetWindowTheme (infoPtr
->hwnd
))
1996 && ((lStyle
& (TCS_VERTICAL
| TCS_BOTTOM
)) == 0))
1998 static const int partIds
[8] = {
2001 TABP_TABITEMLEFTEDGE
,
2002 TABP_TABITEMRIGHTEDGE
,
2003 TABP_TABITEMBOTHEDGE
,
2006 TABP_TOPTABITEMLEFTEDGE
,
2007 TABP_TOPTABITEMRIGHTEDGE
,
2008 TABP_TOPTABITEMBOTHEDGE
,
2011 int stateId
= TIS_NORMAL
;
2013 /* selected and unselected tabs have different parts */
2014 if (iItem
== infoPtr
->iSelected
)
2016 /* The part also differs on the position of a tab on a line.
2017 * "Visually" determining the position works well enough. */
2018 if(selectedRect
.left
== 0)
2020 if(selectedRect
.right
== clRight
)
2023 if (iItem
== infoPtr
->iSelected
)
2024 stateId
= TIS_SELECTED
;
2025 else if (iItem
== infoPtr
->iHotTracked
)
2027 else if (iItem
== infoPtr
->uFocus
)
2028 stateId
= TIS_FOCUSED
;
2030 /* Adjust rectangle for bottommost row */
2031 if (TAB_GetItem(infoPtr
, iItem
)->rect
.top
== infoPtr
->uNumRows
-1)
2034 DrawThemeBackground (theme
, hdc
, partIds
[partIndex
], stateId
, &r
, NULL
);
2035 GetThemeBackgroundContentRect (theme
, hdc
, partIds
[partIndex
], stateId
, &r
, &r
);
2037 else if(lStyle
& TCS_VERTICAL
)
2039 /* These are for adjusting the drawing of a Selected tab */
2040 /* The initial values are for the normal case of non-Selected */
2041 int ZZ
= 1; /* Do not stretch if selected */
2042 if (iItem
== infoPtr
->iSelected
) {
2045 /* if leftmost draw the line longer */
2046 if(selectedRect
.top
== 0)
2047 fillRect
.top
+= CONTROL_BORDER_SIZEY
;
2048 /* if rightmost draw the line longer */
2049 if(selectedRect
.bottom
== clBottom
)
2050 fillRect
.bottom
-= CONTROL_BORDER_SIZEY
;
2053 if (lStyle
& TCS_BOTTOM
)
2055 /* Adjust both rectangles to match native */
2058 TRACE("<right> item=%d, fill=(%s), edge=(%s)\n",
2059 iItem
, wine_dbgstr_rect(&fillRect
), wine_dbgstr_rect(&r
));
2061 /* Clear interior */
2062 SetBkColor(hdc
, bkgnd
);
2063 ExtTextOutW(hdc
, 0, 0, 2, &fillRect
, NULL
, 0, 0);
2065 /* Draw rectangular edge around tab */
2066 DrawEdge(hdc
, &r
, EDGE_RAISED
, BF_SOFT
|BF_RIGHT
|BF_TOP
|BF_BOTTOM
);
2068 /* Now erase the top corner and draw diagonal edge */
2069 SetBkColor(hdc
, corner
);
2070 r1
.left
= r
.right
- ROUND_CORNER_SIZE
- 1;
2073 r1
.bottom
= r1
.top
+ ROUND_CORNER_SIZE
;
2074 ExtTextOutW(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2076 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_DIAGONAL_ENDTOPLEFT
);
2078 /* Now erase the bottom corner and draw diagonal edge */
2079 r1
.left
= r
.right
- ROUND_CORNER_SIZE
- 1;
2080 r1
.bottom
= r
.bottom
;
2082 r1
.top
= r1
.bottom
- ROUND_CORNER_SIZE
;
2083 ExtTextOutW(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2085 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_DIAGONAL_ENDBOTTOMLEFT
);
2087 if ((iItem
== infoPtr
->iSelected
) && (selectedRect
.top
== 0)) {
2091 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_TOP
);
2097 TRACE("<left> item=%d, fill=(%s), edge=(%s)\n",
2098 iItem
, wine_dbgstr_rect(&fillRect
), wine_dbgstr_rect(&r
));
2100 /* Clear interior */
2101 SetBkColor(hdc
, bkgnd
);
2102 ExtTextOutW(hdc
, 0, 0, 2, &fillRect
, NULL
, 0, 0);
2104 /* Draw rectangular edge around tab */
2105 DrawEdge(hdc
, &r
, EDGE_RAISED
, BF_SOFT
|BF_LEFT
|BF_TOP
|BF_BOTTOM
);
2107 /* Now erase the top corner and draw diagonal edge */
2108 SetBkColor(hdc
, corner
);
2111 r1
.right
= r1
.left
+ ROUND_CORNER_SIZE
+ 1;
2112 r1
.bottom
= r1
.top
+ ROUND_CORNER_SIZE
;
2113 ExtTextOutW(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2115 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_DIAGONAL_ENDTOPRIGHT
);
2117 /* Now erase the bottom corner and draw diagonal edge */
2119 r1
.bottom
= r
.bottom
;
2120 r1
.right
= r1
.left
+ ROUND_CORNER_SIZE
+ 1;
2121 r1
.top
= r1
.bottom
- ROUND_CORNER_SIZE
;
2122 ExtTextOutW(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2124 DrawEdge(hdc
, &r1
, EDGE_SUNKEN
, BF_DIAGONAL_ENDTOPLEFT
);
2127 else /* ! TCS_VERTICAL */
2129 /* These are for adjusting the drawing of a Selected tab */
2130 /* The initial values are for the normal case of non-Selected */
2131 if (iItem
== infoPtr
->iSelected
) {
2132 /* if leftmost draw the line longer */
2133 if(selectedRect
.left
== 0)
2134 fillRect
.left
+= CONTROL_BORDER_SIZEX
;
2135 /* if rightmost draw the line longer */
2136 if(selectedRect
.right
== clRight
)
2137 fillRect
.right
-= CONTROL_BORDER_SIZEX
;
2140 if (lStyle
& TCS_BOTTOM
)
2142 /* Adjust both rectangles for topmost row */
2143 if (TAB_GetItem(infoPtr
, iItem
)->rect
.top
== infoPtr
->uNumRows
-1)
2149 TRACE("<bottom> item=%d, fill=(%s), edge=(%s)\n",
2150 iItem
, wine_dbgstr_rect(&fillRect
), wine_dbgstr_rect(&r
));
2152 /* Clear interior */
2153 SetBkColor(hdc
, bkgnd
);
2154 ExtTextOutW(hdc
, 0, 0, 2, &fillRect
, NULL
, 0, 0);
2156 /* Draw rectangular edge around tab */
2157 DrawEdge(hdc
, &r
, EDGE_RAISED
, BF_SOFT
|BF_LEFT
|BF_BOTTOM
|BF_RIGHT
);
2159 /* Now erase the righthand corner and draw diagonal edge */
2160 SetBkColor(hdc
, corner
);
2161 r1
.left
= r
.right
- ROUND_CORNER_SIZE
;
2162 r1
.bottom
= r
.bottom
;
2164 r1
.top
= r1
.bottom
- ROUND_CORNER_SIZE
- 1;
2165 ExtTextOutW(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2167 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_DIAGONAL_ENDBOTTOMLEFT
);
2169 /* Now erase the lefthand corner and draw diagonal edge */
2171 r1
.bottom
= r
.bottom
;
2172 r1
.right
= r1
.left
+ ROUND_CORNER_SIZE
;
2173 r1
.top
= r1
.bottom
- ROUND_CORNER_SIZE
- 1;
2174 ExtTextOutW(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2176 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_DIAGONAL_ENDTOPLEFT
);
2178 if (iItem
== infoPtr
->iSelected
)
2182 if (selectedRect
.left
== 0)
2187 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_LEFT
);
2194 /* Adjust both rectangles for bottommost row */
2195 if (TAB_GetItem(infoPtr
, iItem
)->rect
.top
== infoPtr
->uNumRows
-1)
2197 fillRect
.bottom
+= 3;
2201 TRACE("<top> item=%d, fill=(%s), edge=(%s)\n",
2202 iItem
, wine_dbgstr_rect(&fillRect
), wine_dbgstr_rect(&r
));
2204 /* Clear interior */
2205 SetBkColor(hdc
, bkgnd
);
2206 ExtTextOutW(hdc
, 0, 0, 2, &fillRect
, NULL
, 0, 0);
2208 /* Draw rectangular edge around tab */
2209 DrawEdge(hdc
, &r
, EDGE_RAISED
, BF_SOFT
|BF_LEFT
|BF_TOP
|BF_RIGHT
);
2211 /* Now erase the righthand corner and draw diagonal edge */
2212 SetBkColor(hdc
, corner
);
2213 r1
.left
= r
.right
- ROUND_CORNER_SIZE
;
2216 r1
.bottom
= r1
.top
+ ROUND_CORNER_SIZE
+ 1;
2217 ExtTextOutW(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2219 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_DIAGONAL_ENDBOTTOMRIGHT
);
2221 /* Now erase the lefthand corner and draw diagonal edge */
2224 r1
.right
= r1
.left
+ ROUND_CORNER_SIZE
;
2225 r1
.bottom
= r1
.top
+ ROUND_CORNER_SIZE
+ 1;
2226 ExtTextOutW(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2228 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_DIAGONAL_ENDTOPRIGHT
);
2233 TAB_DumpItemInternal(infoPtr
, iItem
);
2235 /* This modifies r to be the text rectangle. */
2236 TAB_DrawItemInterior(infoPtr
, hdc
, iItem
, &r
);
2240 /******************************************************************************
2243 * This method is used to draw the raised border around the tab control
2246 static void TAB_DrawBorder(const TAB_INFO
*infoPtr
, HDC hdc
)
2249 DWORD lStyle
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
2250 HTHEME theme
= GetWindowTheme (infoPtr
->hwnd
);
2252 GetClientRect (infoPtr
->hwnd
, &rect
);
2255 * Adjust for the style
2258 if (infoPtr
->uNumItem
)
2260 if ((lStyle
& TCS_BOTTOM
) && !(lStyle
& TCS_VERTICAL
))
2261 rect
.bottom
-= infoPtr
->tabHeight
* infoPtr
->uNumRows
+ CONTROL_BORDER_SIZEX
;
2262 else if((lStyle
& TCS_BOTTOM
) && (lStyle
& TCS_VERTICAL
))
2263 rect
.right
-= infoPtr
->tabHeight
* infoPtr
->uNumRows
+ CONTROL_BORDER_SIZEX
;
2264 else if(lStyle
& TCS_VERTICAL
)
2265 rect
.left
+= infoPtr
->tabHeight
* infoPtr
->uNumRows
+ CONTROL_BORDER_SIZEX
;
2266 else /* not TCS_VERTICAL and not TCS_BOTTOM */
2267 rect
.top
+= infoPtr
->tabHeight
* infoPtr
->uNumRows
+ CONTROL_BORDER_SIZEX
;
2270 TRACE("border=(%s)\n", wine_dbgstr_rect(&rect
));
2273 DrawThemeBackground (theme
, hdc
, TABP_PANE
, 0, &rect
, NULL
);
2275 DrawEdge(hdc
, &rect
, EDGE_RAISED
, BF_SOFT
|BF_RECT
);
2278 /******************************************************************************
2281 * This method repaints the tab control..
2283 static void TAB_Refresh (TAB_INFO
*infoPtr
, HDC hdc
)
2288 if (!infoPtr
->DoRedraw
)
2291 hOldFont
= SelectObject (hdc
, infoPtr
->hFont
);
2293 if (GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
) & TCS_BUTTONS
)
2295 for (i
= 0; i
< infoPtr
->uNumItem
; i
++)
2296 TAB_DrawItem (infoPtr
, hdc
, i
);
2300 /* Draw all the non selected item first */
2301 for (i
= 0; i
< infoPtr
->uNumItem
; i
++)
2303 if (i
!= infoPtr
->iSelected
)
2304 TAB_DrawItem (infoPtr
, hdc
, i
);
2307 /* Now, draw the border, draw it before the selected item
2308 * since the selected item overwrites part of the border. */
2309 TAB_DrawBorder (infoPtr
, hdc
);
2311 /* Then, draw the selected item */
2312 TAB_DrawItem (infoPtr
, hdc
, infoPtr
->iSelected
);
2315 SelectObject (hdc
, hOldFont
);
2318 static inline DWORD
TAB_GetRowCount (const TAB_INFO
*infoPtr
)
2320 return infoPtr
->uNumRows
;
2323 static inline LRESULT
TAB_SetRedraw (TAB_INFO
*infoPtr
, BOOL doRedraw
)
2325 infoPtr
->DoRedraw
= doRedraw
;
2329 /******************************************************************************
2330 * TAB_EnsureSelectionVisible
2332 * This method will make sure that the current selection is completely
2333 * visible by scrolling until it is.
2335 static void TAB_EnsureSelectionVisible(
2338 INT iSelected
= infoPtr
->iSelected
;
2339 LONG lStyle
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
2340 INT iOrigLeftmostVisible
= infoPtr
->leftmostVisible
;
2342 /* set the items row to the bottommost row or topmost row depending on
2344 if ((infoPtr
->uNumRows
> 1) && !(lStyle
& TCS_BUTTONS
))
2346 TAB_ITEM
*selected
= TAB_GetItem(infoPtr
, iSelected
);
2350 if(lStyle
& TCS_VERTICAL
)
2351 newselected
= selected
->rect
.left
;
2353 newselected
= selected
->rect
.top
;
2355 /* the target row is always (number of rows - 1)
2356 as row 0 is furthest from the clientRect */
2357 iTargetRow
= infoPtr
->uNumRows
- 1;
2359 if (newselected
!= iTargetRow
)
2362 if(lStyle
& TCS_VERTICAL
)
2364 for (i
=0; i
< infoPtr
->uNumItem
; i
++)
2366 /* move everything in the row of the selected item to the iTargetRow */
2367 TAB_ITEM
*item
= TAB_GetItem(infoPtr
, i
);
2369 if (item
->rect
.left
== newselected
)
2370 item
->rect
.left
= iTargetRow
;
2373 if (item
->rect
.left
> newselected
)
2380 for (i
=0; i
< infoPtr
->uNumItem
; i
++)
2382 TAB_ITEM
*item
= TAB_GetItem(infoPtr
, i
);
2384 if (item
->rect
.top
== newselected
)
2385 item
->rect
.top
= iTargetRow
;
2388 if (item
->rect
.top
> newselected
)
2393 TAB_RecalcHotTrack(infoPtr
, NULL
, NULL
, NULL
);
2398 * Do the trivial cases first.
2400 if ( (!infoPtr
->needsScrolling
) ||
2401 (infoPtr
->hwndUpDown
==0) || (lStyle
& TCS_VERTICAL
))
2404 if (infoPtr
->leftmostVisible
>= iSelected
)
2406 infoPtr
->leftmostVisible
= iSelected
;
2410 TAB_ITEM
*selected
= TAB_GetItem(infoPtr
, iSelected
);
2415 /* Calculate the part of the client area that is visible */
2416 GetClientRect(infoPtr
->hwnd
, &r
);
2419 GetClientRect(infoPtr
->hwndUpDown
, &r
);
2422 if ((selected
->rect
.right
-
2423 selected
->rect
.left
) >= width
)
2425 /* Special case: width of selected item is greater than visible
2428 infoPtr
->leftmostVisible
= iSelected
;
2432 for (i
= infoPtr
->leftmostVisible
; i
< infoPtr
->uNumItem
; i
++)
2434 if ((selected
->rect
.right
- TAB_GetItem(infoPtr
, i
)->rect
.left
) < width
)
2437 infoPtr
->leftmostVisible
= i
;
2441 if (infoPtr
->leftmostVisible
!= iOrigLeftmostVisible
)
2442 TAB_RecalcHotTrack(infoPtr
, NULL
, NULL
, NULL
);
2444 SendMessageW(infoPtr
->hwndUpDown
, UDM_SETPOS
, 0,
2445 MAKELONG(infoPtr
->leftmostVisible
, 0));
2448 /******************************************************************************
2449 * TAB_InvalidateTabArea
2451 * This method will invalidate the portion of the control that contains the
2452 * tabs. It is called when the state of the control changes and needs
2455 static void TAB_InvalidateTabArea(const TAB_INFO
*infoPtr
)
2457 RECT clientRect
, rInvalidate
, rAdjClient
;
2458 DWORD lStyle
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
2459 INT lastRow
= infoPtr
->uNumRows
- 1;
2462 if (lastRow
< 0) return;
2464 GetClientRect(infoPtr
->hwnd
, &clientRect
);
2465 rInvalidate
= clientRect
;
2466 rAdjClient
= clientRect
;
2468 TAB_AdjustRect(infoPtr
, 0, &rAdjClient
);
2470 TAB_InternalGetItemRect(infoPtr
, infoPtr
->uNumItem
-1 , &rect
, NULL
);
2471 if ((lStyle
& TCS_BOTTOM
) && (lStyle
& TCS_VERTICAL
))
2473 rInvalidate
.left
= rAdjClient
.right
;
2474 if (infoPtr
->uNumRows
== 1)
2475 rInvalidate
.bottom
= clientRect
.top
+ rect
.bottom
+ 2 * SELECTED_TAB_OFFSET
;
2477 else if(lStyle
& TCS_VERTICAL
)
2479 rInvalidate
.right
= rAdjClient
.left
;
2480 if (infoPtr
->uNumRows
== 1)
2481 rInvalidate
.bottom
= clientRect
.top
+ rect
.bottom
+ 2 * SELECTED_TAB_OFFSET
;
2483 else if (lStyle
& TCS_BOTTOM
)
2485 rInvalidate
.top
= rAdjClient
.bottom
;
2486 if (infoPtr
->uNumRows
== 1)
2487 rInvalidate
.right
= clientRect
.left
+ rect
.right
+ 2 * SELECTED_TAB_OFFSET
;
2491 rInvalidate
.bottom
= rAdjClient
.top
;
2492 if (infoPtr
->uNumRows
== 1)
2493 rInvalidate
.right
= clientRect
.left
+ rect
.right
+ 2 * SELECTED_TAB_OFFSET
;
2496 /* Punch out the updown control */
2497 if (infoPtr
->needsScrolling
&& (rInvalidate
.right
> 0)) {
2499 GetClientRect(infoPtr
->hwndUpDown
, &r
);
2500 if (rInvalidate
.right
> clientRect
.right
- r
.left
)
2501 rInvalidate
.right
= rInvalidate
.right
- (r
.right
- r
.left
);
2503 rInvalidate
.right
= clientRect
.right
- r
.left
;
2506 TRACE("invalidate (%s)\n", wine_dbgstr_rect(&rInvalidate
));
2508 InvalidateRect(infoPtr
->hwnd
, &rInvalidate
, TRUE
);
2511 static inline LRESULT
TAB_Paint (TAB_INFO
*infoPtr
, HDC hdcPaint
)
2520 hdc
= BeginPaint (infoPtr
->hwnd
, &ps
);
2521 TRACE("erase %d, rect=(%s)\n", ps
.fErase
, wine_dbgstr_rect(&ps
.rcPaint
));
2524 TAB_Refresh (infoPtr
, hdc
);
2527 EndPaint (infoPtr
->hwnd
, &ps
);
2533 TAB_InsertItemT (TAB_INFO
*infoPtr
, WPARAM wParam
, LPARAM lParam
, BOOL bUnicode
)
2540 GetClientRect (infoPtr
->hwnd
, &rect
);
2541 TRACE("Rect: %p %s\n", infoPtr
->hwnd
, wine_dbgstr_rect(&rect
));
2543 pti
= (TCITEMW
*)lParam
;
2544 iItem
= (INT
)wParam
;
2546 if (iItem
< 0) return -1;
2547 if (iItem
> infoPtr
->uNumItem
)
2548 iItem
= infoPtr
->uNumItem
;
2550 TAB_DumpItemExternalT(pti
, iItem
, bUnicode
);
2553 if (infoPtr
->uNumItem
== 0) {
2554 infoPtr
->items
= Alloc (TAB_ITEM_SIZE(infoPtr
));
2555 infoPtr
->uNumItem
++;
2556 infoPtr
->iSelected
= 0;
2559 LPBYTE oldItems
= (LPBYTE
)infoPtr
->items
;
2561 infoPtr
->uNumItem
++;
2562 infoPtr
->items
= Alloc (TAB_ITEM_SIZE(infoPtr
) * infoPtr
->uNumItem
);
2564 /* pre insert copy */
2566 memcpy (infoPtr
->items
, oldItems
,
2567 iItem
* TAB_ITEM_SIZE(infoPtr
));
2570 /* post insert copy */
2571 if (iItem
< infoPtr
->uNumItem
- 1) {
2572 memcpy (TAB_GetItem(infoPtr
, iItem
+ 1),
2573 oldItems
+ iItem
* TAB_ITEM_SIZE(infoPtr
),
2574 (infoPtr
->uNumItem
- iItem
- 1) * TAB_ITEM_SIZE(infoPtr
));
2578 if (iItem
<= infoPtr
->iSelected
)
2579 infoPtr
->iSelected
++;
2584 item
= TAB_GetItem(infoPtr
, iItem
);
2586 item
->pszText
= NULL
;
2588 if (pti
->mask
& TCIF_TEXT
)
2591 Str_SetPtrW (&item
->pszText
, pti
->pszText
);
2593 Str_SetPtrAtoW (&item
->pszText
, (LPSTR
)pti
->pszText
);
2596 if (pti
->mask
& TCIF_IMAGE
)
2597 item
->iImage
= pti
->iImage
;
2601 if (pti
->mask
& TCIF_PARAM
)
2602 memcpy(item
->extra
, &pti
->lParam
, infoPtr
->cbInfo
);
2604 memset(item
->extra
, 0, infoPtr
->cbInfo
);
2606 TAB_SetItemBounds(infoPtr
);
2607 if (infoPtr
->uNumItem
> 1)
2608 TAB_InvalidateTabArea(infoPtr
);
2610 InvalidateRect(infoPtr
->hwnd
, NULL
, TRUE
);
2612 TRACE("[%p]: added item %d %s\n",
2613 infoPtr
->hwnd
, iItem
, debugstr_w(item
->pszText
));
2615 /* If we haven't set the current focus yet, set it now. */
2616 if (infoPtr
->uFocus
== -1)
2617 TAB_SetCurFocus(infoPtr
, iItem
);
2623 TAB_SetItemSize (TAB_INFO
*infoPtr
, LPARAM lParam
)
2625 LONG lStyle
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
2627 BOOL bNeedPaint
= FALSE
;
2629 lResult
= MAKELONG(infoPtr
->tabWidth
, infoPtr
->tabHeight
);
2631 /* UNDOCUMENTED: If requested Width or Height is 0 this means that program wants to use auto size. */
2632 if (lStyle
& TCS_FIXEDWIDTH
&& (infoPtr
->tabWidth
!= (INT
)LOWORD(lParam
)))
2634 infoPtr
->tabWidth
= (INT
)LOWORD(lParam
);
2638 if (infoPtr
->tabHeight
!= (INT
)HIWORD(lParam
))
2640 if ((infoPtr
->fHeightSet
= ((INT
)HIWORD(lParam
) != 0)))
2641 infoPtr
->tabHeight
= (INT
)HIWORD(lParam
);
2645 TRACE("was h=%d,w=%d, now h=%d,w=%d\n",
2646 HIWORD(lResult
), LOWORD(lResult
),
2647 infoPtr
->tabHeight
, infoPtr
->tabWidth
);
2651 TAB_SetItemBounds(infoPtr
);
2652 RedrawWindow(infoPtr
->hwnd
, NULL
, NULL
, RDW_ERASE
| RDW_INVALIDATE
| RDW_UPDATENOW
);
2658 static inline LRESULT
TAB_SetMinTabWidth (TAB_INFO
*infoPtr
, INT cx
)
2662 TRACE("(%p,%d)\n", infoPtr
, cx
);
2664 oldcx
= infoPtr
->tabMinWidth
;
2665 infoPtr
->tabMinWidth
= cx
;
2666 TAB_SetItemBounds(infoPtr
);
2670 static inline LRESULT
2671 TAB_HighlightItem (TAB_INFO
*infoPtr
, INT iItem
, BOOL fHighlight
)
2675 TRACE("(%p,%d,%s)\n", infoPtr
, iItem
, fHighlight
? "true" : "false");
2677 if (!infoPtr
|| iItem
< 0 || iItem
>= infoPtr
->uNumItem
)
2680 lpState
= &TAB_GetItem(infoPtr
, iItem
)->dwState
;
2683 *lpState
|= TCIS_HIGHLIGHTED
;
2685 *lpState
&= ~TCIS_HIGHLIGHTED
;
2691 TAB_SetItemT (TAB_INFO
*infoPtr
, INT iItem
, LPTCITEMW tabItem
, BOOL bUnicode
)
2695 TRACE("(%p,%d,%p,%s)\n", infoPtr
, iItem
, tabItem
, bUnicode
? "true" : "false");
2697 if (iItem
< 0 || iItem
>= infoPtr
->uNumItem
)
2700 TAB_DumpItemExternalT(tabItem
, iItem
, bUnicode
);
2702 wineItem
= TAB_GetItem(infoPtr
, iItem
);
2704 if (tabItem
->mask
& TCIF_IMAGE
)
2705 wineItem
->iImage
= tabItem
->iImage
;
2707 if (tabItem
->mask
& TCIF_PARAM
)
2708 memcpy(wineItem
->extra
, &tabItem
->lParam
, infoPtr
->cbInfo
);
2710 if (tabItem
->mask
& TCIF_RTLREADING
)
2711 FIXME("TCIF_RTLREADING\n");
2713 if (tabItem
->mask
& TCIF_STATE
)
2714 wineItem
->dwState
= tabItem
->dwState
;
2716 if (tabItem
->mask
& TCIF_TEXT
)
2718 Free(wineItem
->pszText
);
2719 wineItem
->pszText
= NULL
;
2721 Str_SetPtrW(&wineItem
->pszText
, tabItem
->pszText
);
2723 Str_SetPtrAtoW(&wineItem
->pszText
, (LPSTR
)tabItem
->pszText
);
2726 /* Update and repaint tabs */
2727 TAB_SetItemBounds(infoPtr
);
2728 TAB_InvalidateTabArea(infoPtr
);
2733 static inline LRESULT
TAB_GetItemCount (const TAB_INFO
*infoPtr
)
2735 return infoPtr
->uNumItem
;
2740 TAB_GetItemT (TAB_INFO
*infoPtr
, INT iItem
, LPTCITEMW tabItem
, BOOL bUnicode
)
2744 TRACE("(%p,%d,%p,%s)\n", infoPtr
, iItem
, tabItem
, bUnicode
? "true" : "false");
2746 if (iItem
< 0 || iItem
>= infoPtr
->uNumItem
)
2749 wineItem
= TAB_GetItem(infoPtr
, iItem
);
2751 if (tabItem
->mask
& TCIF_IMAGE
)
2752 tabItem
->iImage
= wineItem
->iImage
;
2754 if (tabItem
->mask
& TCIF_PARAM
)
2755 memcpy(&tabItem
->lParam
, wineItem
->extra
, infoPtr
->cbInfo
);
2757 if (tabItem
->mask
& TCIF_RTLREADING
)
2758 FIXME("TCIF_RTLREADING\n");
2760 if (tabItem
->mask
& TCIF_STATE
)
2761 tabItem
->dwState
= wineItem
->dwState
;
2763 if (tabItem
->mask
& TCIF_TEXT
)
2766 Str_GetPtrW (wineItem
->pszText
, tabItem
->pszText
, tabItem
->cchTextMax
);
2768 Str_GetPtrWtoA (wineItem
->pszText
, (LPSTR
)tabItem
->pszText
, tabItem
->cchTextMax
);
2771 TAB_DumpItemExternalT(tabItem
, iItem
, bUnicode
);
2777 static LRESULT
TAB_DeleteItem (TAB_INFO
*infoPtr
, INT iItem
)
2779 BOOL bResult
= FALSE
;
2781 TRACE("(%p, %d)\n", infoPtr
, iItem
);
2783 if ((iItem
>= 0) && (iItem
< infoPtr
->uNumItem
))
2785 TAB_ITEM
*item
= TAB_GetItem(infoPtr
, iItem
);
2786 LPBYTE oldItems
= (LPBYTE
)infoPtr
->items
;
2788 TAB_InvalidateTabArea(infoPtr
);
2789 Free(item
->pszText
);
2790 infoPtr
->uNumItem
--;
2792 if (!infoPtr
->uNumItem
)
2794 infoPtr
->items
= NULL
;
2795 if (infoPtr
->iHotTracked
>= 0)
2797 KillTimer(infoPtr
->hwnd
, TAB_HOTTRACK_TIMER
);
2798 infoPtr
->iHotTracked
= -1;
2803 infoPtr
->items
= Alloc(TAB_ITEM_SIZE(infoPtr
) * infoPtr
->uNumItem
);
2806 memcpy(infoPtr
->items
, oldItems
, iItem
* TAB_ITEM_SIZE(infoPtr
));
2808 if (iItem
< infoPtr
->uNumItem
)
2809 memcpy(TAB_GetItem(infoPtr
, iItem
),
2810 oldItems
+ (iItem
+ 1) * TAB_ITEM_SIZE(infoPtr
),
2811 (infoPtr
->uNumItem
- iItem
) * TAB_ITEM_SIZE(infoPtr
));
2813 if (iItem
<= infoPtr
->iHotTracked
)
2815 /* When tabs move left/up, the hot track item may change */
2816 FIXME("Recalc hot track\n");
2821 /* Readjust the selected index */
2822 if ((iItem
== infoPtr
->iSelected
) && (iItem
> 0))
2823 infoPtr
->iSelected
--;
2825 if (iItem
< infoPtr
->iSelected
)
2826 infoPtr
->iSelected
--;
2828 if (infoPtr
->uNumItem
== 0)
2829 infoPtr
->iSelected
= -1;
2831 /* Reposition and repaint tabs */
2832 TAB_SetItemBounds(infoPtr
);
2840 static inline LRESULT
TAB_DeleteAllItems (TAB_INFO
*infoPtr
)
2842 TRACE("(%p)\n", infoPtr
);
2843 while (infoPtr
->uNumItem
)
2844 TAB_DeleteItem (infoPtr
, 0);
2849 static inline LRESULT
TAB_GetFont (const TAB_INFO
*infoPtr
)
2851 TRACE("(%p) returning %p\n", infoPtr
, infoPtr
->hFont
);
2852 return (LRESULT
)infoPtr
->hFont
;
2855 static inline LRESULT
TAB_SetFont (TAB_INFO
*infoPtr
, HFONT hNewFont
)
2857 TRACE("(%p,%p)\n", infoPtr
, hNewFont
);
2859 infoPtr
->hFont
= hNewFont
;
2861 TAB_SetItemBounds(infoPtr
);
2863 TAB_InvalidateTabArea(infoPtr
);
2869 static inline LRESULT
TAB_GetImageList (const TAB_INFO
*infoPtr
)
2872 return (LRESULT
)infoPtr
->himl
;
2875 static inline LRESULT
TAB_SetImageList (TAB_INFO
*infoPtr
, HIMAGELIST himlNew
)
2877 HIMAGELIST himlPrev
= infoPtr
->himl
;
2879 infoPtr
->himl
= himlNew
;
2880 TAB_SetItemBounds(infoPtr
);
2881 InvalidateRect(infoPtr
->hwnd
, NULL
, TRUE
);
2882 return (LRESULT
)himlPrev
;
2885 static inline LRESULT
TAB_GetUnicodeFormat (const TAB_INFO
*infoPtr
)
2887 return infoPtr
->bUnicode
;
2890 static inline LRESULT
TAB_SetUnicodeFormat (TAB_INFO
*infoPtr
, BOOL bUnicode
)
2892 BOOL bTemp
= infoPtr
->bUnicode
;
2894 infoPtr
->bUnicode
= bUnicode
;
2899 static inline LRESULT
TAB_Size (TAB_INFO
*infoPtr
)
2901 /* I'm not really sure what the following code was meant to do.
2902 This is what it is doing:
2903 When WM_SIZE is sent with SIZE_RESTORED, the control
2904 gets positioned in the top left corner.
2908 UINT uPosFlags,cx,cy;
2912 parent = GetParent (hwnd);
2913 GetClientRect(parent, &parent_rect);
2916 if (GetWindowLongW(hwnd, GWL_STYLE) & CCS_NORESIZE)
2917 uPosFlags |= (SWP_NOSIZE | SWP_NOMOVE);
2919 SetWindowPos (hwnd, 0, parent_rect.left, parent_rect.top,
2920 cx, cy, uPosFlags | SWP_NOZORDER);
2922 FIXME("WM_SIZE flag %x %lx not handled\n", wParam, lParam);
2925 /* Recompute the size/position of the tabs. */
2926 TAB_SetItemBounds (infoPtr
);
2928 /* Force a repaint of the control. */
2929 InvalidateRect(infoPtr
->hwnd
, NULL
, TRUE
);
2935 static LRESULT
TAB_Create (HWND hwnd
, WPARAM wParam
, LPARAM lParam
)
2938 TEXTMETRICW fontMetrics
;
2943 infoPtr
= (TAB_INFO
*)Alloc (sizeof(TAB_INFO
));
2945 SetWindowLongPtrW(hwnd
, 0, (DWORD_PTR
)infoPtr
);
2947 infoPtr
->hwnd
= hwnd
;
2948 infoPtr
->hwndNotify
= ((LPCREATESTRUCTW
)lParam
)->hwndParent
;
2949 infoPtr
->uNumItem
= 0;
2950 infoPtr
->uNumRows
= 0;
2951 infoPtr
->uHItemPadding
= 6;
2952 infoPtr
->uVItemPadding
= 3;
2953 infoPtr
->uHItemPadding_s
= 6;
2954 infoPtr
->uVItemPadding_s
= 3;
2957 infoPtr
->hcurArrow
= LoadCursorW (0, (LPWSTR
)IDC_ARROW
);
2958 infoPtr
->iSelected
= -1;
2959 infoPtr
->iHotTracked
= -1;
2960 infoPtr
->uFocus
= -1;
2961 infoPtr
->hwndToolTip
= 0;
2962 infoPtr
->DoRedraw
= TRUE
;
2963 infoPtr
->needsScrolling
= FALSE
;
2964 infoPtr
->hwndUpDown
= 0;
2965 infoPtr
->leftmostVisible
= 0;
2966 infoPtr
->fHeightSet
= FALSE
;
2967 infoPtr
->bUnicode
= IsWindowUnicode (hwnd
);
2968 infoPtr
->cbInfo
= sizeof(LPARAM
);
2970 TRACE("Created tab control, hwnd [%p]\n", hwnd
);
2972 /* The tab control always has the WS_CLIPSIBLINGS style. Even
2973 if you don't specify it in CreateWindow. This is necessary in
2974 order for paint to work correctly. This follows windows behaviour. */
2975 dwStyle
= GetWindowLongW(hwnd
, GWL_STYLE
);
2976 SetWindowLongW(hwnd
, GWL_STYLE
, dwStyle
|WS_CLIPSIBLINGS
);
2978 if (dwStyle
& TCS_TOOLTIPS
) {
2979 /* Create tooltip control */
2980 infoPtr
->hwndToolTip
=
2981 CreateWindowExW (0, TOOLTIPS_CLASSW
, NULL
, WS_POPUP
,
2982 CW_USEDEFAULT
, CW_USEDEFAULT
,
2983 CW_USEDEFAULT
, CW_USEDEFAULT
,
2986 /* Send NM_TOOLTIPSCREATED notification */
2987 if (infoPtr
->hwndToolTip
) {
2988 NMTOOLTIPSCREATED nmttc
;
2990 nmttc
.hdr
.hwndFrom
= hwnd
;
2991 nmttc
.hdr
.idFrom
= GetWindowLongPtrW(hwnd
, GWLP_ID
);
2992 nmttc
.hdr
.code
= NM_TOOLTIPSCREATED
;
2993 nmttc
.hwndToolTips
= infoPtr
->hwndToolTip
;
2995 SendMessageW (infoPtr
->hwndNotify
, WM_NOTIFY
,
2996 (WPARAM
)GetWindowLongPtrW(hwnd
, GWLP_ID
), (LPARAM
)&nmttc
);
3000 OpenThemeData (infoPtr
->hwnd
, themeClass
);
3003 * We need to get text information so we need a DC and we need to select
3007 hOldFont
= SelectObject (hdc
, GetStockObject (SYSTEM_FONT
));
3009 /* Use the system font to determine the initial height of a tab. */
3010 GetTextMetricsW(hdc
, &fontMetrics
);
3013 * Make sure there is enough space for the letters + growing the
3014 * selected item + extra space for the selected item.
3016 infoPtr
->tabHeight
= fontMetrics
.tmHeight
+ SELECTED_TAB_OFFSET
+
3017 ((dwStyle
& TCS_BUTTONS
) ? 2 : 1) *
3018 infoPtr
->uVItemPadding
;
3020 /* Initialize the width of a tab. */
3021 if (dwStyle
& TCS_FIXEDWIDTH
)
3022 infoPtr
->tabWidth
= DEFAULT_TAB_WIDTH_FIXED
;
3024 infoPtr
->tabMinWidth
= -1;
3026 TRACE("tabH=%d, tabW=%d\n", infoPtr
->tabHeight
, infoPtr
->tabWidth
);
3028 SelectObject (hdc
, hOldFont
);
3029 ReleaseDC(hwnd
, hdc
);
3035 TAB_Destroy (TAB_INFO
*infoPtr
)
3042 SetWindowLongPtrW(infoPtr
->hwnd
, 0, 0);
3044 if (infoPtr
->items
) {
3045 for (iItem
= 0; iItem
< infoPtr
->uNumItem
; iItem
++) {
3046 Free (TAB_GetItem(infoPtr
, iItem
)->pszText
);
3048 Free (infoPtr
->items
);
3051 if (infoPtr
->hwndToolTip
)
3052 DestroyWindow (infoPtr
->hwndToolTip
);
3054 if (infoPtr
->hwndUpDown
)
3055 DestroyWindow(infoPtr
->hwndUpDown
);
3057 if (infoPtr
->iHotTracked
>= 0)
3058 KillTimer(infoPtr
->hwnd
, TAB_HOTTRACK_TIMER
);
3060 CloseThemeData (GetWindowTheme (infoPtr
->hwnd
));
3066 /* update theme after a WM_THEMECHANGED message */
3067 static LRESULT
theme_changed(const TAB_INFO
*infoPtr
)
3069 HTHEME theme
= GetWindowTheme (infoPtr
->hwnd
);
3070 CloseThemeData (theme
);
3071 OpenThemeData (infoPtr
->hwnd
, themeClass
);
3075 static LRESULT
TAB_NCCalcSize(HWND hwnd
, WPARAM wParam
, LPARAM lParam
)
3079 return WVR_ALIGNTOP
;
3082 static inline LRESULT
3083 TAB_SetItemExtra (TAB_INFO
*infoPtr
, INT cbInfo
)
3085 if (!infoPtr
|| cbInfo
<= 0)
3088 if (infoPtr
->uNumItem
)
3090 /* FIXME: MSDN says this is not allowed, but this hasn't been verified */
3094 infoPtr
->cbInfo
= cbInfo
;
3098 static LRESULT WINAPI
3099 TAB_WindowProc (HWND hwnd
, UINT uMsg
, WPARAM wParam
, LPARAM lParam
)
3101 TAB_INFO
*infoPtr
= TAB_GetInfoPtr(hwnd
);
3103 TRACE("hwnd=%p msg=%x wParam=%lx lParam=%lx\n", hwnd
, uMsg
, wParam
, lParam
);
3104 if (!infoPtr
&& (uMsg
!= WM_CREATE
))
3105 return DefWindowProcW (hwnd
, uMsg
, wParam
, lParam
);
3109 case TCM_GETIMAGELIST
:
3110 return TAB_GetImageList (infoPtr
);
3112 case TCM_SETIMAGELIST
:
3113 return TAB_SetImageList (infoPtr
, (HIMAGELIST
)lParam
);
3115 case TCM_GETITEMCOUNT
:
3116 return TAB_GetItemCount (infoPtr
);
3120 return TAB_GetItemT (infoPtr
, (INT
)wParam
, (LPTCITEMW
)lParam
, uMsg
== TCM_GETITEMW
);
3124 return TAB_SetItemT (infoPtr
, (INT
)wParam
, (LPTCITEMW
)lParam
, uMsg
== TCM_SETITEMW
);
3126 case TCM_DELETEITEM
:
3127 return TAB_DeleteItem (infoPtr
, (INT
)wParam
);
3129 case TCM_DELETEALLITEMS
:
3130 return TAB_DeleteAllItems (infoPtr
);
3132 case TCM_GETITEMRECT
:
3133 return TAB_GetItemRect (infoPtr
, wParam
, lParam
);
3136 return TAB_GetCurSel (infoPtr
);
3139 return TAB_HitTest (infoPtr
, (LPTCHITTESTINFO
)lParam
);
3142 return TAB_SetCurSel (infoPtr
, (INT
)wParam
);
3144 case TCM_INSERTITEMA
:
3145 case TCM_INSERTITEMW
:
3146 return TAB_InsertItemT (infoPtr
, wParam
, lParam
, uMsg
== TCM_INSERTITEMW
);
3148 case TCM_SETITEMEXTRA
:
3149 return TAB_SetItemExtra (infoPtr
, (int)wParam
);
3151 case TCM_ADJUSTRECT
:
3152 return TAB_AdjustRect (infoPtr
, (BOOL
)wParam
, (LPRECT
)lParam
);
3154 case TCM_SETITEMSIZE
:
3155 return TAB_SetItemSize (infoPtr
, lParam
);
3157 case TCM_REMOVEIMAGE
:
3158 FIXME("Unimplemented msg TCM_REMOVEIMAGE\n");
3161 case TCM_SETPADDING
:
3162 return TAB_SetPadding (infoPtr
, lParam
);
3164 case TCM_GETROWCOUNT
:
3165 return TAB_GetRowCount(infoPtr
);
3167 case TCM_GETUNICODEFORMAT
:
3168 return TAB_GetUnicodeFormat (infoPtr
);
3170 case TCM_SETUNICODEFORMAT
:
3171 return TAB_SetUnicodeFormat (infoPtr
, (BOOL
)wParam
);
3173 case TCM_HIGHLIGHTITEM
:
3174 return TAB_HighlightItem (infoPtr
, (INT
)wParam
, (BOOL
)LOWORD(lParam
));
3176 case TCM_GETTOOLTIPS
:
3177 return TAB_GetToolTips (infoPtr
);
3179 case TCM_SETTOOLTIPS
:
3180 return TAB_SetToolTips (infoPtr
, (HWND
)wParam
);
3182 case TCM_GETCURFOCUS
:
3183 return TAB_GetCurFocus (infoPtr
);
3185 case TCM_SETCURFOCUS
:
3186 return TAB_SetCurFocus (infoPtr
, (INT
)wParam
);
3188 case TCM_SETMINTABWIDTH
:
3189 return TAB_SetMinTabWidth(infoPtr
, (INT
)lParam
);
3191 case TCM_DESELECTALL
:
3192 FIXME("Unimplemented msg TCM_DESELECTALL\n");
3195 case TCM_GETEXTENDEDSTYLE
:
3196 FIXME("Unimplemented msg TCM_GETEXTENDEDSTYLE\n");
3199 case TCM_SETEXTENDEDSTYLE
:
3200 FIXME("Unimplemented msg TCM_SETEXTENDEDSTYLE\n");
3204 return TAB_GetFont (infoPtr
);
3207 return TAB_SetFont (infoPtr
, (HFONT
)wParam
);
3210 return TAB_Create (hwnd
, wParam
, lParam
);
3213 return TAB_Destroy (infoPtr
);
3216 return DLGC_WANTARROWS
| DLGC_WANTCHARS
;
3218 case WM_LBUTTONDOWN
:
3219 return TAB_LButtonDown (infoPtr
, wParam
, lParam
);
3222 return TAB_LButtonUp (infoPtr
);
3225 return SendMessageW(infoPtr
->hwndNotify
, WM_NOTIFY
, wParam
, lParam
);
3227 case WM_RBUTTONDOWN
:
3228 return TAB_RButtonDown (infoPtr
);
3231 return TAB_MouseMove (infoPtr
, wParam
, lParam
);
3233 case WM_PRINTCLIENT
:
3235 return TAB_Paint (infoPtr
, (HDC
)wParam
);
3238 return TAB_Size (infoPtr
);
3241 return TAB_SetRedraw (infoPtr
, (BOOL
)wParam
);
3244 return TAB_OnHScroll(infoPtr
, (int)LOWORD(wParam
), (int)HIWORD(wParam
), (HWND
)lParam
);
3246 case WM_STYLECHANGED
:
3247 TAB_SetItemBounds (infoPtr
);
3248 InvalidateRect(hwnd
, NULL
, TRUE
);
3251 case WM_SYSCOLORCHANGE
:
3252 COMCTL32_RefreshSysColors();
3255 case WM_THEMECHANGED
:
3256 return theme_changed (infoPtr
);
3260 TAB_FocusChanging(infoPtr
);
3261 break; /* Don't disturb normal focus behavior */
3264 return TAB_KeyUp(infoPtr
, wParam
);
3266 return TAB_NCHitTest(infoPtr
, lParam
);
3269 return TAB_NCCalcSize(hwnd
, wParam
, lParam
);
3272 if (uMsg
>= WM_USER
&& uMsg
< WM_APP
)
3273 WARN("unknown msg %04x wp=%08lx lp=%08lx\n",
3274 uMsg
, wParam
, lParam
);
3277 return DefWindowProcW(hwnd
, uMsg
, wParam
, lParam
);
3286 ZeroMemory (&wndClass
, sizeof(WNDCLASSW
));
3287 wndClass
.style
= CS_GLOBALCLASS
| CS_DBLCLKS
| CS_HREDRAW
| CS_VREDRAW
;
3288 wndClass
.lpfnWndProc
= TAB_WindowProc
;
3289 wndClass
.cbClsExtra
= 0;
3290 wndClass
.cbWndExtra
= sizeof(TAB_INFO
*);
3291 wndClass
.hCursor
= LoadCursorW (0, (LPWSTR
)IDC_ARROW
);
3292 wndClass
.hbrBackground
= (HBRUSH
)(COLOR_BTNFACE
+1);
3293 wndClass
.lpszClassName
= WC_TABCONTROLW
;
3295 RegisterClassW (&wndClass
);
3300 TAB_Unregister (void)
3302 UnregisterClassW (WC_TABCONTROLW
, NULL
);