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 (infoPtr->iSelected != iItem) {
255 TAB_GetItem(infoPtr, prevItem)->dwState &= ~TCIS_BUTTONPRESSED;
256 TAB_GetItem(infoPtr, iItem)->dwState |= TCIS_BUTTONPRESSED;
258 infoPtr->iSelected=iItem;
259 infoPtr->uFocus=iItem;
260 TAB_EnsureSelectionVisible(infoPtr);
261 TAB_InvalidateTabArea(infoPtr);
267 static LRESULT TAB_SetCurFocus (TAB_INFO *infoPtr, INT iItem)
269 TRACE("(%p %d)\n", infoPtr, iItem);
272 infoPtr->uFocus = -1;
273 else if (iItem < infoPtr->uNumItem) {
274 if (infoPtr->dwStyle & TCS_BUTTONS) {
275 /* set focus to new item, leave selection as is */
276 if (infoPtr->uFocus != iItem) {
277 INT prev_focus = infoPtr->uFocus;
280 infoPtr->uFocus = iItem;
282 if (prev_focus != infoPtr->iSelected) {
283 if (TAB_InternalGetItemRect(infoPtr, prev_focus, &r, NULL))
284 InvalidateRect(infoPtr->hwnd, &r, FALSE);
287 if (TAB_InternalGetItemRect(infoPtr, iItem, &r, NULL))
288 InvalidateRect(infoPtr->hwnd, &r, FALSE);
290 TAB_SendSimpleNotify(infoPtr, TCN_FOCUSCHANGE);
293 INT oldFocus = infoPtr->uFocus;
294 if (infoPtr->iSelected != iItem || oldFocus == -1 ) {
295 infoPtr->uFocus = iItem;
296 if (oldFocus != -1) {
297 if (!TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGING)) {
298 infoPtr->iSelected = iItem;
299 TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGE);
302 infoPtr->iSelected = iItem;
303 TAB_EnsureSelectionVisible(infoPtr);
304 TAB_InvalidateTabArea(infoPtr);
312 static inline LRESULT
313 TAB_SetToolTips (TAB_INFO *infoPtr, HWND hwndToolTip)
315 TRACE("%p %p\n", infoPtr, hwndToolTip);
316 infoPtr->hwndToolTip = hwndToolTip;
320 static inline LRESULT
321 TAB_SetPadding (TAB_INFO *infoPtr, LPARAM lParam)
323 TRACE("(%p %d %d)\n", infoPtr, LOWORD(lParam), HIWORD(lParam));
324 infoPtr->uHItemPadding_s = LOWORD(lParam);
325 infoPtr->uVItemPadding_s = HIWORD(lParam);
330 /******************************************************************************
331 * TAB_InternalGetItemRect
333 * This method will calculate the rectangle representing a given tab item in
334 * client coordinates. This method takes scrolling into account.
336 * This method returns TRUE if the item is visible in the window and FALSE
337 * if it is completely outside the client area.
339 static BOOL TAB_InternalGetItemRect(
340 const TAB_INFO* infoPtr,
345 RECT tmpItemRect,clientRect;
347 /* Perform a sanity check and a trivial visibility check. */
348 if ( (infoPtr->uNumItem <= 0) ||
349 (itemIndex >= infoPtr->uNumItem) ||
350 (!(((infoPtr->dwStyle & TCS_MULTILINE) || (infoPtr->dwStyle & TCS_VERTICAL))) &&
351 (itemIndex < infoPtr->leftmostVisible)))
353 TRACE("Not Visible\n");
354 /* need to initialize these to empty rects */
357 memset(itemRect,0,sizeof(RECT));
358 itemRect->bottom = infoPtr->tabHeight;
361 memset(selectedRect,0,sizeof(RECT));
366 * Avoid special cases in this procedure by assigning the "out"
367 * parameters if the caller didn't supply them
369 if (itemRect == NULL)
370 itemRect = &tmpItemRect;
372 /* Retrieve the unmodified item rect. */
373 *itemRect = TAB_GetItem(infoPtr,itemIndex)->rect;
375 /* calculate the times bottom and top based on the row */
376 GetClientRect(infoPtr->hwnd, &clientRect);
378 if ((infoPtr->dwStyle & TCS_BOTTOM) && (infoPtr->dwStyle & TCS_VERTICAL))
380 itemRect->right = clientRect.right - SELECTED_TAB_OFFSET - itemRect->left * infoPtr->tabHeight -
381 ((infoPtr->dwStyle & TCS_BUTTONS) ? itemRect->left * BUTTON_SPACINGX : 0);
382 itemRect->left = itemRect->right - infoPtr->tabHeight;
384 else if (infoPtr->dwStyle & TCS_VERTICAL)
386 itemRect->left = clientRect.left + SELECTED_TAB_OFFSET + itemRect->left * infoPtr->tabHeight +
387 ((infoPtr->dwStyle & TCS_BUTTONS) ? itemRect->left * BUTTON_SPACINGX : 0);
388 itemRect->right = itemRect->left + infoPtr->tabHeight;
390 else if (infoPtr->dwStyle & TCS_BOTTOM)
392 itemRect->bottom = clientRect.bottom - itemRect->top * infoPtr->tabHeight -
393 ((infoPtr->dwStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : SELECTED_TAB_OFFSET);
394 itemRect->top = itemRect->bottom - infoPtr->tabHeight;
396 else /* not TCS_BOTTOM and not TCS_VERTICAL */
398 itemRect->top = clientRect.top + itemRect->top * infoPtr->tabHeight +
399 ((infoPtr->dwStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : SELECTED_TAB_OFFSET);
400 itemRect->bottom = itemRect->top + infoPtr->tabHeight;
404 * "scroll" it to make sure the item at the very left of the
405 * tab control is the leftmost visible tab.
407 if(infoPtr->dwStyle & TCS_VERTICAL)
411 -TAB_GetItem(infoPtr, infoPtr->leftmostVisible)->rect.top);
414 * Move the rectangle so the first item is slightly offset from
415 * the bottom of the tab control.
419 SELECTED_TAB_OFFSET);
424 -TAB_GetItem(infoPtr, infoPtr->leftmostVisible)->rect.left,
428 * Move the rectangle so the first item is slightly offset from
429 * the left of the tab control.
435 TRACE("item %d tab h=%d, rect=(%s)\n",
436 itemIndex, infoPtr->tabHeight, wine_dbgstr_rect(itemRect));
438 /* Now, calculate the position of the item as if it were selected. */
439 if (selectedRect!=NULL)
441 CopyRect(selectedRect, itemRect);
443 /* The rectangle of a selected item is a bit wider. */
444 if(infoPtr->dwStyle & TCS_VERTICAL)
445 InflateRect(selectedRect, 0, SELECTED_TAB_OFFSET);
447 InflateRect(selectedRect, SELECTED_TAB_OFFSET, 0);
449 /* If it also a bit higher. */
450 if ((infoPtr->dwStyle & TCS_BOTTOM) && (infoPtr->dwStyle & TCS_VERTICAL))
452 selectedRect->left -= 2; /* the border is thicker on the right */
453 selectedRect->right += SELECTED_TAB_OFFSET;
455 else if (infoPtr->dwStyle & TCS_VERTICAL)
457 selectedRect->left -= SELECTED_TAB_OFFSET;
458 selectedRect->right += 1;
460 else if (infoPtr->dwStyle & TCS_BOTTOM)
462 selectedRect->bottom += SELECTED_TAB_OFFSET;
464 else /* not TCS_BOTTOM and not TCS_VERTICAL */
466 selectedRect->top -= SELECTED_TAB_OFFSET;
467 selectedRect->bottom -= 1;
471 /* Check for visibility */
472 if (infoPtr->dwStyle & TCS_VERTICAL)
473 return (itemRect->top < clientRect.bottom) && (itemRect->bottom > clientRect.top);
475 return (itemRect->left < clientRect.right) && (itemRect->right > clientRect.left);
479 TAB_GetItemRect(const TAB_INFO *infoPtr, INT item, RECT *rect)
481 TRACE("(%p, %d, %p)\n", infoPtr, item, rect);
482 return TAB_InternalGetItemRect(infoPtr, item, rect, NULL);
485 /******************************************************************************
488 * This method is called to handle keyboard input
490 static LRESULT TAB_KeyDown(TAB_INFO* infoPtr, WPARAM keyCode, LPARAM lParam)
495 /* TCN_KEYDOWN notification sent always */
496 nm.hdr.hwndFrom = infoPtr->hwnd;
497 nm.hdr.idFrom = GetWindowLongPtrW(infoPtr->hwnd, GWLP_ID);
498 nm.hdr.code = TCN_KEYDOWN;
501 SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, nm.hdr.idFrom, (LPARAM)&nm);
506 newItem = infoPtr->uFocus - 1;
509 newItem = infoPtr->uFocus + 1;
513 /* If we changed to a valid item, change focused item */
514 if (newItem >= 0 && newItem < infoPtr->uNumItem && infoPtr->uFocus != newItem)
515 TAB_SetCurFocus(infoPtr, newItem);
521 * WM_KILLFOCUS handler
523 static void TAB_KillFocus(TAB_INFO *infoPtr)
525 /* clear current focused item back to selected for TCS_BUTTONS */
526 if ((infoPtr->dwStyle & TCS_BUTTONS) && (infoPtr->uFocus != infoPtr->iSelected))
530 if (TAB_InternalGetItemRect(infoPtr, infoPtr->uFocus, &r, NULL))
531 InvalidateRect(infoPtr->hwnd, &r, FALSE);
533 infoPtr->uFocus = infoPtr->iSelected;
537 /******************************************************************************
540 * This method is called whenever the focus goes in or out of this control
541 * it is used to update the visual state of the control.
543 static void TAB_FocusChanging(const TAB_INFO *infoPtr)
549 * Get the rectangle for the item.
551 isVisible = TAB_InternalGetItemRect(infoPtr,
557 * If the rectangle is not completely invisible, invalidate that
558 * portion of the window.
562 TRACE("invalidate (%s)\n", wine_dbgstr_rect(&selectedRect));
563 InvalidateRect(infoPtr->hwnd, &selectedRect, TRUE);
567 static INT TAB_InternalHitTest (const TAB_INFO *infoPtr, POINT pt, UINT *flags)
572 for (iCount = 0; iCount < infoPtr->uNumItem; iCount++)
574 TAB_InternalGetItemRect(infoPtr, iCount, &rect, NULL);
576 if (PtInRect(&rect, pt))
578 *flags = TCHT_ONITEM;
583 *flags = TCHT_NOWHERE;
587 static inline LRESULT
588 TAB_HitTest (const TAB_INFO *infoPtr, LPTCHITTESTINFO lptest)
590 TRACE("(%p, %p)\n", infoPtr, lptest);
591 return TAB_InternalHitTest (infoPtr, lptest->pt, &lptest->flags);
594 /******************************************************************************
597 * Napster v2b5 has a tab control for its main navigation which has a client
598 * area that covers the whole area of the dialog pages.
599 * That's why it receives all msgs for that area and the underlying dialog ctrls
601 * So I decided that we should handle WM_NCHITTEST here and return
602 * HTTRANSPARENT if we don't hit the tab control buttons.
603 * FIXME: WM_NCHITTEST handling correct ? Fix it if you know that Windows
604 * doesn't do it that way. Maybe depends on tab control styles ?
606 static inline LRESULT
607 TAB_NCHitTest (const TAB_INFO *infoPtr, LPARAM lParam)
612 pt.x = (short)LOWORD(lParam);
613 pt.y = (short)HIWORD(lParam);
614 ScreenToClient(infoPtr->hwnd, &pt);
616 if (TAB_InternalHitTest(infoPtr, pt, &dummyflag) == -1)
617 return HTTRANSPARENT;
623 TAB_LButtonDown (TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
629 if (infoPtr->hwndToolTip)
630 TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
631 WM_LBUTTONDOWN, wParam, lParam);
633 if (!(infoPtr->dwStyle & TCS_FOCUSNEVER)) {
634 SetFocus (infoPtr->hwnd);
637 if (infoPtr->hwndToolTip)
638 TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
639 WM_LBUTTONDOWN, wParam, lParam);
641 pt.x = (short)LOWORD(lParam);
642 pt.y = (short)HIWORD(lParam);
644 newItem = TAB_InternalHitTest (infoPtr, pt, &dummy);
646 TRACE("On Tab, item %d\n", newItem);
648 if ((newItem != -1) && (infoPtr->iSelected != newItem))
650 if ((infoPtr->dwStyle & TCS_BUTTONS) && (infoPtr->dwStyle & TCS_MULTISELECT) &&
651 (wParam & MK_CONTROL))
655 /* toggle multiselection */
656 TAB_GetItem(infoPtr, newItem)->dwState ^= TCIS_BUTTONPRESSED;
657 if (TAB_InternalGetItemRect (infoPtr, newItem, &r, NULL))
658 InvalidateRect (infoPtr->hwnd, &r, TRUE);
663 BOOL pressed = FALSE;
665 /* any button pressed ? */
666 for (i = 0; i < infoPtr->uNumItem; i++)
667 if ((TAB_GetItem (infoPtr, i)->dwState & TCIS_BUTTONPRESSED) &&
668 (infoPtr->iSelected != i))
674 TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGING);
677 TAB_DeselectAll (infoPtr, FALSE);
679 TAB_SetCurSel(infoPtr, newItem);
681 TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGE);
688 static inline LRESULT
689 TAB_LButtonUp (const TAB_INFO *infoPtr)
691 TAB_SendSimpleNotify(infoPtr, NM_CLICK);
696 static inline LRESULT
697 TAB_RButtonDown (const TAB_INFO *infoPtr)
699 TAB_SendSimpleNotify(infoPtr, NM_RCLICK);
703 /******************************************************************************
704 * TAB_DrawLoneItemInterior
706 * This calls TAB_DrawItemInterior. However, TAB_DrawItemInterior is normally
707 * called by TAB_DrawItem which is normally called by TAB_Refresh which sets
708 * up the device context and font. This routine does the same setup but
709 * only calls TAB_DrawItemInterior for the single specified item.
712 TAB_DrawLoneItemInterior(const TAB_INFO* infoPtr, int iItem)
714 HDC hdc = GetDC(infoPtr->hwnd);
717 /* Clip UpDown control to not draw over it */
718 if (infoPtr->needsScrolling)
720 GetWindowRect(infoPtr->hwnd, &rC);
721 GetWindowRect(infoPtr->hwndUpDown, &r);
722 ExcludeClipRect(hdc, r.left - rC.left, r.top - rC.top, r.right - rC.left, r.bottom - rC.top);
724 TAB_DrawItemInterior(infoPtr, hdc, iItem, NULL);
725 ReleaseDC(infoPtr->hwnd, hdc);
728 /* update a tab after hottracking - invalidate it or just redraw the interior,
729 * based on whether theming is used or not */
730 static inline void hottrack_refresh(const TAB_INFO *infoPtr, int tabIndex)
732 if (tabIndex == -1) return;
734 if (GetWindowTheme (infoPtr->hwnd))
737 TAB_InternalGetItemRect(infoPtr, tabIndex, &rect, NULL);
738 InvalidateRect (infoPtr->hwnd, &rect, FALSE);
741 TAB_DrawLoneItemInterior(infoPtr, tabIndex);
744 /******************************************************************************
745 * TAB_HotTrackTimerProc
747 * When a mouse-move event causes a tab to be highlighted (hot-tracking), a
748 * timer is setup so we can check if the mouse is moved out of our window.
749 * (We don't get an event when the mouse leaves, the mouse-move events just
750 * stop being delivered to our window and just start being delivered to
751 * another window.) This function is called when the timer triggers so
752 * we can check if the mouse has left our window. If so, we un-highlight
753 * the hot-tracked tab.
756 TAB_HotTrackTimerProc
758 HWND hwnd, /* handle of window for timer messages */
759 UINT uMsg, /* WM_TIMER message */
760 UINT_PTR idEvent, /* timer identifier */
761 DWORD dwTime /* current system time */
764 TAB_INFO* infoPtr = TAB_GetInfoPtr(hwnd);
766 if (infoPtr != NULL && infoPtr->iHotTracked >= 0)
771 ** If we can't get the cursor position, or if the cursor is outside our
772 ** window, we un-highlight the hot-tracked tab. Note that the cursor is
773 ** "outside" even if it is within our bounding rect if another window
774 ** overlaps. Note also that the case where the cursor stayed within our
775 ** window but has moved off the hot-tracked tab will be handled by the
776 ** WM_MOUSEMOVE event.
778 if (!GetCursorPos(&pt) || WindowFromPoint(pt) != hwnd)
780 /* Redraw iHotTracked to look normal */
781 INT iRedraw = infoPtr->iHotTracked;
782 infoPtr->iHotTracked = -1;
783 hottrack_refresh (infoPtr, iRedraw);
785 /* Kill this timer */
786 KillTimer(hwnd, TAB_HOTTRACK_TIMER);
791 /******************************************************************************
794 * If a tab control has the TCS_HOTTRACK style, then the tab under the mouse
795 * should be highlighted. This function determines which tab in a tab control,
796 * if any, is under the mouse and records that information. The caller may
797 * supply output parameters to receive the item number of the tab item which
798 * was highlighted but isn't any longer and of the tab item which is now
799 * highlighted but wasn't previously. The caller can use this information to
800 * selectively redraw those tab items.
802 * If the caller has a mouse position, it can supply it through the pos
803 * parameter. For example, TAB_MouseMove does this. Otherwise, the caller
804 * supplies NULL and this function determines the current mouse position
812 int* out_redrawLeave,
819 if (out_redrawLeave != NULL)
820 *out_redrawLeave = -1;
821 if (out_redrawEnter != NULL)
822 *out_redrawEnter = -1;
824 if ((infoPtr->dwStyle & TCS_HOTTRACK) || GetWindowTheme(infoPtr->hwnd))
832 ScreenToClient(infoPtr->hwnd, &pt);
836 pt.x = (short)LOWORD(*pos);
837 pt.y = (short)HIWORD(*pos);
840 item = TAB_InternalHitTest(infoPtr, pt, &flags);
843 if (item != infoPtr->iHotTracked)
845 if (infoPtr->iHotTracked >= 0)
847 /* Mark currently hot-tracked to be redrawn to look normal */
848 if (out_redrawLeave != NULL)
849 *out_redrawLeave = infoPtr->iHotTracked;
853 /* Kill timer which forces recheck of mouse pos */
854 KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
859 /* Start timer so we recheck mouse pos */
860 UINT timerID = SetTimer
864 TAB_HOTTRACK_TIMER_INTERVAL,
865 TAB_HotTrackTimerProc
869 return; /* Hot tracking not available */
872 infoPtr->iHotTracked = item;
876 /* Mark new hot-tracked to be redrawn to look highlighted */
877 if (out_redrawEnter != NULL)
878 *out_redrawEnter = item;
883 /******************************************************************************
886 * Handles the mouse-move event. Updates tooltips. Updates hot-tracking.
889 TAB_MouseMove (TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
894 if (infoPtr->hwndToolTip)
895 TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
896 WM_LBUTTONDOWN, wParam, lParam);
898 /* Determine which tab to highlight. Redraw tabs which change highlight
900 TAB_RecalcHotTrack(infoPtr, &lParam, &redrawLeave, &redrawEnter);
902 hottrack_refresh (infoPtr, redrawLeave);
903 hottrack_refresh (infoPtr, redrawEnter);
908 /******************************************************************************
911 * Calculates the tab control's display area given the window rectangle or
912 * the window rectangle given the requested display rectangle.
914 static LRESULT TAB_AdjustRect(const TAB_INFO *infoPtr, WPARAM fLarger, LPRECT prc)
916 LONG *iRightBottom, *iLeftTop;
918 TRACE ("hwnd=%p fLarger=%ld (%s)\n", infoPtr->hwnd, fLarger,
919 wine_dbgstr_rect(prc));
923 if(infoPtr->dwStyle & TCS_VERTICAL)
925 iRightBottom = &(prc->right);
926 iLeftTop = &(prc->left);
930 iRightBottom = &(prc->bottom);
931 iLeftTop = &(prc->top);
934 if (fLarger) /* Go from display rectangle */
936 /* Add the height of the tabs. */
937 if (infoPtr->dwStyle & TCS_BOTTOM)
938 *iRightBottom += infoPtr->tabHeight * infoPtr->uNumRows;
940 *iLeftTop -= infoPtr->tabHeight * infoPtr->uNumRows +
941 ((infoPtr->dwStyle & TCS_BUTTONS)? 3 * (infoPtr->uNumRows - 1) : 0);
943 /* Inflate the rectangle for the padding */
944 InflateRect(prc, DISPLAY_AREA_PADDINGX, DISPLAY_AREA_PADDINGY);
946 /* Inflate for the border */
947 InflateRect(prc, CONTROL_BORDER_SIZEX, CONTROL_BORDER_SIZEY);
949 else /* Go from window rectangle. */
951 /* Deflate the rectangle for the border */
952 InflateRect(prc, -CONTROL_BORDER_SIZEX, -CONTROL_BORDER_SIZEY);
954 /* Deflate the rectangle for the padding */
955 InflateRect(prc, -DISPLAY_AREA_PADDINGX, -DISPLAY_AREA_PADDINGY);
957 /* Remove the height of the tabs. */
958 if (infoPtr->dwStyle & TCS_BOTTOM)
959 *iRightBottom -= infoPtr->tabHeight * infoPtr->uNumRows;
961 *iLeftTop += (infoPtr->tabHeight) * infoPtr->uNumRows +
962 ((infoPtr->dwStyle & TCS_BUTTONS)? 3 * (infoPtr->uNumRows - 1) : 0);
968 /******************************************************************************
971 * This method will handle the notification from the scroll control and
972 * perform the scrolling operation on the tab control.
974 static LRESULT TAB_OnHScroll(TAB_INFO *infoPtr, int nScrollCode, int nPos)
976 if(nScrollCode == SB_THUMBPOSITION && nPos != infoPtr->leftmostVisible)
978 if(nPos < infoPtr->leftmostVisible)
979 infoPtr->leftmostVisible--;
981 infoPtr->leftmostVisible++;
983 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
984 TAB_InvalidateTabArea(infoPtr);
985 SendMessageW(infoPtr->hwndUpDown, UDM_SETPOS, 0,
986 MAKELONG(infoPtr->leftmostVisible, 0));
992 /******************************************************************************
995 * This method will check the current scrolling state and make sure the
996 * scrolling control is displayed (or not).
998 static void TAB_SetupScrolling(
1000 const RECT* clientRect)
1002 static const WCHAR emptyW[] = { 0 };
1005 if (infoPtr->needsScrolling)
1008 INT vsize, tabwidth;
1011 * Calculate the position of the scroll control.
1013 if(infoPtr->dwStyle & TCS_VERTICAL)
1015 controlPos.right = clientRect->right;
1016 controlPos.left = controlPos.right - 2 * GetSystemMetrics(SM_CXHSCROLL);
1018 if (infoPtr->dwStyle & TCS_BOTTOM)
1020 controlPos.top = clientRect->bottom - infoPtr->tabHeight;
1021 controlPos.bottom = controlPos.top + GetSystemMetrics(SM_CYHSCROLL);
1025 controlPos.bottom = clientRect->top + infoPtr->tabHeight;
1026 controlPos.top = controlPos.bottom - GetSystemMetrics(SM_CYHSCROLL);
1031 controlPos.right = clientRect->right;
1032 controlPos.left = controlPos.right - 2 * GetSystemMetrics(SM_CXHSCROLL);
1034 if (infoPtr->dwStyle & TCS_BOTTOM)
1036 controlPos.top = clientRect->bottom - infoPtr->tabHeight;
1037 controlPos.bottom = controlPos.top + GetSystemMetrics(SM_CYHSCROLL);
1041 controlPos.bottom = clientRect->top + infoPtr->tabHeight;
1042 controlPos.top = controlPos.bottom - GetSystemMetrics(SM_CYHSCROLL);
1047 * If we don't have a scroll control yet, we want to create one.
1048 * If we have one, we want to make sure it's positioned properly.
1050 if (infoPtr->hwndUpDown==0)
1052 infoPtr->hwndUpDown = CreateWindowW(UPDOWN_CLASSW, emptyW,
1053 WS_VISIBLE | WS_CHILD | UDS_HORZ,
1054 controlPos.left, controlPos.top,
1055 controlPos.right - controlPos.left,
1056 controlPos.bottom - controlPos.top,
1057 infoPtr->hwnd, NULL, NULL, NULL);
1061 SetWindowPos(infoPtr->hwndUpDown,
1063 controlPos.left, controlPos.top,
1064 controlPos.right - controlPos.left,
1065 controlPos.bottom - controlPos.top,
1066 SWP_SHOWWINDOW | SWP_NOZORDER);
1069 /* Now calculate upper limit of the updown control range.
1070 * We do this by calculating how many tabs will be offscreen when the
1071 * last tab is visible.
1073 if(infoPtr->uNumItem)
1075 vsize = clientRect->right - (controlPos.right - controlPos.left + 1);
1076 maxRange = infoPtr->uNumItem;
1077 tabwidth = TAB_GetItem(infoPtr, infoPtr->uNumItem - 1)->rect.right;
1079 for(; maxRange > 0; maxRange--)
1081 if(tabwidth - TAB_GetItem(infoPtr,maxRange - 1)->rect.left > vsize)
1085 if(maxRange == infoPtr->uNumItem)
1091 /* If we once had a scroll control... hide it */
1092 if (infoPtr->hwndUpDown)
1093 ShowWindow(infoPtr->hwndUpDown, SW_HIDE);
1095 if (infoPtr->hwndUpDown)
1096 SendMessageW(infoPtr->hwndUpDown, UDM_SETRANGE32, 0, maxRange);
1099 /******************************************************************************
1102 * This method will calculate the position rectangles of all the items in the
1103 * control. The rectangle calculated starts at 0 for the first item in the
1104 * list and ignores scrolling and selection.
1105 * It also uses the current font to determine the height of the tab row and
1106 * it checks if all the tabs fit in the client area of the window. If they
1107 * don't, a scrolling control is added.
1109 static void TAB_SetItemBounds (TAB_INFO *infoPtr)
1111 TEXTMETRICW fontMetrics;
1114 INT curItemRowCount;
1115 HFONT hFont, hOldFont;
1124 * We need to get text information so we need a DC and we need to select
1127 hdc = GetDC(infoPtr->hwnd);
1129 hFont = infoPtr->hFont ? infoPtr->hFont : GetStockObject (SYSTEM_FONT);
1130 hOldFont = SelectObject (hdc, hFont);
1133 * We will base the rectangle calculations on the client rectangle
1136 GetClientRect(infoPtr->hwnd, &clientRect);
1138 /* if TCS_VERTICAL then swap the height and width so this code places the
1139 tabs along the top of the rectangle and we can just rotate them after
1140 rather than duplicate all of the below code */
1141 if(infoPtr->dwStyle & TCS_VERTICAL)
1143 iTemp = clientRect.bottom;
1144 clientRect.bottom = clientRect.right;
1145 clientRect.right = iTemp;
1148 /* Now use hPadding and vPadding */
1149 infoPtr->uHItemPadding = infoPtr->uHItemPadding_s;
1150 infoPtr->uVItemPadding = infoPtr->uVItemPadding_s;
1152 /* The leftmost item will be "0" aligned */
1154 curItemRowCount = infoPtr->uNumItem ? 1 : 0;
1156 if (!(infoPtr->fHeightSet))
1159 int icon_height = 0;
1161 /* Use the current font to determine the height of a tab. */
1162 GetTextMetricsW(hdc, &fontMetrics);
1164 /* Get the icon height */
1166 ImageList_GetIconSize(infoPtr->himl, 0, &icon_height);
1168 /* Take the highest between font or icon */
1169 if (fontMetrics.tmHeight > icon_height)
1170 item_height = fontMetrics.tmHeight + 2;
1172 item_height = icon_height;
1175 * Make sure there is enough space for the letters + icon + growing the
1176 * selected item + extra space for the selected item.
1178 infoPtr->tabHeight = item_height +
1179 ((infoPtr->dwStyle & TCS_BUTTONS) ? 2 : 1) *
1180 infoPtr->uVItemPadding;
1182 TRACE("tabH=%d, tmH=%d, iconh=%d\n",
1183 infoPtr->tabHeight, fontMetrics.tmHeight, icon_height);
1186 TRACE("client right=%d\n", clientRect.right);
1188 /* Get the icon width */
1191 ImageList_GetIconSize(infoPtr->himl, &icon_width, 0);
1193 if (infoPtr->dwStyle & TCS_FIXEDWIDTH)
1196 /* Add padding if icon is present */
1197 icon_width += infoPtr->uHItemPadding;
1200 for (curItem = 0; curItem < infoPtr->uNumItem; curItem++)
1202 TAB_ITEM *curr = TAB_GetItem(infoPtr, curItem);
1204 /* Set the leftmost position of the tab. */
1205 curr->rect.left = curItemLeftPos;
1207 if (infoPtr->dwStyle & TCS_FIXEDWIDTH)
1209 curr->rect.right = curr->rect.left +
1210 max(infoPtr->tabWidth, icon_width);
1212 else if (!curr->pszText)
1214 /* If no text use minimum tab width including padding. */
1215 if (infoPtr->tabMinWidth < 0)
1216 curr->rect.right = curr->rect.left + GET_DEFAULT_MIN_TAB_WIDTH(infoPtr);
1219 curr->rect.right = curr->rect.left + infoPtr->tabMinWidth;
1221 /* Add extra padding if icon is present */
1222 if (infoPtr->himl && infoPtr->tabMinWidth > 0 && infoPtr->tabMinWidth < DEFAULT_MIN_TAB_WIDTH
1223 && infoPtr->uHItemPadding > 1)
1224 curr->rect.right += EXTRA_ICON_PADDING * (infoPtr->uHItemPadding-1);
1231 /* Calculate how wide the tab is depending on the text it contains */
1232 GetTextExtentPoint32W(hdc, curr->pszText,
1233 lstrlenW(curr->pszText), &size);
1235 tabwidth = size.cx + icon_width + 2 * infoPtr->uHItemPadding;
1237 if (infoPtr->tabMinWidth < 0)
1238 tabwidth = max(tabwidth, GET_DEFAULT_MIN_TAB_WIDTH(infoPtr));
1240 tabwidth = max(tabwidth, infoPtr->tabMinWidth);
1242 curr->rect.right = curr->rect.left + tabwidth;
1243 TRACE("for <%s>, l,r=%d,%d\n",
1244 debugstr_w(curr->pszText), curr->rect.left, curr->rect.right);
1248 * Check if this is a multiline tab control and if so
1249 * check to see if we should wrap the tabs
1251 * Wrap all these tabs. We will arrange them evenly later.
1255 if (((infoPtr->dwStyle & TCS_MULTILINE) || (infoPtr->dwStyle & TCS_VERTICAL)) &&
1257 (clientRect.right - CONTROL_BORDER_SIZEX - DISPLAY_AREA_PADDINGX)))
1259 curr->rect.right -= curr->rect.left;
1261 curr->rect.left = 0;
1263 TRACE("wrapping <%s>, l,r=%d,%d\n", debugstr_w(curr->pszText),
1264 curr->rect.left, curr->rect.right);
1267 curr->rect.bottom = 0;
1268 curr->rect.top = curItemRowCount - 1;
1270 TRACE("Rect: %s\n", wine_dbgstr_rect(&curr->rect));
1273 * The leftmost position of the next item is the rightmost position
1276 if (infoPtr->dwStyle & TCS_BUTTONS)
1278 curItemLeftPos = curr->rect.right + BUTTON_SPACINGX;
1279 if (infoPtr->dwStyle & TCS_FLATBUTTONS)
1280 curItemLeftPos += FLAT_BTN_SPACINGX;
1283 curItemLeftPos = curr->rect.right;
1286 if (!((infoPtr->dwStyle & TCS_MULTILINE) || (infoPtr->dwStyle & TCS_VERTICAL)))
1289 * Check if we need a scrolling control.
1291 infoPtr->needsScrolling = (curItemLeftPos + (2 * SELECTED_TAB_OFFSET) >
1294 /* Don't need scrolling, then update infoPtr->leftmostVisible */
1295 if(!infoPtr->needsScrolling)
1296 infoPtr->leftmostVisible = 0;
1301 * No scrolling in Multiline or Vertical styles.
1303 infoPtr->needsScrolling = FALSE;
1304 infoPtr->leftmostVisible = 0;
1306 TAB_SetupScrolling(infoPtr, &clientRect);
1308 /* Set the number of rows */
1309 infoPtr->uNumRows = curItemRowCount;
1311 /* Arrange all tabs evenly if style says so */
1312 if (!(infoPtr->dwStyle & TCS_RAGGEDRIGHT) &&
1313 ((infoPtr->dwStyle & TCS_MULTILINE) || (infoPtr->dwStyle & TCS_VERTICAL)) &&
1314 (infoPtr->uNumItem > 0) &&
1315 (infoPtr->uNumRows > 1))
1317 INT tabPerRow,remTab,iRow;
1322 * Ok windows tries to even out the rows. place the same
1323 * number of tabs in each row. So lets give that a shot
1326 tabPerRow = infoPtr->uNumItem / (infoPtr->uNumRows);
1327 remTab = infoPtr->uNumItem % (infoPtr->uNumRows);
1329 for (iItm=0,iRow=0,iCount=0,curItemLeftPos=0;
1330 iItm<infoPtr->uNumItem;
1333 /* normalize the current rect */
1334 TAB_ITEM *curr = TAB_GetItem(infoPtr, iItm);
1336 /* shift the item to the left side of the clientRect */
1337 curr->rect.right -= curr->rect.left;
1338 curr->rect.left = 0;
1340 TRACE("r=%d, cl=%d, cl.r=%d, iCount=%d, iRow=%d, uNumRows=%d, remTab=%d, tabPerRow=%d\n",
1341 curr->rect.right, curItemLeftPos, clientRect.right,
1342 iCount, iRow, infoPtr->uNumRows, remTab, tabPerRow);
1344 /* if we have reached the maximum number of tabs on this row */
1345 /* move to the next row, reset our current item left position and */
1346 /* the count of items on this row */
1348 if (infoPtr->dwStyle & TCS_VERTICAL) {
1349 /* Vert: Add the remaining tabs in the *last* remainder rows */
1350 if (iCount >= ((iRow>=(INT)infoPtr->uNumRows - remTab)?tabPerRow + 1:tabPerRow)) {
1356 /* Horz: Add the remaining tabs in the *first* remainder rows */
1357 if (iCount >= ((iRow<remTab)?tabPerRow + 1:tabPerRow)) {
1364 /* shift the item to the right to place it as the next item in this row */
1365 curr->rect.left += curItemLeftPos;
1366 curr->rect.right += curItemLeftPos;
1367 curr->rect.top = iRow;
1368 if (infoPtr->dwStyle & TCS_BUTTONS)
1370 curItemLeftPos = curr->rect.right + 1;
1371 if (infoPtr->dwStyle & TCS_FLATBUTTONS)
1372 curItemLeftPos += FLAT_BTN_SPACINGX;
1375 curItemLeftPos = curr->rect.right;
1377 TRACE("arranging <%s>, l,r=%d,%d, row=%d\n",
1378 debugstr_w(curr->pszText), curr->rect.left,
1379 curr->rect.right, curr->rect.top);
1386 INT widthDiff, iIndexStart=0, iIndexEnd=0;
1390 while(iIndexStart < infoPtr->uNumItem)
1392 TAB_ITEM *start = TAB_GetItem(infoPtr, iIndexStart);
1395 * find the index of the row
1397 /* find the first item on the next row */
1398 for (iIndexEnd=iIndexStart;
1399 (iIndexEnd < infoPtr->uNumItem) &&
1400 (TAB_GetItem(infoPtr, iIndexEnd)->rect.top ==
1403 /* intentionally blank */;
1406 * we need to justify these tabs so they fill the whole given
1410 /* find the amount of space remaining on this row */
1411 widthDiff = clientRect.right - (2 * SELECTED_TAB_OFFSET) -
1412 TAB_GetItem(infoPtr, iIndexEnd - 1)->rect.right;
1414 /* iCount is the number of tab items on this row */
1415 iCount = iIndexEnd - iIndexStart;
1419 remainder = widthDiff % iCount;
1420 widthDiff = widthDiff / iCount;
1421 /* add widthDiff/iCount, or extra space/items on row, to each item on this row */
1422 for (iIndex=iIndexStart, iCount=0; iIndex < iIndexEnd; iIndex++, iCount++)
1424 TAB_ITEM *item = TAB_GetItem(infoPtr, iIndex);
1426 item->rect.left += iCount * widthDiff;
1427 item->rect.right += (iCount + 1) * widthDiff;
1429 TRACE("adjusting 1 <%s>, l,r=%d,%d\n",
1430 debugstr_w(item->pszText),
1431 item->rect.left, item->rect.right);
1434 TAB_GetItem(infoPtr, iIndex - 1)->rect.right += remainder;
1436 else /* we have only one item on this row, make it take up the entire row */
1438 start->rect.left = clientRect.left;
1439 start->rect.right = clientRect.right - 4;
1441 TRACE("adjusting 2 <%s>, l,r=%d,%d\n",
1442 debugstr_w(start->pszText),
1443 start->rect.left, start->rect.right);
1448 iIndexStart = iIndexEnd;
1453 /* if TCS_VERTICAL rotate the tabs so they are along the side of the clientRect */
1454 if(infoPtr->dwStyle & TCS_VERTICAL)
1457 for(iIndex = 0; iIndex < infoPtr->uNumItem; iIndex++)
1459 rcItem = &TAB_GetItem(infoPtr, iIndex)->rect;
1461 rcOriginal = *rcItem;
1463 /* this is rotating the items by 90 degrees clockwise around the center of the control */
1464 rcItem->top = (rcOriginal.left - clientRect.left);
1465 rcItem->bottom = rcItem->top + (rcOriginal.right - rcOriginal.left);
1466 rcItem->left = rcOriginal.top;
1467 rcItem->right = rcOriginal.bottom;
1471 TAB_EnsureSelectionVisible(infoPtr);
1472 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
1475 SelectObject (hdc, hOldFont);
1476 ReleaseDC (infoPtr->hwnd, hdc);
1481 TAB_EraseTabInterior(const TAB_INFO *infoPtr, HDC hdc, INT iItem, const RECT *drawRect)
1483 HBRUSH hbr = CreateSolidBrush (comctl32_color.clrBtnFace);
1484 BOOL deleteBrush = TRUE;
1485 RECT rTemp = *drawRect;
1487 if (infoPtr->dwStyle & TCS_BUTTONS)
1489 if (iItem == infoPtr->iSelected)
1491 /* Background color */
1492 if (!(infoPtr->dwStyle & TCS_OWNERDRAWFIXED))
1495 hbr = GetSysColorBrush(COLOR_SCROLLBAR);
1497 SetTextColor(hdc, comctl32_color.clr3dFace);
1498 SetBkColor(hdc, comctl32_color.clr3dHilight);
1500 /* if COLOR_WINDOW happens to be the same as COLOR_3DHILIGHT
1501 * we better use 0x55aa bitmap brush to make scrollbar's background
1502 * look different from the window background.
1504 if (comctl32_color.clr3dHilight == comctl32_color.clrWindow)
1505 hbr = COMCTL32_hPattern55AABrush;
1507 deleteBrush = FALSE;
1509 FillRect(hdc, &rTemp, hbr);
1511 else /* ! selected */
1513 if (infoPtr->dwStyle & TCS_FLATBUTTONS)
1515 InflateRect(&rTemp, 2, 2);
1516 FillRect(hdc, &rTemp, hbr);
1517 if (iItem == infoPtr->iHotTracked ||
1518 (iItem != infoPtr->iSelected && iItem == infoPtr->uFocus))
1519 DrawEdge(hdc, &rTemp, BDR_RAISEDINNER, BF_RECT);
1522 FillRect(hdc, &rTemp, hbr);
1526 else /* !TCS_BUTTONS */
1528 InflateRect(&rTemp, -2, -2);
1529 if (!GetWindowTheme (infoPtr->hwnd))
1530 FillRect(hdc, &rTemp, hbr);
1533 /* highlighting is drawn on top of previous fills */
1534 if (TAB_GetItem(infoPtr, iItem)->dwState & TCIS_HIGHLIGHTED)
1539 deleteBrush = FALSE;
1541 hbr = GetSysColorBrush(COLOR_HIGHLIGHT);
1542 FillRect(hdc, &rTemp, hbr);
1546 if (deleteBrush) DeleteObject(hbr);
1549 /******************************************************************************
1550 * TAB_DrawItemInterior
1552 * This method is used to draw the interior (text and icon) of a single tab
1553 * into the tab control.
1556 TAB_DrawItemInterior(const TAB_INFO *infoPtr, HDC hdc, INT iItem, RECT *drawRect)
1565 /* if (drawRect == NULL) */
1572 * Get the rectangle for the item.
1574 isVisible = TAB_InternalGetItemRect(infoPtr, iItem, &itemRect, &selectedRect);
1579 * Make sure drawRect points to something valid; simplifies code.
1581 drawRect = &localRect;
1584 * This logic copied from the part of TAB_DrawItem which draws
1585 * the tab background. It's important to keep it in sync. I
1586 * would have liked to avoid code duplication, but couldn't figure
1587 * out how without making spaghetti of TAB_DrawItem.
1589 if (iItem == infoPtr->iSelected)
1590 *drawRect = selectedRect;
1592 *drawRect = itemRect;
1594 if (infoPtr->dwStyle & TCS_BUTTONS)
1596 if (iItem == infoPtr->iSelected)
1598 drawRect->left += 4;
1600 drawRect->right -= 4;
1602 if (infoPtr->dwStyle & TCS_VERTICAL)
1604 if (!(infoPtr->dwStyle & TCS_BOTTOM)) drawRect->right += 1;
1605 drawRect->bottom -= 4;
1609 if (infoPtr->dwStyle & TCS_BOTTOM)
1612 drawRect->bottom -= 4;
1615 drawRect->bottom -= 1;
1620 drawRect->left += 2;
1622 drawRect->right -= 2;
1623 drawRect->bottom -= 2;
1628 if ((infoPtr->dwStyle & TCS_VERTICAL) && (infoPtr->dwStyle & TCS_BOTTOM))
1630 if (iItem != infoPtr->iSelected)
1632 drawRect->left += 2;
1634 drawRect->bottom -= 2;
1637 else if (infoPtr->dwStyle & TCS_VERTICAL)
1639 if (iItem == infoPtr->iSelected)
1641 drawRect->right += 1;
1646 drawRect->right -= 2;
1647 drawRect->bottom -= 2;
1650 else if (infoPtr->dwStyle & TCS_BOTTOM)
1652 if (iItem == infoPtr->iSelected)
1658 InflateRect(drawRect, -2, -2);
1659 drawRect->bottom += 2;
1664 if (iItem == infoPtr->iSelected)
1666 drawRect->bottom += 3;
1670 drawRect->bottom -= 2;
1671 InflateRect(drawRect, -2, 0);
1676 TRACE("drawRect=(%s)\n", wine_dbgstr_rect(drawRect));
1678 /* Clear interior */
1679 TAB_EraseTabInterior (infoPtr, hdc, iItem, drawRect);
1681 /* Draw the focus rectangle */
1682 if (!(infoPtr->dwStyle & TCS_FOCUSNEVER) &&
1683 (GetFocus() == infoPtr->hwnd) &&
1684 (iItem == infoPtr->uFocus) )
1686 RECT rFocus = *drawRect;
1688 if (!(infoPtr->dwStyle & TCS_BUTTONS)) InflateRect(&rFocus, -3, -3);
1689 if (infoPtr->dwStyle & TCS_BOTTOM && !(infoPtr->dwStyle & TCS_VERTICAL))
1692 /* focus should stay on selected item for TCS_BUTTONS style */
1693 if (!((infoPtr->dwStyle & TCS_BUTTONS) && (infoPtr->iSelected != iItem)))
1694 DrawFocusRect(hdc, &rFocus);
1700 htextPen = CreatePen( PS_SOLID, 1, comctl32_color.clrBtnText );
1701 holdPen = SelectObject(hdc, htextPen);
1702 hOldFont = SelectObject(hdc, infoPtr->hFont);
1705 * Setup for text output
1707 oldBkMode = SetBkMode(hdc, TRANSPARENT);
1708 if (!GetWindowTheme (infoPtr->hwnd) || (infoPtr->dwStyle & TCS_BUTTONS))
1710 if ((infoPtr->dwStyle & TCS_HOTTRACK) && (iItem == infoPtr->iHotTracked) &&
1711 !(infoPtr->dwStyle & TCS_FLATBUTTONS))
1712 SetTextColor(hdc, comctl32_color.clrHighlight);
1713 else if (TAB_GetItem(infoPtr, iItem)->dwState & TCIS_HIGHLIGHTED)
1714 SetTextColor(hdc, comctl32_color.clrHighlightText);
1716 SetTextColor(hdc, comctl32_color.clrBtnText);
1720 * if owner draw, tell the owner to draw
1722 if ((infoPtr->dwStyle & TCS_OWNERDRAWFIXED) && GetParent(infoPtr->hwnd))
1728 drawRect->right -= 1;
1729 if ( iItem == infoPtr->iSelected )
1731 drawRect->right -= 1;
1732 drawRect->left += 1;
1736 * get the control id
1738 id = (UINT)GetWindowLongPtrW( infoPtr->hwnd, GWLP_ID );
1741 * put together the DRAWITEMSTRUCT
1743 dis.CtlType = ODT_TAB;
1746 dis.itemAction = ODA_DRAWENTIRE;
1748 if ( iItem == infoPtr->iSelected )
1749 dis.itemState |= ODS_SELECTED;
1750 if (infoPtr->uFocus == iItem)
1751 dis.itemState |= ODS_FOCUS;
1752 dis.hwndItem = infoPtr->hwnd;
1754 CopyRect(&dis.rcItem,drawRect);
1755 dis.itemData = (ULONG_PTR)TAB_GetItem(infoPtr, iItem)->extra;
1758 * send the draw message
1760 SendMessageW( infoPtr->hwndNotify, WM_DRAWITEM, (WPARAM)id, (LPARAM)&dis );
1764 TAB_ITEM *item = TAB_GetItem(infoPtr, iItem);
1768 /* used to center the icon and text in the tab */
1770 INT center_offset_h, center_offset_v;
1772 /* set rcImage to drawRect, we will use top & left in our ImageList_Draw call */
1773 rcImage = *drawRect;
1777 rcText.left = rcText.top = rcText.right = rcText.bottom = 0;
1779 /* get the rectangle that the text fits in */
1782 DrawTextW(hdc, item->pszText, -1, &rcText, DT_CALCRECT);
1785 * If not owner draw, then do the drawing ourselves.
1789 if (infoPtr->himl && item->iImage != -1)
1794 ImageList_GetIconSize(infoPtr->himl, &cx, &cy);
1796 if(infoPtr->dwStyle & TCS_VERTICAL)
1798 center_offset_h = ((drawRect->bottom - drawRect->top) - (cy + infoPtr->uHItemPadding + (rcText.right - rcText.left))) / 2;
1799 center_offset_v = ((drawRect->right - drawRect->left) - cx) / 2;
1803 center_offset_h = ((drawRect->right - drawRect->left) - (cx + infoPtr->uHItemPadding + (rcText.right - rcText.left))) / 2;
1804 center_offset_v = ((drawRect->bottom - drawRect->top) - cy) / 2;
1807 /* if an item is selected, the icon is shifted up instead of down */
1808 if (iItem == infoPtr->iSelected)
1809 center_offset_v -= infoPtr->uVItemPadding / 2;
1811 center_offset_v += infoPtr->uVItemPadding / 2;
1813 if (infoPtr->dwStyle & TCS_FIXEDWIDTH && infoPtr->dwStyle & (TCS_FORCELABELLEFT | TCS_FORCEICONLEFT))
1814 center_offset_h = infoPtr->uHItemPadding;
1816 if (center_offset_h < 2)
1817 center_offset_h = 2;
1819 if (center_offset_v < 0)
1820 center_offset_v = 0;
1822 TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%s), textlen=%d\n",
1823 debugstr_w(item->pszText), center_offset_h, center_offset_v,
1824 wine_dbgstr_rect(drawRect), (rcText.right-rcText.left));
1826 if((infoPtr->dwStyle & TCS_VERTICAL) && (infoPtr->dwStyle & TCS_BOTTOM))
1828 rcImage.top = drawRect->top + center_offset_h;
1829 /* if tab is TCS_VERTICAL and TCS_BOTTOM, the text is drawn from the */
1830 /* right side of the tab, but the image still uses the left as its x position */
1831 /* this keeps the image always drawn off of the same side of the tab */
1832 rcImage.left = drawRect->right - cx - center_offset_v;
1833 drawRect->top += cy + infoPtr->uHItemPadding;
1835 else if(infoPtr->dwStyle & TCS_VERTICAL)
1837 rcImage.top = drawRect->bottom - cy - center_offset_h;
1838 rcImage.left = drawRect->left + center_offset_v;
1839 drawRect->bottom -= cy + infoPtr->uHItemPadding;
1841 else /* normal style, whether TCS_BOTTOM or not */
1843 rcImage.left = drawRect->left + center_offset_h;
1844 rcImage.top = drawRect->top + center_offset_v;
1845 drawRect->left += cx + infoPtr->uHItemPadding;
1848 TRACE("drawing image=%d, left=%d, top=%d\n",
1849 item->iImage, rcImage.left, rcImage.top-1);
1861 /* Now position text */
1862 if (infoPtr->dwStyle & TCS_FIXEDWIDTH && infoPtr->dwStyle & TCS_FORCELABELLEFT)
1863 center_offset_h = infoPtr->uHItemPadding;
1865 if(infoPtr->dwStyle & TCS_VERTICAL)
1866 center_offset_h = ((drawRect->bottom - drawRect->top) - (rcText.right - rcText.left)) / 2;
1868 center_offset_h = ((drawRect->right - drawRect->left) - (rcText.right - rcText.left)) / 2;
1870 if(infoPtr->dwStyle & TCS_VERTICAL)
1872 if(infoPtr->dwStyle & TCS_BOTTOM)
1873 drawRect->top+=center_offset_h;
1875 drawRect->bottom-=center_offset_h;
1877 center_offset_v = ((drawRect->right - drawRect->left) - (rcText.bottom - rcText.top)) / 2;
1881 drawRect->left += center_offset_h;
1882 center_offset_v = ((drawRect->bottom - drawRect->top) - (rcText.bottom - rcText.top)) / 2;
1885 /* if an item is selected, the text is shifted up instead of down */
1886 if (iItem == infoPtr->iSelected)
1887 center_offset_v -= infoPtr->uVItemPadding / 2;
1889 center_offset_v += infoPtr->uVItemPadding / 2;
1891 if (center_offset_v < 0)
1892 center_offset_v = 0;
1894 if(infoPtr->dwStyle & TCS_VERTICAL)
1895 drawRect->left += center_offset_v;
1897 drawRect->top += center_offset_v;
1900 if(infoPtr->dwStyle & TCS_VERTICAL) /* if we are vertical rotate the text and each character */
1902 static const WCHAR ArialW[] = { 'A','r','i','a','l',0 };
1905 INT nEscapement = 900;
1906 INT nOrientation = 900;
1908 if(infoPtr->dwStyle & TCS_BOTTOM)
1911 nOrientation = -900;
1914 /* to get a font with the escapement and orientation we are looking for, we need to */
1915 /* call CreateFontIndirectA, which requires us to set the values of the logfont we pass in */
1916 if (!GetObjectW((infoPtr->hFont) ?
1917 infoPtr->hFont : GetStockObject(SYSTEM_FONT),
1918 sizeof(LOGFONTW),&logfont))
1922 lstrcpyW(logfont.lfFaceName, ArialW);
1923 logfont.lfHeight = -MulDiv(iPointSize, GetDeviceCaps(hdc, LOGPIXELSY),
1925 logfont.lfWeight = FW_NORMAL;
1926 logfont.lfItalic = 0;
1927 logfont.lfUnderline = 0;
1928 logfont.lfStrikeOut = 0;
1931 logfont.lfEscapement = nEscapement;
1932 logfont.lfOrientation = nOrientation;
1933 hFont = CreateFontIndirectW(&logfont);
1934 SelectObject(hdc, hFont);
1939 (infoPtr->dwStyle & TCS_BOTTOM) ? drawRect->right : drawRect->left,
1940 (!(infoPtr->dwStyle & TCS_BOTTOM)) ? drawRect->bottom : drawRect->top,
1944 lstrlenW(item->pszText),
1948 DeleteObject(hFont);
1952 TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%s), textlen=%d\n",
1953 debugstr_w(item->pszText), center_offset_h, center_offset_v,
1954 wine_dbgstr_rect(drawRect), (rcText.right-rcText.left));
1961 lstrlenW(item->pszText),
1963 DT_LEFT | DT_SINGLELINE
1968 *drawRect = rcTemp; /* restore drawRect */
1974 SelectObject(hdc, hOldFont);
1975 SetBkMode(hdc, oldBkMode);
1976 SelectObject(hdc, holdPen);
1977 DeleteObject( htextPen );
1980 /******************************************************************************
1983 * This method is used to draw a single tab into the tab control.
1985 static void TAB_DrawItem(const TAB_INFO *infoPtr, HDC hdc, INT iItem)
1990 RECT r, fillRect, r1;
1993 COLORREF bkgnd, corner;
1997 * Get the rectangle for the item.
1999 isVisible = TAB_InternalGetItemRect(infoPtr,
2008 /* Clip UpDown control to not draw over it */
2009 if (infoPtr->needsScrolling)
2011 GetWindowRect(infoPtr->hwnd, &rC);
2012 GetWindowRect(infoPtr->hwndUpDown, &rUD);
2013 ExcludeClipRect(hdc, rUD.left - rC.left, rUD.top - rC.top, rUD.right - rC.left, rUD.bottom - rC.top);
2016 /* If you need to see what the control is doing,
2017 * then override these variables. They will change what
2018 * fill colors are used for filling the tabs, and the
2019 * corners when drawing the edge.
2021 bkgnd = comctl32_color.clrBtnFace;
2022 corner = comctl32_color.clrBtnFace;
2024 if (infoPtr->dwStyle & TCS_BUTTONS)
2026 /* Get item rectangle */
2029 /* Separators between flat buttons */
2030 if ((infoPtr->dwStyle & TCS_FLATBUTTONS) && (infoPtr->exStyle & TCS_EX_FLATSEPARATORS))
2033 r1.right += (FLAT_BTN_SPACINGX -2);
2034 DrawEdge(hdc, &r1, EDGE_ETCHED, BF_RIGHT);
2037 if (iItem == infoPtr->iSelected)
2039 DrawEdge(hdc, &r, EDGE_SUNKEN, BF_SOFT|BF_RECT);
2041 OffsetRect(&r, 1, 1);
2043 else /* ! selected */
2045 DWORD state = TAB_GetItem(infoPtr, iItem)->dwState;
2047 if ((state & TCIS_BUTTONPRESSED) || (iItem == infoPtr->uFocus))
2048 DrawEdge(hdc, &r, EDGE_SUNKEN, BF_SOFT|BF_RECT);
2050 if (!(infoPtr->dwStyle & TCS_FLATBUTTONS))
2051 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_RECT);
2054 else /* !TCS_BUTTONS */
2056 /* We draw a rectangle of different sizes depending on the selection
2058 if (iItem == infoPtr->iSelected) {
2060 GetClientRect (infoPtr->hwnd, &rect);
2061 clRight = rect.right;
2062 clBottom = rect.bottom;
2069 * Erase the background. (Delay it but setup rectangle.)
2070 * This is necessary when drawing the selected item since it is larger
2071 * than the others, it might overlap with stuff already drawn by the
2076 /* Draw themed tabs - but only if they are at the top.
2077 * Windows draws even side or bottom tabs themed, with wacky results.
2078 * However, since in Wine apps may get themed that did not opt in via
2079 * a manifest avoid theming when we know the result will be wrong */
2080 if ((theme = GetWindowTheme (infoPtr->hwnd))
2081 && ((infoPtr->dwStyle & (TCS_VERTICAL | TCS_BOTTOM)) == 0))
2083 static const int partIds[8] = {
2086 TABP_TABITEMLEFTEDGE,
2087 TABP_TABITEMRIGHTEDGE,
2088 TABP_TABITEMBOTHEDGE,
2091 TABP_TOPTABITEMLEFTEDGE,
2092 TABP_TOPTABITEMRIGHTEDGE,
2093 TABP_TOPTABITEMBOTHEDGE,
2096 int stateId = TIS_NORMAL;
2098 /* selected and unselected tabs have different parts */
2099 if (iItem == infoPtr->iSelected)
2101 /* The part also differs on the position of a tab on a line.
2102 * "Visually" determining the position works well enough. */
2103 if(selectedRect.left == 0)
2105 if(selectedRect.right == clRight)
2108 if (iItem == infoPtr->iSelected)
2109 stateId = TIS_SELECTED;
2110 else if (iItem == infoPtr->iHotTracked)
2112 else if (iItem == infoPtr->uFocus)
2113 stateId = TIS_FOCUSED;
2115 /* Adjust rectangle for bottommost row */
2116 if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2119 DrawThemeBackground (theme, hdc, partIds[partIndex], stateId, &r, NULL);
2120 GetThemeBackgroundContentRect (theme, hdc, partIds[partIndex], stateId, &r, &r);
2122 else if(infoPtr->dwStyle & TCS_VERTICAL)
2124 /* These are for adjusting the drawing of a Selected tab */
2125 /* The initial values are for the normal case of non-Selected */
2126 int ZZ = 1; /* Do not stretch if selected */
2127 if (iItem == infoPtr->iSelected) {
2130 /* if leftmost draw the line longer */
2131 if(selectedRect.top == 0)
2132 fillRect.top += CONTROL_BORDER_SIZEY;
2133 /* if rightmost draw the line longer */
2134 if(selectedRect.bottom == clBottom)
2135 fillRect.bottom -= CONTROL_BORDER_SIZEY;
2138 if (infoPtr->dwStyle & TCS_BOTTOM)
2140 /* Adjust both rectangles to match native */
2143 TRACE("<right> item=%d, fill=(%s), edge=(%s)\n",
2144 iItem, wine_dbgstr_rect(&fillRect), wine_dbgstr_rect(&r));
2146 /* Clear interior */
2147 SetBkColor(hdc, bkgnd);
2148 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2150 /* Draw rectangular edge around tab */
2151 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_RIGHT|BF_TOP|BF_BOTTOM);
2153 /* Now erase the top corner and draw diagonal edge */
2154 SetBkColor(hdc, corner);
2155 r1.left = r.right - ROUND_CORNER_SIZE - 1;
2158 r1.bottom = r1.top + ROUND_CORNER_SIZE;
2159 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2161 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPLEFT);
2163 /* Now erase the bottom corner and draw diagonal edge */
2164 r1.left = r.right - ROUND_CORNER_SIZE - 1;
2165 r1.bottom = r.bottom;
2167 r1.top = r1.bottom - ROUND_CORNER_SIZE;
2168 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2170 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMLEFT);
2172 if ((iItem == infoPtr->iSelected) && (selectedRect.top == 0)) {
2176 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_TOP);
2182 TRACE("<left> item=%d, fill=(%s), edge=(%s)\n",
2183 iItem, wine_dbgstr_rect(&fillRect), wine_dbgstr_rect(&r));
2185 /* Clear interior */
2186 SetBkColor(hdc, bkgnd);
2187 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2189 /* Draw rectangular edge around tab */
2190 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_TOP|BF_BOTTOM);
2192 /* Now erase the top corner and draw diagonal edge */
2193 SetBkColor(hdc, corner);
2196 r1.right = r1.left + ROUND_CORNER_SIZE + 1;
2197 r1.bottom = r1.top + ROUND_CORNER_SIZE;
2198 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2200 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPRIGHT);
2202 /* Now erase the bottom corner and draw diagonal edge */
2204 r1.bottom = r.bottom;
2205 r1.right = r1.left + ROUND_CORNER_SIZE + 1;
2206 r1.top = r1.bottom - ROUND_CORNER_SIZE;
2207 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2209 DrawEdge(hdc, &r1, EDGE_SUNKEN, BF_DIAGONAL_ENDTOPLEFT);
2212 else /* ! TCS_VERTICAL */
2214 /* These are for adjusting the drawing of a Selected tab */
2215 /* The initial values are for the normal case of non-Selected */
2216 if (iItem == infoPtr->iSelected) {
2217 /* if leftmost draw the line longer */
2218 if(selectedRect.left == 0)
2219 fillRect.left += CONTROL_BORDER_SIZEX;
2220 /* if rightmost draw the line longer */
2221 if(selectedRect.right == clRight)
2222 fillRect.right -= CONTROL_BORDER_SIZEX;
2225 if (infoPtr->dwStyle & TCS_BOTTOM)
2227 /* Adjust both rectangles for topmost row */
2228 if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2234 TRACE("<bottom> item=%d, fill=(%s), edge=(%s)\n",
2235 iItem, wine_dbgstr_rect(&fillRect), wine_dbgstr_rect(&r));
2237 /* Clear interior */
2238 SetBkColor(hdc, bkgnd);
2239 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2241 /* Draw rectangular edge around tab */
2242 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_BOTTOM|BF_RIGHT);
2244 /* Now erase the righthand corner and draw diagonal edge */
2245 SetBkColor(hdc, corner);
2246 r1.left = r.right - ROUND_CORNER_SIZE;
2247 r1.bottom = r.bottom;
2249 r1.top = r1.bottom - ROUND_CORNER_SIZE - 1;
2250 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2252 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMLEFT);
2254 /* Now erase the lefthand corner and draw diagonal edge */
2256 r1.bottom = r.bottom;
2257 r1.right = r1.left + ROUND_CORNER_SIZE;
2258 r1.top = r1.bottom - ROUND_CORNER_SIZE - 1;
2259 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2261 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPLEFT);
2263 if (iItem == infoPtr->iSelected)
2267 if (selectedRect.left == 0)
2272 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_LEFT);
2279 /* Adjust both rectangles for bottommost row */
2280 if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2282 fillRect.bottom += 3;
2286 TRACE("<top> item=%d, fill=(%s), edge=(%s)\n",
2287 iItem, wine_dbgstr_rect(&fillRect), wine_dbgstr_rect(&r));
2289 /* Clear interior */
2290 SetBkColor(hdc, bkgnd);
2291 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2293 /* Draw rectangular edge around tab */
2294 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_TOP|BF_RIGHT);
2296 /* Now erase the righthand corner and draw diagonal edge */
2297 SetBkColor(hdc, corner);
2298 r1.left = r.right - ROUND_CORNER_SIZE;
2301 r1.bottom = r1.top + ROUND_CORNER_SIZE + 1;
2302 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2304 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMRIGHT);
2306 /* Now erase the lefthand corner and draw diagonal edge */
2309 r1.right = r1.left + ROUND_CORNER_SIZE;
2310 r1.bottom = r1.top + ROUND_CORNER_SIZE + 1;
2311 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2313 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPRIGHT);
2318 TAB_DumpItemInternal(infoPtr, iItem);
2320 /* This modifies r to be the text rectangle. */
2321 TAB_DrawItemInterior(infoPtr, hdc, iItem, &r);
2325 /******************************************************************************
2328 * This method is used to draw the raised border around the tab control
2331 static void TAB_DrawBorder(const TAB_INFO *infoPtr, HDC hdc)
2334 HTHEME theme = GetWindowTheme (infoPtr->hwnd);
2336 GetClientRect (infoPtr->hwnd, &rect);
2339 * Adjust for the style
2342 if (infoPtr->uNumItem)
2344 if ((infoPtr->dwStyle & TCS_BOTTOM) && !(infoPtr->dwStyle & TCS_VERTICAL))
2345 rect.bottom -= infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2346 else if((infoPtr->dwStyle & TCS_BOTTOM) && (infoPtr->dwStyle & TCS_VERTICAL))
2347 rect.right -= infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2348 else if(infoPtr->dwStyle & TCS_VERTICAL)
2349 rect.left += infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2350 else /* not TCS_VERTICAL and not TCS_BOTTOM */
2351 rect.top += infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2354 TRACE("border=(%s)\n", wine_dbgstr_rect(&rect));
2357 DrawThemeBackground (theme, hdc, TABP_PANE, 0, &rect, NULL);
2359 DrawEdge(hdc, &rect, EDGE_RAISED, BF_SOFT|BF_RECT);
2362 /******************************************************************************
2365 * This method repaints the tab control..
2367 static void TAB_Refresh (const TAB_INFO *infoPtr, HDC hdc)
2372 if (!infoPtr->DoRedraw)
2375 hOldFont = SelectObject (hdc, infoPtr->hFont);
2377 if (infoPtr->dwStyle & TCS_BUTTONS)
2379 for (i = 0; i < infoPtr->uNumItem; i++)
2380 TAB_DrawItem (infoPtr, hdc, i);
2384 /* Draw all the non selected item first */
2385 for (i = 0; i < infoPtr->uNumItem; i++)
2387 if (i != infoPtr->iSelected)
2388 TAB_DrawItem (infoPtr, hdc, i);
2391 /* Now, draw the border, draw it before the selected item
2392 * since the selected item overwrites part of the border. */
2393 TAB_DrawBorder (infoPtr, hdc);
2395 /* Then, draw the selected item */
2396 TAB_DrawItem (infoPtr, hdc, infoPtr->iSelected);
2399 SelectObject (hdc, hOldFont);
2402 static inline DWORD TAB_GetRowCount (const TAB_INFO *infoPtr)
2404 TRACE("(%p)\n", infoPtr);
2405 return infoPtr->uNumRows;
2408 static inline LRESULT TAB_SetRedraw (TAB_INFO *infoPtr, BOOL doRedraw)
2410 infoPtr->DoRedraw = doRedraw;
2414 /******************************************************************************
2415 * TAB_EnsureSelectionVisible
2417 * This method will make sure that the current selection is completely
2418 * visible by scrolling until it is.
2420 static void TAB_EnsureSelectionVisible(
2423 INT iSelected = infoPtr->iSelected;
2424 INT iOrigLeftmostVisible = infoPtr->leftmostVisible;
2426 /* set the items row to the bottommost row or topmost row depending on
2428 if ((infoPtr->uNumRows > 1) && !(infoPtr->dwStyle & TCS_BUTTONS))
2430 TAB_ITEM *selected = TAB_GetItem(infoPtr, iSelected);
2434 if(infoPtr->dwStyle & TCS_VERTICAL)
2435 newselected = selected->rect.left;
2437 newselected = selected->rect.top;
2439 /* the target row is always (number of rows - 1)
2440 as row 0 is furthest from the clientRect */
2441 iTargetRow = infoPtr->uNumRows - 1;
2443 if (newselected != iTargetRow)
2446 if(infoPtr->dwStyle & TCS_VERTICAL)
2448 for (i=0; i < infoPtr->uNumItem; i++)
2450 /* move everything in the row of the selected item to the iTargetRow */
2451 TAB_ITEM *item = TAB_GetItem(infoPtr, i);
2453 if (item->rect.left == newselected )
2454 item->rect.left = iTargetRow;
2457 if (item->rect.left > newselected)
2464 for (i=0; i < infoPtr->uNumItem; i++)
2466 TAB_ITEM *item = TAB_GetItem(infoPtr, i);
2468 if (item->rect.top == newselected )
2469 item->rect.top = iTargetRow;
2472 if (item->rect.top > newselected)
2477 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
2482 * Do the trivial cases first.
2484 if ( (!infoPtr->needsScrolling) ||
2485 (infoPtr->hwndUpDown==0) || (infoPtr->dwStyle & TCS_VERTICAL))
2488 if (infoPtr->leftmostVisible >= iSelected)
2490 infoPtr->leftmostVisible = iSelected;
2494 TAB_ITEM *selected = TAB_GetItem(infoPtr, iSelected);
2499 /* Calculate the part of the client area that is visible */
2500 GetClientRect(infoPtr->hwnd, &r);
2503 GetClientRect(infoPtr->hwndUpDown, &r);
2506 if ((selected->rect.right -
2507 selected->rect.left) >= width )
2509 /* Special case: width of selected item is greater than visible
2512 infoPtr->leftmostVisible = iSelected;
2516 for (i = infoPtr->leftmostVisible; i < infoPtr->uNumItem; i++)
2518 if ((selected->rect.right - TAB_GetItem(infoPtr, i)->rect.left) < width)
2521 infoPtr->leftmostVisible = i;
2525 if (infoPtr->leftmostVisible != iOrigLeftmostVisible)
2526 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
2528 SendMessageW(infoPtr->hwndUpDown, UDM_SETPOS, 0,
2529 MAKELONG(infoPtr->leftmostVisible, 0));
2532 /******************************************************************************
2533 * TAB_InvalidateTabArea
2535 * This method will invalidate the portion of the control that contains the
2536 * tabs. It is called when the state of the control changes and needs
2539 static void TAB_InvalidateTabArea(const TAB_INFO *infoPtr)
2541 RECT clientRect, rInvalidate, rAdjClient;
2542 INT lastRow = infoPtr->uNumRows - 1;
2545 if (lastRow < 0) return;
2547 GetClientRect(infoPtr->hwnd, &clientRect);
2548 rInvalidate = clientRect;
2549 rAdjClient = clientRect;
2551 TAB_AdjustRect(infoPtr, 0, &rAdjClient);
2553 TAB_InternalGetItemRect(infoPtr, infoPtr->uNumItem-1 , &rect, NULL);
2554 if ((infoPtr->dwStyle & TCS_BOTTOM) && (infoPtr->dwStyle & TCS_VERTICAL))
2556 rInvalidate.left = rAdjClient.right;
2557 if (infoPtr->uNumRows == 1)
2558 rInvalidate.bottom = clientRect.top + rect.bottom + 2 * SELECTED_TAB_OFFSET;
2560 else if(infoPtr->dwStyle & TCS_VERTICAL)
2562 rInvalidate.right = rAdjClient.left;
2563 if (infoPtr->uNumRows == 1)
2564 rInvalidate.bottom = clientRect.top + rect.bottom + 2 * SELECTED_TAB_OFFSET;
2566 else if (infoPtr->dwStyle & TCS_BOTTOM)
2568 rInvalidate.top = rAdjClient.bottom;
2569 if (infoPtr->uNumRows == 1)
2570 rInvalidate.right = clientRect.left + rect.right + 2 * SELECTED_TAB_OFFSET;
2574 rInvalidate.bottom = rAdjClient.top;
2575 if (infoPtr->uNumRows == 1)
2576 rInvalidate.right = clientRect.left + rect.right + 2 * SELECTED_TAB_OFFSET;
2579 /* Punch out the updown control */
2580 if (infoPtr->needsScrolling && (rInvalidate.right > 0)) {
2582 GetClientRect(infoPtr->hwndUpDown, &r);
2583 if (rInvalidate.right > clientRect.right - r.left)
2584 rInvalidate.right = rInvalidate.right - (r.right - r.left);
2586 rInvalidate.right = clientRect.right - r.left;
2589 TRACE("invalidate (%s)\n", wine_dbgstr_rect(&rInvalidate));
2591 InvalidateRect(infoPtr->hwnd, &rInvalidate, TRUE);
2594 static inline LRESULT TAB_Paint (TAB_INFO *infoPtr, HDC hdcPaint)
2603 hdc = BeginPaint (infoPtr->hwnd, &ps);
2604 TRACE("erase %d, rect=(%s)\n", ps.fErase, wine_dbgstr_rect(&ps.rcPaint));
2607 TAB_Refresh (infoPtr, hdc);
2610 EndPaint (infoPtr->hwnd, &ps);
2616 TAB_InsertItemT (TAB_INFO *infoPtr, INT iItem, TCITEMW *pti, BOOL bUnicode)
2621 GetClientRect (infoPtr->hwnd, &rect);
2622 TRACE("Rect: %p %s\n", infoPtr->hwnd, wine_dbgstr_rect(&rect));
2624 if (iItem < 0) return -1;
2625 if (iItem > infoPtr->uNumItem)
2626 iItem = infoPtr->uNumItem;
2628 TAB_DumpItemExternalT(pti, iItem, bUnicode);
2631 if (infoPtr->uNumItem == 0) {
2632 infoPtr->items = Alloc (TAB_ITEM_SIZE(infoPtr));
2633 infoPtr->uNumItem++;
2634 infoPtr->iSelected = 0;
2637 LPBYTE oldItems = (LPBYTE)infoPtr->items;
2639 infoPtr->uNumItem++;
2640 infoPtr->items = Alloc (TAB_ITEM_SIZE(infoPtr) * infoPtr->uNumItem);
2642 /* pre insert copy */
2644 memcpy (infoPtr->items, oldItems,
2645 iItem * TAB_ITEM_SIZE(infoPtr));
2648 /* post insert copy */
2649 if (iItem < infoPtr->uNumItem - 1) {
2650 memcpy (TAB_GetItem(infoPtr, iItem + 1),
2651 oldItems + iItem * TAB_ITEM_SIZE(infoPtr),
2652 (infoPtr->uNumItem - iItem - 1) * TAB_ITEM_SIZE(infoPtr));
2656 if (iItem <= infoPtr->iSelected)
2657 infoPtr->iSelected++;
2662 item = TAB_GetItem(infoPtr, iItem);
2664 item->pszText = NULL;
2666 if (pti->mask & TCIF_TEXT)
2669 Str_SetPtrW (&item->pszText, pti->pszText);
2671 Str_SetPtrAtoW (&item->pszText, (LPSTR)pti->pszText);
2674 if (pti->mask & TCIF_IMAGE)
2675 item->iImage = pti->iImage;
2679 if (pti->mask & TCIF_PARAM)
2680 memcpy(item->extra, &pti->lParam, infoPtr->cbInfo);
2682 memset(item->extra, 0, infoPtr->cbInfo);
2684 TAB_SetItemBounds(infoPtr);
2685 if (infoPtr->uNumItem > 1)
2686 TAB_InvalidateTabArea(infoPtr);
2688 InvalidateRect(infoPtr->hwnd, NULL, TRUE);
2690 TRACE("[%p]: added item %d %s\n",
2691 infoPtr->hwnd, iItem, debugstr_w(item->pszText));
2693 /* If we haven't set the current focus yet, set it now. */
2694 if (infoPtr->uFocus == -1)
2695 TAB_SetCurFocus(infoPtr, iItem);
2701 TAB_SetItemSize (TAB_INFO *infoPtr, INT cx, INT cy)
2704 BOOL bNeedPaint = FALSE;
2706 lResult = MAKELONG(infoPtr->tabWidth, infoPtr->tabHeight);
2708 /* UNDOCUMENTED: If requested Width or Height is 0 this means that program wants to use auto size. */
2709 if (infoPtr->dwStyle & TCS_FIXEDWIDTH && (infoPtr->tabWidth != cx))
2711 infoPtr->tabWidth = cx;
2715 if (infoPtr->tabHeight != cy)
2717 if ((infoPtr->fHeightSet = (cy != 0)))
2718 infoPtr->tabHeight = cy;
2722 TRACE("was h=%d,w=%d, now h=%d,w=%d\n",
2723 HIWORD(lResult), LOWORD(lResult),
2724 infoPtr->tabHeight, infoPtr->tabWidth);
2728 TAB_SetItemBounds(infoPtr);
2729 RedrawWindow(infoPtr->hwnd, NULL, NULL, RDW_ERASE | RDW_INVALIDATE | RDW_UPDATENOW);
2735 static inline LRESULT TAB_SetMinTabWidth (TAB_INFO *infoPtr, INT cx)
2739 TRACE("(%p,%d)\n", infoPtr, cx);
2741 if (infoPtr->tabMinWidth < 0)
2742 oldcx = DEFAULT_MIN_TAB_WIDTH;
2744 oldcx = infoPtr->tabMinWidth;
2745 infoPtr->tabMinWidth = cx;
2746 TAB_SetItemBounds(infoPtr);
2750 static inline LRESULT
2751 TAB_HighlightItem (TAB_INFO *infoPtr, INT iItem, BOOL fHighlight)
2757 TRACE("(%p,%d,%s)\n", infoPtr, iItem, fHighlight ? "true" : "false");
2759 if (iItem < 0 || iItem >= infoPtr->uNumItem)
2762 lpState = &TAB_GetItem(infoPtr, iItem)->dwState;
2763 oldState = *lpState;
2766 *lpState |= TCIS_HIGHLIGHTED;
2768 *lpState &= ~TCIS_HIGHLIGHTED;
2770 if ((oldState != *lpState) && TAB_InternalGetItemRect (infoPtr, iItem, &r, NULL))
2771 InvalidateRect (infoPtr->hwnd, &r, TRUE);
2777 TAB_SetItemT (TAB_INFO *infoPtr, INT iItem, LPTCITEMW tabItem, BOOL bUnicode)
2781 TRACE("(%p,%d,%p,%s)\n", infoPtr, iItem, tabItem, bUnicode ? "true" : "false");
2783 if (iItem < 0 || iItem >= infoPtr->uNumItem)
2786 TAB_DumpItemExternalT(tabItem, iItem, bUnicode);
2788 wineItem = TAB_GetItem(infoPtr, iItem);
2790 if (tabItem->mask & TCIF_IMAGE)
2791 wineItem->iImage = tabItem->iImage;
2793 if (tabItem->mask & TCIF_PARAM)
2794 memcpy(wineItem->extra, &tabItem->lParam, infoPtr->cbInfo);
2796 if (tabItem->mask & TCIF_RTLREADING)
2797 FIXME("TCIF_RTLREADING\n");
2799 if (tabItem->mask & TCIF_STATE)
2800 wineItem->dwState = (wineItem->dwState & ~tabItem->dwStateMask) |
2801 ( tabItem->dwState & tabItem->dwStateMask);
2803 if (tabItem->mask & TCIF_TEXT)
2805 Free(wineItem->pszText);
2806 wineItem->pszText = NULL;
2808 Str_SetPtrW(&wineItem->pszText, tabItem->pszText);
2810 Str_SetPtrAtoW(&wineItem->pszText, (LPSTR)tabItem->pszText);
2813 /* Update and repaint tabs */
2814 TAB_SetItemBounds(infoPtr);
2815 TAB_InvalidateTabArea(infoPtr);
2820 static inline LRESULT TAB_GetItemCount (const TAB_INFO *infoPtr)
2823 return infoPtr->uNumItem;
2828 TAB_GetItemT (TAB_INFO *infoPtr, INT iItem, LPTCITEMW tabItem, BOOL bUnicode)
2832 TRACE("(%p,%d,%p,%s)\n", infoPtr, iItem, tabItem, bUnicode ? "true" : "false");
2834 if (!tabItem) return FALSE;
2836 if (iItem < 0 || iItem >= infoPtr->uNumItem)
2838 /* init requested fields */
2839 if (tabItem->mask & TCIF_IMAGE) tabItem->iImage = 0;
2840 if (tabItem->mask & TCIF_PARAM) tabItem->lParam = 0;
2841 if (tabItem->mask & TCIF_STATE) tabItem->dwState = 0;
2845 wineItem = TAB_GetItem(infoPtr, iItem);
2847 if (tabItem->mask & TCIF_IMAGE)
2848 tabItem->iImage = wineItem->iImage;
2850 if (tabItem->mask & TCIF_PARAM)
2851 memcpy(&tabItem->lParam, wineItem->extra, infoPtr->cbInfo);
2853 if (tabItem->mask & TCIF_RTLREADING)
2854 FIXME("TCIF_RTLREADING\n");
2856 if (tabItem->mask & TCIF_STATE)
2857 tabItem->dwState = wineItem->dwState & tabItem->dwStateMask;
2859 if (tabItem->mask & TCIF_TEXT)
2862 Str_GetPtrW (wineItem->pszText, tabItem->pszText, tabItem->cchTextMax);
2864 Str_GetPtrWtoA (wineItem->pszText, (LPSTR)tabItem->pszText, tabItem->cchTextMax);
2867 TAB_DumpItemExternalT(tabItem, iItem, bUnicode);
2873 static LRESULT TAB_DeleteItem (TAB_INFO *infoPtr, INT iItem)
2875 BOOL bResult = FALSE;
2877 TRACE("(%p, %d)\n", infoPtr, iItem);
2879 if ((iItem >= 0) && (iItem < infoPtr->uNumItem))
2881 TAB_ITEM *item = TAB_GetItem(infoPtr, iItem);
2882 LPBYTE oldItems = (LPBYTE)infoPtr->items;
2884 TAB_InvalidateTabArea(infoPtr);
2885 Free(item->pszText);
2886 infoPtr->uNumItem--;
2888 if (!infoPtr->uNumItem)
2890 infoPtr->items = NULL;
2891 if (infoPtr->iHotTracked >= 0)
2893 KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
2894 infoPtr->iHotTracked = -1;
2899 infoPtr->items = Alloc(TAB_ITEM_SIZE(infoPtr) * infoPtr->uNumItem);
2902 memcpy(infoPtr->items, oldItems, iItem * TAB_ITEM_SIZE(infoPtr));
2904 if (iItem < infoPtr->uNumItem)
2905 memcpy(TAB_GetItem(infoPtr, iItem),
2906 oldItems + (iItem + 1) * TAB_ITEM_SIZE(infoPtr),
2907 (infoPtr->uNumItem - iItem) * TAB_ITEM_SIZE(infoPtr));
2909 if (iItem <= infoPtr->iHotTracked)
2911 /* When tabs move left/up, the hot track item may change */
2912 FIXME("Recalc hot track\n");
2917 /* Readjust the selected index */
2918 if ((iItem == infoPtr->iSelected) && (iItem > 0))
2919 infoPtr->iSelected--;
2921 if (iItem < infoPtr->iSelected)
2922 infoPtr->iSelected--;
2924 if (infoPtr->uNumItem == 0)
2925 infoPtr->iSelected = -1;
2927 /* Reposition and repaint tabs */
2928 TAB_SetItemBounds(infoPtr);
2936 static inline LRESULT TAB_DeleteAllItems (TAB_INFO *infoPtr)
2938 TRACE("(%p)\n", infoPtr);
2939 while (infoPtr->uNumItem)
2940 TAB_DeleteItem (infoPtr, 0);
2945 static inline LRESULT TAB_GetFont (const TAB_INFO *infoPtr)
2947 TRACE("(%p) returning %p\n", infoPtr, infoPtr->hFont);
2948 return (LRESULT)infoPtr->hFont;
2951 static inline LRESULT TAB_SetFont (TAB_INFO *infoPtr, HFONT hNewFont)
2953 TRACE("(%p,%p)\n", infoPtr, hNewFont);
2955 infoPtr->hFont = hNewFont;
2957 TAB_SetItemBounds(infoPtr);
2959 TAB_InvalidateTabArea(infoPtr);
2965 static inline LRESULT TAB_GetImageList (const TAB_INFO *infoPtr)
2968 return (LRESULT)infoPtr->himl;
2971 static inline LRESULT TAB_SetImageList (TAB_INFO *infoPtr, HIMAGELIST himlNew)
2973 HIMAGELIST himlPrev = infoPtr->himl;
2974 TRACE("himl=%p\n", himlNew);
2975 infoPtr->himl = himlNew;
2976 TAB_SetItemBounds(infoPtr);
2977 InvalidateRect(infoPtr->hwnd, NULL, TRUE);
2978 return (LRESULT)himlPrev;
2981 static inline LRESULT TAB_GetUnicodeFormat (const TAB_INFO *infoPtr)
2983 TRACE("(%p)\n", infoPtr);
2984 return infoPtr->bUnicode;
2987 static inline LRESULT TAB_SetUnicodeFormat (TAB_INFO *infoPtr, BOOL bUnicode)
2989 BOOL bTemp = infoPtr->bUnicode;
2991 TRACE("(%p %d)\n", infoPtr, bUnicode);
2992 infoPtr->bUnicode = bUnicode;
2997 static inline LRESULT TAB_Size (TAB_INFO *infoPtr)
2999 /* I'm not really sure what the following code was meant to do.
3000 This is what it is doing:
3001 When WM_SIZE is sent with SIZE_RESTORED, the control
3002 gets positioned in the top left corner.
3006 UINT uPosFlags,cx,cy;
3010 parent = GetParent (hwnd);
3011 GetClientRect(parent, &parent_rect);
3014 if (GetWindowLongW(hwnd, GWL_STYLE) & CCS_NORESIZE)
3015 uPosFlags |= (SWP_NOSIZE | SWP_NOMOVE);
3017 SetWindowPos (hwnd, 0, parent_rect.left, parent_rect.top,
3018 cx, cy, uPosFlags | SWP_NOZORDER);
3020 FIXME("WM_SIZE flag %x %lx not handled\n", wParam, lParam);
3023 /* Recompute the size/position of the tabs. */
3024 TAB_SetItemBounds (infoPtr);
3026 /* Force a repaint of the control. */
3027 InvalidateRect(infoPtr->hwnd, NULL, TRUE);
3033 static LRESULT TAB_Create (HWND hwnd, LPARAM lParam)
3036 TEXTMETRICW fontMetrics;
3041 infoPtr = Alloc (sizeof(TAB_INFO));
3043 SetWindowLongPtrW(hwnd, 0, (DWORD_PTR)infoPtr);
3045 infoPtr->hwnd = hwnd;
3046 infoPtr->hwndNotify = ((LPCREATESTRUCTW)lParam)->hwndParent;
3047 infoPtr->uNumItem = 0;
3048 infoPtr->uNumRows = 0;
3049 infoPtr->uHItemPadding = 6;
3050 infoPtr->uVItemPadding = 3;
3051 infoPtr->uHItemPadding_s = 6;
3052 infoPtr->uVItemPadding_s = 3;
3055 infoPtr->hcurArrow = LoadCursorW (0, (LPWSTR)IDC_ARROW);
3056 infoPtr->iSelected = -1;
3057 infoPtr->iHotTracked = -1;
3058 infoPtr->uFocus = -1;
3059 infoPtr->hwndToolTip = 0;
3060 infoPtr->DoRedraw = TRUE;
3061 infoPtr->needsScrolling = FALSE;
3062 infoPtr->hwndUpDown = 0;
3063 infoPtr->leftmostVisible = 0;
3064 infoPtr->fHeightSet = FALSE;
3065 infoPtr->bUnicode = IsWindowUnicode (hwnd);
3066 infoPtr->cbInfo = sizeof(LPARAM);
3068 TRACE("Created tab control, hwnd [%p]\n", hwnd);
3070 /* The tab control always has the WS_CLIPSIBLINGS style. Even
3071 if you don't specify it in CreateWindow. This is necessary in
3072 order for paint to work correctly. This follows windows behaviour. */
3073 dwStyle = GetWindowLongW(hwnd, GWL_STYLE);
3074 SetWindowLongW(hwnd, GWL_STYLE, dwStyle|WS_CLIPSIBLINGS);
3076 infoPtr->dwStyle = dwStyle | WS_CLIPSIBLINGS;
3077 infoPtr->exStyle = (dwStyle & TCS_FLATBUTTONS) ? TCS_EX_FLATSEPARATORS : 0;
3079 if (infoPtr->dwStyle & TCS_TOOLTIPS) {
3080 /* Create tooltip control */
3081 infoPtr->hwndToolTip =
3082 CreateWindowExW (0, TOOLTIPS_CLASSW, NULL, WS_POPUP,
3083 CW_USEDEFAULT, CW_USEDEFAULT,
3084 CW_USEDEFAULT, CW_USEDEFAULT,
3087 /* Send NM_TOOLTIPSCREATED notification */
3088 if (infoPtr->hwndToolTip) {
3089 NMTOOLTIPSCREATED nmttc;
3091 nmttc.hdr.hwndFrom = hwnd;
3092 nmttc.hdr.idFrom = GetWindowLongPtrW(hwnd, GWLP_ID);
3093 nmttc.hdr.code = NM_TOOLTIPSCREATED;
3094 nmttc.hwndToolTips = infoPtr->hwndToolTip;
3096 SendMessageW (infoPtr->hwndNotify, WM_NOTIFY,
3097 (WPARAM)GetWindowLongPtrW(hwnd, GWLP_ID), (LPARAM)&nmttc);
3101 OpenThemeData (infoPtr->hwnd, themeClass);
3104 * We need to get text information so we need a DC and we need to select
3108 hOldFont = SelectObject (hdc, GetStockObject (SYSTEM_FONT));
3110 /* Use the system font to determine the initial height of a tab. */
3111 GetTextMetricsW(hdc, &fontMetrics);
3114 * Make sure there is enough space for the letters + growing the
3115 * selected item + extra space for the selected item.
3117 infoPtr->tabHeight = fontMetrics.tmHeight + SELECTED_TAB_OFFSET +
3118 ((infoPtr->dwStyle & TCS_BUTTONS) ? 2 : 1) *
3119 infoPtr->uVItemPadding;
3121 /* Initialize the width of a tab. */
3122 if (infoPtr->dwStyle & TCS_FIXEDWIDTH)
3123 infoPtr->tabWidth = GetDeviceCaps(hdc, LOGPIXELSX);
3125 infoPtr->tabMinWidth = -1;
3127 TRACE("tabH=%d, tabW=%d\n", infoPtr->tabHeight, infoPtr->tabWidth);
3129 SelectObject (hdc, hOldFont);
3130 ReleaseDC(hwnd, hdc);
3136 TAB_Destroy (TAB_INFO *infoPtr)
3140 SetWindowLongPtrW(infoPtr->hwnd, 0, 0);
3142 if (infoPtr->items) {
3143 for (iItem = 0; iItem < infoPtr->uNumItem; iItem++) {
3144 Free (TAB_GetItem(infoPtr, iItem)->pszText);
3146 Free (infoPtr->items);
3149 if (infoPtr->hwndToolTip)
3150 DestroyWindow (infoPtr->hwndToolTip);
3152 if (infoPtr->hwndUpDown)
3153 DestroyWindow(infoPtr->hwndUpDown);
3155 if (infoPtr->iHotTracked >= 0)
3156 KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
3158 CloseThemeData (GetWindowTheme (infoPtr->hwnd));
3164 /* update theme after a WM_THEMECHANGED message */
3165 static LRESULT theme_changed(const TAB_INFO *infoPtr)
3167 HTHEME theme = GetWindowTheme (infoPtr->hwnd);
3168 CloseThemeData (theme);
3169 OpenThemeData (infoPtr->hwnd, themeClass);
3173 static LRESULT TAB_NCCalcSize(WPARAM wParam)
3177 return WVR_ALIGNTOP;
3180 static inline LRESULT
3181 TAB_SetItemExtra (TAB_INFO *infoPtr, INT cbInfo)
3183 TRACE("(%p %d)\n", infoPtr, cbInfo);
3188 if (infoPtr->uNumItem)
3190 /* FIXME: MSDN says this is not allowed, but this hasn't been verified */
3194 infoPtr->cbInfo = cbInfo;
3198 static LRESULT TAB_RemoveImage (TAB_INFO *infoPtr, INT image)
3200 TRACE("%p %d\n", infoPtr, image);
3202 if (ImageList_Remove (infoPtr->himl, image))
3207 /* shift indices, repaint items if needed */
3208 for (i = 0; i < infoPtr->uNumItem; i++)
3210 idx = &TAB_GetItem(infoPtr, i)->iImage;
3219 if (TAB_InternalGetItemRect (infoPtr, i, &r, NULL))
3220 InvalidateRect (infoPtr->hwnd, &r, TRUE);
3229 TAB_SetExtendedStyle (TAB_INFO *infoPtr, DWORD exMask, DWORD exStyle)
3231 DWORD prevstyle = infoPtr->exStyle;
3233 /* zero mask means all styles */
3234 if (exMask == 0) exMask = ~0;
3236 if (exMask & TCS_EX_REGISTERDROP)
3238 FIXME("TCS_EX_REGISTERDROP style unimplemented\n");
3239 exMask &= ~TCS_EX_REGISTERDROP;
3240 exStyle &= ~TCS_EX_REGISTERDROP;
3243 if (exMask & TCS_EX_FLATSEPARATORS)
3245 if ((prevstyle ^ exStyle) & TCS_EX_FLATSEPARATORS)
3247 infoPtr->exStyle ^= TCS_EX_FLATSEPARATORS;
3248 TAB_InvalidateTabArea(infoPtr);
3255 static inline LRESULT
3256 TAB_GetExtendedStyle (const TAB_INFO *infoPtr)
3258 return infoPtr->exStyle;
3262 TAB_DeselectAll (TAB_INFO *infoPtr, BOOL excludesel)
3265 INT i, selected = infoPtr->iSelected;
3267 TRACE("(%p, %d)\n", infoPtr, excludesel);
3269 if (!(infoPtr->dwStyle & TCS_BUTTONS))
3272 for (i = 0; i < infoPtr->uNumItem; i++)
3274 if ((TAB_GetItem(infoPtr, i)->dwState & TCIS_BUTTONPRESSED) &&
3277 TAB_GetItem(infoPtr, i)->dwState &= ~TCIS_BUTTONPRESSED;
3282 if (!excludesel && (selected != -1))
3284 TAB_GetItem(infoPtr, selected)->dwState &= ~TCIS_BUTTONPRESSED;
3285 infoPtr->iSelected = -1;
3290 TAB_InvalidateTabArea (infoPtr);
3297 * Processes WM_STYLECHANGED messages.
3300 * [I] infoPtr : valid pointer to the tab data structure
3301 * [I] wStyleType : window style type (normal or extended)
3302 * [I] lpss : window style information
3307 static INT TAB_StyleChanged(TAB_INFO *infoPtr, WPARAM wStyleType,
3308 const STYLESTRUCT *lpss)
3310 TRACE("(styletype=%lx, styleOld=0x%08x, styleNew=0x%08x)\n",
3311 wStyleType, lpss->styleOld, lpss->styleNew);
3313 if (wStyleType != GWL_STYLE) return 0;
3315 infoPtr->dwStyle = lpss->styleNew;
3317 TAB_SetItemBounds (infoPtr);
3318 InvalidateRect(infoPtr->hwnd, NULL, TRUE);
3323 static LRESULT WINAPI
3324 TAB_WindowProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
3326 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
3328 TRACE("hwnd=%p msg=%x wParam=%lx lParam=%lx\n", hwnd, uMsg, wParam, lParam);
3329 if (!infoPtr && (uMsg != WM_CREATE))
3330 return DefWindowProcW (hwnd, uMsg, wParam, lParam);
3334 case TCM_GETIMAGELIST:
3335 return TAB_GetImageList (infoPtr);
3337 case TCM_SETIMAGELIST:
3338 return TAB_SetImageList (infoPtr, (HIMAGELIST)lParam);
3340 case TCM_GETITEMCOUNT:
3341 return TAB_GetItemCount (infoPtr);
3345 return TAB_GetItemT (infoPtr, (INT)wParam, (LPTCITEMW)lParam, uMsg == TCM_GETITEMW);
3349 return TAB_SetItemT (infoPtr, (INT)wParam, (LPTCITEMW)lParam, uMsg == TCM_SETITEMW);
3351 case TCM_DELETEITEM:
3352 return TAB_DeleteItem (infoPtr, (INT)wParam);
3354 case TCM_DELETEALLITEMS:
3355 return TAB_DeleteAllItems (infoPtr);
3357 case TCM_GETITEMRECT:
3358 return TAB_GetItemRect (infoPtr, (INT)wParam, (LPRECT)lParam);
3361 return TAB_GetCurSel (infoPtr);
3364 return TAB_HitTest (infoPtr, (LPTCHITTESTINFO)lParam);
3367 return TAB_SetCurSel (infoPtr, (INT)wParam);
3369 case TCM_INSERTITEMA:
3370 case TCM_INSERTITEMW:
3371 return TAB_InsertItemT (infoPtr, (INT)wParam, (TCITEMW*)lParam, uMsg == TCM_INSERTITEMW);
3373 case TCM_SETITEMEXTRA:
3374 return TAB_SetItemExtra (infoPtr, (INT)wParam);
3376 case TCM_ADJUSTRECT:
3377 return TAB_AdjustRect (infoPtr, (BOOL)wParam, (LPRECT)lParam);
3379 case TCM_SETITEMSIZE:
3380 return TAB_SetItemSize (infoPtr, (INT)LOWORD(lParam), (INT)HIWORD(lParam));
3382 case TCM_REMOVEIMAGE:
3383 return TAB_RemoveImage (infoPtr, (INT)wParam);
3385 case TCM_SETPADDING:
3386 return TAB_SetPadding (infoPtr, lParam);
3388 case TCM_GETROWCOUNT:
3389 return TAB_GetRowCount(infoPtr);
3391 case TCM_GETUNICODEFORMAT:
3392 return TAB_GetUnicodeFormat (infoPtr);
3394 case TCM_SETUNICODEFORMAT:
3395 return TAB_SetUnicodeFormat (infoPtr, (BOOL)wParam);
3397 case TCM_HIGHLIGHTITEM:
3398 return TAB_HighlightItem (infoPtr, (INT)wParam, (BOOL)LOWORD(lParam));
3400 case TCM_GETTOOLTIPS:
3401 return TAB_GetToolTips (infoPtr);
3403 case TCM_SETTOOLTIPS:
3404 return TAB_SetToolTips (infoPtr, (HWND)wParam);
3406 case TCM_GETCURFOCUS:
3407 return TAB_GetCurFocus (infoPtr);
3409 case TCM_SETCURFOCUS:
3410 return TAB_SetCurFocus (infoPtr, (INT)wParam);
3412 case TCM_SETMINTABWIDTH:
3413 return TAB_SetMinTabWidth(infoPtr, (INT)lParam);
3415 case TCM_DESELECTALL:
3416 return TAB_DeselectAll (infoPtr, (BOOL)wParam);
3418 case TCM_GETEXTENDEDSTYLE:
3419 return TAB_GetExtendedStyle (infoPtr);
3421 case TCM_SETEXTENDEDSTYLE:
3422 return TAB_SetExtendedStyle (infoPtr, wParam, lParam);
3425 return TAB_GetFont (infoPtr);
3428 return TAB_SetFont (infoPtr, (HFONT)wParam);
3431 return TAB_Create (hwnd, lParam);
3434 return TAB_Destroy (infoPtr);
3437 return DLGC_WANTARROWS | DLGC_WANTCHARS;
3439 case WM_LBUTTONDOWN:
3440 return TAB_LButtonDown (infoPtr, wParam, lParam);
3443 return TAB_LButtonUp (infoPtr);
3446 return SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, wParam, lParam);
3448 case WM_RBUTTONDOWN:
3449 return TAB_RButtonDown (infoPtr);
3452 return TAB_MouseMove (infoPtr, wParam, lParam);
3454 case WM_PRINTCLIENT:
3456 return TAB_Paint (infoPtr, (HDC)wParam);
3459 return TAB_Size (infoPtr);
3462 return TAB_SetRedraw (infoPtr, (BOOL)wParam);
3465 return TAB_OnHScroll(infoPtr, (int)LOWORD(wParam), (int)HIWORD(wParam));
3467 case WM_STYLECHANGED:
3468 return TAB_StyleChanged(infoPtr, wParam, (LPSTYLESTRUCT)lParam);
3470 case WM_SYSCOLORCHANGE:
3471 COMCTL32_RefreshSysColors();
3474 case WM_THEMECHANGED:
3475 return theme_changed (infoPtr);
3478 TAB_KillFocus(infoPtr);
3480 TAB_FocusChanging(infoPtr);
3481 break; /* Don't disturb normal focus behavior */
3484 return TAB_KeyDown(infoPtr, wParam, lParam);
3487 return TAB_NCHitTest(infoPtr, lParam);
3490 return TAB_NCCalcSize(wParam);
3493 if (uMsg >= WM_USER && uMsg < WM_APP && !COMCTL32_IsReflectedMessage(uMsg))
3494 WARN("unknown msg %04x wp=%08lx lp=%08lx\n",
3495 uMsg, wParam, lParam);
3498 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
3507 ZeroMemory (&wndClass, sizeof(WNDCLASSW));
3508 wndClass.style = CS_GLOBALCLASS | CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
3509 wndClass.lpfnWndProc = TAB_WindowProc;
3510 wndClass.cbClsExtra = 0;
3511 wndClass.cbWndExtra = sizeof(TAB_INFO *);
3512 wndClass.hCursor = LoadCursorW (0, (LPWSTR)IDC_ARROW);
3513 wndClass.hbrBackground = (HBRUSH)(COLOR_BTNFACE+1);
3514 wndClass.lpszClassName = WC_TABCONTROLW;
3516 RegisterClassW (&wndClass);
3521 TAB_Unregister (void)
3523 UnregisterClassW (WC_TABCONTROLW, NULL);