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
);
89 RECT rect
; /* bounding rectangle of the item relative to the
90 * leftmost item (the leftmost item, 0, would have a
91 * "left" member of 0 in this rectangle)
93 * additionally the top member holds the row number
94 * and bottom is unused and should be 0 */
95 BYTE extra
[1]; /* Space for caller supplied info, variable size */
98 /* The size of a tab item depends on how much extra data is requested */
99 #define TAB_ITEM_SIZE(infoPtr) (sizeof(TAB_ITEM) - sizeof(BYTE) + infoPtr->cbInfo)
103 HWND hwnd
; /* Tab control window */
104 HWND hwndNotify
; /* notification window (parent) */
105 UINT uNumItem
; /* number of tab items */
106 UINT uNumRows
; /* number of tab rows */
107 INT tabHeight
; /* height of the tab row */
108 INT tabWidth
; /* width of tabs */
109 INT tabMinWidth
; /* minimum width of items */
110 USHORT uHItemPadding
; /* amount of horizontal padding, in pixels */
111 USHORT uVItemPadding
; /* amount of vertical padding, in pixels */
112 USHORT uHItemPadding_s
; /* Set amount of horizontal padding, in pixels */
113 USHORT uVItemPadding_s
; /* Set amount of vertical padding, in pixels */
114 HFONT hFont
; /* handle to the current font */
115 HCURSOR hcurArrow
; /* handle to the current cursor */
116 HIMAGELIST himl
; /* handle to an image list (may be 0) */
117 HWND hwndToolTip
; /* handle to tab's tooltip */
118 INT leftmostVisible
; /* Used for scrolling, this member contains
119 * the index of the first visible item */
120 INT iSelected
; /* the currently selected item */
121 INT iHotTracked
; /* the highlighted item under the mouse */
122 INT uFocus
; /* item which has the focus */
123 TAB_ITEM
* items
; /* pointer to an array of TAB_ITEM's */
124 BOOL DoRedraw
; /* flag for redrawing when tab contents is changed*/
125 BOOL needsScrolling
; /* TRUE if the size of the tabs is greater than
126 * the size of the control */
127 BOOL fHeightSet
; /* was the height of the tabs explicitly set? */
128 BOOL bUnicode
; /* Unicode control? */
129 HWND hwndUpDown
; /* Updown control used for scrolling */
130 INT cbInfo
; /* Number of bytes of caller supplied info per tab */
133 /******************************************************************************
134 * Positioning constants
136 #define SELECTED_TAB_OFFSET 2
137 #define ROUND_CORNER_SIZE 2
138 #define DISPLAY_AREA_PADDINGX 2
139 #define DISPLAY_AREA_PADDINGY 2
140 #define CONTROL_BORDER_SIZEX 2
141 #define CONTROL_BORDER_SIZEY 2
142 #define BUTTON_SPACINGX 3
143 #define BUTTON_SPACINGY 3
144 #define FLAT_BTN_SPACINGX 8
145 #define DEFAULT_MIN_TAB_WIDTH 54
146 #define DEFAULT_TAB_WIDTH_FIXED 96
147 #define DEFAULT_PADDING_X 6
148 #define EXTRA_ICON_PADDING 3
150 #define TAB_GetInfoPtr(hwnd) ((TAB_INFO *)GetWindowLongPtrW(hwnd,0))
151 /* Since items are variable sized, cannot directly access them */
152 #define TAB_GetItem(info,i) \
153 ((TAB_ITEM*)((LPBYTE)info->items + (i) * TAB_ITEM_SIZE(info)))
155 #define GET_DEFAULT_MIN_TAB_WIDTH(infoPtr) (DEFAULT_MIN_TAB_WIDTH - (DEFAULT_PADDING_X - (infoPtr)->uHItemPadding) * 2)
157 /******************************************************************************
158 * Hot-tracking timer constants
160 #define TAB_HOTTRACK_TIMER 1
161 #define TAB_HOTTRACK_TIMER_INTERVAL 100 /* milliseconds */
163 static const WCHAR themeClass
[] = { 'T','a','b',0 };
165 /******************************************************************************
168 static void TAB_InvalidateTabArea(TAB_INFO
*);
169 static void TAB_EnsureSelectionVisible(TAB_INFO
*);
170 static void TAB_DrawItemInterior(TAB_INFO
*, HDC
, INT
, RECT
*);
173 TAB_SendSimpleNotify (const TAB_INFO
*infoPtr
, UINT code
)
177 nmhdr
.hwndFrom
= infoPtr
->hwnd
;
178 nmhdr
.idFrom
= GetWindowLongPtrW(infoPtr
->hwnd
, GWLP_ID
);
181 return (BOOL
) SendMessageW (infoPtr
->hwndNotify
, WM_NOTIFY
,
182 (WPARAM
) nmhdr
.idFrom
, (LPARAM
) &nmhdr
);
186 TAB_RelayEvent (HWND hwndTip
, HWND hwndMsg
, UINT uMsg
,
187 WPARAM wParam
, LPARAM lParam
)
195 msg
.time
= GetMessageTime ();
196 msg
.pt
.x
= LOWORD(GetMessagePos ());
197 msg
.pt
.y
= HIWORD(GetMessagePos ());
199 SendMessageW (hwndTip
, TTM_RELAYEVENT
, 0, (LPARAM
)&msg
);
203 TAB_DumpItemExternalT(TCITEMW
*pti
, UINT iItem
, BOOL isW
)
206 TRACE("external tab %d, mask=0x%08x, dwState=0x%08lx, dwStateMask=0x%08lx, cchTextMax=0x%08x\n",
207 iItem
, pti
->mask
, pti
->dwState
, pti
->dwStateMask
, pti
->cchTextMax
);
208 TRACE("external tab %d, iImage=%d, lParam=0x%08lx, pszTextW=%s\n",
209 iItem
, pti
->iImage
, pti
->lParam
, isW
? debugstr_w(pti
->pszText
) : debugstr_a((LPSTR
)pti
->pszText
));
214 TAB_DumpItemInternal(TAB_INFO
*infoPtr
, UINT iItem
)
219 ti
= TAB_GetItem(infoPtr
, iItem
);
220 TRACE("tab %d, mask=0x%08x, dwState=0x%08lx, pszText=%s, iImage=%d\n",
221 iItem
, ti
->mask
, ti
->dwState
, debugstr_w(ti
->pszText
),
223 TRACE("tab %d, rect.left=%ld, rect.top(row)=%ld\n",
224 iItem
, ti
->rect
.left
, ti
->rect
.top
);
229 * the index of the selected tab, or -1 if no tab is selected. */
230 static inline LRESULT
TAB_GetCurSel (const TAB_INFO
*infoPtr
)
232 return infoPtr
->iSelected
;
236 * the index of the tab item that has the focus
238 * we have not to return negative value
240 * test for windows */
241 static inline LRESULT
242 TAB_GetCurFocus (const TAB_INFO
*infoPtr
)
244 if (infoPtr
->uFocus
<0)
246 FIXME("we have not to return negative value");
249 return infoPtr
->uFocus
;
252 static inline LRESULT
TAB_GetToolTips (const TAB_INFO
*infoPtr
)
254 if (infoPtr
== NULL
) return 0;
255 return (LRESULT
)infoPtr
->hwndToolTip
;
258 static inline LRESULT
TAB_SetCurSel (TAB_INFO
*infoPtr
, INT iItem
)
262 if (iItem
>= 0 && iItem
< infoPtr
->uNumItem
) {
263 prevItem
=infoPtr
->iSelected
;
264 if (infoPtr
->iSelected
!= iItem
) {
265 infoPtr
->iSelected
=iItem
;
266 TAB_EnsureSelectionVisible(infoPtr
);
267 TAB_InvalidateTabArea(infoPtr
);
273 static LRESULT
TAB_SetCurFocus (TAB_INFO
*infoPtr
, INT iItem
)
275 if (iItem
< 0 || iItem
>= infoPtr
->uNumItem
) return 0;
277 if (GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
) & TCS_BUTTONS
) {
278 FIXME("Should set input focus\n");
280 int oldFocus
= infoPtr
->uFocus
;
281 if (infoPtr
->iSelected
!= iItem
|| oldFocus
== -1 ) {
282 infoPtr
->uFocus
= iItem
;
283 if (oldFocus
!= -1) {
284 if (!TAB_SendSimpleNotify(infoPtr
, TCN_SELCHANGING
)) {
285 infoPtr
->iSelected
= iItem
;
286 TAB_SendSimpleNotify(infoPtr
, TCN_SELCHANGE
);
289 infoPtr
->iSelected
= iItem
;
290 TAB_EnsureSelectionVisible(infoPtr
);
291 TAB_InvalidateTabArea(infoPtr
);
298 static inline LRESULT
299 TAB_SetToolTips (TAB_INFO
*infoPtr
, HWND hwndToolTip
)
302 infoPtr
->hwndToolTip
= hwndToolTip
;
306 static inline LRESULT
307 TAB_SetPadding (TAB_INFO
*infoPtr
, LPARAM lParam
)
311 infoPtr
->uHItemPadding_s
=LOWORD(lParam
);
312 infoPtr
->uVItemPadding_s
=HIWORD(lParam
);
317 /******************************************************************************
318 * TAB_InternalGetItemRect
320 * This method will calculate the rectangle representing a given tab item in
321 * client coordinates. This method takes scrolling into account.
323 * This method returns TRUE if the item is visible in the window and FALSE
324 * if it is completely outside the client area.
326 static BOOL
TAB_InternalGetItemRect(
327 const TAB_INFO
* infoPtr
,
332 RECT tmpItemRect
,clientRect
;
333 LONG lStyle
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
335 /* Perform a sanity check and a trivial visibility check. */
336 if ( (infoPtr
->uNumItem
<= 0) ||
337 (itemIndex
>= infoPtr
->uNumItem
) ||
338 (!((lStyle
& TCS_MULTILINE
) || (lStyle
& TCS_VERTICAL
)) && (itemIndex
< infoPtr
->leftmostVisible
)) )
340 TRACE("Not Visible\n");
341 /* need to initialize these to empty rects */
344 memset(itemRect
,0,sizeof(RECT
));
345 itemRect
->bottom
= infoPtr
->tabHeight
;
348 memset(selectedRect
,0,sizeof(RECT
));
353 * Avoid special cases in this procedure by assigning the "out"
354 * parameters if the caller didn't supply them
356 if (itemRect
== NULL
)
357 itemRect
= &tmpItemRect
;
359 /* Retrieve the unmodified item rect. */
360 *itemRect
= TAB_GetItem(infoPtr
,itemIndex
)->rect
;
362 /* calculate the times bottom and top based on the row */
363 GetClientRect(infoPtr
->hwnd
, &clientRect
);
365 if ((lStyle
& TCS_BOTTOM
) && (lStyle
& TCS_VERTICAL
))
367 itemRect
->right
= clientRect
.right
- SELECTED_TAB_OFFSET
- itemRect
->left
* infoPtr
->tabHeight
-
368 ((lStyle
& TCS_BUTTONS
) ? itemRect
->left
* BUTTON_SPACINGX
: 0);
369 itemRect
->left
= itemRect
->right
- infoPtr
->tabHeight
;
371 else if (lStyle
& TCS_VERTICAL
)
373 itemRect
->left
= clientRect
.left
+ SELECTED_TAB_OFFSET
+ itemRect
->left
* infoPtr
->tabHeight
+
374 ((lStyle
& TCS_BUTTONS
) ? itemRect
->left
* BUTTON_SPACINGX
: 0);
375 itemRect
->right
= itemRect
->left
+ infoPtr
->tabHeight
;
377 else if (lStyle
& TCS_BOTTOM
)
379 itemRect
->bottom
= clientRect
.bottom
- itemRect
->top
* infoPtr
->tabHeight
-
380 ((lStyle
& TCS_BUTTONS
) ? itemRect
->top
* BUTTON_SPACINGY
: SELECTED_TAB_OFFSET
);
381 itemRect
->top
= itemRect
->bottom
- infoPtr
->tabHeight
;
383 else /* not TCS_BOTTOM and not TCS_VERTICAL */
385 itemRect
->top
= clientRect
.top
+ itemRect
->top
* infoPtr
->tabHeight
+
386 ((lStyle
& TCS_BUTTONS
) ? itemRect
->top
* BUTTON_SPACINGY
: SELECTED_TAB_OFFSET
);
387 itemRect
->bottom
= itemRect
->top
+ infoPtr
->tabHeight
;
391 * "scroll" it to make sure the item at the very left of the
392 * tab control is the leftmost visible tab.
394 if(lStyle
& TCS_VERTICAL
)
398 -TAB_GetItem(infoPtr
, infoPtr
->leftmostVisible
)->rect
.top
);
401 * Move the rectangle so the first item is slightly offset from
402 * the bottom of the tab control.
406 SELECTED_TAB_OFFSET
);
411 -TAB_GetItem(infoPtr
, infoPtr
->leftmostVisible
)->rect
.left
,
415 * Move the rectangle so the first item is slightly offset from
416 * the left of the tab control.
422 TRACE("item %d tab h=%d, rect=(%ld,%ld)-(%ld,%ld)\n",
423 itemIndex
, infoPtr
->tabHeight
,
424 itemRect
->left
, itemRect
->top
, itemRect
->right
, itemRect
->bottom
);
426 /* Now, calculate the position of the item as if it were selected. */
427 if (selectedRect
!=NULL
)
429 CopyRect(selectedRect
, itemRect
);
431 /* The rectangle of a selected item is a bit wider. */
432 if(lStyle
& TCS_VERTICAL
)
433 InflateRect(selectedRect
, 0, SELECTED_TAB_OFFSET
);
435 InflateRect(selectedRect
, SELECTED_TAB_OFFSET
, 0);
437 /* If it also a bit higher. */
438 if ((lStyle
& TCS_BOTTOM
) && (lStyle
& TCS_VERTICAL
))
440 selectedRect
->left
-= 2; /* the border is thicker on the right */
441 selectedRect
->right
+= SELECTED_TAB_OFFSET
;
443 else if (lStyle
& TCS_VERTICAL
)
445 selectedRect
->left
-= SELECTED_TAB_OFFSET
;
446 selectedRect
->right
+= 1;
448 else if (lStyle
& TCS_BOTTOM
)
450 selectedRect
->bottom
+= SELECTED_TAB_OFFSET
;
452 else /* not TCS_BOTTOM and not TCS_VERTICAL */
454 selectedRect
->top
-= SELECTED_TAB_OFFSET
;
455 selectedRect
->bottom
-= 1;
459 /* Check for visibility */
460 if (lStyle
& TCS_VERTICAL
)
461 return (itemRect
->top
< clientRect
.bottom
) && (itemRect
->bottom
> clientRect
.top
);
463 return (itemRect
->left
< clientRect
.right
) && (itemRect
->right
> clientRect
.left
);
467 TAB_GetItemRect(TAB_INFO
*infoPtr
, WPARAM wParam
, LPARAM lParam
)
469 return TAB_InternalGetItemRect(infoPtr
, (INT
)wParam
, (LPRECT
)lParam
, (LPRECT
)NULL
);
472 /******************************************************************************
475 * This method is called to handle keyboard input
477 static LRESULT
TAB_KeyUp(TAB_INFO
* infoPtr
, WPARAM keyCode
)
484 newItem
= infoPtr
->uFocus
- 1;
487 newItem
= infoPtr
->uFocus
+ 1;
492 * If we changed to a valid item, change the selection
495 newItem
< infoPtr
->uNumItem
&&
496 infoPtr
->uFocus
!= newItem
)
498 if (!TAB_SendSimpleNotify(infoPtr
, TCN_SELCHANGING
))
500 infoPtr
->iSelected
= newItem
;
501 infoPtr
->uFocus
= newItem
;
502 TAB_SendSimpleNotify(infoPtr
, TCN_SELCHANGE
);
504 TAB_EnsureSelectionVisible(infoPtr
);
505 TAB_InvalidateTabArea(infoPtr
);
512 /******************************************************************************
515 * This method is called whenever the focus goes in or out of this control
516 * it is used to update the visual state of the control.
518 static void TAB_FocusChanging(const TAB_INFO
*infoPtr
)
524 * Get the rectangle for the item.
526 isVisible
= TAB_InternalGetItemRect(infoPtr
,
532 * If the rectangle is not completely invisible, invalidate that
533 * portion of the window.
537 TRACE("invalidate (%ld,%ld)-(%ld,%ld)\n",
538 selectedRect
.left
,selectedRect
.top
,
539 selectedRect
.right
,selectedRect
.bottom
);
540 InvalidateRect(infoPtr
->hwnd
, &selectedRect
, TRUE
);
544 static INT
TAB_InternalHitTest (
553 for (iCount
= 0; iCount
< infoPtr
->uNumItem
; iCount
++)
555 TAB_InternalGetItemRect(infoPtr
, iCount
, &rect
, NULL
);
557 if (PtInRect(&rect
, pt
))
559 *flags
= TCHT_ONITEM
;
564 *flags
= TCHT_NOWHERE
;
568 static inline LRESULT
569 TAB_HitTest (TAB_INFO
*infoPtr
, LPTCHITTESTINFO lptest
)
571 return TAB_InternalHitTest (infoPtr
, lptest
->pt
, &lptest
->flags
);
574 /******************************************************************************
577 * Napster v2b5 has a tab control for its main navigation which has a client
578 * area that covers the whole area of the dialog pages.
579 * That's why it receives all msgs for that area and the underlying dialog ctrls
581 * So I decided that we should handle WM_NCHITTEST here and return
582 * HTTRANSPARENT if we don't hit the tab control buttons.
583 * FIXME: WM_NCHITTEST handling correct ? Fix it if you know that Windows
584 * doesn't do it that way. Maybe depends on tab control styles ?
586 static inline LRESULT
587 TAB_NCHitTest (TAB_INFO
*infoPtr
, LPARAM lParam
)
592 pt
.x
= LOWORD(lParam
);
593 pt
.y
= HIWORD(lParam
);
594 ScreenToClient(infoPtr
->hwnd
, &pt
);
596 if (TAB_InternalHitTest(infoPtr
, pt
, &dummyflag
) == -1)
597 return HTTRANSPARENT
;
603 TAB_LButtonDown (TAB_INFO
*infoPtr
, WPARAM wParam
, LPARAM lParam
)
609 if (infoPtr
->hwndToolTip
)
610 TAB_RelayEvent (infoPtr
->hwndToolTip
, infoPtr
->hwnd
,
611 WM_LBUTTONDOWN
, wParam
, lParam
);
613 if (GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
) & TCS_FOCUSONBUTTONDOWN
) {
614 SetFocus (infoPtr
->hwnd
);
617 if (infoPtr
->hwndToolTip
)
618 TAB_RelayEvent (infoPtr
->hwndToolTip
, infoPtr
->hwnd
,
619 WM_LBUTTONDOWN
, wParam
, lParam
);
621 pt
.x
= (INT
)LOWORD(lParam
);
622 pt
.y
= (INT
)HIWORD(lParam
);
624 newItem
= TAB_InternalHitTest (infoPtr
, pt
, &dummy
);
626 TRACE("On Tab, item %d\n", newItem
);
628 if (newItem
!= -1 && infoPtr
->iSelected
!= newItem
)
630 if (!TAB_SendSimpleNotify(infoPtr
, TCN_SELCHANGING
))
632 infoPtr
->iSelected
= newItem
;
633 infoPtr
->uFocus
= newItem
;
634 TAB_SendSimpleNotify(infoPtr
, TCN_SELCHANGE
);
636 TAB_EnsureSelectionVisible(infoPtr
);
638 TAB_InvalidateTabArea(infoPtr
);
644 static inline LRESULT
645 TAB_LButtonUp (const TAB_INFO
*infoPtr
)
647 TAB_SendSimpleNotify(infoPtr
, NM_CLICK
);
652 static inline LRESULT
653 TAB_RButtonDown (const TAB_INFO
*infoPtr
)
655 TAB_SendSimpleNotify(infoPtr
, NM_RCLICK
);
659 /******************************************************************************
660 * TAB_DrawLoneItemInterior
662 * This calls TAB_DrawItemInterior. However, TAB_DrawItemInterior is normally
663 * called by TAB_DrawItem which is normally called by TAB_Refresh which sets
664 * up the device context and font. This routine does the same setup but
665 * only calls TAB_DrawItemInterior for the single specified item.
668 TAB_DrawLoneItemInterior(TAB_INFO
* infoPtr
, int iItem
)
670 HDC hdc
= GetDC(infoPtr
->hwnd
);
673 /* Clip UpDown control to not draw over it */
674 if (infoPtr
->needsScrolling
)
676 GetWindowRect(infoPtr
->hwnd
, &rC
);
677 GetWindowRect(infoPtr
->hwndUpDown
, &r
);
678 ExcludeClipRect(hdc
, r
.left
- rC
.left
, r
.top
- rC
.top
, r
.right
- rC
.left
, r
.bottom
- rC
.top
);
680 TAB_DrawItemInterior(infoPtr
, hdc
, iItem
, NULL
);
681 ReleaseDC(infoPtr
->hwnd
, hdc
);
684 /* update a tab after hottracking - invalidate it or just redraw the interior,
685 * based on whether theming is used or not */
686 static inline void hottrack_refresh (TAB_INFO
* infoPtr
, int tabIndex
)
688 if (tabIndex
== -1) return;
690 if (GetWindowTheme (infoPtr
->hwnd
))
693 TAB_InternalGetItemRect(infoPtr
, tabIndex
, &rect
, NULL
);
694 InvalidateRect (infoPtr
->hwnd
, &rect
, FALSE
);
697 TAB_DrawLoneItemInterior(infoPtr
, tabIndex
);
700 /******************************************************************************
701 * TAB_HotTrackTimerProc
703 * When a mouse-move event causes a tab to be highlighted (hot-tracking), a
704 * timer is setup so we can check if the mouse is moved out of our window.
705 * (We don't get an event when the mouse leaves, the mouse-move events just
706 * stop being delivered to our window and just start being delivered to
707 * another window.) This function is called when the timer triggers so
708 * we can check if the mouse has left our window. If so, we un-highlight
709 * the hot-tracked tab.
712 TAB_HotTrackTimerProc
714 HWND hwnd
, /* handle of window for timer messages */
715 UINT uMsg
, /* WM_TIMER message */
716 UINT_PTR idEvent
, /* timer identifier */
717 DWORD dwTime
/* current system time */
720 TAB_INFO
* infoPtr
= TAB_GetInfoPtr(hwnd
);
722 if (infoPtr
!= NULL
&& infoPtr
->iHotTracked
>= 0)
727 ** If we can't get the cursor position, or if the cursor is outside our
728 ** window, we un-highlight the hot-tracked tab. Note that the cursor is
729 ** "outside" even if it is within our bounding rect if another window
730 ** overlaps. Note also that the case where the cursor stayed within our
731 ** window but has moved off the hot-tracked tab will be handled by the
732 ** WM_MOUSEMOVE event.
734 if (!GetCursorPos(&pt
) || WindowFromPoint(pt
) != hwnd
)
736 /* Redraw iHotTracked to look normal */
737 INT iRedraw
= infoPtr
->iHotTracked
;
738 infoPtr
->iHotTracked
= -1;
739 hottrack_refresh (infoPtr
, iRedraw
);
741 /* Kill this timer */
742 KillTimer(hwnd
, TAB_HOTTRACK_TIMER
);
747 /******************************************************************************
750 * If a tab control has the TCS_HOTTRACK style, then the tab under the mouse
751 * should be highlighted. This function determines which tab in a tab control,
752 * if any, is under the mouse and records that information. The caller may
753 * supply output parameters to receive the item number of the tab item which
754 * was highlighted but isn't any longer and of the tab item which is now
755 * highlighted but wasn't previously. The caller can use this information to
756 * selectively redraw those tab items.
758 * If the caller has a mouse position, it can supply it through the pos
759 * parameter. For example, TAB_MouseMove does this. Otherwise, the caller
760 * supplies NULL and this function determines the current mouse position
768 int* out_redrawLeave
,
775 if (out_redrawLeave
!= NULL
)
776 *out_redrawLeave
= -1;
777 if (out_redrawEnter
!= NULL
)
778 *out_redrawEnter
= -1;
780 if ((GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
) & TCS_HOTTRACK
)
781 || GetWindowTheme (infoPtr
->hwnd
))
789 ScreenToClient(infoPtr
->hwnd
, &pt
);
797 item
= TAB_InternalHitTest(infoPtr
, pt
, &flags
);
800 if (item
!= infoPtr
->iHotTracked
)
802 if (infoPtr
->iHotTracked
>= 0)
804 /* Mark currently hot-tracked to be redrawn to look normal */
805 if (out_redrawLeave
!= NULL
)
806 *out_redrawLeave
= infoPtr
->iHotTracked
;
810 /* Kill timer which forces recheck of mouse pos */
811 KillTimer(infoPtr
->hwnd
, TAB_HOTTRACK_TIMER
);
816 /* Start timer so we recheck mouse pos */
817 UINT timerID
= SetTimer
821 TAB_HOTTRACK_TIMER_INTERVAL
,
822 TAB_HotTrackTimerProc
826 return; /* Hot tracking not available */
829 infoPtr
->iHotTracked
= item
;
833 /* Mark new hot-tracked to be redrawn to look highlighted */
834 if (out_redrawEnter
!= NULL
)
835 *out_redrawEnter
= item
;
840 /******************************************************************************
843 * Handles the mouse-move event. Updates tooltips. Updates hot-tracking.
846 TAB_MouseMove (TAB_INFO
*infoPtr
, WPARAM wParam
, LPARAM lParam
)
851 if (infoPtr
->hwndToolTip
)
852 TAB_RelayEvent (infoPtr
->hwndToolTip
, infoPtr
->hwnd
,
853 WM_LBUTTONDOWN
, wParam
, lParam
);
855 /* Determine which tab to highlight. Redraw tabs which change highlight
857 TAB_RecalcHotTrack(infoPtr
, &lParam
, &redrawLeave
, &redrawEnter
);
859 hottrack_refresh (infoPtr
, redrawLeave
);
860 hottrack_refresh (infoPtr
, redrawEnter
);
865 /******************************************************************************
868 * Calculates the tab control's display area given the window rectangle or
869 * the window rectangle given the requested display rectangle.
871 static LRESULT
TAB_AdjustRect(
876 DWORD lStyle
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
877 LONG
*iRightBottom
, *iLeftTop
;
879 TRACE ("hwnd=%p fLarger=%d (%ld,%ld)-(%ld,%ld)\n", infoPtr
->hwnd
, fLarger
, prc
->left
, prc
->top
, prc
->right
, prc
->bottom
);
881 if(lStyle
& TCS_VERTICAL
)
883 iRightBottom
= &(prc
->right
);
884 iLeftTop
= &(prc
->left
);
888 iRightBottom
= &(prc
->bottom
);
889 iLeftTop
= &(prc
->top
);
892 if (fLarger
) /* Go from display rectangle */
894 /* Add the height of the tabs. */
895 if (lStyle
& TCS_BOTTOM
)
896 *iRightBottom
+= infoPtr
->tabHeight
* infoPtr
->uNumRows
;
898 *iLeftTop
-= infoPtr
->tabHeight
* infoPtr
->uNumRows
+
899 ((lStyle
& TCS_BUTTONS
)? 3 * (infoPtr
->uNumRows
- 1) : 0);
901 /* Inflate the rectangle for the padding */
902 InflateRect(prc
, DISPLAY_AREA_PADDINGX
, DISPLAY_AREA_PADDINGY
);
904 /* Inflate for the border */
905 InflateRect(prc
, CONTROL_BORDER_SIZEX
, CONTROL_BORDER_SIZEY
);
907 else /* Go from window rectangle. */
909 /* Deflate the rectangle for the border */
910 InflateRect(prc
, -CONTROL_BORDER_SIZEX
, -CONTROL_BORDER_SIZEY
);
912 /* Deflate the rectangle for the padding */
913 InflateRect(prc
, -DISPLAY_AREA_PADDINGX
, -DISPLAY_AREA_PADDINGY
);
915 /* Remove the height of the tabs. */
916 if (lStyle
& TCS_BOTTOM
)
917 *iRightBottom
-= infoPtr
->tabHeight
* infoPtr
->uNumRows
;
919 *iLeftTop
+= (infoPtr
->tabHeight
) * infoPtr
->uNumRows
+
920 ((lStyle
& TCS_BUTTONS
)? 3 * (infoPtr
->uNumRows
- 1) : 0);
926 /******************************************************************************
929 * This method will handle the notification from the scroll control and
930 * perform the scrolling operation on the tab control.
932 static LRESULT
TAB_OnHScroll(
938 if(nScrollCode
== SB_THUMBPOSITION
&& nPos
!= infoPtr
->leftmostVisible
)
940 if(nPos
< infoPtr
->leftmostVisible
)
941 infoPtr
->leftmostVisible
--;
943 infoPtr
->leftmostVisible
++;
945 TAB_RecalcHotTrack(infoPtr
, NULL
, NULL
, NULL
);
946 TAB_InvalidateTabArea(infoPtr
);
947 SendMessageW(infoPtr
->hwndUpDown
, UDM_SETPOS
, 0,
948 MAKELONG(infoPtr
->leftmostVisible
, 0));
954 /******************************************************************************
957 * This method will check the current scrolling state and make sure the
958 * scrolling control is displayed (or not).
960 static void TAB_SetupScrolling(
963 const RECT
* clientRect
)
965 static const WCHAR msctls_updown32W
[] = { 'm','s','c','t','l','s','_','u','p','d','o','w','n','3','2',0 };
966 static const WCHAR emptyW
[] = { 0 };
968 DWORD lStyle
= GetWindowLongW(hwnd
, GWL_STYLE
);
970 if (infoPtr
->needsScrolling
)
976 * Calculate the position of the scroll control.
978 if(lStyle
& TCS_VERTICAL
)
980 controlPos
.right
= clientRect
->right
;
981 controlPos
.left
= controlPos
.right
- 2 * GetSystemMetrics(SM_CXHSCROLL
);
983 if (lStyle
& TCS_BOTTOM
)
985 controlPos
.top
= clientRect
->bottom
- infoPtr
->tabHeight
;
986 controlPos
.bottom
= controlPos
.top
+ GetSystemMetrics(SM_CYHSCROLL
);
990 controlPos
.bottom
= clientRect
->top
+ infoPtr
->tabHeight
;
991 controlPos
.top
= controlPos
.bottom
- GetSystemMetrics(SM_CYHSCROLL
);
996 controlPos
.right
= clientRect
->right
;
997 controlPos
.left
= controlPos
.right
- 2 * GetSystemMetrics(SM_CXHSCROLL
);
999 if (lStyle
& TCS_BOTTOM
)
1001 controlPos
.top
= clientRect
->bottom
- infoPtr
->tabHeight
;
1002 controlPos
.bottom
= controlPos
.top
+ GetSystemMetrics(SM_CYHSCROLL
);
1006 controlPos
.bottom
= clientRect
->top
+ infoPtr
->tabHeight
;
1007 controlPos
.top
= controlPos
.bottom
- GetSystemMetrics(SM_CYHSCROLL
);
1012 * If we don't have a scroll control yet, we want to create one.
1013 * If we have one, we want to make sure it's positioned properly.
1015 if (infoPtr
->hwndUpDown
==0)
1017 infoPtr
->hwndUpDown
= CreateWindowW(msctls_updown32W
, emptyW
,
1018 WS_VISIBLE
| WS_CHILD
| UDS_HORZ
,
1019 controlPos
.left
, controlPos
.top
,
1020 controlPos
.right
- controlPos
.left
,
1021 controlPos
.bottom
- controlPos
.top
,
1022 hwnd
, NULL
, NULL
, NULL
);
1026 SetWindowPos(infoPtr
->hwndUpDown
,
1028 controlPos
.left
, controlPos
.top
,
1029 controlPos
.right
- controlPos
.left
,
1030 controlPos
.bottom
- controlPos
.top
,
1031 SWP_SHOWWINDOW
| SWP_NOZORDER
);
1034 /* Now calculate upper limit of the updown control range.
1035 * We do this by calculating how many tabs will be offscreen when the
1036 * last tab is visible.
1038 if(infoPtr
->uNumItem
)
1040 vsize
= clientRect
->right
- (controlPos
.right
- controlPos
.left
+ 1);
1041 maxRange
= infoPtr
->uNumItem
;
1042 tabwidth
= TAB_GetItem(infoPtr
, infoPtr
->uNumItem
- 1)->rect
.right
;
1044 for(; maxRange
> 0; maxRange
--)
1046 if(tabwidth
- TAB_GetItem(infoPtr
,maxRange
- 1)->rect
.left
> vsize
)
1050 if(maxRange
== infoPtr
->uNumItem
)
1056 /* If we once had a scroll control... hide it */
1057 if (infoPtr
->hwndUpDown
!=0)
1058 ShowWindow(infoPtr
->hwndUpDown
, SW_HIDE
);
1060 if (infoPtr
->hwndUpDown
)
1061 SendMessageW(infoPtr
->hwndUpDown
, UDM_SETRANGE32
, 0, maxRange
);
1064 /******************************************************************************
1067 * This method will calculate the position rectangles of all the items in the
1068 * control. The rectangle calculated starts at 0 for the first item in the
1069 * list and ignores scrolling and selection.
1070 * It also uses the current font to determine the height of the tab row and
1071 * it checks if all the tabs fit in the client area of the window. If they
1072 * don't, a scrolling control is added.
1074 static void TAB_SetItemBounds (TAB_INFO
*infoPtr
)
1076 LONG lStyle
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
1077 TEXTMETRICW fontMetrics
;
1080 INT curItemRowCount
;
1081 HFONT hFont
, hOldFont
;
1090 * We need to get text information so we need a DC and we need to select
1093 hdc
= GetDC(infoPtr
->hwnd
);
1095 hFont
= infoPtr
->hFont
? infoPtr
->hFont
: GetStockObject (SYSTEM_FONT
);
1096 hOldFont
= SelectObject (hdc
, hFont
);
1099 * We will base the rectangle calculations on the client rectangle
1102 GetClientRect(infoPtr
->hwnd
, &clientRect
);
1104 /* if TCS_VERTICAL then swap the height and width so this code places the
1105 tabs along the top of the rectangle and we can just rotate them after
1106 rather than duplicate all of the below code */
1107 if(lStyle
& TCS_VERTICAL
)
1109 iTemp
= clientRect
.bottom
;
1110 clientRect
.bottom
= clientRect
.right
;
1111 clientRect
.right
= iTemp
;
1114 /* Now use hPadding and vPadding */
1115 infoPtr
->uHItemPadding
= infoPtr
->uHItemPadding_s
;
1116 infoPtr
->uVItemPadding
= infoPtr
->uVItemPadding_s
;
1118 /* The leftmost item will be "0" aligned */
1120 curItemRowCount
= infoPtr
->uNumItem
? 1 : 0;
1122 if (!(infoPtr
->fHeightSet
))
1125 int icon_height
= 0;
1127 /* Use the current font to determine the height of a tab. */
1128 GetTextMetricsW(hdc
, &fontMetrics
);
1130 /* Get the icon height */
1132 ImageList_GetIconSize(infoPtr
->himl
, 0, &icon_height
);
1134 /* Take the highest between font or icon */
1135 if (fontMetrics
.tmHeight
> icon_height
)
1136 item_height
= fontMetrics
.tmHeight
+ 2;
1138 item_height
= icon_height
;
1141 * Make sure there is enough space for the letters + icon + growing the
1142 * selected item + extra space for the selected item.
1144 infoPtr
->tabHeight
= item_height
+
1145 ((lStyle
& TCS_BUTTONS
) ? 2 : 1) *
1146 infoPtr
->uVItemPadding
;
1148 TRACE("tabH=%d, tmH=%ld, iconh=%d\n",
1149 infoPtr
->tabHeight
, fontMetrics
.tmHeight
, icon_height
);
1152 TRACE("client right=%ld\n", clientRect
.right
);
1154 /* Get the icon width */
1157 ImageList_GetIconSize(infoPtr
->himl
, &icon_width
, 0);
1159 if (lStyle
& TCS_FIXEDWIDTH
)
1162 /* Add padding if icon is present */
1163 icon_width
+= infoPtr
->uHItemPadding
;
1166 for (curItem
= 0; curItem
< infoPtr
->uNumItem
; curItem
++)
1168 TAB_ITEM
*curr
= TAB_GetItem(infoPtr
, curItem
);
1170 /* Set the leftmost position of the tab. */
1171 curr
->rect
.left
= curItemLeftPos
;
1173 if (lStyle
& TCS_FIXEDWIDTH
)
1175 curr
->rect
.right
= curr
->rect
.left
+
1176 max(infoPtr
->tabWidth
, icon_width
);
1178 else if (!curr
->pszText
)
1180 /* If no text use minimum tab width including padding. */
1181 if (infoPtr
->tabMinWidth
< 0)
1182 curr
->rect
.right
= curr
->rect
.left
+ GET_DEFAULT_MIN_TAB_WIDTH(infoPtr
);
1185 curr
->rect
.right
= curr
->rect
.left
+ infoPtr
->tabMinWidth
;
1187 /* Add extra padding if icon is present */
1188 if (infoPtr
->himl
&& infoPtr
->tabMinWidth
> 0 && infoPtr
->tabMinWidth
< DEFAULT_MIN_TAB_WIDTH
1189 && infoPtr
->uHItemPadding
> 1)
1190 curr
->rect
.right
+= EXTRA_ICON_PADDING
* (infoPtr
->uHItemPadding
-1);
1197 /* Calculate how wide the tab is depending on the text it contains */
1198 GetTextExtentPoint32W(hdc
, curr
->pszText
,
1199 lstrlenW(curr
->pszText
), &size
);
1201 tabwidth
= size
.cx
+ icon_width
+ 2 * infoPtr
->uHItemPadding
;
1203 if (infoPtr
->tabMinWidth
< 0)
1204 tabwidth
= max(tabwidth
, GET_DEFAULT_MIN_TAB_WIDTH(infoPtr
));
1206 tabwidth
= max(tabwidth
, infoPtr
->tabMinWidth
);
1208 curr
->rect
.right
= curr
->rect
.left
+ tabwidth
;
1209 TRACE("for <%s>, l,r=%ld,%ld\n",
1210 debugstr_w(curr
->pszText
), curr
->rect
.left
, curr
->rect
.right
);
1214 * Check if this is a multiline tab control and if so
1215 * check to see if we should wrap the tabs
1217 * Wrap all these tabs. We will arrange them evenly later.
1221 if (((lStyle
& TCS_MULTILINE
) || (lStyle
& TCS_VERTICAL
)) &&
1223 (clientRect
.right
- CONTROL_BORDER_SIZEX
- DISPLAY_AREA_PADDINGX
)))
1225 curr
->rect
.right
-= curr
->rect
.left
;
1227 curr
->rect
.left
= 0;
1229 TRACE("wrapping <%s>, l,r=%ld,%ld\n", debugstr_w(curr
->pszText
),
1230 curr
->rect
.left
, curr
->rect
.right
);
1233 curr
->rect
.bottom
= 0;
1234 curr
->rect
.top
= curItemRowCount
- 1;
1236 TRACE("Rect: T %li, L %li, B %li, R %li\n", curr
->rect
.top
,
1237 curr
->rect
.left
, curr
->rect
.bottom
, curr
->rect
.right
);
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=%ld, cl=%d, cl.r=%ld, 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=%ld,%ld, row=%ld\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=%ld,%ld\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=%ld,%ld\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
1456 LONG lStyle
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
1457 HBRUSH hbr
= CreateSolidBrush (comctl32_color
.clrBtnFace
);
1458 BOOL deleteBrush
= TRUE
;
1459 RECT rTemp
= *drawRect
;
1461 InflateRect(&rTemp
, -2, -2);
1462 if (lStyle
& TCS_BUTTONS
)
1464 if (iItem
== infoPtr
->iSelected
)
1466 /* Background color */
1467 if (!(lStyle
& TCS_OWNERDRAWFIXED
))
1470 hbr
= GetSysColorBrush(COLOR_SCROLLBAR
);
1472 SetTextColor(hdc
, comctl32_color
.clr3dFace
);
1473 SetBkColor(hdc
, comctl32_color
.clr3dHilight
);
1475 /* if COLOR_WINDOW happens to be the same as COLOR_3DHILIGHT
1476 * we better use 0x55aa bitmap brush to make scrollbar's background
1477 * look different from the window background.
1479 if (comctl32_color
.clr3dHilight
== comctl32_color
.clrWindow
)
1480 hbr
= COMCTL32_hPattern55AABrush
;
1482 deleteBrush
= FALSE
;
1484 FillRect(hdc
, &rTemp
, hbr
);
1486 else /* ! selected */
1488 if (lStyle
& TCS_FLATBUTTONS
)
1490 FillRect(hdc
, drawRect
, hbr
);
1491 if (iItem
== infoPtr
->iHotTracked
)
1492 DrawEdge(hdc
, drawRect
, EDGE_RAISED
, BF_SOFT
|BF_RECT
);
1495 FillRect(hdc
, &rTemp
, hbr
);
1499 else /* !TCS_BUTTONS */
1501 if (!GetWindowTheme (infoPtr
->hwnd
))
1502 FillRect(hdc
, &rTemp
, hbr
);
1506 if (deleteBrush
) DeleteObject(hbr
);
1509 /******************************************************************************
1510 * TAB_DrawItemInterior
1512 * This method is used to draw the interior (text and icon) of a single tab
1513 * into the tab control.
1516 TAB_DrawItemInterior
1524 LONG lStyle
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
1533 /* if (drawRect == NULL) */
1540 * Get the rectangle for the item.
1542 isVisible
= TAB_InternalGetItemRect(infoPtr
, iItem
, &itemRect
, &selectedRect
);
1547 * Make sure drawRect points to something valid; simplifies code.
1549 drawRect
= &localRect
;
1552 * This logic copied from the part of TAB_DrawItem which draws
1553 * the tab background. It's important to keep it in sync. I
1554 * would have liked to avoid code duplication, but couldn't figure
1555 * out how without making spaghetti of TAB_DrawItem.
1557 if (iItem
== infoPtr
->iSelected
)
1558 *drawRect
= selectedRect
;
1560 *drawRect
= itemRect
;
1562 if (lStyle
& TCS_BUTTONS
)
1564 if (iItem
== infoPtr
->iSelected
)
1566 drawRect
->left
+= 4;
1568 drawRect
->right
-= 4;
1569 drawRect
->bottom
-= 1;
1573 drawRect
->left
+= 2;
1575 drawRect
->right
-= 2;
1576 drawRect
->bottom
-= 2;
1581 if ((lStyle
& TCS_VERTICAL
) && (lStyle
& TCS_BOTTOM
))
1583 if (iItem
!= infoPtr
->iSelected
)
1585 drawRect
->left
+= 2;
1587 drawRect
->bottom
-= 2;
1590 else if (lStyle
& TCS_VERTICAL
)
1592 if (iItem
== infoPtr
->iSelected
)
1594 drawRect
->right
+= 1;
1599 drawRect
->right
-= 2;
1600 drawRect
->bottom
-= 2;
1603 else if (lStyle
& TCS_BOTTOM
)
1605 if (iItem
== infoPtr
->iSelected
)
1611 InflateRect(drawRect
, -2, -2);
1612 drawRect
->bottom
+= 2;
1617 if (iItem
== infoPtr
->iSelected
)
1619 drawRect
->bottom
+= 3;
1623 drawRect
->bottom
-= 2;
1624 InflateRect(drawRect
, -2, 0);
1629 TRACE("drawRect=(%ld,%ld)-(%ld,%ld)\n",
1630 drawRect
->left
, drawRect
->top
, drawRect
->right
, drawRect
->bottom
);
1632 /* Clear interior */
1633 TAB_EraseTabInterior (infoPtr
, hdc
, iItem
, drawRect
);
1635 /* Draw the focus rectangle */
1636 if (!(lStyle
& TCS_FOCUSNEVER
) &&
1637 (GetFocus() == infoPtr
->hwnd
) &&
1638 (iItem
== infoPtr
->uFocus
) )
1640 RECT rFocus
= *drawRect
;
1641 InflateRect(&rFocus
, -3, -3);
1642 if (lStyle
& TCS_BOTTOM
&& !(lStyle
& TCS_VERTICAL
))
1644 if (lStyle
& TCS_BUTTONS
)
1650 DrawFocusRect(hdc
, &rFocus
);
1656 htextPen
= CreatePen( PS_SOLID
, 1, GetSysColor(COLOR_BTNTEXT
) );
1657 holdPen
= SelectObject(hdc
, htextPen
);
1658 hOldFont
= SelectObject(hdc
, infoPtr
->hFont
);
1661 * Setup for text output
1663 oldBkMode
= SetBkMode(hdc
, TRANSPARENT
);
1664 if (!GetWindowTheme (infoPtr
->hwnd
) || (lStyle
& TCS_BUTTONS
))
1665 SetTextColor(hdc
, (((lStyle
& TCS_HOTTRACK
) && (iItem
== infoPtr
->iHotTracked
)
1666 && !(lStyle
& TCS_FLATBUTTONS
))
1667 | (TAB_GetItem(infoPtr
, iItem
)->dwState
& TCIS_HIGHLIGHTED
)) ?
1668 comctl32_color
.clrHighlight
: comctl32_color
.clrBtnText
);
1671 * if owner draw, tell the owner to draw
1673 if ((lStyle
& TCS_OWNERDRAWFIXED
) && GetParent(infoPtr
->hwnd
))
1679 drawRect
->right
-= 1;
1680 if ( iItem
== infoPtr
->iSelected
)
1682 drawRect
->right
-= 1;
1683 drawRect
->left
+= 1;
1687 * get the control id
1689 id
= (UINT
)GetWindowLongPtrW( infoPtr
->hwnd
, GWLP_ID
);
1692 * put together the DRAWITEMSTRUCT
1694 dis
.CtlType
= ODT_TAB
;
1697 dis
.itemAction
= ODA_DRAWENTIRE
;
1699 if ( iItem
== infoPtr
->iSelected
)
1700 dis
.itemState
|= ODS_SELECTED
;
1701 if (infoPtr
->uFocus
== iItem
)
1702 dis
.itemState
|= ODS_FOCUS
;
1703 dis
.hwndItem
= infoPtr
->hwnd
;
1705 CopyRect(&dis
.rcItem
,drawRect
);
1706 dis
.itemData
= (ULONG_PTR
)TAB_GetItem(infoPtr
, iItem
)->extra
;
1709 * send the draw message
1711 SendMessageW( infoPtr
->hwndNotify
, WM_DRAWITEM
, (WPARAM
)id
, (LPARAM
)&dis
);
1715 TAB_ITEM
*item
= TAB_GetItem(infoPtr
, iItem
);
1719 /* used to center the icon and text in the tab */
1721 INT center_offset_h
, center_offset_v
;
1723 /* set rcImage to drawRect, we will use top & left in our ImageList_Draw call */
1724 rcImage
= *drawRect
;
1728 rcText
.left
= rcText
.top
= rcText
.right
= rcText
.bottom
= 0;
1730 /* get the rectangle that the text fits in */
1733 DrawTextW(hdc
, item
->pszText
, -1, &rcText
, DT_CALCRECT
);
1736 * If not owner draw, then do the drawing ourselves.
1740 if (infoPtr
->himl
&& (item
->mask
& TCIF_IMAGE
))
1745 ImageList_GetIconSize(infoPtr
->himl
, &cx
, &cy
);
1747 if(lStyle
& TCS_VERTICAL
)
1749 center_offset_h
= ((drawRect
->bottom
- drawRect
->top
) - (cy
+ infoPtr
->uHItemPadding
+ (rcText
.right
- rcText
.left
))) / 2;
1750 center_offset_v
= (drawRect
->left
+ (drawRect
->right
- drawRect
->left
) - cx
) / 2;
1754 center_offset_h
= ((drawRect
->right
- drawRect
->left
) - (cx
+ infoPtr
->uHItemPadding
+ (rcText
.right
- rcText
.left
))) / 2;
1755 center_offset_v
= (drawRect
->top
+ (drawRect
->bottom
- drawRect
->top
) - cy
) / 2;
1758 /* if an item is selected, the icon is shifted up instead of down */
1759 if (iItem
== infoPtr
->iSelected
)
1760 center_offset_v
-= infoPtr
->uVItemPadding
/ 2;
1762 center_offset_v
+= infoPtr
->uVItemPadding
/ 2;
1764 if (lStyle
& TCS_FIXEDWIDTH
&& lStyle
& (TCS_FORCELABELLEFT
| TCS_FORCEICONLEFT
))
1765 center_offset_h
= infoPtr
->uHItemPadding
;
1767 if (center_offset_h
< 2)
1768 center_offset_h
= 2;
1770 if (center_offset_v
< 0)
1771 center_offset_v
= 0;
1773 TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%ld,%ld)-(%ld,%ld), textlen=%ld\n",
1774 debugstr_w(item
->pszText
), center_offset_h
, center_offset_v
,
1775 drawRect
->left
, drawRect
->top
, drawRect
->right
, drawRect
->bottom
,
1776 (rcText
.right
-rcText
.left
));
1778 if((lStyle
& TCS_VERTICAL
) && (lStyle
& TCS_BOTTOM
))
1780 rcImage
.top
= drawRect
->top
+ center_offset_h
;
1781 /* if tab is TCS_VERTICAL and TCS_BOTTOM, the text is drawn from the */
1782 /* right side of the tab, but the image still uses the left as its x position */
1783 /* this keeps the image always drawn off of the same side of the tab */
1784 rcImage
.left
= drawRect
->right
- cx
- center_offset_v
;
1785 drawRect
->top
+= cy
+ infoPtr
->uHItemPadding
;
1787 else if(lStyle
& TCS_VERTICAL
)
1789 rcImage
.top
= drawRect
->bottom
- cy
- center_offset_h
;
1790 rcImage
.left
= drawRect
->left
+ center_offset_v
;
1791 drawRect
->bottom
-= cy
+ infoPtr
->uHItemPadding
;
1793 else /* normal style, whether TCS_BOTTOM or not */
1795 rcImage
.left
= drawRect
->left
+ center_offset_h
;
1796 rcImage
.top
= drawRect
->top
+ center_offset_v
;
1797 drawRect
->left
+= cx
+ infoPtr
->uHItemPadding
;
1800 TRACE("drawing image=%d, left=%ld, top=%ld\n",
1801 item
->iImage
, rcImage
.left
, rcImage
.top
-1);
1813 /* Now position text */
1814 if (lStyle
& TCS_FIXEDWIDTH
&& lStyle
& TCS_FORCELABELLEFT
)
1815 center_offset_h
= infoPtr
->uHItemPadding
;
1817 if(lStyle
& TCS_VERTICAL
)
1818 center_offset_h
= ((drawRect
->bottom
- drawRect
->top
) - (rcText
.right
- rcText
.left
)) / 2;
1820 center_offset_h
= ((drawRect
->right
- drawRect
->left
) - (rcText
.right
- rcText
.left
)) / 2;
1822 if(lStyle
& TCS_VERTICAL
)
1824 if(lStyle
& TCS_BOTTOM
)
1825 drawRect
->top
+=center_offset_h
;
1827 drawRect
->bottom
-=center_offset_h
;
1829 center_offset_v
= ((drawRect
->right
- drawRect
->left
) - (rcText
.bottom
- rcText
.top
)) / 2;
1833 drawRect
->left
+= center_offset_h
;
1834 center_offset_v
= ((drawRect
->bottom
- drawRect
->top
) - (rcText
.bottom
- rcText
.top
)) / 2;
1837 /* if an item is selected, the text is shifted up instead of down */
1838 if (iItem
== infoPtr
->iSelected
)
1839 center_offset_v
-= infoPtr
->uVItemPadding
/ 2;
1841 center_offset_v
+= infoPtr
->uVItemPadding
/ 2;
1843 if (center_offset_v
< 0)
1844 center_offset_v
= 0;
1846 if(lStyle
& TCS_VERTICAL
)
1847 drawRect
->left
+= center_offset_v
;
1849 drawRect
->top
+= center_offset_v
;
1852 if(lStyle
& TCS_VERTICAL
) /* if we are vertical rotate the text and each character */
1854 static const WCHAR ArialW
[] = { 'A','r','i','a','l',0 };
1857 INT nEscapement
= 900;
1858 INT nOrientation
= 900;
1860 if(lStyle
& TCS_BOTTOM
)
1863 nOrientation
= -900;
1866 /* to get a font with the escapement and orientation we are looking for, we need to */
1867 /* call CreateFontIndirectA, which requires us to set the values of the logfont we pass in */
1868 if (!GetObjectW((infoPtr
->hFont
) ?
1869 infoPtr
->hFont
: GetStockObject(SYSTEM_FONT
),
1870 sizeof(LOGFONTW
),&logfont
))
1874 lstrcpyW(logfont
.lfFaceName
, ArialW
);
1875 logfont
.lfHeight
= -MulDiv(iPointSize
, GetDeviceCaps(hdc
, LOGPIXELSY
),
1877 logfont
.lfWeight
= FW_NORMAL
;
1878 logfont
.lfItalic
= 0;
1879 logfont
.lfUnderline
= 0;
1880 logfont
.lfStrikeOut
= 0;
1883 logfont
.lfEscapement
= nEscapement
;
1884 logfont
.lfOrientation
= nOrientation
;
1885 hFont
= CreateFontIndirectW(&logfont
);
1886 SelectObject(hdc
, hFont
);
1891 (lStyle
& TCS_BOTTOM
) ? drawRect
->right
: drawRect
->left
,
1892 (!(lStyle
& TCS_BOTTOM
)) ? drawRect
->bottom
: drawRect
->top
,
1896 lstrlenW(item
->pszText
),
1900 DeleteObject(hFont
);
1904 TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%ld,%ld)-(%ld,%ld), textlen=%ld\n",
1905 debugstr_w(item
->pszText
), center_offset_h
, center_offset_v
,
1906 drawRect
->left
, drawRect
->top
, drawRect
->right
, drawRect
->bottom
,
1907 (rcText
.right
-rcText
.left
));
1914 lstrlenW(item
->pszText
),
1916 DT_LEFT
| DT_SINGLELINE
1921 *drawRect
= rcTemp
; /* restore drawRect */
1927 SelectObject(hdc
, hOldFont
);
1928 SetBkMode(hdc
, oldBkMode
);
1929 SelectObject(hdc
, holdPen
);
1930 DeleteObject( htextPen
);
1933 /******************************************************************************
1936 * This method is used to draw a single tab into the tab control.
1938 static void TAB_DrawItem(
1943 LONG lStyle
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
1947 RECT r
, fillRect
, r1
;
1950 COLORREF bkgnd
, corner
;
1954 * Get the rectangle for the item.
1956 isVisible
= TAB_InternalGetItemRect(infoPtr
,
1965 /* Clip UpDown control to not draw over it */
1966 if (infoPtr
->needsScrolling
)
1968 GetWindowRect(infoPtr
->hwnd
, &rC
);
1969 GetWindowRect(infoPtr
->hwndUpDown
, &rUD
);
1970 ExcludeClipRect(hdc
, rUD
.left
- rC
.left
, rUD
.top
- rC
.top
, rUD
.right
- rC
.left
, rUD
.bottom
- rC
.top
);
1973 /* If you need to see what the control is doing,
1974 * then override these variables. They will change what
1975 * fill colors are used for filling the tabs, and the
1976 * corners when drawing the edge.
1978 bkgnd
= comctl32_color
.clrBtnFace
;
1979 corner
= comctl32_color
.clrBtnFace
;
1981 if (lStyle
& TCS_BUTTONS
)
1983 /* Get item rectangle */
1986 /* Separators between flat buttons */
1987 if (lStyle
& TCS_FLATBUTTONS
)
1990 r1
.right
+= (FLAT_BTN_SPACINGX
-2);
1991 DrawEdge(hdc
, &r1
, EDGE_ETCHED
, BF_RIGHT
);
1994 if (iItem
== infoPtr
->iSelected
)
1996 DrawEdge(hdc
, &r
, EDGE_SUNKEN
, BF_SOFT
|BF_RECT
);
1998 OffsetRect(&r
, 1, 1);
2000 else /* ! selected */
2002 if (!(lStyle
& TCS_FLATBUTTONS
))
2003 DrawEdge(hdc
, &r
, EDGE_RAISED
, BF_SOFT
|BF_RECT
);
2006 else /* !TCS_BUTTONS */
2008 /* We draw a rectangle of different sizes depending on the selection
2010 if (iItem
== infoPtr
->iSelected
) {
2012 GetClientRect (infoPtr
->hwnd
, &rect
);
2013 clRight
= rect
.right
;
2014 clBottom
= rect
.bottom
;
2021 * Erase the background. (Delay it but setup rectangle.)
2022 * This is necessary when drawing the selected item since it is larger
2023 * than the others, it might overlap with stuff already drawn by the
2028 /* Draw themed tabs - but only if they are at the top.
2029 * Windows draws even side or bottom tabs themed, with wacky results.
2030 * However, since in Wine apps may get themed that did not opt in via
2031 * a manifest avoid theming when we know the result will be wrong */
2032 if ((theme
= GetWindowTheme (infoPtr
->hwnd
))
2033 && ((lStyle
& (TCS_VERTICAL
| TCS_BOTTOM
)) == 0))
2035 static const int partIds
[8] = {
2038 TABP_TABITEMLEFTEDGE
,
2039 TABP_TABITEMRIGHTEDGE
,
2040 TABP_TABITEMBOTHEDGE
,
2043 TABP_TOPTABITEMLEFTEDGE
,
2044 TABP_TOPTABITEMRIGHTEDGE
,
2045 TABP_TOPTABITEMBOTHEDGE
,
2048 int stateId
= TIS_NORMAL
;
2050 /* selected and unselected tabs have different parts */
2051 if (iItem
== infoPtr
->iSelected
)
2053 /* The part also differs on the position of a tab on a line.
2054 * "Visually" determining the position works well enough. */
2055 if(selectedRect
.left
== 0)
2057 if(selectedRect
.right
== clRight
)
2060 if (iItem
== infoPtr
->iSelected
)
2061 stateId
= TIS_SELECTED
;
2062 else if (iItem
== infoPtr
->iHotTracked
)
2064 else if (iItem
== infoPtr
->uFocus
)
2065 stateId
= TIS_FOCUSED
;
2067 /* Adjust rectangle for bottommost row */
2068 if (TAB_GetItem(infoPtr
, iItem
)->rect
.top
== infoPtr
->uNumRows
-1)
2071 DrawThemeBackground (theme
, hdc
, partIds
[partIndex
], stateId
, &r
, NULL
);
2072 GetThemeBackgroundContentRect (theme
, hdc
, partIds
[partIndex
], stateId
, &r
, &r
);
2074 else if(lStyle
& TCS_VERTICAL
)
2076 /* These are for adjusting the drawing of a Selected tab */
2077 /* The initial values are for the normal case of non-Selected */
2078 int ZZ
= 1; /* Do not strech if selected */
2079 if (iItem
== infoPtr
->iSelected
) {
2082 /* if leftmost draw the line longer */
2083 if(selectedRect
.top
== 0)
2084 fillRect
.top
+= CONTROL_BORDER_SIZEY
;
2085 /* if rightmost draw the line longer */
2086 if(selectedRect
.bottom
== clBottom
)
2087 fillRect
.bottom
-= CONTROL_BORDER_SIZEY
;
2090 if (lStyle
& TCS_BOTTOM
)
2092 /* Adjust both rectangles to match native */
2095 TRACE("<right> item=%d, fill=(%ld,%ld)-(%ld,%ld), edge=(%ld,%ld)-(%ld,%ld)\n",
2097 fillRect
.left
,fillRect
.top
,fillRect
.right
,fillRect
.bottom
,
2098 r
.left
,r
.top
,r
.right
,r
.bottom
);
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_RIGHT
|BF_TOP
|BF_BOTTOM
);
2107 /* Now erase the top corner and draw diagonal edge */
2108 SetBkColor(hdc
, corner
);
2109 r1
.left
= r
.right
- 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_ENDTOPLEFT
);
2117 /* Now erase the bottom corner and draw diagonal edge */
2118 r1
.left
= r
.right
- ROUND_CORNER_SIZE
- 1;
2119 r1
.bottom
= r
.bottom
;
2121 r1
.top
= r1
.bottom
- ROUND_CORNER_SIZE
;
2122 ExtTextOutW(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2124 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_DIAGONAL_ENDBOTTOMLEFT
);
2126 if ((iItem
== infoPtr
->iSelected
) && (selectedRect
.top
== 0)) {
2130 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_TOP
);
2136 TRACE("<left> item=%d, fill=(%ld,%ld)-(%ld,%ld), edge=(%ld,%ld)-(%ld,%ld)\n",
2138 fillRect
.left
,fillRect
.top
,fillRect
.right
,fillRect
.bottom
,
2139 r
.left
,r
.top
,r
.right
,r
.bottom
);
2141 /* Clear interior */
2142 SetBkColor(hdc
, bkgnd
);
2143 ExtTextOutW(hdc
, 0, 0, 2, &fillRect
, NULL
, 0, 0);
2145 /* Draw rectangular edge around tab */
2146 DrawEdge(hdc
, &r
, EDGE_RAISED
, BF_SOFT
|BF_LEFT
|BF_TOP
|BF_BOTTOM
);
2148 /* Now erase the top corner and draw diagonal edge */
2149 SetBkColor(hdc
, corner
);
2152 r1
.right
= r1
.left
+ ROUND_CORNER_SIZE
+ 1;
2153 r1
.bottom
= r1
.top
+ ROUND_CORNER_SIZE
;
2154 ExtTextOutW(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2156 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_DIAGONAL_ENDTOPRIGHT
);
2158 /* Now erase the bottom corner and draw diagonal edge */
2160 r1
.bottom
= r
.bottom
;
2161 r1
.right
= r1
.left
+ ROUND_CORNER_SIZE
+ 1;
2162 r1
.top
= r1
.bottom
- ROUND_CORNER_SIZE
;
2163 ExtTextOutW(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2165 DrawEdge(hdc
, &r1
, EDGE_SUNKEN
, BF_DIAGONAL_ENDTOPLEFT
);
2168 else /* ! TCS_VERTICAL */
2170 /* These are for adjusting the drawing of a Selected tab */
2171 /* The initial values are for the normal case of non-Selected */
2172 if (iItem
== infoPtr
->iSelected
) {
2173 /* if leftmost draw the line longer */
2174 if(selectedRect
.left
== 0)
2175 fillRect
.left
+= CONTROL_BORDER_SIZEX
;
2176 /* if rightmost draw the line longer */
2177 if(selectedRect
.right
== clRight
)
2178 fillRect
.right
-= CONTROL_BORDER_SIZEX
;
2181 if (lStyle
& TCS_BOTTOM
)
2183 /* Adjust both rectangles for topmost row */
2184 if (TAB_GetItem(infoPtr
, iItem
)->rect
.top
== infoPtr
->uNumRows
-1)
2190 TRACE("<bottom> item=%d, fill=(%ld,%ld)-(%ld,%ld), edge=(%ld,%ld)-(%ld,%ld)\n",
2192 fillRect
.left
,fillRect
.top
,fillRect
.right
,fillRect
.bottom
,
2193 r
.left
,r
.top
,r
.right
,r
.bottom
);
2195 /* Clear interior */
2196 SetBkColor(hdc
, bkgnd
);
2197 ExtTextOutW(hdc
, 0, 0, 2, &fillRect
, NULL
, 0, 0);
2199 /* Draw rectangular edge around tab */
2200 DrawEdge(hdc
, &r
, EDGE_RAISED
, BF_SOFT
|BF_LEFT
|BF_BOTTOM
|BF_RIGHT
);
2202 /* Now erase the righthand corner and draw diagonal edge */
2203 SetBkColor(hdc
, corner
);
2204 r1
.left
= r
.right
- ROUND_CORNER_SIZE
;
2205 r1
.bottom
= r
.bottom
;
2207 r1
.top
= r1
.bottom
- ROUND_CORNER_SIZE
- 1;
2208 ExtTextOutW(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2210 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_DIAGONAL_ENDBOTTOMLEFT
);
2212 /* Now erase the lefthand corner and draw diagonal edge */
2214 r1
.bottom
= r
.bottom
;
2215 r1
.right
= r1
.left
+ ROUND_CORNER_SIZE
;
2216 r1
.top
= r1
.bottom
- ROUND_CORNER_SIZE
- 1;
2217 ExtTextOutW(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2219 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_DIAGONAL_ENDTOPLEFT
);
2221 if (iItem
== infoPtr
->iSelected
)
2225 if (selectedRect
.left
== 0)
2230 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_LEFT
);
2237 /* Adjust both rectangles for bottommost row */
2238 if (TAB_GetItem(infoPtr
, iItem
)->rect
.top
== infoPtr
->uNumRows
-1)
2240 fillRect
.bottom
+= 3;
2244 TRACE("<top> item=%d, fill=(%ld,%ld)-(%ld,%ld), edge=(%ld,%ld)-(%ld,%ld)\n",
2246 fillRect
.left
,fillRect
.top
,fillRect
.right
,fillRect
.bottom
,
2247 r
.left
,r
.top
,r
.right
,r
.bottom
);
2249 /* Clear interior */
2250 SetBkColor(hdc
, bkgnd
);
2251 ExtTextOutW(hdc
, 0, 0, 2, &fillRect
, NULL
, 0, 0);
2253 /* Draw rectangular edge around tab */
2254 DrawEdge(hdc
, &r
, EDGE_RAISED
, BF_SOFT
|BF_LEFT
|BF_TOP
|BF_RIGHT
);
2256 /* Now erase the righthand corner and draw diagonal edge */
2257 SetBkColor(hdc
, corner
);
2258 r1
.left
= r
.right
- ROUND_CORNER_SIZE
;
2261 r1
.bottom
= r1
.top
+ ROUND_CORNER_SIZE
+ 1;
2262 ExtTextOutW(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2264 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_DIAGONAL_ENDBOTTOMRIGHT
);
2266 /* Now erase the lefthand corner and draw diagonal edge */
2269 r1
.right
= r1
.left
+ ROUND_CORNER_SIZE
;
2270 r1
.bottom
= r1
.top
+ ROUND_CORNER_SIZE
+ 1;
2271 ExtTextOutW(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2273 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_DIAGONAL_ENDTOPRIGHT
);
2278 TAB_DumpItemInternal(infoPtr
, iItem
);
2280 /* This modifies r to be the text rectangle. */
2281 TAB_DrawItemInterior(infoPtr
, hdc
, iItem
, &r
);
2285 /******************************************************************************
2288 * This method is used to draw the raised border around the tab control
2291 static void TAB_DrawBorder (TAB_INFO
*infoPtr
, HDC hdc
)
2294 DWORD lStyle
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
2295 HTHEME theme
= GetWindowTheme (infoPtr
->hwnd
);
2297 GetClientRect (infoPtr
->hwnd
, &rect
);
2300 * Adjust for the style
2303 if (infoPtr
->uNumItem
)
2305 if ((lStyle
& TCS_BOTTOM
) && !(lStyle
& TCS_VERTICAL
))
2306 rect
.bottom
-= infoPtr
->tabHeight
* infoPtr
->uNumRows
+ CONTROL_BORDER_SIZEX
;
2307 else if((lStyle
& TCS_BOTTOM
) && (lStyle
& TCS_VERTICAL
))
2308 rect
.right
-= infoPtr
->tabHeight
* infoPtr
->uNumRows
+ CONTROL_BORDER_SIZEX
;
2309 else if(lStyle
& TCS_VERTICAL
)
2310 rect
.left
+= infoPtr
->tabHeight
* infoPtr
->uNumRows
+ CONTROL_BORDER_SIZEX
;
2311 else /* not TCS_VERTICAL and not TCS_BOTTOM */
2312 rect
.top
+= infoPtr
->tabHeight
* infoPtr
->uNumRows
+ CONTROL_BORDER_SIZEX
;
2315 TRACE("border=(%ld,%ld)-(%ld,%ld)\n",
2316 rect
.left
, rect
.top
, rect
.right
, rect
.bottom
);
2319 DrawThemeBackground (theme
, hdc
, TABP_PANE
, 0, &rect
, NULL
);
2321 DrawEdge(hdc
, &rect
, EDGE_RAISED
, BF_SOFT
|BF_RECT
);
2324 /******************************************************************************
2327 * This method repaints the tab control..
2329 static void TAB_Refresh (TAB_INFO
*infoPtr
, HDC hdc
)
2334 if (!infoPtr
->DoRedraw
)
2337 hOldFont
= SelectObject (hdc
, infoPtr
->hFont
);
2339 if (GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
) & TCS_BUTTONS
)
2341 for (i
= 0; i
< infoPtr
->uNumItem
; i
++)
2342 TAB_DrawItem (infoPtr
, hdc
, i
);
2346 /* Draw all the non selected item first */
2347 for (i
= 0; i
< infoPtr
->uNumItem
; i
++)
2349 if (i
!= infoPtr
->iSelected
)
2350 TAB_DrawItem (infoPtr
, hdc
, i
);
2353 /* Now, draw the border, draw it before the selected item
2354 * since the selected item overwrites part of the border. */
2355 TAB_DrawBorder (infoPtr
, hdc
);
2357 /* Then, draw the selected item */
2358 TAB_DrawItem (infoPtr
, hdc
, infoPtr
->iSelected
);
2360 /* If we haven't set the current focus yet, set it now.
2361 * Only happens when we first paint the tab controls */
2362 if (infoPtr
->uFocus
== -1)
2363 TAB_SetCurFocus(infoPtr
, infoPtr
->iSelected
);
2366 SelectObject (hdc
, hOldFont
);
2369 static inline DWORD
TAB_GetRowCount (const TAB_INFO
*infoPtr
)
2371 return infoPtr
->uNumRows
;
2374 static inline LRESULT
TAB_SetRedraw (TAB_INFO
*infoPtr
, BOOL doRedraw
)
2376 infoPtr
->DoRedraw
= doRedraw
;
2380 /******************************************************************************
2381 * TAB_EnsureSelectionVisible
2383 * This method will make sure that the current selection is completely
2384 * visible by scrolling until it is.
2386 static void TAB_EnsureSelectionVisible(
2389 INT iSelected
= infoPtr
->iSelected
;
2390 LONG lStyle
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
2391 INT iOrigLeftmostVisible
= infoPtr
->leftmostVisible
;
2393 /* set the items row to the bottommost row or topmost row depending on
2395 if ((infoPtr
->uNumRows
> 1) && !(lStyle
& TCS_BUTTONS
))
2397 TAB_ITEM
*selected
= TAB_GetItem(infoPtr
, iSelected
);
2401 if(lStyle
& TCS_VERTICAL
)
2402 newselected
= selected
->rect
.left
;
2404 newselected
= selected
->rect
.top
;
2406 /* the target row is always (number of rows - 1)
2407 as row 0 is furthest from the clientRect */
2408 iTargetRow
= infoPtr
->uNumRows
- 1;
2410 if (newselected
!= iTargetRow
)
2413 if(lStyle
& TCS_VERTICAL
)
2415 for (i
=0; i
< infoPtr
->uNumItem
; i
++)
2417 /* move everything in the row of the selected item to the iTargetRow */
2418 TAB_ITEM
*item
= TAB_GetItem(infoPtr
, i
);
2420 if (item
->rect
.left
== newselected
)
2421 item
->rect
.left
= iTargetRow
;
2424 if (item
->rect
.left
> newselected
)
2431 for (i
=0; i
< infoPtr
->uNumItem
; i
++)
2433 TAB_ITEM
*item
= TAB_GetItem(infoPtr
, i
);
2435 if (item
->rect
.top
== newselected
)
2436 item
->rect
.top
= iTargetRow
;
2439 if (item
->rect
.top
> newselected
)
2444 TAB_RecalcHotTrack(infoPtr
, NULL
, NULL
, NULL
);
2449 * Do the trivial cases first.
2451 if ( (!infoPtr
->needsScrolling
) ||
2452 (infoPtr
->hwndUpDown
==0) || (lStyle
& TCS_VERTICAL
))
2455 if (infoPtr
->leftmostVisible
>= iSelected
)
2457 infoPtr
->leftmostVisible
= iSelected
;
2461 TAB_ITEM
*selected
= TAB_GetItem(infoPtr
, iSelected
);
2466 /* Calculate the part of the client area that is visible */
2467 GetClientRect(infoPtr
->hwnd
, &r
);
2470 GetClientRect(infoPtr
->hwndUpDown
, &r
);
2473 if ((selected
->rect
.right
-
2474 selected
->rect
.left
) >= width
)
2476 /* Special case: width of selected item is greater than visible
2479 infoPtr
->leftmostVisible
= iSelected
;
2483 for (i
= infoPtr
->leftmostVisible
; i
< infoPtr
->uNumItem
; i
++)
2485 if ((selected
->rect
.right
- TAB_GetItem(infoPtr
, i
)->rect
.left
) < width
)
2488 infoPtr
->leftmostVisible
= i
;
2492 if (infoPtr
->leftmostVisible
!= iOrigLeftmostVisible
)
2493 TAB_RecalcHotTrack(infoPtr
, NULL
, NULL
, NULL
);
2495 SendMessageW(infoPtr
->hwndUpDown
, UDM_SETPOS
, 0,
2496 MAKELONG(infoPtr
->leftmostVisible
, 0));
2499 /******************************************************************************
2500 * TAB_InvalidateTabArea
2502 * This method will invalidate the portion of the control that contains the
2503 * tabs. It is called when the state of the control changes and needs
2506 static void TAB_InvalidateTabArea(TAB_INFO
* infoPtr
)
2508 RECT clientRect
, rInvalidate
, rAdjClient
;
2509 DWORD lStyle
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
2510 INT lastRow
= infoPtr
->uNumRows
- 1;
2513 if (lastRow
< 0) return;
2515 GetClientRect(infoPtr
->hwnd
, &clientRect
);
2516 rInvalidate
= clientRect
;
2517 rAdjClient
= clientRect
;
2519 TAB_AdjustRect(infoPtr
, 0, &rAdjClient
);
2521 TAB_InternalGetItemRect(infoPtr
, infoPtr
->uNumItem
-1 , &rect
, NULL
);
2522 if ((lStyle
& TCS_BOTTOM
) && (lStyle
& TCS_VERTICAL
))
2524 rInvalidate
.left
= rAdjClient
.right
;
2525 if (infoPtr
->uNumRows
== 1)
2526 rInvalidate
.bottom
= clientRect
.top
+ rect
.bottom
+ 2 * SELECTED_TAB_OFFSET
;
2528 else if(lStyle
& TCS_VERTICAL
)
2530 rInvalidate
.right
= rAdjClient
.left
;
2531 if (infoPtr
->uNumRows
== 1)
2532 rInvalidate
.bottom
= clientRect
.top
+ rect
.bottom
+ 2 * SELECTED_TAB_OFFSET
;
2534 else if (lStyle
& TCS_BOTTOM
)
2536 rInvalidate
.top
= rAdjClient
.bottom
;
2537 if (infoPtr
->uNumRows
== 1)
2538 rInvalidate
.right
= clientRect
.left
+ rect
.right
+ 2 * SELECTED_TAB_OFFSET
;
2542 rInvalidate
.bottom
= rAdjClient
.top
;
2543 if (infoPtr
->uNumRows
== 1)
2544 rInvalidate
.right
= clientRect
.left
+ rect
.right
+ 2 * SELECTED_TAB_OFFSET
;
2547 /* Punch out the updown control */
2548 if (infoPtr
->needsScrolling
&& (rInvalidate
.right
> 0)) {
2550 GetClientRect(infoPtr
->hwndUpDown
, &r
);
2551 if (rInvalidate
.right
> clientRect
.right
- r
.left
)
2552 rInvalidate
.right
= rInvalidate
.right
- (r
.right
- r
.left
);
2554 rInvalidate
.right
= clientRect
.right
- r
.left
;
2557 TRACE("invalidate (%ld,%ld)-(%ld,%ld)\n",
2558 rInvalidate
.left
, rInvalidate
.top
,
2559 rInvalidate
.right
, rInvalidate
.bottom
);
2561 InvalidateRect(infoPtr
->hwnd
, &rInvalidate
, TRUE
);
2564 static inline LRESULT
TAB_Paint (TAB_INFO
*infoPtr
, HDC hdcPaint
)
2573 hdc
= BeginPaint (infoPtr
->hwnd
, &ps
);
2574 TRACE("erase %d, rect=(%ld,%ld)-(%ld,%ld)\n",
2576 ps
.rcPaint
.left
,ps
.rcPaint
.top
,ps
.rcPaint
.right
,ps
.rcPaint
.bottom
);
2579 TAB_Refresh (infoPtr
, hdc
);
2582 EndPaint (infoPtr
->hwnd
, &ps
);
2588 TAB_InsertItemT (TAB_INFO
*infoPtr
, WPARAM wParam
, LPARAM lParam
, BOOL bUnicode
)
2595 GetClientRect (infoPtr
->hwnd
, &rect
);
2596 TRACE("Rect: %p T %li, L %li, B %li, R %li\n", infoPtr
->hwnd
,
2597 rect
.top
, rect
.left
, rect
.bottom
, rect
.right
);
2599 pti
= (TCITEMW
*)lParam
;
2600 iItem
= (INT
)wParam
;
2602 if (iItem
< 0) return -1;
2603 if (iItem
> infoPtr
->uNumItem
)
2604 iItem
= infoPtr
->uNumItem
;
2606 TAB_DumpItemExternalT(pti
, iItem
, bUnicode
);
2609 if (infoPtr
->uNumItem
== 0) {
2610 infoPtr
->items
= Alloc (TAB_ITEM_SIZE(infoPtr
));
2611 infoPtr
->uNumItem
++;
2612 infoPtr
->iSelected
= 0;
2615 LPBYTE oldItems
= (LPBYTE
)infoPtr
->items
;
2617 infoPtr
->uNumItem
++;
2618 infoPtr
->items
= Alloc (TAB_ITEM_SIZE(infoPtr
) * infoPtr
->uNumItem
);
2620 /* pre insert copy */
2622 memcpy (infoPtr
->items
, oldItems
,
2623 iItem
* TAB_ITEM_SIZE(infoPtr
));
2626 /* post insert copy */
2627 if (iItem
< infoPtr
->uNumItem
- 1) {
2628 memcpy (TAB_GetItem(infoPtr
, iItem
+ 1),
2629 oldItems
+ iItem
* TAB_ITEM_SIZE(infoPtr
),
2630 (infoPtr
->uNumItem
- iItem
- 1) * TAB_ITEM_SIZE(infoPtr
));
2634 if (iItem
<= infoPtr
->iSelected
)
2635 infoPtr
->iSelected
++;
2640 item
= TAB_GetItem(infoPtr
, iItem
);
2642 item
->mask
= pti
->mask
;
2643 item
->pszText
= NULL
;
2645 if (pti
->mask
& TCIF_TEXT
)
2648 Str_SetPtrW (&item
->pszText
, pti
->pszText
);
2650 Str_SetPtrAtoW (&item
->pszText
, (LPSTR
)pti
->pszText
);
2653 if (pti
->mask
& TCIF_IMAGE
)
2654 item
->iImage
= pti
->iImage
;
2658 if (pti
->mask
& TCIF_PARAM
)
2659 memcpy(item
->extra
, &pti
->lParam
, infoPtr
->cbInfo
);
2661 memset(item
->extra
, 0, infoPtr
->cbInfo
);
2663 TAB_SetItemBounds(infoPtr
);
2664 if (infoPtr
->uNumItem
> 1)
2665 TAB_InvalidateTabArea(infoPtr
);
2667 InvalidateRect(infoPtr
->hwnd
, NULL
, TRUE
);
2669 TRACE("[%p]: added item %d %s\n",
2670 infoPtr
->hwnd
, iItem
, debugstr_w(item
->pszText
));
2676 TAB_SetItemSize (TAB_INFO
*infoPtr
, LPARAM lParam
)
2678 LONG lStyle
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
2680 BOOL bNeedPaint
= FALSE
;
2682 lResult
= MAKELONG(infoPtr
->tabWidth
, infoPtr
->tabHeight
);
2684 /* UNDOCUMENTED: If requested Width or Height is 0 this means that program wants to use auto size. */
2685 if (lStyle
& TCS_FIXEDWIDTH
&& (infoPtr
->tabWidth
!= (INT
)LOWORD(lParam
)))
2687 infoPtr
->tabWidth
= (INT
)LOWORD(lParam
);
2691 if (infoPtr
->tabHeight
!= (INT
)HIWORD(lParam
))
2693 if ((infoPtr
->fHeightSet
= ((INT
)HIWORD(lParam
) != 0)))
2694 infoPtr
->tabHeight
= (INT
)HIWORD(lParam
);
2698 TRACE("was h=%d,w=%d, now h=%d,w=%d\n",
2699 HIWORD(lResult
), LOWORD(lResult
),
2700 infoPtr
->tabHeight
, infoPtr
->tabWidth
);
2704 TAB_SetItemBounds(infoPtr
);
2705 RedrawWindow(infoPtr
->hwnd
, NULL
, NULL
, RDW_ERASE
| RDW_INVALIDATE
| RDW_UPDATENOW
);
2711 static inline LRESULT
TAB_SetMinTabWidth (TAB_INFO
*infoPtr
, INT cx
)
2715 TRACE("(%p,%d)\n", infoPtr
, cx
);
2718 oldcx
= infoPtr
->tabMinWidth
;
2719 infoPtr
->tabMinWidth
= cx
;
2721 TAB_SetItemBounds(infoPtr
);
2726 static inline LRESULT
2727 TAB_HighlightItem (TAB_INFO
*infoPtr
, INT iItem
, BOOL fHighlight
)
2731 TRACE("(%p,%d,%s)\n", infoPtr
, iItem
, fHighlight
? "true" : "false");
2733 if (!infoPtr
|| iItem
< 0 || iItem
>= infoPtr
->uNumItem
)
2736 lpState
= &TAB_GetItem(infoPtr
, iItem
)->dwState
;
2739 *lpState
|= TCIS_HIGHLIGHTED
;
2741 *lpState
&= ~TCIS_HIGHLIGHTED
;
2747 TAB_SetItemT (TAB_INFO
*infoPtr
, INT iItem
, LPTCITEMW tabItem
, BOOL bUnicode
)
2751 TRACE("(%p,%d,%p,%s)\n", infoPtr
, iItem
, tabItem
, bUnicode
? "true" : "false");
2753 if (iItem
< 0 || iItem
>= infoPtr
->uNumItem
)
2756 TAB_DumpItemExternalT(tabItem
, iItem
, bUnicode
);
2758 wineItem
= TAB_GetItem(infoPtr
, iItem
);
2760 if (tabItem
->mask
& TCIF_IMAGE
)
2761 wineItem
->iImage
= tabItem
->iImage
;
2763 if (tabItem
->mask
& TCIF_PARAM
)
2764 memcpy(wineItem
->extra
, &tabItem
->lParam
, infoPtr
->cbInfo
);
2766 if (tabItem
->mask
& TCIF_RTLREADING
)
2767 FIXME("TCIF_RTLREADING\n");
2769 if (tabItem
->mask
& TCIF_STATE
)
2770 wineItem
->dwState
= tabItem
->dwState
;
2772 if (tabItem
->mask
& TCIF_TEXT
)
2774 if (wineItem
->pszText
)
2776 Free(wineItem
->pszText
);
2777 wineItem
->pszText
= NULL
;
2780 Str_SetPtrW(&wineItem
->pszText
, tabItem
->pszText
);
2782 Str_SetPtrAtoW(&wineItem
->pszText
, (LPSTR
)tabItem
->pszText
);
2785 /* Update and repaint tabs */
2786 TAB_SetItemBounds(infoPtr
);
2787 TAB_InvalidateTabArea(infoPtr
);
2792 static inline LRESULT
TAB_GetItemCount (const TAB_INFO
*infoPtr
)
2794 return infoPtr
->uNumItem
;
2799 TAB_GetItemT (TAB_INFO
*infoPtr
, INT iItem
, LPTCITEMW tabItem
, BOOL bUnicode
)
2803 TRACE("(%p,%d,%p,%s)\n", infoPtr
, iItem
, tabItem
, bUnicode
? "true" : "false");
2805 if (iItem
< 0 || iItem
>= infoPtr
->uNumItem
)
2808 wineItem
= TAB_GetItem(infoPtr
, iItem
);
2810 if (tabItem
->mask
& TCIF_IMAGE
)
2811 tabItem
->iImage
= wineItem
->iImage
;
2813 if (tabItem
->mask
& TCIF_PARAM
)
2814 memcpy(&tabItem
->lParam
, wineItem
->extra
, infoPtr
->cbInfo
);
2816 if (tabItem
->mask
& TCIF_RTLREADING
)
2817 FIXME("TCIF_RTLREADING\n");
2819 if (tabItem
->mask
& TCIF_STATE
)
2820 tabItem
->dwState
= wineItem
->dwState
;
2822 if (tabItem
->mask
& TCIF_TEXT
)
2825 Str_GetPtrW (wineItem
->pszText
, tabItem
->pszText
, tabItem
->cchTextMax
);
2827 Str_GetPtrWtoA (wineItem
->pszText
, (LPSTR
)tabItem
->pszText
, tabItem
->cchTextMax
);
2830 TAB_DumpItemExternalT(tabItem
, iItem
, bUnicode
);
2836 static LRESULT
TAB_DeleteItem (TAB_INFO
*infoPtr
, INT iItem
)
2838 BOOL bResult
= FALSE
;
2840 TRACE("(%p, %d)\n", infoPtr
, iItem
);
2842 if ((iItem
>= 0) && (iItem
< infoPtr
->uNumItem
))
2844 TAB_ITEM
*item
= TAB_GetItem(infoPtr
, iItem
);
2845 LPBYTE oldItems
= (LPBYTE
)infoPtr
->items
;
2847 TAB_InvalidateTabArea(infoPtr
);
2849 if ((item
->mask
& TCIF_TEXT
) && item
->pszText
)
2850 Free(item
->pszText
);
2852 infoPtr
->uNumItem
--;
2854 if (!infoPtr
->uNumItem
)
2856 infoPtr
->items
= NULL
;
2857 if (infoPtr
->iHotTracked
>= 0)
2859 KillTimer(infoPtr
->hwnd
, TAB_HOTTRACK_TIMER
);
2860 infoPtr
->iHotTracked
= -1;
2865 infoPtr
->items
= Alloc(TAB_ITEM_SIZE(infoPtr
) * infoPtr
->uNumItem
);
2868 memcpy(infoPtr
->items
, oldItems
, iItem
* TAB_ITEM_SIZE(infoPtr
));
2870 if (iItem
< infoPtr
->uNumItem
)
2871 memcpy(TAB_GetItem(infoPtr
, iItem
),
2872 oldItems
+ (iItem
+ 1) * TAB_ITEM_SIZE(infoPtr
),
2873 (infoPtr
->uNumItem
- iItem
) * TAB_ITEM_SIZE(infoPtr
));
2875 if (iItem
<= infoPtr
->iHotTracked
)
2877 /* When tabs move left/up, the hot track item may change */
2878 FIXME("Recalc hot track");
2883 /* Readjust the selected index */
2884 if ((iItem
== infoPtr
->iSelected
) && (iItem
> 0))
2885 infoPtr
->iSelected
--;
2887 if (iItem
< infoPtr
->iSelected
)
2888 infoPtr
->iSelected
--;
2890 if (infoPtr
->uNumItem
== 0)
2891 infoPtr
->iSelected
= -1;
2893 /* Reposition and repaint tabs */
2894 TAB_SetItemBounds(infoPtr
);
2902 static inline LRESULT
TAB_DeleteAllItems (TAB_INFO
*infoPtr
)
2904 TRACE("(%p)\n", infoPtr
);
2905 while (infoPtr
->uNumItem
)
2906 TAB_DeleteItem (infoPtr
, 0);
2911 static inline LRESULT
TAB_GetFont (const TAB_INFO
*infoPtr
)
2913 TRACE("(%p) returning %p\n", infoPtr
, infoPtr
->hFont
);
2914 return (LRESULT
)infoPtr
->hFont
;
2917 static inline LRESULT
TAB_SetFont (TAB_INFO
*infoPtr
, HFONT hNewFont
)
2919 TRACE("(%p,%p)\n", infoPtr
, hNewFont
);
2921 infoPtr
->hFont
= hNewFont
;
2923 TAB_SetItemBounds(infoPtr
);
2925 TAB_InvalidateTabArea(infoPtr
);
2931 static inline LRESULT
TAB_GetImageList (const TAB_INFO
*infoPtr
)
2934 return (LRESULT
)infoPtr
->himl
;
2937 static inline LRESULT
TAB_SetImageList (TAB_INFO
*infoPtr
, HIMAGELIST himlNew
)
2939 HIMAGELIST himlPrev
= infoPtr
->himl
;
2941 infoPtr
->himl
= himlNew
;
2942 return (LRESULT
)himlPrev
;
2945 static inline LRESULT
TAB_GetUnicodeFormat (const TAB_INFO
*infoPtr
)
2947 return infoPtr
->bUnicode
;
2950 static inline LRESULT
TAB_SetUnicodeFormat (TAB_INFO
*infoPtr
, BOOL bUnicode
)
2952 BOOL bTemp
= infoPtr
->bUnicode
;
2954 infoPtr
->bUnicode
= bUnicode
;
2959 static inline LRESULT
TAB_Size (TAB_INFO
*infoPtr
)
2961 /* I'm not really sure what the following code was meant to do.
2962 This is what it is doing:
2963 When WM_SIZE is sent with SIZE_RESTORED, the control
2964 gets positioned in the top left corner.
2968 UINT uPosFlags,cx,cy;
2972 parent = GetParent (hwnd);
2973 GetClientRect(parent, &parent_rect);
2976 if (GetWindowLongW(hwnd, GWL_STYLE) & CCS_NORESIZE)
2977 uPosFlags |= (SWP_NOSIZE | SWP_NOMOVE);
2979 SetWindowPos (hwnd, 0, parent_rect.left, parent_rect.top,
2980 cx, cy, uPosFlags | SWP_NOZORDER);
2982 FIXME("WM_SIZE flag %x %lx not handled\n", wParam, lParam);
2985 /* Recompute the size/position of the tabs. */
2986 TAB_SetItemBounds (infoPtr
);
2988 /* Force a repaint of the control. */
2989 InvalidateRect(infoPtr
->hwnd
, NULL
, TRUE
);
2995 static LRESULT
TAB_Create (HWND hwnd
, WPARAM wParam
, LPARAM lParam
)
2998 TEXTMETRICW fontMetrics
;
3003 infoPtr
= (TAB_INFO
*)Alloc (sizeof(TAB_INFO
));
3005 SetWindowLongPtrW(hwnd
, 0, (DWORD_PTR
)infoPtr
);
3007 infoPtr
->hwnd
= hwnd
;
3008 infoPtr
->hwndNotify
= ((LPCREATESTRUCTW
)lParam
)->hwndParent
;
3009 infoPtr
->uNumItem
= 0;
3010 infoPtr
->uNumRows
= 0;
3011 infoPtr
->uHItemPadding
= 6;
3012 infoPtr
->uVItemPadding
= 3;
3013 infoPtr
->uHItemPadding_s
= 6;
3014 infoPtr
->uVItemPadding_s
= 3;
3017 infoPtr
->hcurArrow
= LoadCursorW (0, (LPWSTR
)IDC_ARROW
);
3018 infoPtr
->iSelected
= -1;
3019 infoPtr
->iHotTracked
= -1;
3020 infoPtr
->uFocus
= -1;
3021 infoPtr
->hwndToolTip
= 0;
3022 infoPtr
->DoRedraw
= TRUE
;
3023 infoPtr
->needsScrolling
= FALSE
;
3024 infoPtr
->hwndUpDown
= 0;
3025 infoPtr
->leftmostVisible
= 0;
3026 infoPtr
->fHeightSet
= FALSE
;
3027 infoPtr
->bUnicode
= IsWindowUnicode (hwnd
);
3028 infoPtr
->cbInfo
= sizeof(LPARAM
);
3030 TRACE("Created tab control, hwnd [%p]\n", hwnd
);
3032 /* The tab control always has the WS_CLIPSIBLINGS style. Even
3033 if you don't specify it in CreateWindow. This is necessary in
3034 order for paint to work correctly. This follows windows behaviour. */
3035 dwStyle
= GetWindowLongW(hwnd
, GWL_STYLE
);
3036 SetWindowLongW(hwnd
, GWL_STYLE
, dwStyle
|WS_CLIPSIBLINGS
);
3038 if (dwStyle
& TCS_TOOLTIPS
) {
3039 /* Create tooltip control */
3040 infoPtr
->hwndToolTip
=
3041 CreateWindowExW (0, TOOLTIPS_CLASSW
, NULL
, 0,
3042 CW_USEDEFAULT
, CW_USEDEFAULT
,
3043 CW_USEDEFAULT
, CW_USEDEFAULT
,
3046 /* Send NM_TOOLTIPSCREATED notification */
3047 if (infoPtr
->hwndToolTip
) {
3048 NMTOOLTIPSCREATED nmttc
;
3050 nmttc
.hdr
.hwndFrom
= hwnd
;
3051 nmttc
.hdr
.idFrom
= GetWindowLongPtrW(hwnd
, GWLP_ID
);
3052 nmttc
.hdr
.code
= NM_TOOLTIPSCREATED
;
3053 nmttc
.hwndToolTips
= infoPtr
->hwndToolTip
;
3055 SendMessageW (infoPtr
->hwndNotify
, WM_NOTIFY
,
3056 (WPARAM
)GetWindowLongPtrW(hwnd
, GWLP_ID
), (LPARAM
)&nmttc
);
3060 OpenThemeData (infoPtr
->hwnd
, themeClass
);
3063 * We need to get text information so we need a DC and we need to select
3067 hOldFont
= SelectObject (hdc
, GetStockObject (SYSTEM_FONT
));
3069 /* Use the system font to determine the initial height of a tab. */
3070 GetTextMetricsW(hdc
, &fontMetrics
);
3073 * Make sure there is enough space for the letters + growing the
3074 * selected item + extra space for the selected item.
3076 infoPtr
->tabHeight
= fontMetrics
.tmHeight
+ SELECTED_TAB_OFFSET
+
3077 ((dwStyle
& TCS_BUTTONS
) ? 2 : 1) *
3078 infoPtr
->uVItemPadding
;
3080 /* Initialize the width of a tab. */
3081 if (dwStyle
& TCS_FIXEDWIDTH
)
3082 infoPtr
->tabWidth
= DEFAULT_TAB_WIDTH_FIXED
;
3084 infoPtr
->tabMinWidth
= -1;
3086 TRACE("tabH=%d, tabW=%d\n", infoPtr
->tabHeight
, infoPtr
->tabWidth
);
3088 SelectObject (hdc
, hOldFont
);
3089 ReleaseDC(hwnd
, hdc
);
3095 TAB_Destroy (TAB_INFO
*infoPtr
)
3102 SetWindowLongPtrW(infoPtr
->hwnd
, 0, 0);
3104 if (infoPtr
->items
) {
3105 for (iItem
= 0; iItem
< infoPtr
->uNumItem
; iItem
++) {
3106 if (TAB_GetItem(infoPtr
, iItem
)->pszText
)
3107 Free (TAB_GetItem(infoPtr
, iItem
)->pszText
);
3109 Free (infoPtr
->items
);
3112 if (infoPtr
->hwndToolTip
)
3113 DestroyWindow (infoPtr
->hwndToolTip
);
3115 if (infoPtr
->hwndUpDown
)
3116 DestroyWindow(infoPtr
->hwndUpDown
);
3118 if (infoPtr
->iHotTracked
>= 0)
3119 KillTimer(infoPtr
->hwnd
, TAB_HOTTRACK_TIMER
);
3121 CloseThemeData (GetWindowTheme (infoPtr
->hwnd
));
3127 /* update theme after a WM_THEMECHANGED message */
3128 static LRESULT
theme_changed (TAB_INFO
* infoPtr
)
3130 HTHEME theme
= GetWindowTheme (infoPtr
->hwnd
);
3131 CloseThemeData (theme
);
3132 OpenThemeData (infoPtr
->hwnd
, themeClass
);
3136 static LRESULT
TAB_NCCalcSize(HWND hwnd
, WPARAM wParam
, LPARAM lParam
)
3140 return WVR_ALIGNTOP
;
3143 static inline LRESULT
3144 TAB_SetItemExtra (TAB_INFO
*infoPtr
, INT cbInfo
)
3146 if (!infoPtr
|| cbInfo
<= 0)
3149 if (infoPtr
->uNumItem
)
3151 /* FIXME: MSDN says this is not allowed, but this hasn't been verified */
3155 infoPtr
->cbInfo
= cbInfo
;
3159 static LRESULT WINAPI
3160 TAB_WindowProc (HWND hwnd
, UINT uMsg
, WPARAM wParam
, LPARAM lParam
)
3162 TAB_INFO
*infoPtr
= TAB_GetInfoPtr(hwnd
);
3164 TRACE("hwnd=%p msg=%x wParam=%x lParam=%lx\n", hwnd
, uMsg
, wParam
, lParam
);
3165 if (!infoPtr
&& (uMsg
!= WM_CREATE
))
3166 return DefWindowProcW (hwnd
, uMsg
, wParam
, lParam
);
3170 case TCM_GETIMAGELIST
:
3171 return TAB_GetImageList (infoPtr
);
3173 case TCM_SETIMAGELIST
:
3174 return TAB_SetImageList (infoPtr
, (HIMAGELIST
)lParam
);
3176 case TCM_GETITEMCOUNT
:
3177 return TAB_GetItemCount (infoPtr
);
3181 return TAB_GetItemT (infoPtr
, (INT
)wParam
, (LPTCITEMW
)lParam
, uMsg
== TCM_GETITEMW
);
3185 return TAB_SetItemT (infoPtr
, (INT
)wParam
, (LPTCITEMW
)lParam
, uMsg
== TCM_SETITEMW
);
3187 case TCM_DELETEITEM
:
3188 return TAB_DeleteItem (infoPtr
, (INT
)wParam
);
3190 case TCM_DELETEALLITEMS
:
3191 return TAB_DeleteAllItems (infoPtr
);
3193 case TCM_GETITEMRECT
:
3194 return TAB_GetItemRect (infoPtr
, wParam
, lParam
);
3197 return TAB_GetCurSel (infoPtr
);
3200 return TAB_HitTest (infoPtr
, (LPTCHITTESTINFO
)lParam
);
3203 return TAB_SetCurSel (infoPtr
, (INT
)wParam
);
3205 case TCM_INSERTITEMA
:
3206 case TCM_INSERTITEMW
:
3207 return TAB_InsertItemT (infoPtr
, wParam
, lParam
, uMsg
== TCM_INSERTITEMW
);
3209 case TCM_SETITEMEXTRA
:
3210 return TAB_SetItemExtra (infoPtr
, (int)wParam
);
3212 case TCM_ADJUSTRECT
:
3213 return TAB_AdjustRect (infoPtr
, (BOOL
)wParam
, (LPRECT
)lParam
);
3215 case TCM_SETITEMSIZE
:
3216 return TAB_SetItemSize (infoPtr
, lParam
);
3218 case TCM_REMOVEIMAGE
:
3219 FIXME("Unimplemented msg TCM_REMOVEIMAGE\n");
3222 case TCM_SETPADDING
:
3223 return TAB_SetPadding (infoPtr
, lParam
);
3225 case TCM_GETROWCOUNT
:
3226 return TAB_GetRowCount(infoPtr
);
3228 case TCM_GETUNICODEFORMAT
:
3229 return TAB_GetUnicodeFormat (infoPtr
);
3231 case TCM_SETUNICODEFORMAT
:
3232 return TAB_SetUnicodeFormat (infoPtr
, (BOOL
)wParam
);
3234 case TCM_HIGHLIGHTITEM
:
3235 return TAB_HighlightItem (infoPtr
, (INT
)wParam
, (BOOL
)LOWORD(lParam
));
3237 case TCM_GETTOOLTIPS
:
3238 return TAB_GetToolTips (infoPtr
);
3240 case TCM_SETTOOLTIPS
:
3241 return TAB_SetToolTips (infoPtr
, (HWND
)wParam
);
3243 case TCM_GETCURFOCUS
:
3244 return TAB_GetCurFocus (infoPtr
);
3246 case TCM_SETCURFOCUS
:
3247 return TAB_SetCurFocus (infoPtr
, (INT
)wParam
);
3249 case TCM_SETMINTABWIDTH
:
3250 return TAB_SetMinTabWidth(infoPtr
, (INT
)lParam
);
3252 case TCM_DESELECTALL
:
3253 FIXME("Unimplemented msg TCM_DESELECTALL\n");
3256 case TCM_GETEXTENDEDSTYLE
:
3257 FIXME("Unimplemented msg TCM_GETEXTENDEDSTYLE\n");
3260 case TCM_SETEXTENDEDSTYLE
:
3261 FIXME("Unimplemented msg TCM_SETEXTENDEDSTYLE\n");
3265 return TAB_GetFont (infoPtr
);
3268 return TAB_SetFont (infoPtr
, (HFONT
)wParam
);
3271 return TAB_Create (hwnd
, wParam
, lParam
);
3274 return TAB_Destroy (infoPtr
);
3277 return DLGC_WANTARROWS
| DLGC_WANTCHARS
;
3279 case WM_LBUTTONDOWN
:
3280 return TAB_LButtonDown (infoPtr
, wParam
, lParam
);
3283 return TAB_LButtonUp (infoPtr
);
3286 return SendMessageW(infoPtr
->hwndNotify
, WM_NOTIFY
, wParam
, lParam
);
3288 case WM_RBUTTONDOWN
:
3289 return TAB_RButtonDown (infoPtr
);
3292 return TAB_MouseMove (infoPtr
, wParam
, lParam
);
3294 case WM_PRINTCLIENT
:
3296 return TAB_Paint (infoPtr
, (HDC
)wParam
);
3299 return TAB_Size (infoPtr
);
3302 return TAB_SetRedraw (infoPtr
, (BOOL
)wParam
);
3305 return TAB_OnHScroll(infoPtr
, (int)LOWORD(wParam
), (int)HIWORD(wParam
), (HWND
)lParam
);
3307 case WM_STYLECHANGED
:
3308 TAB_SetItemBounds (infoPtr
);
3309 InvalidateRect(hwnd
, NULL
, TRUE
);
3312 case WM_SYSCOLORCHANGE
:
3313 COMCTL32_RefreshSysColors();
3316 case WM_THEMECHANGED
:
3317 return theme_changed (infoPtr
);
3321 TAB_FocusChanging(infoPtr
);
3322 break; /* Don't disturb normal focus behavior */
3325 return TAB_KeyUp(infoPtr
, wParam
);
3327 return TAB_NCHitTest(infoPtr
, lParam
);
3330 return TAB_NCCalcSize(hwnd
, wParam
, lParam
);
3333 if (uMsg
>= WM_USER
&& uMsg
< WM_APP
)
3334 WARN("unknown msg %04x wp=%08x lp=%08lx\n",
3335 uMsg
, wParam
, lParam
);
3338 return DefWindowProcW(hwnd
, uMsg
, wParam
, lParam
);
3347 ZeroMemory (&wndClass
, sizeof(WNDCLASSW
));
3348 wndClass
.style
= CS_GLOBALCLASS
| CS_DBLCLKS
| CS_HREDRAW
| CS_VREDRAW
;
3349 wndClass
.lpfnWndProc
= TAB_WindowProc
;
3350 wndClass
.cbClsExtra
= 0;
3351 wndClass
.cbWndExtra
= sizeof(TAB_INFO
*);
3352 wndClass
.hCursor
= LoadCursorW (0, (LPWSTR
)IDC_ARROW
);
3353 wndClass
.hbrBackground
= (HBRUSH
)(COLOR_BTNFACE
+1);
3354 wndClass
.lpszClassName
= WC_TABCONTROLW
;
3356 RegisterClassW (&wndClass
);
3361 TAB_Unregister (void)
3363 UnregisterClassW (WC_TABCONTROLW
, NULL
);