4 * Copyright 1998 Anders Carlsson
5 * Copyright 1999 Alex Priem <alexp@sci.kun.nl>
6 * Copyright 1999 Francis Beaudet
7 * Copyright 2003 Vitaliy Margolen
9 * This library is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Lesser General Public
11 * License as published by the Free Software Foundation; either
12 * version 2.1 of the License, or (at your option) any later version.
14 * This library is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Lesser General Public License for more details.
19 * You should have received a copy of the GNU Lesser General Public
20 * License along with this library; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
25 * This code was audited for completeness against the documented features
26 * of Comctl32.dll version 6.0 on May. 20, 2005, by James Hawkins.
28 * Unless otherwise noted, we believe this code to be complete, as per
29 * the specification mentioned above.
30 * If you discover missing features, or bugs, please note them below.
43 * TCS_EX_FLATSEPARATORS
58 * TCM_GETEXTENDEDSTYLE
59 * TCM_SETEXTENDEDSTYLE
78 #include "wine/debug.h"
81 WINE_DEFAULT_DEBUG_CHANNEL(tab
);
88 RECT rect
; /* bounding rectangle of the item relative to the
89 * leftmost item (the leftmost item, 0, would have a
90 * "left" member of 0 in this rectangle)
92 * additionally the top member holds the row number
93 * and bottom is unused and should be 0 */
94 BYTE extra
[1]; /* Space for caller supplied info, variable size */
97 /* The size of a tab item depends on how much extra data is requested */
98 #define TAB_ITEM_SIZE(infoPtr) (FIELD_OFFSET(TAB_ITEM, extra[(infoPtr)->cbInfo]))
102 HWND hwnd
; /* Tab control window */
103 HWND hwndNotify
; /* notification window (parent) */
104 UINT uNumItem
; /* number of tab items */
105 UINT uNumRows
; /* number of tab rows */
106 INT tabHeight
; /* height of the tab row */
107 INT tabWidth
; /* width of tabs */
108 INT tabMinWidth
; /* minimum width of items */
109 USHORT uHItemPadding
; /* amount of horizontal padding, in pixels */
110 USHORT uVItemPadding
; /* amount of vertical padding, in pixels */
111 USHORT uHItemPadding_s
; /* Set amount of horizontal padding, in pixels */
112 USHORT uVItemPadding_s
; /* Set amount of vertical padding, in pixels */
113 HFONT hFont
; /* handle to the current font */
114 HCURSOR hcurArrow
; /* handle to the current cursor */
115 HIMAGELIST himl
; /* handle to an image list (may be 0) */
116 HWND hwndToolTip
; /* handle to tab's tooltip */
117 INT leftmostVisible
; /* Used for scrolling, this member contains
118 * the index of the first visible item */
119 INT iSelected
; /* the currently selected item */
120 INT iHotTracked
; /* the highlighted item under the mouse */
121 INT uFocus
; /* item which has the focus */
122 TAB_ITEM
* items
; /* pointer to an array of TAB_ITEM's */
123 BOOL DoRedraw
; /* flag for redrawing when tab contents is changed*/
124 BOOL needsScrolling
; /* TRUE if the size of the tabs is greater than
125 * the size of the control */
126 BOOL fHeightSet
; /* was the height of the tabs explicitly set? */
127 BOOL bUnicode
; /* Unicode control? */
128 HWND hwndUpDown
; /* Updown control used for scrolling */
129 INT cbInfo
; /* Number of bytes of caller supplied info per tab */
132 /******************************************************************************
133 * Positioning constants
135 #define SELECTED_TAB_OFFSET 2
136 #define ROUND_CORNER_SIZE 2
137 #define DISPLAY_AREA_PADDINGX 2
138 #define DISPLAY_AREA_PADDINGY 2
139 #define CONTROL_BORDER_SIZEX 2
140 #define CONTROL_BORDER_SIZEY 2
141 #define BUTTON_SPACINGX 3
142 #define BUTTON_SPACINGY 3
143 #define FLAT_BTN_SPACINGX 8
144 #define DEFAULT_MIN_TAB_WIDTH 54
145 #define DEFAULT_PADDING_X 6
146 #define EXTRA_ICON_PADDING 3
148 #define TAB_GetInfoPtr(hwnd) ((TAB_INFO *)GetWindowLongPtrW(hwnd,0))
149 /* Since items are variable sized, cannot directly access them */
150 #define TAB_GetItem(info,i) \
151 ((TAB_ITEM*)((LPBYTE)info->items + (i) * TAB_ITEM_SIZE(info)))
153 #define GET_DEFAULT_MIN_TAB_WIDTH(infoPtr) (DEFAULT_MIN_TAB_WIDTH - (DEFAULT_PADDING_X - (infoPtr)->uHItemPadding) * 2)
155 /******************************************************************************
156 * Hot-tracking timer constants
158 #define TAB_HOTTRACK_TIMER 1
159 #define TAB_HOTTRACK_TIMER_INTERVAL 100 /* milliseconds */
161 static const WCHAR themeClass
[] = { 'T','a','b',0 };
163 /******************************************************************************
166 static void TAB_InvalidateTabArea(const TAB_INFO
*);
167 static void TAB_EnsureSelectionVisible(TAB_INFO
*);
168 static void TAB_DrawItemInterior(const TAB_INFO
*, HDC
, INT
, RECT
*);
171 TAB_SendSimpleNotify (const TAB_INFO
*infoPtr
, UINT code
)
175 nmhdr
.hwndFrom
= infoPtr
->hwnd
;
176 nmhdr
.idFrom
= GetWindowLongPtrW(infoPtr
->hwnd
, GWLP_ID
);
179 return (BOOL
) SendMessageW (infoPtr
->hwndNotify
, WM_NOTIFY
,
180 nmhdr
.idFrom
, (LPARAM
) &nmhdr
);
184 TAB_RelayEvent (HWND hwndTip
, HWND hwndMsg
, UINT uMsg
,
185 WPARAM wParam
, LPARAM lParam
)
193 msg
.time
= GetMessageTime ();
194 msg
.pt
.x
= (short)LOWORD(GetMessagePos ());
195 msg
.pt
.y
= (short)HIWORD(GetMessagePos ());
197 SendMessageW (hwndTip
, TTM_RELAYEVENT
, 0, (LPARAM
)&msg
);
201 TAB_DumpItemExternalT(const TCITEMW
*pti
, UINT iItem
, BOOL isW
)
204 TRACE("external tab %d, mask=0x%08x, dwState=0x%08x, dwStateMask=0x%08x, cchTextMax=0x%08x\n",
205 iItem
, pti
->mask
, pti
->dwState
, pti
->dwStateMask
, pti
->cchTextMax
);
206 TRACE("external tab %d, iImage=%d, lParam=0x%08lx, pszTextW=%s\n",
207 iItem
, pti
->iImage
, pti
->lParam
, isW
? debugstr_w(pti
->pszText
) : debugstr_a((LPSTR
)pti
->pszText
));
212 TAB_DumpItemInternal(const TAB_INFO
*infoPtr
, UINT iItem
)
217 ti
= TAB_GetItem(infoPtr
, iItem
);
218 TRACE("tab %d, dwState=0x%08x, pszText=%s, iImage=%d\n",
219 iItem
, ti
->dwState
, debugstr_w(ti
->pszText
), ti
->iImage
);
220 TRACE("tab %d, rect.left=%d, rect.top(row)=%d\n",
221 iItem
, ti
->rect
.left
, ti
->rect
.top
);
226 * the index of the selected tab, or -1 if no tab is selected. */
227 static inline LRESULT
TAB_GetCurSel (const TAB_INFO
*infoPtr
)
229 return infoPtr
->iSelected
;
233 * the index of the tab item that has the focus. */
234 static inline LRESULT
235 TAB_GetCurFocus (const TAB_INFO
*infoPtr
)
237 return infoPtr
->uFocus
;
240 static inline LRESULT
TAB_GetToolTips (const TAB_INFO
*infoPtr
)
242 if (infoPtr
== NULL
) return 0;
243 return (LRESULT
)infoPtr
->hwndToolTip
;
246 static inline LRESULT
TAB_SetCurSel (TAB_INFO
*infoPtr
, INT iItem
)
248 INT prevItem
= infoPtr
->iSelected
;
251 infoPtr
->iSelected
=-1;
252 else if (iItem
>= infoPtr
->uNumItem
)
255 if (infoPtr
->iSelected
!= iItem
) {
256 infoPtr
->iSelected
=iItem
;
257 infoPtr
->uFocus
=iItem
;
258 TAB_EnsureSelectionVisible(infoPtr
);
259 TAB_InvalidateTabArea(infoPtr
);
265 static LRESULT
TAB_SetCurFocus (TAB_INFO
*infoPtr
, INT iItem
)
268 infoPtr
->uFocus
= -1;
269 else if (iItem
< infoPtr
->uNumItem
) {
270 if (GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
) & TCS_BUTTONS
) {
271 FIXME("Should set input focus\n");
273 int oldFocus
= infoPtr
->uFocus
;
274 if (infoPtr
->iSelected
!= iItem
|| oldFocus
== -1 ) {
275 infoPtr
->uFocus
= iItem
;
276 if (oldFocus
!= -1) {
277 if (!TAB_SendSimpleNotify(infoPtr
, TCN_SELCHANGING
)) {
278 infoPtr
->iSelected
= iItem
;
279 TAB_SendSimpleNotify(infoPtr
, TCN_SELCHANGE
);
282 infoPtr
->iSelected
= iItem
;
283 TAB_EnsureSelectionVisible(infoPtr
);
284 TAB_InvalidateTabArea(infoPtr
);
292 static inline LRESULT
293 TAB_SetToolTips (TAB_INFO
*infoPtr
, HWND hwndToolTip
)
296 infoPtr
->hwndToolTip
= hwndToolTip
;
300 static inline LRESULT
301 TAB_SetPadding (TAB_INFO
*infoPtr
, LPARAM lParam
)
305 infoPtr
->uHItemPadding_s
=LOWORD(lParam
);
306 infoPtr
->uVItemPadding_s
=HIWORD(lParam
);
311 /******************************************************************************
312 * TAB_InternalGetItemRect
314 * This method will calculate the rectangle representing a given tab item in
315 * client coordinates. This method takes scrolling into account.
317 * This method returns TRUE if the item is visible in the window and FALSE
318 * if it is completely outside the client area.
320 static BOOL
TAB_InternalGetItemRect(
321 const TAB_INFO
* infoPtr
,
326 RECT tmpItemRect
,clientRect
;
327 LONG lStyle
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
329 /* Perform a sanity check and a trivial visibility check. */
330 if ( (infoPtr
->uNumItem
<= 0) ||
331 (itemIndex
>= infoPtr
->uNumItem
) ||
332 (!((lStyle
& TCS_MULTILINE
) || (lStyle
& TCS_VERTICAL
)) && (itemIndex
< infoPtr
->leftmostVisible
)) )
334 TRACE("Not Visible\n");
335 /* need to initialize these to empty rects */
338 memset(itemRect
,0,sizeof(RECT
));
339 itemRect
->bottom
= infoPtr
->tabHeight
;
342 memset(selectedRect
,0,sizeof(RECT
));
347 * Avoid special cases in this procedure by assigning the "out"
348 * parameters if the caller didn't supply them
350 if (itemRect
== NULL
)
351 itemRect
= &tmpItemRect
;
353 /* Retrieve the unmodified item rect. */
354 *itemRect
= TAB_GetItem(infoPtr
,itemIndex
)->rect
;
356 /* calculate the times bottom and top based on the row */
357 GetClientRect(infoPtr
->hwnd
, &clientRect
);
359 if ((lStyle
& TCS_BOTTOM
) && (lStyle
& TCS_VERTICAL
))
361 itemRect
->right
= clientRect
.right
- SELECTED_TAB_OFFSET
- itemRect
->left
* infoPtr
->tabHeight
-
362 ((lStyle
& TCS_BUTTONS
) ? itemRect
->left
* BUTTON_SPACINGX
: 0);
363 itemRect
->left
= itemRect
->right
- infoPtr
->tabHeight
;
365 else if (lStyle
& TCS_VERTICAL
)
367 itemRect
->left
= clientRect
.left
+ SELECTED_TAB_OFFSET
+ itemRect
->left
* infoPtr
->tabHeight
+
368 ((lStyle
& TCS_BUTTONS
) ? itemRect
->left
* BUTTON_SPACINGX
: 0);
369 itemRect
->right
= itemRect
->left
+ infoPtr
->tabHeight
;
371 else if (lStyle
& TCS_BOTTOM
)
373 itemRect
->bottom
= clientRect
.bottom
- itemRect
->top
* infoPtr
->tabHeight
-
374 ((lStyle
& TCS_BUTTONS
) ? itemRect
->top
* BUTTON_SPACINGY
: SELECTED_TAB_OFFSET
);
375 itemRect
->top
= itemRect
->bottom
- infoPtr
->tabHeight
;
377 else /* not TCS_BOTTOM and not TCS_VERTICAL */
379 itemRect
->top
= clientRect
.top
+ itemRect
->top
* infoPtr
->tabHeight
+
380 ((lStyle
& TCS_BUTTONS
) ? itemRect
->top
* BUTTON_SPACINGY
: SELECTED_TAB_OFFSET
);
381 itemRect
->bottom
= itemRect
->top
+ infoPtr
->tabHeight
;
385 * "scroll" it to make sure the item at the very left of the
386 * tab control is the leftmost visible tab.
388 if(lStyle
& TCS_VERTICAL
)
392 -TAB_GetItem(infoPtr
, infoPtr
->leftmostVisible
)->rect
.top
);
395 * Move the rectangle so the first item is slightly offset from
396 * the bottom of the tab control.
400 SELECTED_TAB_OFFSET
);
405 -TAB_GetItem(infoPtr
, infoPtr
->leftmostVisible
)->rect
.left
,
409 * Move the rectangle so the first item is slightly offset from
410 * the left of the tab control.
416 TRACE("item %d tab h=%d, rect=(%s)\n",
417 itemIndex
, infoPtr
->tabHeight
, wine_dbgstr_rect(itemRect
));
419 /* Now, calculate the position of the item as if it were selected. */
420 if (selectedRect
!=NULL
)
422 CopyRect(selectedRect
, itemRect
);
424 /* The rectangle of a selected item is a bit wider. */
425 if(lStyle
& TCS_VERTICAL
)
426 InflateRect(selectedRect
, 0, SELECTED_TAB_OFFSET
);
428 InflateRect(selectedRect
, SELECTED_TAB_OFFSET
, 0);
430 /* If it also a bit higher. */
431 if ((lStyle
& TCS_BOTTOM
) && (lStyle
& TCS_VERTICAL
))
433 selectedRect
->left
-= 2; /* the border is thicker on the right */
434 selectedRect
->right
+= SELECTED_TAB_OFFSET
;
436 else if (lStyle
& TCS_VERTICAL
)
438 selectedRect
->left
-= SELECTED_TAB_OFFSET
;
439 selectedRect
->right
+= 1;
441 else if (lStyle
& TCS_BOTTOM
)
443 selectedRect
->bottom
+= SELECTED_TAB_OFFSET
;
445 else /* not TCS_BOTTOM and not TCS_VERTICAL */
447 selectedRect
->top
-= SELECTED_TAB_OFFSET
;
448 selectedRect
->bottom
-= 1;
452 /* Check for visibility */
453 if (lStyle
& TCS_VERTICAL
)
454 return (itemRect
->top
< clientRect
.bottom
) && (itemRect
->bottom
> clientRect
.top
);
456 return (itemRect
->left
< clientRect
.right
) && (itemRect
->right
> clientRect
.left
);
460 TAB_GetItemRect(const TAB_INFO
*infoPtr
, WPARAM wParam
, LPARAM lParam
)
462 return TAB_InternalGetItemRect(infoPtr
, wParam
, (LPRECT
)lParam
, NULL
);
465 /******************************************************************************
468 * This method is called to handle keyboard input
470 static LRESULT
TAB_KeyUp(TAB_INFO
* infoPtr
, WPARAM keyCode
)
477 newItem
= infoPtr
->uFocus
- 1;
480 newItem
= infoPtr
->uFocus
+ 1;
485 * If we changed to a valid item, change the selection
488 newItem
< infoPtr
->uNumItem
&&
489 infoPtr
->uFocus
!= newItem
)
491 if (!TAB_SendSimpleNotify(infoPtr
, TCN_SELCHANGING
))
493 infoPtr
->iSelected
= newItem
;
494 infoPtr
->uFocus
= newItem
;
495 TAB_SendSimpleNotify(infoPtr
, TCN_SELCHANGE
);
497 TAB_EnsureSelectionVisible(infoPtr
);
498 TAB_InvalidateTabArea(infoPtr
);
505 /******************************************************************************
508 * This method is called whenever the focus goes in or out of this control
509 * it is used to update the visual state of the control.
511 static void TAB_FocusChanging(const TAB_INFO
*infoPtr
)
517 * Get the rectangle for the item.
519 isVisible
= TAB_InternalGetItemRect(infoPtr
,
525 * If the rectangle is not completely invisible, invalidate that
526 * portion of the window.
530 TRACE("invalidate (%s)\n", wine_dbgstr_rect(&selectedRect
));
531 InvalidateRect(infoPtr
->hwnd
, &selectedRect
, TRUE
);
535 static INT
TAB_InternalHitTest (const TAB_INFO
*infoPtr
, POINT pt
, UINT
*flags
)
540 for (iCount
= 0; iCount
< infoPtr
->uNumItem
; iCount
++)
542 TAB_InternalGetItemRect(infoPtr
, iCount
, &rect
, NULL
);
544 if (PtInRect(&rect
, pt
))
546 *flags
= TCHT_ONITEM
;
551 *flags
= TCHT_NOWHERE
;
555 static inline LRESULT
556 TAB_HitTest (const TAB_INFO
*infoPtr
, LPTCHITTESTINFO lptest
)
558 return TAB_InternalHitTest (infoPtr
, lptest
->pt
, &lptest
->flags
);
561 /******************************************************************************
564 * Napster v2b5 has a tab control for its main navigation which has a client
565 * area that covers the whole area of the dialog pages.
566 * That's why it receives all msgs for that area and the underlying dialog ctrls
568 * So I decided that we should handle WM_NCHITTEST here and return
569 * HTTRANSPARENT if we don't hit the tab control buttons.
570 * FIXME: WM_NCHITTEST handling correct ? Fix it if you know that Windows
571 * doesn't do it that way. Maybe depends on tab control styles ?
573 static inline LRESULT
574 TAB_NCHitTest (const TAB_INFO
*infoPtr
, LPARAM lParam
)
579 pt
.x
= (short)LOWORD(lParam
);
580 pt
.y
= (short)HIWORD(lParam
);
581 ScreenToClient(infoPtr
->hwnd
, &pt
);
583 if (TAB_InternalHitTest(infoPtr
, pt
, &dummyflag
) == -1)
584 return HTTRANSPARENT
;
590 TAB_LButtonDown (TAB_INFO
*infoPtr
, WPARAM wParam
, LPARAM lParam
)
596 if (infoPtr
->hwndToolTip
)
597 TAB_RelayEvent (infoPtr
->hwndToolTip
, infoPtr
->hwnd
,
598 WM_LBUTTONDOWN
, wParam
, lParam
);
600 if (GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
) & TCS_FOCUSONBUTTONDOWN
) {
601 SetFocus (infoPtr
->hwnd
);
604 if (infoPtr
->hwndToolTip
)
605 TAB_RelayEvent (infoPtr
->hwndToolTip
, infoPtr
->hwnd
,
606 WM_LBUTTONDOWN
, wParam
, lParam
);
608 pt
.x
= (short)LOWORD(lParam
);
609 pt
.y
= (short)HIWORD(lParam
);
611 newItem
= TAB_InternalHitTest (infoPtr
, pt
, &dummy
);
613 TRACE("On Tab, item %d\n", newItem
);
615 if (newItem
!= -1 && infoPtr
->iSelected
!= newItem
)
617 if (!TAB_SendSimpleNotify(infoPtr
, TCN_SELCHANGING
))
619 infoPtr
->iSelected
= newItem
;
620 infoPtr
->uFocus
= newItem
;
621 TAB_SendSimpleNotify(infoPtr
, TCN_SELCHANGE
);
623 TAB_EnsureSelectionVisible(infoPtr
);
625 TAB_InvalidateTabArea(infoPtr
);
631 static inline LRESULT
632 TAB_LButtonUp (const TAB_INFO
*infoPtr
)
634 TAB_SendSimpleNotify(infoPtr
, NM_CLICK
);
639 static inline LRESULT
640 TAB_RButtonDown (const TAB_INFO
*infoPtr
)
642 TAB_SendSimpleNotify(infoPtr
, NM_RCLICK
);
646 /******************************************************************************
647 * TAB_DrawLoneItemInterior
649 * This calls TAB_DrawItemInterior. However, TAB_DrawItemInterior is normally
650 * called by TAB_DrawItem which is normally called by TAB_Refresh which sets
651 * up the device context and font. This routine does the same setup but
652 * only calls TAB_DrawItemInterior for the single specified item.
655 TAB_DrawLoneItemInterior(const TAB_INFO
* infoPtr
, int iItem
)
657 HDC hdc
= GetDC(infoPtr
->hwnd
);
660 /* Clip UpDown control to not draw over it */
661 if (infoPtr
->needsScrolling
)
663 GetWindowRect(infoPtr
->hwnd
, &rC
);
664 GetWindowRect(infoPtr
->hwndUpDown
, &r
);
665 ExcludeClipRect(hdc
, r
.left
- rC
.left
, r
.top
- rC
.top
, r
.right
- rC
.left
, r
.bottom
- rC
.top
);
667 TAB_DrawItemInterior(infoPtr
, hdc
, iItem
, NULL
);
668 ReleaseDC(infoPtr
->hwnd
, hdc
);
671 /* update a tab after hottracking - invalidate it or just redraw the interior,
672 * based on whether theming is used or not */
673 static inline void hottrack_refresh(const TAB_INFO
*infoPtr
, int tabIndex
)
675 if (tabIndex
== -1) return;
677 if (GetWindowTheme (infoPtr
->hwnd
))
680 TAB_InternalGetItemRect(infoPtr
, tabIndex
, &rect
, NULL
);
681 InvalidateRect (infoPtr
->hwnd
, &rect
, FALSE
);
684 TAB_DrawLoneItemInterior(infoPtr
, tabIndex
);
687 /******************************************************************************
688 * TAB_HotTrackTimerProc
690 * When a mouse-move event causes a tab to be highlighted (hot-tracking), a
691 * timer is setup so we can check if the mouse is moved out of our window.
692 * (We don't get an event when the mouse leaves, the mouse-move events just
693 * stop being delivered to our window and just start being delivered to
694 * another window.) This function is called when the timer triggers so
695 * we can check if the mouse has left our window. If so, we un-highlight
696 * the hot-tracked tab.
699 TAB_HotTrackTimerProc
701 HWND hwnd
, /* handle of window for timer messages */
702 UINT uMsg
, /* WM_TIMER message */
703 UINT_PTR idEvent
, /* timer identifier */
704 DWORD dwTime
/* current system time */
707 TAB_INFO
* infoPtr
= TAB_GetInfoPtr(hwnd
);
709 if (infoPtr
!= NULL
&& infoPtr
->iHotTracked
>= 0)
714 ** If we can't get the cursor position, or if the cursor is outside our
715 ** window, we un-highlight the hot-tracked tab. Note that the cursor is
716 ** "outside" even if it is within our bounding rect if another window
717 ** overlaps. Note also that the case where the cursor stayed within our
718 ** window but has moved off the hot-tracked tab will be handled by the
719 ** WM_MOUSEMOVE event.
721 if (!GetCursorPos(&pt
) || WindowFromPoint(pt
) != hwnd
)
723 /* Redraw iHotTracked to look normal */
724 INT iRedraw
= infoPtr
->iHotTracked
;
725 infoPtr
->iHotTracked
= -1;
726 hottrack_refresh (infoPtr
, iRedraw
);
728 /* Kill this timer */
729 KillTimer(hwnd
, TAB_HOTTRACK_TIMER
);
734 /******************************************************************************
737 * If a tab control has the TCS_HOTTRACK style, then the tab under the mouse
738 * should be highlighted. This function determines which tab in a tab control,
739 * if any, is under the mouse and records that information. The caller may
740 * supply output parameters to receive the item number of the tab item which
741 * was highlighted but isn't any longer and of the tab item which is now
742 * highlighted but wasn't previously. The caller can use this information to
743 * selectively redraw those tab items.
745 * If the caller has a mouse position, it can supply it through the pos
746 * parameter. For example, TAB_MouseMove does this. Otherwise, the caller
747 * supplies NULL and this function determines the current mouse position
755 int* out_redrawLeave
,
762 if (out_redrawLeave
!= NULL
)
763 *out_redrawLeave
= -1;
764 if (out_redrawEnter
!= NULL
)
765 *out_redrawEnter
= -1;
767 if ((GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
) & TCS_HOTTRACK
)
768 || GetWindowTheme (infoPtr
->hwnd
))
776 ScreenToClient(infoPtr
->hwnd
, &pt
);
780 pt
.x
= (short)LOWORD(*pos
);
781 pt
.y
= (short)HIWORD(*pos
);
784 item
= TAB_InternalHitTest(infoPtr
, pt
, &flags
);
787 if (item
!= infoPtr
->iHotTracked
)
789 if (infoPtr
->iHotTracked
>= 0)
791 /* Mark currently hot-tracked to be redrawn to look normal */
792 if (out_redrawLeave
!= NULL
)
793 *out_redrawLeave
= infoPtr
->iHotTracked
;
797 /* Kill timer which forces recheck of mouse pos */
798 KillTimer(infoPtr
->hwnd
, TAB_HOTTRACK_TIMER
);
803 /* Start timer so we recheck mouse pos */
804 UINT timerID
= SetTimer
808 TAB_HOTTRACK_TIMER_INTERVAL
,
809 TAB_HotTrackTimerProc
813 return; /* Hot tracking not available */
816 infoPtr
->iHotTracked
= item
;
820 /* Mark new hot-tracked to be redrawn to look highlighted */
821 if (out_redrawEnter
!= NULL
)
822 *out_redrawEnter
= item
;
827 /******************************************************************************
830 * Handles the mouse-move event. Updates tooltips. Updates hot-tracking.
833 TAB_MouseMove (TAB_INFO
*infoPtr
, WPARAM wParam
, LPARAM lParam
)
838 if (infoPtr
->hwndToolTip
)
839 TAB_RelayEvent (infoPtr
->hwndToolTip
, infoPtr
->hwnd
,
840 WM_LBUTTONDOWN
, wParam
, lParam
);
842 /* Determine which tab to highlight. Redraw tabs which change highlight
844 TAB_RecalcHotTrack(infoPtr
, &lParam
, &redrawLeave
, &redrawEnter
);
846 hottrack_refresh (infoPtr
, redrawLeave
);
847 hottrack_refresh (infoPtr
, redrawEnter
);
852 /******************************************************************************
855 * Calculates the tab control's display area given the window rectangle or
856 * the window rectangle given the requested display rectangle.
858 static LRESULT
TAB_AdjustRect(const TAB_INFO
*infoPtr
, WPARAM fLarger
, LPRECT prc
)
860 DWORD lStyle
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
861 LONG
*iRightBottom
, *iLeftTop
;
863 TRACE ("hwnd=%p fLarger=%ld (%s)\n", infoPtr
->hwnd
, fLarger
,
864 wine_dbgstr_rect(prc
));
868 if(lStyle
& TCS_VERTICAL
)
870 iRightBottom
= &(prc
->right
);
871 iLeftTop
= &(prc
->left
);
875 iRightBottom
= &(prc
->bottom
);
876 iLeftTop
= &(prc
->top
);
879 if (fLarger
) /* Go from display rectangle */
881 /* Add the height of the tabs. */
882 if (lStyle
& TCS_BOTTOM
)
883 *iRightBottom
+= infoPtr
->tabHeight
* infoPtr
->uNumRows
;
885 *iLeftTop
-= infoPtr
->tabHeight
* infoPtr
->uNumRows
+
886 ((lStyle
& TCS_BUTTONS
)? 3 * (infoPtr
->uNumRows
- 1) : 0);
888 /* Inflate the rectangle for the padding */
889 InflateRect(prc
, DISPLAY_AREA_PADDINGX
, DISPLAY_AREA_PADDINGY
);
891 /* Inflate for the border */
892 InflateRect(prc
, CONTROL_BORDER_SIZEX
, CONTROL_BORDER_SIZEY
);
894 else /* Go from window rectangle. */
896 /* Deflate the rectangle for the border */
897 InflateRect(prc
, -CONTROL_BORDER_SIZEX
, -CONTROL_BORDER_SIZEY
);
899 /* Deflate the rectangle for the padding */
900 InflateRect(prc
, -DISPLAY_AREA_PADDINGX
, -DISPLAY_AREA_PADDINGY
);
902 /* Remove the height of the tabs. */
903 if (lStyle
& TCS_BOTTOM
)
904 *iRightBottom
-= infoPtr
->tabHeight
* infoPtr
->uNumRows
;
906 *iLeftTop
+= (infoPtr
->tabHeight
) * infoPtr
->uNumRows
+
907 ((lStyle
& TCS_BUTTONS
)? 3 * (infoPtr
->uNumRows
- 1) : 0);
913 /******************************************************************************
916 * This method will handle the notification from the scroll control and
917 * perform the scrolling operation on the tab control.
919 static LRESULT
TAB_OnHScroll(TAB_INFO
*infoPtr
, int nScrollCode
, int nPos
, HWND hwndScroll
)
921 if(nScrollCode
== SB_THUMBPOSITION
&& nPos
!= infoPtr
->leftmostVisible
)
923 if(nPos
< infoPtr
->leftmostVisible
)
924 infoPtr
->leftmostVisible
--;
926 infoPtr
->leftmostVisible
++;
928 TAB_RecalcHotTrack(infoPtr
, NULL
, NULL
, NULL
);
929 TAB_InvalidateTabArea(infoPtr
);
930 SendMessageW(infoPtr
->hwndUpDown
, UDM_SETPOS
, 0,
931 MAKELONG(infoPtr
->leftmostVisible
, 0));
937 /******************************************************************************
940 * This method will check the current scrolling state and make sure the
941 * scrolling control is displayed (or not).
943 static void TAB_SetupScrolling(
946 const RECT
* clientRect
)
948 static const WCHAR msctls_updown32W
[] = { 'm','s','c','t','l','s','_','u','p','d','o','w','n','3','2',0 };
949 static const WCHAR emptyW
[] = { 0 };
951 DWORD lStyle
= GetWindowLongW(hwnd
, GWL_STYLE
);
953 if (infoPtr
->needsScrolling
)
959 * Calculate the position of the scroll control.
961 if(lStyle
& TCS_VERTICAL
)
963 controlPos
.right
= clientRect
->right
;
964 controlPos
.left
= controlPos
.right
- 2 * GetSystemMetrics(SM_CXHSCROLL
);
966 if (lStyle
& TCS_BOTTOM
)
968 controlPos
.top
= clientRect
->bottom
- infoPtr
->tabHeight
;
969 controlPos
.bottom
= controlPos
.top
+ GetSystemMetrics(SM_CYHSCROLL
);
973 controlPos
.bottom
= clientRect
->top
+ infoPtr
->tabHeight
;
974 controlPos
.top
= controlPos
.bottom
- GetSystemMetrics(SM_CYHSCROLL
);
979 controlPos
.right
= clientRect
->right
;
980 controlPos
.left
= controlPos
.right
- 2 * GetSystemMetrics(SM_CXHSCROLL
);
982 if (lStyle
& TCS_BOTTOM
)
984 controlPos
.top
= clientRect
->bottom
- infoPtr
->tabHeight
;
985 controlPos
.bottom
= controlPos
.top
+ GetSystemMetrics(SM_CYHSCROLL
);
989 controlPos
.bottom
= clientRect
->top
+ infoPtr
->tabHeight
;
990 controlPos
.top
= controlPos
.bottom
- GetSystemMetrics(SM_CYHSCROLL
);
995 * If we don't have a scroll control yet, we want to create one.
996 * If we have one, we want to make sure it's positioned properly.
998 if (infoPtr
->hwndUpDown
==0)
1000 infoPtr
->hwndUpDown
= CreateWindowW(msctls_updown32W
, emptyW
,
1001 WS_VISIBLE
| WS_CHILD
| UDS_HORZ
,
1002 controlPos
.left
, controlPos
.top
,
1003 controlPos
.right
- controlPos
.left
,
1004 controlPos
.bottom
- controlPos
.top
,
1005 hwnd
, NULL
, NULL
, NULL
);
1009 SetWindowPos(infoPtr
->hwndUpDown
,
1011 controlPos
.left
, controlPos
.top
,
1012 controlPos
.right
- controlPos
.left
,
1013 controlPos
.bottom
- controlPos
.top
,
1014 SWP_SHOWWINDOW
| SWP_NOZORDER
);
1017 /* Now calculate upper limit of the updown control range.
1018 * We do this by calculating how many tabs will be offscreen when the
1019 * last tab is visible.
1021 if(infoPtr
->uNumItem
)
1023 vsize
= clientRect
->right
- (controlPos
.right
- controlPos
.left
+ 1);
1024 maxRange
= infoPtr
->uNumItem
;
1025 tabwidth
= TAB_GetItem(infoPtr
, infoPtr
->uNumItem
- 1)->rect
.right
;
1027 for(; maxRange
> 0; maxRange
--)
1029 if(tabwidth
- TAB_GetItem(infoPtr
,maxRange
- 1)->rect
.left
> vsize
)
1033 if(maxRange
== infoPtr
->uNumItem
)
1039 /* If we once had a scroll control... hide it */
1040 if (infoPtr
->hwndUpDown
!=0)
1041 ShowWindow(infoPtr
->hwndUpDown
, SW_HIDE
);
1043 if (infoPtr
->hwndUpDown
)
1044 SendMessageW(infoPtr
->hwndUpDown
, UDM_SETRANGE32
, 0, maxRange
);
1047 /******************************************************************************
1050 * This method will calculate the position rectangles of all the items in the
1051 * control. The rectangle calculated starts at 0 for the first item in the
1052 * list and ignores scrolling and selection.
1053 * It also uses the current font to determine the height of the tab row and
1054 * it checks if all the tabs fit in the client area of the window. If they
1055 * don't, a scrolling control is added.
1057 static void TAB_SetItemBounds (TAB_INFO
*infoPtr
)
1059 LONG lStyle
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
1060 TEXTMETRICW fontMetrics
;
1063 INT curItemRowCount
;
1064 HFONT hFont
, hOldFont
;
1073 * We need to get text information so we need a DC and we need to select
1076 hdc
= GetDC(infoPtr
->hwnd
);
1078 hFont
= infoPtr
->hFont
? infoPtr
->hFont
: GetStockObject (SYSTEM_FONT
);
1079 hOldFont
= SelectObject (hdc
, hFont
);
1082 * We will base the rectangle calculations on the client rectangle
1085 GetClientRect(infoPtr
->hwnd
, &clientRect
);
1087 /* if TCS_VERTICAL then swap the height and width so this code places the
1088 tabs along the top of the rectangle and we can just rotate them after
1089 rather than duplicate all of the below code */
1090 if(lStyle
& TCS_VERTICAL
)
1092 iTemp
= clientRect
.bottom
;
1093 clientRect
.bottom
= clientRect
.right
;
1094 clientRect
.right
= iTemp
;
1097 /* Now use hPadding and vPadding */
1098 infoPtr
->uHItemPadding
= infoPtr
->uHItemPadding_s
;
1099 infoPtr
->uVItemPadding
= infoPtr
->uVItemPadding_s
;
1101 /* The leftmost item will be "0" aligned */
1103 curItemRowCount
= infoPtr
->uNumItem
? 1 : 0;
1105 if (!(infoPtr
->fHeightSet
))
1108 int icon_height
= 0;
1110 /* Use the current font to determine the height of a tab. */
1111 GetTextMetricsW(hdc
, &fontMetrics
);
1113 /* Get the icon height */
1115 ImageList_GetIconSize(infoPtr
->himl
, 0, &icon_height
);
1117 /* Take the highest between font or icon */
1118 if (fontMetrics
.tmHeight
> icon_height
)
1119 item_height
= fontMetrics
.tmHeight
+ 2;
1121 item_height
= icon_height
;
1124 * Make sure there is enough space for the letters + icon + growing the
1125 * selected item + extra space for the selected item.
1127 infoPtr
->tabHeight
= item_height
+
1128 ((lStyle
& TCS_BUTTONS
) ? 2 : 1) *
1129 infoPtr
->uVItemPadding
;
1131 TRACE("tabH=%d, tmH=%d, iconh=%d\n",
1132 infoPtr
->tabHeight
, fontMetrics
.tmHeight
, icon_height
);
1135 TRACE("client right=%d\n", clientRect
.right
);
1137 /* Get the icon width */
1140 ImageList_GetIconSize(infoPtr
->himl
, &icon_width
, 0);
1142 if (lStyle
& TCS_FIXEDWIDTH
)
1145 /* Add padding if icon is present */
1146 icon_width
+= infoPtr
->uHItemPadding
;
1149 for (curItem
= 0; curItem
< infoPtr
->uNumItem
; curItem
++)
1151 TAB_ITEM
*curr
= TAB_GetItem(infoPtr
, curItem
);
1153 /* Set the leftmost position of the tab. */
1154 curr
->rect
.left
= curItemLeftPos
;
1156 if (lStyle
& TCS_FIXEDWIDTH
)
1158 curr
->rect
.right
= curr
->rect
.left
+
1159 max(infoPtr
->tabWidth
, icon_width
);
1161 else if (!curr
->pszText
)
1163 /* If no text use minimum tab width including padding. */
1164 if (infoPtr
->tabMinWidth
< 0)
1165 curr
->rect
.right
= curr
->rect
.left
+ GET_DEFAULT_MIN_TAB_WIDTH(infoPtr
);
1168 curr
->rect
.right
= curr
->rect
.left
+ infoPtr
->tabMinWidth
;
1170 /* Add extra padding if icon is present */
1171 if (infoPtr
->himl
&& infoPtr
->tabMinWidth
> 0 && infoPtr
->tabMinWidth
< DEFAULT_MIN_TAB_WIDTH
1172 && infoPtr
->uHItemPadding
> 1)
1173 curr
->rect
.right
+= EXTRA_ICON_PADDING
* (infoPtr
->uHItemPadding
-1);
1180 /* Calculate how wide the tab is depending on the text it contains */
1181 GetTextExtentPoint32W(hdc
, curr
->pszText
,
1182 lstrlenW(curr
->pszText
), &size
);
1184 tabwidth
= size
.cx
+ icon_width
+ 2 * infoPtr
->uHItemPadding
;
1186 if (infoPtr
->tabMinWidth
< 0)
1187 tabwidth
= max(tabwidth
, GET_DEFAULT_MIN_TAB_WIDTH(infoPtr
));
1189 tabwidth
= max(tabwidth
, infoPtr
->tabMinWidth
);
1191 curr
->rect
.right
= curr
->rect
.left
+ tabwidth
;
1192 TRACE("for <%s>, l,r=%d,%d\n",
1193 debugstr_w(curr
->pszText
), curr
->rect
.left
, curr
->rect
.right
);
1197 * Check if this is a multiline tab control and if so
1198 * check to see if we should wrap the tabs
1200 * Wrap all these tabs. We will arrange them evenly later.
1204 if (((lStyle
& TCS_MULTILINE
) || (lStyle
& TCS_VERTICAL
)) &&
1206 (clientRect
.right
- CONTROL_BORDER_SIZEX
- DISPLAY_AREA_PADDINGX
)))
1208 curr
->rect
.right
-= curr
->rect
.left
;
1210 curr
->rect
.left
= 0;
1212 TRACE("wrapping <%s>, l,r=%d,%d\n", debugstr_w(curr
->pszText
),
1213 curr
->rect
.left
, curr
->rect
.right
);
1216 curr
->rect
.bottom
= 0;
1217 curr
->rect
.top
= curItemRowCount
- 1;
1219 TRACE("Rect: %s\n", wine_dbgstr_rect(&curr
->rect
));
1222 * The leftmost position of the next item is the rightmost position
1225 if (lStyle
& TCS_BUTTONS
)
1227 curItemLeftPos
= curr
->rect
.right
+ BUTTON_SPACINGX
;
1228 if (lStyle
& TCS_FLATBUTTONS
)
1229 curItemLeftPos
+= FLAT_BTN_SPACINGX
;
1232 curItemLeftPos
= curr
->rect
.right
;
1235 if (!((lStyle
& TCS_MULTILINE
) || (lStyle
& TCS_VERTICAL
)))
1238 * Check if we need a scrolling control.
1240 infoPtr
->needsScrolling
= (curItemLeftPos
+ (2 * SELECTED_TAB_OFFSET
) >
1243 /* Don't need scrolling, then update infoPtr->leftmostVisible */
1244 if(!infoPtr
->needsScrolling
)
1245 infoPtr
->leftmostVisible
= 0;
1250 * No scrolling in Multiline or Vertical styles.
1252 infoPtr
->needsScrolling
= FALSE
;
1253 infoPtr
->leftmostVisible
= 0;
1255 TAB_SetupScrolling(infoPtr
->hwnd
, infoPtr
, &clientRect
);
1257 /* Set the number of rows */
1258 infoPtr
->uNumRows
= curItemRowCount
;
1260 /* Arrange all tabs evenly if style says so */
1261 if (!(lStyle
& TCS_RAGGEDRIGHT
) &&
1262 ((lStyle
& TCS_MULTILINE
) || (lStyle
& TCS_VERTICAL
)) &&
1263 (infoPtr
->uNumItem
> 0) &&
1264 (infoPtr
->uNumRows
> 1))
1266 INT tabPerRow
,remTab
,iRow
;
1271 * Ok windows tries to even out the rows. place the same
1272 * number of tabs in each row. So lets give that a shot
1275 tabPerRow
= infoPtr
->uNumItem
/ (infoPtr
->uNumRows
);
1276 remTab
= infoPtr
->uNumItem
% (infoPtr
->uNumRows
);
1278 for (iItm
=0,iRow
=0,iCount
=0,curItemLeftPos
=0;
1279 iItm
<infoPtr
->uNumItem
;
1282 /* normalize the current rect */
1283 TAB_ITEM
*curr
= TAB_GetItem(infoPtr
, iItm
);
1285 /* shift the item to the left side of the clientRect */
1286 curr
->rect
.right
-= curr
->rect
.left
;
1287 curr
->rect
.left
= 0;
1289 TRACE("r=%d, cl=%d, cl.r=%d, iCount=%d, iRow=%d, uNumRows=%d, remTab=%d, tabPerRow=%d\n",
1290 curr
->rect
.right
, curItemLeftPos
, clientRect
.right
,
1291 iCount
, iRow
, infoPtr
->uNumRows
, remTab
, tabPerRow
);
1293 /* if we have reached the maximum number of tabs on this row */
1294 /* move to the next row, reset our current item left position and */
1295 /* the count of items on this row */
1297 if (lStyle
& TCS_VERTICAL
) {
1298 /* Vert: Add the remaining tabs in the *last* remainder rows */
1299 if (iCount
>= ((iRow
>=(INT
)infoPtr
->uNumRows
- remTab
)?tabPerRow
+ 1:tabPerRow
)) {
1305 /* Horz: Add the remaining tabs in the *first* remainder rows */
1306 if (iCount
>= ((iRow
<remTab
)?tabPerRow
+ 1:tabPerRow
)) {
1313 /* shift the item to the right to place it as the next item in this row */
1314 curr
->rect
.left
+= curItemLeftPos
;
1315 curr
->rect
.right
+= curItemLeftPos
;
1316 curr
->rect
.top
= iRow
;
1317 if (lStyle
& TCS_BUTTONS
)
1319 curItemLeftPos
= curr
->rect
.right
+ 1;
1320 if (lStyle
& TCS_FLATBUTTONS
)
1321 curItemLeftPos
+= FLAT_BTN_SPACINGX
;
1324 curItemLeftPos
= curr
->rect
.right
;
1326 TRACE("arranging <%s>, l,r=%d,%d, row=%d\n",
1327 debugstr_w(curr
->pszText
), curr
->rect
.left
,
1328 curr
->rect
.right
, curr
->rect
.top
);
1335 INT widthDiff
, iIndexStart
=0, iIndexEnd
=0;
1339 while(iIndexStart
< infoPtr
->uNumItem
)
1341 TAB_ITEM
*start
= TAB_GetItem(infoPtr
, iIndexStart
);
1344 * find the index of the row
1346 /* find the first item on the next row */
1347 for (iIndexEnd
=iIndexStart
;
1348 (iIndexEnd
< infoPtr
->uNumItem
) &&
1349 (TAB_GetItem(infoPtr
, iIndexEnd
)->rect
.top
==
1352 /* intentionally blank */;
1355 * we need to justify these tabs so they fill the whole given
1359 /* find the amount of space remaining on this row */
1360 widthDiff
= clientRect
.right
- (2 * SELECTED_TAB_OFFSET
) -
1361 TAB_GetItem(infoPtr
, iIndexEnd
- 1)->rect
.right
;
1363 /* iCount is the number of tab items on this row */
1364 iCount
= iIndexEnd
- iIndexStart
;
1368 remainder
= widthDiff
% iCount
;
1369 widthDiff
= widthDiff
/ iCount
;
1370 /* add widthDiff/iCount, or extra space/items on row, to each item on this row */
1371 for (iIndex
=iIndexStart
, iCount
=0; iIndex
< iIndexEnd
; iIndex
++, iCount
++)
1373 TAB_ITEM
*item
= TAB_GetItem(infoPtr
, iIndex
);
1375 item
->rect
.left
+= iCount
* widthDiff
;
1376 item
->rect
.right
+= (iCount
+ 1) * widthDiff
;
1378 TRACE("adjusting 1 <%s>, l,r=%d,%d\n",
1379 debugstr_w(item
->pszText
),
1380 item
->rect
.left
, item
->rect
.right
);
1383 TAB_GetItem(infoPtr
, iIndex
- 1)->rect
.right
+= remainder
;
1385 else /* we have only one item on this row, make it take up the entire row */
1387 start
->rect
.left
= clientRect
.left
;
1388 start
->rect
.right
= clientRect
.right
- 4;
1390 TRACE("adjusting 2 <%s>, l,r=%d,%d\n",
1391 debugstr_w(start
->pszText
),
1392 start
->rect
.left
, start
->rect
.right
);
1397 iIndexStart
= iIndexEnd
;
1402 /* if TCS_VERTICAL rotate the tabs so they are along the side of the clientRect */
1403 if(lStyle
& TCS_VERTICAL
)
1406 for(iIndex
= 0; iIndex
< infoPtr
->uNumItem
; iIndex
++)
1408 rcItem
= &TAB_GetItem(infoPtr
, iIndex
)->rect
;
1410 rcOriginal
= *rcItem
;
1412 /* this is rotating the items by 90 degrees clockwise around the center of the control */
1413 rcItem
->top
= (rcOriginal
.left
- clientRect
.left
);
1414 rcItem
->bottom
= rcItem
->top
+ (rcOriginal
.right
- rcOriginal
.left
);
1415 rcItem
->left
= rcOriginal
.top
;
1416 rcItem
->right
= rcOriginal
.bottom
;
1420 TAB_EnsureSelectionVisible(infoPtr
);
1421 TAB_RecalcHotTrack(infoPtr
, NULL
, NULL
, NULL
);
1424 SelectObject (hdc
, hOldFont
);
1425 ReleaseDC (infoPtr
->hwnd
, hdc
);
1430 TAB_EraseTabInterior(const TAB_INFO
*infoPtr
, HDC hdc
, INT iItem
, RECT
*drawRect
)
1432 LONG lStyle
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
1433 HBRUSH hbr
= CreateSolidBrush (comctl32_color
.clrBtnFace
);
1434 BOOL deleteBrush
= TRUE
;
1435 RECT rTemp
= *drawRect
;
1437 InflateRect(&rTemp
, -2, -2);
1438 if (lStyle
& TCS_BUTTONS
)
1440 if (iItem
== infoPtr
->iSelected
)
1442 /* Background color */
1443 if (!(lStyle
& TCS_OWNERDRAWFIXED
))
1446 hbr
= GetSysColorBrush(COLOR_SCROLLBAR
);
1448 SetTextColor(hdc
, comctl32_color
.clr3dFace
);
1449 SetBkColor(hdc
, comctl32_color
.clr3dHilight
);
1451 /* if COLOR_WINDOW happens to be the same as COLOR_3DHILIGHT
1452 * we better use 0x55aa bitmap brush to make scrollbar's background
1453 * look different from the window background.
1455 if (comctl32_color
.clr3dHilight
== comctl32_color
.clrWindow
)
1456 hbr
= COMCTL32_hPattern55AABrush
;
1458 deleteBrush
= FALSE
;
1460 FillRect(hdc
, &rTemp
, hbr
);
1462 else /* ! selected */
1464 if (lStyle
& TCS_FLATBUTTONS
)
1466 FillRect(hdc
, drawRect
, hbr
);
1467 if (iItem
== infoPtr
->iHotTracked
)
1468 DrawEdge(hdc
, drawRect
, EDGE_RAISED
, BF_SOFT
|BF_RECT
);
1471 FillRect(hdc
, &rTemp
, hbr
);
1475 else /* !TCS_BUTTONS */
1477 if (!GetWindowTheme (infoPtr
->hwnd
))
1478 FillRect(hdc
, &rTemp
, hbr
);
1482 if (deleteBrush
) DeleteObject(hbr
);
1485 /******************************************************************************
1486 * TAB_DrawItemInterior
1488 * This method is used to draw the interior (text and icon) of a single tab
1489 * into the tab control.
1492 TAB_DrawItemInterior(const TAB_INFO
*infoPtr
, HDC hdc
, INT iItem
, RECT
*drawRect
)
1494 LONG lStyle
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
1503 /* if (drawRect == NULL) */
1510 * Get the rectangle for the item.
1512 isVisible
= TAB_InternalGetItemRect(infoPtr
, iItem
, &itemRect
, &selectedRect
);
1517 * Make sure drawRect points to something valid; simplifies code.
1519 drawRect
= &localRect
;
1522 * This logic copied from the part of TAB_DrawItem which draws
1523 * the tab background. It's important to keep it in sync. I
1524 * would have liked to avoid code duplication, but couldn't figure
1525 * out how without making spaghetti of TAB_DrawItem.
1527 if (iItem
== infoPtr
->iSelected
)
1528 *drawRect
= selectedRect
;
1530 *drawRect
= itemRect
;
1532 if (lStyle
& TCS_BUTTONS
)
1534 if (iItem
== infoPtr
->iSelected
)
1536 drawRect
->left
+= 4;
1538 drawRect
->right
-= 4;
1539 drawRect
->bottom
-= 1;
1543 drawRect
->left
+= 2;
1545 drawRect
->right
-= 2;
1546 drawRect
->bottom
-= 2;
1551 if ((lStyle
& TCS_VERTICAL
) && (lStyle
& TCS_BOTTOM
))
1553 if (iItem
!= infoPtr
->iSelected
)
1555 drawRect
->left
+= 2;
1557 drawRect
->bottom
-= 2;
1560 else if (lStyle
& TCS_VERTICAL
)
1562 if (iItem
== infoPtr
->iSelected
)
1564 drawRect
->right
+= 1;
1569 drawRect
->right
-= 2;
1570 drawRect
->bottom
-= 2;
1573 else if (lStyle
& TCS_BOTTOM
)
1575 if (iItem
== infoPtr
->iSelected
)
1581 InflateRect(drawRect
, -2, -2);
1582 drawRect
->bottom
+= 2;
1587 if (iItem
== infoPtr
->iSelected
)
1589 drawRect
->bottom
+= 3;
1593 drawRect
->bottom
-= 2;
1594 InflateRect(drawRect
, -2, 0);
1599 TRACE("drawRect=(%s)\n", wine_dbgstr_rect(drawRect
));
1601 /* Clear interior */
1602 TAB_EraseTabInterior (infoPtr
, hdc
, iItem
, drawRect
);
1604 /* Draw the focus rectangle */
1605 if (!(lStyle
& TCS_FOCUSNEVER
) &&
1606 (GetFocus() == infoPtr
->hwnd
) &&
1607 (iItem
== infoPtr
->uFocus
) )
1609 RECT rFocus
= *drawRect
;
1610 InflateRect(&rFocus
, -3, -3);
1611 if (lStyle
& TCS_BOTTOM
&& !(lStyle
& TCS_VERTICAL
))
1613 if (lStyle
& TCS_BUTTONS
)
1619 DrawFocusRect(hdc
, &rFocus
);
1625 htextPen
= CreatePen( PS_SOLID
, 1, GetSysColor(COLOR_BTNTEXT
) );
1626 holdPen
= SelectObject(hdc
, htextPen
);
1627 hOldFont
= SelectObject(hdc
, infoPtr
->hFont
);
1630 * Setup for text output
1632 oldBkMode
= SetBkMode(hdc
, TRANSPARENT
);
1633 if (!GetWindowTheme (infoPtr
->hwnd
) || (lStyle
& TCS_BUTTONS
))
1634 SetTextColor(hdc
, (((lStyle
& TCS_HOTTRACK
) && (iItem
== infoPtr
->iHotTracked
)
1635 && !(lStyle
& TCS_FLATBUTTONS
))
1636 | (TAB_GetItem(infoPtr
, iItem
)->dwState
& TCIS_HIGHLIGHTED
)) ?
1637 comctl32_color
.clrHighlight
: comctl32_color
.clrBtnText
);
1640 * if owner draw, tell the owner to draw
1642 if ((lStyle
& TCS_OWNERDRAWFIXED
) && GetParent(infoPtr
->hwnd
))
1648 drawRect
->right
-= 1;
1649 if ( iItem
== infoPtr
->iSelected
)
1651 drawRect
->right
-= 1;
1652 drawRect
->left
+= 1;
1656 * get the control id
1658 id
= (UINT
)GetWindowLongPtrW( infoPtr
->hwnd
, GWLP_ID
);
1661 * put together the DRAWITEMSTRUCT
1663 dis
.CtlType
= ODT_TAB
;
1666 dis
.itemAction
= ODA_DRAWENTIRE
;
1668 if ( iItem
== infoPtr
->iSelected
)
1669 dis
.itemState
|= ODS_SELECTED
;
1670 if (infoPtr
->uFocus
== iItem
)
1671 dis
.itemState
|= ODS_FOCUS
;
1672 dis
.hwndItem
= infoPtr
->hwnd
;
1674 CopyRect(&dis
.rcItem
,drawRect
);
1675 dis
.itemData
= (ULONG_PTR
)TAB_GetItem(infoPtr
, iItem
)->extra
;
1678 * send the draw message
1680 SendMessageW( infoPtr
->hwndNotify
, WM_DRAWITEM
, (WPARAM
)id
, (LPARAM
)&dis
);
1684 TAB_ITEM
*item
= TAB_GetItem(infoPtr
, iItem
);
1688 /* used to center the icon and text in the tab */
1690 INT center_offset_h
, center_offset_v
;
1692 /* set rcImage to drawRect, we will use top & left in our ImageList_Draw call */
1693 rcImage
= *drawRect
;
1697 rcText
.left
= rcText
.top
= rcText
.right
= rcText
.bottom
= 0;
1699 /* get the rectangle that the text fits in */
1702 DrawTextW(hdc
, item
->pszText
, -1, &rcText
, DT_CALCRECT
);
1705 * If not owner draw, then do the drawing ourselves.
1709 if (infoPtr
->himl
&& item
->iImage
!= -1)
1714 ImageList_GetIconSize(infoPtr
->himl
, &cx
, &cy
);
1716 if(lStyle
& TCS_VERTICAL
)
1718 center_offset_h
= ((drawRect
->bottom
- drawRect
->top
) - (cy
+ infoPtr
->uHItemPadding
+ (rcText
.right
- rcText
.left
))) / 2;
1719 center_offset_v
= ((drawRect
->right
- drawRect
->left
) - cx
) / 2;
1723 center_offset_h
= ((drawRect
->right
- drawRect
->left
) - (cx
+ infoPtr
->uHItemPadding
+ (rcText
.right
- rcText
.left
))) / 2;
1724 center_offset_v
= ((drawRect
->bottom
- drawRect
->top
) - cy
) / 2;
1727 /* if an item is selected, the icon is shifted up instead of down */
1728 if (iItem
== infoPtr
->iSelected
)
1729 center_offset_v
-= infoPtr
->uVItemPadding
/ 2;
1731 center_offset_v
+= infoPtr
->uVItemPadding
/ 2;
1733 if (lStyle
& TCS_FIXEDWIDTH
&& lStyle
& (TCS_FORCELABELLEFT
| TCS_FORCEICONLEFT
))
1734 center_offset_h
= infoPtr
->uHItemPadding
;
1736 if (center_offset_h
< 2)
1737 center_offset_h
= 2;
1739 if (center_offset_v
< 0)
1740 center_offset_v
= 0;
1742 TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%s), textlen=%d\n",
1743 debugstr_w(item
->pszText
), center_offset_h
, center_offset_v
,
1744 wine_dbgstr_rect(drawRect
), (rcText
.right
-rcText
.left
));
1746 if((lStyle
& TCS_VERTICAL
) && (lStyle
& TCS_BOTTOM
))
1748 rcImage
.top
= drawRect
->top
+ center_offset_h
;
1749 /* if tab is TCS_VERTICAL and TCS_BOTTOM, the text is drawn from the */
1750 /* right side of the tab, but the image still uses the left as its x position */
1751 /* this keeps the image always drawn off of the same side of the tab */
1752 rcImage
.left
= drawRect
->right
- cx
- center_offset_v
;
1753 drawRect
->top
+= cy
+ infoPtr
->uHItemPadding
;
1755 else if(lStyle
& TCS_VERTICAL
)
1757 rcImage
.top
= drawRect
->bottom
- cy
- center_offset_h
;
1758 rcImage
.left
= drawRect
->left
+ center_offset_v
;
1759 drawRect
->bottom
-= cy
+ infoPtr
->uHItemPadding
;
1761 else /* normal style, whether TCS_BOTTOM or not */
1763 rcImage
.left
= drawRect
->left
+ center_offset_h
;
1764 rcImage
.top
= drawRect
->top
+ center_offset_v
;
1765 drawRect
->left
+= cx
+ infoPtr
->uHItemPadding
;
1768 TRACE("drawing image=%d, left=%d, top=%d\n",
1769 item
->iImage
, rcImage
.left
, rcImage
.top
-1);
1781 /* Now position text */
1782 if (lStyle
& TCS_FIXEDWIDTH
&& lStyle
& TCS_FORCELABELLEFT
)
1783 center_offset_h
= infoPtr
->uHItemPadding
;
1785 if(lStyle
& TCS_VERTICAL
)
1786 center_offset_h
= ((drawRect
->bottom
- drawRect
->top
) - (rcText
.right
- rcText
.left
)) / 2;
1788 center_offset_h
= ((drawRect
->right
- drawRect
->left
) - (rcText
.right
- rcText
.left
)) / 2;
1790 if(lStyle
& TCS_VERTICAL
)
1792 if(lStyle
& TCS_BOTTOM
)
1793 drawRect
->top
+=center_offset_h
;
1795 drawRect
->bottom
-=center_offset_h
;
1797 center_offset_v
= ((drawRect
->right
- drawRect
->left
) - (rcText
.bottom
- rcText
.top
)) / 2;
1801 drawRect
->left
+= center_offset_h
;
1802 center_offset_v
= ((drawRect
->bottom
- drawRect
->top
) - (rcText
.bottom
- rcText
.top
)) / 2;
1805 /* if an item is selected, the text is shifted up instead of down */
1806 if (iItem
== infoPtr
->iSelected
)
1807 center_offset_v
-= infoPtr
->uVItemPadding
/ 2;
1809 center_offset_v
+= infoPtr
->uVItemPadding
/ 2;
1811 if (center_offset_v
< 0)
1812 center_offset_v
= 0;
1814 if(lStyle
& TCS_VERTICAL
)
1815 drawRect
->left
+= center_offset_v
;
1817 drawRect
->top
+= center_offset_v
;
1820 if(lStyle
& TCS_VERTICAL
) /* if we are vertical rotate the text and each character */
1822 static const WCHAR ArialW
[] = { 'A','r','i','a','l',0 };
1825 INT nEscapement
= 900;
1826 INT nOrientation
= 900;
1828 if(lStyle
& TCS_BOTTOM
)
1831 nOrientation
= -900;
1834 /* to get a font with the escapement and orientation we are looking for, we need to */
1835 /* call CreateFontIndirectA, which requires us to set the values of the logfont we pass in */
1836 if (!GetObjectW((infoPtr
->hFont
) ?
1837 infoPtr
->hFont
: GetStockObject(SYSTEM_FONT
),
1838 sizeof(LOGFONTW
),&logfont
))
1842 lstrcpyW(logfont
.lfFaceName
, ArialW
);
1843 logfont
.lfHeight
= -MulDiv(iPointSize
, GetDeviceCaps(hdc
, LOGPIXELSY
),
1845 logfont
.lfWeight
= FW_NORMAL
;
1846 logfont
.lfItalic
= 0;
1847 logfont
.lfUnderline
= 0;
1848 logfont
.lfStrikeOut
= 0;
1851 logfont
.lfEscapement
= nEscapement
;
1852 logfont
.lfOrientation
= nOrientation
;
1853 hFont
= CreateFontIndirectW(&logfont
);
1854 SelectObject(hdc
, hFont
);
1859 (lStyle
& TCS_BOTTOM
) ? drawRect
->right
: drawRect
->left
,
1860 (!(lStyle
& TCS_BOTTOM
)) ? drawRect
->bottom
: drawRect
->top
,
1864 lstrlenW(item
->pszText
),
1868 DeleteObject(hFont
);
1872 TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%s), textlen=%d\n",
1873 debugstr_w(item
->pszText
), center_offset_h
, center_offset_v
,
1874 wine_dbgstr_rect(drawRect
), (rcText
.right
-rcText
.left
));
1881 lstrlenW(item
->pszText
),
1883 DT_LEFT
| DT_SINGLELINE
1888 *drawRect
= rcTemp
; /* restore drawRect */
1894 SelectObject(hdc
, hOldFont
);
1895 SetBkMode(hdc
, oldBkMode
);
1896 SelectObject(hdc
, holdPen
);
1897 DeleteObject( htextPen
);
1900 /******************************************************************************
1903 * This method is used to draw a single tab into the tab control.
1905 static void TAB_DrawItem(const TAB_INFO
*infoPtr
, HDC hdc
, INT iItem
)
1907 LONG lStyle
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
1911 RECT r
, fillRect
, r1
;
1914 COLORREF bkgnd
, corner
;
1918 * Get the rectangle for the item.
1920 isVisible
= TAB_InternalGetItemRect(infoPtr
,
1929 /* Clip UpDown control to not draw over it */
1930 if (infoPtr
->needsScrolling
)
1932 GetWindowRect(infoPtr
->hwnd
, &rC
);
1933 GetWindowRect(infoPtr
->hwndUpDown
, &rUD
);
1934 ExcludeClipRect(hdc
, rUD
.left
- rC
.left
, rUD
.top
- rC
.top
, rUD
.right
- rC
.left
, rUD
.bottom
- rC
.top
);
1937 /* If you need to see what the control is doing,
1938 * then override these variables. They will change what
1939 * fill colors are used for filling the tabs, and the
1940 * corners when drawing the edge.
1942 bkgnd
= comctl32_color
.clrBtnFace
;
1943 corner
= comctl32_color
.clrBtnFace
;
1945 if (lStyle
& TCS_BUTTONS
)
1947 /* Get item rectangle */
1950 /* Separators between flat buttons */
1951 if (lStyle
& TCS_FLATBUTTONS
)
1954 r1
.right
+= (FLAT_BTN_SPACINGX
-2);
1955 DrawEdge(hdc
, &r1
, EDGE_ETCHED
, BF_RIGHT
);
1958 if (iItem
== infoPtr
->iSelected
)
1960 DrawEdge(hdc
, &r
, EDGE_SUNKEN
, BF_SOFT
|BF_RECT
);
1962 OffsetRect(&r
, 1, 1);
1964 else /* ! selected */
1966 if (!(lStyle
& TCS_FLATBUTTONS
))
1967 DrawEdge(hdc
, &r
, EDGE_RAISED
, BF_SOFT
|BF_RECT
);
1970 else /* !TCS_BUTTONS */
1972 /* We draw a rectangle of different sizes depending on the selection
1974 if (iItem
== infoPtr
->iSelected
) {
1976 GetClientRect (infoPtr
->hwnd
, &rect
);
1977 clRight
= rect
.right
;
1978 clBottom
= rect
.bottom
;
1985 * Erase the background. (Delay it but setup rectangle.)
1986 * This is necessary when drawing the selected item since it is larger
1987 * than the others, it might overlap with stuff already drawn by the
1992 /* Draw themed tabs - but only if they are at the top.
1993 * Windows draws even side or bottom tabs themed, with wacky results.
1994 * However, since in Wine apps may get themed that did not opt in via
1995 * a manifest avoid theming when we know the result will be wrong */
1996 if ((theme
= GetWindowTheme (infoPtr
->hwnd
))
1997 && ((lStyle
& (TCS_VERTICAL
| TCS_BOTTOM
)) == 0))
1999 static const int partIds
[8] = {
2002 TABP_TABITEMLEFTEDGE
,
2003 TABP_TABITEMRIGHTEDGE
,
2004 TABP_TABITEMBOTHEDGE
,
2007 TABP_TOPTABITEMLEFTEDGE
,
2008 TABP_TOPTABITEMRIGHTEDGE
,
2009 TABP_TOPTABITEMBOTHEDGE
,
2012 int stateId
= TIS_NORMAL
;
2014 /* selected and unselected tabs have different parts */
2015 if (iItem
== infoPtr
->iSelected
)
2017 /* The part also differs on the position of a tab on a line.
2018 * "Visually" determining the position works well enough. */
2019 if(selectedRect
.left
== 0)
2021 if(selectedRect
.right
== clRight
)
2024 if (iItem
== infoPtr
->iSelected
)
2025 stateId
= TIS_SELECTED
;
2026 else if (iItem
== infoPtr
->iHotTracked
)
2028 else if (iItem
== infoPtr
->uFocus
)
2029 stateId
= TIS_FOCUSED
;
2031 /* Adjust rectangle for bottommost row */
2032 if (TAB_GetItem(infoPtr
, iItem
)->rect
.top
== infoPtr
->uNumRows
-1)
2035 DrawThemeBackground (theme
, hdc
, partIds
[partIndex
], stateId
, &r
, NULL
);
2036 GetThemeBackgroundContentRect (theme
, hdc
, partIds
[partIndex
], stateId
, &r
, &r
);
2038 else if(lStyle
& TCS_VERTICAL
)
2040 /* These are for adjusting the drawing of a Selected tab */
2041 /* The initial values are for the normal case of non-Selected */
2042 int ZZ
= 1; /* Do not stretch if selected */
2043 if (iItem
== infoPtr
->iSelected
) {
2046 /* if leftmost draw the line longer */
2047 if(selectedRect
.top
== 0)
2048 fillRect
.top
+= CONTROL_BORDER_SIZEY
;
2049 /* if rightmost draw the line longer */
2050 if(selectedRect
.bottom
== clBottom
)
2051 fillRect
.bottom
-= CONTROL_BORDER_SIZEY
;
2054 if (lStyle
& TCS_BOTTOM
)
2056 /* Adjust both rectangles to match native */
2059 TRACE("<right> item=%d, fill=(%s), edge=(%s)\n",
2060 iItem
, wine_dbgstr_rect(&fillRect
), wine_dbgstr_rect(&r
));
2062 /* Clear interior */
2063 SetBkColor(hdc
, bkgnd
);
2064 ExtTextOutW(hdc
, 0, 0, 2, &fillRect
, NULL
, 0, 0);
2066 /* Draw rectangular edge around tab */
2067 DrawEdge(hdc
, &r
, EDGE_RAISED
, BF_SOFT
|BF_RIGHT
|BF_TOP
|BF_BOTTOM
);
2069 /* Now erase the top corner and draw diagonal edge */
2070 SetBkColor(hdc
, corner
);
2071 r1
.left
= r
.right
- ROUND_CORNER_SIZE
- 1;
2074 r1
.bottom
= r1
.top
+ ROUND_CORNER_SIZE
;
2075 ExtTextOutW(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2077 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_DIAGONAL_ENDTOPLEFT
);
2079 /* Now erase the bottom corner and draw diagonal edge */
2080 r1
.left
= r
.right
- ROUND_CORNER_SIZE
- 1;
2081 r1
.bottom
= r
.bottom
;
2083 r1
.top
= r1
.bottom
- ROUND_CORNER_SIZE
;
2084 ExtTextOutW(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2086 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_DIAGONAL_ENDBOTTOMLEFT
);
2088 if ((iItem
== infoPtr
->iSelected
) && (selectedRect
.top
== 0)) {
2092 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_TOP
);
2098 TRACE("<left> item=%d, fill=(%s), edge=(%s)\n",
2099 iItem
, wine_dbgstr_rect(&fillRect
), wine_dbgstr_rect(&r
));
2101 /* Clear interior */
2102 SetBkColor(hdc
, bkgnd
);
2103 ExtTextOutW(hdc
, 0, 0, 2, &fillRect
, NULL
, 0, 0);
2105 /* Draw rectangular edge around tab */
2106 DrawEdge(hdc
, &r
, EDGE_RAISED
, BF_SOFT
|BF_LEFT
|BF_TOP
|BF_BOTTOM
);
2108 /* Now erase the top corner and draw diagonal edge */
2109 SetBkColor(hdc
, corner
);
2112 r1
.right
= r1
.left
+ ROUND_CORNER_SIZE
+ 1;
2113 r1
.bottom
= r1
.top
+ ROUND_CORNER_SIZE
;
2114 ExtTextOutW(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2116 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_DIAGONAL_ENDTOPRIGHT
);
2118 /* Now erase the bottom corner and draw diagonal edge */
2120 r1
.bottom
= r
.bottom
;
2121 r1
.right
= r1
.left
+ ROUND_CORNER_SIZE
+ 1;
2122 r1
.top
= r1
.bottom
- ROUND_CORNER_SIZE
;
2123 ExtTextOutW(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2125 DrawEdge(hdc
, &r1
, EDGE_SUNKEN
, BF_DIAGONAL_ENDTOPLEFT
);
2128 else /* ! TCS_VERTICAL */
2130 /* These are for adjusting the drawing of a Selected tab */
2131 /* The initial values are for the normal case of non-Selected */
2132 if (iItem
== infoPtr
->iSelected
) {
2133 /* if leftmost draw the line longer */
2134 if(selectedRect
.left
== 0)
2135 fillRect
.left
+= CONTROL_BORDER_SIZEX
;
2136 /* if rightmost draw the line longer */
2137 if(selectedRect
.right
== clRight
)
2138 fillRect
.right
-= CONTROL_BORDER_SIZEX
;
2141 if (lStyle
& TCS_BOTTOM
)
2143 /* Adjust both rectangles for topmost row */
2144 if (TAB_GetItem(infoPtr
, iItem
)->rect
.top
== infoPtr
->uNumRows
-1)
2150 TRACE("<bottom> item=%d, fill=(%s), edge=(%s)\n",
2151 iItem
, wine_dbgstr_rect(&fillRect
), wine_dbgstr_rect(&r
));
2153 /* Clear interior */
2154 SetBkColor(hdc
, bkgnd
);
2155 ExtTextOutW(hdc
, 0, 0, 2, &fillRect
, NULL
, 0, 0);
2157 /* Draw rectangular edge around tab */
2158 DrawEdge(hdc
, &r
, EDGE_RAISED
, BF_SOFT
|BF_LEFT
|BF_BOTTOM
|BF_RIGHT
);
2160 /* Now erase the righthand corner and draw diagonal edge */
2161 SetBkColor(hdc
, corner
);
2162 r1
.left
= r
.right
- ROUND_CORNER_SIZE
;
2163 r1
.bottom
= r
.bottom
;
2165 r1
.top
= r1
.bottom
- ROUND_CORNER_SIZE
- 1;
2166 ExtTextOutW(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2168 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_DIAGONAL_ENDBOTTOMLEFT
);
2170 /* Now erase the lefthand corner and draw diagonal edge */
2172 r1
.bottom
= r
.bottom
;
2173 r1
.right
= r1
.left
+ ROUND_CORNER_SIZE
;
2174 r1
.top
= r1
.bottom
- ROUND_CORNER_SIZE
- 1;
2175 ExtTextOutW(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2177 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_DIAGONAL_ENDTOPLEFT
);
2179 if (iItem
== infoPtr
->iSelected
)
2183 if (selectedRect
.left
== 0)
2188 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_LEFT
);
2195 /* Adjust both rectangles for bottommost row */
2196 if (TAB_GetItem(infoPtr
, iItem
)->rect
.top
== infoPtr
->uNumRows
-1)
2198 fillRect
.bottom
+= 3;
2202 TRACE("<top> item=%d, fill=(%s), edge=(%s)\n",
2203 iItem
, wine_dbgstr_rect(&fillRect
), wine_dbgstr_rect(&r
));
2205 /* Clear interior */
2206 SetBkColor(hdc
, bkgnd
);
2207 ExtTextOutW(hdc
, 0, 0, 2, &fillRect
, NULL
, 0, 0);
2209 /* Draw rectangular edge around tab */
2210 DrawEdge(hdc
, &r
, EDGE_RAISED
, BF_SOFT
|BF_LEFT
|BF_TOP
|BF_RIGHT
);
2212 /* Now erase the righthand corner and draw diagonal edge */
2213 SetBkColor(hdc
, corner
);
2214 r1
.left
= r
.right
- ROUND_CORNER_SIZE
;
2217 r1
.bottom
= r1
.top
+ ROUND_CORNER_SIZE
+ 1;
2218 ExtTextOutW(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2220 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_DIAGONAL_ENDBOTTOMRIGHT
);
2222 /* Now erase the lefthand corner and draw diagonal edge */
2225 r1
.right
= r1
.left
+ ROUND_CORNER_SIZE
;
2226 r1
.bottom
= r1
.top
+ ROUND_CORNER_SIZE
+ 1;
2227 ExtTextOutW(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2229 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_DIAGONAL_ENDTOPRIGHT
);
2234 TAB_DumpItemInternal(infoPtr
, iItem
);
2236 /* This modifies r to be the text rectangle. */
2237 TAB_DrawItemInterior(infoPtr
, hdc
, iItem
, &r
);
2241 /******************************************************************************
2244 * This method is used to draw the raised border around the tab control
2247 static void TAB_DrawBorder(const TAB_INFO
*infoPtr
, HDC hdc
)
2250 DWORD lStyle
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
2251 HTHEME theme
= GetWindowTheme (infoPtr
->hwnd
);
2253 GetClientRect (infoPtr
->hwnd
, &rect
);
2256 * Adjust for the style
2259 if (infoPtr
->uNumItem
)
2261 if ((lStyle
& TCS_BOTTOM
) && !(lStyle
& TCS_VERTICAL
))
2262 rect
.bottom
-= infoPtr
->tabHeight
* infoPtr
->uNumRows
+ CONTROL_BORDER_SIZEX
;
2263 else if((lStyle
& TCS_BOTTOM
) && (lStyle
& TCS_VERTICAL
))
2264 rect
.right
-= infoPtr
->tabHeight
* infoPtr
->uNumRows
+ CONTROL_BORDER_SIZEX
;
2265 else if(lStyle
& TCS_VERTICAL
)
2266 rect
.left
+= infoPtr
->tabHeight
* infoPtr
->uNumRows
+ CONTROL_BORDER_SIZEX
;
2267 else /* not TCS_VERTICAL and not TCS_BOTTOM */
2268 rect
.top
+= infoPtr
->tabHeight
* infoPtr
->uNumRows
+ CONTROL_BORDER_SIZEX
;
2271 TRACE("border=(%s)\n", wine_dbgstr_rect(&rect
));
2274 DrawThemeBackground (theme
, hdc
, TABP_PANE
, 0, &rect
, NULL
);
2276 DrawEdge(hdc
, &rect
, EDGE_RAISED
, BF_SOFT
|BF_RECT
);
2279 /******************************************************************************
2282 * This method repaints the tab control..
2284 static void TAB_Refresh (TAB_INFO
*infoPtr
, HDC hdc
)
2289 if (!infoPtr
->DoRedraw
)
2292 hOldFont
= SelectObject (hdc
, infoPtr
->hFont
);
2294 if (GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
) & TCS_BUTTONS
)
2296 for (i
= 0; i
< infoPtr
->uNumItem
; i
++)
2297 TAB_DrawItem (infoPtr
, hdc
, i
);
2301 /* Draw all the non selected item first */
2302 for (i
= 0; i
< infoPtr
->uNumItem
; i
++)
2304 if (i
!= infoPtr
->iSelected
)
2305 TAB_DrawItem (infoPtr
, hdc
, i
);
2308 /* Now, draw the border, draw it before the selected item
2309 * since the selected item overwrites part of the border. */
2310 TAB_DrawBorder (infoPtr
, hdc
);
2312 /* Then, draw the selected item */
2313 TAB_DrawItem (infoPtr
, hdc
, infoPtr
->iSelected
);
2316 SelectObject (hdc
, hOldFont
);
2319 static inline DWORD
TAB_GetRowCount (const TAB_INFO
*infoPtr
)
2321 return infoPtr
->uNumRows
;
2324 static inline LRESULT
TAB_SetRedraw (TAB_INFO
*infoPtr
, BOOL doRedraw
)
2326 infoPtr
->DoRedraw
= doRedraw
;
2330 /******************************************************************************
2331 * TAB_EnsureSelectionVisible
2333 * This method will make sure that the current selection is completely
2334 * visible by scrolling until it is.
2336 static void TAB_EnsureSelectionVisible(
2339 INT iSelected
= infoPtr
->iSelected
;
2340 LONG lStyle
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
2341 INT iOrigLeftmostVisible
= infoPtr
->leftmostVisible
;
2343 /* set the items row to the bottommost row or topmost row depending on
2345 if ((infoPtr
->uNumRows
> 1) && !(lStyle
& TCS_BUTTONS
))
2347 TAB_ITEM
*selected
= TAB_GetItem(infoPtr
, iSelected
);
2351 if(lStyle
& TCS_VERTICAL
)
2352 newselected
= selected
->rect
.left
;
2354 newselected
= selected
->rect
.top
;
2356 /* the target row is always (number of rows - 1)
2357 as row 0 is furthest from the clientRect */
2358 iTargetRow
= infoPtr
->uNumRows
- 1;
2360 if (newselected
!= iTargetRow
)
2363 if(lStyle
& TCS_VERTICAL
)
2365 for (i
=0; i
< infoPtr
->uNumItem
; i
++)
2367 /* move everything in the row of the selected item to the iTargetRow */
2368 TAB_ITEM
*item
= TAB_GetItem(infoPtr
, i
);
2370 if (item
->rect
.left
== newselected
)
2371 item
->rect
.left
= iTargetRow
;
2374 if (item
->rect
.left
> newselected
)
2381 for (i
=0; i
< infoPtr
->uNumItem
; i
++)
2383 TAB_ITEM
*item
= TAB_GetItem(infoPtr
, i
);
2385 if (item
->rect
.top
== newselected
)
2386 item
->rect
.top
= iTargetRow
;
2389 if (item
->rect
.top
> newselected
)
2394 TAB_RecalcHotTrack(infoPtr
, NULL
, NULL
, NULL
);
2399 * Do the trivial cases first.
2401 if ( (!infoPtr
->needsScrolling
) ||
2402 (infoPtr
->hwndUpDown
==0) || (lStyle
& TCS_VERTICAL
))
2405 if (infoPtr
->leftmostVisible
>= iSelected
)
2407 infoPtr
->leftmostVisible
= iSelected
;
2411 TAB_ITEM
*selected
= TAB_GetItem(infoPtr
, iSelected
);
2416 /* Calculate the part of the client area that is visible */
2417 GetClientRect(infoPtr
->hwnd
, &r
);
2420 GetClientRect(infoPtr
->hwndUpDown
, &r
);
2423 if ((selected
->rect
.right
-
2424 selected
->rect
.left
) >= width
)
2426 /* Special case: width of selected item is greater than visible
2429 infoPtr
->leftmostVisible
= iSelected
;
2433 for (i
= infoPtr
->leftmostVisible
; i
< infoPtr
->uNumItem
; i
++)
2435 if ((selected
->rect
.right
- TAB_GetItem(infoPtr
, i
)->rect
.left
) < width
)
2438 infoPtr
->leftmostVisible
= i
;
2442 if (infoPtr
->leftmostVisible
!= iOrigLeftmostVisible
)
2443 TAB_RecalcHotTrack(infoPtr
, NULL
, NULL
, NULL
);
2445 SendMessageW(infoPtr
->hwndUpDown
, UDM_SETPOS
, 0,
2446 MAKELONG(infoPtr
->leftmostVisible
, 0));
2449 /******************************************************************************
2450 * TAB_InvalidateTabArea
2452 * This method will invalidate the portion of the control that contains the
2453 * tabs. It is called when the state of the control changes and needs
2456 static void TAB_InvalidateTabArea(const TAB_INFO
*infoPtr
)
2458 RECT clientRect
, rInvalidate
, rAdjClient
;
2459 DWORD lStyle
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
2460 INT lastRow
= infoPtr
->uNumRows
- 1;
2463 if (lastRow
< 0) return;
2465 GetClientRect(infoPtr
->hwnd
, &clientRect
);
2466 rInvalidate
= clientRect
;
2467 rAdjClient
= clientRect
;
2469 TAB_AdjustRect(infoPtr
, 0, &rAdjClient
);
2471 TAB_InternalGetItemRect(infoPtr
, infoPtr
->uNumItem
-1 , &rect
, NULL
);
2472 if ((lStyle
& TCS_BOTTOM
) && (lStyle
& TCS_VERTICAL
))
2474 rInvalidate
.left
= rAdjClient
.right
;
2475 if (infoPtr
->uNumRows
== 1)
2476 rInvalidate
.bottom
= clientRect
.top
+ rect
.bottom
+ 2 * SELECTED_TAB_OFFSET
;
2478 else if(lStyle
& TCS_VERTICAL
)
2480 rInvalidate
.right
= rAdjClient
.left
;
2481 if (infoPtr
->uNumRows
== 1)
2482 rInvalidate
.bottom
= clientRect
.top
+ rect
.bottom
+ 2 * SELECTED_TAB_OFFSET
;
2484 else if (lStyle
& TCS_BOTTOM
)
2486 rInvalidate
.top
= rAdjClient
.bottom
;
2487 if (infoPtr
->uNumRows
== 1)
2488 rInvalidate
.right
= clientRect
.left
+ rect
.right
+ 2 * SELECTED_TAB_OFFSET
;
2492 rInvalidate
.bottom
= rAdjClient
.top
;
2493 if (infoPtr
->uNumRows
== 1)
2494 rInvalidate
.right
= clientRect
.left
+ rect
.right
+ 2 * SELECTED_TAB_OFFSET
;
2497 /* Punch out the updown control */
2498 if (infoPtr
->needsScrolling
&& (rInvalidate
.right
> 0)) {
2500 GetClientRect(infoPtr
->hwndUpDown
, &r
);
2501 if (rInvalidate
.right
> clientRect
.right
- r
.left
)
2502 rInvalidate
.right
= rInvalidate
.right
- (r
.right
- r
.left
);
2504 rInvalidate
.right
= clientRect
.right
- r
.left
;
2507 TRACE("invalidate (%s)\n", wine_dbgstr_rect(&rInvalidate
));
2509 InvalidateRect(infoPtr
->hwnd
, &rInvalidate
, TRUE
);
2512 static inline LRESULT
TAB_Paint (TAB_INFO
*infoPtr
, HDC hdcPaint
)
2521 hdc
= BeginPaint (infoPtr
->hwnd
, &ps
);
2522 TRACE("erase %d, rect=(%s)\n", ps
.fErase
, wine_dbgstr_rect(&ps
.rcPaint
));
2525 TAB_Refresh (infoPtr
, hdc
);
2528 EndPaint (infoPtr
->hwnd
, &ps
);
2534 TAB_InsertItemT (TAB_INFO
*infoPtr
, WPARAM wParam
, LPARAM lParam
, BOOL bUnicode
)
2541 GetClientRect (infoPtr
->hwnd
, &rect
);
2542 TRACE("Rect: %p %s\n", infoPtr
->hwnd
, wine_dbgstr_rect(&rect
));
2544 pti
= (TCITEMW
*)lParam
;
2545 iItem
= (INT
)wParam
;
2547 if (iItem
< 0) return -1;
2548 if (iItem
> infoPtr
->uNumItem
)
2549 iItem
= infoPtr
->uNumItem
;
2551 TAB_DumpItemExternalT(pti
, iItem
, bUnicode
);
2554 if (infoPtr
->uNumItem
== 0) {
2555 infoPtr
->items
= Alloc (TAB_ITEM_SIZE(infoPtr
));
2556 infoPtr
->uNumItem
++;
2557 infoPtr
->iSelected
= 0;
2560 LPBYTE oldItems
= (LPBYTE
)infoPtr
->items
;
2562 infoPtr
->uNumItem
++;
2563 infoPtr
->items
= Alloc (TAB_ITEM_SIZE(infoPtr
) * infoPtr
->uNumItem
);
2565 /* pre insert copy */
2567 memcpy (infoPtr
->items
, oldItems
,
2568 iItem
* TAB_ITEM_SIZE(infoPtr
));
2571 /* post insert copy */
2572 if (iItem
< infoPtr
->uNumItem
- 1) {
2573 memcpy (TAB_GetItem(infoPtr
, iItem
+ 1),
2574 oldItems
+ iItem
* TAB_ITEM_SIZE(infoPtr
),
2575 (infoPtr
->uNumItem
- iItem
- 1) * TAB_ITEM_SIZE(infoPtr
));
2579 if (iItem
<= infoPtr
->iSelected
)
2580 infoPtr
->iSelected
++;
2585 item
= TAB_GetItem(infoPtr
, iItem
);
2587 item
->pszText
= NULL
;
2589 if (pti
->mask
& TCIF_TEXT
)
2592 Str_SetPtrW (&item
->pszText
, pti
->pszText
);
2594 Str_SetPtrAtoW (&item
->pszText
, (LPSTR
)pti
->pszText
);
2597 if (pti
->mask
& TCIF_IMAGE
)
2598 item
->iImage
= pti
->iImage
;
2602 if (pti
->mask
& TCIF_PARAM
)
2603 memcpy(item
->extra
, &pti
->lParam
, infoPtr
->cbInfo
);
2605 memset(item
->extra
, 0, infoPtr
->cbInfo
);
2607 TAB_SetItemBounds(infoPtr
);
2608 if (infoPtr
->uNumItem
> 1)
2609 TAB_InvalidateTabArea(infoPtr
);
2611 InvalidateRect(infoPtr
->hwnd
, NULL
, TRUE
);
2613 TRACE("[%p]: added item %d %s\n",
2614 infoPtr
->hwnd
, iItem
, debugstr_w(item
->pszText
));
2616 /* If we haven't set the current focus yet, set it now. */
2617 if (infoPtr
->uFocus
== -1)
2618 TAB_SetCurFocus(infoPtr
, iItem
);
2624 TAB_SetItemSize (TAB_INFO
*infoPtr
, LPARAM lParam
)
2626 LONG lStyle
= GetWindowLongW(infoPtr
->hwnd
, GWL_STYLE
);
2628 BOOL bNeedPaint
= FALSE
;
2630 lResult
= MAKELONG(infoPtr
->tabWidth
, infoPtr
->tabHeight
);
2632 /* UNDOCUMENTED: If requested Width or Height is 0 this means that program wants to use auto size. */
2633 if (lStyle
& TCS_FIXEDWIDTH
&& (infoPtr
->tabWidth
!= (INT
)LOWORD(lParam
)))
2635 infoPtr
->tabWidth
= (INT
)LOWORD(lParam
);
2639 if (infoPtr
->tabHeight
!= (INT
)HIWORD(lParam
))
2641 if ((infoPtr
->fHeightSet
= ((INT
)HIWORD(lParam
) != 0)))
2642 infoPtr
->tabHeight
= (INT
)HIWORD(lParam
);
2646 TRACE("was h=%d,w=%d, now h=%d,w=%d\n",
2647 HIWORD(lResult
), LOWORD(lResult
),
2648 infoPtr
->tabHeight
, infoPtr
->tabWidth
);
2652 TAB_SetItemBounds(infoPtr
);
2653 RedrawWindow(infoPtr
->hwnd
, NULL
, NULL
, RDW_ERASE
| RDW_INVALIDATE
| RDW_UPDATENOW
);
2659 static inline LRESULT
TAB_SetMinTabWidth (TAB_INFO
*infoPtr
, INT cx
)
2663 TRACE("(%p,%d)\n", infoPtr
, cx
);
2665 oldcx
= infoPtr
->tabMinWidth
;
2666 infoPtr
->tabMinWidth
= cx
;
2667 TAB_SetItemBounds(infoPtr
);
2671 static inline LRESULT
2672 TAB_HighlightItem (TAB_INFO
*infoPtr
, INT iItem
, BOOL fHighlight
)
2676 TRACE("(%p,%d,%s)\n", infoPtr
, iItem
, fHighlight
? "true" : "false");
2678 if (!infoPtr
|| iItem
< 0 || iItem
>= infoPtr
->uNumItem
)
2681 lpState
= &TAB_GetItem(infoPtr
, iItem
)->dwState
;
2684 *lpState
|= TCIS_HIGHLIGHTED
;
2686 *lpState
&= ~TCIS_HIGHLIGHTED
;
2692 TAB_SetItemT (TAB_INFO
*infoPtr
, INT iItem
, LPTCITEMW tabItem
, BOOL bUnicode
)
2696 TRACE("(%p,%d,%p,%s)\n", infoPtr
, iItem
, tabItem
, bUnicode
? "true" : "false");
2698 if (iItem
< 0 || iItem
>= infoPtr
->uNumItem
)
2701 TAB_DumpItemExternalT(tabItem
, iItem
, bUnicode
);
2703 wineItem
= TAB_GetItem(infoPtr
, iItem
);
2705 if (tabItem
->mask
& TCIF_IMAGE
)
2706 wineItem
->iImage
= tabItem
->iImage
;
2708 if (tabItem
->mask
& TCIF_PARAM
)
2709 memcpy(wineItem
->extra
, &tabItem
->lParam
, infoPtr
->cbInfo
);
2711 if (tabItem
->mask
& TCIF_RTLREADING
)
2712 FIXME("TCIF_RTLREADING\n");
2714 if (tabItem
->mask
& TCIF_STATE
)
2715 wineItem
->dwState
= tabItem
->dwState
;
2717 if (tabItem
->mask
& TCIF_TEXT
)
2719 Free(wineItem
->pszText
);
2720 wineItem
->pszText
= NULL
;
2722 Str_SetPtrW(&wineItem
->pszText
, tabItem
->pszText
);
2724 Str_SetPtrAtoW(&wineItem
->pszText
, (LPSTR
)tabItem
->pszText
);
2727 /* Update and repaint tabs */
2728 TAB_SetItemBounds(infoPtr
);
2729 TAB_InvalidateTabArea(infoPtr
);
2734 static inline LRESULT
TAB_GetItemCount (const TAB_INFO
*infoPtr
)
2736 return infoPtr
->uNumItem
;
2741 TAB_GetItemT (TAB_INFO
*infoPtr
, INT iItem
, LPTCITEMW tabItem
, BOOL bUnicode
)
2745 TRACE("(%p,%d,%p,%s)\n", infoPtr
, iItem
, tabItem
, bUnicode
? "true" : "false");
2747 if (iItem
< 0 || iItem
>= infoPtr
->uNumItem
)
2750 wineItem
= TAB_GetItem(infoPtr
, iItem
);
2752 if (tabItem
->mask
& TCIF_IMAGE
)
2753 tabItem
->iImage
= wineItem
->iImage
;
2755 if (tabItem
->mask
& TCIF_PARAM
)
2756 memcpy(&tabItem
->lParam
, wineItem
->extra
, infoPtr
->cbInfo
);
2758 if (tabItem
->mask
& TCIF_RTLREADING
)
2759 FIXME("TCIF_RTLREADING\n");
2761 if (tabItem
->mask
& TCIF_STATE
)
2762 tabItem
->dwState
= wineItem
->dwState
;
2764 if (tabItem
->mask
& TCIF_TEXT
)
2767 Str_GetPtrW (wineItem
->pszText
, tabItem
->pszText
, tabItem
->cchTextMax
);
2769 Str_GetPtrWtoA (wineItem
->pszText
, (LPSTR
)tabItem
->pszText
, tabItem
->cchTextMax
);
2772 TAB_DumpItemExternalT(tabItem
, iItem
, bUnicode
);
2778 static LRESULT
TAB_DeleteItem (TAB_INFO
*infoPtr
, INT iItem
)
2780 BOOL bResult
= FALSE
;
2782 TRACE("(%p, %d)\n", infoPtr
, iItem
);
2784 if ((iItem
>= 0) && (iItem
< infoPtr
->uNumItem
))
2786 TAB_ITEM
*item
= TAB_GetItem(infoPtr
, iItem
);
2787 LPBYTE oldItems
= (LPBYTE
)infoPtr
->items
;
2789 TAB_InvalidateTabArea(infoPtr
);
2790 Free(item
->pszText
);
2791 infoPtr
->uNumItem
--;
2793 if (!infoPtr
->uNumItem
)
2795 infoPtr
->items
= NULL
;
2796 if (infoPtr
->iHotTracked
>= 0)
2798 KillTimer(infoPtr
->hwnd
, TAB_HOTTRACK_TIMER
);
2799 infoPtr
->iHotTracked
= -1;
2804 infoPtr
->items
= Alloc(TAB_ITEM_SIZE(infoPtr
) * infoPtr
->uNumItem
);
2807 memcpy(infoPtr
->items
, oldItems
, iItem
* TAB_ITEM_SIZE(infoPtr
));
2809 if (iItem
< infoPtr
->uNumItem
)
2810 memcpy(TAB_GetItem(infoPtr
, iItem
),
2811 oldItems
+ (iItem
+ 1) * TAB_ITEM_SIZE(infoPtr
),
2812 (infoPtr
->uNumItem
- iItem
) * TAB_ITEM_SIZE(infoPtr
));
2814 if (iItem
<= infoPtr
->iHotTracked
)
2816 /* When tabs move left/up, the hot track item may change */
2817 FIXME("Recalc hot track\n");
2822 /* Readjust the selected index */
2823 if ((iItem
== infoPtr
->iSelected
) && (iItem
> 0))
2824 infoPtr
->iSelected
--;
2826 if (iItem
< infoPtr
->iSelected
)
2827 infoPtr
->iSelected
--;
2829 if (infoPtr
->uNumItem
== 0)
2830 infoPtr
->iSelected
= -1;
2832 /* Reposition and repaint tabs */
2833 TAB_SetItemBounds(infoPtr
);
2841 static inline LRESULT
TAB_DeleteAllItems (TAB_INFO
*infoPtr
)
2843 TRACE("(%p)\n", infoPtr
);
2844 while (infoPtr
->uNumItem
)
2845 TAB_DeleteItem (infoPtr
, 0);
2850 static inline LRESULT
TAB_GetFont (const TAB_INFO
*infoPtr
)
2852 TRACE("(%p) returning %p\n", infoPtr
, infoPtr
->hFont
);
2853 return (LRESULT
)infoPtr
->hFont
;
2856 static inline LRESULT
TAB_SetFont (TAB_INFO
*infoPtr
, HFONT hNewFont
)
2858 TRACE("(%p,%p)\n", infoPtr
, hNewFont
);
2860 infoPtr
->hFont
= hNewFont
;
2862 TAB_SetItemBounds(infoPtr
);
2864 TAB_InvalidateTabArea(infoPtr
);
2870 static inline LRESULT
TAB_GetImageList (const TAB_INFO
*infoPtr
)
2873 return (LRESULT
)infoPtr
->himl
;
2876 static inline LRESULT
TAB_SetImageList (TAB_INFO
*infoPtr
, HIMAGELIST himlNew
)
2878 HIMAGELIST himlPrev
= infoPtr
->himl
;
2880 infoPtr
->himl
= himlNew
;
2881 TAB_SetItemBounds(infoPtr
);
2882 InvalidateRect(infoPtr
->hwnd
, NULL
, TRUE
);
2883 return (LRESULT
)himlPrev
;
2886 static inline LRESULT
TAB_GetUnicodeFormat (const TAB_INFO
*infoPtr
)
2888 return infoPtr
->bUnicode
;
2891 static inline LRESULT
TAB_SetUnicodeFormat (TAB_INFO
*infoPtr
, BOOL bUnicode
)
2893 BOOL bTemp
= infoPtr
->bUnicode
;
2895 infoPtr
->bUnicode
= bUnicode
;
2900 static inline LRESULT
TAB_Size (TAB_INFO
*infoPtr
)
2902 /* I'm not really sure what the following code was meant to do.
2903 This is what it is doing:
2904 When WM_SIZE is sent with SIZE_RESTORED, the control
2905 gets positioned in the top left corner.
2909 UINT uPosFlags,cx,cy;
2913 parent = GetParent (hwnd);
2914 GetClientRect(parent, &parent_rect);
2917 if (GetWindowLongW(hwnd, GWL_STYLE) & CCS_NORESIZE)
2918 uPosFlags |= (SWP_NOSIZE | SWP_NOMOVE);
2920 SetWindowPos (hwnd, 0, parent_rect.left, parent_rect.top,
2921 cx, cy, uPosFlags | SWP_NOZORDER);
2923 FIXME("WM_SIZE flag %x %lx not handled\n", wParam, lParam);
2926 /* Recompute the size/position of the tabs. */
2927 TAB_SetItemBounds (infoPtr
);
2929 /* Force a repaint of the control. */
2930 InvalidateRect(infoPtr
->hwnd
, NULL
, TRUE
);
2936 static LRESULT
TAB_Create (HWND hwnd
, WPARAM wParam
, LPARAM lParam
)
2939 TEXTMETRICW fontMetrics
;
2944 infoPtr
= Alloc (sizeof(TAB_INFO
));
2946 SetWindowLongPtrW(hwnd
, 0, (DWORD_PTR
)infoPtr
);
2948 infoPtr
->hwnd
= hwnd
;
2949 infoPtr
->hwndNotify
= ((LPCREATESTRUCTW
)lParam
)->hwndParent
;
2950 infoPtr
->uNumItem
= 0;
2951 infoPtr
->uNumRows
= 0;
2952 infoPtr
->uHItemPadding
= 6;
2953 infoPtr
->uVItemPadding
= 3;
2954 infoPtr
->uHItemPadding_s
= 6;
2955 infoPtr
->uVItemPadding_s
= 3;
2958 infoPtr
->hcurArrow
= LoadCursorW (0, (LPWSTR
)IDC_ARROW
);
2959 infoPtr
->iSelected
= -1;
2960 infoPtr
->iHotTracked
= -1;
2961 infoPtr
->uFocus
= -1;
2962 infoPtr
->hwndToolTip
= 0;
2963 infoPtr
->DoRedraw
= TRUE
;
2964 infoPtr
->needsScrolling
= FALSE
;
2965 infoPtr
->hwndUpDown
= 0;
2966 infoPtr
->leftmostVisible
= 0;
2967 infoPtr
->fHeightSet
= FALSE
;
2968 infoPtr
->bUnicode
= IsWindowUnicode (hwnd
);
2969 infoPtr
->cbInfo
= sizeof(LPARAM
);
2971 TRACE("Created tab control, hwnd [%p]\n", hwnd
);
2973 /* The tab control always has the WS_CLIPSIBLINGS style. Even
2974 if you don't specify it in CreateWindow. This is necessary in
2975 order for paint to work correctly. This follows windows behaviour. */
2976 dwStyle
= GetWindowLongW(hwnd
, GWL_STYLE
);
2977 SetWindowLongW(hwnd
, GWL_STYLE
, dwStyle
|WS_CLIPSIBLINGS
);
2979 if (dwStyle
& TCS_TOOLTIPS
) {
2980 /* Create tooltip control */
2981 infoPtr
->hwndToolTip
=
2982 CreateWindowExW (0, TOOLTIPS_CLASSW
, NULL
, WS_POPUP
,
2983 CW_USEDEFAULT
, CW_USEDEFAULT
,
2984 CW_USEDEFAULT
, CW_USEDEFAULT
,
2987 /* Send NM_TOOLTIPSCREATED notification */
2988 if (infoPtr
->hwndToolTip
) {
2989 NMTOOLTIPSCREATED nmttc
;
2991 nmttc
.hdr
.hwndFrom
= hwnd
;
2992 nmttc
.hdr
.idFrom
= GetWindowLongPtrW(hwnd
, GWLP_ID
);
2993 nmttc
.hdr
.code
= NM_TOOLTIPSCREATED
;
2994 nmttc
.hwndToolTips
= infoPtr
->hwndToolTip
;
2996 SendMessageW (infoPtr
->hwndNotify
, WM_NOTIFY
,
2997 (WPARAM
)GetWindowLongPtrW(hwnd
, GWLP_ID
), (LPARAM
)&nmttc
);
3001 OpenThemeData (infoPtr
->hwnd
, themeClass
);
3004 * We need to get text information so we need a DC and we need to select
3008 hOldFont
= SelectObject (hdc
, GetStockObject (SYSTEM_FONT
));
3010 /* Use the system font to determine the initial height of a tab. */
3011 GetTextMetricsW(hdc
, &fontMetrics
);
3014 * Make sure there is enough space for the letters + growing the
3015 * selected item + extra space for the selected item.
3017 infoPtr
->tabHeight
= fontMetrics
.tmHeight
+ SELECTED_TAB_OFFSET
+
3018 ((dwStyle
& TCS_BUTTONS
) ? 2 : 1) *
3019 infoPtr
->uVItemPadding
;
3021 /* Initialize the width of a tab. */
3022 if (dwStyle
& TCS_FIXEDWIDTH
)
3023 infoPtr
->tabWidth
= GetDeviceCaps(hdc
, LOGPIXELSX
);
3025 infoPtr
->tabMinWidth
= -1;
3027 TRACE("tabH=%d, tabW=%d\n", infoPtr
->tabHeight
, infoPtr
->tabWidth
);
3029 SelectObject (hdc
, hOldFont
);
3030 ReleaseDC(hwnd
, hdc
);
3036 TAB_Destroy (TAB_INFO
*infoPtr
)
3043 SetWindowLongPtrW(infoPtr
->hwnd
, 0, 0);
3045 if (infoPtr
->items
) {
3046 for (iItem
= 0; iItem
< infoPtr
->uNumItem
; iItem
++) {
3047 Free (TAB_GetItem(infoPtr
, iItem
)->pszText
);
3049 Free (infoPtr
->items
);
3052 if (infoPtr
->hwndToolTip
)
3053 DestroyWindow (infoPtr
->hwndToolTip
);
3055 if (infoPtr
->hwndUpDown
)
3056 DestroyWindow(infoPtr
->hwndUpDown
);
3058 if (infoPtr
->iHotTracked
>= 0)
3059 KillTimer(infoPtr
->hwnd
, TAB_HOTTRACK_TIMER
);
3061 CloseThemeData (GetWindowTheme (infoPtr
->hwnd
));
3067 /* update theme after a WM_THEMECHANGED message */
3068 static LRESULT
theme_changed(const TAB_INFO
*infoPtr
)
3070 HTHEME theme
= GetWindowTheme (infoPtr
->hwnd
);
3071 CloseThemeData (theme
);
3072 OpenThemeData (infoPtr
->hwnd
, themeClass
);
3076 static LRESULT
TAB_NCCalcSize(HWND hwnd
, WPARAM wParam
, LPARAM lParam
)
3080 return WVR_ALIGNTOP
;
3083 static inline LRESULT
3084 TAB_SetItemExtra (TAB_INFO
*infoPtr
, INT cbInfo
)
3086 if (!infoPtr
|| cbInfo
<= 0)
3089 if (infoPtr
->uNumItem
)
3091 /* FIXME: MSDN says this is not allowed, but this hasn't been verified */
3095 infoPtr
->cbInfo
= cbInfo
;
3099 static LRESULT WINAPI
3100 TAB_WindowProc (HWND hwnd
, UINT uMsg
, WPARAM wParam
, LPARAM lParam
)
3102 TAB_INFO
*infoPtr
= TAB_GetInfoPtr(hwnd
);
3104 TRACE("hwnd=%p msg=%x wParam=%lx lParam=%lx\n", hwnd
, uMsg
, wParam
, lParam
);
3105 if (!infoPtr
&& (uMsg
!= WM_CREATE
))
3106 return DefWindowProcW (hwnd
, uMsg
, wParam
, lParam
);
3110 case TCM_GETIMAGELIST
:
3111 return TAB_GetImageList (infoPtr
);
3113 case TCM_SETIMAGELIST
:
3114 return TAB_SetImageList (infoPtr
, (HIMAGELIST
)lParam
);
3116 case TCM_GETITEMCOUNT
:
3117 return TAB_GetItemCount (infoPtr
);
3121 return TAB_GetItemT (infoPtr
, (INT
)wParam
, (LPTCITEMW
)lParam
, uMsg
== TCM_GETITEMW
);
3125 return TAB_SetItemT (infoPtr
, (INT
)wParam
, (LPTCITEMW
)lParam
, uMsg
== TCM_SETITEMW
);
3127 case TCM_DELETEITEM
:
3128 return TAB_DeleteItem (infoPtr
, (INT
)wParam
);
3130 case TCM_DELETEALLITEMS
:
3131 return TAB_DeleteAllItems (infoPtr
);
3133 case TCM_GETITEMRECT
:
3134 return TAB_GetItemRect (infoPtr
, wParam
, lParam
);
3137 return TAB_GetCurSel (infoPtr
);
3140 return TAB_HitTest (infoPtr
, (LPTCHITTESTINFO
)lParam
);
3143 return TAB_SetCurSel (infoPtr
, (INT
)wParam
);
3145 case TCM_INSERTITEMA
:
3146 case TCM_INSERTITEMW
:
3147 return TAB_InsertItemT (infoPtr
, wParam
, lParam
, uMsg
== TCM_INSERTITEMW
);
3149 case TCM_SETITEMEXTRA
:
3150 return TAB_SetItemExtra (infoPtr
, (int)wParam
);
3152 case TCM_ADJUSTRECT
:
3153 return TAB_AdjustRect (infoPtr
, (BOOL
)wParam
, (LPRECT
)lParam
);
3155 case TCM_SETITEMSIZE
:
3156 return TAB_SetItemSize (infoPtr
, lParam
);
3158 case TCM_REMOVEIMAGE
:
3159 FIXME("Unimplemented msg TCM_REMOVEIMAGE\n");
3162 case TCM_SETPADDING
:
3163 return TAB_SetPadding (infoPtr
, lParam
);
3165 case TCM_GETROWCOUNT
:
3166 return TAB_GetRowCount(infoPtr
);
3168 case TCM_GETUNICODEFORMAT
:
3169 return TAB_GetUnicodeFormat (infoPtr
);
3171 case TCM_SETUNICODEFORMAT
:
3172 return TAB_SetUnicodeFormat (infoPtr
, (BOOL
)wParam
);
3174 case TCM_HIGHLIGHTITEM
:
3175 return TAB_HighlightItem (infoPtr
, (INT
)wParam
, (BOOL
)LOWORD(lParam
));
3177 case TCM_GETTOOLTIPS
:
3178 return TAB_GetToolTips (infoPtr
);
3180 case TCM_SETTOOLTIPS
:
3181 return TAB_SetToolTips (infoPtr
, (HWND
)wParam
);
3183 case TCM_GETCURFOCUS
:
3184 return TAB_GetCurFocus (infoPtr
);
3186 case TCM_SETCURFOCUS
:
3187 return TAB_SetCurFocus (infoPtr
, (INT
)wParam
);
3189 case TCM_SETMINTABWIDTH
:
3190 return TAB_SetMinTabWidth(infoPtr
, (INT
)lParam
);
3192 case TCM_DESELECTALL
:
3193 FIXME("Unimplemented msg TCM_DESELECTALL\n");
3196 case TCM_GETEXTENDEDSTYLE
:
3197 FIXME("Unimplemented msg TCM_GETEXTENDEDSTYLE\n");
3200 case TCM_SETEXTENDEDSTYLE
:
3201 FIXME("Unimplemented msg TCM_SETEXTENDEDSTYLE\n");
3205 return TAB_GetFont (infoPtr
);
3208 return TAB_SetFont (infoPtr
, (HFONT
)wParam
);
3211 return TAB_Create (hwnd
, wParam
, lParam
);
3214 return TAB_Destroy (infoPtr
);
3217 return DLGC_WANTARROWS
| DLGC_WANTCHARS
;
3219 case WM_LBUTTONDOWN
:
3220 return TAB_LButtonDown (infoPtr
, wParam
, lParam
);
3223 return TAB_LButtonUp (infoPtr
);
3226 return SendMessageW(infoPtr
->hwndNotify
, WM_NOTIFY
, wParam
, lParam
);
3228 case WM_RBUTTONDOWN
:
3229 return TAB_RButtonDown (infoPtr
);
3232 return TAB_MouseMove (infoPtr
, wParam
, lParam
);
3234 case WM_PRINTCLIENT
:
3236 return TAB_Paint (infoPtr
, (HDC
)wParam
);
3239 return TAB_Size (infoPtr
);
3242 return TAB_SetRedraw (infoPtr
, (BOOL
)wParam
);
3245 return TAB_OnHScroll(infoPtr
, (int)LOWORD(wParam
), (int)HIWORD(wParam
), (HWND
)lParam
);
3247 case WM_STYLECHANGED
:
3248 TAB_SetItemBounds (infoPtr
);
3249 InvalidateRect(hwnd
, NULL
, TRUE
);
3252 case WM_SYSCOLORCHANGE
:
3253 COMCTL32_RefreshSysColors();
3256 case WM_THEMECHANGED
:
3257 return theme_changed (infoPtr
);
3261 TAB_FocusChanging(infoPtr
);
3262 break; /* Don't disturb normal focus behavior */
3265 return TAB_KeyUp(infoPtr
, wParam
);
3267 return TAB_NCHitTest(infoPtr
, lParam
);
3270 return TAB_NCCalcSize(hwnd
, wParam
, lParam
);
3273 if (uMsg
>= WM_USER
&& uMsg
< WM_APP
&& !COMCTL32_IsReflectedMessage(uMsg
))
3274 WARN("unknown msg %04x wp=%08lx lp=%08lx\n",
3275 uMsg
, wParam
, lParam
);
3278 return DefWindowProcW(hwnd
, uMsg
, wParam
, lParam
);
3287 ZeroMemory (&wndClass
, sizeof(WNDCLASSW
));
3288 wndClass
.style
= CS_GLOBALCLASS
| CS_DBLCLKS
| CS_HREDRAW
| CS_VREDRAW
;
3289 wndClass
.lpfnWndProc
= TAB_WindowProc
;
3290 wndClass
.cbClsExtra
= 0;
3291 wndClass
.cbWndExtra
= sizeof(TAB_INFO
*);
3292 wndClass
.hCursor
= LoadCursorW (0, (LPWSTR
)IDC_ARROW
);
3293 wndClass
.hbrBackground
= (HBRUSH
)(COLOR_BTNFACE
+1);
3294 wndClass
.lpszClassName
= WC_TABCONTROLW
;
3296 RegisterClassW (&wndClass
);
3301 TAB_Unregister (void)
3303 UnregisterClassW (WC_TABCONTROLW
, NULL
);