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 #define TAB_ITEM_SIZE(infoPtr) (FIELD_OFFSET(TAB_ITEM, extra[(infoPtr)->cbInfo]))
92 HWND hwnd
; /* Tab control window */
93 HWND hwndNotify
; /* notification window (parent) */
94 UINT uNumItem
; /* number of tab items */
95 UINT uNumRows
; /* number of tab rows */
96 INT tabHeight
; /* height of the tab row */
97 INT tabWidth
; /* width of tabs */
98 INT tabMinWidth
; /* minimum width of items */
99 USHORT uHItemPadding
; /* amount of horizontal padding, in pixels */
100 USHORT uVItemPadding
; /* amount of vertical padding, in pixels */
101 USHORT uHItemPadding_s
; /* Set amount of horizontal padding, in pixels */
102 USHORT uVItemPadding_s
; /* Set amount of vertical padding, in pixels */
103 HFONT hFont
; /* handle to the current font */
104 HCURSOR hcurArrow
; /* handle to the current cursor */
105 HIMAGELIST himl
; /* handle to an image list (may be 0) */
106 HWND hwndToolTip
; /* handle to tab's tooltip */
107 INT leftmostVisible
; /* Used for scrolling, this member contains
108 * the index of the first visible item */
109 INT iSelected
; /* the currently selected item */
110 INT iHotTracked
; /* the highlighted item under the mouse */
111 INT uFocus
; /* item which has the focus */
112 TAB_ITEM
* items
; /* pointer to an array of TAB_ITEM's */
113 BOOL DoRedraw
; /* flag for redrawing when tab contents is changed*/
114 BOOL needsScrolling
; /* TRUE if the size of the tabs is greater than
115 * the size of the control */
116 BOOL fHeightSet
; /* was the height of the tabs explicitly set? */
117 BOOL bUnicode
; /* Unicode control? */
118 HWND hwndUpDown
; /* Updown control used for scrolling */
119 INT cbInfo
; /* Number of bytes of caller supplied info per tab */
121 DWORD exStyle
; /* Extended style used, currently:
122 TCS_EX_FLATSEPARATORS, TCS_EX_REGISTERDROP */
125 /******************************************************************************
126 * Positioning constants
128 #define SELECTED_TAB_OFFSET 2
129 #define ROUND_CORNER_SIZE 2
130 #define DISPLAY_AREA_PADDINGX 2
131 #define DISPLAY_AREA_PADDINGY 2
132 #define CONTROL_BORDER_SIZEX 2
133 #define CONTROL_BORDER_SIZEY 2
134 #define BUTTON_SPACINGX 3
135 #define BUTTON_SPACINGY 3
136 #define FLAT_BTN_SPACINGX 8
137 #define DEFAULT_MIN_TAB_WIDTH 54
138 #define DEFAULT_PADDING_X 6
139 #define EXTRA_ICON_PADDING 3
141 #define TAB_GetInfoPtr(hwnd) ((TAB_INFO *)GetWindowLongPtrW(hwnd,0))
142 /* Since items are variable sized, cannot directly access them */
143 #define TAB_GetItem(info,i) \
144 ((TAB_ITEM*)((LPBYTE)info->items + (i) * TAB_ITEM_SIZE(info)))
146 #define GET_DEFAULT_MIN_TAB_WIDTH(infoPtr) (DEFAULT_MIN_TAB_WIDTH - (DEFAULT_PADDING_X - (infoPtr)->uHItemPadding) * 2)
148 /******************************************************************************
149 * Hot-tracking timer constants
151 #define TAB_HOTTRACK_TIMER 1
152 #define TAB_HOTTRACK_TIMER_INTERVAL 100 /* milliseconds */
154 static const WCHAR themeClass
[] = { 'T','a','b',0 };
156 /******************************************************************************
159 static void TAB_InvalidateTabArea(const TAB_INFO
*);
160 static void TAB_EnsureSelectionVisible(TAB_INFO
*);
161 static void TAB_DrawItemInterior(const TAB_INFO
*, HDC
, INT
, RECT
*);
162 static LRESULT
TAB_DeselectAll(TAB_INFO
*, BOOL
);
165 TAB_SendSimpleNotify (const TAB_INFO
*infoPtr
, UINT code
)
169 nmhdr
.hwndFrom
= infoPtr
->hwnd
;
170 nmhdr
.idFrom
= GetWindowLongPtrW(infoPtr
->hwnd
, GWLP_ID
);
173 return (BOOL
) SendMessageW (infoPtr
->hwndNotify
, WM_NOTIFY
,
174 nmhdr
.idFrom
, (LPARAM
) &nmhdr
);
178 TAB_RelayEvent (HWND hwndTip
, HWND hwndMsg
, UINT uMsg
,
179 WPARAM wParam
, LPARAM lParam
)
187 msg
.time
= GetMessageTime ();
188 msg
.pt
.x
= (short)LOWORD(GetMessagePos ());
189 msg
.pt
.y
= (short)HIWORD(GetMessagePos ());
191 SendMessageW (hwndTip
, TTM_RELAYEVENT
, 0, (LPARAM
)&msg
);
195 TAB_DumpItemExternalT(const TCITEMW
*pti
, UINT iItem
, BOOL isW
)
198 TRACE("external tab %d, mask=0x%08x, dwState=0x%08x, dwStateMask=0x%08x, cchTextMax=0x%08x\n",
199 iItem
, pti
->mask
, pti
->dwState
, pti
->dwStateMask
, pti
->cchTextMax
);
200 TRACE("external tab %d, iImage=%d, lParam=0x%08lx, pszTextW=%s\n",
201 iItem
, pti
->iImage
, pti
->lParam
, isW
? debugstr_w(pti
->pszText
) : debugstr_a((LPSTR
)pti
->pszText
));
206 TAB_DumpItemInternal(const TAB_INFO
*infoPtr
, UINT iItem
)
211 ti
= TAB_GetItem(infoPtr
, iItem
);
212 TRACE("tab %d, dwState=0x%08x, pszText=%s, iImage=%d\n",
213 iItem
, ti
->dwState
, debugstr_w(ti
->pszText
), ti
->iImage
);
214 TRACE("tab %d, rect.left=%d, rect.top(row)=%d\n",
215 iItem
, ti
->rect
.left
, ti
->rect
.top
);
220 * the index of the selected tab, or -1 if no tab is selected. */
221 static inline LRESULT
TAB_GetCurSel (const TAB_INFO
*infoPtr
)
223 return infoPtr
->iSelected
;
227 * the index of the tab item that has the focus. */
228 static inline LRESULT
229 TAB_GetCurFocus (const TAB_INFO
*infoPtr
)
231 return infoPtr
->uFocus
;
234 static inline LRESULT
TAB_GetToolTips (const TAB_INFO
*infoPtr
)
236 if (infoPtr
== NULL
) return 0;
237 return (LRESULT
)infoPtr
->hwndToolTip
;
240 static inline LRESULT
TAB_SetCurSel (TAB_INFO
*infoPtr
, INT iItem
)
242 INT prevItem
= infoPtr
->iSelected
;
245 infoPtr
->iSelected
=-1;
246 else if (iItem
>= infoPtr
->uNumItem
)
249 if (infoPtr
->iSelected
!= iItem
) {
250 TAB_GetItem(infoPtr
, prevItem
)->dwState
&= ~TCIS_BUTTONPRESSED
;
251 TAB_GetItem(infoPtr
, iItem
)->dwState
|= TCIS_BUTTONPRESSED
;
253 infoPtr
->iSelected
=iItem
;
254 infoPtr
->uFocus
=iItem
;
255 TAB_EnsureSelectionVisible(infoPtr
);
256 TAB_InvalidateTabArea(infoPtr
);
262 static LRESULT
TAB_SetCurFocus (TAB_INFO
*infoPtr
, INT iItem
)
265 infoPtr
->uFocus
= -1;
266 else if (iItem
< infoPtr
->uNumItem
) {
267 if (GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
) & TCS_BUTTONS
) {
268 FIXME("Should set input focus\n");
270 int oldFocus
= infoPtr
->uFocus
;
271 if (infoPtr
->iSelected
!= iItem
|| oldFocus
== -1 ) {
272 infoPtr
->uFocus
= iItem
;
273 if (oldFocus
!= -1) {
274 if (!TAB_SendSimpleNotify(infoPtr
, TCN_SELCHANGING
)) {
275 infoPtr
->iSelected
= iItem
;
276 TAB_SendSimpleNotify(infoPtr
, TCN_SELCHANGE
);
279 infoPtr
->iSelected
= iItem
;
280 TAB_EnsureSelectionVisible(infoPtr
);
281 TAB_InvalidateTabArea(infoPtr
);
289 static inline LRESULT
290 TAB_SetToolTips (TAB_INFO
*infoPtr
, HWND hwndToolTip
)
293 infoPtr
->hwndToolTip
= hwndToolTip
;
297 static inline LRESULT
298 TAB_SetPadding (TAB_INFO
*infoPtr
, LPARAM lParam
)
302 infoPtr
->uHItemPadding_s
=LOWORD(lParam
);
303 infoPtr
->uVItemPadding_s
=HIWORD(lParam
);
308 /******************************************************************************
309 * TAB_InternalGetItemRect
311 * This method will calculate the rectangle representing a given tab item in
312 * client coordinates. This method takes scrolling into account.
314 * This method returns TRUE if the item is visible in the window and FALSE
315 * if it is completely outside the client area.
317 static BOOL
TAB_InternalGetItemRect(
318 const TAB_INFO
* infoPtr
,
323 RECT tmpItemRect
,clientRect
;
324 LONG lStyle
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
326 /* Perform a sanity check and a trivial visibility check. */
327 if ( (infoPtr
->uNumItem
<= 0) ||
328 (itemIndex
>= infoPtr
->uNumItem
) ||
329 (!((lStyle
& TCS_MULTILINE
) || (lStyle
& TCS_VERTICAL
)) && (itemIndex
< infoPtr
->leftmostVisible
)) )
331 TRACE("Not Visible\n");
332 /* need to initialize these to empty rects */
335 memset(itemRect
,0,sizeof(RECT
));
336 itemRect
->bottom
= infoPtr
->tabHeight
;
339 memset(selectedRect
,0,sizeof(RECT
));
344 * Avoid special cases in this procedure by assigning the "out"
345 * parameters if the caller didn't supply them
347 if (itemRect
== NULL
)
348 itemRect
= &tmpItemRect
;
350 /* Retrieve the unmodified item rect. */
351 *itemRect
= TAB_GetItem(infoPtr
,itemIndex
)->rect
;
353 /* calculate the times bottom and top based on the row */
354 GetClientRect(infoPtr
->hwnd
, &clientRect
);
356 if ((lStyle
& TCS_BOTTOM
) && (lStyle
& TCS_VERTICAL
))
358 itemRect
->right
= clientRect
.right
- SELECTED_TAB_OFFSET
- itemRect
->left
* infoPtr
->tabHeight
-
359 ((lStyle
& TCS_BUTTONS
) ? itemRect
->left
* BUTTON_SPACINGX
: 0);
360 itemRect
->left
= itemRect
->right
- infoPtr
->tabHeight
;
362 else if (lStyle
& TCS_VERTICAL
)
364 itemRect
->left
= clientRect
.left
+ SELECTED_TAB_OFFSET
+ itemRect
->left
* infoPtr
->tabHeight
+
365 ((lStyle
& TCS_BUTTONS
) ? itemRect
->left
* BUTTON_SPACINGX
: 0);
366 itemRect
->right
= itemRect
->left
+ infoPtr
->tabHeight
;
368 else if (lStyle
& TCS_BOTTOM
)
370 itemRect
->bottom
= clientRect
.bottom
- itemRect
->top
* infoPtr
->tabHeight
-
371 ((lStyle
& TCS_BUTTONS
) ? itemRect
->top
* BUTTON_SPACINGY
: SELECTED_TAB_OFFSET
);
372 itemRect
->top
= itemRect
->bottom
- infoPtr
->tabHeight
;
374 else /* not TCS_BOTTOM and not TCS_VERTICAL */
376 itemRect
->top
= clientRect
.top
+ itemRect
->top
* infoPtr
->tabHeight
+
377 ((lStyle
& TCS_BUTTONS
) ? itemRect
->top
* BUTTON_SPACINGY
: SELECTED_TAB_OFFSET
);
378 itemRect
->bottom
= itemRect
->top
+ infoPtr
->tabHeight
;
382 * "scroll" it to make sure the item at the very left of the
383 * tab control is the leftmost visible tab.
385 if(lStyle
& TCS_VERTICAL
)
389 -TAB_GetItem(infoPtr
, infoPtr
->leftmostVisible
)->rect
.top
);
392 * Move the rectangle so the first item is slightly offset from
393 * the bottom of the tab control.
397 SELECTED_TAB_OFFSET
);
402 -TAB_GetItem(infoPtr
, infoPtr
->leftmostVisible
)->rect
.left
,
406 * Move the rectangle so the first item is slightly offset from
407 * the left of the tab control.
413 TRACE("item %d tab h=%d, rect=(%s)\n",
414 itemIndex
, infoPtr
->tabHeight
, wine_dbgstr_rect(itemRect
));
416 /* Now, calculate the position of the item as if it were selected. */
417 if (selectedRect
!=NULL
)
419 CopyRect(selectedRect
, itemRect
);
421 /* The rectangle of a selected item is a bit wider. */
422 if(lStyle
& TCS_VERTICAL
)
423 InflateRect(selectedRect
, 0, SELECTED_TAB_OFFSET
);
425 InflateRect(selectedRect
, SELECTED_TAB_OFFSET
, 0);
427 /* If it also a bit higher. */
428 if ((lStyle
& TCS_BOTTOM
) && (lStyle
& TCS_VERTICAL
))
430 selectedRect
->left
-= 2; /* the border is thicker on the right */
431 selectedRect
->right
+= SELECTED_TAB_OFFSET
;
433 else if (lStyle
& TCS_VERTICAL
)
435 selectedRect
->left
-= SELECTED_TAB_OFFSET
;
436 selectedRect
->right
+= 1;
438 else if (lStyle
& TCS_BOTTOM
)
440 selectedRect
->bottom
+= SELECTED_TAB_OFFSET
;
442 else /* not TCS_BOTTOM and not TCS_VERTICAL */
444 selectedRect
->top
-= SELECTED_TAB_OFFSET
;
445 selectedRect
->bottom
-= 1;
449 /* Check for visibility */
450 if (lStyle
& TCS_VERTICAL
)
451 return (itemRect
->top
< clientRect
.bottom
) && (itemRect
->bottom
> clientRect
.top
);
453 return (itemRect
->left
< clientRect
.right
) && (itemRect
->right
> clientRect
.left
);
457 TAB_GetItemRect(const TAB_INFO
*infoPtr
, WPARAM wParam
, LPARAM lParam
)
459 return TAB_InternalGetItemRect(infoPtr
, wParam
, (LPRECT
)lParam
, NULL
);
462 /******************************************************************************
465 * This method is called to handle keyboard input
467 static LRESULT
TAB_KeyUp(TAB_INFO
* infoPtr
, WPARAM keyCode
)
474 newItem
= infoPtr
->uFocus
- 1;
477 newItem
= infoPtr
->uFocus
+ 1;
482 * If we changed to a valid item, change the selection
485 newItem
< infoPtr
->uNumItem
&&
486 infoPtr
->uFocus
!= newItem
)
488 if (!TAB_SendSimpleNotify(infoPtr
, TCN_SELCHANGING
))
490 TAB_SetCurSel(infoPtr
, newItem
);
491 TAB_SendSimpleNotify(infoPtr
, TCN_SELCHANGE
);
498 /******************************************************************************
501 * This method is called whenever the focus goes in or out of this control
502 * it is used to update the visual state of the control.
504 static void TAB_FocusChanging(const TAB_INFO
*infoPtr
)
510 * Get the rectangle for the item.
512 isVisible
= TAB_InternalGetItemRect(infoPtr
,
518 * If the rectangle is not completely invisible, invalidate that
519 * portion of the window.
523 TRACE("invalidate (%s)\n", wine_dbgstr_rect(&selectedRect
));
524 InvalidateRect(infoPtr
->hwnd
, &selectedRect
, TRUE
);
528 static INT
TAB_InternalHitTest (const TAB_INFO
*infoPtr
, POINT pt
, UINT
*flags
)
533 for (iCount
= 0; iCount
< infoPtr
->uNumItem
; iCount
++)
535 TAB_InternalGetItemRect(infoPtr
, iCount
, &rect
, NULL
);
537 if (PtInRect(&rect
, pt
))
539 *flags
= TCHT_ONITEM
;
544 *flags
= TCHT_NOWHERE
;
548 static inline LRESULT
549 TAB_HitTest (const TAB_INFO
*infoPtr
, LPTCHITTESTINFO lptest
)
551 return TAB_InternalHitTest (infoPtr
, lptest
->pt
, &lptest
->flags
);
554 /******************************************************************************
557 * Napster v2b5 has a tab control for its main navigation which has a client
558 * area that covers the whole area of the dialog pages.
559 * That's why it receives all msgs for that area and the underlying dialog ctrls
561 * So I decided that we should handle WM_NCHITTEST here and return
562 * HTTRANSPARENT if we don't hit the tab control buttons.
563 * FIXME: WM_NCHITTEST handling correct ? Fix it if you know that Windows
564 * doesn't do it that way. Maybe depends on tab control styles ?
566 static inline LRESULT
567 TAB_NCHitTest (const TAB_INFO
*infoPtr
, LPARAM lParam
)
572 pt
.x
= (short)LOWORD(lParam
);
573 pt
.y
= (short)HIWORD(lParam
);
574 ScreenToClient(infoPtr
->hwnd
, &pt
);
576 if (TAB_InternalHitTest(infoPtr
, pt
, &dummyflag
) == -1)
577 return HTTRANSPARENT
;
583 TAB_LButtonDown (TAB_INFO
*infoPtr
, WPARAM wParam
, LPARAM lParam
)
588 LONG lStyle
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
590 if (infoPtr
->hwndToolTip
)
591 TAB_RelayEvent (infoPtr
->hwndToolTip
, infoPtr
->hwnd
,
592 WM_LBUTTONDOWN
, wParam
, lParam
);
594 if (GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
) & TCS_FOCUSONBUTTONDOWN
) {
595 SetFocus (infoPtr
->hwnd
);
598 if (infoPtr
->hwndToolTip
)
599 TAB_RelayEvent (infoPtr
->hwndToolTip
, infoPtr
->hwnd
,
600 WM_LBUTTONDOWN
, wParam
, lParam
);
602 pt
.x
= (short)LOWORD(lParam
);
603 pt
.y
= (short)HIWORD(lParam
);
605 newItem
= TAB_InternalHitTest (infoPtr
, pt
, &dummy
);
607 TRACE("On Tab, item %d\n", newItem
);
609 if ((newItem
!= -1) && (infoPtr
->iSelected
!= newItem
))
611 if ((lStyle
& TCS_BUTTONS
) && (lStyle
& TCS_MULTISELECT
) &&
612 (wParam
& MK_CONTROL
))
616 /* toggle multiselection */
617 TAB_GetItem(infoPtr
, newItem
)->dwState
^= TCIS_BUTTONPRESSED
;
618 if (TAB_InternalGetItemRect (infoPtr
, newItem
, &r
, NULL
))
619 InvalidateRect (infoPtr
->hwnd
, &r
, TRUE
);
624 BOOL pressed
= FALSE
;
626 /* any button pressed ? */
627 for (i
= 0; i
< infoPtr
->uNumItem
; i
++)
628 if ((TAB_GetItem (infoPtr
, i
)->dwState
& TCIS_BUTTONPRESSED
) &&
629 (infoPtr
->iSelected
!= i
))
635 TAB_SendSimpleNotify(infoPtr
, TCN_SELCHANGING
);
638 TAB_DeselectAll (infoPtr
, FALSE
);
640 TAB_SetCurSel(infoPtr
, newItem
);
642 TAB_SendSimpleNotify(infoPtr
, TCN_SELCHANGE
);
649 static inline LRESULT
650 TAB_LButtonUp (const TAB_INFO
*infoPtr
)
652 TAB_SendSimpleNotify(infoPtr
, NM_CLICK
);
657 static inline LRESULT
658 TAB_RButtonDown (const TAB_INFO
*infoPtr
)
660 TAB_SendSimpleNotify(infoPtr
, NM_RCLICK
);
664 /******************************************************************************
665 * TAB_DrawLoneItemInterior
667 * This calls TAB_DrawItemInterior. However, TAB_DrawItemInterior is normally
668 * called by TAB_DrawItem which is normally called by TAB_Refresh which sets
669 * up the device context and font. This routine does the same setup but
670 * only calls TAB_DrawItemInterior for the single specified item.
673 TAB_DrawLoneItemInterior(const TAB_INFO
* infoPtr
, int iItem
)
675 HDC hdc
= GetDC(infoPtr
->hwnd
);
678 /* Clip UpDown control to not draw over it */
679 if (infoPtr
->needsScrolling
)
681 GetWindowRect(infoPtr
->hwnd
, &rC
);
682 GetWindowRect(infoPtr
->hwndUpDown
, &r
);
683 ExcludeClipRect(hdc
, r
.left
- rC
.left
, r
.top
- rC
.top
, r
.right
- rC
.left
, r
.bottom
- rC
.top
);
685 TAB_DrawItemInterior(infoPtr
, hdc
, iItem
, NULL
);
686 ReleaseDC(infoPtr
->hwnd
, hdc
);
689 /* update a tab after hottracking - invalidate it or just redraw the interior,
690 * based on whether theming is used or not */
691 static inline void hottrack_refresh(const TAB_INFO
*infoPtr
, int tabIndex
)
693 if (tabIndex
== -1) return;
695 if (GetWindowTheme (infoPtr
->hwnd
))
698 TAB_InternalGetItemRect(infoPtr
, tabIndex
, &rect
, NULL
);
699 InvalidateRect (infoPtr
->hwnd
, &rect
, FALSE
);
702 TAB_DrawLoneItemInterior(infoPtr
, tabIndex
);
705 /******************************************************************************
706 * TAB_HotTrackTimerProc
708 * When a mouse-move event causes a tab to be highlighted (hot-tracking), a
709 * timer is setup so we can check if the mouse is moved out of our window.
710 * (We don't get an event when the mouse leaves, the mouse-move events just
711 * stop being delivered to our window and just start being delivered to
712 * another window.) This function is called when the timer triggers so
713 * we can check if the mouse has left our window. If so, we un-highlight
714 * the hot-tracked tab.
717 TAB_HotTrackTimerProc
719 HWND hwnd
, /* handle of window for timer messages */
720 UINT uMsg
, /* WM_TIMER message */
721 UINT_PTR idEvent
, /* timer identifier */
722 DWORD dwTime
/* current system time */
725 TAB_INFO
* infoPtr
= TAB_GetInfoPtr(hwnd
);
727 if (infoPtr
!= NULL
&& infoPtr
->iHotTracked
>= 0)
732 ** If we can't get the cursor position, or if the cursor is outside our
733 ** window, we un-highlight the hot-tracked tab. Note that the cursor is
734 ** "outside" even if it is within our bounding rect if another window
735 ** overlaps. Note also that the case where the cursor stayed within our
736 ** window but has moved off the hot-tracked tab will be handled by the
737 ** WM_MOUSEMOVE event.
739 if (!GetCursorPos(&pt
) || WindowFromPoint(pt
) != hwnd
)
741 /* Redraw iHotTracked to look normal */
742 INT iRedraw
= infoPtr
->iHotTracked
;
743 infoPtr
->iHotTracked
= -1;
744 hottrack_refresh (infoPtr
, iRedraw
);
746 /* Kill this timer */
747 KillTimer(hwnd
, TAB_HOTTRACK_TIMER
);
752 /******************************************************************************
755 * If a tab control has the TCS_HOTTRACK style, then the tab under the mouse
756 * should be highlighted. This function determines which tab in a tab control,
757 * if any, is under the mouse and records that information. The caller may
758 * supply output parameters to receive the item number of the tab item which
759 * was highlighted but isn't any longer and of the tab item which is now
760 * highlighted but wasn't previously. The caller can use this information to
761 * selectively redraw those tab items.
763 * If the caller has a mouse position, it can supply it through the pos
764 * parameter. For example, TAB_MouseMove does this. Otherwise, the caller
765 * supplies NULL and this function determines the current mouse position
773 int* out_redrawLeave
,
780 if (out_redrawLeave
!= NULL
)
781 *out_redrawLeave
= -1;
782 if (out_redrawEnter
!= NULL
)
783 *out_redrawEnter
= -1;
785 if ((GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
) & TCS_HOTTRACK
)
786 || GetWindowTheme (infoPtr
->hwnd
))
794 ScreenToClient(infoPtr
->hwnd
, &pt
);
798 pt
.x
= (short)LOWORD(*pos
);
799 pt
.y
= (short)HIWORD(*pos
);
802 item
= TAB_InternalHitTest(infoPtr
, pt
, &flags
);
805 if (item
!= infoPtr
->iHotTracked
)
807 if (infoPtr
->iHotTracked
>= 0)
809 /* Mark currently hot-tracked to be redrawn to look normal */
810 if (out_redrawLeave
!= NULL
)
811 *out_redrawLeave
= infoPtr
->iHotTracked
;
815 /* Kill timer which forces recheck of mouse pos */
816 KillTimer(infoPtr
->hwnd
, TAB_HOTTRACK_TIMER
);
821 /* Start timer so we recheck mouse pos */
822 UINT timerID
= SetTimer
826 TAB_HOTTRACK_TIMER_INTERVAL
,
827 TAB_HotTrackTimerProc
831 return; /* Hot tracking not available */
834 infoPtr
->iHotTracked
= item
;
838 /* Mark new hot-tracked to be redrawn to look highlighted */
839 if (out_redrawEnter
!= NULL
)
840 *out_redrawEnter
= item
;
845 /******************************************************************************
848 * Handles the mouse-move event. Updates tooltips. Updates hot-tracking.
851 TAB_MouseMove (TAB_INFO
*infoPtr
, WPARAM wParam
, LPARAM lParam
)
856 if (infoPtr
->hwndToolTip
)
857 TAB_RelayEvent (infoPtr
->hwndToolTip
, infoPtr
->hwnd
,
858 WM_LBUTTONDOWN
, wParam
, lParam
);
860 /* Determine which tab to highlight. Redraw tabs which change highlight
862 TAB_RecalcHotTrack(infoPtr
, &lParam
, &redrawLeave
, &redrawEnter
);
864 hottrack_refresh (infoPtr
, redrawLeave
);
865 hottrack_refresh (infoPtr
, redrawEnter
);
870 /******************************************************************************
873 * Calculates the tab control's display area given the window rectangle or
874 * the window rectangle given the requested display rectangle.
876 static LRESULT
TAB_AdjustRect(const TAB_INFO
*infoPtr
, WPARAM fLarger
, LPRECT prc
)
878 DWORD lStyle
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
879 LONG
*iRightBottom
, *iLeftTop
;
881 TRACE ("hwnd=%p fLarger=%ld (%s)\n", infoPtr
->hwnd
, fLarger
,
882 wine_dbgstr_rect(prc
));
886 if(lStyle
& TCS_VERTICAL
)
888 iRightBottom
= &(prc
->right
);
889 iLeftTop
= &(prc
->left
);
893 iRightBottom
= &(prc
->bottom
);
894 iLeftTop
= &(prc
->top
);
897 if (fLarger
) /* Go from display rectangle */
899 /* Add the height of the tabs. */
900 if (lStyle
& TCS_BOTTOM
)
901 *iRightBottom
+= infoPtr
->tabHeight
* infoPtr
->uNumRows
;
903 *iLeftTop
-= infoPtr
->tabHeight
* infoPtr
->uNumRows
+
904 ((lStyle
& TCS_BUTTONS
)? 3 * (infoPtr
->uNumRows
- 1) : 0);
906 /* Inflate the rectangle for the padding */
907 InflateRect(prc
, DISPLAY_AREA_PADDINGX
, DISPLAY_AREA_PADDINGY
);
909 /* Inflate for the border */
910 InflateRect(prc
, CONTROL_BORDER_SIZEX
, CONTROL_BORDER_SIZEY
);
912 else /* Go from window rectangle. */
914 /* Deflate the rectangle for the border */
915 InflateRect(prc
, -CONTROL_BORDER_SIZEX
, -CONTROL_BORDER_SIZEY
);
917 /* Deflate the rectangle for the padding */
918 InflateRect(prc
, -DISPLAY_AREA_PADDINGX
, -DISPLAY_AREA_PADDINGY
);
920 /* Remove the height of the tabs. */
921 if (lStyle
& TCS_BOTTOM
)
922 *iRightBottom
-= infoPtr
->tabHeight
* infoPtr
->uNumRows
;
924 *iLeftTop
+= (infoPtr
->tabHeight
) * infoPtr
->uNumRows
+
925 ((lStyle
& TCS_BUTTONS
)? 3 * (infoPtr
->uNumRows
- 1) : 0);
931 /******************************************************************************
934 * This method will handle the notification from the scroll control and
935 * perform the scrolling operation on the tab control.
937 static LRESULT
TAB_OnHScroll(TAB_INFO
*infoPtr
, int nScrollCode
, int nPos
)
939 if(nScrollCode
== SB_THUMBPOSITION
&& nPos
!= infoPtr
->leftmostVisible
)
941 if(nPos
< infoPtr
->leftmostVisible
)
942 infoPtr
->leftmostVisible
--;
944 infoPtr
->leftmostVisible
++;
946 TAB_RecalcHotTrack(infoPtr
, NULL
, NULL
, NULL
);
947 TAB_InvalidateTabArea(infoPtr
);
948 SendMessageW(infoPtr
->hwndUpDown
, UDM_SETPOS
, 0,
949 MAKELONG(infoPtr
->leftmostVisible
, 0));
955 /******************************************************************************
958 * This method will check the current scrolling state and make sure the
959 * scrolling control is displayed (or not).
961 static void TAB_SetupScrolling(
964 const RECT
* clientRect
)
966 static const WCHAR msctls_updown32W
[] = { 'm','s','c','t','l','s','_','u','p','d','o','w','n','3','2',0 };
967 static const WCHAR emptyW
[] = { 0 };
969 DWORD lStyle
= GetWindowLongW(hwnd
, GWL_STYLE
);
971 if (infoPtr
->needsScrolling
)
977 * Calculate the position of the scroll control.
979 if(lStyle
& TCS_VERTICAL
)
981 controlPos
.right
= clientRect
->right
;
982 controlPos
.left
= controlPos
.right
- 2 * GetSystemMetrics(SM_CXHSCROLL
);
984 if (lStyle
& TCS_BOTTOM
)
986 controlPos
.top
= clientRect
->bottom
- infoPtr
->tabHeight
;
987 controlPos
.bottom
= controlPos
.top
+ GetSystemMetrics(SM_CYHSCROLL
);
991 controlPos
.bottom
= clientRect
->top
+ infoPtr
->tabHeight
;
992 controlPos
.top
= controlPos
.bottom
- GetSystemMetrics(SM_CYHSCROLL
);
997 controlPos
.right
= clientRect
->right
;
998 controlPos
.left
= controlPos
.right
- 2 * GetSystemMetrics(SM_CXHSCROLL
);
1000 if (lStyle
& TCS_BOTTOM
)
1002 controlPos
.top
= clientRect
->bottom
- infoPtr
->tabHeight
;
1003 controlPos
.bottom
= controlPos
.top
+ GetSystemMetrics(SM_CYHSCROLL
);
1007 controlPos
.bottom
= clientRect
->top
+ infoPtr
->tabHeight
;
1008 controlPos
.top
= controlPos
.bottom
- GetSystemMetrics(SM_CYHSCROLL
);
1013 * If we don't have a scroll control yet, we want to create one.
1014 * If we have one, we want to make sure it's positioned properly.
1016 if (infoPtr
->hwndUpDown
==0)
1018 infoPtr
->hwndUpDown
= CreateWindowW(msctls_updown32W
, emptyW
,
1019 WS_VISIBLE
| WS_CHILD
| UDS_HORZ
,
1020 controlPos
.left
, controlPos
.top
,
1021 controlPos
.right
- controlPos
.left
,
1022 controlPos
.bottom
- controlPos
.top
,
1023 hwnd
, NULL
, NULL
, NULL
);
1027 SetWindowPos(infoPtr
->hwndUpDown
,
1029 controlPos
.left
, controlPos
.top
,
1030 controlPos
.right
- controlPos
.left
,
1031 controlPos
.bottom
- controlPos
.top
,
1032 SWP_SHOWWINDOW
| SWP_NOZORDER
);
1035 /* Now calculate upper limit of the updown control range.
1036 * We do this by calculating how many tabs will be offscreen when the
1037 * last tab is visible.
1039 if(infoPtr
->uNumItem
)
1041 vsize
= clientRect
->right
- (controlPos
.right
- controlPos
.left
+ 1);
1042 maxRange
= infoPtr
->uNumItem
;
1043 tabwidth
= TAB_GetItem(infoPtr
, infoPtr
->uNumItem
- 1)->rect
.right
;
1045 for(; maxRange
> 0; maxRange
--)
1047 if(tabwidth
- TAB_GetItem(infoPtr
,maxRange
- 1)->rect
.left
> vsize
)
1051 if(maxRange
== infoPtr
->uNumItem
)
1057 /* If we once had a scroll control... hide it */
1058 if (infoPtr
->hwndUpDown
!=0)
1059 ShowWindow(infoPtr
->hwndUpDown
, SW_HIDE
);
1061 if (infoPtr
->hwndUpDown
)
1062 SendMessageW(infoPtr
->hwndUpDown
, UDM_SETRANGE32
, 0, maxRange
);
1065 /******************************************************************************
1068 * This method will calculate the position rectangles of all the items in the
1069 * control. The rectangle calculated starts at 0 for the first item in the
1070 * list and ignores scrolling and selection.
1071 * It also uses the current font to determine the height of the tab row and
1072 * it checks if all the tabs fit in the client area of the window. If they
1073 * don't, a scrolling control is added.
1075 static void TAB_SetItemBounds (TAB_INFO
*infoPtr
)
1077 LONG lStyle
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
1078 TEXTMETRICW fontMetrics
;
1081 INT curItemRowCount
;
1082 HFONT hFont
, hOldFont
;
1091 * We need to get text information so we need a DC and we need to select
1094 hdc
= GetDC(infoPtr
->hwnd
);
1096 hFont
= infoPtr
->hFont
? infoPtr
->hFont
: GetStockObject (SYSTEM_FONT
);
1097 hOldFont
= SelectObject (hdc
, hFont
);
1100 * We will base the rectangle calculations on the client rectangle
1103 GetClientRect(infoPtr
->hwnd
, &clientRect
);
1105 /* if TCS_VERTICAL then swap the height and width so this code places the
1106 tabs along the top of the rectangle and we can just rotate them after
1107 rather than duplicate all of the below code */
1108 if(lStyle
& TCS_VERTICAL
)
1110 iTemp
= clientRect
.bottom
;
1111 clientRect
.bottom
= clientRect
.right
;
1112 clientRect
.right
= iTemp
;
1115 /* Now use hPadding and vPadding */
1116 infoPtr
->uHItemPadding
= infoPtr
->uHItemPadding_s
;
1117 infoPtr
->uVItemPadding
= infoPtr
->uVItemPadding_s
;
1119 /* The leftmost item will be "0" aligned */
1121 curItemRowCount
= infoPtr
->uNumItem
? 1 : 0;
1123 if (!(infoPtr
->fHeightSet
))
1126 int icon_height
= 0;
1128 /* Use the current font to determine the height of a tab. */
1129 GetTextMetricsW(hdc
, &fontMetrics
);
1131 /* Get the icon height */
1133 ImageList_GetIconSize(infoPtr
->himl
, 0, &icon_height
);
1135 /* Take the highest between font or icon */
1136 if (fontMetrics
.tmHeight
> icon_height
)
1137 item_height
= fontMetrics
.tmHeight
+ 2;
1139 item_height
= icon_height
;
1142 * Make sure there is enough space for the letters + icon + growing the
1143 * selected item + extra space for the selected item.
1145 infoPtr
->tabHeight
= item_height
+
1146 ((lStyle
& TCS_BUTTONS
) ? 2 : 1) *
1147 infoPtr
->uVItemPadding
;
1149 TRACE("tabH=%d, tmH=%d, iconh=%d\n",
1150 infoPtr
->tabHeight
, fontMetrics
.tmHeight
, icon_height
);
1153 TRACE("client right=%d\n", clientRect
.right
);
1155 /* Get the icon width */
1158 ImageList_GetIconSize(infoPtr
->himl
, &icon_width
, 0);
1160 if (lStyle
& TCS_FIXEDWIDTH
)
1163 /* Add padding if icon is present */
1164 icon_width
+= infoPtr
->uHItemPadding
;
1167 for (curItem
= 0; curItem
< infoPtr
->uNumItem
; curItem
++)
1169 TAB_ITEM
*curr
= TAB_GetItem(infoPtr
, curItem
);
1171 /* Set the leftmost position of the tab. */
1172 curr
->rect
.left
= curItemLeftPos
;
1174 if (lStyle
& TCS_FIXEDWIDTH
)
1176 curr
->rect
.right
= curr
->rect
.left
+
1177 max(infoPtr
->tabWidth
, icon_width
);
1179 else if (!curr
->pszText
)
1181 /* If no text use minimum tab width including padding. */
1182 if (infoPtr
->tabMinWidth
< 0)
1183 curr
->rect
.right
= curr
->rect
.left
+ GET_DEFAULT_MIN_TAB_WIDTH(infoPtr
);
1186 curr
->rect
.right
= curr
->rect
.left
+ infoPtr
->tabMinWidth
;
1188 /* Add extra padding if icon is present */
1189 if (infoPtr
->himl
&& infoPtr
->tabMinWidth
> 0 && infoPtr
->tabMinWidth
< DEFAULT_MIN_TAB_WIDTH
1190 && infoPtr
->uHItemPadding
> 1)
1191 curr
->rect
.right
+= EXTRA_ICON_PADDING
* (infoPtr
->uHItemPadding
-1);
1198 /* Calculate how wide the tab is depending on the text it contains */
1199 GetTextExtentPoint32W(hdc
, curr
->pszText
,
1200 lstrlenW(curr
->pszText
), &size
);
1202 tabwidth
= size
.cx
+ icon_width
+ 2 * infoPtr
->uHItemPadding
;
1204 if (infoPtr
->tabMinWidth
< 0)
1205 tabwidth
= max(tabwidth
, GET_DEFAULT_MIN_TAB_WIDTH(infoPtr
));
1207 tabwidth
= max(tabwidth
, infoPtr
->tabMinWidth
);
1209 curr
->rect
.right
= curr
->rect
.left
+ tabwidth
;
1210 TRACE("for <%s>, l,r=%d,%d\n",
1211 debugstr_w(curr
->pszText
), curr
->rect
.left
, curr
->rect
.right
);
1215 * Check if this is a multiline tab control and if so
1216 * check to see if we should wrap the tabs
1218 * Wrap all these tabs. We will arrange them evenly later.
1222 if (((lStyle
& TCS_MULTILINE
) || (lStyle
& TCS_VERTICAL
)) &&
1224 (clientRect
.right
- CONTROL_BORDER_SIZEX
- DISPLAY_AREA_PADDINGX
)))
1226 curr
->rect
.right
-= curr
->rect
.left
;
1228 curr
->rect
.left
= 0;
1230 TRACE("wrapping <%s>, l,r=%d,%d\n", debugstr_w(curr
->pszText
),
1231 curr
->rect
.left
, curr
->rect
.right
);
1234 curr
->rect
.bottom
= 0;
1235 curr
->rect
.top
= curItemRowCount
- 1;
1237 TRACE("Rect: %s\n", wine_dbgstr_rect(&curr
->rect
));
1240 * The leftmost position of the next item is the rightmost position
1243 if (lStyle
& TCS_BUTTONS
)
1245 curItemLeftPos
= curr
->rect
.right
+ BUTTON_SPACINGX
;
1246 if (lStyle
& TCS_FLATBUTTONS
)
1247 curItemLeftPos
+= FLAT_BTN_SPACINGX
;
1250 curItemLeftPos
= curr
->rect
.right
;
1253 if (!((lStyle
& TCS_MULTILINE
) || (lStyle
& TCS_VERTICAL
)))
1256 * Check if we need a scrolling control.
1258 infoPtr
->needsScrolling
= (curItemLeftPos
+ (2 * SELECTED_TAB_OFFSET
) >
1261 /* Don't need scrolling, then update infoPtr->leftmostVisible */
1262 if(!infoPtr
->needsScrolling
)
1263 infoPtr
->leftmostVisible
= 0;
1268 * No scrolling in Multiline or Vertical styles.
1270 infoPtr
->needsScrolling
= FALSE
;
1271 infoPtr
->leftmostVisible
= 0;
1273 TAB_SetupScrolling(infoPtr
->hwnd
, infoPtr
, &clientRect
);
1275 /* Set the number of rows */
1276 infoPtr
->uNumRows
= curItemRowCount
;
1278 /* Arrange all tabs evenly if style says so */
1279 if (!(lStyle
& TCS_RAGGEDRIGHT
) &&
1280 ((lStyle
& TCS_MULTILINE
) || (lStyle
& TCS_VERTICAL
)) &&
1281 (infoPtr
->uNumItem
> 0) &&
1282 (infoPtr
->uNumRows
> 1))
1284 INT tabPerRow
,remTab
,iRow
;
1289 * Ok windows tries to even out the rows. place the same
1290 * number of tabs in each row. So lets give that a shot
1293 tabPerRow
= infoPtr
->uNumItem
/ (infoPtr
->uNumRows
);
1294 remTab
= infoPtr
->uNumItem
% (infoPtr
->uNumRows
);
1296 for (iItm
=0,iRow
=0,iCount
=0,curItemLeftPos
=0;
1297 iItm
<infoPtr
->uNumItem
;
1300 /* normalize the current rect */
1301 TAB_ITEM
*curr
= TAB_GetItem(infoPtr
, iItm
);
1303 /* shift the item to the left side of the clientRect */
1304 curr
->rect
.right
-= curr
->rect
.left
;
1305 curr
->rect
.left
= 0;
1307 TRACE("r=%d, cl=%d, cl.r=%d, iCount=%d, iRow=%d, uNumRows=%d, remTab=%d, tabPerRow=%d\n",
1308 curr
->rect
.right
, curItemLeftPos
, clientRect
.right
,
1309 iCount
, iRow
, infoPtr
->uNumRows
, remTab
, tabPerRow
);
1311 /* if we have reached the maximum number of tabs on this row */
1312 /* move to the next row, reset our current item left position and */
1313 /* the count of items on this row */
1315 if (lStyle
& TCS_VERTICAL
) {
1316 /* Vert: Add the remaining tabs in the *last* remainder rows */
1317 if (iCount
>= ((iRow
>=(INT
)infoPtr
->uNumRows
- remTab
)?tabPerRow
+ 1:tabPerRow
)) {
1323 /* Horz: Add the remaining tabs in the *first* remainder rows */
1324 if (iCount
>= ((iRow
<remTab
)?tabPerRow
+ 1:tabPerRow
)) {
1331 /* shift the item to the right to place it as the next item in this row */
1332 curr
->rect
.left
+= curItemLeftPos
;
1333 curr
->rect
.right
+= curItemLeftPos
;
1334 curr
->rect
.top
= iRow
;
1335 if (lStyle
& TCS_BUTTONS
)
1337 curItemLeftPos
= curr
->rect
.right
+ 1;
1338 if (lStyle
& TCS_FLATBUTTONS
)
1339 curItemLeftPos
+= FLAT_BTN_SPACINGX
;
1342 curItemLeftPos
= curr
->rect
.right
;
1344 TRACE("arranging <%s>, l,r=%d,%d, row=%d\n",
1345 debugstr_w(curr
->pszText
), curr
->rect
.left
,
1346 curr
->rect
.right
, curr
->rect
.top
);
1353 INT widthDiff
, iIndexStart
=0, iIndexEnd
=0;
1357 while(iIndexStart
< infoPtr
->uNumItem
)
1359 TAB_ITEM
*start
= TAB_GetItem(infoPtr
, iIndexStart
);
1362 * find the index of the row
1364 /* find the first item on the next row */
1365 for (iIndexEnd
=iIndexStart
;
1366 (iIndexEnd
< infoPtr
->uNumItem
) &&
1367 (TAB_GetItem(infoPtr
, iIndexEnd
)->rect
.top
==
1370 /* intentionally blank */;
1373 * we need to justify these tabs so they fill the whole given
1377 /* find the amount of space remaining on this row */
1378 widthDiff
= clientRect
.right
- (2 * SELECTED_TAB_OFFSET
) -
1379 TAB_GetItem(infoPtr
, iIndexEnd
- 1)->rect
.right
;
1381 /* iCount is the number of tab items on this row */
1382 iCount
= iIndexEnd
- iIndexStart
;
1386 remainder
= widthDiff
% iCount
;
1387 widthDiff
= widthDiff
/ iCount
;
1388 /* add widthDiff/iCount, or extra space/items on row, to each item on this row */
1389 for (iIndex
=iIndexStart
, iCount
=0; iIndex
< iIndexEnd
; iIndex
++, iCount
++)
1391 TAB_ITEM
*item
= TAB_GetItem(infoPtr
, iIndex
);
1393 item
->rect
.left
+= iCount
* widthDiff
;
1394 item
->rect
.right
+= (iCount
+ 1) * widthDiff
;
1396 TRACE("adjusting 1 <%s>, l,r=%d,%d\n",
1397 debugstr_w(item
->pszText
),
1398 item
->rect
.left
, item
->rect
.right
);
1401 TAB_GetItem(infoPtr
, iIndex
- 1)->rect
.right
+= remainder
;
1403 else /* we have only one item on this row, make it take up the entire row */
1405 start
->rect
.left
= clientRect
.left
;
1406 start
->rect
.right
= clientRect
.right
- 4;
1408 TRACE("adjusting 2 <%s>, l,r=%d,%d\n",
1409 debugstr_w(start
->pszText
),
1410 start
->rect
.left
, start
->rect
.right
);
1415 iIndexStart
= iIndexEnd
;
1420 /* if TCS_VERTICAL rotate the tabs so they are along the side of the clientRect */
1421 if(lStyle
& TCS_VERTICAL
)
1424 for(iIndex
= 0; iIndex
< infoPtr
->uNumItem
; iIndex
++)
1426 rcItem
= &TAB_GetItem(infoPtr
, iIndex
)->rect
;
1428 rcOriginal
= *rcItem
;
1430 /* this is rotating the items by 90 degrees clockwise around the center of the control */
1431 rcItem
->top
= (rcOriginal
.left
- clientRect
.left
);
1432 rcItem
->bottom
= rcItem
->top
+ (rcOriginal
.right
- rcOriginal
.left
);
1433 rcItem
->left
= rcOriginal
.top
;
1434 rcItem
->right
= rcOriginal
.bottom
;
1438 TAB_EnsureSelectionVisible(infoPtr
);
1439 TAB_RecalcHotTrack(infoPtr
, NULL
, NULL
, NULL
);
1442 SelectObject (hdc
, hOldFont
);
1443 ReleaseDC (infoPtr
->hwnd
, hdc
);
1448 TAB_EraseTabInterior(const TAB_INFO
*infoPtr
, HDC hdc
, INT iItem
, RECT
*drawRect
)
1450 LONG lStyle
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
1451 HBRUSH hbr
= CreateSolidBrush (comctl32_color
.clrBtnFace
);
1452 BOOL deleteBrush
= TRUE
;
1453 RECT rTemp
= *drawRect
;
1455 if (lStyle
& TCS_BUTTONS
)
1457 if (iItem
== infoPtr
->iSelected
)
1459 /* Background color */
1460 if (!(lStyle
& TCS_OWNERDRAWFIXED
))
1463 hbr
= GetSysColorBrush(COLOR_SCROLLBAR
);
1465 SetTextColor(hdc
, comctl32_color
.clr3dFace
);
1466 SetBkColor(hdc
, comctl32_color
.clr3dHilight
);
1468 /* if COLOR_WINDOW happens to be the same as COLOR_3DHILIGHT
1469 * we better use 0x55aa bitmap brush to make scrollbar's background
1470 * look different from the window background.
1472 if (comctl32_color
.clr3dHilight
== comctl32_color
.clrWindow
)
1473 hbr
= COMCTL32_hPattern55AABrush
;
1475 deleteBrush
= FALSE
;
1477 FillRect(hdc
, &rTemp
, hbr
);
1479 else /* ! selected */
1481 if (lStyle
& TCS_FLATBUTTONS
)
1483 InflateRect(&rTemp
, 2, 2);
1484 FillRect(hdc
, &rTemp
, hbr
);
1485 if (iItem
== infoPtr
->iHotTracked
)
1487 DrawEdge(hdc
, &rTemp
, EDGE_RAISED
, BF_SOFT
|BF_TOPLEFT
);
1488 DrawEdge(hdc
, &rTemp
, EDGE_RAISED
, BF_FLAT
|BF_BOTTOMRIGHT
);
1492 FillRect(hdc
, &rTemp
, hbr
);
1496 else /* !TCS_BUTTONS */
1498 InflateRect(&rTemp
, -2, -2);
1499 if (!GetWindowTheme (infoPtr
->hwnd
))
1500 FillRect(hdc
, &rTemp
, hbr
);
1503 /* highlighting is drawn on top of previous fills */
1504 if (TAB_GetItem(infoPtr
, iItem
)->dwState
& TCIS_HIGHLIGHTED
)
1509 deleteBrush
= FALSE
;
1511 hbr
= GetSysColorBrush(COLOR_HIGHLIGHT
);
1512 FillRect(hdc
, &rTemp
, hbr
);
1516 if (deleteBrush
) DeleteObject(hbr
);
1519 /******************************************************************************
1520 * TAB_DrawItemInterior
1522 * This method is used to draw the interior (text and icon) of a single tab
1523 * into the tab control.
1526 TAB_DrawItemInterior(const TAB_INFO
*infoPtr
, HDC hdc
, INT iItem
, RECT
*drawRect
)
1528 LONG lStyle
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
1537 /* if (drawRect == NULL) */
1544 * Get the rectangle for the item.
1546 isVisible
= TAB_InternalGetItemRect(infoPtr
, iItem
, &itemRect
, &selectedRect
);
1551 * Make sure drawRect points to something valid; simplifies code.
1553 drawRect
= &localRect
;
1556 * This logic copied from the part of TAB_DrawItem which draws
1557 * the tab background. It's important to keep it in sync. I
1558 * would have liked to avoid code duplication, but couldn't figure
1559 * out how without making spaghetti of TAB_DrawItem.
1561 if (iItem
== infoPtr
->iSelected
)
1562 *drawRect
= selectedRect
;
1564 *drawRect
= itemRect
;
1566 if (lStyle
& TCS_BUTTONS
)
1568 if (iItem
== infoPtr
->iSelected
)
1570 drawRect
->left
+= 4;
1572 drawRect
->right
-= 4;
1574 if (lStyle
& TCS_VERTICAL
)
1576 if (!(lStyle
& TCS_BOTTOM
)) drawRect
->right
+= 1;
1577 drawRect
->bottom
-= 4;
1581 if (lStyle
& TCS_BOTTOM
)
1584 drawRect
->bottom
-= 4;
1587 drawRect
->bottom
-= 1;
1592 drawRect
->left
+= 2;
1594 drawRect
->right
-= 2;
1595 drawRect
->bottom
-= 2;
1600 if ((lStyle
& TCS_VERTICAL
) && (lStyle
& TCS_BOTTOM
))
1602 if (iItem
!= infoPtr
->iSelected
)
1604 drawRect
->left
+= 2;
1606 drawRect
->bottom
-= 2;
1609 else if (lStyle
& TCS_VERTICAL
)
1611 if (iItem
== infoPtr
->iSelected
)
1613 drawRect
->right
+= 1;
1618 drawRect
->right
-= 2;
1619 drawRect
->bottom
-= 2;
1622 else if (lStyle
& TCS_BOTTOM
)
1624 if (iItem
== infoPtr
->iSelected
)
1630 InflateRect(drawRect
, -2, -2);
1631 drawRect
->bottom
+= 2;
1636 if (iItem
== infoPtr
->iSelected
)
1638 drawRect
->bottom
+= 3;
1642 drawRect
->bottom
-= 2;
1643 InflateRect(drawRect
, -2, 0);
1648 TRACE("drawRect=(%s)\n", wine_dbgstr_rect(drawRect
));
1650 /* Clear interior */
1651 TAB_EraseTabInterior (infoPtr
, hdc
, iItem
, drawRect
);
1653 /* Draw the focus rectangle */
1654 if (!(lStyle
& TCS_FOCUSNEVER
) &&
1655 (GetFocus() == infoPtr
->hwnd
) &&
1656 (iItem
== infoPtr
->uFocus
) )
1658 RECT rFocus
= *drawRect
;
1659 InflateRect(&rFocus
, -3, -3);
1660 if (lStyle
& TCS_BOTTOM
&& !(lStyle
& TCS_VERTICAL
))
1662 if (lStyle
& TCS_BUTTONS
)
1668 DrawFocusRect(hdc
, &rFocus
);
1674 htextPen
= CreatePen( PS_SOLID
, 1, GetSysColor(COLOR_BTNTEXT
) );
1675 holdPen
= SelectObject(hdc
, htextPen
);
1676 hOldFont
= SelectObject(hdc
, infoPtr
->hFont
);
1679 * Setup for text output
1681 oldBkMode
= SetBkMode(hdc
, TRANSPARENT
);
1682 if (!GetWindowTheme (infoPtr
->hwnd
) || (lStyle
& TCS_BUTTONS
))
1684 if ((lStyle
& TCS_HOTTRACK
) && (iItem
== infoPtr
->iHotTracked
) &&
1685 !(lStyle
& TCS_FLATBUTTONS
))
1686 SetTextColor(hdc
, comctl32_color
.clrHighlight
);
1687 else if (TAB_GetItem(infoPtr
, iItem
)->dwState
& TCIS_HIGHLIGHTED
)
1688 SetTextColor(hdc
, comctl32_color
.clrHighlightText
);
1690 SetTextColor(hdc
, comctl32_color
.clrBtnText
);
1694 * if owner draw, tell the owner to draw
1696 if ((lStyle
& TCS_OWNERDRAWFIXED
) && GetParent(infoPtr
->hwnd
))
1702 drawRect
->right
-= 1;
1703 if ( iItem
== infoPtr
->iSelected
)
1705 drawRect
->right
-= 1;
1706 drawRect
->left
+= 1;
1710 * get the control id
1712 id
= (UINT
)GetWindowLongPtrW( infoPtr
->hwnd
, GWLP_ID
);
1715 * put together the DRAWITEMSTRUCT
1717 dis
.CtlType
= ODT_TAB
;
1720 dis
.itemAction
= ODA_DRAWENTIRE
;
1722 if ( iItem
== infoPtr
->iSelected
)
1723 dis
.itemState
|= ODS_SELECTED
;
1724 if (infoPtr
->uFocus
== iItem
)
1725 dis
.itemState
|= ODS_FOCUS
;
1726 dis
.hwndItem
= infoPtr
->hwnd
;
1728 CopyRect(&dis
.rcItem
,drawRect
);
1729 dis
.itemData
= (ULONG_PTR
)TAB_GetItem(infoPtr
, iItem
)->extra
;
1732 * send the draw message
1734 SendMessageW( infoPtr
->hwndNotify
, WM_DRAWITEM
, (WPARAM
)id
, (LPARAM
)&dis
);
1738 TAB_ITEM
*item
= TAB_GetItem(infoPtr
, iItem
);
1742 /* used to center the icon and text in the tab */
1744 INT center_offset_h
, center_offset_v
;
1746 /* set rcImage to drawRect, we will use top & left in our ImageList_Draw call */
1747 rcImage
= *drawRect
;
1751 rcText
.left
= rcText
.top
= rcText
.right
= rcText
.bottom
= 0;
1753 /* get the rectangle that the text fits in */
1756 DrawTextW(hdc
, item
->pszText
, -1, &rcText
, DT_CALCRECT
);
1759 * If not owner draw, then do the drawing ourselves.
1763 if (infoPtr
->himl
&& item
->iImage
!= -1)
1768 ImageList_GetIconSize(infoPtr
->himl
, &cx
, &cy
);
1770 if(lStyle
& TCS_VERTICAL
)
1772 center_offset_h
= ((drawRect
->bottom
- drawRect
->top
) - (cy
+ infoPtr
->uHItemPadding
+ (rcText
.right
- rcText
.left
))) / 2;
1773 center_offset_v
= ((drawRect
->right
- drawRect
->left
) - cx
) / 2;
1777 center_offset_h
= ((drawRect
->right
- drawRect
->left
) - (cx
+ infoPtr
->uHItemPadding
+ (rcText
.right
- rcText
.left
))) / 2;
1778 center_offset_v
= ((drawRect
->bottom
- drawRect
->top
) - cy
) / 2;
1781 /* if an item is selected, the icon is shifted up instead of down */
1782 if (iItem
== infoPtr
->iSelected
)
1783 center_offset_v
-= infoPtr
->uVItemPadding
/ 2;
1785 center_offset_v
+= infoPtr
->uVItemPadding
/ 2;
1787 if (lStyle
& TCS_FIXEDWIDTH
&& lStyle
& (TCS_FORCELABELLEFT
| TCS_FORCEICONLEFT
))
1788 center_offset_h
= infoPtr
->uHItemPadding
;
1790 if (center_offset_h
< 2)
1791 center_offset_h
= 2;
1793 if (center_offset_v
< 0)
1794 center_offset_v
= 0;
1796 TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%s), textlen=%d\n",
1797 debugstr_w(item
->pszText
), center_offset_h
, center_offset_v
,
1798 wine_dbgstr_rect(drawRect
), (rcText
.right
-rcText
.left
));
1800 if((lStyle
& TCS_VERTICAL
) && (lStyle
& TCS_BOTTOM
))
1802 rcImage
.top
= drawRect
->top
+ center_offset_h
;
1803 /* if tab is TCS_VERTICAL and TCS_BOTTOM, the text is drawn from the */
1804 /* right side of the tab, but the image still uses the left as its x position */
1805 /* this keeps the image always drawn off of the same side of the tab */
1806 rcImage
.left
= drawRect
->right
- cx
- center_offset_v
;
1807 drawRect
->top
+= cy
+ infoPtr
->uHItemPadding
;
1809 else if(lStyle
& TCS_VERTICAL
)
1811 rcImage
.top
= drawRect
->bottom
- cy
- center_offset_h
;
1812 rcImage
.left
= drawRect
->left
+ center_offset_v
;
1813 drawRect
->bottom
-= cy
+ infoPtr
->uHItemPadding
;
1815 else /* normal style, whether TCS_BOTTOM or not */
1817 rcImage
.left
= drawRect
->left
+ center_offset_h
;
1818 rcImage
.top
= drawRect
->top
+ center_offset_v
;
1819 drawRect
->left
+= cx
+ infoPtr
->uHItemPadding
;
1822 TRACE("drawing image=%d, left=%d, top=%d\n",
1823 item
->iImage
, rcImage
.left
, rcImage
.top
-1);
1835 /* Now position text */
1836 if (lStyle
& TCS_FIXEDWIDTH
&& lStyle
& TCS_FORCELABELLEFT
)
1837 center_offset_h
= infoPtr
->uHItemPadding
;
1839 if(lStyle
& TCS_VERTICAL
)
1840 center_offset_h
= ((drawRect
->bottom
- drawRect
->top
) - (rcText
.right
- rcText
.left
)) / 2;
1842 center_offset_h
= ((drawRect
->right
- drawRect
->left
) - (rcText
.right
- rcText
.left
)) / 2;
1844 if(lStyle
& TCS_VERTICAL
)
1846 if(lStyle
& TCS_BOTTOM
)
1847 drawRect
->top
+=center_offset_h
;
1849 drawRect
->bottom
-=center_offset_h
;
1851 center_offset_v
= ((drawRect
->right
- drawRect
->left
) - (rcText
.bottom
- rcText
.top
)) / 2;
1855 drawRect
->left
+= center_offset_h
;
1856 center_offset_v
= ((drawRect
->bottom
- drawRect
->top
) - (rcText
.bottom
- rcText
.top
)) / 2;
1859 /* if an item is selected, the text is shifted up instead of down */
1860 if (iItem
== infoPtr
->iSelected
)
1861 center_offset_v
-= infoPtr
->uVItemPadding
/ 2;
1863 center_offset_v
+= infoPtr
->uVItemPadding
/ 2;
1865 if (center_offset_v
< 0)
1866 center_offset_v
= 0;
1868 if(lStyle
& TCS_VERTICAL
)
1869 drawRect
->left
+= center_offset_v
;
1871 drawRect
->top
+= center_offset_v
;
1874 if(lStyle
& TCS_VERTICAL
) /* if we are vertical rotate the text and each character */
1876 static const WCHAR ArialW
[] = { 'A','r','i','a','l',0 };
1879 INT nEscapement
= 900;
1880 INT nOrientation
= 900;
1882 if(lStyle
& TCS_BOTTOM
)
1885 nOrientation
= -900;
1888 /* to get a font with the escapement and orientation we are looking for, we need to */
1889 /* call CreateFontIndirectA, which requires us to set the values of the logfont we pass in */
1890 if (!GetObjectW((infoPtr
->hFont
) ?
1891 infoPtr
->hFont
: GetStockObject(SYSTEM_FONT
),
1892 sizeof(LOGFONTW
),&logfont
))
1896 lstrcpyW(logfont
.lfFaceName
, ArialW
);
1897 logfont
.lfHeight
= -MulDiv(iPointSize
, GetDeviceCaps(hdc
, LOGPIXELSY
),
1899 logfont
.lfWeight
= FW_NORMAL
;
1900 logfont
.lfItalic
= 0;
1901 logfont
.lfUnderline
= 0;
1902 logfont
.lfStrikeOut
= 0;
1905 logfont
.lfEscapement
= nEscapement
;
1906 logfont
.lfOrientation
= nOrientation
;
1907 hFont
= CreateFontIndirectW(&logfont
);
1908 SelectObject(hdc
, hFont
);
1913 (lStyle
& TCS_BOTTOM
) ? drawRect
->right
: drawRect
->left
,
1914 (!(lStyle
& TCS_BOTTOM
)) ? drawRect
->bottom
: drawRect
->top
,
1918 lstrlenW(item
->pszText
),
1922 DeleteObject(hFont
);
1926 TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%s), textlen=%d\n",
1927 debugstr_w(item
->pszText
), center_offset_h
, center_offset_v
,
1928 wine_dbgstr_rect(drawRect
), (rcText
.right
-rcText
.left
));
1935 lstrlenW(item
->pszText
),
1937 DT_LEFT
| DT_SINGLELINE
1942 *drawRect
= rcTemp
; /* restore drawRect */
1948 SelectObject(hdc
, hOldFont
);
1949 SetBkMode(hdc
, oldBkMode
);
1950 SelectObject(hdc
, holdPen
);
1951 DeleteObject( htextPen
);
1954 /******************************************************************************
1957 * This method is used to draw a single tab into the tab control.
1959 static void TAB_DrawItem(const TAB_INFO
*infoPtr
, HDC hdc
, INT iItem
)
1961 LONG lStyle
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
1965 RECT r
, fillRect
, r1
;
1968 COLORREF bkgnd
, corner
;
1972 * Get the rectangle for the item.
1974 isVisible
= TAB_InternalGetItemRect(infoPtr
,
1983 /* Clip UpDown control to not draw over it */
1984 if (infoPtr
->needsScrolling
)
1986 GetWindowRect(infoPtr
->hwnd
, &rC
);
1987 GetWindowRect(infoPtr
->hwndUpDown
, &rUD
);
1988 ExcludeClipRect(hdc
, rUD
.left
- rC
.left
, rUD
.top
- rC
.top
, rUD
.right
- rC
.left
, rUD
.bottom
- rC
.top
);
1991 /* If you need to see what the control is doing,
1992 * then override these variables. They will change what
1993 * fill colors are used for filling the tabs, and the
1994 * corners when drawing the edge.
1996 bkgnd
= comctl32_color
.clrBtnFace
;
1997 corner
= comctl32_color
.clrBtnFace
;
1999 if (lStyle
& TCS_BUTTONS
)
2001 /* Get item rectangle */
2004 /* Separators between flat buttons */
2005 if ((lStyle
& TCS_FLATBUTTONS
) && (infoPtr
->exStyle
& TCS_EX_FLATSEPARATORS
))
2008 r1
.right
+= (FLAT_BTN_SPACINGX
-2);
2009 DrawEdge(hdc
, &r1
, EDGE_ETCHED
, BF_RIGHT
);
2012 if (iItem
== infoPtr
->iSelected
)
2014 DrawEdge(hdc
, &r
, EDGE_SUNKEN
, BF_SOFT
|BF_RECT
);
2016 OffsetRect(&r
, 1, 1);
2018 else /* ! selected */
2020 DWORD state
= TAB_GetItem(infoPtr
, iItem
)->dwState
;
2022 if (state
& TCIS_BUTTONPRESSED
)
2023 DrawEdge(hdc
, &r
, EDGE_SUNKEN
, BF_SOFT
|BF_RECT
);
2025 if (!(lStyle
& TCS_FLATBUTTONS
))
2026 DrawEdge(hdc
, &r
, EDGE_RAISED
, BF_SOFT
|BF_RECT
);
2029 else /* !TCS_BUTTONS */
2031 /* We draw a rectangle of different sizes depending on the selection
2033 if (iItem
== infoPtr
->iSelected
) {
2035 GetClientRect (infoPtr
->hwnd
, &rect
);
2036 clRight
= rect
.right
;
2037 clBottom
= rect
.bottom
;
2044 * Erase the background. (Delay it but setup rectangle.)
2045 * This is necessary when drawing the selected item since it is larger
2046 * than the others, it might overlap with stuff already drawn by the
2051 /* Draw themed tabs - but only if they are at the top.
2052 * Windows draws even side or bottom tabs themed, with wacky results.
2053 * However, since in Wine apps may get themed that did not opt in via
2054 * a manifest avoid theming when we know the result will be wrong */
2055 if ((theme
= GetWindowTheme (infoPtr
->hwnd
))
2056 && ((lStyle
& (TCS_VERTICAL
| TCS_BOTTOM
)) == 0))
2058 static const int partIds
[8] = {
2061 TABP_TABITEMLEFTEDGE
,
2062 TABP_TABITEMRIGHTEDGE
,
2063 TABP_TABITEMBOTHEDGE
,
2066 TABP_TOPTABITEMLEFTEDGE
,
2067 TABP_TOPTABITEMRIGHTEDGE
,
2068 TABP_TOPTABITEMBOTHEDGE
,
2071 int stateId
= TIS_NORMAL
;
2073 /* selected and unselected tabs have different parts */
2074 if (iItem
== infoPtr
->iSelected
)
2076 /* The part also differs on the position of a tab on a line.
2077 * "Visually" determining the position works well enough. */
2078 if(selectedRect
.left
== 0)
2080 if(selectedRect
.right
== clRight
)
2083 if (iItem
== infoPtr
->iSelected
)
2084 stateId
= TIS_SELECTED
;
2085 else if (iItem
== infoPtr
->iHotTracked
)
2087 else if (iItem
== infoPtr
->uFocus
)
2088 stateId
= TIS_FOCUSED
;
2090 /* Adjust rectangle for bottommost row */
2091 if (TAB_GetItem(infoPtr
, iItem
)->rect
.top
== infoPtr
->uNumRows
-1)
2094 DrawThemeBackground (theme
, hdc
, partIds
[partIndex
], stateId
, &r
, NULL
);
2095 GetThemeBackgroundContentRect (theme
, hdc
, partIds
[partIndex
], stateId
, &r
, &r
);
2097 else if(lStyle
& TCS_VERTICAL
)
2099 /* These are for adjusting the drawing of a Selected tab */
2100 /* The initial values are for the normal case of non-Selected */
2101 int ZZ
= 1; /* Do not stretch if selected */
2102 if (iItem
== infoPtr
->iSelected
) {
2105 /* if leftmost draw the line longer */
2106 if(selectedRect
.top
== 0)
2107 fillRect
.top
+= CONTROL_BORDER_SIZEY
;
2108 /* if rightmost draw the line longer */
2109 if(selectedRect
.bottom
== clBottom
)
2110 fillRect
.bottom
-= CONTROL_BORDER_SIZEY
;
2113 if (lStyle
& TCS_BOTTOM
)
2115 /* Adjust both rectangles to match native */
2118 TRACE("<right> item=%d, fill=(%s), edge=(%s)\n",
2119 iItem
, wine_dbgstr_rect(&fillRect
), wine_dbgstr_rect(&r
));
2121 /* Clear interior */
2122 SetBkColor(hdc
, bkgnd
);
2123 ExtTextOutW(hdc
, 0, 0, 2, &fillRect
, NULL
, 0, 0);
2125 /* Draw rectangular edge around tab */
2126 DrawEdge(hdc
, &r
, EDGE_RAISED
, BF_SOFT
|BF_RIGHT
|BF_TOP
|BF_BOTTOM
);
2128 /* Now erase the top corner and draw diagonal edge */
2129 SetBkColor(hdc
, corner
);
2130 r1
.left
= r
.right
- ROUND_CORNER_SIZE
- 1;
2133 r1
.bottom
= r1
.top
+ ROUND_CORNER_SIZE
;
2134 ExtTextOutW(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2136 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_DIAGONAL_ENDTOPLEFT
);
2138 /* Now erase the bottom corner and draw diagonal edge */
2139 r1
.left
= r
.right
- ROUND_CORNER_SIZE
- 1;
2140 r1
.bottom
= r
.bottom
;
2142 r1
.top
= r1
.bottom
- ROUND_CORNER_SIZE
;
2143 ExtTextOutW(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2145 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_DIAGONAL_ENDBOTTOMLEFT
);
2147 if ((iItem
== infoPtr
->iSelected
) && (selectedRect
.top
== 0)) {
2151 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_TOP
);
2157 TRACE("<left> item=%d, fill=(%s), edge=(%s)\n",
2158 iItem
, wine_dbgstr_rect(&fillRect
), wine_dbgstr_rect(&r
));
2160 /* Clear interior */
2161 SetBkColor(hdc
, bkgnd
);
2162 ExtTextOutW(hdc
, 0, 0, 2, &fillRect
, NULL
, 0, 0);
2164 /* Draw rectangular edge around tab */
2165 DrawEdge(hdc
, &r
, EDGE_RAISED
, BF_SOFT
|BF_LEFT
|BF_TOP
|BF_BOTTOM
);
2167 /* Now erase the top corner and draw diagonal edge */
2168 SetBkColor(hdc
, corner
);
2171 r1
.right
= r1
.left
+ ROUND_CORNER_SIZE
+ 1;
2172 r1
.bottom
= r1
.top
+ ROUND_CORNER_SIZE
;
2173 ExtTextOutW(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2175 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_DIAGONAL_ENDTOPRIGHT
);
2177 /* Now erase the bottom corner and draw diagonal edge */
2179 r1
.bottom
= r
.bottom
;
2180 r1
.right
= r1
.left
+ ROUND_CORNER_SIZE
+ 1;
2181 r1
.top
= r1
.bottom
- ROUND_CORNER_SIZE
;
2182 ExtTextOutW(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2184 DrawEdge(hdc
, &r1
, EDGE_SUNKEN
, BF_DIAGONAL_ENDTOPLEFT
);
2187 else /* ! TCS_VERTICAL */
2189 /* These are for adjusting the drawing of a Selected tab */
2190 /* The initial values are for the normal case of non-Selected */
2191 if (iItem
== infoPtr
->iSelected
) {
2192 /* if leftmost draw the line longer */
2193 if(selectedRect
.left
== 0)
2194 fillRect
.left
+= CONTROL_BORDER_SIZEX
;
2195 /* if rightmost draw the line longer */
2196 if(selectedRect
.right
== clRight
)
2197 fillRect
.right
-= CONTROL_BORDER_SIZEX
;
2200 if (lStyle
& TCS_BOTTOM
)
2202 /* Adjust both rectangles for topmost row */
2203 if (TAB_GetItem(infoPtr
, iItem
)->rect
.top
== infoPtr
->uNumRows
-1)
2209 TRACE("<bottom> item=%d, fill=(%s), edge=(%s)\n",
2210 iItem
, wine_dbgstr_rect(&fillRect
), wine_dbgstr_rect(&r
));
2212 /* Clear interior */
2213 SetBkColor(hdc
, bkgnd
);
2214 ExtTextOutW(hdc
, 0, 0, 2, &fillRect
, NULL
, 0, 0);
2216 /* Draw rectangular edge around tab */
2217 DrawEdge(hdc
, &r
, EDGE_RAISED
, BF_SOFT
|BF_LEFT
|BF_BOTTOM
|BF_RIGHT
);
2219 /* Now erase the righthand corner and draw diagonal edge */
2220 SetBkColor(hdc
, corner
);
2221 r1
.left
= r
.right
- ROUND_CORNER_SIZE
;
2222 r1
.bottom
= r
.bottom
;
2224 r1
.top
= r1
.bottom
- ROUND_CORNER_SIZE
- 1;
2225 ExtTextOutW(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2227 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_DIAGONAL_ENDBOTTOMLEFT
);
2229 /* Now erase the lefthand corner and draw diagonal edge */
2231 r1
.bottom
= r
.bottom
;
2232 r1
.right
= r1
.left
+ ROUND_CORNER_SIZE
;
2233 r1
.top
= r1
.bottom
- ROUND_CORNER_SIZE
- 1;
2234 ExtTextOutW(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2236 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_DIAGONAL_ENDTOPLEFT
);
2238 if (iItem
== infoPtr
->iSelected
)
2242 if (selectedRect
.left
== 0)
2247 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_LEFT
);
2254 /* Adjust both rectangles for bottommost row */
2255 if (TAB_GetItem(infoPtr
, iItem
)->rect
.top
== infoPtr
->uNumRows
-1)
2257 fillRect
.bottom
+= 3;
2261 TRACE("<top> item=%d, fill=(%s), edge=(%s)\n",
2262 iItem
, wine_dbgstr_rect(&fillRect
), wine_dbgstr_rect(&r
));
2264 /* Clear interior */
2265 SetBkColor(hdc
, bkgnd
);
2266 ExtTextOutW(hdc
, 0, 0, 2, &fillRect
, NULL
, 0, 0);
2268 /* Draw rectangular edge around tab */
2269 DrawEdge(hdc
, &r
, EDGE_RAISED
, BF_SOFT
|BF_LEFT
|BF_TOP
|BF_RIGHT
);
2271 /* Now erase the righthand corner and draw diagonal edge */
2272 SetBkColor(hdc
, corner
);
2273 r1
.left
= r
.right
- ROUND_CORNER_SIZE
;
2276 r1
.bottom
= r1
.top
+ ROUND_CORNER_SIZE
+ 1;
2277 ExtTextOutW(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2279 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_DIAGONAL_ENDBOTTOMRIGHT
);
2281 /* Now erase the lefthand corner and draw diagonal edge */
2284 r1
.right
= r1
.left
+ ROUND_CORNER_SIZE
;
2285 r1
.bottom
= r1
.top
+ ROUND_CORNER_SIZE
+ 1;
2286 ExtTextOutW(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2288 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_DIAGONAL_ENDTOPRIGHT
);
2293 TAB_DumpItemInternal(infoPtr
, iItem
);
2295 /* This modifies r to be the text rectangle. */
2296 TAB_DrawItemInterior(infoPtr
, hdc
, iItem
, &r
);
2300 /******************************************************************************
2303 * This method is used to draw the raised border around the tab control
2306 static void TAB_DrawBorder(const TAB_INFO
*infoPtr
, HDC hdc
)
2309 DWORD lStyle
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
2310 HTHEME theme
= GetWindowTheme (infoPtr
->hwnd
);
2312 GetClientRect (infoPtr
->hwnd
, &rect
);
2315 * Adjust for the style
2318 if (infoPtr
->uNumItem
)
2320 if ((lStyle
& TCS_BOTTOM
) && !(lStyle
& TCS_VERTICAL
))
2321 rect
.bottom
-= infoPtr
->tabHeight
* infoPtr
->uNumRows
+ CONTROL_BORDER_SIZEX
;
2322 else if((lStyle
& TCS_BOTTOM
) && (lStyle
& TCS_VERTICAL
))
2323 rect
.right
-= infoPtr
->tabHeight
* infoPtr
->uNumRows
+ CONTROL_BORDER_SIZEX
;
2324 else if(lStyle
& TCS_VERTICAL
)
2325 rect
.left
+= infoPtr
->tabHeight
* infoPtr
->uNumRows
+ CONTROL_BORDER_SIZEX
;
2326 else /* not TCS_VERTICAL and not TCS_BOTTOM */
2327 rect
.top
+= infoPtr
->tabHeight
* infoPtr
->uNumRows
+ CONTROL_BORDER_SIZEX
;
2330 TRACE("border=(%s)\n", wine_dbgstr_rect(&rect
));
2333 DrawThemeBackground (theme
, hdc
, TABP_PANE
, 0, &rect
, NULL
);
2335 DrawEdge(hdc
, &rect
, EDGE_RAISED
, BF_SOFT
|BF_RECT
);
2338 /******************************************************************************
2341 * This method repaints the tab control..
2343 static void TAB_Refresh (TAB_INFO
*infoPtr
, HDC hdc
)
2348 if (!infoPtr
->DoRedraw
)
2351 hOldFont
= SelectObject (hdc
, infoPtr
->hFont
);
2353 if (GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
) & TCS_BUTTONS
)
2355 for (i
= 0; i
< infoPtr
->uNumItem
; i
++)
2356 TAB_DrawItem (infoPtr
, hdc
, i
);
2360 /* Draw all the non selected item first */
2361 for (i
= 0; i
< infoPtr
->uNumItem
; i
++)
2363 if (i
!= infoPtr
->iSelected
)
2364 TAB_DrawItem (infoPtr
, hdc
, i
);
2367 /* Now, draw the border, draw it before the selected item
2368 * since the selected item overwrites part of the border. */
2369 TAB_DrawBorder (infoPtr
, hdc
);
2371 /* Then, draw the selected item */
2372 TAB_DrawItem (infoPtr
, hdc
, infoPtr
->iSelected
);
2375 SelectObject (hdc
, hOldFont
);
2378 static inline DWORD
TAB_GetRowCount (const TAB_INFO
*infoPtr
)
2380 return infoPtr
->uNumRows
;
2383 static inline LRESULT
TAB_SetRedraw (TAB_INFO
*infoPtr
, BOOL doRedraw
)
2385 infoPtr
->DoRedraw
= doRedraw
;
2389 /******************************************************************************
2390 * TAB_EnsureSelectionVisible
2392 * This method will make sure that the current selection is completely
2393 * visible by scrolling until it is.
2395 static void TAB_EnsureSelectionVisible(
2398 INT iSelected
= infoPtr
->iSelected
;
2399 LONG lStyle
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
2400 INT iOrigLeftmostVisible
= infoPtr
->leftmostVisible
;
2402 /* set the items row to the bottommost row or topmost row depending on
2404 if ((infoPtr
->uNumRows
> 1) && !(lStyle
& TCS_BUTTONS
))
2406 TAB_ITEM
*selected
= TAB_GetItem(infoPtr
, iSelected
);
2410 if(lStyle
& TCS_VERTICAL
)
2411 newselected
= selected
->rect
.left
;
2413 newselected
= selected
->rect
.top
;
2415 /* the target row is always (number of rows - 1)
2416 as row 0 is furthest from the clientRect */
2417 iTargetRow
= infoPtr
->uNumRows
- 1;
2419 if (newselected
!= iTargetRow
)
2422 if(lStyle
& TCS_VERTICAL
)
2424 for (i
=0; i
< infoPtr
->uNumItem
; i
++)
2426 /* move everything in the row of the selected item to the iTargetRow */
2427 TAB_ITEM
*item
= TAB_GetItem(infoPtr
, i
);
2429 if (item
->rect
.left
== newselected
)
2430 item
->rect
.left
= iTargetRow
;
2433 if (item
->rect
.left
> newselected
)
2440 for (i
=0; i
< infoPtr
->uNumItem
; i
++)
2442 TAB_ITEM
*item
= TAB_GetItem(infoPtr
, i
);
2444 if (item
->rect
.top
== newselected
)
2445 item
->rect
.top
= iTargetRow
;
2448 if (item
->rect
.top
> newselected
)
2453 TAB_RecalcHotTrack(infoPtr
, NULL
, NULL
, NULL
);
2458 * Do the trivial cases first.
2460 if ( (!infoPtr
->needsScrolling
) ||
2461 (infoPtr
->hwndUpDown
==0) || (lStyle
& TCS_VERTICAL
))
2464 if (infoPtr
->leftmostVisible
>= iSelected
)
2466 infoPtr
->leftmostVisible
= iSelected
;
2470 TAB_ITEM
*selected
= TAB_GetItem(infoPtr
, iSelected
);
2475 /* Calculate the part of the client area that is visible */
2476 GetClientRect(infoPtr
->hwnd
, &r
);
2479 GetClientRect(infoPtr
->hwndUpDown
, &r
);
2482 if ((selected
->rect
.right
-
2483 selected
->rect
.left
) >= width
)
2485 /* Special case: width of selected item is greater than visible
2488 infoPtr
->leftmostVisible
= iSelected
;
2492 for (i
= infoPtr
->leftmostVisible
; i
< infoPtr
->uNumItem
; i
++)
2494 if ((selected
->rect
.right
- TAB_GetItem(infoPtr
, i
)->rect
.left
) < width
)
2497 infoPtr
->leftmostVisible
= i
;
2501 if (infoPtr
->leftmostVisible
!= iOrigLeftmostVisible
)
2502 TAB_RecalcHotTrack(infoPtr
, NULL
, NULL
, NULL
);
2504 SendMessageW(infoPtr
->hwndUpDown
, UDM_SETPOS
, 0,
2505 MAKELONG(infoPtr
->leftmostVisible
, 0));
2508 /******************************************************************************
2509 * TAB_InvalidateTabArea
2511 * This method will invalidate the portion of the control that contains the
2512 * tabs. It is called when the state of the control changes and needs
2515 static void TAB_InvalidateTabArea(const TAB_INFO
*infoPtr
)
2517 RECT clientRect
, rInvalidate
, rAdjClient
;
2518 DWORD lStyle
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
2519 INT lastRow
= infoPtr
->uNumRows
- 1;
2522 if (lastRow
< 0) return;
2524 GetClientRect(infoPtr
->hwnd
, &clientRect
);
2525 rInvalidate
= clientRect
;
2526 rAdjClient
= clientRect
;
2528 TAB_AdjustRect(infoPtr
, 0, &rAdjClient
);
2530 TAB_InternalGetItemRect(infoPtr
, infoPtr
->uNumItem
-1 , &rect
, NULL
);
2531 if ((lStyle
& TCS_BOTTOM
) && (lStyle
& TCS_VERTICAL
))
2533 rInvalidate
.left
= rAdjClient
.right
;
2534 if (infoPtr
->uNumRows
== 1)
2535 rInvalidate
.bottom
= clientRect
.top
+ rect
.bottom
+ 2 * SELECTED_TAB_OFFSET
;
2537 else if(lStyle
& TCS_VERTICAL
)
2539 rInvalidate
.right
= rAdjClient
.left
;
2540 if (infoPtr
->uNumRows
== 1)
2541 rInvalidate
.bottom
= clientRect
.top
+ rect
.bottom
+ 2 * SELECTED_TAB_OFFSET
;
2543 else if (lStyle
& TCS_BOTTOM
)
2545 rInvalidate
.top
= rAdjClient
.bottom
;
2546 if (infoPtr
->uNumRows
== 1)
2547 rInvalidate
.right
= clientRect
.left
+ rect
.right
+ 2 * SELECTED_TAB_OFFSET
;
2551 rInvalidate
.bottom
= rAdjClient
.top
;
2552 if (infoPtr
->uNumRows
== 1)
2553 rInvalidate
.right
= clientRect
.left
+ rect
.right
+ 2 * SELECTED_TAB_OFFSET
;
2556 /* Punch out the updown control */
2557 if (infoPtr
->needsScrolling
&& (rInvalidate
.right
> 0)) {
2559 GetClientRect(infoPtr
->hwndUpDown
, &r
);
2560 if (rInvalidate
.right
> clientRect
.right
- r
.left
)
2561 rInvalidate
.right
= rInvalidate
.right
- (r
.right
- r
.left
);
2563 rInvalidate
.right
= clientRect
.right
- r
.left
;
2566 TRACE("invalidate (%s)\n", wine_dbgstr_rect(&rInvalidate
));
2568 InvalidateRect(infoPtr
->hwnd
, &rInvalidate
, TRUE
);
2571 static inline LRESULT
TAB_Paint (TAB_INFO
*infoPtr
, HDC hdcPaint
)
2580 hdc
= BeginPaint (infoPtr
->hwnd
, &ps
);
2581 TRACE("erase %d, rect=(%s)\n", ps
.fErase
, wine_dbgstr_rect(&ps
.rcPaint
));
2584 TAB_Refresh (infoPtr
, hdc
);
2587 EndPaint (infoPtr
->hwnd
, &ps
);
2593 TAB_InsertItemT (TAB_INFO
*infoPtr
, WPARAM wParam
, LPARAM lParam
, BOOL bUnicode
)
2600 GetClientRect (infoPtr
->hwnd
, &rect
);
2601 TRACE("Rect: %p %s\n", infoPtr
->hwnd
, wine_dbgstr_rect(&rect
));
2603 pti
= (TCITEMW
*)lParam
;
2604 iItem
= (INT
)wParam
;
2606 if (iItem
< 0) return -1;
2607 if (iItem
> infoPtr
->uNumItem
)
2608 iItem
= infoPtr
->uNumItem
;
2610 TAB_DumpItemExternalT(pti
, iItem
, bUnicode
);
2613 if (infoPtr
->uNumItem
== 0) {
2614 infoPtr
->items
= Alloc (TAB_ITEM_SIZE(infoPtr
));
2615 infoPtr
->uNumItem
++;
2616 infoPtr
->iSelected
= 0;
2619 LPBYTE oldItems
= (LPBYTE
)infoPtr
->items
;
2621 infoPtr
->uNumItem
++;
2622 infoPtr
->items
= Alloc (TAB_ITEM_SIZE(infoPtr
) * infoPtr
->uNumItem
);
2624 /* pre insert copy */
2626 memcpy (infoPtr
->items
, oldItems
,
2627 iItem
* TAB_ITEM_SIZE(infoPtr
));
2630 /* post insert copy */
2631 if (iItem
< infoPtr
->uNumItem
- 1) {
2632 memcpy (TAB_GetItem(infoPtr
, iItem
+ 1),
2633 oldItems
+ iItem
* TAB_ITEM_SIZE(infoPtr
),
2634 (infoPtr
->uNumItem
- iItem
- 1) * TAB_ITEM_SIZE(infoPtr
));
2638 if (iItem
<= infoPtr
->iSelected
)
2639 infoPtr
->iSelected
++;
2644 item
= TAB_GetItem(infoPtr
, iItem
);
2646 item
->pszText
= NULL
;
2648 if (pti
->mask
& TCIF_TEXT
)
2651 Str_SetPtrW (&item
->pszText
, pti
->pszText
);
2653 Str_SetPtrAtoW (&item
->pszText
, (LPSTR
)pti
->pszText
);
2656 if (pti
->mask
& TCIF_IMAGE
)
2657 item
->iImage
= pti
->iImage
;
2661 if (pti
->mask
& TCIF_PARAM
)
2662 memcpy(item
->extra
, &pti
->lParam
, infoPtr
->cbInfo
);
2664 memset(item
->extra
, 0, infoPtr
->cbInfo
);
2666 TAB_SetItemBounds(infoPtr
);
2667 if (infoPtr
->uNumItem
> 1)
2668 TAB_InvalidateTabArea(infoPtr
);
2670 InvalidateRect(infoPtr
->hwnd
, NULL
, TRUE
);
2672 TRACE("[%p]: added item %d %s\n",
2673 infoPtr
->hwnd
, iItem
, debugstr_w(item
->pszText
));
2675 /* If we haven't set the current focus yet, set it now. */
2676 if (infoPtr
->uFocus
== -1)
2677 TAB_SetCurFocus(infoPtr
, iItem
);
2683 TAB_SetItemSize (TAB_INFO
*infoPtr
, LPARAM lParam
)
2685 LONG lStyle
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
2687 BOOL bNeedPaint
= FALSE
;
2689 lResult
= MAKELONG(infoPtr
->tabWidth
, infoPtr
->tabHeight
);
2691 /* UNDOCUMENTED: If requested Width or Height is 0 this means that program wants to use auto size. */
2692 if (lStyle
& TCS_FIXEDWIDTH
&& (infoPtr
->tabWidth
!= (INT
)LOWORD(lParam
)))
2694 infoPtr
->tabWidth
= (INT
)LOWORD(lParam
);
2698 if (infoPtr
->tabHeight
!= (INT
)HIWORD(lParam
))
2700 if ((infoPtr
->fHeightSet
= ((INT
)HIWORD(lParam
) != 0)))
2701 infoPtr
->tabHeight
= (INT
)HIWORD(lParam
);
2705 TRACE("was h=%d,w=%d, now h=%d,w=%d\n",
2706 HIWORD(lResult
), LOWORD(lResult
),
2707 infoPtr
->tabHeight
, infoPtr
->tabWidth
);
2711 TAB_SetItemBounds(infoPtr
);
2712 RedrawWindow(infoPtr
->hwnd
, NULL
, NULL
, RDW_ERASE
| RDW_INVALIDATE
| RDW_UPDATENOW
);
2718 static inline LRESULT
TAB_SetMinTabWidth (TAB_INFO
*infoPtr
, INT cx
)
2722 TRACE("(%p,%d)\n", infoPtr
, cx
);
2724 if (infoPtr
->tabMinWidth
< 0)
2725 oldcx
= DEFAULT_MIN_TAB_WIDTH
;
2727 oldcx
= infoPtr
->tabMinWidth
;
2728 infoPtr
->tabMinWidth
= cx
;
2729 TAB_SetItemBounds(infoPtr
);
2733 static inline LRESULT
2734 TAB_HighlightItem (TAB_INFO
*infoPtr
, INT iItem
, BOOL fHighlight
)
2740 TRACE("(%p,%d,%s)\n", infoPtr
, iItem
, fHighlight
? "true" : "false");
2742 if (!infoPtr
|| iItem
< 0 || iItem
>= infoPtr
->uNumItem
)
2745 lpState
= &TAB_GetItem(infoPtr
, iItem
)->dwState
;
2746 oldState
= *lpState
;
2749 *lpState
|= TCIS_HIGHLIGHTED
;
2751 *lpState
&= ~TCIS_HIGHLIGHTED
;
2753 if ((oldState
!= *lpState
) && TAB_InternalGetItemRect (infoPtr
, iItem
, &r
, NULL
))
2754 InvalidateRect (infoPtr
->hwnd
, &r
, TRUE
);
2760 TAB_SetItemT (TAB_INFO
*infoPtr
, INT iItem
, LPTCITEMW tabItem
, BOOL bUnicode
)
2764 TRACE("(%p,%d,%p,%s)\n", infoPtr
, iItem
, tabItem
, bUnicode
? "true" : "false");
2766 if (iItem
< 0 || iItem
>= infoPtr
->uNumItem
)
2769 TAB_DumpItemExternalT(tabItem
, iItem
, bUnicode
);
2771 wineItem
= TAB_GetItem(infoPtr
, iItem
);
2773 if (tabItem
->mask
& TCIF_IMAGE
)
2774 wineItem
->iImage
= tabItem
->iImage
;
2776 if (tabItem
->mask
& TCIF_PARAM
)
2777 memcpy(wineItem
->extra
, &tabItem
->lParam
, infoPtr
->cbInfo
);
2779 if (tabItem
->mask
& TCIF_RTLREADING
)
2780 FIXME("TCIF_RTLREADING\n");
2782 if (tabItem
->mask
& TCIF_STATE
)
2783 wineItem
->dwState
= (wineItem
->dwState
& ~tabItem
->dwStateMask
) |
2784 ( tabItem
->dwState
& tabItem
->dwStateMask
);
2786 if (tabItem
->mask
& TCIF_TEXT
)
2788 Free(wineItem
->pszText
);
2789 wineItem
->pszText
= NULL
;
2791 Str_SetPtrW(&wineItem
->pszText
, tabItem
->pszText
);
2793 Str_SetPtrAtoW(&wineItem
->pszText
, (LPSTR
)tabItem
->pszText
);
2796 /* Update and repaint tabs */
2797 TAB_SetItemBounds(infoPtr
);
2798 TAB_InvalidateTabArea(infoPtr
);
2803 static inline LRESULT
TAB_GetItemCount (const TAB_INFO
*infoPtr
)
2805 return infoPtr
->uNumItem
;
2810 TAB_GetItemT (TAB_INFO
*infoPtr
, INT iItem
, LPTCITEMW tabItem
, BOOL bUnicode
)
2814 TRACE("(%p,%d,%p,%s)\n", infoPtr
, iItem
, tabItem
, bUnicode
? "true" : "false");
2816 if (iItem
< 0 || iItem
>= infoPtr
->uNumItem
)
2819 wineItem
= TAB_GetItem(infoPtr
, iItem
);
2821 if (tabItem
->mask
& TCIF_IMAGE
)
2822 tabItem
->iImage
= wineItem
->iImage
;
2824 if (tabItem
->mask
& TCIF_PARAM
)
2825 memcpy(&tabItem
->lParam
, wineItem
->extra
, infoPtr
->cbInfo
);
2827 if (tabItem
->mask
& TCIF_RTLREADING
)
2828 FIXME("TCIF_RTLREADING\n");
2830 if (tabItem
->mask
& TCIF_STATE
)
2831 tabItem
->dwState
= wineItem
->dwState
& tabItem
->dwStateMask
;
2833 if (tabItem
->mask
& TCIF_TEXT
)
2836 Str_GetPtrW (wineItem
->pszText
, tabItem
->pszText
, tabItem
->cchTextMax
);
2838 Str_GetPtrWtoA (wineItem
->pszText
, (LPSTR
)tabItem
->pszText
, tabItem
->cchTextMax
);
2841 TAB_DumpItemExternalT(tabItem
, iItem
, bUnicode
);
2847 static LRESULT
TAB_DeleteItem (TAB_INFO
*infoPtr
, INT iItem
)
2849 BOOL bResult
= FALSE
;
2851 TRACE("(%p, %d)\n", infoPtr
, iItem
);
2853 if ((iItem
>= 0) && (iItem
< infoPtr
->uNumItem
))
2855 TAB_ITEM
*item
= TAB_GetItem(infoPtr
, iItem
);
2856 LPBYTE oldItems
= (LPBYTE
)infoPtr
->items
;
2858 TAB_InvalidateTabArea(infoPtr
);
2859 Free(item
->pszText
);
2860 infoPtr
->uNumItem
--;
2862 if (!infoPtr
->uNumItem
)
2864 infoPtr
->items
= NULL
;
2865 if (infoPtr
->iHotTracked
>= 0)
2867 KillTimer(infoPtr
->hwnd
, TAB_HOTTRACK_TIMER
);
2868 infoPtr
->iHotTracked
= -1;
2873 infoPtr
->items
= Alloc(TAB_ITEM_SIZE(infoPtr
) * infoPtr
->uNumItem
);
2876 memcpy(infoPtr
->items
, oldItems
, iItem
* TAB_ITEM_SIZE(infoPtr
));
2878 if (iItem
< infoPtr
->uNumItem
)
2879 memcpy(TAB_GetItem(infoPtr
, iItem
),
2880 oldItems
+ (iItem
+ 1) * TAB_ITEM_SIZE(infoPtr
),
2881 (infoPtr
->uNumItem
- iItem
) * TAB_ITEM_SIZE(infoPtr
));
2883 if (iItem
<= infoPtr
->iHotTracked
)
2885 /* When tabs move left/up, the hot track item may change */
2886 FIXME("Recalc hot track\n");
2891 /* Readjust the selected index */
2892 if ((iItem
== infoPtr
->iSelected
) && (iItem
> 0))
2893 infoPtr
->iSelected
--;
2895 if (iItem
< infoPtr
->iSelected
)
2896 infoPtr
->iSelected
--;
2898 if (infoPtr
->uNumItem
== 0)
2899 infoPtr
->iSelected
= -1;
2901 /* Reposition and repaint tabs */
2902 TAB_SetItemBounds(infoPtr
);
2910 static inline LRESULT
TAB_DeleteAllItems (TAB_INFO
*infoPtr
)
2912 TRACE("(%p)\n", infoPtr
);
2913 while (infoPtr
->uNumItem
)
2914 TAB_DeleteItem (infoPtr
, 0);
2919 static inline LRESULT
TAB_GetFont (const TAB_INFO
*infoPtr
)
2921 TRACE("(%p) returning %p\n", infoPtr
, infoPtr
->hFont
);
2922 return (LRESULT
)infoPtr
->hFont
;
2925 static inline LRESULT
TAB_SetFont (TAB_INFO
*infoPtr
, HFONT hNewFont
)
2927 TRACE("(%p,%p)\n", infoPtr
, hNewFont
);
2929 infoPtr
->hFont
= hNewFont
;
2931 TAB_SetItemBounds(infoPtr
);
2933 TAB_InvalidateTabArea(infoPtr
);
2939 static inline LRESULT
TAB_GetImageList (const TAB_INFO
*infoPtr
)
2942 return (LRESULT
)infoPtr
->himl
;
2945 static inline LRESULT
TAB_SetImageList (TAB_INFO
*infoPtr
, HIMAGELIST himlNew
)
2947 HIMAGELIST himlPrev
= infoPtr
->himl
;
2949 infoPtr
->himl
= himlNew
;
2950 TAB_SetItemBounds(infoPtr
);
2951 InvalidateRect(infoPtr
->hwnd
, NULL
, TRUE
);
2952 return (LRESULT
)himlPrev
;
2955 static inline LRESULT
TAB_GetUnicodeFormat (const TAB_INFO
*infoPtr
)
2957 return infoPtr
->bUnicode
;
2960 static inline LRESULT
TAB_SetUnicodeFormat (TAB_INFO
*infoPtr
, BOOL bUnicode
)
2962 BOOL bTemp
= infoPtr
->bUnicode
;
2964 infoPtr
->bUnicode
= bUnicode
;
2969 static inline LRESULT
TAB_Size (TAB_INFO
*infoPtr
)
2971 /* I'm not really sure what the following code was meant to do.
2972 This is what it is doing:
2973 When WM_SIZE is sent with SIZE_RESTORED, the control
2974 gets positioned in the top left corner.
2978 UINT uPosFlags,cx,cy;
2982 parent = GetParent (hwnd);
2983 GetClientRect(parent, &parent_rect);
2986 if (GetWindowLongW(hwnd, GWL_STYLE) & CCS_NORESIZE)
2987 uPosFlags |= (SWP_NOSIZE | SWP_NOMOVE);
2989 SetWindowPos (hwnd, 0, parent_rect.left, parent_rect.top,
2990 cx, cy, uPosFlags | SWP_NOZORDER);
2992 FIXME("WM_SIZE flag %x %lx not handled\n", wParam, lParam);
2995 /* Recompute the size/position of the tabs. */
2996 TAB_SetItemBounds (infoPtr
);
2998 /* Force a repaint of the control. */
2999 InvalidateRect(infoPtr
->hwnd
, NULL
, TRUE
);
3005 static LRESULT
TAB_Create (HWND hwnd
, LPARAM lParam
)
3008 TEXTMETRICW fontMetrics
;
3013 infoPtr
= Alloc (sizeof(TAB_INFO
));
3015 SetWindowLongPtrW(hwnd
, 0, (DWORD_PTR
)infoPtr
);
3017 infoPtr
->hwnd
= hwnd
;
3018 infoPtr
->hwndNotify
= ((LPCREATESTRUCTW
)lParam
)->hwndParent
;
3019 infoPtr
->uNumItem
= 0;
3020 infoPtr
->uNumRows
= 0;
3021 infoPtr
->uHItemPadding
= 6;
3022 infoPtr
->uVItemPadding
= 3;
3023 infoPtr
->uHItemPadding_s
= 6;
3024 infoPtr
->uVItemPadding_s
= 3;
3027 infoPtr
->hcurArrow
= LoadCursorW (0, (LPWSTR
)IDC_ARROW
);
3028 infoPtr
->iSelected
= -1;
3029 infoPtr
->iHotTracked
= -1;
3030 infoPtr
->uFocus
= -1;
3031 infoPtr
->hwndToolTip
= 0;
3032 infoPtr
->DoRedraw
= TRUE
;
3033 infoPtr
->needsScrolling
= FALSE
;
3034 infoPtr
->hwndUpDown
= 0;
3035 infoPtr
->leftmostVisible
= 0;
3036 infoPtr
->fHeightSet
= FALSE
;
3037 infoPtr
->bUnicode
= IsWindowUnicode (hwnd
);
3038 infoPtr
->cbInfo
= sizeof(LPARAM
);
3040 TRACE("Created tab control, hwnd [%p]\n", hwnd
);
3042 /* The tab control always has the WS_CLIPSIBLINGS style. Even
3043 if you don't specify it in CreateWindow. This is necessary in
3044 order for paint to work correctly. This follows windows behaviour. */
3045 dwStyle
= GetWindowLongW(hwnd
, GWL_STYLE
);
3046 SetWindowLongW(hwnd
, GWL_STYLE
, dwStyle
|WS_CLIPSIBLINGS
);
3048 infoPtr
->exStyle
= (dwStyle
& TCS_FLATBUTTONS
) ? TCS_EX_FLATSEPARATORS
: 0;
3050 if (dwStyle
& TCS_TOOLTIPS
) {
3051 /* Create tooltip control */
3052 infoPtr
->hwndToolTip
=
3053 CreateWindowExW (0, TOOLTIPS_CLASSW
, NULL
, WS_POPUP
,
3054 CW_USEDEFAULT
, CW_USEDEFAULT
,
3055 CW_USEDEFAULT
, CW_USEDEFAULT
,
3058 /* Send NM_TOOLTIPSCREATED notification */
3059 if (infoPtr
->hwndToolTip
) {
3060 NMTOOLTIPSCREATED nmttc
;
3062 nmttc
.hdr
.hwndFrom
= hwnd
;
3063 nmttc
.hdr
.idFrom
= GetWindowLongPtrW(hwnd
, GWLP_ID
);
3064 nmttc
.hdr
.code
= NM_TOOLTIPSCREATED
;
3065 nmttc
.hwndToolTips
= infoPtr
->hwndToolTip
;
3067 SendMessageW (infoPtr
->hwndNotify
, WM_NOTIFY
,
3068 (WPARAM
)GetWindowLongPtrW(hwnd
, GWLP_ID
), (LPARAM
)&nmttc
);
3072 OpenThemeData (infoPtr
->hwnd
, themeClass
);
3075 * We need to get text information so we need a DC and we need to select
3079 hOldFont
= SelectObject (hdc
, GetStockObject (SYSTEM_FONT
));
3081 /* Use the system font to determine the initial height of a tab. */
3082 GetTextMetricsW(hdc
, &fontMetrics
);
3085 * Make sure there is enough space for the letters + growing the
3086 * selected item + extra space for the selected item.
3088 infoPtr
->tabHeight
= fontMetrics
.tmHeight
+ SELECTED_TAB_OFFSET
+
3089 ((dwStyle
& TCS_BUTTONS
) ? 2 : 1) *
3090 infoPtr
->uVItemPadding
;
3092 /* Initialize the width of a tab. */
3093 if (dwStyle
& TCS_FIXEDWIDTH
)
3094 infoPtr
->tabWidth
= GetDeviceCaps(hdc
, LOGPIXELSX
);
3096 infoPtr
->tabMinWidth
= -1;
3098 TRACE("tabH=%d, tabW=%d\n", infoPtr
->tabHeight
, infoPtr
->tabWidth
);
3100 SelectObject (hdc
, hOldFont
);
3101 ReleaseDC(hwnd
, hdc
);
3107 TAB_Destroy (TAB_INFO
*infoPtr
)
3114 SetWindowLongPtrW(infoPtr
->hwnd
, 0, 0);
3116 if (infoPtr
->items
) {
3117 for (iItem
= 0; iItem
< infoPtr
->uNumItem
; iItem
++) {
3118 Free (TAB_GetItem(infoPtr
, iItem
)->pszText
);
3120 Free (infoPtr
->items
);
3123 if (infoPtr
->hwndToolTip
)
3124 DestroyWindow (infoPtr
->hwndToolTip
);
3126 if (infoPtr
->hwndUpDown
)
3127 DestroyWindow(infoPtr
->hwndUpDown
);
3129 if (infoPtr
->iHotTracked
>= 0)
3130 KillTimer(infoPtr
->hwnd
, TAB_HOTTRACK_TIMER
);
3132 CloseThemeData (GetWindowTheme (infoPtr
->hwnd
));
3138 /* update theme after a WM_THEMECHANGED message */
3139 static LRESULT
theme_changed(const TAB_INFO
*infoPtr
)
3141 HTHEME theme
= GetWindowTheme (infoPtr
->hwnd
);
3142 CloseThemeData (theme
);
3143 OpenThemeData (infoPtr
->hwnd
, themeClass
);
3147 static LRESULT
TAB_NCCalcSize(WPARAM wParam
)
3151 return WVR_ALIGNTOP
;
3154 static inline LRESULT
3155 TAB_SetItemExtra (TAB_INFO
*infoPtr
, INT cbInfo
)
3157 if (!infoPtr
|| cbInfo
<= 0)
3160 if (infoPtr
->uNumItem
)
3162 /* FIXME: MSDN says this is not allowed, but this hasn't been verified */
3166 infoPtr
->cbInfo
= cbInfo
;
3170 static LRESULT
TAB_RemoveImage (TAB_INFO
*infoPtr
, INT image
)
3175 if (ImageList_Remove (infoPtr
->himl
, image
))
3180 /* shift indices, repaint items if needed */
3181 for (i
= 0; i
< infoPtr
->uNumItem
; i
++)
3183 idx
= &TAB_GetItem(infoPtr
, i
)->iImage
;
3192 if (TAB_InternalGetItemRect (infoPtr
, i
, &r
, NULL
))
3193 InvalidateRect (infoPtr
->hwnd
, &r
, TRUE
);
3202 TAB_SetExtendedStyle (TAB_INFO
*infoPtr
, DWORD exMask
, DWORD exStyle
)
3204 DWORD prevstyle
= infoPtr
->exStyle
;
3206 /* zero mask means all styles */
3207 if (exMask
== 0) exMask
= ~0;
3209 if (exMask
& TCS_EX_REGISTERDROP
)
3211 FIXME("TCS_EX_REGISTERDROP style unimplemented\n");
3212 exMask
&= ~TCS_EX_REGISTERDROP
;
3213 exStyle
&= ~TCS_EX_REGISTERDROP
;
3216 if (exMask
& TCS_EX_FLATSEPARATORS
)
3218 if ((prevstyle
^ exStyle
) & TCS_EX_FLATSEPARATORS
)
3220 infoPtr
->exStyle
^= TCS_EX_FLATSEPARATORS
;
3221 TAB_InvalidateTabArea(infoPtr
);
3228 static inline LRESULT
3229 TAB_GetExtendedStyle (TAB_INFO
*infoPtr
)
3231 return infoPtr
->exStyle
;
3235 TAB_DeselectAll (TAB_INFO
*infoPtr
, BOOL excludesel
)
3237 LONG style
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
3239 INT i
, selected
= infoPtr
->iSelected
;
3241 if (!(style
& TCS_BUTTONS
))
3244 for (i
= 0; i
< infoPtr
->uNumItem
; i
++)
3246 if ((TAB_GetItem(infoPtr
, i
)->dwState
& TCIS_BUTTONPRESSED
) &&
3249 TAB_GetItem(infoPtr
, i
)->dwState
&= ~TCIS_BUTTONPRESSED
;
3254 if (!excludesel
&& (selected
!= -1))
3256 TAB_GetItem(infoPtr
, selected
)->dwState
&= ~TCIS_BUTTONPRESSED
;
3257 infoPtr
->iSelected
= -1;
3262 TAB_InvalidateTabArea (infoPtr
);
3267 static LRESULT WINAPI
3268 TAB_WindowProc (HWND hwnd
, UINT uMsg
, WPARAM wParam
, LPARAM lParam
)
3270 TAB_INFO
*infoPtr
= TAB_GetInfoPtr(hwnd
);
3272 TRACE("hwnd=%p msg=%x wParam=%lx lParam=%lx\n", hwnd
, uMsg
, wParam
, lParam
);
3273 if (!infoPtr
&& (uMsg
!= WM_CREATE
))
3274 return DefWindowProcW (hwnd
, uMsg
, wParam
, lParam
);
3278 case TCM_GETIMAGELIST
:
3279 return TAB_GetImageList (infoPtr
);
3281 case TCM_SETIMAGELIST
:
3282 return TAB_SetImageList (infoPtr
, (HIMAGELIST
)lParam
);
3284 case TCM_GETITEMCOUNT
:
3285 return TAB_GetItemCount (infoPtr
);
3289 return TAB_GetItemT (infoPtr
, (INT
)wParam
, (LPTCITEMW
)lParam
, uMsg
== TCM_GETITEMW
);
3293 return TAB_SetItemT (infoPtr
, (INT
)wParam
, (LPTCITEMW
)lParam
, uMsg
== TCM_SETITEMW
);
3295 case TCM_DELETEITEM
:
3296 return TAB_DeleteItem (infoPtr
, (INT
)wParam
);
3298 case TCM_DELETEALLITEMS
:
3299 return TAB_DeleteAllItems (infoPtr
);
3301 case TCM_GETITEMRECT
:
3302 return TAB_GetItemRect (infoPtr
, wParam
, lParam
);
3305 return TAB_GetCurSel (infoPtr
);
3308 return TAB_HitTest (infoPtr
, (LPTCHITTESTINFO
)lParam
);
3311 return TAB_SetCurSel (infoPtr
, (INT
)wParam
);
3313 case TCM_INSERTITEMA
:
3314 case TCM_INSERTITEMW
:
3315 return TAB_InsertItemT (infoPtr
, wParam
, lParam
, uMsg
== TCM_INSERTITEMW
);
3317 case TCM_SETITEMEXTRA
:
3318 return TAB_SetItemExtra (infoPtr
, (int)wParam
);
3320 case TCM_ADJUSTRECT
:
3321 return TAB_AdjustRect (infoPtr
, (BOOL
)wParam
, (LPRECT
)lParam
);
3323 case TCM_SETITEMSIZE
:
3324 return TAB_SetItemSize (infoPtr
, lParam
);
3326 case TCM_REMOVEIMAGE
:
3327 return TAB_RemoveImage (infoPtr
, wParam
);
3329 case TCM_SETPADDING
:
3330 return TAB_SetPadding (infoPtr
, lParam
);
3332 case TCM_GETROWCOUNT
:
3333 return TAB_GetRowCount(infoPtr
);
3335 case TCM_GETUNICODEFORMAT
:
3336 return TAB_GetUnicodeFormat (infoPtr
);
3338 case TCM_SETUNICODEFORMAT
:
3339 return TAB_SetUnicodeFormat (infoPtr
, (BOOL
)wParam
);
3341 case TCM_HIGHLIGHTITEM
:
3342 return TAB_HighlightItem (infoPtr
, (INT
)wParam
, (BOOL
)LOWORD(lParam
));
3344 case TCM_GETTOOLTIPS
:
3345 return TAB_GetToolTips (infoPtr
);
3347 case TCM_SETTOOLTIPS
:
3348 return TAB_SetToolTips (infoPtr
, (HWND
)wParam
);
3350 case TCM_GETCURFOCUS
:
3351 return TAB_GetCurFocus (infoPtr
);
3353 case TCM_SETCURFOCUS
:
3354 return TAB_SetCurFocus (infoPtr
, (INT
)wParam
);
3356 case TCM_SETMINTABWIDTH
:
3357 return TAB_SetMinTabWidth(infoPtr
, (INT
)lParam
);
3359 case TCM_DESELECTALL
:
3360 return TAB_DeselectAll (infoPtr
, (BOOL
)wParam
);
3362 case TCM_GETEXTENDEDSTYLE
:
3363 return TAB_GetExtendedStyle (infoPtr
);
3365 case TCM_SETEXTENDEDSTYLE
:
3366 return TAB_SetExtendedStyle (infoPtr
, wParam
, lParam
);
3369 return TAB_GetFont (infoPtr
);
3372 return TAB_SetFont (infoPtr
, (HFONT
)wParam
);
3375 return TAB_Create (hwnd
, lParam
);
3378 return TAB_Destroy (infoPtr
);
3381 return DLGC_WANTARROWS
| DLGC_WANTCHARS
;
3383 case WM_LBUTTONDOWN
:
3384 return TAB_LButtonDown (infoPtr
, wParam
, lParam
);
3387 return TAB_LButtonUp (infoPtr
);
3390 return SendMessageW(infoPtr
->hwndNotify
, WM_NOTIFY
, wParam
, lParam
);
3392 case WM_RBUTTONDOWN
:
3393 return TAB_RButtonDown (infoPtr
);
3396 return TAB_MouseMove (infoPtr
, wParam
, lParam
);
3398 case WM_PRINTCLIENT
:
3400 return TAB_Paint (infoPtr
, (HDC
)wParam
);
3403 return TAB_Size (infoPtr
);
3406 return TAB_SetRedraw (infoPtr
, (BOOL
)wParam
);
3409 return TAB_OnHScroll(infoPtr
, (int)LOWORD(wParam
), (int)HIWORD(wParam
));
3411 case WM_STYLECHANGED
:
3412 TAB_SetItemBounds (infoPtr
);
3413 InvalidateRect(hwnd
, NULL
, TRUE
);
3416 case WM_SYSCOLORCHANGE
:
3417 COMCTL32_RefreshSysColors();
3420 case WM_THEMECHANGED
:
3421 return theme_changed (infoPtr
);
3425 TAB_FocusChanging(infoPtr
);
3426 break; /* Don't disturb normal focus behavior */
3429 return TAB_KeyUp(infoPtr
, wParam
);
3431 return TAB_NCHitTest(infoPtr
, lParam
);
3434 return TAB_NCCalcSize(wParam
);
3437 if (uMsg
>= WM_USER
&& uMsg
< WM_APP
&& !COMCTL32_IsReflectedMessage(uMsg
))
3438 WARN("unknown msg %04x wp=%08lx lp=%08lx\n",
3439 uMsg
, wParam
, lParam
);
3442 return DefWindowProcW(hwnd
, uMsg
, wParam
, lParam
);
3451 ZeroMemory (&wndClass
, sizeof(WNDCLASSW
));
3452 wndClass
.style
= CS_GLOBALCLASS
| CS_DBLCLKS
| CS_HREDRAW
| CS_VREDRAW
;
3453 wndClass
.lpfnWndProc
= TAB_WindowProc
;
3454 wndClass
.cbClsExtra
= 0;
3455 wndClass
.cbWndExtra
= sizeof(TAB_INFO
*);
3456 wndClass
.hCursor
= LoadCursorW (0, (LPWSTR
)IDC_ARROW
);
3457 wndClass
.hbrBackground
= (HBRUSH
)(COLOR_BTNFACE
+1);
3458 wndClass
.lpszClassName
= WC_TABCONTROLW
;
3460 RegisterClassW (&wndClass
);
3465 TAB_Unregister (void)
3467 UnregisterClassW (WC_TABCONTROLW
, NULL
);