4 * Copyright 1998 Anders Carlsson
5 * Copyright 1999 Alex Priem <alexp@sci.kun.nl>
6 * Copyright 1999 Francis Beaudet
7 * Copyright 2003 Vitaliy Margolen
9 * This library is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Lesser General Public
11 * License as published by the Free Software Foundation; either
12 * version 2.1 of the License, or (at your option) any later version.
14 * This library is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Lesser General Public License for more details.
19 * You should have received a copy of the GNU Lesser General Public
20 * License along with this library; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
25 * This code was audited for completeness against the documented features
26 * of Comctl32.dll version 6.0 on May. 20, 2005, by James Hawkins.
28 * Unless otherwise noted, we believe this code to be complete, as per
29 * the specification mentioned above.
30 * If you discover missing features, or bugs, please note them below.
35 * TCS_MULTISELECT - implement for VK_SPACE selection
67 #include "wine/debug.h"
70 WINE_DEFAULT_DEBUG_CHANNEL(tab);
77 RECT rect; /* bounding rectangle of the item relative to the
78 * leftmost item (the leftmost item, 0, would have a
79 * "left" member of 0 in this rectangle)
81 * additionally the top member holds the row number
82 * and bottom is unused and should be 0 */
83 BYTE extra[1]; /* Space for caller supplied info, variable size */
86 /* The size of a tab item depends on how much extra data is requested */
87 #define TAB_ITEM_SIZE(infoPtr) (FIELD_OFFSET(TAB_ITEM, extra[(infoPtr)->cbInfo]))
91 HWND hwnd; /* Tab control window */
92 HWND hwndNotify; /* notification window (parent) */
93 UINT uNumItem; /* number of tab items */
94 UINT uNumRows; /* number of tab rows */
95 INT tabHeight; /* height of the tab row */
96 INT tabWidth; /* width of tabs */
97 INT tabMinWidth; /* minimum width of items */
98 USHORT uHItemPadding; /* amount of horizontal padding, in pixels */
99 USHORT uVItemPadding; /* amount of vertical padding, in pixels */
100 USHORT uHItemPadding_s; /* Set amount of horizontal padding, in pixels */
101 USHORT uVItemPadding_s; /* Set amount of vertical padding, in pixels */
102 HFONT hFont; /* handle to the current font */
103 HCURSOR hcurArrow; /* handle to the current cursor */
104 HIMAGELIST himl; /* handle to an image list (may be 0) */
105 HWND hwndToolTip; /* handle to tab's tooltip */
106 INT leftmostVisible; /* Used for scrolling, this member contains
107 * the index of the first visible item */
108 INT iSelected; /* the currently selected item */
109 INT iHotTracked; /* the highlighted item under the mouse */
110 INT uFocus; /* item which has the focus */
111 TAB_ITEM* items; /* pointer to an array of TAB_ITEM's */
112 BOOL DoRedraw; /* flag for redrawing when tab contents is changed*/
113 BOOL needsScrolling; /* TRUE if the size of the tabs is greater than
114 * the size of the control */
115 BOOL fHeightSet; /* was the height of the tabs explicitly set? */
116 BOOL bUnicode; /* Unicode control? */
117 HWND hwndUpDown; /* Updown control used for scrolling */
118 INT cbInfo; /* Number of bytes of caller supplied info per tab */
120 DWORD exStyle; /* Extended style used, currently:
121 TCS_EX_FLATSEPARATORS, TCS_EX_REGISTERDROP */
122 DWORD dwStyle; /* the cached window GWL_STYLE */
125 /******************************************************************************
126 * Positioning constants
128 #define SELECTED_TAB_OFFSET 2
129 #define ROUND_CORNER_SIZE 2
130 #define DISPLAY_AREA_PADDINGX 2
131 #define DISPLAY_AREA_PADDINGY 2
132 #define CONTROL_BORDER_SIZEX 2
133 #define CONTROL_BORDER_SIZEY 2
134 #define BUTTON_SPACINGX 3
135 #define BUTTON_SPACINGY 3
136 #define FLAT_BTN_SPACINGX 8
137 #define DEFAULT_MIN_TAB_WIDTH 54
138 #define DEFAULT_PADDING_X 6
139 #define EXTRA_ICON_PADDING 3
141 #define TAB_GetInfoPtr(hwnd) ((TAB_INFO *)GetWindowLongPtrW(hwnd,0))
142 /* Since items are variable sized, cannot directly access them */
143 #define TAB_GetItem(info,i) \
144 ((TAB_ITEM*)((LPBYTE)info->items + (i) * TAB_ITEM_SIZE(info)))
146 #define GET_DEFAULT_MIN_TAB_WIDTH(infoPtr) (DEFAULT_MIN_TAB_WIDTH - (DEFAULT_PADDING_X - (infoPtr)->uHItemPadding) * 2)
148 /******************************************************************************
149 * Hot-tracking timer constants
151 #define TAB_HOTTRACK_TIMER 1
152 #define TAB_HOTTRACK_TIMER_INTERVAL 100 /* milliseconds */
154 static const WCHAR themeClass[] = { 'T','a','b',0 };
156 /******************************************************************************
159 static void TAB_InvalidateTabArea(const TAB_INFO *);
160 static void TAB_EnsureSelectionVisible(TAB_INFO *);
161 static void TAB_DrawItemInterior(const TAB_INFO *, HDC, INT, RECT*);
162 static LRESULT TAB_DeselectAll(TAB_INFO *, BOOL);
163 static BOOL TAB_InternalGetItemRect(const TAB_INFO *, INT, RECT*, RECT*);
166 TAB_SendSimpleNotify (const TAB_INFO *infoPtr, UINT code)
170 nmhdr.hwndFrom = infoPtr->hwnd;
171 nmhdr.idFrom = GetWindowLongPtrW(infoPtr->hwnd, GWLP_ID);
174 return (BOOL) SendMessageW (infoPtr->hwndNotify, WM_NOTIFY,
175 nmhdr.idFrom, (LPARAM) &nmhdr);
179 TAB_RelayEvent (HWND hwndTip, HWND hwndMsg, UINT uMsg,
180 WPARAM wParam, LPARAM lParam)
188 msg.time = GetMessageTime ();
189 msg.pt.x = (short)LOWORD(GetMessagePos ());
190 msg.pt.y = (short)HIWORD(GetMessagePos ());
192 SendMessageW (hwndTip, TTM_RELAYEVENT, 0, (LPARAM)&msg);
196 TAB_DumpItemExternalT(const TCITEMW *pti, UINT iItem, BOOL isW)
199 TRACE("external tab %d, mask=0x%08x, dwState=0x%08x, dwStateMask=0x%08x, cchTextMax=0x%08x\n",
200 iItem, pti->mask, pti->dwState, pti->dwStateMask, pti->cchTextMax);
201 TRACE("external tab %d, iImage=%d, lParam=0x%08lx, pszTextW=%s\n",
202 iItem, pti->iImage, pti->lParam, isW ? debugstr_w(pti->pszText) : debugstr_a((LPSTR)pti->pszText));
207 TAB_DumpItemInternal(const TAB_INFO *infoPtr, UINT iItem)
212 ti = TAB_GetItem(infoPtr, iItem);
213 TRACE("tab %d, dwState=0x%08x, pszText=%s, iImage=%d\n",
214 iItem, ti->dwState, debugstr_w(ti->pszText), ti->iImage);
215 TRACE("tab %d, rect.left=%d, rect.top(row)=%d\n",
216 iItem, ti->rect.left, ti->rect.top);
221 * the index of the selected tab, or -1 if no tab is selected. */
222 static inline LRESULT TAB_GetCurSel (const TAB_INFO *infoPtr)
224 TRACE("(%p)\n", infoPtr);
225 return infoPtr->iSelected;
229 * the index of the tab item that has the focus. */
230 static inline LRESULT
231 TAB_GetCurFocus (const TAB_INFO *infoPtr)
233 TRACE("(%p)\n", infoPtr);
234 return infoPtr->uFocus;
237 static inline LRESULT TAB_GetToolTips (const TAB_INFO *infoPtr)
239 TRACE("(%p)\n", infoPtr);
240 return (LRESULT)infoPtr->hwndToolTip;
243 static inline LRESULT TAB_SetCurSel (TAB_INFO *infoPtr, INT iItem)
245 INT prevItem = infoPtr->iSelected;
247 TRACE("(%p %d)\n", infoPtr, iItem);
250 infoPtr->iSelected = -1;
251 else if (iItem >= infoPtr->uNumItem)
254 if (prevItem != iItem) {
256 TAB_GetItem(infoPtr, prevItem)->dwState &= ~TCIS_BUTTONPRESSED;
257 TAB_GetItem(infoPtr, iItem)->dwState |= TCIS_BUTTONPRESSED;
259 infoPtr->iSelected = iItem;
260 infoPtr->uFocus = iItem;
261 TAB_EnsureSelectionVisible(infoPtr);
262 TAB_InvalidateTabArea(infoPtr);
268 static LRESULT TAB_SetCurFocus (TAB_INFO *infoPtr, INT iItem)
270 TRACE("(%p %d)\n", infoPtr, iItem);
273 infoPtr->uFocus = -1;
274 if (infoPtr->iSelected != -1) {
275 infoPtr->iSelected = -1;
276 TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGE);
277 TAB_InvalidateTabArea(infoPtr);
280 else if (iItem < infoPtr->uNumItem) {
281 if (infoPtr->dwStyle & TCS_BUTTONS) {
282 /* set focus to new item, leave selection as is */
283 if (infoPtr->uFocus != iItem) {
284 INT prev_focus = infoPtr->uFocus;
287 infoPtr->uFocus = iItem;
289 if (prev_focus != infoPtr->iSelected) {
290 if (TAB_InternalGetItemRect(infoPtr, prev_focus, &r, NULL))
291 InvalidateRect(infoPtr->hwnd, &r, FALSE);
294 if (TAB_InternalGetItemRect(infoPtr, iItem, &r, NULL))
295 InvalidateRect(infoPtr->hwnd, &r, FALSE);
297 TAB_SendSimpleNotify(infoPtr, TCN_FOCUSCHANGE);
300 INT oldFocus = infoPtr->uFocus;
301 if (infoPtr->iSelected != iItem || oldFocus == -1 ) {
302 infoPtr->uFocus = iItem;
303 if (oldFocus != -1) {
304 if (!TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGING)) {
305 infoPtr->iSelected = iItem;
306 TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGE);
309 infoPtr->iSelected = iItem;
310 TAB_EnsureSelectionVisible(infoPtr);
311 TAB_InvalidateTabArea(infoPtr);
319 static inline LRESULT
320 TAB_SetToolTips (TAB_INFO *infoPtr, HWND hwndToolTip)
322 TRACE("%p %p\n", infoPtr, hwndToolTip);
323 infoPtr->hwndToolTip = hwndToolTip;
327 static inline LRESULT
328 TAB_SetPadding (TAB_INFO *infoPtr, LPARAM lParam)
330 TRACE("(%p %d %d)\n", infoPtr, LOWORD(lParam), HIWORD(lParam));
331 infoPtr->uHItemPadding_s = LOWORD(lParam);
332 infoPtr->uVItemPadding_s = HIWORD(lParam);
337 /******************************************************************************
338 * TAB_InternalGetItemRect
340 * This method will calculate the rectangle representing a given tab item in
341 * client coordinates. This method takes scrolling into account.
343 * This method returns TRUE if the item is visible in the window and FALSE
344 * if it is completely outside the client area.
346 static BOOL TAB_InternalGetItemRect(
347 const TAB_INFO* infoPtr,
352 RECT tmpItemRect,clientRect;
354 /* Perform a sanity check and a trivial visibility check. */
355 if ( (infoPtr->uNumItem <= 0) ||
356 (itemIndex >= infoPtr->uNumItem) ||
357 (!(((infoPtr->dwStyle & TCS_MULTILINE) || (infoPtr->dwStyle & TCS_VERTICAL))) &&
358 (itemIndex < infoPtr->leftmostVisible)))
360 TRACE("Not Visible\n");
361 /* need to initialize these to empty rects */
364 memset(itemRect,0,sizeof(RECT));
365 itemRect->bottom = infoPtr->tabHeight;
368 memset(selectedRect,0,sizeof(RECT));
373 * Avoid special cases in this procedure by assigning the "out"
374 * parameters if the caller didn't supply them
376 if (itemRect == NULL)
377 itemRect = &tmpItemRect;
379 /* Retrieve the unmodified item rect. */
380 *itemRect = TAB_GetItem(infoPtr,itemIndex)->rect;
382 /* calculate the times bottom and top based on the row */
383 GetClientRect(infoPtr->hwnd, &clientRect);
385 if ((infoPtr->dwStyle & TCS_BOTTOM) && (infoPtr->dwStyle & TCS_VERTICAL))
387 itemRect->right = clientRect.right - SELECTED_TAB_OFFSET - itemRect->left * infoPtr->tabHeight -
388 ((infoPtr->dwStyle & TCS_BUTTONS) ? itemRect->left * BUTTON_SPACINGX : 0);
389 itemRect->left = itemRect->right - infoPtr->tabHeight;
391 else if (infoPtr->dwStyle & TCS_VERTICAL)
393 itemRect->left = clientRect.left + SELECTED_TAB_OFFSET + itemRect->left * infoPtr->tabHeight +
394 ((infoPtr->dwStyle & TCS_BUTTONS) ? itemRect->left * BUTTON_SPACINGX : 0);
395 itemRect->right = itemRect->left + infoPtr->tabHeight;
397 else if (infoPtr->dwStyle & TCS_BOTTOM)
399 itemRect->bottom = clientRect.bottom - itemRect->top * infoPtr->tabHeight -
400 ((infoPtr->dwStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : SELECTED_TAB_OFFSET);
401 itemRect->top = itemRect->bottom - infoPtr->tabHeight;
403 else /* not TCS_BOTTOM and not TCS_VERTICAL */
405 itemRect->top = clientRect.top + itemRect->top * infoPtr->tabHeight +
406 ((infoPtr->dwStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : SELECTED_TAB_OFFSET);
407 itemRect->bottom = itemRect->top + infoPtr->tabHeight;
411 * "scroll" it to make sure the item at the very left of the
412 * tab control is the leftmost visible tab.
414 if(infoPtr->dwStyle & TCS_VERTICAL)
418 -TAB_GetItem(infoPtr, infoPtr->leftmostVisible)->rect.top);
421 * Move the rectangle so the first item is slightly offset from
422 * the bottom of the tab control.
426 SELECTED_TAB_OFFSET);
431 -TAB_GetItem(infoPtr, infoPtr->leftmostVisible)->rect.left,
435 * Move the rectangle so the first item is slightly offset from
436 * the left of the tab control.
442 TRACE("item %d tab h=%d, rect=(%s)\n",
443 itemIndex, infoPtr->tabHeight, wine_dbgstr_rect(itemRect));
445 /* Now, calculate the position of the item as if it were selected. */
446 if (selectedRect!=NULL)
448 CopyRect(selectedRect, itemRect);
450 /* The rectangle of a selected item is a bit wider. */
451 if(infoPtr->dwStyle & TCS_VERTICAL)
452 InflateRect(selectedRect, 0, SELECTED_TAB_OFFSET);
454 InflateRect(selectedRect, SELECTED_TAB_OFFSET, 0);
456 /* If it also a bit higher. */
457 if ((infoPtr->dwStyle & TCS_BOTTOM) && (infoPtr->dwStyle & TCS_VERTICAL))
459 selectedRect->left -= 2; /* the border is thicker on the right */
460 selectedRect->right += SELECTED_TAB_OFFSET;
462 else if (infoPtr->dwStyle & TCS_VERTICAL)
464 selectedRect->left -= SELECTED_TAB_OFFSET;
465 selectedRect->right += 1;
467 else if (infoPtr->dwStyle & TCS_BOTTOM)
469 selectedRect->bottom += SELECTED_TAB_OFFSET;
471 else /* not TCS_BOTTOM and not TCS_VERTICAL */
473 selectedRect->top -= SELECTED_TAB_OFFSET;
474 selectedRect->bottom -= 1;
478 /* Check for visibility */
479 if (infoPtr->dwStyle & TCS_VERTICAL)
480 return (itemRect->top < clientRect.bottom) && (itemRect->bottom > clientRect.top);
482 return (itemRect->left < clientRect.right) && (itemRect->right > clientRect.left);
486 TAB_GetItemRect(const TAB_INFO *infoPtr, INT item, RECT *rect)
488 TRACE("(%p, %d, %p)\n", infoPtr, item, rect);
489 return TAB_InternalGetItemRect(infoPtr, item, rect, NULL);
492 /******************************************************************************
495 * This method is called to handle keyboard input
497 static LRESULT TAB_KeyDown(TAB_INFO* infoPtr, WPARAM keyCode, LPARAM lParam)
502 /* TCN_KEYDOWN notification sent always */
503 nm.hdr.hwndFrom = infoPtr->hwnd;
504 nm.hdr.idFrom = GetWindowLongPtrW(infoPtr->hwnd, GWLP_ID);
505 nm.hdr.code = TCN_KEYDOWN;
508 SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, nm.hdr.idFrom, (LPARAM)&nm);
513 newItem = infoPtr->uFocus - 1;
516 newItem = infoPtr->uFocus + 1;
520 /* If we changed to a valid item, change focused item */
521 if (newItem >= 0 && newItem < infoPtr->uNumItem && infoPtr->uFocus != newItem)
522 TAB_SetCurFocus(infoPtr, newItem);
528 * WM_KILLFOCUS handler
530 static void TAB_KillFocus(TAB_INFO *infoPtr)
532 /* clear current focused item back to selected for TCS_BUTTONS */
533 if ((infoPtr->dwStyle & TCS_BUTTONS) && (infoPtr->uFocus != infoPtr->iSelected))
537 if (TAB_InternalGetItemRect(infoPtr, infoPtr->uFocus, &r, NULL))
538 InvalidateRect(infoPtr->hwnd, &r, FALSE);
540 infoPtr->uFocus = infoPtr->iSelected;
544 /******************************************************************************
547 * This method is called whenever the focus goes in or out of this control
548 * it is used to update the visual state of the control.
550 static void TAB_FocusChanging(const TAB_INFO *infoPtr)
556 * Get the rectangle for the item.
558 isVisible = TAB_InternalGetItemRect(infoPtr,
564 * If the rectangle is not completely invisible, invalidate that
565 * portion of the window.
569 TRACE("invalidate (%s)\n", wine_dbgstr_rect(&selectedRect));
570 InvalidateRect(infoPtr->hwnd, &selectedRect, TRUE);
574 static INT TAB_InternalHitTest (const TAB_INFO *infoPtr, POINT pt, UINT *flags)
579 for (iCount = 0; iCount < infoPtr->uNumItem; iCount++)
581 TAB_InternalGetItemRect(infoPtr, iCount, &rect, NULL);
583 if (PtInRect(&rect, pt))
585 *flags = TCHT_ONITEM;
590 *flags = TCHT_NOWHERE;
594 static inline LRESULT
595 TAB_HitTest (const TAB_INFO *infoPtr, LPTCHITTESTINFO lptest)
597 TRACE("(%p, %p)\n", infoPtr, lptest);
598 return TAB_InternalHitTest (infoPtr, lptest->pt, &lptest->flags);
601 /******************************************************************************
604 * Napster v2b5 has a tab control for its main navigation which has a client
605 * area that covers the whole area of the dialog pages.
606 * That's why it receives all msgs for that area and the underlying dialog ctrls
608 * So I decided that we should handle WM_NCHITTEST here and return
609 * HTTRANSPARENT if we don't hit the tab control buttons.
610 * FIXME: WM_NCHITTEST handling correct ? Fix it if you know that Windows
611 * doesn't do it that way. Maybe depends on tab control styles ?
613 static inline LRESULT
614 TAB_NCHitTest (const TAB_INFO *infoPtr, LPARAM lParam)
619 pt.x = (short)LOWORD(lParam);
620 pt.y = (short)HIWORD(lParam);
621 ScreenToClient(infoPtr->hwnd, &pt);
623 if (TAB_InternalHitTest(infoPtr, pt, &dummyflag) == -1)
624 return HTTRANSPARENT;
630 TAB_LButtonDown (TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
636 if (infoPtr->hwndToolTip)
637 TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
638 WM_LBUTTONDOWN, wParam, lParam);
640 if (!(infoPtr->dwStyle & TCS_FOCUSNEVER)) {
641 SetFocus (infoPtr->hwnd);
644 if (infoPtr->hwndToolTip)
645 TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
646 WM_LBUTTONDOWN, wParam, lParam);
648 pt.x = (short)LOWORD(lParam);
649 pt.y = (short)HIWORD(lParam);
651 newItem = TAB_InternalHitTest (infoPtr, pt, &dummy);
653 TRACE("On Tab, item %d\n", newItem);
655 if ((newItem != -1) && (infoPtr->iSelected != newItem))
657 if ((infoPtr->dwStyle & TCS_BUTTONS) && (infoPtr->dwStyle & TCS_MULTISELECT) &&
658 (wParam & MK_CONTROL))
662 /* toggle multiselection */
663 TAB_GetItem(infoPtr, newItem)->dwState ^= TCIS_BUTTONPRESSED;
664 if (TAB_InternalGetItemRect (infoPtr, newItem, &r, NULL))
665 InvalidateRect (infoPtr->hwnd, &r, TRUE);
670 BOOL pressed = FALSE;
672 /* any button pressed ? */
673 for (i = 0; i < infoPtr->uNumItem; i++)
674 if ((TAB_GetItem (infoPtr, i)->dwState & TCIS_BUTTONPRESSED) &&
675 (infoPtr->iSelected != i))
681 TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGING);
684 TAB_DeselectAll (infoPtr, FALSE);
686 TAB_SetCurSel(infoPtr, newItem);
688 TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGE);
695 static inline LRESULT
696 TAB_LButtonUp (const TAB_INFO *infoPtr)
698 TAB_SendSimpleNotify(infoPtr, NM_CLICK);
703 static inline LRESULT
704 TAB_RButtonDown (const TAB_INFO *infoPtr)
706 TAB_SendSimpleNotify(infoPtr, NM_RCLICK);
710 /******************************************************************************
711 * TAB_DrawLoneItemInterior
713 * This calls TAB_DrawItemInterior. However, TAB_DrawItemInterior is normally
714 * called by TAB_DrawItem which is normally called by TAB_Refresh which sets
715 * up the device context and font. This routine does the same setup but
716 * only calls TAB_DrawItemInterior for the single specified item.
719 TAB_DrawLoneItemInterior(const TAB_INFO* infoPtr, int iItem)
721 HDC hdc = GetDC(infoPtr->hwnd);
724 /* Clip UpDown control to not draw over it */
725 if (infoPtr->needsScrolling)
727 GetWindowRect(infoPtr->hwnd, &rC);
728 GetWindowRect(infoPtr->hwndUpDown, &r);
729 ExcludeClipRect(hdc, r.left - rC.left, r.top - rC.top, r.right - rC.left, r.bottom - rC.top);
731 TAB_DrawItemInterior(infoPtr, hdc, iItem, NULL);
732 ReleaseDC(infoPtr->hwnd, hdc);
735 /* update a tab after hottracking - invalidate it or just redraw the interior,
736 * based on whether theming is used or not */
737 static inline void hottrack_refresh(const TAB_INFO *infoPtr, int tabIndex)
739 if (tabIndex == -1) return;
741 if (GetWindowTheme (infoPtr->hwnd))
744 TAB_InternalGetItemRect(infoPtr, tabIndex, &rect, NULL);
745 InvalidateRect (infoPtr->hwnd, &rect, FALSE);
748 TAB_DrawLoneItemInterior(infoPtr, tabIndex);
751 /******************************************************************************
752 * TAB_HotTrackTimerProc
754 * When a mouse-move event causes a tab to be highlighted (hot-tracking), a
755 * timer is setup so we can check if the mouse is moved out of our window.
756 * (We don't get an event when the mouse leaves, the mouse-move events just
757 * stop being delivered to our window and just start being delivered to
758 * another window.) This function is called when the timer triggers so
759 * we can check if the mouse has left our window. If so, we un-highlight
760 * the hot-tracked tab.
763 TAB_HotTrackTimerProc
765 HWND hwnd, /* handle of window for timer messages */
766 UINT uMsg, /* WM_TIMER message */
767 UINT_PTR idEvent, /* timer identifier */
768 DWORD dwTime /* current system time */
771 TAB_INFO* infoPtr = TAB_GetInfoPtr(hwnd);
773 if (infoPtr != NULL && infoPtr->iHotTracked >= 0)
778 ** If we can't get the cursor position, or if the cursor is outside our
779 ** window, we un-highlight the hot-tracked tab. Note that the cursor is
780 ** "outside" even if it is within our bounding rect if another window
781 ** overlaps. Note also that the case where the cursor stayed within our
782 ** window but has moved off the hot-tracked tab will be handled by the
783 ** WM_MOUSEMOVE event.
785 if (!GetCursorPos(&pt) || WindowFromPoint(pt) != hwnd)
787 /* Redraw iHotTracked to look normal */
788 INT iRedraw = infoPtr->iHotTracked;
789 infoPtr->iHotTracked = -1;
790 hottrack_refresh (infoPtr, iRedraw);
792 /* Kill this timer */
793 KillTimer(hwnd, TAB_HOTTRACK_TIMER);
798 /******************************************************************************
801 * If a tab control has the TCS_HOTTRACK style, then the tab under the mouse
802 * should be highlighted. This function determines which tab in a tab control,
803 * if any, is under the mouse and records that information. The caller may
804 * supply output parameters to receive the item number of the tab item which
805 * was highlighted but isn't any longer and of the tab item which is now
806 * highlighted but wasn't previously. The caller can use this information to
807 * selectively redraw those tab items.
809 * If the caller has a mouse position, it can supply it through the pos
810 * parameter. For example, TAB_MouseMove does this. Otherwise, the caller
811 * supplies NULL and this function determines the current mouse position
819 int* out_redrawLeave,
826 if (out_redrawLeave != NULL)
827 *out_redrawLeave = -1;
828 if (out_redrawEnter != NULL)
829 *out_redrawEnter = -1;
831 if ((infoPtr->dwStyle & TCS_HOTTRACK) || GetWindowTheme(infoPtr->hwnd))
839 ScreenToClient(infoPtr->hwnd, &pt);
843 pt.x = (short)LOWORD(*pos);
844 pt.y = (short)HIWORD(*pos);
847 item = TAB_InternalHitTest(infoPtr, pt, &flags);
850 if (item != infoPtr->iHotTracked)
852 if (infoPtr->iHotTracked >= 0)
854 /* Mark currently hot-tracked to be redrawn to look normal */
855 if (out_redrawLeave != NULL)
856 *out_redrawLeave = infoPtr->iHotTracked;
860 /* Kill timer which forces recheck of mouse pos */
861 KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
866 /* Start timer so we recheck mouse pos */
867 UINT timerID = SetTimer
871 TAB_HOTTRACK_TIMER_INTERVAL,
872 TAB_HotTrackTimerProc
876 return; /* Hot tracking not available */
879 infoPtr->iHotTracked = item;
883 /* Mark new hot-tracked to be redrawn to look highlighted */
884 if (out_redrawEnter != NULL)
885 *out_redrawEnter = item;
890 /******************************************************************************
893 * Handles the mouse-move event. Updates tooltips. Updates hot-tracking.
896 TAB_MouseMove (TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
901 if (infoPtr->hwndToolTip)
902 TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
903 WM_LBUTTONDOWN, wParam, lParam);
905 /* Determine which tab to highlight. Redraw tabs which change highlight
907 TAB_RecalcHotTrack(infoPtr, &lParam, &redrawLeave, &redrawEnter);
909 hottrack_refresh (infoPtr, redrawLeave);
910 hottrack_refresh (infoPtr, redrawEnter);
915 /******************************************************************************
918 * Calculates the tab control's display area given the window rectangle or
919 * the window rectangle given the requested display rectangle.
921 static LRESULT TAB_AdjustRect(const TAB_INFO *infoPtr, WPARAM fLarger, LPRECT prc)
923 LONG *iRightBottom, *iLeftTop;
925 TRACE ("hwnd=%p fLarger=%ld (%s)\n", infoPtr->hwnd, fLarger,
926 wine_dbgstr_rect(prc));
930 if(infoPtr->dwStyle & TCS_VERTICAL)
932 iRightBottom = &(prc->right);
933 iLeftTop = &(prc->left);
937 iRightBottom = &(prc->bottom);
938 iLeftTop = &(prc->top);
941 if (fLarger) /* Go from display rectangle */
943 /* Add the height of the tabs. */
944 if (infoPtr->dwStyle & TCS_BOTTOM)
945 *iRightBottom += infoPtr->tabHeight * infoPtr->uNumRows;
947 *iLeftTop -= infoPtr->tabHeight * infoPtr->uNumRows +
948 ((infoPtr->dwStyle & TCS_BUTTONS)? 3 * (infoPtr->uNumRows - 1) : 0);
950 /* Inflate the rectangle for the padding */
951 InflateRect(prc, DISPLAY_AREA_PADDINGX, DISPLAY_AREA_PADDINGY);
953 /* Inflate for the border */
954 InflateRect(prc, CONTROL_BORDER_SIZEX, CONTROL_BORDER_SIZEY);
956 else /* Go from window rectangle. */
958 /* Deflate the rectangle for the border */
959 InflateRect(prc, -CONTROL_BORDER_SIZEX, -CONTROL_BORDER_SIZEY);
961 /* Deflate the rectangle for the padding */
962 InflateRect(prc, -DISPLAY_AREA_PADDINGX, -DISPLAY_AREA_PADDINGY);
964 /* Remove the height of the tabs. */
965 if (infoPtr->dwStyle & TCS_BOTTOM)
966 *iRightBottom -= infoPtr->tabHeight * infoPtr->uNumRows;
968 *iLeftTop += (infoPtr->tabHeight) * infoPtr->uNumRows +
969 ((infoPtr->dwStyle & TCS_BUTTONS)? 3 * (infoPtr->uNumRows - 1) : 0);
975 /******************************************************************************
978 * This method will handle the notification from the scroll control and
979 * perform the scrolling operation on the tab control.
981 static LRESULT TAB_OnHScroll(TAB_INFO *infoPtr, int nScrollCode, int nPos)
983 if(nScrollCode == SB_THUMBPOSITION && nPos != infoPtr->leftmostVisible)
985 if(nPos < infoPtr->leftmostVisible)
986 infoPtr->leftmostVisible--;
988 infoPtr->leftmostVisible++;
990 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
991 TAB_InvalidateTabArea(infoPtr);
992 SendMessageW(infoPtr->hwndUpDown, UDM_SETPOS, 0,
993 MAKELONG(infoPtr->leftmostVisible, 0));
999 /******************************************************************************
1000 * TAB_SetupScrolling
1002 * This method will check the current scrolling state and make sure the
1003 * scrolling control is displayed (or not).
1005 static void TAB_SetupScrolling(
1007 const RECT* clientRect)
1009 static const WCHAR emptyW[] = { 0 };
1012 if (infoPtr->needsScrolling)
1015 INT vsize, tabwidth;
1018 * Calculate the position of the scroll control.
1020 if(infoPtr->dwStyle & TCS_VERTICAL)
1022 controlPos.right = clientRect->right;
1023 controlPos.left = controlPos.right - 2 * GetSystemMetrics(SM_CXHSCROLL);
1025 if (infoPtr->dwStyle & TCS_BOTTOM)
1027 controlPos.top = clientRect->bottom - infoPtr->tabHeight;
1028 controlPos.bottom = controlPos.top + GetSystemMetrics(SM_CYHSCROLL);
1032 controlPos.bottom = clientRect->top + infoPtr->tabHeight;
1033 controlPos.top = controlPos.bottom - GetSystemMetrics(SM_CYHSCROLL);
1038 controlPos.right = clientRect->right;
1039 controlPos.left = controlPos.right - 2 * GetSystemMetrics(SM_CXHSCROLL);
1041 if (infoPtr->dwStyle & TCS_BOTTOM)
1043 controlPos.top = clientRect->bottom - infoPtr->tabHeight;
1044 controlPos.bottom = controlPos.top + GetSystemMetrics(SM_CYHSCROLL);
1048 controlPos.bottom = clientRect->top + infoPtr->tabHeight;
1049 controlPos.top = controlPos.bottom - GetSystemMetrics(SM_CYHSCROLL);
1054 * If we don't have a scroll control yet, we want to create one.
1055 * If we have one, we want to make sure it's positioned properly.
1057 if (infoPtr->hwndUpDown==0)
1059 infoPtr->hwndUpDown = CreateWindowW(UPDOWN_CLASSW, emptyW,
1060 WS_VISIBLE | WS_CHILD | UDS_HORZ,
1061 controlPos.left, controlPos.top,
1062 controlPos.right - controlPos.left,
1063 controlPos.bottom - controlPos.top,
1064 infoPtr->hwnd, NULL, NULL, NULL);
1068 SetWindowPos(infoPtr->hwndUpDown,
1070 controlPos.left, controlPos.top,
1071 controlPos.right - controlPos.left,
1072 controlPos.bottom - controlPos.top,
1073 SWP_SHOWWINDOW | SWP_NOZORDER);
1076 /* Now calculate upper limit of the updown control range.
1077 * We do this by calculating how many tabs will be offscreen when the
1078 * last tab is visible.
1080 if(infoPtr->uNumItem)
1082 vsize = clientRect->right - (controlPos.right - controlPos.left + 1);
1083 maxRange = infoPtr->uNumItem;
1084 tabwidth = TAB_GetItem(infoPtr, infoPtr->uNumItem - 1)->rect.right;
1086 for(; maxRange > 0; maxRange--)
1088 if(tabwidth - TAB_GetItem(infoPtr,maxRange - 1)->rect.left > vsize)
1092 if(maxRange == infoPtr->uNumItem)
1098 /* If we once had a scroll control... hide it */
1099 if (infoPtr->hwndUpDown)
1100 ShowWindow(infoPtr->hwndUpDown, SW_HIDE);
1102 if (infoPtr->hwndUpDown)
1103 SendMessageW(infoPtr->hwndUpDown, UDM_SETRANGE32, 0, maxRange);
1106 /******************************************************************************
1109 * This method will calculate the position rectangles of all the items in the
1110 * control. The rectangle calculated starts at 0 for the first item in the
1111 * list and ignores scrolling and selection.
1112 * It also uses the current font to determine the height of the tab row and
1113 * it checks if all the tabs fit in the client area of the window. If they
1114 * don't, a scrolling control is added.
1116 static void TAB_SetItemBounds (TAB_INFO *infoPtr)
1118 TEXTMETRICW fontMetrics;
1121 INT curItemRowCount;
1122 HFONT hFont, hOldFont;
1131 * We need to get text information so we need a DC and we need to select
1134 hdc = GetDC(infoPtr->hwnd);
1136 hFont = infoPtr->hFont ? infoPtr->hFont : GetStockObject (SYSTEM_FONT);
1137 hOldFont = SelectObject (hdc, hFont);
1140 * We will base the rectangle calculations on the client rectangle
1143 GetClientRect(infoPtr->hwnd, &clientRect);
1145 /* if TCS_VERTICAL then swap the height and width so this code places the
1146 tabs along the top of the rectangle and we can just rotate them after
1147 rather than duplicate all of the below code */
1148 if(infoPtr->dwStyle & TCS_VERTICAL)
1150 iTemp = clientRect.bottom;
1151 clientRect.bottom = clientRect.right;
1152 clientRect.right = iTemp;
1155 /* Now use hPadding and vPadding */
1156 infoPtr->uHItemPadding = infoPtr->uHItemPadding_s;
1157 infoPtr->uVItemPadding = infoPtr->uVItemPadding_s;
1159 /* The leftmost item will be "0" aligned */
1161 curItemRowCount = infoPtr->uNumItem ? 1 : 0;
1163 if (!(infoPtr->fHeightSet))
1166 int icon_height = 0;
1168 /* Use the current font to determine the height of a tab. */
1169 GetTextMetricsW(hdc, &fontMetrics);
1171 /* Get the icon height */
1173 ImageList_GetIconSize(infoPtr->himl, 0, &icon_height);
1175 /* Take the highest between font or icon */
1176 if (fontMetrics.tmHeight > icon_height)
1177 item_height = fontMetrics.tmHeight + 2;
1179 item_height = icon_height;
1182 * Make sure there is enough space for the letters + icon + growing the
1183 * selected item + extra space for the selected item.
1185 infoPtr->tabHeight = item_height +
1186 ((infoPtr->dwStyle & TCS_BUTTONS) ? 2 : 1) *
1187 infoPtr->uVItemPadding;
1189 TRACE("tabH=%d, tmH=%d, iconh=%d\n",
1190 infoPtr->tabHeight, fontMetrics.tmHeight, icon_height);
1193 TRACE("client right=%d\n", clientRect.right);
1195 /* Get the icon width */
1198 ImageList_GetIconSize(infoPtr->himl, &icon_width, 0);
1200 if (infoPtr->dwStyle & TCS_FIXEDWIDTH)
1203 /* Add padding if icon is present */
1204 icon_width += infoPtr->uHItemPadding;
1207 for (curItem = 0; curItem < infoPtr->uNumItem; curItem++)
1209 TAB_ITEM *curr = TAB_GetItem(infoPtr, curItem);
1211 /* Set the leftmost position of the tab. */
1212 curr->rect.left = curItemLeftPos;
1214 if (infoPtr->dwStyle & TCS_FIXEDWIDTH)
1216 curr->rect.right = curr->rect.left +
1217 max(infoPtr->tabWidth, icon_width);
1219 else if (!curr->pszText)
1221 /* If no text use minimum tab width including padding. */
1222 if (infoPtr->tabMinWidth < 0)
1223 curr->rect.right = curr->rect.left + GET_DEFAULT_MIN_TAB_WIDTH(infoPtr);
1226 curr->rect.right = curr->rect.left + infoPtr->tabMinWidth;
1228 /* Add extra padding if icon is present */
1229 if (infoPtr->himl && infoPtr->tabMinWidth > 0 && infoPtr->tabMinWidth < DEFAULT_MIN_TAB_WIDTH
1230 && infoPtr->uHItemPadding > 1)
1231 curr->rect.right += EXTRA_ICON_PADDING * (infoPtr->uHItemPadding-1);
1238 /* Calculate how wide the tab is depending on the text it contains */
1239 GetTextExtentPoint32W(hdc, curr->pszText,
1240 lstrlenW(curr->pszText), &size);
1242 tabwidth = size.cx + icon_width + 2 * infoPtr->uHItemPadding;
1244 if (infoPtr->tabMinWidth < 0)
1245 tabwidth = max(tabwidth, GET_DEFAULT_MIN_TAB_WIDTH(infoPtr));
1247 tabwidth = max(tabwidth, infoPtr->tabMinWidth);
1249 curr->rect.right = curr->rect.left + tabwidth;
1250 TRACE("for <%s>, l,r=%d,%d\n",
1251 debugstr_w(curr->pszText), curr->rect.left, curr->rect.right);
1255 * Check if this is a multiline tab control and if so
1256 * check to see if we should wrap the tabs
1258 * Wrap all these tabs. We will arrange them evenly later.
1262 if (((infoPtr->dwStyle & TCS_MULTILINE) || (infoPtr->dwStyle & TCS_VERTICAL)) &&
1264 (clientRect.right - CONTROL_BORDER_SIZEX - DISPLAY_AREA_PADDINGX)))
1266 curr->rect.right -= curr->rect.left;
1268 curr->rect.left = 0;
1270 TRACE("wrapping <%s>, l,r=%d,%d\n", debugstr_w(curr->pszText),
1271 curr->rect.left, curr->rect.right);
1274 curr->rect.bottom = 0;
1275 curr->rect.top = curItemRowCount - 1;
1277 TRACE("Rect: %s\n", wine_dbgstr_rect(&curr->rect));
1280 * The leftmost position of the next item is the rightmost position
1283 if (infoPtr->dwStyle & TCS_BUTTONS)
1285 curItemLeftPos = curr->rect.right + BUTTON_SPACINGX;
1286 if (infoPtr->dwStyle & TCS_FLATBUTTONS)
1287 curItemLeftPos += FLAT_BTN_SPACINGX;
1290 curItemLeftPos = curr->rect.right;
1293 if (!((infoPtr->dwStyle & TCS_MULTILINE) || (infoPtr->dwStyle & TCS_VERTICAL)))
1296 * Check if we need a scrolling control.
1298 infoPtr->needsScrolling = (curItemLeftPos + (2 * SELECTED_TAB_OFFSET) >
1301 /* Don't need scrolling, then update infoPtr->leftmostVisible */
1302 if(!infoPtr->needsScrolling)
1303 infoPtr->leftmostVisible = 0;
1308 * No scrolling in Multiline or Vertical styles.
1310 infoPtr->needsScrolling = FALSE;
1311 infoPtr->leftmostVisible = 0;
1313 TAB_SetupScrolling(infoPtr, &clientRect);
1315 /* Set the number of rows */
1316 infoPtr->uNumRows = curItemRowCount;
1318 /* Arrange all tabs evenly if style says so */
1319 if (!(infoPtr->dwStyle & TCS_RAGGEDRIGHT) &&
1320 ((infoPtr->dwStyle & TCS_MULTILINE) || (infoPtr->dwStyle & TCS_VERTICAL)) &&
1321 (infoPtr->uNumItem > 0) &&
1322 (infoPtr->uNumRows > 1))
1324 INT tabPerRow,remTab,iRow;
1329 * Ok windows tries to even out the rows. place the same
1330 * number of tabs in each row. So lets give that a shot
1333 tabPerRow = infoPtr->uNumItem / (infoPtr->uNumRows);
1334 remTab = infoPtr->uNumItem % (infoPtr->uNumRows);
1336 for (iItm=0,iRow=0,iCount=0,curItemLeftPos=0;
1337 iItm<infoPtr->uNumItem;
1340 /* normalize the current rect */
1341 TAB_ITEM *curr = TAB_GetItem(infoPtr, iItm);
1343 /* shift the item to the left side of the clientRect */
1344 curr->rect.right -= curr->rect.left;
1345 curr->rect.left = 0;
1347 TRACE("r=%d, cl=%d, cl.r=%d, iCount=%d, iRow=%d, uNumRows=%d, remTab=%d, tabPerRow=%d\n",
1348 curr->rect.right, curItemLeftPos, clientRect.right,
1349 iCount, iRow, infoPtr->uNumRows, remTab, tabPerRow);
1351 /* if we have reached the maximum number of tabs on this row */
1352 /* move to the next row, reset our current item left position and */
1353 /* the count of items on this row */
1355 if (infoPtr->dwStyle & TCS_VERTICAL) {
1356 /* Vert: Add the remaining tabs in the *last* remainder rows */
1357 if (iCount >= ((iRow>=(INT)infoPtr->uNumRows - remTab)?tabPerRow + 1:tabPerRow)) {
1363 /* Horz: Add the remaining tabs in the *first* remainder rows */
1364 if (iCount >= ((iRow<remTab)?tabPerRow + 1:tabPerRow)) {
1371 /* shift the item to the right to place it as the next item in this row */
1372 curr->rect.left += curItemLeftPos;
1373 curr->rect.right += curItemLeftPos;
1374 curr->rect.top = iRow;
1375 if (infoPtr->dwStyle & TCS_BUTTONS)
1377 curItemLeftPos = curr->rect.right + 1;
1378 if (infoPtr->dwStyle & TCS_FLATBUTTONS)
1379 curItemLeftPos += FLAT_BTN_SPACINGX;
1382 curItemLeftPos = curr->rect.right;
1384 TRACE("arranging <%s>, l,r=%d,%d, row=%d\n",
1385 debugstr_w(curr->pszText), curr->rect.left,
1386 curr->rect.right, curr->rect.top);
1393 INT widthDiff, iIndexStart=0, iIndexEnd=0;
1397 while(iIndexStart < infoPtr->uNumItem)
1399 TAB_ITEM *start = TAB_GetItem(infoPtr, iIndexStart);
1402 * find the index of the row
1404 /* find the first item on the next row */
1405 for (iIndexEnd=iIndexStart;
1406 (iIndexEnd < infoPtr->uNumItem) &&
1407 (TAB_GetItem(infoPtr, iIndexEnd)->rect.top ==
1410 /* intentionally blank */;
1413 * we need to justify these tabs so they fill the whole given
1417 /* find the amount of space remaining on this row */
1418 widthDiff = clientRect.right - (2 * SELECTED_TAB_OFFSET) -
1419 TAB_GetItem(infoPtr, iIndexEnd - 1)->rect.right;
1421 /* iCount is the number of tab items on this row */
1422 iCount = iIndexEnd - iIndexStart;
1426 remainder = widthDiff % iCount;
1427 widthDiff = widthDiff / iCount;
1428 /* add widthDiff/iCount, or extra space/items on row, to each item on this row */
1429 for (iIndex=iIndexStart, iCount=0; iIndex < iIndexEnd; iIndex++, iCount++)
1431 TAB_ITEM *item = TAB_GetItem(infoPtr, iIndex);
1433 item->rect.left += iCount * widthDiff;
1434 item->rect.right += (iCount + 1) * widthDiff;
1436 TRACE("adjusting 1 <%s>, l,r=%d,%d\n",
1437 debugstr_w(item->pszText),
1438 item->rect.left, item->rect.right);
1441 TAB_GetItem(infoPtr, iIndex - 1)->rect.right += remainder;
1443 else /* we have only one item on this row, make it take up the entire row */
1445 start->rect.left = clientRect.left;
1446 start->rect.right = clientRect.right - 4;
1448 TRACE("adjusting 2 <%s>, l,r=%d,%d\n",
1449 debugstr_w(start->pszText),
1450 start->rect.left, start->rect.right);
1455 iIndexStart = iIndexEnd;
1460 /* if TCS_VERTICAL rotate the tabs so they are along the side of the clientRect */
1461 if(infoPtr->dwStyle & TCS_VERTICAL)
1464 for(iIndex = 0; iIndex < infoPtr->uNumItem; iIndex++)
1466 rcItem = &TAB_GetItem(infoPtr, iIndex)->rect;
1468 rcOriginal = *rcItem;
1470 /* this is rotating the items by 90 degrees clockwise around the center of the control */
1471 rcItem->top = (rcOriginal.left - clientRect.left);
1472 rcItem->bottom = rcItem->top + (rcOriginal.right - rcOriginal.left);
1473 rcItem->left = rcOriginal.top;
1474 rcItem->right = rcOriginal.bottom;
1478 TAB_EnsureSelectionVisible(infoPtr);
1479 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
1482 SelectObject (hdc, hOldFont);
1483 ReleaseDC (infoPtr->hwnd, hdc);
1488 TAB_EraseTabInterior(const TAB_INFO *infoPtr, HDC hdc, INT iItem, const RECT *drawRect)
1490 HBRUSH hbr = CreateSolidBrush (comctl32_color.clrBtnFace);
1491 BOOL deleteBrush = TRUE;
1492 RECT rTemp = *drawRect;
1494 if (infoPtr->dwStyle & TCS_BUTTONS)
1496 if (iItem == infoPtr->iSelected)
1498 /* Background color */
1499 if (!(infoPtr->dwStyle & TCS_OWNERDRAWFIXED))
1502 hbr = GetSysColorBrush(COLOR_SCROLLBAR);
1504 SetTextColor(hdc, comctl32_color.clr3dFace);
1505 SetBkColor(hdc, comctl32_color.clr3dHilight);
1507 /* if COLOR_WINDOW happens to be the same as COLOR_3DHILIGHT
1508 * we better use 0x55aa bitmap brush to make scrollbar's background
1509 * look different from the window background.
1511 if (comctl32_color.clr3dHilight == comctl32_color.clrWindow)
1512 hbr = COMCTL32_hPattern55AABrush;
1514 deleteBrush = FALSE;
1516 FillRect(hdc, &rTemp, hbr);
1518 else /* ! selected */
1520 if (infoPtr->dwStyle & TCS_FLATBUTTONS)
1522 InflateRect(&rTemp, 2, 2);
1523 FillRect(hdc, &rTemp, hbr);
1524 if (iItem == infoPtr->iHotTracked ||
1525 (iItem != infoPtr->iSelected && iItem == infoPtr->uFocus))
1526 DrawEdge(hdc, &rTemp, BDR_RAISEDINNER, BF_RECT);
1529 FillRect(hdc, &rTemp, hbr);
1533 else /* !TCS_BUTTONS */
1535 InflateRect(&rTemp, -2, -2);
1536 if (!GetWindowTheme (infoPtr->hwnd))
1537 FillRect(hdc, &rTemp, hbr);
1540 /* highlighting is drawn on top of previous fills */
1541 if (TAB_GetItem(infoPtr, iItem)->dwState & TCIS_HIGHLIGHTED)
1546 deleteBrush = FALSE;
1548 hbr = GetSysColorBrush(COLOR_HIGHLIGHT);
1549 FillRect(hdc, &rTemp, hbr);
1553 if (deleteBrush) DeleteObject(hbr);
1556 /******************************************************************************
1557 * TAB_DrawItemInterior
1559 * This method is used to draw the interior (text and icon) of a single tab
1560 * into the tab control.
1563 TAB_DrawItemInterior(const TAB_INFO *infoPtr, HDC hdc, INT iItem, RECT *drawRect)
1572 /* if (drawRect == NULL) */
1579 * Get the rectangle for the item.
1581 isVisible = TAB_InternalGetItemRect(infoPtr, iItem, &itemRect, &selectedRect);
1586 * Make sure drawRect points to something valid; simplifies code.
1588 drawRect = &localRect;
1591 * This logic copied from the part of TAB_DrawItem which draws
1592 * the tab background. It's important to keep it in sync. I
1593 * would have liked to avoid code duplication, but couldn't figure
1594 * out how without making spaghetti of TAB_DrawItem.
1596 if (iItem == infoPtr->iSelected)
1597 *drawRect = selectedRect;
1599 *drawRect = itemRect;
1601 if (infoPtr->dwStyle & TCS_BUTTONS)
1603 if (iItem == infoPtr->iSelected)
1605 drawRect->left += 4;
1607 drawRect->right -= 4;
1609 if (infoPtr->dwStyle & TCS_VERTICAL)
1611 if (!(infoPtr->dwStyle & TCS_BOTTOM)) drawRect->right += 1;
1612 drawRect->bottom -= 4;
1616 if (infoPtr->dwStyle & TCS_BOTTOM)
1619 drawRect->bottom -= 4;
1622 drawRect->bottom -= 1;
1627 drawRect->left += 2;
1629 drawRect->right -= 2;
1630 drawRect->bottom -= 2;
1635 if ((infoPtr->dwStyle & TCS_VERTICAL) && (infoPtr->dwStyle & TCS_BOTTOM))
1637 if (iItem != infoPtr->iSelected)
1639 drawRect->left += 2;
1641 drawRect->bottom -= 2;
1644 else if (infoPtr->dwStyle & TCS_VERTICAL)
1646 if (iItem == infoPtr->iSelected)
1648 drawRect->right += 1;
1653 drawRect->right -= 2;
1654 drawRect->bottom -= 2;
1657 else if (infoPtr->dwStyle & TCS_BOTTOM)
1659 if (iItem == infoPtr->iSelected)
1665 InflateRect(drawRect, -2, -2);
1666 drawRect->bottom += 2;
1671 if (iItem == infoPtr->iSelected)
1673 drawRect->bottom += 3;
1677 drawRect->bottom -= 2;
1678 InflateRect(drawRect, -2, 0);
1683 TRACE("drawRect=(%s)\n", wine_dbgstr_rect(drawRect));
1685 /* Clear interior */
1686 TAB_EraseTabInterior (infoPtr, hdc, iItem, drawRect);
1688 /* Draw the focus rectangle */
1689 if (!(infoPtr->dwStyle & TCS_FOCUSNEVER) &&
1690 (GetFocus() == infoPtr->hwnd) &&
1691 (iItem == infoPtr->uFocus) )
1693 RECT rFocus = *drawRect;
1695 if (!(infoPtr->dwStyle & TCS_BUTTONS)) InflateRect(&rFocus, -3, -3);
1696 if (infoPtr->dwStyle & TCS_BOTTOM && !(infoPtr->dwStyle & TCS_VERTICAL))
1699 /* focus should stay on selected item for TCS_BUTTONS style */
1700 if (!((infoPtr->dwStyle & TCS_BUTTONS) && (infoPtr->iSelected != iItem)))
1701 DrawFocusRect(hdc, &rFocus);
1707 htextPen = CreatePen( PS_SOLID, 1, comctl32_color.clrBtnText );
1708 holdPen = SelectObject(hdc, htextPen);
1709 hOldFont = SelectObject(hdc, infoPtr->hFont);
1712 * Setup for text output
1714 oldBkMode = SetBkMode(hdc, TRANSPARENT);
1715 if (!GetWindowTheme (infoPtr->hwnd) || (infoPtr->dwStyle & TCS_BUTTONS))
1717 if ((infoPtr->dwStyle & TCS_HOTTRACK) && (iItem == infoPtr->iHotTracked) &&
1718 !(infoPtr->dwStyle & TCS_FLATBUTTONS))
1719 SetTextColor(hdc, comctl32_color.clrHighlight);
1720 else if (TAB_GetItem(infoPtr, iItem)->dwState & TCIS_HIGHLIGHTED)
1721 SetTextColor(hdc, comctl32_color.clrHighlightText);
1723 SetTextColor(hdc, comctl32_color.clrBtnText);
1727 * if owner draw, tell the owner to draw
1729 if ((infoPtr->dwStyle & TCS_OWNERDRAWFIXED) && GetParent(infoPtr->hwnd))
1735 drawRect->right -= 1;
1736 if ( iItem == infoPtr->iSelected )
1738 drawRect->right -= 1;
1739 drawRect->left += 1;
1743 * get the control id
1745 id = (UINT)GetWindowLongPtrW( infoPtr->hwnd, GWLP_ID );
1748 * put together the DRAWITEMSTRUCT
1750 dis.CtlType = ODT_TAB;
1753 dis.itemAction = ODA_DRAWENTIRE;
1755 if ( iItem == infoPtr->iSelected )
1756 dis.itemState |= ODS_SELECTED;
1757 if (infoPtr->uFocus == iItem)
1758 dis.itemState |= ODS_FOCUS;
1759 dis.hwndItem = infoPtr->hwnd;
1761 CopyRect(&dis.rcItem,drawRect);
1762 dis.itemData = (ULONG_PTR)TAB_GetItem(infoPtr, iItem)->extra;
1765 * send the draw message
1767 SendMessageW( infoPtr->hwndNotify, WM_DRAWITEM, id, (LPARAM)&dis );
1771 TAB_ITEM *item = TAB_GetItem(infoPtr, iItem);
1775 /* used to center the icon and text in the tab */
1777 INT center_offset_h, center_offset_v;
1779 /* set rcImage to drawRect, we will use top & left in our ImageList_Draw call */
1780 rcImage = *drawRect;
1784 rcText.left = rcText.top = rcText.right = rcText.bottom = 0;
1786 /* get the rectangle that the text fits in */
1789 DrawTextW(hdc, item->pszText, -1, &rcText, DT_CALCRECT);
1792 * If not owner draw, then do the drawing ourselves.
1796 if (infoPtr->himl && item->iImage != -1)
1801 ImageList_GetIconSize(infoPtr->himl, &cx, &cy);
1803 if(infoPtr->dwStyle & TCS_VERTICAL)
1805 center_offset_h = ((drawRect->bottom - drawRect->top) - (cy + infoPtr->uHItemPadding + (rcText.right - rcText.left))) / 2;
1806 center_offset_v = ((drawRect->right - drawRect->left) - cx) / 2;
1810 center_offset_h = ((drawRect->right - drawRect->left) - (cx + infoPtr->uHItemPadding + (rcText.right - rcText.left))) / 2;
1811 center_offset_v = ((drawRect->bottom - drawRect->top) - cy) / 2;
1814 /* if an item is selected, the icon is shifted up instead of down */
1815 if (iItem == infoPtr->iSelected)
1816 center_offset_v -= infoPtr->uVItemPadding / 2;
1818 center_offset_v += infoPtr->uVItemPadding / 2;
1820 if (infoPtr->dwStyle & TCS_FIXEDWIDTH && infoPtr->dwStyle & (TCS_FORCELABELLEFT | TCS_FORCEICONLEFT))
1821 center_offset_h = infoPtr->uHItemPadding;
1823 if (center_offset_h < 2)
1824 center_offset_h = 2;
1826 if (center_offset_v < 0)
1827 center_offset_v = 0;
1829 TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%s), textlen=%d\n",
1830 debugstr_w(item->pszText), center_offset_h, center_offset_v,
1831 wine_dbgstr_rect(drawRect), (rcText.right-rcText.left));
1833 if((infoPtr->dwStyle & TCS_VERTICAL) && (infoPtr->dwStyle & TCS_BOTTOM))
1835 rcImage.top = drawRect->top + center_offset_h;
1836 /* if tab is TCS_VERTICAL and TCS_BOTTOM, the text is drawn from the */
1837 /* right side of the tab, but the image still uses the left as its x position */
1838 /* this keeps the image always drawn off of the same side of the tab */
1839 rcImage.left = drawRect->right - cx - center_offset_v;
1840 drawRect->top += cy + infoPtr->uHItemPadding;
1842 else if(infoPtr->dwStyle & TCS_VERTICAL)
1844 rcImage.top = drawRect->bottom - cy - center_offset_h;
1845 rcImage.left = drawRect->left + center_offset_v;
1846 drawRect->bottom -= cy + infoPtr->uHItemPadding;
1848 else /* normal style, whether TCS_BOTTOM or not */
1850 rcImage.left = drawRect->left + center_offset_h;
1851 rcImage.top = drawRect->top + center_offset_v;
1852 drawRect->left += cx + infoPtr->uHItemPadding;
1855 TRACE("drawing image=%d, left=%d, top=%d\n",
1856 item->iImage, rcImage.left, rcImage.top-1);
1868 /* Now position text */
1869 if (infoPtr->dwStyle & TCS_FIXEDWIDTH && infoPtr->dwStyle & TCS_FORCELABELLEFT)
1870 center_offset_h = infoPtr->uHItemPadding;
1872 if(infoPtr->dwStyle & TCS_VERTICAL)
1873 center_offset_h = ((drawRect->bottom - drawRect->top) - (rcText.right - rcText.left)) / 2;
1875 center_offset_h = ((drawRect->right - drawRect->left) - (rcText.right - rcText.left)) / 2;
1877 if(infoPtr->dwStyle & TCS_VERTICAL)
1879 if(infoPtr->dwStyle & TCS_BOTTOM)
1880 drawRect->top+=center_offset_h;
1882 drawRect->bottom-=center_offset_h;
1884 center_offset_v = ((drawRect->right - drawRect->left) - (rcText.bottom - rcText.top)) / 2;
1888 drawRect->left += center_offset_h;
1889 center_offset_v = ((drawRect->bottom - drawRect->top) - (rcText.bottom - rcText.top)) / 2;
1892 /* if an item is selected, the text is shifted up instead of down */
1893 if (iItem == infoPtr->iSelected)
1894 center_offset_v -= infoPtr->uVItemPadding / 2;
1896 center_offset_v += infoPtr->uVItemPadding / 2;
1898 if (center_offset_v < 0)
1899 center_offset_v = 0;
1901 if(infoPtr->dwStyle & TCS_VERTICAL)
1902 drawRect->left += center_offset_v;
1904 drawRect->top += center_offset_v;
1907 if(infoPtr->dwStyle & TCS_VERTICAL) /* if we are vertical rotate the text and each character */
1909 static const WCHAR ArialW[] = { 'A','r','i','a','l',0 };
1912 INT nEscapement = 900;
1913 INT nOrientation = 900;
1915 if(infoPtr->dwStyle & TCS_BOTTOM)
1918 nOrientation = -900;
1921 /* to get a font with the escapement and orientation we are looking for, we need to */
1922 /* call CreateFontIndirectA, which requires us to set the values of the logfont we pass in */
1923 if (!GetObjectW((infoPtr->hFont) ?
1924 infoPtr->hFont : GetStockObject(SYSTEM_FONT),
1925 sizeof(LOGFONTW),&logfont))
1929 lstrcpyW(logfont.lfFaceName, ArialW);
1930 logfont.lfHeight = -MulDiv(iPointSize, GetDeviceCaps(hdc, LOGPIXELSY),
1932 logfont.lfWeight = FW_NORMAL;
1933 logfont.lfItalic = 0;
1934 logfont.lfUnderline = 0;
1935 logfont.lfStrikeOut = 0;
1938 logfont.lfEscapement = nEscapement;
1939 logfont.lfOrientation = nOrientation;
1940 hFont = CreateFontIndirectW(&logfont);
1941 SelectObject(hdc, hFont);
1946 (infoPtr->dwStyle & TCS_BOTTOM) ? drawRect->right : drawRect->left,
1947 (!(infoPtr->dwStyle & TCS_BOTTOM)) ? drawRect->bottom : drawRect->top,
1951 lstrlenW(item->pszText),
1955 DeleteObject(hFont);
1959 TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%s), textlen=%d\n",
1960 debugstr_w(item->pszText), center_offset_h, center_offset_v,
1961 wine_dbgstr_rect(drawRect), (rcText.right-rcText.left));
1968 lstrlenW(item->pszText),
1970 DT_LEFT | DT_SINGLELINE
1975 *drawRect = rcTemp; /* restore drawRect */
1981 SelectObject(hdc, hOldFont);
1982 SetBkMode(hdc, oldBkMode);
1983 SelectObject(hdc, holdPen);
1984 DeleteObject( htextPen );
1987 /******************************************************************************
1990 * This method is used to draw a single tab into the tab control.
1992 static void TAB_DrawItem(const TAB_INFO *infoPtr, HDC hdc, INT iItem)
1997 RECT r, fillRect, r1;
2000 COLORREF bkgnd, corner;
2004 * Get the rectangle for the item.
2006 isVisible = TAB_InternalGetItemRect(infoPtr,
2015 /* Clip UpDown control to not draw over it */
2016 if (infoPtr->needsScrolling)
2018 GetWindowRect(infoPtr->hwnd, &rC);
2019 GetWindowRect(infoPtr->hwndUpDown, &rUD);
2020 ExcludeClipRect(hdc, rUD.left - rC.left, rUD.top - rC.top, rUD.right - rC.left, rUD.bottom - rC.top);
2023 /* If you need to see what the control is doing,
2024 * then override these variables. They will change what
2025 * fill colors are used for filling the tabs, and the
2026 * corners when drawing the edge.
2028 bkgnd = comctl32_color.clrBtnFace;
2029 corner = comctl32_color.clrBtnFace;
2031 if (infoPtr->dwStyle & TCS_BUTTONS)
2033 /* Get item rectangle */
2036 /* Separators between flat buttons */
2037 if ((infoPtr->dwStyle & TCS_FLATBUTTONS) && (infoPtr->exStyle & TCS_EX_FLATSEPARATORS))
2040 r1.right += (FLAT_BTN_SPACINGX -2);
2041 DrawEdge(hdc, &r1, EDGE_ETCHED, BF_RIGHT);
2044 if (iItem == infoPtr->iSelected)
2046 DrawEdge(hdc, &r, EDGE_SUNKEN, BF_SOFT|BF_RECT);
2048 OffsetRect(&r, 1, 1);
2050 else /* ! selected */
2052 DWORD state = TAB_GetItem(infoPtr, iItem)->dwState;
2054 if ((state & TCIS_BUTTONPRESSED) || (iItem == infoPtr->uFocus))
2055 DrawEdge(hdc, &r, EDGE_SUNKEN, BF_SOFT|BF_RECT);
2057 if (!(infoPtr->dwStyle & TCS_FLATBUTTONS))
2058 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_RECT);
2061 else /* !TCS_BUTTONS */
2063 /* We draw a rectangle of different sizes depending on the selection
2065 if (iItem == infoPtr->iSelected) {
2067 GetClientRect (infoPtr->hwnd, &rect);
2068 clRight = rect.right;
2069 clBottom = rect.bottom;
2076 * Erase the background. (Delay it but setup rectangle.)
2077 * This is necessary when drawing the selected item since it is larger
2078 * than the others, it might overlap with stuff already drawn by the
2083 /* Draw themed tabs - but only if they are at the top.
2084 * Windows draws even side or bottom tabs themed, with wacky results.
2085 * However, since in Wine apps may get themed that did not opt in via
2086 * a manifest avoid theming when we know the result will be wrong */
2087 if ((theme = GetWindowTheme (infoPtr->hwnd))
2088 && ((infoPtr->dwStyle & (TCS_VERTICAL | TCS_BOTTOM)) == 0))
2090 static const int partIds[8] = {
2093 TABP_TABITEMLEFTEDGE,
2094 TABP_TABITEMRIGHTEDGE,
2095 TABP_TABITEMBOTHEDGE,
2098 TABP_TOPTABITEMLEFTEDGE,
2099 TABP_TOPTABITEMRIGHTEDGE,
2100 TABP_TOPTABITEMBOTHEDGE,
2103 int stateId = TIS_NORMAL;
2105 /* selected and unselected tabs have different parts */
2106 if (iItem == infoPtr->iSelected)
2108 /* The part also differs on the position of a tab on a line.
2109 * "Visually" determining the position works well enough. */
2110 if(selectedRect.left == 0)
2112 if(selectedRect.right == clRight)
2115 if (iItem == infoPtr->iSelected)
2116 stateId = TIS_SELECTED;
2117 else if (iItem == infoPtr->iHotTracked)
2119 else if (iItem == infoPtr->uFocus)
2120 stateId = TIS_FOCUSED;
2122 /* Adjust rectangle for bottommost row */
2123 if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2126 DrawThemeBackground (theme, hdc, partIds[partIndex], stateId, &r, NULL);
2127 GetThemeBackgroundContentRect (theme, hdc, partIds[partIndex], stateId, &r, &r);
2129 else if(infoPtr->dwStyle & TCS_VERTICAL)
2131 /* These are for adjusting the drawing of a Selected tab */
2132 /* The initial values are for the normal case of non-Selected */
2133 int ZZ = 1; /* Do not stretch if selected */
2134 if (iItem == infoPtr->iSelected) {
2137 /* if leftmost draw the line longer */
2138 if(selectedRect.top == 0)
2139 fillRect.top += CONTROL_BORDER_SIZEY;
2140 /* if rightmost draw the line longer */
2141 if(selectedRect.bottom == clBottom)
2142 fillRect.bottom -= CONTROL_BORDER_SIZEY;
2145 if (infoPtr->dwStyle & TCS_BOTTOM)
2147 /* Adjust both rectangles to match native */
2150 TRACE("<right> 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_RIGHT|BF_TOP|BF_BOTTOM);
2160 /* Now erase the top corner and draw diagonal edge */
2161 SetBkColor(hdc, corner);
2162 r1.left = r.right - ROUND_CORNER_SIZE - 1;
2165 r1.bottom = r1.top + ROUND_CORNER_SIZE;
2166 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2168 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPLEFT);
2170 /* Now erase the bottom corner and draw diagonal edge */
2171 r1.left = r.right - ROUND_CORNER_SIZE - 1;
2172 r1.bottom = r.bottom;
2174 r1.top = r1.bottom - ROUND_CORNER_SIZE;
2175 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2177 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMLEFT);
2179 if ((iItem == infoPtr->iSelected) && (selectedRect.top == 0)) {
2183 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_TOP);
2189 TRACE("<left> item=%d, fill=(%s), edge=(%s)\n",
2190 iItem, wine_dbgstr_rect(&fillRect), wine_dbgstr_rect(&r));
2192 /* Clear interior */
2193 SetBkColor(hdc, bkgnd);
2194 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2196 /* Draw rectangular edge around tab */
2197 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_TOP|BF_BOTTOM);
2199 /* Now erase the top corner and draw diagonal edge */
2200 SetBkColor(hdc, corner);
2203 r1.right = r1.left + ROUND_CORNER_SIZE + 1;
2204 r1.bottom = r1.top + ROUND_CORNER_SIZE;
2205 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2207 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPRIGHT);
2209 /* Now erase the bottom corner and draw diagonal edge */
2211 r1.bottom = r.bottom;
2212 r1.right = r1.left + ROUND_CORNER_SIZE + 1;
2213 r1.top = r1.bottom - ROUND_CORNER_SIZE;
2214 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2216 DrawEdge(hdc, &r1, EDGE_SUNKEN, BF_DIAGONAL_ENDTOPLEFT);
2219 else /* ! TCS_VERTICAL */
2221 /* These are for adjusting the drawing of a Selected tab */
2222 /* The initial values are for the normal case of non-Selected */
2223 if (iItem == infoPtr->iSelected) {
2224 /* if leftmost draw the line longer */
2225 if(selectedRect.left == 0)
2226 fillRect.left += CONTROL_BORDER_SIZEX;
2227 /* if rightmost draw the line longer */
2228 if(selectedRect.right == clRight)
2229 fillRect.right -= CONTROL_BORDER_SIZEX;
2232 if (infoPtr->dwStyle & TCS_BOTTOM)
2234 /* Adjust both rectangles for topmost row */
2235 if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2241 TRACE("<bottom> item=%d, fill=(%s), edge=(%s)\n",
2242 iItem, wine_dbgstr_rect(&fillRect), wine_dbgstr_rect(&r));
2244 /* Clear interior */
2245 SetBkColor(hdc, bkgnd);
2246 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2248 /* Draw rectangular edge around tab */
2249 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_BOTTOM|BF_RIGHT);
2251 /* Now erase the righthand corner and draw diagonal edge */
2252 SetBkColor(hdc, corner);
2253 r1.left = r.right - ROUND_CORNER_SIZE;
2254 r1.bottom = r.bottom;
2256 r1.top = r1.bottom - ROUND_CORNER_SIZE - 1;
2257 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2259 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMLEFT);
2261 /* Now erase the lefthand corner and draw diagonal edge */
2263 r1.bottom = r.bottom;
2264 r1.right = r1.left + ROUND_CORNER_SIZE;
2265 r1.top = r1.bottom - ROUND_CORNER_SIZE - 1;
2266 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2268 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPLEFT);
2270 if (iItem == infoPtr->iSelected)
2274 if (selectedRect.left == 0)
2279 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_LEFT);
2286 /* Adjust both rectangles for bottommost row */
2287 if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2289 fillRect.bottom += 3;
2293 TRACE("<top> item=%d, fill=(%s), edge=(%s)\n",
2294 iItem, wine_dbgstr_rect(&fillRect), wine_dbgstr_rect(&r));
2296 /* Clear interior */
2297 SetBkColor(hdc, bkgnd);
2298 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2300 /* Draw rectangular edge around tab */
2301 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_TOP|BF_RIGHT);
2303 /* Now erase the righthand corner and draw diagonal edge */
2304 SetBkColor(hdc, corner);
2305 r1.left = r.right - ROUND_CORNER_SIZE;
2308 r1.bottom = r1.top + ROUND_CORNER_SIZE + 1;
2309 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2311 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMRIGHT);
2313 /* Now erase the lefthand corner and draw diagonal edge */
2316 r1.right = r1.left + ROUND_CORNER_SIZE;
2317 r1.bottom = r1.top + ROUND_CORNER_SIZE + 1;
2318 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2320 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPRIGHT);
2325 TAB_DumpItemInternal(infoPtr, iItem);
2327 /* This modifies r to be the text rectangle. */
2328 TAB_DrawItemInterior(infoPtr, hdc, iItem, &r);
2332 /******************************************************************************
2335 * This method is used to draw the raised border around the tab control
2338 static void TAB_DrawBorder(const TAB_INFO *infoPtr, HDC hdc)
2341 HTHEME theme = GetWindowTheme (infoPtr->hwnd);
2343 GetClientRect (infoPtr->hwnd, &rect);
2346 * Adjust for the style
2349 if (infoPtr->uNumItem)
2351 if ((infoPtr->dwStyle & TCS_BOTTOM) && !(infoPtr->dwStyle & TCS_VERTICAL))
2352 rect.bottom -= infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2353 else if((infoPtr->dwStyle & TCS_BOTTOM) && (infoPtr->dwStyle & TCS_VERTICAL))
2354 rect.right -= infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2355 else if(infoPtr->dwStyle & TCS_VERTICAL)
2356 rect.left += infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2357 else /* not TCS_VERTICAL and not TCS_BOTTOM */
2358 rect.top += infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2361 TRACE("border=(%s)\n", wine_dbgstr_rect(&rect));
2364 DrawThemeBackground (theme, hdc, TABP_PANE, 0, &rect, NULL);
2366 DrawEdge(hdc, &rect, EDGE_RAISED, BF_SOFT|BF_RECT);
2369 /******************************************************************************
2372 * This method repaints the tab control..
2374 static void TAB_Refresh (const TAB_INFO *infoPtr, HDC hdc)
2379 if (!infoPtr->DoRedraw)
2382 hOldFont = SelectObject (hdc, infoPtr->hFont);
2384 if (infoPtr->dwStyle & TCS_BUTTONS)
2386 for (i = 0; i < infoPtr->uNumItem; i++)
2387 TAB_DrawItem (infoPtr, hdc, i);
2391 /* Draw all the non selected item first */
2392 for (i = 0; i < infoPtr->uNumItem; i++)
2394 if (i != infoPtr->iSelected)
2395 TAB_DrawItem (infoPtr, hdc, i);
2398 /* Now, draw the border, draw it before the selected item
2399 * since the selected item overwrites part of the border. */
2400 TAB_DrawBorder (infoPtr, hdc);
2402 /* Then, draw the selected item */
2403 TAB_DrawItem (infoPtr, hdc, infoPtr->iSelected);
2406 SelectObject (hdc, hOldFont);
2409 static inline DWORD TAB_GetRowCount (const TAB_INFO *infoPtr)
2411 TRACE("(%p)\n", infoPtr);
2412 return infoPtr->uNumRows;
2415 static inline LRESULT TAB_SetRedraw (TAB_INFO *infoPtr, BOOL doRedraw)
2417 infoPtr->DoRedraw = doRedraw;
2421 /******************************************************************************
2422 * TAB_EnsureSelectionVisible
2424 * This method will make sure that the current selection is completely
2425 * visible by scrolling until it is.
2427 static void TAB_EnsureSelectionVisible(
2430 INT iSelected = infoPtr->iSelected;
2431 INT iOrigLeftmostVisible = infoPtr->leftmostVisible;
2433 /* set the items row to the bottommost row or topmost row depending on
2435 if ((infoPtr->uNumRows > 1) && !(infoPtr->dwStyle & TCS_BUTTONS))
2437 TAB_ITEM *selected = TAB_GetItem(infoPtr, iSelected);
2441 if(infoPtr->dwStyle & TCS_VERTICAL)
2442 newselected = selected->rect.left;
2444 newselected = selected->rect.top;
2446 /* the target row is always (number of rows - 1)
2447 as row 0 is furthest from the clientRect */
2448 iTargetRow = infoPtr->uNumRows - 1;
2450 if (newselected != iTargetRow)
2453 if(infoPtr->dwStyle & TCS_VERTICAL)
2455 for (i=0; i < infoPtr->uNumItem; i++)
2457 /* move everything in the row of the selected item to the iTargetRow */
2458 TAB_ITEM *item = TAB_GetItem(infoPtr, i);
2460 if (item->rect.left == newselected )
2461 item->rect.left = iTargetRow;
2464 if (item->rect.left > newselected)
2471 for (i=0; i < infoPtr->uNumItem; i++)
2473 TAB_ITEM *item = TAB_GetItem(infoPtr, i);
2475 if (item->rect.top == newselected )
2476 item->rect.top = iTargetRow;
2479 if (item->rect.top > newselected)
2484 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
2489 * Do the trivial cases first.
2491 if ( (!infoPtr->needsScrolling) ||
2492 (infoPtr->hwndUpDown==0) || (infoPtr->dwStyle & TCS_VERTICAL))
2495 if (infoPtr->leftmostVisible >= iSelected)
2497 infoPtr->leftmostVisible = iSelected;
2501 TAB_ITEM *selected = TAB_GetItem(infoPtr, iSelected);
2506 /* Calculate the part of the client area that is visible */
2507 GetClientRect(infoPtr->hwnd, &r);
2510 GetClientRect(infoPtr->hwndUpDown, &r);
2513 if ((selected->rect.right -
2514 selected->rect.left) >= width )
2516 /* Special case: width of selected item is greater than visible
2519 infoPtr->leftmostVisible = iSelected;
2523 for (i = infoPtr->leftmostVisible; i < infoPtr->uNumItem; i++)
2525 if ((selected->rect.right - TAB_GetItem(infoPtr, i)->rect.left) < width)
2528 infoPtr->leftmostVisible = i;
2532 if (infoPtr->leftmostVisible != iOrigLeftmostVisible)
2533 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
2535 SendMessageW(infoPtr->hwndUpDown, UDM_SETPOS, 0,
2536 MAKELONG(infoPtr->leftmostVisible, 0));
2539 /******************************************************************************
2540 * TAB_InvalidateTabArea
2542 * This method will invalidate the portion of the control that contains the
2543 * tabs. It is called when the state of the control changes and needs
2546 static void TAB_InvalidateTabArea(const TAB_INFO *infoPtr)
2548 RECT clientRect, rInvalidate, rAdjClient;
2549 INT lastRow = infoPtr->uNumRows - 1;
2552 if (lastRow < 0) return;
2554 GetClientRect(infoPtr->hwnd, &clientRect);
2555 rInvalidate = clientRect;
2556 rAdjClient = clientRect;
2558 TAB_AdjustRect(infoPtr, 0, &rAdjClient);
2560 TAB_InternalGetItemRect(infoPtr, infoPtr->uNumItem-1 , &rect, NULL);
2561 if ((infoPtr->dwStyle & TCS_BOTTOM) && (infoPtr->dwStyle & TCS_VERTICAL))
2563 rInvalidate.left = rAdjClient.right;
2564 if (infoPtr->uNumRows == 1)
2565 rInvalidate.bottom = clientRect.top + rect.bottom + 2 * SELECTED_TAB_OFFSET;
2567 else if(infoPtr->dwStyle & TCS_VERTICAL)
2569 rInvalidate.right = rAdjClient.left;
2570 if (infoPtr->uNumRows == 1)
2571 rInvalidate.bottom = clientRect.top + rect.bottom + 2 * SELECTED_TAB_OFFSET;
2573 else if (infoPtr->dwStyle & TCS_BOTTOM)
2575 rInvalidate.top = rAdjClient.bottom;
2576 if (infoPtr->uNumRows == 1)
2577 rInvalidate.right = clientRect.left + rect.right + 2 * SELECTED_TAB_OFFSET;
2581 rInvalidate.bottom = rAdjClient.top;
2582 if (infoPtr->uNumRows == 1)
2583 rInvalidate.right = clientRect.left + rect.right + 2 * SELECTED_TAB_OFFSET;
2586 /* Punch out the updown control */
2587 if (infoPtr->needsScrolling && (rInvalidate.right > 0)) {
2589 GetClientRect(infoPtr->hwndUpDown, &r);
2590 if (rInvalidate.right > clientRect.right - r.left)
2591 rInvalidate.right = rInvalidate.right - (r.right - r.left);
2593 rInvalidate.right = clientRect.right - r.left;
2596 TRACE("invalidate (%s)\n", wine_dbgstr_rect(&rInvalidate));
2598 InvalidateRect(infoPtr->hwnd, &rInvalidate, TRUE);
2601 static inline LRESULT TAB_Paint (TAB_INFO *infoPtr, HDC hdcPaint)
2610 hdc = BeginPaint (infoPtr->hwnd, &ps);
2611 TRACE("erase %d, rect=(%s)\n", ps.fErase, wine_dbgstr_rect(&ps.rcPaint));
2614 TAB_Refresh (infoPtr, hdc);
2617 EndPaint (infoPtr->hwnd, &ps);
2623 TAB_InsertItemT (TAB_INFO *infoPtr, INT iItem, TCITEMW *pti, BOOL bUnicode)
2628 GetClientRect (infoPtr->hwnd, &rect);
2629 TRACE("Rect: %p %s\n", infoPtr->hwnd, wine_dbgstr_rect(&rect));
2631 if (iItem < 0) return -1;
2632 if (iItem > infoPtr->uNumItem)
2633 iItem = infoPtr->uNumItem;
2635 TAB_DumpItemExternalT(pti, iItem, bUnicode);
2638 if (infoPtr->uNumItem == 0) {
2639 infoPtr->items = Alloc (TAB_ITEM_SIZE(infoPtr));
2640 infoPtr->uNumItem++;
2641 infoPtr->iSelected = 0;
2644 LPBYTE oldItems = (LPBYTE)infoPtr->items;
2646 infoPtr->uNumItem++;
2647 infoPtr->items = Alloc (TAB_ITEM_SIZE(infoPtr) * infoPtr->uNumItem);
2649 /* pre insert copy */
2651 memcpy (infoPtr->items, oldItems,
2652 iItem * TAB_ITEM_SIZE(infoPtr));
2655 /* post insert copy */
2656 if (iItem < infoPtr->uNumItem - 1) {
2657 memcpy (TAB_GetItem(infoPtr, iItem + 1),
2658 oldItems + iItem * TAB_ITEM_SIZE(infoPtr),
2659 (infoPtr->uNumItem - iItem - 1) * TAB_ITEM_SIZE(infoPtr));
2663 if (iItem <= infoPtr->iSelected)
2664 infoPtr->iSelected++;
2669 item = TAB_GetItem(infoPtr, iItem);
2671 item->pszText = NULL;
2673 if (pti->mask & TCIF_TEXT)
2676 Str_SetPtrW (&item->pszText, pti->pszText);
2678 Str_SetPtrAtoW (&item->pszText, (LPSTR)pti->pszText);
2681 if (pti->mask & TCIF_IMAGE)
2682 item->iImage = pti->iImage;
2686 if (pti->mask & TCIF_PARAM)
2687 memcpy(item->extra, &pti->lParam, infoPtr->cbInfo);
2689 memset(item->extra, 0, infoPtr->cbInfo);
2691 TAB_SetItemBounds(infoPtr);
2692 if (infoPtr->uNumItem > 1)
2693 TAB_InvalidateTabArea(infoPtr);
2695 InvalidateRect(infoPtr->hwnd, NULL, TRUE);
2697 TRACE("[%p]: added item %d %s\n",
2698 infoPtr->hwnd, iItem, debugstr_w(item->pszText));
2700 /* If we haven't set the current focus yet, set it now. */
2701 if (infoPtr->uFocus == -1)
2702 TAB_SetCurFocus(infoPtr, iItem);
2708 TAB_SetItemSize (TAB_INFO *infoPtr, INT cx, INT cy)
2711 BOOL bNeedPaint = FALSE;
2713 lResult = MAKELONG(infoPtr->tabWidth, infoPtr->tabHeight);
2715 /* UNDOCUMENTED: If requested Width or Height is 0 this means that program wants to use auto size. */
2716 if (infoPtr->dwStyle & TCS_FIXEDWIDTH && (infoPtr->tabWidth != cx))
2718 infoPtr->tabWidth = cx;
2722 if (infoPtr->tabHeight != cy)
2724 if ((infoPtr->fHeightSet = (cy != 0)))
2725 infoPtr->tabHeight = cy;
2729 TRACE("was h=%d,w=%d, now h=%d,w=%d\n",
2730 HIWORD(lResult), LOWORD(lResult),
2731 infoPtr->tabHeight, infoPtr->tabWidth);
2735 TAB_SetItemBounds(infoPtr);
2736 RedrawWindow(infoPtr->hwnd, NULL, NULL, RDW_ERASE | RDW_INVALIDATE | RDW_UPDATENOW);
2742 static inline LRESULT TAB_SetMinTabWidth (TAB_INFO *infoPtr, INT cx)
2746 TRACE("(%p,%d)\n", infoPtr, cx);
2748 if (infoPtr->tabMinWidth < 0)
2749 oldcx = DEFAULT_MIN_TAB_WIDTH;
2751 oldcx = infoPtr->tabMinWidth;
2752 infoPtr->tabMinWidth = cx;
2753 TAB_SetItemBounds(infoPtr);
2757 static inline LRESULT
2758 TAB_HighlightItem (TAB_INFO *infoPtr, INT iItem, BOOL fHighlight)
2764 TRACE("(%p,%d,%s)\n", infoPtr, iItem, fHighlight ? "true" : "false");
2766 if (iItem < 0 || iItem >= infoPtr->uNumItem)
2769 lpState = &TAB_GetItem(infoPtr, iItem)->dwState;
2770 oldState = *lpState;
2773 *lpState |= TCIS_HIGHLIGHTED;
2775 *lpState &= ~TCIS_HIGHLIGHTED;
2777 if ((oldState != *lpState) && TAB_InternalGetItemRect (infoPtr, iItem, &r, NULL))
2778 InvalidateRect (infoPtr->hwnd, &r, TRUE);
2784 TAB_SetItemT (TAB_INFO *infoPtr, INT iItem, LPTCITEMW tabItem, BOOL bUnicode)
2788 TRACE("(%p,%d,%p,%s)\n", infoPtr, iItem, tabItem, bUnicode ? "true" : "false");
2790 if (iItem < 0 || iItem >= infoPtr->uNumItem)
2793 TAB_DumpItemExternalT(tabItem, iItem, bUnicode);
2795 wineItem = TAB_GetItem(infoPtr, iItem);
2797 if (tabItem->mask & TCIF_IMAGE)
2798 wineItem->iImage = tabItem->iImage;
2800 if (tabItem->mask & TCIF_PARAM)
2801 memcpy(wineItem->extra, &tabItem->lParam, infoPtr->cbInfo);
2803 if (tabItem->mask & TCIF_RTLREADING)
2804 FIXME("TCIF_RTLREADING\n");
2806 if (tabItem->mask & TCIF_STATE)
2807 wineItem->dwState = (wineItem->dwState & ~tabItem->dwStateMask) |
2808 ( tabItem->dwState & tabItem->dwStateMask);
2810 if (tabItem->mask & TCIF_TEXT)
2812 Free(wineItem->pszText);
2813 wineItem->pszText = NULL;
2815 Str_SetPtrW(&wineItem->pszText, tabItem->pszText);
2817 Str_SetPtrAtoW(&wineItem->pszText, (LPSTR)tabItem->pszText);
2820 /* Update and repaint tabs */
2821 TAB_SetItemBounds(infoPtr);
2822 TAB_InvalidateTabArea(infoPtr);
2827 static inline LRESULT TAB_GetItemCount (const TAB_INFO *infoPtr)
2830 return infoPtr->uNumItem;
2835 TAB_GetItemT (TAB_INFO *infoPtr, INT iItem, LPTCITEMW tabItem, BOOL bUnicode)
2839 TRACE("(%p,%d,%p,%s)\n", infoPtr, iItem, tabItem, bUnicode ? "true" : "false");
2841 if (!tabItem) return FALSE;
2843 if (iItem < 0 || iItem >= infoPtr->uNumItem)
2845 /* init requested fields */
2846 if (tabItem->mask & TCIF_IMAGE) tabItem->iImage = 0;
2847 if (tabItem->mask & TCIF_PARAM) tabItem->lParam = 0;
2848 if (tabItem->mask & TCIF_STATE) tabItem->dwState = 0;
2852 wineItem = TAB_GetItem(infoPtr, iItem);
2854 if (tabItem->mask & TCIF_IMAGE)
2855 tabItem->iImage = wineItem->iImage;
2857 if (tabItem->mask & TCIF_PARAM)
2858 memcpy(&tabItem->lParam, wineItem->extra, infoPtr->cbInfo);
2860 if (tabItem->mask & TCIF_RTLREADING)
2861 FIXME("TCIF_RTLREADING\n");
2863 if (tabItem->mask & TCIF_STATE)
2864 tabItem->dwState = wineItem->dwState & tabItem->dwStateMask;
2866 if (tabItem->mask & TCIF_TEXT)
2869 Str_GetPtrW (wineItem->pszText, tabItem->pszText, tabItem->cchTextMax);
2871 Str_GetPtrWtoA (wineItem->pszText, (LPSTR)tabItem->pszText, tabItem->cchTextMax);
2874 TAB_DumpItemExternalT(tabItem, iItem, bUnicode);
2880 static LRESULT TAB_DeleteItem (TAB_INFO *infoPtr, INT iItem)
2882 BOOL bResult = FALSE;
2884 TRACE("(%p, %d)\n", infoPtr, iItem);
2886 if ((iItem >= 0) && (iItem < infoPtr->uNumItem))
2888 TAB_ITEM *item = TAB_GetItem(infoPtr, iItem);
2889 LPBYTE oldItems = (LPBYTE)infoPtr->items;
2891 TAB_InvalidateTabArea(infoPtr);
2892 Free(item->pszText);
2893 infoPtr->uNumItem--;
2895 if (!infoPtr->uNumItem)
2897 infoPtr->items = NULL;
2898 if (infoPtr->iHotTracked >= 0)
2900 KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
2901 infoPtr->iHotTracked = -1;
2906 infoPtr->items = Alloc(TAB_ITEM_SIZE(infoPtr) * infoPtr->uNumItem);
2909 memcpy(infoPtr->items, oldItems, iItem * TAB_ITEM_SIZE(infoPtr));
2911 if (iItem < infoPtr->uNumItem)
2912 memcpy(TAB_GetItem(infoPtr, iItem),
2913 oldItems + (iItem + 1) * TAB_ITEM_SIZE(infoPtr),
2914 (infoPtr->uNumItem - iItem) * TAB_ITEM_SIZE(infoPtr));
2916 if (iItem <= infoPtr->iHotTracked)
2918 /* When tabs move left/up, the hot track item may change */
2919 FIXME("Recalc hot track\n");
2924 /* Readjust the selected index */
2925 if (iItem == infoPtr->iSelected)
2926 infoPtr->iSelected = -1;
2927 else if (iItem < infoPtr->iSelected)
2928 infoPtr->iSelected--;
2930 if (infoPtr->uNumItem == 0)
2931 infoPtr->iSelected = -1;
2933 /* Reposition and repaint tabs */
2934 TAB_SetItemBounds(infoPtr);
2942 static inline LRESULT TAB_DeleteAllItems (TAB_INFO *infoPtr)
2944 TRACE("(%p)\n", infoPtr);
2945 while (infoPtr->uNumItem)
2946 TAB_DeleteItem (infoPtr, 0);
2951 static inline LRESULT TAB_GetFont (const TAB_INFO *infoPtr)
2953 TRACE("(%p) returning %p\n", infoPtr, infoPtr->hFont);
2954 return (LRESULT)infoPtr->hFont;
2957 static inline LRESULT TAB_SetFont (TAB_INFO *infoPtr, HFONT hNewFont)
2959 TRACE("(%p,%p)\n", infoPtr, hNewFont);
2961 infoPtr->hFont = hNewFont;
2963 TAB_SetItemBounds(infoPtr);
2965 TAB_InvalidateTabArea(infoPtr);
2971 static inline LRESULT TAB_GetImageList (const TAB_INFO *infoPtr)
2974 return (LRESULT)infoPtr->himl;
2977 static inline LRESULT TAB_SetImageList (TAB_INFO *infoPtr, HIMAGELIST himlNew)
2979 HIMAGELIST himlPrev = infoPtr->himl;
2980 TRACE("himl=%p\n", himlNew);
2981 infoPtr->himl = himlNew;
2982 TAB_SetItemBounds(infoPtr);
2983 InvalidateRect(infoPtr->hwnd, NULL, TRUE);
2984 return (LRESULT)himlPrev;
2987 static inline LRESULT TAB_GetUnicodeFormat (const TAB_INFO *infoPtr)
2989 TRACE("(%p)\n", infoPtr);
2990 return infoPtr->bUnicode;
2993 static inline LRESULT TAB_SetUnicodeFormat (TAB_INFO *infoPtr, BOOL bUnicode)
2995 BOOL bTemp = infoPtr->bUnicode;
2997 TRACE("(%p %d)\n", infoPtr, bUnicode);
2998 infoPtr->bUnicode = bUnicode;
3003 static inline LRESULT TAB_Size (TAB_INFO *infoPtr)
3005 /* I'm not really sure what the following code was meant to do.
3006 This is what it is doing:
3007 When WM_SIZE is sent with SIZE_RESTORED, the control
3008 gets positioned in the top left corner.
3012 UINT uPosFlags,cx,cy;
3016 parent = GetParent (hwnd);
3017 GetClientRect(parent, &parent_rect);
3020 if (GetWindowLongW(hwnd, GWL_STYLE) & CCS_NORESIZE)
3021 uPosFlags |= (SWP_NOSIZE | SWP_NOMOVE);
3023 SetWindowPos (hwnd, 0, parent_rect.left, parent_rect.top,
3024 cx, cy, uPosFlags | SWP_NOZORDER);
3026 FIXME("WM_SIZE flag %x %lx not handled\n", wParam, lParam);
3029 /* Recompute the size/position of the tabs. */
3030 TAB_SetItemBounds (infoPtr);
3032 /* Force a repaint of the control. */
3033 InvalidateRect(infoPtr->hwnd, NULL, TRUE);
3039 static LRESULT TAB_Create (HWND hwnd, LPARAM lParam)
3042 TEXTMETRICW fontMetrics;
3047 infoPtr = Alloc (sizeof(TAB_INFO));
3049 SetWindowLongPtrW(hwnd, 0, (DWORD_PTR)infoPtr);
3051 infoPtr->hwnd = hwnd;
3052 infoPtr->hwndNotify = ((LPCREATESTRUCTW)lParam)->hwndParent;
3053 infoPtr->uNumItem = 0;
3054 infoPtr->uNumRows = 0;
3055 infoPtr->uHItemPadding = 6;
3056 infoPtr->uVItemPadding = 3;
3057 infoPtr->uHItemPadding_s = 6;
3058 infoPtr->uVItemPadding_s = 3;
3061 infoPtr->hcurArrow = LoadCursorW (0, (LPWSTR)IDC_ARROW);
3062 infoPtr->iSelected = -1;
3063 infoPtr->iHotTracked = -1;
3064 infoPtr->uFocus = -1;
3065 infoPtr->hwndToolTip = 0;
3066 infoPtr->DoRedraw = TRUE;
3067 infoPtr->needsScrolling = FALSE;
3068 infoPtr->hwndUpDown = 0;
3069 infoPtr->leftmostVisible = 0;
3070 infoPtr->fHeightSet = FALSE;
3071 infoPtr->bUnicode = IsWindowUnicode (hwnd);
3072 infoPtr->cbInfo = sizeof(LPARAM);
3074 TRACE("Created tab control, hwnd [%p]\n", hwnd);
3076 /* The tab control always has the WS_CLIPSIBLINGS style. Even
3077 if you don't specify it in CreateWindow. This is necessary in
3078 order for paint to work correctly. This follows windows behaviour. */
3079 dwStyle = GetWindowLongW(hwnd, GWL_STYLE);
3080 SetWindowLongW(hwnd, GWL_STYLE, dwStyle|WS_CLIPSIBLINGS);
3082 infoPtr->dwStyle = dwStyle | WS_CLIPSIBLINGS;
3083 infoPtr->exStyle = (dwStyle & TCS_FLATBUTTONS) ? TCS_EX_FLATSEPARATORS : 0;
3085 if (infoPtr->dwStyle & TCS_TOOLTIPS) {
3086 /* Create tooltip control */
3087 infoPtr->hwndToolTip =
3088 CreateWindowExW (0, TOOLTIPS_CLASSW, NULL, WS_POPUP,
3089 CW_USEDEFAULT, CW_USEDEFAULT,
3090 CW_USEDEFAULT, CW_USEDEFAULT,
3093 /* Send NM_TOOLTIPSCREATED notification */
3094 if (infoPtr->hwndToolTip) {
3095 NMTOOLTIPSCREATED nmttc;
3097 nmttc.hdr.hwndFrom = hwnd;
3098 nmttc.hdr.idFrom = GetWindowLongPtrW(hwnd, GWLP_ID);
3099 nmttc.hdr.code = NM_TOOLTIPSCREATED;
3100 nmttc.hwndToolTips = infoPtr->hwndToolTip;
3102 SendMessageW (infoPtr->hwndNotify, WM_NOTIFY,
3103 GetWindowLongPtrW(hwnd, GWLP_ID), (LPARAM)&nmttc);
3107 OpenThemeData (infoPtr->hwnd, themeClass);
3110 * We need to get text information so we need a DC and we need to select
3114 hOldFont = SelectObject (hdc, GetStockObject (SYSTEM_FONT));
3116 /* Use the system font to determine the initial height of a tab. */
3117 GetTextMetricsW(hdc, &fontMetrics);
3120 * Make sure there is enough space for the letters + growing the
3121 * selected item + extra space for the selected item.
3123 infoPtr->tabHeight = fontMetrics.tmHeight + SELECTED_TAB_OFFSET +
3124 ((infoPtr->dwStyle & TCS_BUTTONS) ? 2 : 1) *
3125 infoPtr->uVItemPadding;
3127 /* Initialize the width of a tab. */
3128 if (infoPtr->dwStyle & TCS_FIXEDWIDTH)
3129 infoPtr->tabWidth = GetDeviceCaps(hdc, LOGPIXELSX);
3131 infoPtr->tabMinWidth = -1;
3133 TRACE("tabH=%d, tabW=%d\n", infoPtr->tabHeight, infoPtr->tabWidth);
3135 SelectObject (hdc, hOldFont);
3136 ReleaseDC(hwnd, hdc);
3142 TAB_Destroy (TAB_INFO *infoPtr)
3146 SetWindowLongPtrW(infoPtr->hwnd, 0, 0);
3148 if (infoPtr->items) {
3149 for (iItem = 0; iItem < infoPtr->uNumItem; iItem++) {
3150 Free (TAB_GetItem(infoPtr, iItem)->pszText);
3152 Free (infoPtr->items);
3155 if (infoPtr->hwndToolTip)
3156 DestroyWindow (infoPtr->hwndToolTip);
3158 if (infoPtr->hwndUpDown)
3159 DestroyWindow(infoPtr->hwndUpDown);
3161 if (infoPtr->iHotTracked >= 0)
3162 KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
3164 CloseThemeData (GetWindowTheme (infoPtr->hwnd));
3170 /* update theme after a WM_THEMECHANGED message */
3171 static LRESULT theme_changed(const TAB_INFO *infoPtr)
3173 HTHEME theme = GetWindowTheme (infoPtr->hwnd);
3174 CloseThemeData (theme);
3175 OpenThemeData (infoPtr->hwnd, themeClass);
3179 static LRESULT TAB_NCCalcSize(WPARAM wParam)
3183 return WVR_ALIGNTOP;
3186 static inline LRESULT
3187 TAB_SetItemExtra (TAB_INFO *infoPtr, INT cbInfo)
3189 TRACE("(%p %d)\n", infoPtr, cbInfo);
3194 if (infoPtr->uNumItem)
3196 /* FIXME: MSDN says this is not allowed, but this hasn't been verified */
3200 infoPtr->cbInfo = cbInfo;
3204 static LRESULT TAB_RemoveImage (TAB_INFO *infoPtr, INT image)
3206 TRACE("%p %d\n", infoPtr, image);
3208 if (ImageList_Remove (infoPtr->himl, image))
3213 /* shift indices, repaint items if needed */
3214 for (i = 0; i < infoPtr->uNumItem; i++)
3216 idx = &TAB_GetItem(infoPtr, i)->iImage;
3225 if (TAB_InternalGetItemRect (infoPtr, i, &r, NULL))
3226 InvalidateRect (infoPtr->hwnd, &r, TRUE);
3235 TAB_SetExtendedStyle (TAB_INFO *infoPtr, DWORD exMask, DWORD exStyle)
3237 DWORD prevstyle = infoPtr->exStyle;
3239 /* zero mask means all styles */
3240 if (exMask == 0) exMask = ~0;
3242 if (exMask & TCS_EX_REGISTERDROP)
3244 FIXME("TCS_EX_REGISTERDROP style unimplemented\n");
3245 exMask &= ~TCS_EX_REGISTERDROP;
3246 exStyle &= ~TCS_EX_REGISTERDROP;
3249 if (exMask & TCS_EX_FLATSEPARATORS)
3251 if ((prevstyle ^ exStyle) & TCS_EX_FLATSEPARATORS)
3253 infoPtr->exStyle ^= TCS_EX_FLATSEPARATORS;
3254 TAB_InvalidateTabArea(infoPtr);
3261 static inline LRESULT
3262 TAB_GetExtendedStyle (const TAB_INFO *infoPtr)
3264 return infoPtr->exStyle;
3268 TAB_DeselectAll (TAB_INFO *infoPtr, BOOL excludesel)
3271 INT i, selected = infoPtr->iSelected;
3273 TRACE("(%p, %d)\n", infoPtr, excludesel);
3275 if (!(infoPtr->dwStyle & TCS_BUTTONS))
3278 for (i = 0; i < infoPtr->uNumItem; i++)
3280 if ((TAB_GetItem(infoPtr, i)->dwState & TCIS_BUTTONPRESSED) &&
3283 TAB_GetItem(infoPtr, i)->dwState &= ~TCIS_BUTTONPRESSED;
3288 if (!excludesel && (selected != -1))
3290 TAB_GetItem(infoPtr, selected)->dwState &= ~TCIS_BUTTONPRESSED;
3291 infoPtr->iSelected = -1;
3296 TAB_InvalidateTabArea (infoPtr);
3303 * Processes WM_STYLECHANGED messages.
3306 * [I] infoPtr : valid pointer to the tab data structure
3307 * [I] wStyleType : window style type (normal or extended)
3308 * [I] lpss : window style information
3313 static INT TAB_StyleChanged(TAB_INFO *infoPtr, WPARAM wStyleType,
3314 const STYLESTRUCT *lpss)
3316 TRACE("(styletype=%lx, styleOld=0x%08x, styleNew=0x%08x)\n",
3317 wStyleType, lpss->styleOld, lpss->styleNew);
3319 if (wStyleType != GWL_STYLE) return 0;
3321 infoPtr->dwStyle = lpss->styleNew;
3323 TAB_SetItemBounds (infoPtr);
3324 InvalidateRect(infoPtr->hwnd, NULL, TRUE);
3329 static LRESULT WINAPI
3330 TAB_WindowProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
3332 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
3334 TRACE("hwnd=%p msg=%x wParam=%lx lParam=%lx\n", hwnd, uMsg, wParam, lParam);
3335 if (!infoPtr && (uMsg != WM_CREATE))
3336 return DefWindowProcW (hwnd, uMsg, wParam, lParam);
3340 case TCM_GETIMAGELIST:
3341 return TAB_GetImageList (infoPtr);
3343 case TCM_SETIMAGELIST:
3344 return TAB_SetImageList (infoPtr, (HIMAGELIST)lParam);
3346 case TCM_GETITEMCOUNT:
3347 return TAB_GetItemCount (infoPtr);
3351 return TAB_GetItemT (infoPtr, (INT)wParam, (LPTCITEMW)lParam, uMsg == TCM_GETITEMW);
3355 return TAB_SetItemT (infoPtr, (INT)wParam, (LPTCITEMW)lParam, uMsg == TCM_SETITEMW);
3357 case TCM_DELETEITEM:
3358 return TAB_DeleteItem (infoPtr, (INT)wParam);
3360 case TCM_DELETEALLITEMS:
3361 return TAB_DeleteAllItems (infoPtr);
3363 case TCM_GETITEMRECT:
3364 return TAB_GetItemRect (infoPtr, (INT)wParam, (LPRECT)lParam);
3367 return TAB_GetCurSel (infoPtr);
3370 return TAB_HitTest (infoPtr, (LPTCHITTESTINFO)lParam);
3373 return TAB_SetCurSel (infoPtr, (INT)wParam);
3375 case TCM_INSERTITEMA:
3376 case TCM_INSERTITEMW:
3377 return TAB_InsertItemT (infoPtr, (INT)wParam, (TCITEMW*)lParam, uMsg == TCM_INSERTITEMW);
3379 case TCM_SETITEMEXTRA:
3380 return TAB_SetItemExtra (infoPtr, (INT)wParam);
3382 case TCM_ADJUSTRECT:
3383 return TAB_AdjustRect (infoPtr, (BOOL)wParam, (LPRECT)lParam);
3385 case TCM_SETITEMSIZE:
3386 return TAB_SetItemSize (infoPtr, (INT)LOWORD(lParam), (INT)HIWORD(lParam));
3388 case TCM_REMOVEIMAGE:
3389 return TAB_RemoveImage (infoPtr, (INT)wParam);
3391 case TCM_SETPADDING:
3392 return TAB_SetPadding (infoPtr, lParam);
3394 case TCM_GETROWCOUNT:
3395 return TAB_GetRowCount(infoPtr);
3397 case TCM_GETUNICODEFORMAT:
3398 return TAB_GetUnicodeFormat (infoPtr);
3400 case TCM_SETUNICODEFORMAT:
3401 return TAB_SetUnicodeFormat (infoPtr, (BOOL)wParam);
3403 case TCM_HIGHLIGHTITEM:
3404 return TAB_HighlightItem (infoPtr, (INT)wParam, (BOOL)LOWORD(lParam));
3406 case TCM_GETTOOLTIPS:
3407 return TAB_GetToolTips (infoPtr);
3409 case TCM_SETTOOLTIPS:
3410 return TAB_SetToolTips (infoPtr, (HWND)wParam);
3412 case TCM_GETCURFOCUS:
3413 return TAB_GetCurFocus (infoPtr);
3415 case TCM_SETCURFOCUS:
3416 return TAB_SetCurFocus (infoPtr, (INT)wParam);
3418 case TCM_SETMINTABWIDTH:
3419 return TAB_SetMinTabWidth(infoPtr, (INT)lParam);
3421 case TCM_DESELECTALL:
3422 return TAB_DeselectAll (infoPtr, (BOOL)wParam);
3424 case TCM_GETEXTENDEDSTYLE:
3425 return TAB_GetExtendedStyle (infoPtr);
3427 case TCM_SETEXTENDEDSTYLE:
3428 return TAB_SetExtendedStyle (infoPtr, wParam, lParam);
3431 return TAB_GetFont (infoPtr);
3434 return TAB_SetFont (infoPtr, (HFONT)wParam);
3437 return TAB_Create (hwnd, lParam);
3440 return TAB_Destroy (infoPtr);
3443 return DLGC_WANTARROWS | DLGC_WANTCHARS;
3445 case WM_LBUTTONDOWN:
3446 return TAB_LButtonDown (infoPtr, wParam, lParam);
3449 return TAB_LButtonUp (infoPtr);
3452 return SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, wParam, lParam);
3454 case WM_RBUTTONDOWN:
3455 return TAB_RButtonDown (infoPtr);
3458 return TAB_MouseMove (infoPtr, wParam, lParam);
3460 case WM_PRINTCLIENT:
3462 return TAB_Paint (infoPtr, (HDC)wParam);
3465 return TAB_Size (infoPtr);
3468 return TAB_SetRedraw (infoPtr, (BOOL)wParam);
3471 return TAB_OnHScroll(infoPtr, (int)LOWORD(wParam), (int)HIWORD(wParam));
3473 case WM_STYLECHANGED:
3474 return TAB_StyleChanged(infoPtr, wParam, (LPSTYLESTRUCT)lParam);
3476 case WM_SYSCOLORCHANGE:
3477 COMCTL32_RefreshSysColors();
3480 case WM_THEMECHANGED:
3481 return theme_changed (infoPtr);
3484 TAB_KillFocus(infoPtr);
3486 TAB_FocusChanging(infoPtr);
3487 break; /* Don't disturb normal focus behavior */
3490 return TAB_KeyDown(infoPtr, wParam, lParam);
3493 return TAB_NCHitTest(infoPtr, lParam);
3496 return TAB_NCCalcSize(wParam);
3499 if (uMsg >= WM_USER && uMsg < WM_APP && !COMCTL32_IsReflectedMessage(uMsg))
3500 WARN("unknown msg %04x wp=%08lx lp=%08lx\n",
3501 uMsg, wParam, lParam);
3504 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
3513 ZeroMemory (&wndClass, sizeof(WNDCLASSW));
3514 wndClass.style = CS_GLOBALCLASS | CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
3515 wndClass.lpfnWndProc = TAB_WindowProc;
3516 wndClass.cbClsExtra = 0;
3517 wndClass.cbWndExtra = sizeof(TAB_INFO *);
3518 wndClass.hCursor = LoadCursorW (0, (LPWSTR)IDC_ARROW);
3519 wndClass.hbrBackground = (HBRUSH)(COLOR_BTNFACE+1);
3520 wndClass.lpszClassName = WC_TABCONTROLW;
3522 RegisterClassW (&wndClass);
3527 TAB_Unregister (void)
3529 UnregisterClassW (WC_TABCONTROLW, NULL);