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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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 infoPtr
->iSelected
=iItem
;
265 TAB_EnsureSelectionVisible(infoPtr
);
266 TAB_InvalidateTabArea(infoPtr
);
271 static LRESULT
TAB_SetCurFocus (TAB_INFO
*infoPtr
, INT iItem
)
273 if (iItem
< 0 || iItem
>= infoPtr
->uNumItem
) return 0;
275 if (GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
) & TCS_BUTTONS
) {
276 FIXME("Should set input focus\n");
278 int oldFocus
= infoPtr
->uFocus
;
279 if (infoPtr
->iSelected
!= iItem
|| oldFocus
== -1 ) {
280 infoPtr
->uFocus
= iItem
;
281 if (oldFocus
!= -1) {
282 if (!TAB_SendSimpleNotify(infoPtr
, TCN_SELCHANGING
)) {
283 infoPtr
->iSelected
= iItem
;
284 TAB_SendSimpleNotify(infoPtr
, TCN_SELCHANGE
);
287 infoPtr
->iSelected
= iItem
;
288 TAB_EnsureSelectionVisible(infoPtr
);
289 TAB_InvalidateTabArea(infoPtr
);
296 static inline LRESULT
297 TAB_SetToolTips (TAB_INFO
*infoPtr
, HWND hwndToolTip
)
300 infoPtr
->hwndToolTip
= hwndToolTip
;
304 static inline LRESULT
305 TAB_SetPadding (TAB_INFO
*infoPtr
, LPARAM lParam
)
309 infoPtr
->uHItemPadding_s
=LOWORD(lParam
);
310 infoPtr
->uVItemPadding_s
=HIWORD(lParam
);
315 /******************************************************************************
316 * TAB_InternalGetItemRect
318 * This method will calculate the rectangle representing a given tab item in
319 * client coordinates. This method takes scrolling into account.
321 * This method returns TRUE if the item is visible in the window and FALSE
322 * if it is completely outside the client area.
324 static BOOL
TAB_InternalGetItemRect(
325 const TAB_INFO
* infoPtr
,
330 RECT tmpItemRect
,clientRect
;
331 LONG lStyle
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
333 /* Perform a sanity check and a trivial visibility check. */
334 if ( (infoPtr
->uNumItem
<= 0) ||
335 (itemIndex
>= infoPtr
->uNumItem
) ||
336 (!((lStyle
& TCS_MULTILINE
) || (lStyle
& TCS_VERTICAL
)) && (itemIndex
< infoPtr
->leftmostVisible
)) )
338 TRACE("Not Visible\n");
339 /* need to initialize these to empty rects */
342 memset(itemRect
,0,sizeof(RECT
));
343 itemRect
->bottom
= infoPtr
->tabHeight
;
346 memset(selectedRect
,0,sizeof(RECT
));
351 * Avoid special cases in this procedure by assigning the "out"
352 * parameters if the caller didn't supply them
354 if (itemRect
== NULL
)
355 itemRect
= &tmpItemRect
;
357 /* Retrieve the unmodified item rect. */
358 *itemRect
= TAB_GetItem(infoPtr
,itemIndex
)->rect
;
360 /* calculate the times bottom and top based on the row */
361 GetClientRect(infoPtr
->hwnd
, &clientRect
);
363 if ((lStyle
& TCS_BOTTOM
) && (lStyle
& TCS_VERTICAL
))
365 itemRect
->right
= clientRect
.right
- SELECTED_TAB_OFFSET
- itemRect
->left
* infoPtr
->tabHeight
-
366 ((lStyle
& TCS_BUTTONS
) ? itemRect
->left
* BUTTON_SPACINGX
: 0);
367 itemRect
->left
= itemRect
->right
- infoPtr
->tabHeight
;
369 else if (lStyle
& TCS_VERTICAL
)
371 itemRect
->left
= clientRect
.left
+ SELECTED_TAB_OFFSET
+ itemRect
->left
* infoPtr
->tabHeight
+
372 ((lStyle
& TCS_BUTTONS
) ? itemRect
->left
* BUTTON_SPACINGX
: 0);
373 itemRect
->right
= itemRect
->left
+ infoPtr
->tabHeight
;
375 else if (lStyle
& TCS_BOTTOM
)
377 itemRect
->bottom
= clientRect
.bottom
- itemRect
->top
* infoPtr
->tabHeight
-
378 ((lStyle
& TCS_BUTTONS
) ? itemRect
->top
* BUTTON_SPACINGY
: SELECTED_TAB_OFFSET
);
379 itemRect
->top
= itemRect
->bottom
- infoPtr
->tabHeight
;
381 else /* not TCS_BOTTOM and not TCS_VERTICAL */
383 itemRect
->top
= clientRect
.top
+ itemRect
->top
* infoPtr
->tabHeight
+
384 ((lStyle
& TCS_BUTTONS
) ? itemRect
->top
* BUTTON_SPACINGY
: SELECTED_TAB_OFFSET
);
385 itemRect
->bottom
= itemRect
->top
+ infoPtr
->tabHeight
;
389 * "scroll" it to make sure the item at the very left of the
390 * tab control is the leftmost visible tab.
392 if(lStyle
& TCS_VERTICAL
)
396 -TAB_GetItem(infoPtr
, infoPtr
->leftmostVisible
)->rect
.top
);
399 * Move the rectangle so the first item is slightly offset from
400 * the bottom of the tab control.
404 SELECTED_TAB_OFFSET
);
409 -TAB_GetItem(infoPtr
, infoPtr
->leftmostVisible
)->rect
.left
,
413 * Move the rectangle so the first item is slightly offset from
414 * the left of the tab control.
420 TRACE("item %d tab h=%d, rect=(%ld,%ld)-(%ld,%ld)\n",
421 itemIndex
, infoPtr
->tabHeight
,
422 itemRect
->left
, itemRect
->top
, itemRect
->right
, itemRect
->bottom
);
424 /* Now, calculate the position of the item as if it were selected. */
425 if (selectedRect
!=NULL
)
427 CopyRect(selectedRect
, itemRect
);
429 /* The rectangle of a selected item is a bit wider. */
430 if(lStyle
& TCS_VERTICAL
)
431 InflateRect(selectedRect
, 0, SELECTED_TAB_OFFSET
);
433 InflateRect(selectedRect
, SELECTED_TAB_OFFSET
, 0);
435 /* If it also a bit higher. */
436 if ((lStyle
& TCS_BOTTOM
) && (lStyle
& TCS_VERTICAL
))
438 selectedRect
->left
-= 2; /* the border is thicker on the right */
439 selectedRect
->right
+= SELECTED_TAB_OFFSET
;
441 else if (lStyle
& TCS_VERTICAL
)
443 selectedRect
->left
-= SELECTED_TAB_OFFSET
;
444 selectedRect
->right
+= 1;
446 else if (lStyle
& TCS_BOTTOM
)
448 selectedRect
->bottom
+= SELECTED_TAB_OFFSET
;
450 else /* not TCS_BOTTOM and not TCS_VERTICAL */
452 selectedRect
->top
-= SELECTED_TAB_OFFSET
;
453 selectedRect
->bottom
-= 1;
457 /* Check for visibility */
458 if (lStyle
& TCS_VERTICAL
)
459 return (itemRect
->top
< clientRect
.bottom
) && (itemRect
->bottom
> clientRect
.top
);
461 return (itemRect
->left
< clientRect
.right
) && (itemRect
->right
> clientRect
.left
);
465 TAB_GetItemRect(TAB_INFO
*infoPtr
, WPARAM wParam
, LPARAM lParam
)
467 return TAB_InternalGetItemRect(infoPtr
, (INT
)wParam
, (LPRECT
)lParam
, (LPRECT
)NULL
);
470 /******************************************************************************
473 * This method is called to handle keyboard input
475 static LRESULT
TAB_KeyUp(TAB_INFO
* infoPtr
, WPARAM keyCode
)
482 newItem
= infoPtr
->uFocus
- 1;
485 newItem
= infoPtr
->uFocus
+ 1;
490 * If we changed to a valid item, change the selection
493 newItem
< infoPtr
->uNumItem
&&
494 infoPtr
->uFocus
!= newItem
)
496 if (!TAB_SendSimpleNotify(infoPtr
, TCN_SELCHANGING
))
498 infoPtr
->iSelected
= newItem
;
499 infoPtr
->uFocus
= newItem
;
500 TAB_SendSimpleNotify(infoPtr
, TCN_SELCHANGE
);
502 TAB_EnsureSelectionVisible(infoPtr
);
503 TAB_InvalidateTabArea(infoPtr
);
510 /******************************************************************************
513 * This method is called whenever the focus goes in or out of this control
514 * it is used to update the visual state of the control.
516 static void TAB_FocusChanging(const TAB_INFO
*infoPtr
)
522 * Get the rectangle for the item.
524 isVisible
= TAB_InternalGetItemRect(infoPtr
,
530 * If the rectangle is not completely invisible, invalidate that
531 * portion of the window.
535 TRACE("invalidate (%ld,%ld)-(%ld,%ld)\n",
536 selectedRect
.left
,selectedRect
.top
,
537 selectedRect
.right
,selectedRect
.bottom
);
538 InvalidateRect(infoPtr
->hwnd
, &selectedRect
, TRUE
);
542 static INT
TAB_InternalHitTest (
551 for (iCount
= 0; iCount
< infoPtr
->uNumItem
; iCount
++)
553 TAB_InternalGetItemRect(infoPtr
, iCount
, &rect
, NULL
);
555 if (PtInRect(&rect
, pt
))
557 *flags
= TCHT_ONITEM
;
562 *flags
= TCHT_NOWHERE
;
566 static inline LRESULT
567 TAB_HitTest (TAB_INFO
*infoPtr
, LPTCHITTESTINFO lptest
)
569 return TAB_InternalHitTest (infoPtr
, lptest
->pt
, &lptest
->flags
);
572 /******************************************************************************
575 * Napster v2b5 has a tab control for its main navigation which has a client
576 * area that covers the whole area of the dialog pages.
577 * That's why it receives all msgs for that area and the underlying dialog ctrls
579 * So I decided that we should handle WM_NCHITTEST here and return
580 * HTTRANSPARENT if we don't hit the tab control buttons.
581 * FIXME: WM_NCHITTEST handling correct ? Fix it if you know that Windows
582 * doesn't do it that way. Maybe depends on tab control styles ?
584 static inline LRESULT
585 TAB_NCHitTest (TAB_INFO
*infoPtr
, LPARAM lParam
)
590 pt
.x
= LOWORD(lParam
);
591 pt
.y
= HIWORD(lParam
);
592 ScreenToClient(infoPtr
->hwnd
, &pt
);
594 if (TAB_InternalHitTest(infoPtr
, pt
, &dummyflag
) == -1)
595 return HTTRANSPARENT
;
601 TAB_LButtonDown (TAB_INFO
*infoPtr
, WPARAM wParam
, LPARAM lParam
)
607 if (infoPtr
->hwndToolTip
)
608 TAB_RelayEvent (infoPtr
->hwndToolTip
, infoPtr
->hwnd
,
609 WM_LBUTTONDOWN
, wParam
, lParam
);
611 if (GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
) & TCS_FOCUSONBUTTONDOWN
) {
612 SetFocus (infoPtr
->hwnd
);
615 if (infoPtr
->hwndToolTip
)
616 TAB_RelayEvent (infoPtr
->hwndToolTip
, infoPtr
->hwnd
,
617 WM_LBUTTONDOWN
, wParam
, lParam
);
619 pt
.x
= (INT
)LOWORD(lParam
);
620 pt
.y
= (INT
)HIWORD(lParam
);
622 newItem
= TAB_InternalHitTest (infoPtr
, pt
, &dummy
);
624 TRACE("On Tab, item %d\n", newItem
);
626 if (newItem
!= -1 && infoPtr
->iSelected
!= newItem
)
628 if (!TAB_SendSimpleNotify(infoPtr
, TCN_SELCHANGING
))
630 infoPtr
->iSelected
= newItem
;
631 infoPtr
->uFocus
= newItem
;
632 TAB_SendSimpleNotify(infoPtr
, TCN_SELCHANGE
);
634 TAB_EnsureSelectionVisible(infoPtr
);
636 TAB_InvalidateTabArea(infoPtr
);
642 static inline LRESULT
643 TAB_LButtonUp (const TAB_INFO
*infoPtr
)
645 TAB_SendSimpleNotify(infoPtr
, NM_CLICK
);
650 static inline LRESULT
651 TAB_RButtonDown (const TAB_INFO
*infoPtr
)
653 TAB_SendSimpleNotify(infoPtr
, NM_RCLICK
);
657 /******************************************************************************
658 * TAB_DrawLoneItemInterior
660 * This calls TAB_DrawItemInterior. However, TAB_DrawItemInterior is normally
661 * called by TAB_DrawItem which is normally called by TAB_Refresh which sets
662 * up the device context and font. This routine does the same setup but
663 * only calls TAB_DrawItemInterior for the single specified item.
666 TAB_DrawLoneItemInterior(TAB_INFO
* infoPtr
, int iItem
)
668 HDC hdc
= GetDC(infoPtr
->hwnd
);
671 /* Clip UpDown control to not draw over it */
672 if (infoPtr
->needsScrolling
)
674 GetWindowRect(infoPtr
->hwnd
, &rC
);
675 GetWindowRect(infoPtr
->hwndUpDown
, &r
);
676 ExcludeClipRect(hdc
, r
.left
- rC
.left
, r
.top
- rC
.top
, r
.right
- rC
.left
, r
.bottom
- rC
.top
);
678 TAB_DrawItemInterior(infoPtr
, hdc
, iItem
, NULL
);
679 ReleaseDC(infoPtr
->hwnd
, hdc
);
682 /* update a tab after hottracking - invalidate it or just redraw the interior,
683 * based on whether theming is used or not */
684 static inline void hottrack_refresh (TAB_INFO
* infoPtr
, int tabIndex
)
686 if (tabIndex
== -1) return;
688 if (GetWindowTheme (infoPtr
->hwnd
))
691 TAB_InternalGetItemRect(infoPtr
, tabIndex
, &rect
, NULL
);
692 InvalidateRect (infoPtr
->hwnd
, &rect
, FALSE
);
695 TAB_DrawLoneItemInterior(infoPtr
, tabIndex
);
698 /******************************************************************************
699 * TAB_HotTrackTimerProc
701 * When a mouse-move event causes a tab to be highlighted (hot-tracking), a
702 * timer is setup so we can check if the mouse is moved out of our window.
703 * (We don't get an event when the mouse leaves, the mouse-move events just
704 * stop being delivered to our window and just start being delivered to
705 * another window.) This function is called when the timer triggers so
706 * we can check if the mouse has left our window. If so, we un-highlight
707 * the hot-tracked tab.
710 TAB_HotTrackTimerProc
712 HWND hwnd
, /* handle of window for timer messages */
713 UINT uMsg
, /* WM_TIMER message */
714 UINT_PTR idEvent
, /* timer identifier */
715 DWORD dwTime
/* current system time */
718 TAB_INFO
* infoPtr
= TAB_GetInfoPtr(hwnd
);
720 if (infoPtr
!= NULL
&& infoPtr
->iHotTracked
>= 0)
725 ** If we can't get the cursor position, or if the cursor is outside our
726 ** window, we un-highlight the hot-tracked tab. Note that the cursor is
727 ** "outside" even if it is within our bounding rect if another window
728 ** overlaps. Note also that the case where the cursor stayed within our
729 ** window but has moved off the hot-tracked tab will be handled by the
730 ** WM_MOUSEMOVE event.
732 if (!GetCursorPos(&pt
) || WindowFromPoint(pt
) != hwnd
)
734 /* Redraw iHotTracked to look normal */
735 INT iRedraw
= infoPtr
->iHotTracked
;
736 infoPtr
->iHotTracked
= -1;
737 hottrack_refresh (infoPtr
, iRedraw
);
739 /* Kill this timer */
740 KillTimer(hwnd
, TAB_HOTTRACK_TIMER
);
745 /******************************************************************************
748 * If a tab control has the TCS_HOTTRACK style, then the tab under the mouse
749 * should be highlighted. This function determines which tab in a tab control,
750 * if any, is under the mouse and records that information. The caller may
751 * supply output parameters to receive the item number of the tab item which
752 * was highlighted but isn't any longer and of the tab item which is now
753 * highlighted but wasn't previously. The caller can use this information to
754 * selectively redraw those tab items.
756 * If the caller has a mouse position, it can supply it through the pos
757 * parameter. For example, TAB_MouseMove does this. Otherwise, the caller
758 * supplies NULL and this function determines the current mouse position
766 int* out_redrawLeave
,
773 if (out_redrawLeave
!= NULL
)
774 *out_redrawLeave
= -1;
775 if (out_redrawEnter
!= NULL
)
776 *out_redrawEnter
= -1;
778 if ((GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
) & TCS_HOTTRACK
)
779 || GetWindowTheme (infoPtr
->hwnd
))
787 ScreenToClient(infoPtr
->hwnd
, &pt
);
795 item
= TAB_InternalHitTest(infoPtr
, pt
, &flags
);
798 if (item
!= infoPtr
->iHotTracked
)
800 if (infoPtr
->iHotTracked
>= 0)
802 /* Mark currently hot-tracked to be redrawn to look normal */
803 if (out_redrawLeave
!= NULL
)
804 *out_redrawLeave
= infoPtr
->iHotTracked
;
808 /* Kill timer which forces recheck of mouse pos */
809 KillTimer(infoPtr
->hwnd
, TAB_HOTTRACK_TIMER
);
814 /* Start timer so we recheck mouse pos */
815 UINT timerID
= SetTimer
819 TAB_HOTTRACK_TIMER_INTERVAL
,
820 TAB_HotTrackTimerProc
824 return; /* Hot tracking not available */
827 infoPtr
->iHotTracked
= item
;
831 /* Mark new hot-tracked to be redrawn to look highlighted */
832 if (out_redrawEnter
!= NULL
)
833 *out_redrawEnter
= item
;
838 /******************************************************************************
841 * Handles the mouse-move event. Updates tooltips. Updates hot-tracking.
844 TAB_MouseMove (TAB_INFO
*infoPtr
, WPARAM wParam
, LPARAM lParam
)
849 if (infoPtr
->hwndToolTip
)
850 TAB_RelayEvent (infoPtr
->hwndToolTip
, infoPtr
->hwnd
,
851 WM_LBUTTONDOWN
, wParam
, lParam
);
853 /* Determine which tab to highlight. Redraw tabs which change highlight
855 TAB_RecalcHotTrack(infoPtr
, &lParam
, &redrawLeave
, &redrawEnter
);
857 hottrack_refresh (infoPtr
, redrawLeave
);
858 hottrack_refresh (infoPtr
, redrawEnter
);
863 /******************************************************************************
866 * Calculates the tab control's display area given the window rectangle or
867 * the window rectangle given the requested display rectangle.
869 static LRESULT
TAB_AdjustRect(
874 DWORD lStyle
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
875 LONG
*iRightBottom
, *iLeftTop
;
877 TRACE ("hwnd=%p fLarger=%d (%ld,%ld)-(%ld,%ld)\n", infoPtr
->hwnd
, fLarger
, prc
->left
, prc
->top
, prc
->right
, prc
->bottom
);
879 if(lStyle
& TCS_VERTICAL
)
881 iRightBottom
= &(prc
->right
);
882 iLeftTop
= &(prc
->left
);
886 iRightBottom
= &(prc
->bottom
);
887 iLeftTop
= &(prc
->top
);
890 if (fLarger
) /* Go from display rectangle */
892 /* Add the height of the tabs. */
893 if (lStyle
& TCS_BOTTOM
)
894 *iRightBottom
+= infoPtr
->tabHeight
* infoPtr
->uNumRows
;
896 *iLeftTop
-= infoPtr
->tabHeight
* infoPtr
->uNumRows
+
897 ((lStyle
& TCS_BUTTONS
)? 3 * (infoPtr
->uNumRows
- 1) : 0);
899 /* Inflate the rectangle for the padding */
900 InflateRect(prc
, DISPLAY_AREA_PADDINGX
, DISPLAY_AREA_PADDINGY
);
902 /* Inflate for the border */
903 InflateRect(prc
, CONTROL_BORDER_SIZEX
, CONTROL_BORDER_SIZEY
);
905 else /* Go from window rectangle. */
907 /* Deflate the rectangle for the border */
908 InflateRect(prc
, -CONTROL_BORDER_SIZEX
, -CONTROL_BORDER_SIZEY
);
910 /* Deflate the rectangle for the padding */
911 InflateRect(prc
, -DISPLAY_AREA_PADDINGX
, -DISPLAY_AREA_PADDINGY
);
913 /* Remove the height of the tabs. */
914 if (lStyle
& TCS_BOTTOM
)
915 *iRightBottom
-= infoPtr
->tabHeight
* infoPtr
->uNumRows
;
917 *iLeftTop
+= (infoPtr
->tabHeight
) * infoPtr
->uNumRows
+
918 ((lStyle
& TCS_BUTTONS
)? 3 * (infoPtr
->uNumRows
- 1) : 0);
924 /******************************************************************************
927 * This method will handle the notification from the scroll control and
928 * perform the scrolling operation on the tab control.
930 static LRESULT
TAB_OnHScroll(
936 if(nScrollCode
== SB_THUMBPOSITION
&& nPos
!= infoPtr
->leftmostVisible
)
938 if(nPos
< infoPtr
->leftmostVisible
)
939 infoPtr
->leftmostVisible
--;
941 infoPtr
->leftmostVisible
++;
943 TAB_RecalcHotTrack(infoPtr
, NULL
, NULL
, NULL
);
944 TAB_InvalidateTabArea(infoPtr
);
945 SendMessageW(infoPtr
->hwndUpDown
, UDM_SETPOS
, 0,
946 MAKELONG(infoPtr
->leftmostVisible
, 0));
952 /******************************************************************************
955 * This method will check the current scrolling state and make sure the
956 * scrolling control is displayed (or not).
958 static void TAB_SetupScrolling(
961 const RECT
* clientRect
)
963 static const WCHAR msctls_updown32W
[] = { 'm','s','c','t','l','s','_','u','p','d','o','w','n','3','2',0 };
964 static const WCHAR emptyW
[] = { 0 };
966 DWORD lStyle
= GetWindowLongW(hwnd
, GWL_STYLE
);
968 if (infoPtr
->needsScrolling
)
974 * Calculate the position of the scroll control.
976 if(lStyle
& TCS_VERTICAL
)
978 controlPos
.right
= clientRect
->right
;
979 controlPos
.left
= controlPos
.right
- 2 * GetSystemMetrics(SM_CXHSCROLL
);
981 if (lStyle
& TCS_BOTTOM
)
983 controlPos
.top
= clientRect
->bottom
- infoPtr
->tabHeight
;
984 controlPos
.bottom
= controlPos
.top
+ GetSystemMetrics(SM_CYHSCROLL
);
988 controlPos
.bottom
= clientRect
->top
+ infoPtr
->tabHeight
;
989 controlPos
.top
= controlPos
.bottom
- GetSystemMetrics(SM_CYHSCROLL
);
994 controlPos
.right
= clientRect
->right
;
995 controlPos
.left
= controlPos
.right
- 2 * GetSystemMetrics(SM_CXHSCROLL
);
997 if (lStyle
& TCS_BOTTOM
)
999 controlPos
.top
= clientRect
->bottom
- infoPtr
->tabHeight
;
1000 controlPos
.bottom
= controlPos
.top
+ GetSystemMetrics(SM_CYHSCROLL
);
1004 controlPos
.bottom
= clientRect
->top
+ infoPtr
->tabHeight
;
1005 controlPos
.top
= controlPos
.bottom
- GetSystemMetrics(SM_CYHSCROLL
);
1010 * If we don't have a scroll control yet, we want to create one.
1011 * If we have one, we want to make sure it's positioned properly.
1013 if (infoPtr
->hwndUpDown
==0)
1015 infoPtr
->hwndUpDown
= CreateWindowW(msctls_updown32W
, emptyW
,
1016 WS_VISIBLE
| WS_CHILD
| UDS_HORZ
,
1017 controlPos
.left
, controlPos
.top
,
1018 controlPos
.right
- controlPos
.left
,
1019 controlPos
.bottom
- controlPos
.top
,
1020 hwnd
, NULL
, NULL
, NULL
);
1024 SetWindowPos(infoPtr
->hwndUpDown
,
1026 controlPos
.left
, controlPos
.top
,
1027 controlPos
.right
- controlPos
.left
,
1028 controlPos
.bottom
- controlPos
.top
,
1029 SWP_SHOWWINDOW
| SWP_NOZORDER
);
1032 /* Now calculate upper limit of the updown control range.
1033 * We do this by calculating how many tabs will be offscreen when the
1034 * last tab is visible.
1036 if(infoPtr
->uNumItem
)
1038 vsize
= clientRect
->right
- (controlPos
.right
- controlPos
.left
+ 1);
1039 maxRange
= infoPtr
->uNumItem
;
1040 tabwidth
= TAB_GetItem(infoPtr
, infoPtr
->uNumItem
- 1)->rect
.right
;
1042 for(; maxRange
> 0; maxRange
--)
1044 if(tabwidth
- TAB_GetItem(infoPtr
,maxRange
- 1)->rect
.left
> vsize
)
1048 if(maxRange
== infoPtr
->uNumItem
)
1054 /* If we once had a scroll control... hide it */
1055 if (infoPtr
->hwndUpDown
!=0)
1056 ShowWindow(infoPtr
->hwndUpDown
, SW_HIDE
);
1058 if (infoPtr
->hwndUpDown
)
1059 SendMessageW(infoPtr
->hwndUpDown
, UDM_SETRANGE32
, 0, maxRange
);
1062 /******************************************************************************
1065 * This method will calculate the position rectangles of all the items in the
1066 * control. The rectangle calculated starts at 0 for the first item in the
1067 * list and ignores scrolling and selection.
1068 * It also uses the current font to determine the height of the tab row and
1069 * it checks if all the tabs fit in the client area of the window. If they
1070 * don't, a scrolling control is added.
1072 static void TAB_SetItemBounds (TAB_INFO
*infoPtr
)
1074 LONG lStyle
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
1075 TEXTMETRICW fontMetrics
;
1078 INT curItemRowCount
;
1079 HFONT hFont
, hOldFont
;
1089 * We need to get text information so we need a DC and we need to select
1092 hdc
= GetDC(infoPtr
->hwnd
);
1094 hFont
= infoPtr
->hFont
? infoPtr
->hFont
: GetStockObject (SYSTEM_FONT
);
1095 hOldFont
= SelectObject (hdc
, hFont
);
1098 * We will base the rectangle calculations on the client rectangle
1101 GetClientRect(infoPtr
->hwnd
, &clientRect
);
1103 /* if TCS_VERTICAL then swap the height and width so this code places the
1104 tabs along the top of the rectangle and we can just rotate them after
1105 rather than duplicate all of the below code */
1106 if(lStyle
& TCS_VERTICAL
)
1108 iTemp
= clientRect
.bottom
;
1109 clientRect
.bottom
= clientRect
.right
;
1110 clientRect
.right
= iTemp
;
1113 /* Now use hPadding and vPadding */
1114 infoPtr
->uHItemPadding
= infoPtr
->uHItemPadding_s
;
1115 infoPtr
->uVItemPadding
= infoPtr
->uVItemPadding_s
;
1117 /* The leftmost item will be "0" aligned */
1119 curItemRowCount
= infoPtr
->uNumItem
? 1 : 0;
1121 if (!(infoPtr
->fHeightSet
))
1124 int icon_height
= 0;
1126 /* Use the current font to determine the height of a tab. */
1127 GetTextMetricsW(hdc
, &fontMetrics
);
1129 /* Get the icon height */
1131 ImageList_GetIconSize(infoPtr
->himl
, 0, &icon_height
);
1133 /* Take the highest between font or icon */
1134 if (fontMetrics
.tmHeight
> icon_height
)
1135 item_height
= fontMetrics
.tmHeight
+ 2;
1137 item_height
= icon_height
;
1140 * Make sure there is enough space for the letters + icon + growing the
1141 * selected item + extra space for the selected item.
1143 infoPtr
->tabHeight
= item_height
+
1144 ((lStyle
& TCS_BUTTONS
) ? 2 : 1) *
1145 infoPtr
->uVItemPadding
;
1147 TRACE("tabH=%d, tmH=%ld, iconh=%d\n",
1148 infoPtr
->tabHeight
, fontMetrics
.tmHeight
, icon_height
);
1151 TRACE("client right=%ld\n", clientRect
.right
);
1153 /* Get the icon width */
1156 ImageList_GetIconSize(infoPtr
->himl
, &icon_width
, 0);
1158 if (lStyle
& TCS_FIXEDWIDTH
)
1161 /* Add padding if icon is present */
1162 icon_width
+= infoPtr
->uHItemPadding
;
1165 for (curItem
= 0; curItem
< infoPtr
->uNumItem
; curItem
++)
1167 TAB_ITEM
*curr
= TAB_GetItem(infoPtr
, curItem
);
1169 /* Set the leftmost position of the tab. */
1170 curr
->rect
.left
= curItemLeftPos
;
1172 if (lStyle
& TCS_FIXEDWIDTH
)
1174 curr
->rect
.right
= curr
->rect
.left
+
1175 max(infoPtr
->tabWidth
, icon_width
);
1177 else if (!curr
->pszText
)
1179 /* If no text use minimum tab width including padding. */
1180 if (infoPtr
->tabMinWidth
< 0)
1181 curr
->rect
.right
= curr
->rect
.left
+ GET_DEFAULT_MIN_TAB_WIDTH(infoPtr
);
1184 curr
->rect
.right
= curr
->rect
.left
+ infoPtr
->tabMinWidth
;
1186 /* Add extra padding if icon is present */
1187 if (infoPtr
->himl
&& infoPtr
->tabMinWidth
> 0 && infoPtr
->tabMinWidth
< DEFAULT_MIN_TAB_WIDTH
1188 && infoPtr
->uHItemPadding
> 1)
1189 curr
->rect
.right
+= EXTRA_ICON_PADDING
* (infoPtr
->uHItemPadding
-1);
1195 /* Calculate how wide the tab is depending on the text it contains */
1196 GetTextExtentPoint32W(hdc
, curr
->pszText
,
1197 lstrlenW(curr
->pszText
), &size
);
1199 tabwidth
= size
.cx
+ icon_width
+ 2 * infoPtr
->uHItemPadding
;
1201 if (infoPtr
->tabMinWidth
< 0)
1202 tabwidth
= max(tabwidth
, GET_DEFAULT_MIN_TAB_WIDTH(infoPtr
));
1204 tabwidth
= max(tabwidth
, infoPtr
->tabMinWidth
);
1206 curr
->rect
.right
= curr
->rect
.left
+ tabwidth
;
1207 TRACE("for <%s>, l,r=%ld,%ld\n",
1208 debugstr_w(curr
->pszText
), curr
->rect
.left
, curr
->rect
.right
);
1212 * Check if this is a multiline tab control and if so
1213 * check to see if we should wrap the tabs
1215 * Wrap all these tabs. We will arrange them evenly later.
1219 if (((lStyle
& TCS_MULTILINE
) || (lStyle
& TCS_VERTICAL
)) &&
1221 (clientRect
.right
- CONTROL_BORDER_SIZEX
- DISPLAY_AREA_PADDINGX
)))
1223 curr
->rect
.right
-= curr
->rect
.left
;
1225 curr
->rect
.left
= 0;
1227 TRACE("wrapping <%s>, l,r=%ld,%ld\n", debugstr_w(curr
->pszText
),
1228 curr
->rect
.left
, curr
->rect
.right
);
1231 curr
->rect
.bottom
= 0;
1232 curr
->rect
.top
= curItemRowCount
- 1;
1234 TRACE("TextSize: %li\n", size
.cx
);
1235 TRACE("Rect: T %li, L %li, B %li, R %li\n", curr
->rect
.top
,
1236 curr
->rect
.left
, curr
->rect
.bottom
, curr
->rect
.right
);
1239 * The leftmost position of the next item is the rightmost position
1242 if (lStyle
& TCS_BUTTONS
)
1244 curItemLeftPos
= curr
->rect
.right
+ BUTTON_SPACINGX
;
1245 if (lStyle
& TCS_FLATBUTTONS
)
1246 curItemLeftPos
+= FLAT_BTN_SPACINGX
;
1249 curItemLeftPos
= curr
->rect
.right
;
1252 if (!((lStyle
& TCS_MULTILINE
) || (lStyle
& TCS_VERTICAL
)))
1255 * Check if we need a scrolling control.
1257 infoPtr
->needsScrolling
= (curItemLeftPos
+ (2 * SELECTED_TAB_OFFSET
) >
1260 /* Don't need scrolling, then update infoPtr->leftmostVisible */
1261 if(!infoPtr
->needsScrolling
)
1262 infoPtr
->leftmostVisible
= 0;
1267 * No scrolling in Multiline or Vertical styles.
1269 infoPtr
->needsScrolling
= FALSE
;
1270 infoPtr
->leftmostVisible
= 0;
1272 TAB_SetupScrolling(infoPtr
->hwnd
, infoPtr
, &clientRect
);
1274 /* Set the number of rows */
1275 infoPtr
->uNumRows
= curItemRowCount
;
1277 /* Arrange all tabs evenly if style says so */
1278 if (!(lStyle
& TCS_RAGGEDRIGHT
) && ((lStyle
& TCS_MULTILINE
) || (lStyle
& TCS_VERTICAL
)) && (infoPtr
->uNumItem
> 0))
1280 INT tabPerRow
,remTab
,iRow
;
1285 * Ok windows tries to even out the rows. place the same
1286 * number of tabs in each row. So lets give that a shot
1289 tabPerRow
= infoPtr
->uNumItem
/ (infoPtr
->uNumRows
);
1290 remTab
= infoPtr
->uNumItem
% (infoPtr
->uNumRows
);
1292 for (iItm
=0,iRow
=0,iCount
=0,curItemLeftPos
=0;
1293 iItm
<infoPtr
->uNumItem
;
1296 /* normalize the current rect */
1297 TAB_ITEM
*curr
= TAB_GetItem(infoPtr
, iItm
);
1299 /* shift the item to the left side of the clientRect */
1300 curr
->rect
.right
-= curr
->rect
.left
;
1301 curr
->rect
.left
= 0;
1303 TRACE("r=%ld, cl=%d, cl.r=%ld, iCount=%d, iRow=%d, uNumRows=%d, remTab=%d, tabPerRow=%d\n",
1304 curr
->rect
.right
, curItemLeftPos
, clientRect
.right
,
1305 iCount
, iRow
, infoPtr
->uNumRows
, remTab
, tabPerRow
);
1307 /* if we have reached the maximum number of tabs on this row */
1308 /* move to the next row, reset our current item left position and */
1309 /* the count of items on this row */
1311 if (lStyle
& TCS_VERTICAL
) {
1312 /* Vert: Add the remaining tabs in the *last* remainder rows */
1313 if (iCount
>= ((iRow
>=(INT
)infoPtr
->uNumRows
- remTab
)?tabPerRow
+ 1:tabPerRow
)) {
1319 /* Horz: Add the remaining tabs in the *first* remainder rows */
1320 if (iCount
>= ((iRow
<remTab
)?tabPerRow
+ 1:tabPerRow
)) {
1327 /* shift the item to the right to place it as the next item in this row */
1328 curr
->rect
.left
+= curItemLeftPos
;
1329 curr
->rect
.right
+= curItemLeftPos
;
1330 curr
->rect
.top
= iRow
;
1331 if (lStyle
& TCS_BUTTONS
)
1333 curItemLeftPos
= curr
->rect
.right
+ 1;
1334 if (lStyle
& TCS_FLATBUTTONS
)
1335 curItemLeftPos
+= FLAT_BTN_SPACINGX
;
1338 curItemLeftPos
= curr
->rect
.right
;
1340 TRACE("arranging <%s>, l,r=%ld,%ld, row=%ld\n",
1341 debugstr_w(curr
->pszText
), curr
->rect
.left
,
1342 curr
->rect
.right
, curr
->rect
.top
);
1349 INT widthDiff
, iIndexStart
=0, iIndexEnd
=0;
1353 while(iIndexStart
< infoPtr
->uNumItem
)
1355 TAB_ITEM
*start
= TAB_GetItem(infoPtr
, iIndexStart
);
1358 * find the index of the row
1360 /* find the first item on the next row */
1361 for (iIndexEnd
=iIndexStart
;
1362 (iIndexEnd
< infoPtr
->uNumItem
) &&
1363 (TAB_GetItem(infoPtr
, iIndexEnd
)->rect
.top
==
1366 /* intentionally blank */;
1369 * we need to justify these tabs so they fill the whole given
1373 /* find the amount of space remaining on this row */
1374 widthDiff
= clientRect
.right
- (2 * SELECTED_TAB_OFFSET
) -
1375 TAB_GetItem(infoPtr
, iIndexEnd
- 1)->rect
.right
;
1377 /* iCount is the number of tab items on this row */
1378 iCount
= iIndexEnd
- iIndexStart
;
1382 remainder
= widthDiff
% iCount
;
1383 widthDiff
= widthDiff
/ iCount
;
1384 /* add widthDiff/iCount, or extra space/items on row, to each item on this row */
1385 for (iIndex
=iIndexStart
, iCount
=0; iIndex
< iIndexEnd
; iIndex
++, iCount
++)
1387 TAB_ITEM
*item
= TAB_GetItem(infoPtr
, iIndex
);
1389 item
->rect
.left
+= iCount
* widthDiff
;
1390 item
->rect
.right
+= (iCount
+ 1) * widthDiff
;
1392 TRACE("adjusting 1 <%s>, l,r=%ld,%ld\n",
1393 debugstr_w(item
->pszText
),
1394 item
->rect
.left
, item
->rect
.right
);
1397 TAB_GetItem(infoPtr
, iIndex
- 1)->rect
.right
+= remainder
;
1399 else /* we have only one item on this row, make it take up the entire row */
1401 start
->rect
.left
= clientRect
.left
;
1402 start
->rect
.right
= clientRect
.right
- 4;
1404 TRACE("adjusting 2 <%s>, l,r=%ld,%ld\n",
1405 debugstr_w(start
->pszText
),
1406 start
->rect
.left
, start
->rect
.right
);
1411 iIndexStart
= iIndexEnd
;
1416 /* if TCS_VERTICAL rotate the tabs so they are along the side of the clientRect */
1417 if(lStyle
& TCS_VERTICAL
)
1420 for(iIndex
= 0; iIndex
< infoPtr
->uNumItem
; iIndex
++)
1422 rcItem
= &TAB_GetItem(infoPtr
, iIndex
)->rect
;
1424 rcOriginal
= *rcItem
;
1426 /* this is rotating the items by 90 degrees clockwise around the center of the control */
1427 rcItem
->top
= (rcOriginal
.left
- clientRect
.left
);
1428 rcItem
->bottom
= rcItem
->top
+ (rcOriginal
.right
- rcOriginal
.left
);
1429 rcItem
->left
= rcOriginal
.top
;
1430 rcItem
->right
= rcOriginal
.bottom
;
1434 TAB_EnsureSelectionVisible(infoPtr
);
1435 TAB_RecalcHotTrack(infoPtr
, NULL
, NULL
, NULL
);
1438 SelectObject (hdc
, hOldFont
);
1439 ReleaseDC (infoPtr
->hwnd
, hdc
);
1444 TAB_EraseTabInterior
1452 LONG lStyle
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
1453 HBRUSH hbr
= CreateSolidBrush (comctl32_color
.clrBtnFace
);
1454 BOOL deleteBrush
= TRUE
;
1455 RECT rTemp
= *drawRect
;
1457 InflateRect(&rTemp
, -2, -2);
1458 if (lStyle
& TCS_BUTTONS
)
1460 if (iItem
== infoPtr
->iSelected
)
1462 /* Background color */
1463 if (!(lStyle
& TCS_OWNERDRAWFIXED
))
1466 hbr
= GetSysColorBrush(COLOR_SCROLLBAR
);
1468 SetTextColor(hdc
, comctl32_color
.clr3dFace
);
1469 SetBkColor(hdc
, comctl32_color
.clr3dHilight
);
1471 /* if COLOR_WINDOW happens to be the same as COLOR_3DHILIGHT
1472 * we better use 0x55aa bitmap brush to make scrollbar's background
1473 * look different from the window background.
1475 if (comctl32_color
.clr3dHilight
== comctl32_color
.clrWindow
)
1476 hbr
= COMCTL32_hPattern55AABrush
;
1478 deleteBrush
= FALSE
;
1480 FillRect(hdc
, &rTemp
, hbr
);
1482 else /* ! selected */
1484 if (lStyle
& TCS_FLATBUTTONS
)
1486 FillRect(hdc
, drawRect
, hbr
);
1487 if (iItem
== infoPtr
->iHotTracked
)
1488 DrawEdge(hdc
, drawRect
, EDGE_RAISED
, BF_SOFT
|BF_RECT
);
1491 FillRect(hdc
, &rTemp
, hbr
);
1495 else /* !TCS_BUTTONS */
1497 if (!GetWindowTheme (infoPtr
->hwnd
))
1498 FillRect(hdc
, &rTemp
, hbr
);
1502 if (deleteBrush
) DeleteObject(hbr
);
1505 /******************************************************************************
1506 * TAB_DrawItemInterior
1508 * This method is used to draw the interior (text and icon) of a single tab
1509 * into the tab control.
1512 TAB_DrawItemInterior
1520 LONG lStyle
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
1529 /* if (drawRect == NULL) */
1536 * Get the rectangle for the item.
1538 isVisible
= TAB_InternalGetItemRect(infoPtr
, iItem
, &itemRect
, &selectedRect
);
1543 * Make sure drawRect points to something valid; simplifies code.
1545 drawRect
= &localRect
;
1548 * This logic copied from the part of TAB_DrawItem which draws
1549 * the tab background. It's important to keep it in sync. I
1550 * would have liked to avoid code duplication, but couldn't figure
1551 * out how without making spaghetti of TAB_DrawItem.
1553 if (iItem
== infoPtr
->iSelected
)
1554 *drawRect
= selectedRect
;
1556 *drawRect
= itemRect
;
1558 if (lStyle
& TCS_BUTTONS
)
1560 if (iItem
== infoPtr
->iSelected
)
1562 drawRect
->left
+= 4;
1564 drawRect
->right
-= 4;
1565 drawRect
->bottom
-= 1;
1569 drawRect
->left
+= 2;
1571 drawRect
->right
-= 2;
1572 drawRect
->bottom
-= 2;
1577 if ((lStyle
& TCS_VERTICAL
) && (lStyle
& TCS_BOTTOM
))
1579 if (iItem
!= infoPtr
->iSelected
)
1581 drawRect
->left
+= 2;
1583 drawRect
->bottom
-= 2;
1586 else if (lStyle
& TCS_VERTICAL
)
1588 if (iItem
== infoPtr
->iSelected
)
1590 drawRect
->right
+= 1;
1595 drawRect
->right
-= 2;
1596 drawRect
->bottom
-= 2;
1599 else if (lStyle
& TCS_BOTTOM
)
1601 if (iItem
== infoPtr
->iSelected
)
1607 InflateRect(drawRect
, -2, -2);
1608 drawRect
->bottom
+= 2;
1613 if (iItem
== infoPtr
->iSelected
)
1615 drawRect
->bottom
+= 3;
1619 drawRect
->bottom
-= 2;
1620 InflateRect(drawRect
, -2, 0);
1625 TRACE("drawRect=(%ld,%ld)-(%ld,%ld)\n",
1626 drawRect
->left
, drawRect
->top
, drawRect
->right
, drawRect
->bottom
);
1628 /* Clear interior */
1629 TAB_EraseTabInterior (infoPtr
, hdc
, iItem
, drawRect
);
1631 /* Draw the focus rectangle */
1632 if (!(lStyle
& TCS_FOCUSNEVER
) &&
1633 (GetFocus() == infoPtr
->hwnd
) &&
1634 (iItem
== infoPtr
->uFocus
) )
1636 RECT rFocus
= *drawRect
;
1637 InflateRect(&rFocus
, -3, -3);
1638 if (lStyle
& TCS_BOTTOM
&& !(lStyle
& TCS_VERTICAL
))
1640 if (lStyle
& TCS_BUTTONS
)
1646 DrawFocusRect(hdc
, &rFocus
);
1652 htextPen
= CreatePen( PS_SOLID
, 1, GetSysColor(COLOR_BTNTEXT
) );
1653 holdPen
= SelectObject(hdc
, htextPen
);
1654 hOldFont
= SelectObject(hdc
, infoPtr
->hFont
);
1657 * Setup for text output
1659 oldBkMode
= SetBkMode(hdc
, TRANSPARENT
);
1660 if (!GetWindowTheme (infoPtr
->hwnd
) || (lStyle
& TCS_BUTTONS
))
1661 SetTextColor(hdc
, (((lStyle
& TCS_HOTTRACK
) && (iItem
== infoPtr
->iHotTracked
)
1662 && !(lStyle
& TCS_FLATBUTTONS
))
1663 | (TAB_GetItem(infoPtr
, iItem
)->dwState
& TCIS_HIGHLIGHTED
)) ?
1664 comctl32_color
.clrHighlight
: comctl32_color
.clrBtnText
);
1667 * if owner draw, tell the owner to draw
1669 if ((lStyle
& TCS_OWNERDRAWFIXED
) && GetParent(infoPtr
->hwnd
))
1675 drawRect
->right
-= 1;
1676 if ( iItem
== infoPtr
->iSelected
)
1678 drawRect
->right
-= 1;
1679 drawRect
->left
+= 1;
1683 * get the control id
1685 id
= (UINT
)GetWindowLongPtrW( infoPtr
->hwnd
, GWLP_ID
);
1688 * put together the DRAWITEMSTRUCT
1690 dis
.CtlType
= ODT_TAB
;
1693 dis
.itemAction
= ODA_DRAWENTIRE
;
1695 if ( iItem
== infoPtr
->iSelected
)
1696 dis
.itemState
|= ODS_SELECTED
;
1697 if (infoPtr
->uFocus
== iItem
)
1698 dis
.itemState
|= ODS_FOCUS
;
1699 dis
.hwndItem
= infoPtr
->hwnd
;
1701 CopyRect(&dis
.rcItem
,drawRect
);
1702 dis
.itemData
= (ULONG_PTR
)TAB_GetItem(infoPtr
, iItem
)->extra
;
1705 * send the draw message
1707 SendMessageW( infoPtr
->hwndNotify
, WM_DRAWITEM
, (WPARAM
)id
, (LPARAM
)&dis
);
1711 TAB_ITEM
*item
= TAB_GetItem(infoPtr
, iItem
);
1715 /* used to center the icon and text in the tab */
1717 INT center_offset_h
, center_offset_v
;
1719 /* set rcImage to drawRect, we will use top & left in our ImageList_Draw call */
1720 rcImage
= *drawRect
;
1724 rcText
.left
= rcText
.top
= rcText
.right
= rcText
.bottom
= 0;
1726 /* get the rectangle that the text fits in */
1729 DrawTextW(hdc
, item
->pszText
, -1, &rcText
, DT_CALCRECT
);
1732 * If not owner draw, then do the drawing ourselves.
1736 if (infoPtr
->himl
&& (item
->mask
& TCIF_IMAGE
))
1741 ImageList_GetIconSize(infoPtr
->himl
, &cx
, &cy
);
1743 if(lStyle
& TCS_VERTICAL
)
1745 center_offset_h
= ((drawRect
->bottom
- drawRect
->top
) - (cy
+ infoPtr
->uHItemPadding
+ (rcText
.right
- rcText
.left
))) / 2;
1746 center_offset_v
= ((drawRect
->right
- drawRect
->left
) - (cx
+ infoPtr
->uVItemPadding
)) / 2;
1750 center_offset_h
= ((drawRect
->right
- drawRect
->left
) - (cx
+ infoPtr
->uHItemPadding
+ (rcText
.right
- rcText
.left
))) / 2;
1751 center_offset_v
= ((drawRect
->bottom
- drawRect
->top
) - (cy
+ infoPtr
->uVItemPadding
)) / 2;
1754 if (lStyle
& TCS_FIXEDWIDTH
&& lStyle
& (TCS_FORCELABELLEFT
| TCS_FORCEICONLEFT
))
1755 center_offset_h
= infoPtr
->uHItemPadding
;
1757 if (center_offset_h
< 2)
1758 center_offset_h
= 2;
1760 if (center_offset_v
< 0)
1761 center_offset_v
= 0;
1763 TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%ld,%ld)-(%ld,%ld), textlen=%ld\n",
1764 debugstr_w(item
->pszText
), center_offset_h
, center_offset_v
,
1765 drawRect
->left
, drawRect
->top
, drawRect
->right
, drawRect
->bottom
,
1766 (rcText
.right
-rcText
.left
));
1768 if((lStyle
& TCS_VERTICAL
) && (lStyle
& TCS_BOTTOM
))
1770 rcImage
.top
= drawRect
->top
+ center_offset_h
;
1771 /* if tab is TCS_VERTICAL and TCS_BOTTOM, the text is drawn from the */
1772 /* right side of the tab, but the image still uses the left as its x position */
1773 /* this keeps the image always drawn off of the same side of the tab */
1774 rcImage
.left
= drawRect
->right
- cx
- center_offset_v
;
1775 drawRect
->top
+= cy
+ infoPtr
->uHItemPadding
;
1777 else if(lStyle
& TCS_VERTICAL
)
1779 rcImage
.top
= drawRect
->bottom
- cy
- center_offset_h
;
1780 rcImage
.left
= drawRect
->left
+ center_offset_v
;
1781 drawRect
->bottom
-= cy
+ infoPtr
->uHItemPadding
;
1783 else /* normal style, whether TCS_BOTTOM or not */
1785 rcImage
.left
= drawRect
->left
+ center_offset_h
;
1786 rcImage
.top
= drawRect
->top
+ center_offset_v
;
1787 drawRect
->left
+= cx
+ infoPtr
->uHItemPadding
;
1790 TRACE("drawing image=%d, left=%ld, top=%ld\n",
1791 item
->iImage
, rcImage
.left
, rcImage
.top
-1);
1803 /* Now position text */
1804 if (lStyle
& TCS_FIXEDWIDTH
&& lStyle
& TCS_FORCELABELLEFT
)
1805 center_offset_h
= infoPtr
->uHItemPadding
;
1807 if(lStyle
& TCS_VERTICAL
)
1808 center_offset_h
= ((drawRect
->bottom
- drawRect
->top
) - (rcText
.right
- rcText
.left
)) / 2;
1810 center_offset_h
= ((drawRect
->right
- drawRect
->left
) - (rcText
.right
- rcText
.left
)) / 2;
1812 if(lStyle
& TCS_VERTICAL
)
1814 if(lStyle
& TCS_BOTTOM
)
1815 drawRect
->top
+=center_offset_h
;
1817 drawRect
->bottom
-=center_offset_h
;
1819 center_offset_v
= ((drawRect
->right
- drawRect
->left
) - (rcText
.bottom
- rcText
.top
)) / 2;
1823 drawRect
->left
+= center_offset_h
;
1824 center_offset_v
= ((drawRect
->bottom
- drawRect
->top
) - (rcText
.bottom
- rcText
.top
)) / 2;
1827 /* if an item is selected, the text is shifted up instead of down */
1828 if (iItem
== infoPtr
->iSelected
)
1829 center_offset_v
-= infoPtr
->uVItemPadding
/ 2;
1831 center_offset_v
+= infoPtr
->uVItemPadding
/ 2;
1833 if (center_offset_v
< 0)
1834 center_offset_v
= 0;
1836 if(lStyle
& TCS_VERTICAL
)
1837 drawRect
->left
+= center_offset_v
;
1839 drawRect
->top
+= center_offset_v
;
1842 if(lStyle
& TCS_VERTICAL
) /* if we are vertical rotate the text and each character */
1844 static const WCHAR ArialW
[] = { 'A','r','i','a','l',0 };
1847 INT nEscapement
= 900;
1848 INT nOrientation
= 900;
1850 if(lStyle
& TCS_BOTTOM
)
1853 nOrientation
= -900;
1856 /* to get a font with the escapement and orientation we are looking for, we need to */
1857 /* call CreateFontIndirectA, which requires us to set the values of the logfont we pass in */
1858 if (!GetObjectW((infoPtr
->hFont
) ?
1859 infoPtr
->hFont
: GetStockObject(SYSTEM_FONT
),
1860 sizeof(LOGFONTW
),&logfont
))
1864 lstrcpyW(logfont
.lfFaceName
, ArialW
);
1865 logfont
.lfHeight
= -MulDiv(iPointSize
, GetDeviceCaps(hdc
, LOGPIXELSY
),
1867 logfont
.lfWeight
= FW_NORMAL
;
1868 logfont
.lfItalic
= 0;
1869 logfont
.lfUnderline
= 0;
1870 logfont
.lfStrikeOut
= 0;
1873 logfont
.lfEscapement
= nEscapement
;
1874 logfont
.lfOrientation
= nOrientation
;
1875 hFont
= CreateFontIndirectW(&logfont
);
1876 SelectObject(hdc
, hFont
);
1881 (lStyle
& TCS_BOTTOM
) ? drawRect
->right
: drawRect
->left
,
1882 (!(lStyle
& TCS_BOTTOM
)) ? drawRect
->bottom
: drawRect
->top
,
1886 lstrlenW(item
->pszText
),
1890 DeleteObject(hFont
);
1894 TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%ld,%ld)-(%ld,%ld), textlen=%ld\n",
1895 debugstr_w(item
->pszText
), center_offset_h
, center_offset_v
,
1896 drawRect
->left
, drawRect
->top
, drawRect
->right
, drawRect
->bottom
,
1897 (rcText
.right
-rcText
.left
));
1904 lstrlenW(item
->pszText
),
1906 DT_LEFT
| DT_SINGLELINE
1911 *drawRect
= rcTemp
; /* restore drawRect */
1917 SelectObject(hdc
, hOldFont
);
1918 SetBkMode(hdc
, oldBkMode
);
1919 SelectObject(hdc
, holdPen
);
1920 DeleteObject( htextPen
);
1923 /******************************************************************************
1926 * This method is used to draw a single tab into the tab control.
1928 static void TAB_DrawItem(
1933 LONG lStyle
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
1937 RECT r
, fillRect
, r1
;
1940 COLORREF bkgnd
, corner
;
1944 * Get the rectangle for the item.
1946 isVisible
= TAB_InternalGetItemRect(infoPtr
,
1955 /* Clip UpDown control to not draw over it */
1956 if (infoPtr
->needsScrolling
)
1958 GetWindowRect(infoPtr
->hwnd
, &rC
);
1959 GetWindowRect(infoPtr
->hwndUpDown
, &rUD
);
1960 ExcludeClipRect(hdc
, rUD
.left
- rC
.left
, rUD
.top
- rC
.top
, rUD
.right
- rC
.left
, rUD
.bottom
- rC
.top
);
1963 /* If you need to see what the control is doing,
1964 * then override these variables. They will change what
1965 * fill colors are used for filling the tabs, and the
1966 * corners when drawing the edge.
1968 bkgnd
= comctl32_color
.clrBtnFace
;
1969 corner
= comctl32_color
.clrBtnFace
;
1971 if (lStyle
& TCS_BUTTONS
)
1973 /* Get item rectangle */
1976 /* Separators between flat buttons */
1977 if (lStyle
& TCS_FLATBUTTONS
)
1980 r1
.right
+= (FLAT_BTN_SPACINGX
-2);
1981 DrawEdge(hdc
, &r1
, EDGE_ETCHED
, BF_RIGHT
);
1984 if (iItem
== infoPtr
->iSelected
)
1986 DrawEdge(hdc
, &r
, EDGE_SUNKEN
, BF_SOFT
|BF_RECT
);
1988 OffsetRect(&r
, 1, 1);
1990 else /* ! selected */
1992 if (!(lStyle
& TCS_FLATBUTTONS
))
1993 DrawEdge(hdc
, &r
, EDGE_RAISED
, BF_SOFT
|BF_RECT
);
1996 else /* !TCS_BUTTONS */
1998 /* We draw a rectangle of different sizes depending on the selection
2000 if (iItem
== infoPtr
->iSelected
) {
2002 GetClientRect (infoPtr
->hwnd
, &rect
);
2003 clRight
= rect
.right
;
2004 clBottom
= rect
.bottom
;
2011 * Erase the background. (Delay it but setup rectangle.)
2012 * This is necessary when drawing the selected item since it is larger
2013 * than the others, it might overlap with stuff already drawn by the
2018 /* Draw themed tabs - but only if they are at the top.
2019 * Windows draws even side or bottom tabs themed, with wacky results.
2020 * However, since in Wine apps may get themed that did not opt in via
2021 * a manifest avoid theming when we know the result will be wrong */
2022 if ((theme
= GetWindowTheme (infoPtr
->hwnd
))
2023 && ((lStyle
& (TCS_VERTICAL
| TCS_BOTTOM
)) == 0))
2025 const static int partIds
[8] = {
2028 TABP_TABITEMLEFTEDGE
,
2029 TABP_TABITEMRIGHTEDGE
,
2030 TABP_TABITEMBOTHEDGE
,
2033 TABP_TOPTABITEMLEFTEDGE
,
2034 TABP_TOPTABITEMRIGHTEDGE
,
2035 TABP_TOPTABITEMBOTHEDGE
,
2038 int stateId
= TIS_NORMAL
;
2040 /* selected and unselected tabs have different parts */
2041 if (iItem
== infoPtr
->iSelected
)
2043 /* The part also differs on the position of a tab on a line.
2044 * "Visually" determining the position works well enough. */
2045 if(selectedRect
.left
== 0)
2047 if(selectedRect
.right
== clRight
)
2050 if (iItem
== infoPtr
->iSelected
)
2051 stateId
= TIS_SELECTED
;
2052 else if (iItem
== infoPtr
->iHotTracked
)
2054 else if (iItem
== infoPtr
->uFocus
)
2055 stateId
= TIS_FOCUSED
;
2057 /* Adjust rectangle for bottommost row */
2058 if (TAB_GetItem(infoPtr
, iItem
)->rect
.top
== infoPtr
->uNumRows
-1)
2061 DrawThemeBackground (theme
, hdc
, partIds
[partIndex
], stateId
, &r
, NULL
);
2062 GetThemeBackgroundContentRect (theme
, hdc
, partIds
[partIndex
], stateId
, &r
, &r
);
2064 else if(lStyle
& TCS_VERTICAL
)
2066 /* These are for adjusting the drawing of a Selected tab */
2067 /* The initial values are for the normal case of non-Selected */
2068 int ZZ
= 1; /* Do not strech if selected */
2069 if (iItem
== infoPtr
->iSelected
) {
2072 /* if leftmost draw the line longer */
2073 if(selectedRect
.top
== 0)
2074 fillRect
.top
+= CONTROL_BORDER_SIZEY
;
2075 /* if rightmost draw the line longer */
2076 if(selectedRect
.bottom
== clBottom
)
2077 fillRect
.bottom
-= CONTROL_BORDER_SIZEY
;
2080 if (lStyle
& TCS_BOTTOM
)
2082 /* Adjust both rectangles to match native */
2085 TRACE("<right> item=%d, fill=(%ld,%ld)-(%ld,%ld), edge=(%ld,%ld)-(%ld,%ld)\n",
2087 fillRect
.left
,fillRect
.top
,fillRect
.right
,fillRect
.bottom
,
2088 r
.left
,r
.top
,r
.right
,r
.bottom
);
2090 /* Clear interior */
2091 SetBkColor(hdc
, bkgnd
);
2092 ExtTextOutW(hdc
, 0, 0, 2, &fillRect
, NULL
, 0, 0);
2094 /* Draw rectangular edge around tab */
2095 DrawEdge(hdc
, &r
, EDGE_RAISED
, BF_SOFT
|BF_RIGHT
|BF_TOP
|BF_BOTTOM
);
2097 /* Now erase the top corner and draw diagonal edge */
2098 SetBkColor(hdc
, corner
);
2099 r1
.left
= r
.right
- ROUND_CORNER_SIZE
- 1;
2102 r1
.bottom
= r1
.top
+ ROUND_CORNER_SIZE
;
2103 ExtTextOutW(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2105 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_DIAGONAL_ENDTOPLEFT
);
2107 /* Now erase the bottom corner and draw diagonal edge */
2108 r1
.left
= r
.right
- ROUND_CORNER_SIZE
- 1;
2109 r1
.bottom
= r
.bottom
;
2111 r1
.top
= r1
.bottom
- ROUND_CORNER_SIZE
;
2112 ExtTextOutW(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2114 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_DIAGONAL_ENDBOTTOMLEFT
);
2116 if ((iItem
== infoPtr
->iSelected
) && (selectedRect
.top
== 0)) {
2120 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_TOP
);
2126 TRACE("<left> item=%d, fill=(%ld,%ld)-(%ld,%ld), edge=(%ld,%ld)-(%ld,%ld)\n",
2128 fillRect
.left
,fillRect
.top
,fillRect
.right
,fillRect
.bottom
,
2129 r
.left
,r
.top
,r
.right
,r
.bottom
);
2131 /* Clear interior */
2132 SetBkColor(hdc
, bkgnd
);
2133 ExtTextOutW(hdc
, 0, 0, 2, &fillRect
, NULL
, 0, 0);
2135 /* Draw rectangular edge around tab */
2136 DrawEdge(hdc
, &r
, EDGE_RAISED
, BF_SOFT
|BF_LEFT
|BF_TOP
|BF_BOTTOM
);
2138 /* Now erase the top corner and draw diagonal edge */
2139 SetBkColor(hdc
, corner
);
2142 r1
.right
= r1
.left
+ ROUND_CORNER_SIZE
+ 1;
2143 r1
.bottom
= r1
.top
+ ROUND_CORNER_SIZE
;
2144 ExtTextOutW(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2146 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_DIAGONAL_ENDTOPRIGHT
);
2148 /* Now erase the bottom corner and draw diagonal edge */
2150 r1
.bottom
= r
.bottom
;
2151 r1
.right
= r1
.left
+ ROUND_CORNER_SIZE
+ 1;
2152 r1
.top
= r1
.bottom
- ROUND_CORNER_SIZE
;
2153 ExtTextOutW(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2155 DrawEdge(hdc
, &r1
, EDGE_SUNKEN
, BF_DIAGONAL_ENDTOPLEFT
);
2158 else /* ! TCS_VERTICAL */
2160 /* These are for adjusting the drawing of a Selected tab */
2161 /* The initial values are for the normal case of non-Selected */
2162 if (iItem
== infoPtr
->iSelected
) {
2163 /* if leftmost draw the line longer */
2164 if(selectedRect
.left
== 0)
2165 fillRect
.left
+= CONTROL_BORDER_SIZEX
;
2166 /* if rightmost draw the line longer */
2167 if(selectedRect
.right
== clRight
)
2168 fillRect
.right
-= CONTROL_BORDER_SIZEX
;
2171 if (lStyle
& TCS_BOTTOM
)
2173 /* Adjust both rectangles for topmost row */
2174 if (TAB_GetItem(infoPtr
, iItem
)->rect
.top
== infoPtr
->uNumRows
-1)
2180 TRACE("<bottom> item=%d, fill=(%ld,%ld)-(%ld,%ld), edge=(%ld,%ld)-(%ld,%ld)\n",
2182 fillRect
.left
,fillRect
.top
,fillRect
.right
,fillRect
.bottom
,
2183 r
.left
,r
.top
,r
.right
,r
.bottom
);
2185 /* Clear interior */
2186 SetBkColor(hdc
, bkgnd
);
2187 ExtTextOutW(hdc
, 0, 0, 2, &fillRect
, NULL
, 0, 0);
2189 /* Draw rectangular edge around tab */
2190 DrawEdge(hdc
, &r
, EDGE_RAISED
, BF_SOFT
|BF_LEFT
|BF_BOTTOM
|BF_RIGHT
);
2192 /* Now erase the righthand corner and draw diagonal edge */
2193 SetBkColor(hdc
, corner
);
2194 r1
.left
= r
.right
- ROUND_CORNER_SIZE
;
2195 r1
.bottom
= r
.bottom
;
2197 r1
.top
= r1
.bottom
- ROUND_CORNER_SIZE
- 1;
2198 ExtTextOutW(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2200 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_DIAGONAL_ENDBOTTOMLEFT
);
2202 /* Now erase the lefthand corner and draw diagonal edge */
2204 r1
.bottom
= r
.bottom
;
2205 r1
.right
= r1
.left
+ ROUND_CORNER_SIZE
;
2206 r1
.top
= r1
.bottom
- ROUND_CORNER_SIZE
- 1;
2207 ExtTextOutW(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2209 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_DIAGONAL_ENDTOPLEFT
);
2211 if (iItem
== infoPtr
->iSelected
)
2215 if (selectedRect
.left
== 0)
2220 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_LEFT
);
2227 /* Adjust both rectangles for bottommost row */
2228 if (TAB_GetItem(infoPtr
, iItem
)->rect
.top
== infoPtr
->uNumRows
-1)
2230 fillRect
.bottom
+= 3;
2234 TRACE("<top> item=%d, fill=(%ld,%ld)-(%ld,%ld), edge=(%ld,%ld)-(%ld,%ld)\n",
2236 fillRect
.left
,fillRect
.top
,fillRect
.right
,fillRect
.bottom
,
2237 r
.left
,r
.top
,r
.right
,r
.bottom
);
2239 /* Clear interior */
2240 SetBkColor(hdc
, bkgnd
);
2241 ExtTextOutW(hdc
, 0, 0, 2, &fillRect
, NULL
, 0, 0);
2243 /* Draw rectangular edge around tab */
2244 DrawEdge(hdc
, &r
, EDGE_RAISED
, BF_SOFT
|BF_LEFT
|BF_TOP
|BF_RIGHT
);
2246 /* Now erase the righthand corner and draw diagonal edge */
2247 SetBkColor(hdc
, corner
);
2248 r1
.left
= r
.right
- ROUND_CORNER_SIZE
;
2251 r1
.bottom
= r1
.top
+ ROUND_CORNER_SIZE
+ 1;
2252 ExtTextOutW(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2254 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_DIAGONAL_ENDBOTTOMRIGHT
);
2256 /* Now erase the lefthand corner and draw diagonal edge */
2259 r1
.right
= r1
.left
+ ROUND_CORNER_SIZE
;
2260 r1
.bottom
= r1
.top
+ ROUND_CORNER_SIZE
+ 1;
2261 ExtTextOutW(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2263 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_DIAGONAL_ENDTOPRIGHT
);
2268 TAB_DumpItemInternal(infoPtr
, iItem
);
2270 /* This modifies r to be the text rectangle. */
2271 TAB_DrawItemInterior(infoPtr
, hdc
, iItem
, &r
);
2275 /******************************************************************************
2278 * This method is used to draw the raised border around the tab control
2281 static void TAB_DrawBorder (TAB_INFO
*infoPtr
, HDC hdc
)
2284 DWORD lStyle
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
2285 HTHEME theme
= GetWindowTheme (infoPtr
->hwnd
);
2287 GetClientRect (infoPtr
->hwnd
, &rect
);
2290 * Adjust for the style
2293 if (infoPtr
->uNumItem
)
2295 if ((lStyle
& TCS_BOTTOM
) && !(lStyle
& TCS_VERTICAL
))
2296 rect
.bottom
-= infoPtr
->tabHeight
* infoPtr
->uNumRows
+ CONTROL_BORDER_SIZEX
;
2297 else if((lStyle
& TCS_BOTTOM
) && (lStyle
& TCS_VERTICAL
))
2298 rect
.right
-= infoPtr
->tabHeight
* infoPtr
->uNumRows
+ CONTROL_BORDER_SIZEX
;
2299 else if(lStyle
& TCS_VERTICAL
)
2300 rect
.left
+= infoPtr
->tabHeight
* infoPtr
->uNumRows
+ CONTROL_BORDER_SIZEX
;
2301 else /* not TCS_VERTICAL and not TCS_BOTTOM */
2302 rect
.top
+= infoPtr
->tabHeight
* infoPtr
->uNumRows
+ CONTROL_BORDER_SIZEX
;
2305 TRACE("border=(%ld,%ld)-(%ld,%ld)\n",
2306 rect
.left
, rect
.top
, rect
.right
, rect
.bottom
);
2309 DrawThemeBackground (theme
, hdc
, TABP_PANE
, 0, &rect
, NULL
);
2311 DrawEdge(hdc
, &rect
, EDGE_RAISED
, BF_SOFT
|BF_RECT
);
2314 /******************************************************************************
2317 * This method repaints the tab control..
2319 static void TAB_Refresh (TAB_INFO
*infoPtr
, HDC hdc
)
2324 if (!infoPtr
->DoRedraw
)
2327 hOldFont
= SelectObject (hdc
, infoPtr
->hFont
);
2329 if (GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
) & TCS_BUTTONS
)
2331 for (i
= 0; i
< infoPtr
->uNumItem
; i
++)
2332 TAB_DrawItem (infoPtr
, hdc
, i
);
2336 /* Draw all the non selected item first */
2337 for (i
= 0; i
< infoPtr
->uNumItem
; i
++)
2339 if (i
!= infoPtr
->iSelected
)
2340 TAB_DrawItem (infoPtr
, hdc
, i
);
2343 /* Now, draw the border, draw it before the selected item
2344 * since the selected item overwrites part of the border. */
2345 TAB_DrawBorder (infoPtr
, hdc
);
2347 /* Then, draw the selected item */
2348 TAB_DrawItem (infoPtr
, hdc
, infoPtr
->iSelected
);
2350 /* If we haven't set the current focus yet, set it now.
2351 * Only happens when we first paint the tab controls */
2352 if (infoPtr
->uFocus
== -1)
2353 TAB_SetCurFocus(infoPtr
, infoPtr
->iSelected
);
2356 SelectObject (hdc
, hOldFont
);
2359 static inline DWORD
TAB_GetRowCount (const TAB_INFO
*infoPtr
)
2361 return infoPtr
->uNumRows
;
2364 static inline LRESULT
TAB_SetRedraw (TAB_INFO
*infoPtr
, BOOL doRedraw
)
2366 infoPtr
->DoRedraw
= doRedraw
;
2370 /******************************************************************************
2371 * TAB_EnsureSelectionVisible
2373 * This method will make sure that the current selection is completely
2374 * visible by scrolling until it is.
2376 static void TAB_EnsureSelectionVisible(
2379 INT iSelected
= infoPtr
->iSelected
;
2380 LONG lStyle
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
2381 INT iOrigLeftmostVisible
= infoPtr
->leftmostVisible
;
2383 /* set the items row to the bottommost row or topmost row depending on
2385 if ((infoPtr
->uNumRows
> 1) && !(lStyle
& TCS_BUTTONS
))
2387 TAB_ITEM
*selected
= TAB_GetItem(infoPtr
, iSelected
);
2391 if(lStyle
& TCS_VERTICAL
)
2392 newselected
= selected
->rect
.left
;
2394 newselected
= selected
->rect
.top
;
2396 /* the target row is always (number of rows - 1)
2397 as row 0 is furthest from the clientRect */
2398 iTargetRow
= infoPtr
->uNumRows
- 1;
2400 if (newselected
!= iTargetRow
)
2403 if(lStyle
& TCS_VERTICAL
)
2405 for (i
=0; i
< infoPtr
->uNumItem
; i
++)
2407 /* move everything in the row of the selected item to the iTargetRow */
2408 TAB_ITEM
*item
= TAB_GetItem(infoPtr
, i
);
2410 if (item
->rect
.left
== newselected
)
2411 item
->rect
.left
= iTargetRow
;
2414 if (item
->rect
.left
> newselected
)
2421 for (i
=0; i
< infoPtr
->uNumItem
; i
++)
2423 TAB_ITEM
*item
= TAB_GetItem(infoPtr
, i
);
2425 if (item
->rect
.top
== newselected
)
2426 item
->rect
.top
= iTargetRow
;
2429 if (item
->rect
.top
> newselected
)
2434 TAB_RecalcHotTrack(infoPtr
, NULL
, NULL
, NULL
);
2439 * Do the trivial cases first.
2441 if ( (!infoPtr
->needsScrolling
) ||
2442 (infoPtr
->hwndUpDown
==0) || (lStyle
& TCS_VERTICAL
))
2445 if (infoPtr
->leftmostVisible
>= iSelected
)
2447 infoPtr
->leftmostVisible
= iSelected
;
2451 TAB_ITEM
*selected
= TAB_GetItem(infoPtr
, iSelected
);
2456 /* Calculate the part of the client area that is visible */
2457 GetClientRect(infoPtr
->hwnd
, &r
);
2460 GetClientRect(infoPtr
->hwndUpDown
, &r
);
2463 if ((selected
->rect
.right
-
2464 selected
->rect
.left
) >= width
)
2466 /* Special case: width of selected item is greater than visible
2469 infoPtr
->leftmostVisible
= iSelected
;
2473 for (i
= infoPtr
->leftmostVisible
; i
< infoPtr
->uNumItem
; i
++)
2475 if ((selected
->rect
.right
- TAB_GetItem(infoPtr
, i
)->rect
.left
) < width
)
2478 infoPtr
->leftmostVisible
= i
;
2482 if (infoPtr
->leftmostVisible
!= iOrigLeftmostVisible
)
2483 TAB_RecalcHotTrack(infoPtr
, NULL
, NULL
, NULL
);
2485 SendMessageW(infoPtr
->hwndUpDown
, UDM_SETPOS
, 0,
2486 MAKELONG(infoPtr
->leftmostVisible
, 0));
2489 /******************************************************************************
2490 * TAB_InvalidateTabArea
2492 * This method will invalidate the portion of the control that contains the
2493 * tabs. It is called when the state of the control changes and needs
2496 static void TAB_InvalidateTabArea(TAB_INFO
* infoPtr
)
2498 RECT clientRect
, rInvalidate
, rAdjClient
;
2499 DWORD lStyle
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
2500 INT lastRow
= infoPtr
->uNumRows
- 1;
2503 if (lastRow
< 0) return;
2505 GetClientRect(infoPtr
->hwnd
, &clientRect
);
2506 rInvalidate
= clientRect
;
2507 rAdjClient
= clientRect
;
2509 TAB_AdjustRect(infoPtr
, 0, &rAdjClient
);
2511 TAB_InternalGetItemRect(infoPtr
, infoPtr
->uNumItem
-1 , &rect
, NULL
);
2512 if ((lStyle
& TCS_BOTTOM
) && (lStyle
& TCS_VERTICAL
))
2514 rInvalidate
.left
= rAdjClient
.right
;
2515 if (infoPtr
->uNumRows
== 1)
2516 rInvalidate
.bottom
= clientRect
.top
+ rect
.bottom
+ 2 * SELECTED_TAB_OFFSET
;
2518 else if(lStyle
& TCS_VERTICAL
)
2520 rInvalidate
.right
= rAdjClient
.left
;
2521 if (infoPtr
->uNumRows
== 1)
2522 rInvalidate
.bottom
= clientRect
.top
+ rect
.bottom
+ 2 * SELECTED_TAB_OFFSET
;
2524 else if (lStyle
& TCS_BOTTOM
)
2526 rInvalidate
.top
= rAdjClient
.bottom
;
2527 if (infoPtr
->uNumRows
== 1)
2528 rInvalidate
.right
= clientRect
.left
+ rect
.right
+ 2 * SELECTED_TAB_OFFSET
;
2532 rInvalidate
.bottom
= rAdjClient
.top
;
2533 if (infoPtr
->uNumRows
== 1)
2534 rInvalidate
.right
= clientRect
.left
+ rect
.right
+ 2 * SELECTED_TAB_OFFSET
;
2537 /* Punch out the updown control */
2538 if (infoPtr
->needsScrolling
&& (rInvalidate
.right
> 0)) {
2540 GetClientRect(infoPtr
->hwndUpDown
, &r
);
2541 if (rInvalidate
.right
> clientRect
.right
- r
.left
)
2542 rInvalidate
.right
= rInvalidate
.right
- (r
.right
- r
.left
);
2544 rInvalidate
.right
= clientRect
.right
- r
.left
;
2547 TRACE("invalidate (%ld,%ld)-(%ld,%ld)\n",
2548 rInvalidate
.left
, rInvalidate
.top
,
2549 rInvalidate
.right
, rInvalidate
.bottom
);
2551 InvalidateRect(infoPtr
->hwnd
, &rInvalidate
, TRUE
);
2554 static inline LRESULT
TAB_Paint (TAB_INFO
*infoPtr
, HDC hdcPaint
)
2563 hdc
= BeginPaint (infoPtr
->hwnd
, &ps
);
2564 TRACE("erase %d, rect=(%ld,%ld)-(%ld,%ld)\n",
2566 ps
.rcPaint
.left
,ps
.rcPaint
.top
,ps
.rcPaint
.right
,ps
.rcPaint
.bottom
);
2569 TAB_Refresh (infoPtr
, hdc
);
2572 EndPaint (infoPtr
->hwnd
, &ps
);
2578 TAB_InsertItemT (TAB_INFO
*infoPtr
, WPARAM wParam
, LPARAM lParam
, BOOL bUnicode
)
2585 GetClientRect (infoPtr
->hwnd
, &rect
);
2586 TRACE("Rect: %p T %li, L %li, B %li, R %li\n", infoPtr
->hwnd
,
2587 rect
.top
, rect
.left
, rect
.bottom
, rect
.right
);
2589 pti
= (TCITEMW
*)lParam
;
2590 iItem
= (INT
)wParam
;
2592 if (iItem
< 0) return -1;
2593 if (iItem
> infoPtr
->uNumItem
)
2594 iItem
= infoPtr
->uNumItem
;
2596 TAB_DumpItemExternalT(pti
, iItem
, bUnicode
);
2599 if (infoPtr
->uNumItem
== 0) {
2600 infoPtr
->items
= Alloc (TAB_ITEM_SIZE(infoPtr
));
2601 infoPtr
->uNumItem
++;
2602 infoPtr
->iSelected
= 0;
2605 LPBYTE oldItems
= (LPBYTE
)infoPtr
->items
;
2607 infoPtr
->uNumItem
++;
2608 infoPtr
->items
= Alloc (TAB_ITEM_SIZE(infoPtr
) * infoPtr
->uNumItem
);
2610 /* pre insert copy */
2612 memcpy (infoPtr
->items
, oldItems
,
2613 iItem
* TAB_ITEM_SIZE(infoPtr
));
2616 /* post insert copy */
2617 if (iItem
< infoPtr
->uNumItem
- 1) {
2618 memcpy (TAB_GetItem(infoPtr
, iItem
+ 1),
2619 oldItems
+ iItem
* TAB_ITEM_SIZE(infoPtr
),
2620 (infoPtr
->uNumItem
- iItem
- 1) * TAB_ITEM_SIZE(infoPtr
));
2624 if (iItem
<= infoPtr
->iSelected
)
2625 infoPtr
->iSelected
++;
2630 item
= TAB_GetItem(infoPtr
, iItem
);
2632 item
->mask
= pti
->mask
;
2633 item
->pszText
= NULL
;
2635 if (pti
->mask
& TCIF_TEXT
)
2638 Str_SetPtrW (&item
->pszText
, pti
->pszText
);
2640 Str_SetPtrAtoW (&item
->pszText
, (LPSTR
)pti
->pszText
);
2643 if (pti
->mask
& TCIF_IMAGE
)
2644 item
->iImage
= pti
->iImage
;
2648 if (pti
->mask
& TCIF_PARAM
)
2649 memcpy(item
->extra
, &pti
->lParam
, infoPtr
->cbInfo
);
2651 memset(item
->extra
, 0, infoPtr
->cbInfo
);
2653 TAB_SetItemBounds(infoPtr
);
2654 if (infoPtr
->uNumItem
> 1)
2655 TAB_InvalidateTabArea(infoPtr
);
2657 InvalidateRect(infoPtr
->hwnd
, NULL
, TRUE
);
2659 TRACE("[%p]: added item %d %s\n",
2660 infoPtr
->hwnd
, iItem
, debugstr_w(item
->pszText
));
2666 TAB_SetItemSize (TAB_INFO
*infoPtr
, LPARAM lParam
)
2668 LONG lStyle
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
2670 BOOL bNeedPaint
= FALSE
;
2672 lResult
= MAKELONG(infoPtr
->tabWidth
, infoPtr
->tabHeight
);
2674 /* UNDOCUMENTED: If requested Width or Height is 0 this means that program wants to use auto size. */
2675 if (lStyle
& TCS_FIXEDWIDTH
&& (infoPtr
->tabWidth
!= (INT
)LOWORD(lParam
)))
2677 infoPtr
->tabWidth
= (INT
)LOWORD(lParam
);
2681 if (infoPtr
->tabHeight
!= (INT
)HIWORD(lParam
))
2683 if ((infoPtr
->fHeightSet
= ((INT
)HIWORD(lParam
) != 0)))
2684 infoPtr
->tabHeight
= (INT
)HIWORD(lParam
);
2688 TRACE("was h=%d,w=%d, now h=%d,w=%d\n",
2689 HIWORD(lResult
), LOWORD(lResult
),
2690 infoPtr
->tabHeight
, infoPtr
->tabWidth
);
2694 TAB_SetItemBounds(infoPtr
);
2695 RedrawWindow(infoPtr
->hwnd
, NULL
, NULL
, RDW_ERASE
| RDW_INVALIDATE
| RDW_UPDATENOW
);
2701 static inline LRESULT
TAB_SetMinTabWidth (TAB_INFO
*infoPtr
, INT cx
)
2705 TRACE("(%p,%d)\n", infoPtr
, cx
);
2708 oldcx
= infoPtr
->tabMinWidth
;
2709 infoPtr
->tabMinWidth
= cx
;
2711 TAB_SetItemBounds(infoPtr
);
2716 static inline LRESULT
2717 TAB_HighlightItem (TAB_INFO
*infoPtr
, INT iItem
, BOOL fHighlight
)
2721 TRACE("(%p,%d,%s)\n", infoPtr
, iItem
, fHighlight
? "true" : "false");
2723 if (!infoPtr
|| iItem
< 0 || iItem
>= infoPtr
->uNumItem
)
2726 lpState
= &TAB_GetItem(infoPtr
, iItem
)->dwState
;
2729 *lpState
|= TCIS_HIGHLIGHTED
;
2731 *lpState
&= ~TCIS_HIGHLIGHTED
;
2737 TAB_SetItemT (TAB_INFO
*infoPtr
, INT iItem
, LPTCITEMW tabItem
, BOOL bUnicode
)
2741 TRACE("(%p,%d,%p,%s)\n", infoPtr
, iItem
, tabItem
, bUnicode
? "true" : "false");
2743 if (iItem
< 0 || iItem
>= infoPtr
->uNumItem
)
2746 TAB_DumpItemExternalT(tabItem
, iItem
, bUnicode
);
2748 wineItem
= TAB_GetItem(infoPtr
, iItem
);
2750 if (tabItem
->mask
& TCIF_IMAGE
)
2751 wineItem
->iImage
= tabItem
->iImage
;
2753 if (tabItem
->mask
& TCIF_PARAM
)
2754 memcpy(wineItem
->extra
, &tabItem
->lParam
, infoPtr
->cbInfo
);
2756 if (tabItem
->mask
& TCIF_RTLREADING
)
2757 FIXME("TCIF_RTLREADING\n");
2759 if (tabItem
->mask
& TCIF_STATE
)
2760 wineItem
->dwState
= tabItem
->dwState
;
2762 if (tabItem
->mask
& TCIF_TEXT
)
2764 if (wineItem
->pszText
)
2766 Free(wineItem
->pszText
);
2767 wineItem
->pszText
= NULL
;
2770 Str_SetPtrW(&wineItem
->pszText
, tabItem
->pszText
);
2772 Str_SetPtrAtoW(&wineItem
->pszText
, (LPSTR
)tabItem
->pszText
);
2775 /* Update and repaint tabs */
2776 TAB_SetItemBounds(infoPtr
);
2777 TAB_InvalidateTabArea(infoPtr
);
2782 static inline LRESULT
TAB_GetItemCount (const TAB_INFO
*infoPtr
)
2784 return infoPtr
->uNumItem
;
2789 TAB_GetItemT (TAB_INFO
*infoPtr
, INT iItem
, LPTCITEMW tabItem
, BOOL bUnicode
)
2793 TRACE("(%p,%d,%p,%s)\n", infoPtr
, iItem
, tabItem
, bUnicode
? "true" : "false");
2795 if (iItem
< 0 || iItem
>= infoPtr
->uNumItem
)
2798 wineItem
= TAB_GetItem(infoPtr
, iItem
);
2800 if (tabItem
->mask
& TCIF_IMAGE
)
2801 tabItem
->iImage
= wineItem
->iImage
;
2803 if (tabItem
->mask
& TCIF_PARAM
)
2804 memcpy(&tabItem
->lParam
, wineItem
->extra
, infoPtr
->cbInfo
);
2806 if (tabItem
->mask
& TCIF_RTLREADING
)
2807 FIXME("TCIF_RTLREADING\n");
2809 if (tabItem
->mask
& TCIF_STATE
)
2810 tabItem
->dwState
= wineItem
->dwState
;
2812 if (tabItem
->mask
& TCIF_TEXT
)
2815 Str_GetPtrW (wineItem
->pszText
, tabItem
->pszText
, tabItem
->cchTextMax
);
2817 Str_GetPtrWtoA (wineItem
->pszText
, (LPSTR
)tabItem
->pszText
, tabItem
->cchTextMax
);
2820 TAB_DumpItemExternalT(tabItem
, iItem
, bUnicode
);
2826 static LRESULT
TAB_DeleteItem (TAB_INFO
*infoPtr
, INT iItem
)
2828 BOOL bResult
= FALSE
;
2830 TRACE("(%p, %d)\n", infoPtr
, iItem
);
2832 if ((iItem
>= 0) && (iItem
< infoPtr
->uNumItem
))
2834 TAB_ITEM
*item
= TAB_GetItem(infoPtr
, iItem
);
2835 LPBYTE oldItems
= (LPBYTE
)infoPtr
->items
;
2837 TAB_InvalidateTabArea(infoPtr
);
2839 if ((item
->mask
& TCIF_TEXT
) && item
->pszText
)
2840 Free(item
->pszText
);
2842 infoPtr
->uNumItem
--;
2844 if (!infoPtr
->uNumItem
)
2846 infoPtr
->items
= NULL
;
2847 if (infoPtr
->iHotTracked
>= 0)
2849 KillTimer(infoPtr
->hwnd
, TAB_HOTTRACK_TIMER
);
2850 infoPtr
->iHotTracked
= -1;
2855 infoPtr
->items
= Alloc(TAB_ITEM_SIZE(infoPtr
) * infoPtr
->uNumItem
);
2858 memcpy(infoPtr
->items
, oldItems
, iItem
* TAB_ITEM_SIZE(infoPtr
));
2860 if (iItem
< infoPtr
->uNumItem
)
2861 memcpy(TAB_GetItem(infoPtr
, iItem
),
2862 oldItems
+ (iItem
+ 1) * TAB_ITEM_SIZE(infoPtr
),
2863 (infoPtr
->uNumItem
- iItem
) * TAB_ITEM_SIZE(infoPtr
));
2865 if (iItem
<= infoPtr
->iHotTracked
)
2867 /* When tabs move left/up, the hot track item may change */
2868 FIXME("Recalc hot track");
2873 /* Readjust the selected index */
2874 if ((iItem
== infoPtr
->iSelected
) && (iItem
> 0))
2875 infoPtr
->iSelected
--;
2877 if (iItem
< infoPtr
->iSelected
)
2878 infoPtr
->iSelected
--;
2880 if (infoPtr
->uNumItem
== 0)
2881 infoPtr
->iSelected
= -1;
2883 /* Reposition and repaint tabs */
2884 TAB_SetItemBounds(infoPtr
);
2892 static inline LRESULT
TAB_DeleteAllItems (TAB_INFO
*infoPtr
)
2894 TRACE("(%p)\n", infoPtr
);
2895 while (infoPtr
->uNumItem
)
2896 TAB_DeleteItem (infoPtr
, 0);
2901 static inline LRESULT
TAB_GetFont (const TAB_INFO
*infoPtr
)
2903 TRACE("(%p) returning %p\n", infoPtr
, infoPtr
->hFont
);
2904 return (LRESULT
)infoPtr
->hFont
;
2907 static inline LRESULT
TAB_SetFont (TAB_INFO
*infoPtr
, HFONT hNewFont
)
2909 TRACE("(%p,%p)\n", infoPtr
, hNewFont
);
2911 infoPtr
->hFont
= hNewFont
;
2913 TAB_SetItemBounds(infoPtr
);
2915 TAB_InvalidateTabArea(infoPtr
);
2921 static inline LRESULT
TAB_GetImageList (const TAB_INFO
*infoPtr
)
2924 return (LRESULT
)infoPtr
->himl
;
2927 static inline LRESULT
TAB_SetImageList (TAB_INFO
*infoPtr
, HIMAGELIST himlNew
)
2929 HIMAGELIST himlPrev
= infoPtr
->himl
;
2931 infoPtr
->himl
= himlNew
;
2932 return (LRESULT
)himlPrev
;
2935 static inline LRESULT
TAB_GetUnicodeFormat (const TAB_INFO
*infoPtr
)
2937 return infoPtr
->bUnicode
;
2940 static inline LRESULT
TAB_SetUnicodeFormat (TAB_INFO
*infoPtr
, BOOL bUnicode
)
2942 BOOL bTemp
= infoPtr
->bUnicode
;
2944 infoPtr
->bUnicode
= bUnicode
;
2949 static inline LRESULT
TAB_Size (TAB_INFO
*infoPtr
)
2951 /* I'm not really sure what the following code was meant to do.
2952 This is what it is doing:
2953 When WM_SIZE is sent with SIZE_RESTORED, the control
2954 gets positioned in the top left corner.
2958 UINT uPosFlags,cx,cy;
2962 parent = GetParent (hwnd);
2963 GetClientRect(parent, &parent_rect);
2966 if (GetWindowLongW(hwnd, GWL_STYLE) & CCS_NORESIZE)
2967 uPosFlags |= (SWP_NOSIZE | SWP_NOMOVE);
2969 SetWindowPos (hwnd, 0, parent_rect.left, parent_rect.top,
2970 cx, cy, uPosFlags | SWP_NOZORDER);
2972 FIXME("WM_SIZE flag %x %lx not handled\n", wParam, lParam);
2975 /* Recompute the size/position of the tabs. */
2976 TAB_SetItemBounds (infoPtr
);
2978 /* Force a repaint of the control. */
2979 InvalidateRect(infoPtr
->hwnd
, NULL
, TRUE
);
2985 static LRESULT
TAB_Create (HWND hwnd
, WPARAM wParam
, LPARAM lParam
)
2988 TEXTMETRICW fontMetrics
;
2993 infoPtr
= (TAB_INFO
*)Alloc (sizeof(TAB_INFO
));
2995 SetWindowLongPtrW(hwnd
, 0, (DWORD_PTR
)infoPtr
);
2997 infoPtr
->hwnd
= hwnd
;
2998 infoPtr
->hwndNotify
= ((LPCREATESTRUCTW
)lParam
)->hwndParent
;
2999 infoPtr
->uNumItem
= 0;
3000 infoPtr
->uNumRows
= 0;
3001 infoPtr
->uHItemPadding
= 6;
3002 infoPtr
->uVItemPadding
= 3;
3003 infoPtr
->uHItemPadding_s
= 6;
3004 infoPtr
->uVItemPadding_s
= 3;
3007 infoPtr
->hcurArrow
= LoadCursorW (0, (LPWSTR
)IDC_ARROW
);
3008 infoPtr
->iSelected
= -1;
3009 infoPtr
->iHotTracked
= -1;
3010 infoPtr
->uFocus
= -1;
3011 infoPtr
->hwndToolTip
= 0;
3012 infoPtr
->DoRedraw
= TRUE
;
3013 infoPtr
->needsScrolling
= FALSE
;
3014 infoPtr
->hwndUpDown
= 0;
3015 infoPtr
->leftmostVisible
= 0;
3016 infoPtr
->fHeightSet
= FALSE
;
3017 infoPtr
->bUnicode
= IsWindowUnicode (hwnd
);
3018 infoPtr
->cbInfo
= sizeof(LPARAM
);
3020 TRACE("Created tab control, hwnd [%p]\n", hwnd
);
3022 /* The tab control always has the WS_CLIPSIBLINGS style. Even
3023 if you don't specify it in CreateWindow. This is necessary in
3024 order for paint to work correctly. This follows windows behaviour. */
3025 dwStyle
= GetWindowLongW(hwnd
, GWL_STYLE
);
3026 SetWindowLongW(hwnd
, GWL_STYLE
, dwStyle
|WS_CLIPSIBLINGS
);
3028 if (dwStyle
& TCS_TOOLTIPS
) {
3029 /* Create tooltip control */
3030 infoPtr
->hwndToolTip
=
3031 CreateWindowExW (0, TOOLTIPS_CLASSW
, NULL
, 0,
3032 CW_USEDEFAULT
, CW_USEDEFAULT
,
3033 CW_USEDEFAULT
, CW_USEDEFAULT
,
3036 /* Send NM_TOOLTIPSCREATED notification */
3037 if (infoPtr
->hwndToolTip
) {
3038 NMTOOLTIPSCREATED nmttc
;
3040 nmttc
.hdr
.hwndFrom
= hwnd
;
3041 nmttc
.hdr
.idFrom
= GetWindowLongPtrW(hwnd
, GWLP_ID
);
3042 nmttc
.hdr
.code
= NM_TOOLTIPSCREATED
;
3043 nmttc
.hwndToolTips
= infoPtr
->hwndToolTip
;
3045 SendMessageW (infoPtr
->hwndNotify
, WM_NOTIFY
,
3046 (WPARAM
)GetWindowLongPtrW(hwnd
, GWLP_ID
), (LPARAM
)&nmttc
);
3050 OpenThemeData (infoPtr
->hwnd
, themeClass
);
3053 * We need to get text information so we need a DC and we need to select
3057 hOldFont
= SelectObject (hdc
, GetStockObject (SYSTEM_FONT
));
3059 /* Use the system font to determine the initial height of a tab. */
3060 GetTextMetricsW(hdc
, &fontMetrics
);
3063 * Make sure there is enough space for the letters + growing the
3064 * selected item + extra space for the selected item.
3066 infoPtr
->tabHeight
= fontMetrics
.tmHeight
+ SELECTED_TAB_OFFSET
+
3067 ((dwStyle
& TCS_BUTTONS
) ? 2 : 1) *
3068 infoPtr
->uVItemPadding
;
3070 /* Initialize the width of a tab. */
3071 if (dwStyle
& TCS_FIXEDWIDTH
)
3072 infoPtr
->tabWidth
= DEFAULT_TAB_WIDTH_FIXED
;
3074 infoPtr
->tabMinWidth
= -1;
3076 TRACE("tabH=%d, tabW=%d\n", infoPtr
->tabHeight
, infoPtr
->tabWidth
);
3078 SelectObject (hdc
, hOldFont
);
3079 ReleaseDC(hwnd
, hdc
);
3085 TAB_Destroy (TAB_INFO
*infoPtr
)
3092 SetWindowLongPtrW(infoPtr
->hwnd
, 0, 0);
3094 if (infoPtr
->items
) {
3095 for (iItem
= 0; iItem
< infoPtr
->uNumItem
; iItem
++) {
3096 if (TAB_GetItem(infoPtr
, iItem
)->pszText
)
3097 Free (TAB_GetItem(infoPtr
, iItem
)->pszText
);
3099 Free (infoPtr
->items
);
3102 if (infoPtr
->hwndToolTip
)
3103 DestroyWindow (infoPtr
->hwndToolTip
);
3105 if (infoPtr
->hwndUpDown
)
3106 DestroyWindow(infoPtr
->hwndUpDown
);
3108 if (infoPtr
->iHotTracked
>= 0)
3109 KillTimer(infoPtr
->hwnd
, TAB_HOTTRACK_TIMER
);
3111 CloseThemeData (GetWindowTheme (infoPtr
->hwnd
));
3117 /* update theme after a WM_THEMECHANGED message */
3118 static LRESULT
theme_changed (TAB_INFO
* infoPtr
)
3120 HTHEME theme
= GetWindowTheme (infoPtr
->hwnd
);
3121 CloseThemeData (theme
);
3122 OpenThemeData (infoPtr
->hwnd
, themeClass
);
3126 static LRESULT
TAB_NCCalcSize(HWND hwnd
, WPARAM wParam
, LPARAM lParam
)
3130 return WVR_ALIGNTOP
;
3133 static inline LRESULT
3134 TAB_SetItemExtra (TAB_INFO
*infoPtr
, INT cbInfo
)
3136 if (!infoPtr
|| cbInfo
<= 0)
3139 if (infoPtr
->uNumItem
)
3141 /* FIXME: MSDN says this is not allowed, but this hasn't been verified */
3145 infoPtr
->cbInfo
= cbInfo
;
3149 static LRESULT WINAPI
3150 TAB_WindowProc (HWND hwnd
, UINT uMsg
, WPARAM wParam
, LPARAM lParam
)
3152 TAB_INFO
*infoPtr
= TAB_GetInfoPtr(hwnd
);
3154 TRACE("hwnd=%p msg=%x wParam=%x lParam=%lx\n", hwnd
, uMsg
, wParam
, lParam
);
3155 if (!infoPtr
&& (uMsg
!= WM_CREATE
))
3156 return DefWindowProcW (hwnd
, uMsg
, wParam
, lParam
);
3160 case TCM_GETIMAGELIST
:
3161 return TAB_GetImageList (infoPtr
);
3163 case TCM_SETIMAGELIST
:
3164 return TAB_SetImageList (infoPtr
, (HIMAGELIST
)lParam
);
3166 case TCM_GETITEMCOUNT
:
3167 return TAB_GetItemCount (infoPtr
);
3171 return TAB_GetItemT (infoPtr
, (INT
)wParam
, (LPTCITEMW
)lParam
, uMsg
== TCM_GETITEMW
);
3175 return TAB_SetItemT (infoPtr
, (INT
)wParam
, (LPTCITEMW
)lParam
, uMsg
== TCM_SETITEMW
);
3177 case TCM_DELETEITEM
:
3178 return TAB_DeleteItem (infoPtr
, (INT
)wParam
);
3180 case TCM_DELETEALLITEMS
:
3181 return TAB_DeleteAllItems (infoPtr
);
3183 case TCM_GETITEMRECT
:
3184 return TAB_GetItemRect (infoPtr
, wParam
, lParam
);
3187 return TAB_GetCurSel (infoPtr
);
3190 return TAB_HitTest (infoPtr
, (LPTCHITTESTINFO
)lParam
);
3193 return TAB_SetCurSel (infoPtr
, (INT
)wParam
);
3195 case TCM_INSERTITEMA
:
3196 case TCM_INSERTITEMW
:
3197 return TAB_InsertItemT (infoPtr
, wParam
, lParam
, uMsg
== TCM_INSERTITEMW
);
3199 case TCM_SETITEMEXTRA
:
3200 return TAB_SetItemExtra (infoPtr
, (int)wParam
);
3202 case TCM_ADJUSTRECT
:
3203 return TAB_AdjustRect (infoPtr
, (BOOL
)wParam
, (LPRECT
)lParam
);
3205 case TCM_SETITEMSIZE
:
3206 return TAB_SetItemSize (infoPtr
, lParam
);
3208 case TCM_REMOVEIMAGE
:
3209 FIXME("Unimplemented msg TCM_REMOVEIMAGE\n");
3212 case TCM_SETPADDING
:
3213 return TAB_SetPadding (infoPtr
, lParam
);
3215 case TCM_GETROWCOUNT
:
3216 return TAB_GetRowCount(infoPtr
);
3218 case TCM_GETUNICODEFORMAT
:
3219 return TAB_GetUnicodeFormat (infoPtr
);
3221 case TCM_SETUNICODEFORMAT
:
3222 return TAB_SetUnicodeFormat (infoPtr
, (BOOL
)wParam
);
3224 case TCM_HIGHLIGHTITEM
:
3225 return TAB_HighlightItem (infoPtr
, (INT
)wParam
, (BOOL
)LOWORD(lParam
));
3227 case TCM_GETTOOLTIPS
:
3228 return TAB_GetToolTips (infoPtr
);
3230 case TCM_SETTOOLTIPS
:
3231 return TAB_SetToolTips (infoPtr
, (HWND
)wParam
);
3233 case TCM_GETCURFOCUS
:
3234 return TAB_GetCurFocus (infoPtr
);
3236 case TCM_SETCURFOCUS
:
3237 return TAB_SetCurFocus (infoPtr
, (INT
)wParam
);
3239 case TCM_SETMINTABWIDTH
:
3240 return TAB_SetMinTabWidth(infoPtr
, (INT
)lParam
);
3242 case TCM_DESELECTALL
:
3243 FIXME("Unimplemented msg TCM_DESELECTALL\n");
3246 case TCM_GETEXTENDEDSTYLE
:
3247 FIXME("Unimplemented msg TCM_GETEXTENDEDSTYLE\n");
3250 case TCM_SETEXTENDEDSTYLE
:
3251 FIXME("Unimplemented msg TCM_SETEXTENDEDSTYLE\n");
3255 return TAB_GetFont (infoPtr
);
3258 return TAB_SetFont (infoPtr
, (HFONT
)wParam
);
3261 return TAB_Create (hwnd
, wParam
, lParam
);
3264 return TAB_Destroy (infoPtr
);
3267 return DLGC_WANTARROWS
| DLGC_WANTCHARS
;
3269 case WM_LBUTTONDOWN
:
3270 return TAB_LButtonDown (infoPtr
, wParam
, lParam
);
3273 return TAB_LButtonUp (infoPtr
);
3276 return SendMessageW(infoPtr
->hwndNotify
, WM_NOTIFY
, wParam
, lParam
);
3278 case WM_RBUTTONDOWN
:
3279 return TAB_RButtonDown (infoPtr
);
3282 return TAB_MouseMove (infoPtr
, wParam
, lParam
);
3285 return TAB_Paint (infoPtr
, (HDC
)wParam
);
3288 return TAB_Size (infoPtr
);
3291 return TAB_SetRedraw (infoPtr
, (BOOL
)wParam
);
3294 return TAB_OnHScroll(infoPtr
, (int)LOWORD(wParam
), (int)HIWORD(wParam
), (HWND
)lParam
);
3296 case WM_STYLECHANGED
:
3297 TAB_SetItemBounds (infoPtr
);
3298 InvalidateRect(hwnd
, NULL
, TRUE
);
3301 case WM_SYSCOLORCHANGE
:
3302 COMCTL32_RefreshSysColors();
3305 case WM_THEMECHANGED
:
3306 return theme_changed (infoPtr
);
3310 TAB_FocusChanging(infoPtr
);
3311 break; /* Don't disturb normal focus behavior */
3314 return TAB_KeyUp(infoPtr
, wParam
);
3316 return TAB_NCHitTest(infoPtr
, lParam
);
3319 return TAB_NCCalcSize(hwnd
, wParam
, lParam
);
3322 if (uMsg
>= WM_USER
&& uMsg
< WM_APP
)
3323 WARN("unknown msg %04x wp=%08x lp=%08lx\n",
3324 uMsg
, wParam
, lParam
);
3327 return DefWindowProcW(hwnd
, uMsg
, wParam
, lParam
);
3336 ZeroMemory (&wndClass
, sizeof(WNDCLASSW
));
3337 wndClass
.style
= CS_GLOBALCLASS
| CS_DBLCLKS
| CS_HREDRAW
| CS_VREDRAW
;
3338 wndClass
.lpfnWndProc
= TAB_WindowProc
;
3339 wndClass
.cbClsExtra
= 0;
3340 wndClass
.cbWndExtra
= sizeof(TAB_INFO
*);
3341 wndClass
.hCursor
= LoadCursorW (0, (LPWSTR
)IDC_ARROW
);
3342 wndClass
.hbrBackground
= (HBRUSH
)(COLOR_BTNFACE
+1);
3343 wndClass
.lpszClassName
= WC_TABCONTROLW
;
3345 RegisterClassW (&wndClass
);
3350 TAB_Unregister (void)
3352 UnregisterClassW (WC_TABCONTROLW
, NULL
);