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 else if (iItem < infoPtr->uNumItem) {
275 if (infoPtr->dwStyle & TCS_BUTTONS) {
276 /* set focus to new item, leave selection as is */
277 if (infoPtr->uFocus != iItem) {
278 INT prev_focus = infoPtr->uFocus;
281 infoPtr->uFocus = iItem;
283 if (prev_focus != infoPtr->iSelected) {
284 if (TAB_InternalGetItemRect(infoPtr, prev_focus, &r, NULL))
285 InvalidateRect(infoPtr->hwnd, &r, FALSE);
288 if (TAB_InternalGetItemRect(infoPtr, iItem, &r, NULL))
289 InvalidateRect(infoPtr->hwnd, &r, FALSE);
291 TAB_SendSimpleNotify(infoPtr, TCN_FOCUSCHANGE);
294 INT oldFocus = infoPtr->uFocus;
295 if (infoPtr->iSelected != iItem || oldFocus == -1 ) {
296 infoPtr->uFocus = iItem;
297 if (oldFocus != -1) {
298 if (!TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGING)) {
299 infoPtr->iSelected = iItem;
300 TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGE);
303 infoPtr->iSelected = iItem;
304 TAB_EnsureSelectionVisible(infoPtr);
305 TAB_InvalidateTabArea(infoPtr);
313 static inline LRESULT
314 TAB_SetToolTips (TAB_INFO *infoPtr, HWND hwndToolTip)
316 TRACE("%p %p\n", infoPtr, hwndToolTip);
317 infoPtr->hwndToolTip = hwndToolTip;
321 static inline LRESULT
322 TAB_SetPadding (TAB_INFO *infoPtr, LPARAM lParam)
324 TRACE("(%p %d %d)\n", infoPtr, LOWORD(lParam), HIWORD(lParam));
325 infoPtr->uHItemPadding_s = LOWORD(lParam);
326 infoPtr->uVItemPadding_s = HIWORD(lParam);
331 /******************************************************************************
332 * TAB_InternalGetItemRect
334 * This method will calculate the rectangle representing a given tab item in
335 * client coordinates. This method takes scrolling into account.
337 * This method returns TRUE if the item is visible in the window and FALSE
338 * if it is completely outside the client area.
340 static BOOL TAB_InternalGetItemRect(
341 const TAB_INFO* infoPtr,
346 RECT tmpItemRect,clientRect;
348 /* Perform a sanity check and a trivial visibility check. */
349 if ( (infoPtr->uNumItem <= 0) ||
350 (itemIndex >= infoPtr->uNumItem) ||
351 (!(((infoPtr->dwStyle & TCS_MULTILINE) || (infoPtr->dwStyle & TCS_VERTICAL))) &&
352 (itemIndex < infoPtr->leftmostVisible)))
354 TRACE("Not Visible\n");
355 /* need to initialize these to empty rects */
358 memset(itemRect,0,sizeof(RECT));
359 itemRect->bottom = infoPtr->tabHeight;
362 memset(selectedRect,0,sizeof(RECT));
367 * Avoid special cases in this procedure by assigning the "out"
368 * parameters if the caller didn't supply them
370 if (itemRect == NULL)
371 itemRect = &tmpItemRect;
373 /* Retrieve the unmodified item rect. */
374 *itemRect = TAB_GetItem(infoPtr,itemIndex)->rect;
376 /* calculate the times bottom and top based on the row */
377 GetClientRect(infoPtr->hwnd, &clientRect);
379 if ((infoPtr->dwStyle & TCS_BOTTOM) && (infoPtr->dwStyle & TCS_VERTICAL))
381 itemRect->right = clientRect.right - SELECTED_TAB_OFFSET - itemRect->left * infoPtr->tabHeight -
382 ((infoPtr->dwStyle & TCS_BUTTONS) ? itemRect->left * BUTTON_SPACINGX : 0);
383 itemRect->left = itemRect->right - infoPtr->tabHeight;
385 else if (infoPtr->dwStyle & TCS_VERTICAL)
387 itemRect->left = clientRect.left + SELECTED_TAB_OFFSET + itemRect->left * infoPtr->tabHeight +
388 ((infoPtr->dwStyle & TCS_BUTTONS) ? itemRect->left * BUTTON_SPACINGX : 0);
389 itemRect->right = itemRect->left + infoPtr->tabHeight;
391 else if (infoPtr->dwStyle & TCS_BOTTOM)
393 itemRect->bottom = clientRect.bottom - itemRect->top * infoPtr->tabHeight -
394 ((infoPtr->dwStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : SELECTED_TAB_OFFSET);
395 itemRect->top = itemRect->bottom - infoPtr->tabHeight;
397 else /* not TCS_BOTTOM and not TCS_VERTICAL */
399 itemRect->top = clientRect.top + itemRect->top * infoPtr->tabHeight +
400 ((infoPtr->dwStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : SELECTED_TAB_OFFSET);
401 itemRect->bottom = itemRect->top + infoPtr->tabHeight;
405 * "scroll" it to make sure the item at the very left of the
406 * tab control is the leftmost visible tab.
408 if(infoPtr->dwStyle & TCS_VERTICAL)
412 -TAB_GetItem(infoPtr, infoPtr->leftmostVisible)->rect.top);
415 * Move the rectangle so the first item is slightly offset from
416 * the bottom of the tab control.
420 SELECTED_TAB_OFFSET);
425 -TAB_GetItem(infoPtr, infoPtr->leftmostVisible)->rect.left,
429 * Move the rectangle so the first item is slightly offset from
430 * the left of the tab control.
436 TRACE("item %d tab h=%d, rect=(%s)\n",
437 itemIndex, infoPtr->tabHeight, wine_dbgstr_rect(itemRect));
439 /* Now, calculate the position of the item as if it were selected. */
440 if (selectedRect!=NULL)
442 CopyRect(selectedRect, itemRect);
444 /* The rectangle of a selected item is a bit wider. */
445 if(infoPtr->dwStyle & TCS_VERTICAL)
446 InflateRect(selectedRect, 0, SELECTED_TAB_OFFSET);
448 InflateRect(selectedRect, SELECTED_TAB_OFFSET, 0);
450 /* If it also a bit higher. */
451 if ((infoPtr->dwStyle & TCS_BOTTOM) && (infoPtr->dwStyle & TCS_VERTICAL))
453 selectedRect->left -= 2; /* the border is thicker on the right */
454 selectedRect->right += SELECTED_TAB_OFFSET;
456 else if (infoPtr->dwStyle & TCS_VERTICAL)
458 selectedRect->left -= SELECTED_TAB_OFFSET;
459 selectedRect->right += 1;
461 else if (infoPtr->dwStyle & TCS_BOTTOM)
463 selectedRect->bottom += SELECTED_TAB_OFFSET;
465 else /* not TCS_BOTTOM and not TCS_VERTICAL */
467 selectedRect->top -= SELECTED_TAB_OFFSET;
468 selectedRect->bottom -= 1;
472 /* Check for visibility */
473 if (infoPtr->dwStyle & TCS_VERTICAL)
474 return (itemRect->top < clientRect.bottom) && (itemRect->bottom > clientRect.top);
476 return (itemRect->left < clientRect.right) && (itemRect->right > clientRect.left);
480 TAB_GetItemRect(const TAB_INFO *infoPtr, INT item, RECT *rect)
482 TRACE("(%p, %d, %p)\n", infoPtr, item, rect);
483 return TAB_InternalGetItemRect(infoPtr, item, rect, NULL);
486 /******************************************************************************
489 * This method is called to handle keyboard input
491 static LRESULT TAB_KeyDown(TAB_INFO* infoPtr, WPARAM keyCode, LPARAM lParam)
496 /* TCN_KEYDOWN notification sent always */
497 nm.hdr.hwndFrom = infoPtr->hwnd;
498 nm.hdr.idFrom = GetWindowLongPtrW(infoPtr->hwnd, GWLP_ID);
499 nm.hdr.code = TCN_KEYDOWN;
502 SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, nm.hdr.idFrom, (LPARAM)&nm);
507 newItem = infoPtr->uFocus - 1;
510 newItem = infoPtr->uFocus + 1;
514 /* If we changed to a valid item, change focused item */
515 if (newItem >= 0 && newItem < infoPtr->uNumItem && infoPtr->uFocus != newItem)
516 TAB_SetCurFocus(infoPtr, newItem);
522 * WM_KILLFOCUS handler
524 static void TAB_KillFocus(TAB_INFO *infoPtr)
526 /* clear current focused item back to selected for TCS_BUTTONS */
527 if ((infoPtr->dwStyle & TCS_BUTTONS) && (infoPtr->uFocus != infoPtr->iSelected))
531 if (TAB_InternalGetItemRect(infoPtr, infoPtr->uFocus, &r, NULL))
532 InvalidateRect(infoPtr->hwnd, &r, FALSE);
534 infoPtr->uFocus = infoPtr->iSelected;
538 /******************************************************************************
541 * This method is called whenever the focus goes in or out of this control
542 * it is used to update the visual state of the control.
544 static void TAB_FocusChanging(const TAB_INFO *infoPtr)
550 * Get the rectangle for the item.
552 isVisible = TAB_InternalGetItemRect(infoPtr,
558 * If the rectangle is not completely invisible, invalidate that
559 * portion of the window.
563 TRACE("invalidate (%s)\n", wine_dbgstr_rect(&selectedRect));
564 InvalidateRect(infoPtr->hwnd, &selectedRect, TRUE);
568 static INT TAB_InternalHitTest (const TAB_INFO *infoPtr, POINT pt, UINT *flags)
573 for (iCount = 0; iCount < infoPtr->uNumItem; iCount++)
575 TAB_InternalGetItemRect(infoPtr, iCount, &rect, NULL);
577 if (PtInRect(&rect, pt))
579 *flags = TCHT_ONITEM;
584 *flags = TCHT_NOWHERE;
588 static inline LRESULT
589 TAB_HitTest (const TAB_INFO *infoPtr, LPTCHITTESTINFO lptest)
591 TRACE("(%p, %p)\n", infoPtr, lptest);
592 return TAB_InternalHitTest (infoPtr, lptest->pt, &lptest->flags);
595 /******************************************************************************
598 * Napster v2b5 has a tab control for its main navigation which has a client
599 * area that covers the whole area of the dialog pages.
600 * That's why it receives all msgs for that area and the underlying dialog ctrls
602 * So I decided that we should handle WM_NCHITTEST here and return
603 * HTTRANSPARENT if we don't hit the tab control buttons.
604 * FIXME: WM_NCHITTEST handling correct ? Fix it if you know that Windows
605 * doesn't do it that way. Maybe depends on tab control styles ?
607 static inline LRESULT
608 TAB_NCHitTest (const TAB_INFO *infoPtr, LPARAM lParam)
613 pt.x = (short)LOWORD(lParam);
614 pt.y = (short)HIWORD(lParam);
615 ScreenToClient(infoPtr->hwnd, &pt);
617 if (TAB_InternalHitTest(infoPtr, pt, &dummyflag) == -1)
618 return HTTRANSPARENT;
624 TAB_LButtonDown (TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
630 if (infoPtr->hwndToolTip)
631 TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
632 WM_LBUTTONDOWN, wParam, lParam);
634 if (!(infoPtr->dwStyle & TCS_FOCUSNEVER)) {
635 SetFocus (infoPtr->hwnd);
638 if (infoPtr->hwndToolTip)
639 TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
640 WM_LBUTTONDOWN, wParam, lParam);
642 pt.x = (short)LOWORD(lParam);
643 pt.y = (short)HIWORD(lParam);
645 newItem = TAB_InternalHitTest (infoPtr, pt, &dummy);
647 TRACE("On Tab, item %d\n", newItem);
649 if ((newItem != -1) && (infoPtr->iSelected != newItem))
651 if ((infoPtr->dwStyle & TCS_BUTTONS) && (infoPtr->dwStyle & TCS_MULTISELECT) &&
652 (wParam & MK_CONTROL))
656 /* toggle multiselection */
657 TAB_GetItem(infoPtr, newItem)->dwState ^= TCIS_BUTTONPRESSED;
658 if (TAB_InternalGetItemRect (infoPtr, newItem, &r, NULL))
659 InvalidateRect (infoPtr->hwnd, &r, TRUE);
664 BOOL pressed = FALSE;
666 /* any button pressed ? */
667 for (i = 0; i < infoPtr->uNumItem; i++)
668 if ((TAB_GetItem (infoPtr, i)->dwState & TCIS_BUTTONPRESSED) &&
669 (infoPtr->iSelected != i))
675 TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGING);
678 TAB_DeselectAll (infoPtr, FALSE);
680 TAB_SetCurSel(infoPtr, newItem);
682 TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGE);
689 static inline LRESULT
690 TAB_LButtonUp (const TAB_INFO *infoPtr)
692 TAB_SendSimpleNotify(infoPtr, NM_CLICK);
697 static inline LRESULT
698 TAB_RButtonDown (const TAB_INFO *infoPtr)
700 TAB_SendSimpleNotify(infoPtr, NM_RCLICK);
704 /******************************************************************************
705 * TAB_DrawLoneItemInterior
707 * This calls TAB_DrawItemInterior. However, TAB_DrawItemInterior is normally
708 * called by TAB_DrawItem which is normally called by TAB_Refresh which sets
709 * up the device context and font. This routine does the same setup but
710 * only calls TAB_DrawItemInterior for the single specified item.
713 TAB_DrawLoneItemInterior(const TAB_INFO* infoPtr, int iItem)
715 HDC hdc = GetDC(infoPtr->hwnd);
718 /* Clip UpDown control to not draw over it */
719 if (infoPtr->needsScrolling)
721 GetWindowRect(infoPtr->hwnd, &rC);
722 GetWindowRect(infoPtr->hwndUpDown, &r);
723 ExcludeClipRect(hdc, r.left - rC.left, r.top - rC.top, r.right - rC.left, r.bottom - rC.top);
725 TAB_DrawItemInterior(infoPtr, hdc, iItem, NULL);
726 ReleaseDC(infoPtr->hwnd, hdc);
729 /* update a tab after hottracking - invalidate it or just redraw the interior,
730 * based on whether theming is used or not */
731 static inline void hottrack_refresh(const TAB_INFO *infoPtr, int tabIndex)
733 if (tabIndex == -1) return;
735 if (GetWindowTheme (infoPtr->hwnd))
738 TAB_InternalGetItemRect(infoPtr, tabIndex, &rect, NULL);
739 InvalidateRect (infoPtr->hwnd, &rect, FALSE);
742 TAB_DrawLoneItemInterior(infoPtr, tabIndex);
745 /******************************************************************************
746 * TAB_HotTrackTimerProc
748 * When a mouse-move event causes a tab to be highlighted (hot-tracking), a
749 * timer is setup so we can check if the mouse is moved out of our window.
750 * (We don't get an event when the mouse leaves, the mouse-move events just
751 * stop being delivered to our window and just start being delivered to
752 * another window.) This function is called when the timer triggers so
753 * we can check if the mouse has left our window. If so, we un-highlight
754 * the hot-tracked tab.
757 TAB_HotTrackTimerProc
759 HWND hwnd, /* handle of window for timer messages */
760 UINT uMsg, /* WM_TIMER message */
761 UINT_PTR idEvent, /* timer identifier */
762 DWORD dwTime /* current system time */
765 TAB_INFO* infoPtr = TAB_GetInfoPtr(hwnd);
767 if (infoPtr != NULL && infoPtr->iHotTracked >= 0)
772 ** If we can't get the cursor position, or if the cursor is outside our
773 ** window, we un-highlight the hot-tracked tab. Note that the cursor is
774 ** "outside" even if it is within our bounding rect if another window
775 ** overlaps. Note also that the case where the cursor stayed within our
776 ** window but has moved off the hot-tracked tab will be handled by the
777 ** WM_MOUSEMOVE event.
779 if (!GetCursorPos(&pt) || WindowFromPoint(pt) != hwnd)
781 /* Redraw iHotTracked to look normal */
782 INT iRedraw = infoPtr->iHotTracked;
783 infoPtr->iHotTracked = -1;
784 hottrack_refresh (infoPtr, iRedraw);
786 /* Kill this timer */
787 KillTimer(hwnd, TAB_HOTTRACK_TIMER);
792 /******************************************************************************
795 * If a tab control has the TCS_HOTTRACK style, then the tab under the mouse
796 * should be highlighted. This function determines which tab in a tab control,
797 * if any, is under the mouse and records that information. The caller may
798 * supply output parameters to receive the item number of the tab item which
799 * was highlighted but isn't any longer and of the tab item which is now
800 * highlighted but wasn't previously. The caller can use this information to
801 * selectively redraw those tab items.
803 * If the caller has a mouse position, it can supply it through the pos
804 * parameter. For example, TAB_MouseMove does this. Otherwise, the caller
805 * supplies NULL and this function determines the current mouse position
813 int* out_redrawLeave,
820 if (out_redrawLeave != NULL)
821 *out_redrawLeave = -1;
822 if (out_redrawEnter != NULL)
823 *out_redrawEnter = -1;
825 if ((infoPtr->dwStyle & TCS_HOTTRACK) || GetWindowTheme(infoPtr->hwnd))
833 ScreenToClient(infoPtr->hwnd, &pt);
837 pt.x = (short)LOWORD(*pos);
838 pt.y = (short)HIWORD(*pos);
841 item = TAB_InternalHitTest(infoPtr, pt, &flags);
844 if (item != infoPtr->iHotTracked)
846 if (infoPtr->iHotTracked >= 0)
848 /* Mark currently hot-tracked to be redrawn to look normal */
849 if (out_redrawLeave != NULL)
850 *out_redrawLeave = infoPtr->iHotTracked;
854 /* Kill timer which forces recheck of mouse pos */
855 KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
860 /* Start timer so we recheck mouse pos */
861 UINT timerID = SetTimer
865 TAB_HOTTRACK_TIMER_INTERVAL,
866 TAB_HotTrackTimerProc
870 return; /* Hot tracking not available */
873 infoPtr->iHotTracked = item;
877 /* Mark new hot-tracked to be redrawn to look highlighted */
878 if (out_redrawEnter != NULL)
879 *out_redrawEnter = item;
884 /******************************************************************************
887 * Handles the mouse-move event. Updates tooltips. Updates hot-tracking.
890 TAB_MouseMove (TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
895 if (infoPtr->hwndToolTip)
896 TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
897 WM_LBUTTONDOWN, wParam, lParam);
899 /* Determine which tab to highlight. Redraw tabs which change highlight
901 TAB_RecalcHotTrack(infoPtr, &lParam, &redrawLeave, &redrawEnter);
903 hottrack_refresh (infoPtr, redrawLeave);
904 hottrack_refresh (infoPtr, redrawEnter);
909 /******************************************************************************
912 * Calculates the tab control's display area given the window rectangle or
913 * the window rectangle given the requested display rectangle.
915 static LRESULT TAB_AdjustRect(const TAB_INFO *infoPtr, WPARAM fLarger, LPRECT prc)
917 LONG *iRightBottom, *iLeftTop;
919 TRACE ("hwnd=%p fLarger=%ld (%s)\n", infoPtr->hwnd, fLarger,
920 wine_dbgstr_rect(prc));
924 if(infoPtr->dwStyle & TCS_VERTICAL)
926 iRightBottom = &(prc->right);
927 iLeftTop = &(prc->left);
931 iRightBottom = &(prc->bottom);
932 iLeftTop = &(prc->top);
935 if (fLarger) /* Go from display rectangle */
937 /* Add the height of the tabs. */
938 if (infoPtr->dwStyle & TCS_BOTTOM)
939 *iRightBottom += infoPtr->tabHeight * infoPtr->uNumRows;
941 *iLeftTop -= infoPtr->tabHeight * infoPtr->uNumRows +
942 ((infoPtr->dwStyle & TCS_BUTTONS)? 3 * (infoPtr->uNumRows - 1) : 0);
944 /* Inflate the rectangle for the padding */
945 InflateRect(prc, DISPLAY_AREA_PADDINGX, DISPLAY_AREA_PADDINGY);
947 /* Inflate for the border */
948 InflateRect(prc, CONTROL_BORDER_SIZEX, CONTROL_BORDER_SIZEY);
950 else /* Go from window rectangle. */
952 /* Deflate the rectangle for the border */
953 InflateRect(prc, -CONTROL_BORDER_SIZEX, -CONTROL_BORDER_SIZEY);
955 /* Deflate the rectangle for the padding */
956 InflateRect(prc, -DISPLAY_AREA_PADDINGX, -DISPLAY_AREA_PADDINGY);
958 /* Remove the height of the tabs. */
959 if (infoPtr->dwStyle & TCS_BOTTOM)
960 *iRightBottom -= infoPtr->tabHeight * infoPtr->uNumRows;
962 *iLeftTop += (infoPtr->tabHeight) * infoPtr->uNumRows +
963 ((infoPtr->dwStyle & TCS_BUTTONS)? 3 * (infoPtr->uNumRows - 1) : 0);
969 /******************************************************************************
972 * This method will handle the notification from the scroll control and
973 * perform the scrolling operation on the tab control.
975 static LRESULT TAB_OnHScroll(TAB_INFO *infoPtr, int nScrollCode, int nPos)
977 if(nScrollCode == SB_THUMBPOSITION && nPos != infoPtr->leftmostVisible)
979 if(nPos < infoPtr->leftmostVisible)
980 infoPtr->leftmostVisible--;
982 infoPtr->leftmostVisible++;
984 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
985 TAB_InvalidateTabArea(infoPtr);
986 SendMessageW(infoPtr->hwndUpDown, UDM_SETPOS, 0,
987 MAKELONG(infoPtr->leftmostVisible, 0));
993 /******************************************************************************
996 * This method will check the current scrolling state and make sure the
997 * scrolling control is displayed (or not).
999 static void TAB_SetupScrolling(
1001 const RECT* clientRect)
1003 static const WCHAR emptyW[] = { 0 };
1006 if (infoPtr->needsScrolling)
1009 INT vsize, tabwidth;
1012 * Calculate the position of the scroll control.
1014 if(infoPtr->dwStyle & TCS_VERTICAL)
1016 controlPos.right = clientRect->right;
1017 controlPos.left = controlPos.right - 2 * GetSystemMetrics(SM_CXHSCROLL);
1019 if (infoPtr->dwStyle & TCS_BOTTOM)
1021 controlPos.top = clientRect->bottom - infoPtr->tabHeight;
1022 controlPos.bottom = controlPos.top + GetSystemMetrics(SM_CYHSCROLL);
1026 controlPos.bottom = clientRect->top + infoPtr->tabHeight;
1027 controlPos.top = controlPos.bottom - GetSystemMetrics(SM_CYHSCROLL);
1032 controlPos.right = clientRect->right;
1033 controlPos.left = controlPos.right - 2 * GetSystemMetrics(SM_CXHSCROLL);
1035 if (infoPtr->dwStyle & TCS_BOTTOM)
1037 controlPos.top = clientRect->bottom - infoPtr->tabHeight;
1038 controlPos.bottom = controlPos.top + GetSystemMetrics(SM_CYHSCROLL);
1042 controlPos.bottom = clientRect->top + infoPtr->tabHeight;
1043 controlPos.top = controlPos.bottom - GetSystemMetrics(SM_CYHSCROLL);
1048 * If we don't have a scroll control yet, we want to create one.
1049 * If we have one, we want to make sure it's positioned properly.
1051 if (infoPtr->hwndUpDown==0)
1053 infoPtr->hwndUpDown = CreateWindowW(UPDOWN_CLASSW, emptyW,
1054 WS_VISIBLE | WS_CHILD | UDS_HORZ,
1055 controlPos.left, controlPos.top,
1056 controlPos.right - controlPos.left,
1057 controlPos.bottom - controlPos.top,
1058 infoPtr->hwnd, NULL, NULL, NULL);
1062 SetWindowPos(infoPtr->hwndUpDown,
1064 controlPos.left, controlPos.top,
1065 controlPos.right - controlPos.left,
1066 controlPos.bottom - controlPos.top,
1067 SWP_SHOWWINDOW | SWP_NOZORDER);
1070 /* Now calculate upper limit of the updown control range.
1071 * We do this by calculating how many tabs will be offscreen when the
1072 * last tab is visible.
1074 if(infoPtr->uNumItem)
1076 vsize = clientRect->right - (controlPos.right - controlPos.left + 1);
1077 maxRange = infoPtr->uNumItem;
1078 tabwidth = TAB_GetItem(infoPtr, infoPtr->uNumItem - 1)->rect.right;
1080 for(; maxRange > 0; maxRange--)
1082 if(tabwidth - TAB_GetItem(infoPtr,maxRange - 1)->rect.left > vsize)
1086 if(maxRange == infoPtr->uNumItem)
1092 /* If we once had a scroll control... hide it */
1093 if (infoPtr->hwndUpDown)
1094 ShowWindow(infoPtr->hwndUpDown, SW_HIDE);
1096 if (infoPtr->hwndUpDown)
1097 SendMessageW(infoPtr->hwndUpDown, UDM_SETRANGE32, 0, maxRange);
1100 /******************************************************************************
1103 * This method will calculate the position rectangles of all the items in the
1104 * control. The rectangle calculated starts at 0 for the first item in the
1105 * list and ignores scrolling and selection.
1106 * It also uses the current font to determine the height of the tab row and
1107 * it checks if all the tabs fit in the client area of the window. If they
1108 * don't, a scrolling control is added.
1110 static void TAB_SetItemBounds (TAB_INFO *infoPtr)
1112 TEXTMETRICW fontMetrics;
1115 INT curItemRowCount;
1116 HFONT hFont, hOldFont;
1125 * We need to get text information so we need a DC and we need to select
1128 hdc = GetDC(infoPtr->hwnd);
1130 hFont = infoPtr->hFont ? infoPtr->hFont : GetStockObject (SYSTEM_FONT);
1131 hOldFont = SelectObject (hdc, hFont);
1134 * We will base the rectangle calculations on the client rectangle
1137 GetClientRect(infoPtr->hwnd, &clientRect);
1139 /* if TCS_VERTICAL then swap the height and width so this code places the
1140 tabs along the top of the rectangle and we can just rotate them after
1141 rather than duplicate all of the below code */
1142 if(infoPtr->dwStyle & TCS_VERTICAL)
1144 iTemp = clientRect.bottom;
1145 clientRect.bottom = clientRect.right;
1146 clientRect.right = iTemp;
1149 /* Now use hPadding and vPadding */
1150 infoPtr->uHItemPadding = infoPtr->uHItemPadding_s;
1151 infoPtr->uVItemPadding = infoPtr->uVItemPadding_s;
1153 /* The leftmost item will be "0" aligned */
1155 curItemRowCount = infoPtr->uNumItem ? 1 : 0;
1157 if (!(infoPtr->fHeightSet))
1160 int icon_height = 0;
1162 /* Use the current font to determine the height of a tab. */
1163 GetTextMetricsW(hdc, &fontMetrics);
1165 /* Get the icon height */
1167 ImageList_GetIconSize(infoPtr->himl, 0, &icon_height);
1169 /* Take the highest between font or icon */
1170 if (fontMetrics.tmHeight > icon_height)
1171 item_height = fontMetrics.tmHeight + 2;
1173 item_height = icon_height;
1176 * Make sure there is enough space for the letters + icon + growing the
1177 * selected item + extra space for the selected item.
1179 infoPtr->tabHeight = item_height +
1180 ((infoPtr->dwStyle & TCS_BUTTONS) ? 2 : 1) *
1181 infoPtr->uVItemPadding;
1183 TRACE("tabH=%d, tmH=%d, iconh=%d\n",
1184 infoPtr->tabHeight, fontMetrics.tmHeight, icon_height);
1187 TRACE("client right=%d\n", clientRect.right);
1189 /* Get the icon width */
1192 ImageList_GetIconSize(infoPtr->himl, &icon_width, 0);
1194 if (infoPtr->dwStyle & TCS_FIXEDWIDTH)
1197 /* Add padding if icon is present */
1198 icon_width += infoPtr->uHItemPadding;
1201 for (curItem = 0; curItem < infoPtr->uNumItem; curItem++)
1203 TAB_ITEM *curr = TAB_GetItem(infoPtr, curItem);
1205 /* Set the leftmost position of the tab. */
1206 curr->rect.left = curItemLeftPos;
1208 if (infoPtr->dwStyle & TCS_FIXEDWIDTH)
1210 curr->rect.right = curr->rect.left +
1211 max(infoPtr->tabWidth, icon_width);
1213 else if (!curr->pszText)
1215 /* If no text use minimum tab width including padding. */
1216 if (infoPtr->tabMinWidth < 0)
1217 curr->rect.right = curr->rect.left + GET_DEFAULT_MIN_TAB_WIDTH(infoPtr);
1220 curr->rect.right = curr->rect.left + infoPtr->tabMinWidth;
1222 /* Add extra padding if icon is present */
1223 if (infoPtr->himl && infoPtr->tabMinWidth > 0 && infoPtr->tabMinWidth < DEFAULT_MIN_TAB_WIDTH
1224 && infoPtr->uHItemPadding > 1)
1225 curr->rect.right += EXTRA_ICON_PADDING * (infoPtr->uHItemPadding-1);
1232 /* Calculate how wide the tab is depending on the text it contains */
1233 GetTextExtentPoint32W(hdc, curr->pszText,
1234 lstrlenW(curr->pszText), &size);
1236 tabwidth = size.cx + icon_width + 2 * infoPtr->uHItemPadding;
1238 if (infoPtr->tabMinWidth < 0)
1239 tabwidth = max(tabwidth, GET_DEFAULT_MIN_TAB_WIDTH(infoPtr));
1241 tabwidth = max(tabwidth, infoPtr->tabMinWidth);
1243 curr->rect.right = curr->rect.left + tabwidth;
1244 TRACE("for <%s>, l,r=%d,%d\n",
1245 debugstr_w(curr->pszText), curr->rect.left, curr->rect.right);
1249 * Check if this is a multiline tab control and if so
1250 * check to see if we should wrap the tabs
1252 * Wrap all these tabs. We will arrange them evenly later.
1256 if (((infoPtr->dwStyle & TCS_MULTILINE) || (infoPtr->dwStyle & TCS_VERTICAL)) &&
1258 (clientRect.right - CONTROL_BORDER_SIZEX - DISPLAY_AREA_PADDINGX)))
1260 curr->rect.right -= curr->rect.left;
1262 curr->rect.left = 0;
1264 TRACE("wrapping <%s>, l,r=%d,%d\n", debugstr_w(curr->pszText),
1265 curr->rect.left, curr->rect.right);
1268 curr->rect.bottom = 0;
1269 curr->rect.top = curItemRowCount - 1;
1271 TRACE("Rect: %s\n", wine_dbgstr_rect(&curr->rect));
1274 * The leftmost position of the next item is the rightmost position
1277 if (infoPtr->dwStyle & TCS_BUTTONS)
1279 curItemLeftPos = curr->rect.right + BUTTON_SPACINGX;
1280 if (infoPtr->dwStyle & TCS_FLATBUTTONS)
1281 curItemLeftPos += FLAT_BTN_SPACINGX;
1284 curItemLeftPos = curr->rect.right;
1287 if (!((infoPtr->dwStyle & TCS_MULTILINE) || (infoPtr->dwStyle & TCS_VERTICAL)))
1290 * Check if we need a scrolling control.
1292 infoPtr->needsScrolling = (curItemLeftPos + (2 * SELECTED_TAB_OFFSET) >
1295 /* Don't need scrolling, then update infoPtr->leftmostVisible */
1296 if(!infoPtr->needsScrolling)
1297 infoPtr->leftmostVisible = 0;
1302 * No scrolling in Multiline or Vertical styles.
1304 infoPtr->needsScrolling = FALSE;
1305 infoPtr->leftmostVisible = 0;
1307 TAB_SetupScrolling(infoPtr, &clientRect);
1309 /* Set the number of rows */
1310 infoPtr->uNumRows = curItemRowCount;
1312 /* Arrange all tabs evenly if style says so */
1313 if (!(infoPtr->dwStyle & TCS_RAGGEDRIGHT) &&
1314 ((infoPtr->dwStyle & TCS_MULTILINE) || (infoPtr->dwStyle & TCS_VERTICAL)) &&
1315 (infoPtr->uNumItem > 0) &&
1316 (infoPtr->uNumRows > 1))
1318 INT tabPerRow,remTab,iRow;
1323 * Ok windows tries to even out the rows. place the same
1324 * number of tabs in each row. So lets give that a shot
1327 tabPerRow = infoPtr->uNumItem / (infoPtr->uNumRows);
1328 remTab = infoPtr->uNumItem % (infoPtr->uNumRows);
1330 for (iItm=0,iRow=0,iCount=0,curItemLeftPos=0;
1331 iItm<infoPtr->uNumItem;
1334 /* normalize the current rect */
1335 TAB_ITEM *curr = TAB_GetItem(infoPtr, iItm);
1337 /* shift the item to the left side of the clientRect */
1338 curr->rect.right -= curr->rect.left;
1339 curr->rect.left = 0;
1341 TRACE("r=%d, cl=%d, cl.r=%d, iCount=%d, iRow=%d, uNumRows=%d, remTab=%d, tabPerRow=%d\n",
1342 curr->rect.right, curItemLeftPos, clientRect.right,
1343 iCount, iRow, infoPtr->uNumRows, remTab, tabPerRow);
1345 /* if we have reached the maximum number of tabs on this row */
1346 /* move to the next row, reset our current item left position and */
1347 /* the count of items on this row */
1349 if (infoPtr->dwStyle & TCS_VERTICAL) {
1350 /* Vert: Add the remaining tabs in the *last* remainder rows */
1351 if (iCount >= ((iRow>=(INT)infoPtr->uNumRows - remTab)?tabPerRow + 1:tabPerRow)) {
1357 /* Horz: Add the remaining tabs in the *first* remainder rows */
1358 if (iCount >= ((iRow<remTab)?tabPerRow + 1:tabPerRow)) {
1365 /* shift the item to the right to place it as the next item in this row */
1366 curr->rect.left += curItemLeftPos;
1367 curr->rect.right += curItemLeftPos;
1368 curr->rect.top = iRow;
1369 if (infoPtr->dwStyle & TCS_BUTTONS)
1371 curItemLeftPos = curr->rect.right + 1;
1372 if (infoPtr->dwStyle & TCS_FLATBUTTONS)
1373 curItemLeftPos += FLAT_BTN_SPACINGX;
1376 curItemLeftPos = curr->rect.right;
1378 TRACE("arranging <%s>, l,r=%d,%d, row=%d\n",
1379 debugstr_w(curr->pszText), curr->rect.left,
1380 curr->rect.right, curr->rect.top);
1387 INT widthDiff, iIndexStart=0, iIndexEnd=0;
1391 while(iIndexStart < infoPtr->uNumItem)
1393 TAB_ITEM *start = TAB_GetItem(infoPtr, iIndexStart);
1396 * find the index of the row
1398 /* find the first item on the next row */
1399 for (iIndexEnd=iIndexStart;
1400 (iIndexEnd < infoPtr->uNumItem) &&
1401 (TAB_GetItem(infoPtr, iIndexEnd)->rect.top ==
1404 /* intentionally blank */;
1407 * we need to justify these tabs so they fill the whole given
1411 /* find the amount of space remaining on this row */
1412 widthDiff = clientRect.right - (2 * SELECTED_TAB_OFFSET) -
1413 TAB_GetItem(infoPtr, iIndexEnd - 1)->rect.right;
1415 /* iCount is the number of tab items on this row */
1416 iCount = iIndexEnd - iIndexStart;
1420 remainder = widthDiff % iCount;
1421 widthDiff = widthDiff / iCount;
1422 /* add widthDiff/iCount, or extra space/items on row, to each item on this row */
1423 for (iIndex=iIndexStart, iCount=0; iIndex < iIndexEnd; iIndex++, iCount++)
1425 TAB_ITEM *item = TAB_GetItem(infoPtr, iIndex);
1427 item->rect.left += iCount * widthDiff;
1428 item->rect.right += (iCount + 1) * widthDiff;
1430 TRACE("adjusting 1 <%s>, l,r=%d,%d\n",
1431 debugstr_w(item->pszText),
1432 item->rect.left, item->rect.right);
1435 TAB_GetItem(infoPtr, iIndex - 1)->rect.right += remainder;
1437 else /* we have only one item on this row, make it take up the entire row */
1439 start->rect.left = clientRect.left;
1440 start->rect.right = clientRect.right - 4;
1442 TRACE("adjusting 2 <%s>, l,r=%d,%d\n",
1443 debugstr_w(start->pszText),
1444 start->rect.left, start->rect.right);
1449 iIndexStart = iIndexEnd;
1454 /* if TCS_VERTICAL rotate the tabs so they are along the side of the clientRect */
1455 if(infoPtr->dwStyle & TCS_VERTICAL)
1458 for(iIndex = 0; iIndex < infoPtr->uNumItem; iIndex++)
1460 rcItem = &TAB_GetItem(infoPtr, iIndex)->rect;
1462 rcOriginal = *rcItem;
1464 /* this is rotating the items by 90 degrees clockwise around the center of the control */
1465 rcItem->top = (rcOriginal.left - clientRect.left);
1466 rcItem->bottom = rcItem->top + (rcOriginal.right - rcOriginal.left);
1467 rcItem->left = rcOriginal.top;
1468 rcItem->right = rcOriginal.bottom;
1472 TAB_EnsureSelectionVisible(infoPtr);
1473 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
1476 SelectObject (hdc, hOldFont);
1477 ReleaseDC (infoPtr->hwnd, hdc);
1482 TAB_EraseTabInterior(const TAB_INFO *infoPtr, HDC hdc, INT iItem, const RECT *drawRect)
1484 HBRUSH hbr = CreateSolidBrush (comctl32_color.clrBtnFace);
1485 BOOL deleteBrush = TRUE;
1486 RECT rTemp = *drawRect;
1488 if (infoPtr->dwStyle & TCS_BUTTONS)
1490 if (iItem == infoPtr->iSelected)
1492 /* Background color */
1493 if (!(infoPtr->dwStyle & TCS_OWNERDRAWFIXED))
1496 hbr = GetSysColorBrush(COLOR_SCROLLBAR);
1498 SetTextColor(hdc, comctl32_color.clr3dFace);
1499 SetBkColor(hdc, comctl32_color.clr3dHilight);
1501 /* if COLOR_WINDOW happens to be the same as COLOR_3DHILIGHT
1502 * we better use 0x55aa bitmap brush to make scrollbar's background
1503 * look different from the window background.
1505 if (comctl32_color.clr3dHilight == comctl32_color.clrWindow)
1506 hbr = COMCTL32_hPattern55AABrush;
1508 deleteBrush = FALSE;
1510 FillRect(hdc, &rTemp, hbr);
1512 else /* ! selected */
1514 if (infoPtr->dwStyle & TCS_FLATBUTTONS)
1516 InflateRect(&rTemp, 2, 2);
1517 FillRect(hdc, &rTemp, hbr);
1518 if (iItem == infoPtr->iHotTracked ||
1519 (iItem != infoPtr->iSelected && iItem == infoPtr->uFocus))
1520 DrawEdge(hdc, &rTemp, BDR_RAISEDINNER, BF_RECT);
1523 FillRect(hdc, &rTemp, hbr);
1527 else /* !TCS_BUTTONS */
1529 InflateRect(&rTemp, -2, -2);
1530 if (!GetWindowTheme (infoPtr->hwnd))
1531 FillRect(hdc, &rTemp, hbr);
1534 /* highlighting is drawn on top of previous fills */
1535 if (TAB_GetItem(infoPtr, iItem)->dwState & TCIS_HIGHLIGHTED)
1540 deleteBrush = FALSE;
1542 hbr = GetSysColorBrush(COLOR_HIGHLIGHT);
1543 FillRect(hdc, &rTemp, hbr);
1547 if (deleteBrush) DeleteObject(hbr);
1550 /******************************************************************************
1551 * TAB_DrawItemInterior
1553 * This method is used to draw the interior (text and icon) of a single tab
1554 * into the tab control.
1557 TAB_DrawItemInterior(const TAB_INFO *infoPtr, HDC hdc, INT iItem, RECT *drawRect)
1566 /* if (drawRect == NULL) */
1573 * Get the rectangle for the item.
1575 isVisible = TAB_InternalGetItemRect(infoPtr, iItem, &itemRect, &selectedRect);
1580 * Make sure drawRect points to something valid; simplifies code.
1582 drawRect = &localRect;
1585 * This logic copied from the part of TAB_DrawItem which draws
1586 * the tab background. It's important to keep it in sync. I
1587 * would have liked to avoid code duplication, but couldn't figure
1588 * out how without making spaghetti of TAB_DrawItem.
1590 if (iItem == infoPtr->iSelected)
1591 *drawRect = selectedRect;
1593 *drawRect = itemRect;
1595 if (infoPtr->dwStyle & TCS_BUTTONS)
1597 if (iItem == infoPtr->iSelected)
1599 drawRect->left += 4;
1601 drawRect->right -= 4;
1603 if (infoPtr->dwStyle & TCS_VERTICAL)
1605 if (!(infoPtr->dwStyle & TCS_BOTTOM)) drawRect->right += 1;
1606 drawRect->bottom -= 4;
1610 if (infoPtr->dwStyle & TCS_BOTTOM)
1613 drawRect->bottom -= 4;
1616 drawRect->bottom -= 1;
1621 drawRect->left += 2;
1623 drawRect->right -= 2;
1624 drawRect->bottom -= 2;
1629 if ((infoPtr->dwStyle & TCS_VERTICAL) && (infoPtr->dwStyle & TCS_BOTTOM))
1631 if (iItem != infoPtr->iSelected)
1633 drawRect->left += 2;
1635 drawRect->bottom -= 2;
1638 else if (infoPtr->dwStyle & TCS_VERTICAL)
1640 if (iItem == infoPtr->iSelected)
1642 drawRect->right += 1;
1647 drawRect->right -= 2;
1648 drawRect->bottom -= 2;
1651 else if (infoPtr->dwStyle & TCS_BOTTOM)
1653 if (iItem == infoPtr->iSelected)
1659 InflateRect(drawRect, -2, -2);
1660 drawRect->bottom += 2;
1665 if (iItem == infoPtr->iSelected)
1667 drawRect->bottom += 3;
1671 drawRect->bottom -= 2;
1672 InflateRect(drawRect, -2, 0);
1677 TRACE("drawRect=(%s)\n", wine_dbgstr_rect(drawRect));
1679 /* Clear interior */
1680 TAB_EraseTabInterior (infoPtr, hdc, iItem, drawRect);
1682 /* Draw the focus rectangle */
1683 if (!(infoPtr->dwStyle & TCS_FOCUSNEVER) &&
1684 (GetFocus() == infoPtr->hwnd) &&
1685 (iItem == infoPtr->uFocus) )
1687 RECT rFocus = *drawRect;
1689 if (!(infoPtr->dwStyle & TCS_BUTTONS)) InflateRect(&rFocus, -3, -3);
1690 if (infoPtr->dwStyle & TCS_BOTTOM && !(infoPtr->dwStyle & TCS_VERTICAL))
1693 /* focus should stay on selected item for TCS_BUTTONS style */
1694 if (!((infoPtr->dwStyle & TCS_BUTTONS) && (infoPtr->iSelected != iItem)))
1695 DrawFocusRect(hdc, &rFocus);
1701 htextPen = CreatePen( PS_SOLID, 1, comctl32_color.clrBtnText );
1702 holdPen = SelectObject(hdc, htextPen);
1703 hOldFont = SelectObject(hdc, infoPtr->hFont);
1706 * Setup for text output
1708 oldBkMode = SetBkMode(hdc, TRANSPARENT);
1709 if (!GetWindowTheme (infoPtr->hwnd) || (infoPtr->dwStyle & TCS_BUTTONS))
1711 if ((infoPtr->dwStyle & TCS_HOTTRACK) && (iItem == infoPtr->iHotTracked) &&
1712 !(infoPtr->dwStyle & TCS_FLATBUTTONS))
1713 SetTextColor(hdc, comctl32_color.clrHighlight);
1714 else if (TAB_GetItem(infoPtr, iItem)->dwState & TCIS_HIGHLIGHTED)
1715 SetTextColor(hdc, comctl32_color.clrHighlightText);
1717 SetTextColor(hdc, comctl32_color.clrBtnText);
1721 * if owner draw, tell the owner to draw
1723 if ((infoPtr->dwStyle & TCS_OWNERDRAWFIXED) && GetParent(infoPtr->hwnd))
1729 drawRect->right -= 1;
1730 if ( iItem == infoPtr->iSelected )
1732 drawRect->right -= 1;
1733 drawRect->left += 1;
1737 * get the control id
1739 id = (UINT)GetWindowLongPtrW( infoPtr->hwnd, GWLP_ID );
1742 * put together the DRAWITEMSTRUCT
1744 dis.CtlType = ODT_TAB;
1747 dis.itemAction = ODA_DRAWENTIRE;
1749 if ( iItem == infoPtr->iSelected )
1750 dis.itemState |= ODS_SELECTED;
1751 if (infoPtr->uFocus == iItem)
1752 dis.itemState |= ODS_FOCUS;
1753 dis.hwndItem = infoPtr->hwnd;
1755 CopyRect(&dis.rcItem,drawRect);
1756 dis.itemData = (ULONG_PTR)TAB_GetItem(infoPtr, iItem)->extra;
1759 * send the draw message
1761 SendMessageW( infoPtr->hwndNotify, WM_DRAWITEM, (WPARAM)id, (LPARAM)&dis );
1765 TAB_ITEM *item = TAB_GetItem(infoPtr, iItem);
1769 /* used to center the icon and text in the tab */
1771 INT center_offset_h, center_offset_v;
1773 /* set rcImage to drawRect, we will use top & left in our ImageList_Draw call */
1774 rcImage = *drawRect;
1778 rcText.left = rcText.top = rcText.right = rcText.bottom = 0;
1780 /* get the rectangle that the text fits in */
1783 DrawTextW(hdc, item->pszText, -1, &rcText, DT_CALCRECT);
1786 * If not owner draw, then do the drawing ourselves.
1790 if (infoPtr->himl && item->iImage != -1)
1795 ImageList_GetIconSize(infoPtr->himl, &cx, &cy);
1797 if(infoPtr->dwStyle & TCS_VERTICAL)
1799 center_offset_h = ((drawRect->bottom - drawRect->top) - (cy + infoPtr->uHItemPadding + (rcText.right - rcText.left))) / 2;
1800 center_offset_v = ((drawRect->right - drawRect->left) - cx) / 2;
1804 center_offset_h = ((drawRect->right - drawRect->left) - (cx + infoPtr->uHItemPadding + (rcText.right - rcText.left))) / 2;
1805 center_offset_v = ((drawRect->bottom - drawRect->top) - cy) / 2;
1808 /* if an item is selected, the icon is shifted up instead of down */
1809 if (iItem == infoPtr->iSelected)
1810 center_offset_v -= infoPtr->uVItemPadding / 2;
1812 center_offset_v += infoPtr->uVItemPadding / 2;
1814 if (infoPtr->dwStyle & TCS_FIXEDWIDTH && infoPtr->dwStyle & (TCS_FORCELABELLEFT | TCS_FORCEICONLEFT))
1815 center_offset_h = infoPtr->uHItemPadding;
1817 if (center_offset_h < 2)
1818 center_offset_h = 2;
1820 if (center_offset_v < 0)
1821 center_offset_v = 0;
1823 TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%s), textlen=%d\n",
1824 debugstr_w(item->pszText), center_offset_h, center_offset_v,
1825 wine_dbgstr_rect(drawRect), (rcText.right-rcText.left));
1827 if((infoPtr->dwStyle & TCS_VERTICAL) && (infoPtr->dwStyle & TCS_BOTTOM))
1829 rcImage.top = drawRect->top + center_offset_h;
1830 /* if tab is TCS_VERTICAL and TCS_BOTTOM, the text is drawn from the */
1831 /* right side of the tab, but the image still uses the left as its x position */
1832 /* this keeps the image always drawn off of the same side of the tab */
1833 rcImage.left = drawRect->right - cx - center_offset_v;
1834 drawRect->top += cy + infoPtr->uHItemPadding;
1836 else if(infoPtr->dwStyle & TCS_VERTICAL)
1838 rcImage.top = drawRect->bottom - cy - center_offset_h;
1839 rcImage.left = drawRect->left + center_offset_v;
1840 drawRect->bottom -= cy + infoPtr->uHItemPadding;
1842 else /* normal style, whether TCS_BOTTOM or not */
1844 rcImage.left = drawRect->left + center_offset_h;
1845 rcImage.top = drawRect->top + center_offset_v;
1846 drawRect->left += cx + infoPtr->uHItemPadding;
1849 TRACE("drawing image=%d, left=%d, top=%d\n",
1850 item->iImage, rcImage.left, rcImage.top-1);
1862 /* Now position text */
1863 if (infoPtr->dwStyle & TCS_FIXEDWIDTH && infoPtr->dwStyle & TCS_FORCELABELLEFT)
1864 center_offset_h = infoPtr->uHItemPadding;
1866 if(infoPtr->dwStyle & TCS_VERTICAL)
1867 center_offset_h = ((drawRect->bottom - drawRect->top) - (rcText.right - rcText.left)) / 2;
1869 center_offset_h = ((drawRect->right - drawRect->left) - (rcText.right - rcText.left)) / 2;
1871 if(infoPtr->dwStyle & TCS_VERTICAL)
1873 if(infoPtr->dwStyle & TCS_BOTTOM)
1874 drawRect->top+=center_offset_h;
1876 drawRect->bottom-=center_offset_h;
1878 center_offset_v = ((drawRect->right - drawRect->left) - (rcText.bottom - rcText.top)) / 2;
1882 drawRect->left += center_offset_h;
1883 center_offset_v = ((drawRect->bottom - drawRect->top) - (rcText.bottom - rcText.top)) / 2;
1886 /* if an item is selected, the text is shifted up instead of down */
1887 if (iItem == infoPtr->iSelected)
1888 center_offset_v -= infoPtr->uVItemPadding / 2;
1890 center_offset_v += infoPtr->uVItemPadding / 2;
1892 if (center_offset_v < 0)
1893 center_offset_v = 0;
1895 if(infoPtr->dwStyle & TCS_VERTICAL)
1896 drawRect->left += center_offset_v;
1898 drawRect->top += center_offset_v;
1901 if(infoPtr->dwStyle & TCS_VERTICAL) /* if we are vertical rotate the text and each character */
1903 static const WCHAR ArialW[] = { 'A','r','i','a','l',0 };
1906 INT nEscapement = 900;
1907 INT nOrientation = 900;
1909 if(infoPtr->dwStyle & TCS_BOTTOM)
1912 nOrientation = -900;
1915 /* to get a font with the escapement and orientation we are looking for, we need to */
1916 /* call CreateFontIndirectA, which requires us to set the values of the logfont we pass in */
1917 if (!GetObjectW((infoPtr->hFont) ?
1918 infoPtr->hFont : GetStockObject(SYSTEM_FONT),
1919 sizeof(LOGFONTW),&logfont))
1923 lstrcpyW(logfont.lfFaceName, ArialW);
1924 logfont.lfHeight = -MulDiv(iPointSize, GetDeviceCaps(hdc, LOGPIXELSY),
1926 logfont.lfWeight = FW_NORMAL;
1927 logfont.lfItalic = 0;
1928 logfont.lfUnderline = 0;
1929 logfont.lfStrikeOut = 0;
1932 logfont.lfEscapement = nEscapement;
1933 logfont.lfOrientation = nOrientation;
1934 hFont = CreateFontIndirectW(&logfont);
1935 SelectObject(hdc, hFont);
1940 (infoPtr->dwStyle & TCS_BOTTOM) ? drawRect->right : drawRect->left,
1941 (!(infoPtr->dwStyle & TCS_BOTTOM)) ? drawRect->bottom : drawRect->top,
1945 lstrlenW(item->pszText),
1949 DeleteObject(hFont);
1953 TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%s), textlen=%d\n",
1954 debugstr_w(item->pszText), center_offset_h, center_offset_v,
1955 wine_dbgstr_rect(drawRect), (rcText.right-rcText.left));
1962 lstrlenW(item->pszText),
1964 DT_LEFT | DT_SINGLELINE
1969 *drawRect = rcTemp; /* restore drawRect */
1975 SelectObject(hdc, hOldFont);
1976 SetBkMode(hdc, oldBkMode);
1977 SelectObject(hdc, holdPen);
1978 DeleteObject( htextPen );
1981 /******************************************************************************
1984 * This method is used to draw a single tab into the tab control.
1986 static void TAB_DrawItem(const TAB_INFO *infoPtr, HDC hdc, INT iItem)
1991 RECT r, fillRect, r1;
1994 COLORREF bkgnd, corner;
1998 * Get the rectangle for the item.
2000 isVisible = TAB_InternalGetItemRect(infoPtr,
2009 /* Clip UpDown control to not draw over it */
2010 if (infoPtr->needsScrolling)
2012 GetWindowRect(infoPtr->hwnd, &rC);
2013 GetWindowRect(infoPtr->hwndUpDown, &rUD);
2014 ExcludeClipRect(hdc, rUD.left - rC.left, rUD.top - rC.top, rUD.right - rC.left, rUD.bottom - rC.top);
2017 /* If you need to see what the control is doing,
2018 * then override these variables. They will change what
2019 * fill colors are used for filling the tabs, and the
2020 * corners when drawing the edge.
2022 bkgnd = comctl32_color.clrBtnFace;
2023 corner = comctl32_color.clrBtnFace;
2025 if (infoPtr->dwStyle & TCS_BUTTONS)
2027 /* Get item rectangle */
2030 /* Separators between flat buttons */
2031 if ((infoPtr->dwStyle & TCS_FLATBUTTONS) && (infoPtr->exStyle & TCS_EX_FLATSEPARATORS))
2034 r1.right += (FLAT_BTN_SPACINGX -2);
2035 DrawEdge(hdc, &r1, EDGE_ETCHED, BF_RIGHT);
2038 if (iItem == infoPtr->iSelected)
2040 DrawEdge(hdc, &r, EDGE_SUNKEN, BF_SOFT|BF_RECT);
2042 OffsetRect(&r, 1, 1);
2044 else /* ! selected */
2046 DWORD state = TAB_GetItem(infoPtr, iItem)->dwState;
2048 if ((state & TCIS_BUTTONPRESSED) || (iItem == infoPtr->uFocus))
2049 DrawEdge(hdc, &r, EDGE_SUNKEN, BF_SOFT|BF_RECT);
2051 if (!(infoPtr->dwStyle & TCS_FLATBUTTONS))
2052 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_RECT);
2055 else /* !TCS_BUTTONS */
2057 /* We draw a rectangle of different sizes depending on the selection
2059 if (iItem == infoPtr->iSelected) {
2061 GetClientRect (infoPtr->hwnd, &rect);
2062 clRight = rect.right;
2063 clBottom = rect.bottom;
2070 * Erase the background. (Delay it but setup rectangle.)
2071 * This is necessary when drawing the selected item since it is larger
2072 * than the others, it might overlap with stuff already drawn by the
2077 /* Draw themed tabs - but only if they are at the top.
2078 * Windows draws even side or bottom tabs themed, with wacky results.
2079 * However, since in Wine apps may get themed that did not opt in via
2080 * a manifest avoid theming when we know the result will be wrong */
2081 if ((theme = GetWindowTheme (infoPtr->hwnd))
2082 && ((infoPtr->dwStyle & (TCS_VERTICAL | TCS_BOTTOM)) == 0))
2084 static const int partIds[8] = {
2087 TABP_TABITEMLEFTEDGE,
2088 TABP_TABITEMRIGHTEDGE,
2089 TABP_TABITEMBOTHEDGE,
2092 TABP_TOPTABITEMLEFTEDGE,
2093 TABP_TOPTABITEMRIGHTEDGE,
2094 TABP_TOPTABITEMBOTHEDGE,
2097 int stateId = TIS_NORMAL;
2099 /* selected and unselected tabs have different parts */
2100 if (iItem == infoPtr->iSelected)
2102 /* The part also differs on the position of a tab on a line.
2103 * "Visually" determining the position works well enough. */
2104 if(selectedRect.left == 0)
2106 if(selectedRect.right == clRight)
2109 if (iItem == infoPtr->iSelected)
2110 stateId = TIS_SELECTED;
2111 else if (iItem == infoPtr->iHotTracked)
2113 else if (iItem == infoPtr->uFocus)
2114 stateId = TIS_FOCUSED;
2116 /* Adjust rectangle for bottommost row */
2117 if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2120 DrawThemeBackground (theme, hdc, partIds[partIndex], stateId, &r, NULL);
2121 GetThemeBackgroundContentRect (theme, hdc, partIds[partIndex], stateId, &r, &r);
2123 else if(infoPtr->dwStyle & TCS_VERTICAL)
2125 /* These are for adjusting the drawing of a Selected tab */
2126 /* The initial values are for the normal case of non-Selected */
2127 int ZZ = 1; /* Do not stretch if selected */
2128 if (iItem == infoPtr->iSelected) {
2131 /* if leftmost draw the line longer */
2132 if(selectedRect.top == 0)
2133 fillRect.top += CONTROL_BORDER_SIZEY;
2134 /* if rightmost draw the line longer */
2135 if(selectedRect.bottom == clBottom)
2136 fillRect.bottom -= CONTROL_BORDER_SIZEY;
2139 if (infoPtr->dwStyle & TCS_BOTTOM)
2141 /* Adjust both rectangles to match native */
2144 TRACE("<right> item=%d, fill=(%s), edge=(%s)\n",
2145 iItem, wine_dbgstr_rect(&fillRect), wine_dbgstr_rect(&r));
2147 /* Clear interior */
2148 SetBkColor(hdc, bkgnd);
2149 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2151 /* Draw rectangular edge around tab */
2152 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_RIGHT|BF_TOP|BF_BOTTOM);
2154 /* Now erase the top corner and draw diagonal edge */
2155 SetBkColor(hdc, corner);
2156 r1.left = r.right - ROUND_CORNER_SIZE - 1;
2159 r1.bottom = r1.top + ROUND_CORNER_SIZE;
2160 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2162 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPLEFT);
2164 /* Now erase the bottom corner and draw diagonal edge */
2165 r1.left = r.right - ROUND_CORNER_SIZE - 1;
2166 r1.bottom = r.bottom;
2168 r1.top = r1.bottom - ROUND_CORNER_SIZE;
2169 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2171 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMLEFT);
2173 if ((iItem == infoPtr->iSelected) && (selectedRect.top == 0)) {
2177 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_TOP);
2183 TRACE("<left> item=%d, fill=(%s), edge=(%s)\n",
2184 iItem, wine_dbgstr_rect(&fillRect), wine_dbgstr_rect(&r));
2186 /* Clear interior */
2187 SetBkColor(hdc, bkgnd);
2188 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2190 /* Draw rectangular edge around tab */
2191 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_TOP|BF_BOTTOM);
2193 /* Now erase the top corner and draw diagonal edge */
2194 SetBkColor(hdc, corner);
2197 r1.right = r1.left + ROUND_CORNER_SIZE + 1;
2198 r1.bottom = r1.top + ROUND_CORNER_SIZE;
2199 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2201 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPRIGHT);
2203 /* Now erase the bottom corner and draw diagonal edge */
2205 r1.bottom = r.bottom;
2206 r1.right = r1.left + ROUND_CORNER_SIZE + 1;
2207 r1.top = r1.bottom - ROUND_CORNER_SIZE;
2208 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2210 DrawEdge(hdc, &r1, EDGE_SUNKEN, BF_DIAGONAL_ENDTOPLEFT);
2213 else /* ! TCS_VERTICAL */
2215 /* These are for adjusting the drawing of a Selected tab */
2216 /* The initial values are for the normal case of non-Selected */
2217 if (iItem == infoPtr->iSelected) {
2218 /* if leftmost draw the line longer */
2219 if(selectedRect.left == 0)
2220 fillRect.left += CONTROL_BORDER_SIZEX;
2221 /* if rightmost draw the line longer */
2222 if(selectedRect.right == clRight)
2223 fillRect.right -= CONTROL_BORDER_SIZEX;
2226 if (infoPtr->dwStyle & TCS_BOTTOM)
2228 /* Adjust both rectangles for topmost row */
2229 if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2235 TRACE("<bottom> item=%d, fill=(%s), edge=(%s)\n",
2236 iItem, wine_dbgstr_rect(&fillRect), wine_dbgstr_rect(&r));
2238 /* Clear interior */
2239 SetBkColor(hdc, bkgnd);
2240 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2242 /* Draw rectangular edge around tab */
2243 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_BOTTOM|BF_RIGHT);
2245 /* Now erase the righthand corner and draw diagonal edge */
2246 SetBkColor(hdc, corner);
2247 r1.left = r.right - ROUND_CORNER_SIZE;
2248 r1.bottom = r.bottom;
2250 r1.top = r1.bottom - ROUND_CORNER_SIZE - 1;
2251 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2253 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMLEFT);
2255 /* Now erase the lefthand corner and draw diagonal edge */
2257 r1.bottom = r.bottom;
2258 r1.right = r1.left + ROUND_CORNER_SIZE;
2259 r1.top = r1.bottom - ROUND_CORNER_SIZE - 1;
2260 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2262 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPLEFT);
2264 if (iItem == infoPtr->iSelected)
2268 if (selectedRect.left == 0)
2273 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_LEFT);
2280 /* Adjust both rectangles for bottommost row */
2281 if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2283 fillRect.bottom += 3;
2287 TRACE("<top> item=%d, fill=(%s), edge=(%s)\n",
2288 iItem, wine_dbgstr_rect(&fillRect), wine_dbgstr_rect(&r));
2290 /* Clear interior */
2291 SetBkColor(hdc, bkgnd);
2292 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2294 /* Draw rectangular edge around tab */
2295 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_TOP|BF_RIGHT);
2297 /* Now erase the righthand corner and draw diagonal edge */
2298 SetBkColor(hdc, corner);
2299 r1.left = r.right - ROUND_CORNER_SIZE;
2302 r1.bottom = r1.top + ROUND_CORNER_SIZE + 1;
2303 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2305 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMRIGHT);
2307 /* Now erase the lefthand corner and draw diagonal edge */
2310 r1.right = r1.left + ROUND_CORNER_SIZE;
2311 r1.bottom = r1.top + ROUND_CORNER_SIZE + 1;
2312 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2314 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPRIGHT);
2319 TAB_DumpItemInternal(infoPtr, iItem);
2321 /* This modifies r to be the text rectangle. */
2322 TAB_DrawItemInterior(infoPtr, hdc, iItem, &r);
2326 /******************************************************************************
2329 * This method is used to draw the raised border around the tab control
2332 static void TAB_DrawBorder(const TAB_INFO *infoPtr, HDC hdc)
2335 HTHEME theme = GetWindowTheme (infoPtr->hwnd);
2337 GetClientRect (infoPtr->hwnd, &rect);
2340 * Adjust for the style
2343 if (infoPtr->uNumItem)
2345 if ((infoPtr->dwStyle & TCS_BOTTOM) && !(infoPtr->dwStyle & TCS_VERTICAL))
2346 rect.bottom -= infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2347 else if((infoPtr->dwStyle & TCS_BOTTOM) && (infoPtr->dwStyle & TCS_VERTICAL))
2348 rect.right -= infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2349 else if(infoPtr->dwStyle & TCS_VERTICAL)
2350 rect.left += infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2351 else /* not TCS_VERTICAL and not TCS_BOTTOM */
2352 rect.top += infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2355 TRACE("border=(%s)\n", wine_dbgstr_rect(&rect));
2358 DrawThemeBackground (theme, hdc, TABP_PANE, 0, &rect, NULL);
2360 DrawEdge(hdc, &rect, EDGE_RAISED, BF_SOFT|BF_RECT);
2363 /******************************************************************************
2366 * This method repaints the tab control..
2368 static void TAB_Refresh (const TAB_INFO *infoPtr, HDC hdc)
2373 if (!infoPtr->DoRedraw)
2376 hOldFont = SelectObject (hdc, infoPtr->hFont);
2378 if (infoPtr->dwStyle & TCS_BUTTONS)
2380 for (i = 0; i < infoPtr->uNumItem; i++)
2381 TAB_DrawItem (infoPtr, hdc, i);
2385 /* Draw all the non selected item first */
2386 for (i = 0; i < infoPtr->uNumItem; i++)
2388 if (i != infoPtr->iSelected)
2389 TAB_DrawItem (infoPtr, hdc, i);
2392 /* Now, draw the border, draw it before the selected item
2393 * since the selected item overwrites part of the border. */
2394 TAB_DrawBorder (infoPtr, hdc);
2396 /* Then, draw the selected item */
2397 TAB_DrawItem (infoPtr, hdc, infoPtr->iSelected);
2400 SelectObject (hdc, hOldFont);
2403 static inline DWORD TAB_GetRowCount (const TAB_INFO *infoPtr)
2405 TRACE("(%p)\n", infoPtr);
2406 return infoPtr->uNumRows;
2409 static inline LRESULT TAB_SetRedraw (TAB_INFO *infoPtr, BOOL doRedraw)
2411 infoPtr->DoRedraw = doRedraw;
2415 /******************************************************************************
2416 * TAB_EnsureSelectionVisible
2418 * This method will make sure that the current selection is completely
2419 * visible by scrolling until it is.
2421 static void TAB_EnsureSelectionVisible(
2424 INT iSelected = infoPtr->iSelected;
2425 INT iOrigLeftmostVisible = infoPtr->leftmostVisible;
2427 /* set the items row to the bottommost row or topmost row depending on
2429 if ((infoPtr->uNumRows > 1) && !(infoPtr->dwStyle & TCS_BUTTONS))
2431 TAB_ITEM *selected = TAB_GetItem(infoPtr, iSelected);
2435 if(infoPtr->dwStyle & TCS_VERTICAL)
2436 newselected = selected->rect.left;
2438 newselected = selected->rect.top;
2440 /* the target row is always (number of rows - 1)
2441 as row 0 is furthest from the clientRect */
2442 iTargetRow = infoPtr->uNumRows - 1;
2444 if (newselected != iTargetRow)
2447 if(infoPtr->dwStyle & TCS_VERTICAL)
2449 for (i=0; i < infoPtr->uNumItem; i++)
2451 /* move everything in the row of the selected item to the iTargetRow */
2452 TAB_ITEM *item = TAB_GetItem(infoPtr, i);
2454 if (item->rect.left == newselected )
2455 item->rect.left = iTargetRow;
2458 if (item->rect.left > newselected)
2465 for (i=0; i < infoPtr->uNumItem; i++)
2467 TAB_ITEM *item = TAB_GetItem(infoPtr, i);
2469 if (item->rect.top == newselected )
2470 item->rect.top = iTargetRow;
2473 if (item->rect.top > newselected)
2478 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
2483 * Do the trivial cases first.
2485 if ( (!infoPtr->needsScrolling) ||
2486 (infoPtr->hwndUpDown==0) || (infoPtr->dwStyle & TCS_VERTICAL))
2489 if (infoPtr->leftmostVisible >= iSelected)
2491 infoPtr->leftmostVisible = iSelected;
2495 TAB_ITEM *selected = TAB_GetItem(infoPtr, iSelected);
2500 /* Calculate the part of the client area that is visible */
2501 GetClientRect(infoPtr->hwnd, &r);
2504 GetClientRect(infoPtr->hwndUpDown, &r);
2507 if ((selected->rect.right -
2508 selected->rect.left) >= width )
2510 /* Special case: width of selected item is greater than visible
2513 infoPtr->leftmostVisible = iSelected;
2517 for (i = infoPtr->leftmostVisible; i < infoPtr->uNumItem; i++)
2519 if ((selected->rect.right - TAB_GetItem(infoPtr, i)->rect.left) < width)
2522 infoPtr->leftmostVisible = i;
2526 if (infoPtr->leftmostVisible != iOrigLeftmostVisible)
2527 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
2529 SendMessageW(infoPtr->hwndUpDown, UDM_SETPOS, 0,
2530 MAKELONG(infoPtr->leftmostVisible, 0));
2533 /******************************************************************************
2534 * TAB_InvalidateTabArea
2536 * This method will invalidate the portion of the control that contains the
2537 * tabs. It is called when the state of the control changes and needs
2540 static void TAB_InvalidateTabArea(const TAB_INFO *infoPtr)
2542 RECT clientRect, rInvalidate, rAdjClient;
2543 INT lastRow = infoPtr->uNumRows - 1;
2546 if (lastRow < 0) return;
2548 GetClientRect(infoPtr->hwnd, &clientRect);
2549 rInvalidate = clientRect;
2550 rAdjClient = clientRect;
2552 TAB_AdjustRect(infoPtr, 0, &rAdjClient);
2554 TAB_InternalGetItemRect(infoPtr, infoPtr->uNumItem-1 , &rect, NULL);
2555 if ((infoPtr->dwStyle & TCS_BOTTOM) && (infoPtr->dwStyle & TCS_VERTICAL))
2557 rInvalidate.left = rAdjClient.right;
2558 if (infoPtr->uNumRows == 1)
2559 rInvalidate.bottom = clientRect.top + rect.bottom + 2 * SELECTED_TAB_OFFSET;
2561 else if(infoPtr->dwStyle & TCS_VERTICAL)
2563 rInvalidate.right = rAdjClient.left;
2564 if (infoPtr->uNumRows == 1)
2565 rInvalidate.bottom = clientRect.top + rect.bottom + 2 * SELECTED_TAB_OFFSET;
2567 else if (infoPtr->dwStyle & TCS_BOTTOM)
2569 rInvalidate.top = rAdjClient.bottom;
2570 if (infoPtr->uNumRows == 1)
2571 rInvalidate.right = clientRect.left + rect.right + 2 * SELECTED_TAB_OFFSET;
2575 rInvalidate.bottom = rAdjClient.top;
2576 if (infoPtr->uNumRows == 1)
2577 rInvalidate.right = clientRect.left + rect.right + 2 * SELECTED_TAB_OFFSET;
2580 /* Punch out the updown control */
2581 if (infoPtr->needsScrolling && (rInvalidate.right > 0)) {
2583 GetClientRect(infoPtr->hwndUpDown, &r);
2584 if (rInvalidate.right > clientRect.right - r.left)
2585 rInvalidate.right = rInvalidate.right - (r.right - r.left);
2587 rInvalidate.right = clientRect.right - r.left;
2590 TRACE("invalidate (%s)\n", wine_dbgstr_rect(&rInvalidate));
2592 InvalidateRect(infoPtr->hwnd, &rInvalidate, TRUE);
2595 static inline LRESULT TAB_Paint (TAB_INFO *infoPtr, HDC hdcPaint)
2604 hdc = BeginPaint (infoPtr->hwnd, &ps);
2605 TRACE("erase %d, rect=(%s)\n", ps.fErase, wine_dbgstr_rect(&ps.rcPaint));
2608 TAB_Refresh (infoPtr, hdc);
2611 EndPaint (infoPtr->hwnd, &ps);
2617 TAB_InsertItemT (TAB_INFO *infoPtr, INT iItem, TCITEMW *pti, BOOL bUnicode)
2622 GetClientRect (infoPtr->hwnd, &rect);
2623 TRACE("Rect: %p %s\n", infoPtr->hwnd, wine_dbgstr_rect(&rect));
2625 if (iItem < 0) return -1;
2626 if (iItem > infoPtr->uNumItem)
2627 iItem = infoPtr->uNumItem;
2629 TAB_DumpItemExternalT(pti, iItem, bUnicode);
2632 if (infoPtr->uNumItem == 0) {
2633 infoPtr->items = Alloc (TAB_ITEM_SIZE(infoPtr));
2634 infoPtr->uNumItem++;
2635 infoPtr->iSelected = 0;
2638 LPBYTE oldItems = (LPBYTE)infoPtr->items;
2640 infoPtr->uNumItem++;
2641 infoPtr->items = Alloc (TAB_ITEM_SIZE(infoPtr) * infoPtr->uNumItem);
2643 /* pre insert copy */
2645 memcpy (infoPtr->items, oldItems,
2646 iItem * TAB_ITEM_SIZE(infoPtr));
2649 /* post insert copy */
2650 if (iItem < infoPtr->uNumItem - 1) {
2651 memcpy (TAB_GetItem(infoPtr, iItem + 1),
2652 oldItems + iItem * TAB_ITEM_SIZE(infoPtr),
2653 (infoPtr->uNumItem - iItem - 1) * TAB_ITEM_SIZE(infoPtr));
2657 if (iItem <= infoPtr->iSelected)
2658 infoPtr->iSelected++;
2663 item = TAB_GetItem(infoPtr, iItem);
2665 item->pszText = NULL;
2667 if (pti->mask & TCIF_TEXT)
2670 Str_SetPtrW (&item->pszText, pti->pszText);
2672 Str_SetPtrAtoW (&item->pszText, (LPSTR)pti->pszText);
2675 if (pti->mask & TCIF_IMAGE)
2676 item->iImage = pti->iImage;
2680 if (pti->mask & TCIF_PARAM)
2681 memcpy(item->extra, &pti->lParam, infoPtr->cbInfo);
2683 memset(item->extra, 0, infoPtr->cbInfo);
2685 TAB_SetItemBounds(infoPtr);
2686 if (infoPtr->uNumItem > 1)
2687 TAB_InvalidateTabArea(infoPtr);
2689 InvalidateRect(infoPtr->hwnd, NULL, TRUE);
2691 TRACE("[%p]: added item %d %s\n",
2692 infoPtr->hwnd, iItem, debugstr_w(item->pszText));
2694 /* If we haven't set the current focus yet, set it now. */
2695 if (infoPtr->uFocus == -1)
2696 TAB_SetCurFocus(infoPtr, iItem);
2702 TAB_SetItemSize (TAB_INFO *infoPtr, INT cx, INT cy)
2705 BOOL bNeedPaint = FALSE;
2707 lResult = MAKELONG(infoPtr->tabWidth, infoPtr->tabHeight);
2709 /* UNDOCUMENTED: If requested Width or Height is 0 this means that program wants to use auto size. */
2710 if (infoPtr->dwStyle & TCS_FIXEDWIDTH && (infoPtr->tabWidth != cx))
2712 infoPtr->tabWidth = cx;
2716 if (infoPtr->tabHeight != cy)
2718 if ((infoPtr->fHeightSet = (cy != 0)))
2719 infoPtr->tabHeight = cy;
2723 TRACE("was h=%d,w=%d, now h=%d,w=%d\n",
2724 HIWORD(lResult), LOWORD(lResult),
2725 infoPtr->tabHeight, infoPtr->tabWidth);
2729 TAB_SetItemBounds(infoPtr);
2730 RedrawWindow(infoPtr->hwnd, NULL, NULL, RDW_ERASE | RDW_INVALIDATE | RDW_UPDATENOW);
2736 static inline LRESULT TAB_SetMinTabWidth (TAB_INFO *infoPtr, INT cx)
2740 TRACE("(%p,%d)\n", infoPtr, cx);
2742 if (infoPtr->tabMinWidth < 0)
2743 oldcx = DEFAULT_MIN_TAB_WIDTH;
2745 oldcx = infoPtr->tabMinWidth;
2746 infoPtr->tabMinWidth = cx;
2747 TAB_SetItemBounds(infoPtr);
2751 static inline LRESULT
2752 TAB_HighlightItem (TAB_INFO *infoPtr, INT iItem, BOOL fHighlight)
2758 TRACE("(%p,%d,%s)\n", infoPtr, iItem, fHighlight ? "true" : "false");
2760 if (iItem < 0 || iItem >= infoPtr->uNumItem)
2763 lpState = &TAB_GetItem(infoPtr, iItem)->dwState;
2764 oldState = *lpState;
2767 *lpState |= TCIS_HIGHLIGHTED;
2769 *lpState &= ~TCIS_HIGHLIGHTED;
2771 if ((oldState != *lpState) && TAB_InternalGetItemRect (infoPtr, iItem, &r, NULL))
2772 InvalidateRect (infoPtr->hwnd, &r, TRUE);
2778 TAB_SetItemT (TAB_INFO *infoPtr, INT iItem, LPTCITEMW tabItem, BOOL bUnicode)
2782 TRACE("(%p,%d,%p,%s)\n", infoPtr, iItem, tabItem, bUnicode ? "true" : "false");
2784 if (iItem < 0 || iItem >= infoPtr->uNumItem)
2787 TAB_DumpItemExternalT(tabItem, iItem, bUnicode);
2789 wineItem = TAB_GetItem(infoPtr, iItem);
2791 if (tabItem->mask & TCIF_IMAGE)
2792 wineItem->iImage = tabItem->iImage;
2794 if (tabItem->mask & TCIF_PARAM)
2795 memcpy(wineItem->extra, &tabItem->lParam, infoPtr->cbInfo);
2797 if (tabItem->mask & TCIF_RTLREADING)
2798 FIXME("TCIF_RTLREADING\n");
2800 if (tabItem->mask & TCIF_STATE)
2801 wineItem->dwState = (wineItem->dwState & ~tabItem->dwStateMask) |
2802 ( tabItem->dwState & tabItem->dwStateMask);
2804 if (tabItem->mask & TCIF_TEXT)
2806 Free(wineItem->pszText);
2807 wineItem->pszText = NULL;
2809 Str_SetPtrW(&wineItem->pszText, tabItem->pszText);
2811 Str_SetPtrAtoW(&wineItem->pszText, (LPSTR)tabItem->pszText);
2814 /* Update and repaint tabs */
2815 TAB_SetItemBounds(infoPtr);
2816 TAB_InvalidateTabArea(infoPtr);
2821 static inline LRESULT TAB_GetItemCount (const TAB_INFO *infoPtr)
2824 return infoPtr->uNumItem;
2829 TAB_GetItemT (TAB_INFO *infoPtr, INT iItem, LPTCITEMW tabItem, BOOL bUnicode)
2833 TRACE("(%p,%d,%p,%s)\n", infoPtr, iItem, tabItem, bUnicode ? "true" : "false");
2835 if (!tabItem) return FALSE;
2837 if (iItem < 0 || iItem >= infoPtr->uNumItem)
2839 /* init requested fields */
2840 if (tabItem->mask & TCIF_IMAGE) tabItem->iImage = 0;
2841 if (tabItem->mask & TCIF_PARAM) tabItem->lParam = 0;
2842 if (tabItem->mask & TCIF_STATE) tabItem->dwState = 0;
2846 wineItem = TAB_GetItem(infoPtr, iItem);
2848 if (tabItem->mask & TCIF_IMAGE)
2849 tabItem->iImage = wineItem->iImage;
2851 if (tabItem->mask & TCIF_PARAM)
2852 memcpy(&tabItem->lParam, wineItem->extra, infoPtr->cbInfo);
2854 if (tabItem->mask & TCIF_RTLREADING)
2855 FIXME("TCIF_RTLREADING\n");
2857 if (tabItem->mask & TCIF_STATE)
2858 tabItem->dwState = wineItem->dwState & tabItem->dwStateMask;
2860 if (tabItem->mask & TCIF_TEXT)
2863 Str_GetPtrW (wineItem->pszText, tabItem->pszText, tabItem->cchTextMax);
2865 Str_GetPtrWtoA (wineItem->pszText, (LPSTR)tabItem->pszText, tabItem->cchTextMax);
2868 TAB_DumpItemExternalT(tabItem, iItem, bUnicode);
2874 static LRESULT TAB_DeleteItem (TAB_INFO *infoPtr, INT iItem)
2876 BOOL bResult = FALSE;
2878 TRACE("(%p, %d)\n", infoPtr, iItem);
2880 if ((iItem >= 0) && (iItem < infoPtr->uNumItem))
2882 TAB_ITEM *item = TAB_GetItem(infoPtr, iItem);
2883 LPBYTE oldItems = (LPBYTE)infoPtr->items;
2885 TAB_InvalidateTabArea(infoPtr);
2886 Free(item->pszText);
2887 infoPtr->uNumItem--;
2889 if (!infoPtr->uNumItem)
2891 infoPtr->items = NULL;
2892 if (infoPtr->iHotTracked >= 0)
2894 KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
2895 infoPtr->iHotTracked = -1;
2900 infoPtr->items = Alloc(TAB_ITEM_SIZE(infoPtr) * infoPtr->uNumItem);
2903 memcpy(infoPtr->items, oldItems, iItem * TAB_ITEM_SIZE(infoPtr));
2905 if (iItem < infoPtr->uNumItem)
2906 memcpy(TAB_GetItem(infoPtr, iItem),
2907 oldItems + (iItem + 1) * TAB_ITEM_SIZE(infoPtr),
2908 (infoPtr->uNumItem - iItem) * TAB_ITEM_SIZE(infoPtr));
2910 if (iItem <= infoPtr->iHotTracked)
2912 /* When tabs move left/up, the hot track item may change */
2913 FIXME("Recalc hot track\n");
2918 /* Readjust the selected index */
2919 if ((iItem == infoPtr->iSelected) && (iItem > 0))
2920 infoPtr->iSelected--;
2922 if (iItem < infoPtr->iSelected)
2923 infoPtr->iSelected--;
2925 if (infoPtr->uNumItem == 0)
2926 infoPtr->iSelected = -1;
2928 /* Reposition and repaint tabs */
2929 TAB_SetItemBounds(infoPtr);
2937 static inline LRESULT TAB_DeleteAllItems (TAB_INFO *infoPtr)
2939 TRACE("(%p)\n", infoPtr);
2940 while (infoPtr->uNumItem)
2941 TAB_DeleteItem (infoPtr, 0);
2946 static inline LRESULT TAB_GetFont (const TAB_INFO *infoPtr)
2948 TRACE("(%p) returning %p\n", infoPtr, infoPtr->hFont);
2949 return (LRESULT)infoPtr->hFont;
2952 static inline LRESULT TAB_SetFont (TAB_INFO *infoPtr, HFONT hNewFont)
2954 TRACE("(%p,%p)\n", infoPtr, hNewFont);
2956 infoPtr->hFont = hNewFont;
2958 TAB_SetItemBounds(infoPtr);
2960 TAB_InvalidateTabArea(infoPtr);
2966 static inline LRESULT TAB_GetImageList (const TAB_INFO *infoPtr)
2969 return (LRESULT)infoPtr->himl;
2972 static inline LRESULT TAB_SetImageList (TAB_INFO *infoPtr, HIMAGELIST himlNew)
2974 HIMAGELIST himlPrev = infoPtr->himl;
2975 TRACE("himl=%p\n", himlNew);
2976 infoPtr->himl = himlNew;
2977 TAB_SetItemBounds(infoPtr);
2978 InvalidateRect(infoPtr->hwnd, NULL, TRUE);
2979 return (LRESULT)himlPrev;
2982 static inline LRESULT TAB_GetUnicodeFormat (const TAB_INFO *infoPtr)
2984 TRACE("(%p)\n", infoPtr);
2985 return infoPtr->bUnicode;
2988 static inline LRESULT TAB_SetUnicodeFormat (TAB_INFO *infoPtr, BOOL bUnicode)
2990 BOOL bTemp = infoPtr->bUnicode;
2992 TRACE("(%p %d)\n", infoPtr, bUnicode);
2993 infoPtr->bUnicode = bUnicode;
2998 static inline LRESULT TAB_Size (TAB_INFO *infoPtr)
3000 /* I'm not really sure what the following code was meant to do.
3001 This is what it is doing:
3002 When WM_SIZE is sent with SIZE_RESTORED, the control
3003 gets positioned in the top left corner.
3007 UINT uPosFlags,cx,cy;
3011 parent = GetParent (hwnd);
3012 GetClientRect(parent, &parent_rect);
3015 if (GetWindowLongW(hwnd, GWL_STYLE) & CCS_NORESIZE)
3016 uPosFlags |= (SWP_NOSIZE | SWP_NOMOVE);
3018 SetWindowPos (hwnd, 0, parent_rect.left, parent_rect.top,
3019 cx, cy, uPosFlags | SWP_NOZORDER);
3021 FIXME("WM_SIZE flag %x %lx not handled\n", wParam, lParam);
3024 /* Recompute the size/position of the tabs. */
3025 TAB_SetItemBounds (infoPtr);
3027 /* Force a repaint of the control. */
3028 InvalidateRect(infoPtr->hwnd, NULL, TRUE);
3034 static LRESULT TAB_Create (HWND hwnd, LPARAM lParam)
3037 TEXTMETRICW fontMetrics;
3042 infoPtr = Alloc (sizeof(TAB_INFO));
3044 SetWindowLongPtrW(hwnd, 0, (DWORD_PTR)infoPtr);
3046 infoPtr->hwnd = hwnd;
3047 infoPtr->hwndNotify = ((LPCREATESTRUCTW)lParam)->hwndParent;
3048 infoPtr->uNumItem = 0;
3049 infoPtr->uNumRows = 0;
3050 infoPtr->uHItemPadding = 6;
3051 infoPtr->uVItemPadding = 3;
3052 infoPtr->uHItemPadding_s = 6;
3053 infoPtr->uVItemPadding_s = 3;
3056 infoPtr->hcurArrow = LoadCursorW (0, (LPWSTR)IDC_ARROW);
3057 infoPtr->iSelected = -1;
3058 infoPtr->iHotTracked = -1;
3059 infoPtr->uFocus = -1;
3060 infoPtr->hwndToolTip = 0;
3061 infoPtr->DoRedraw = TRUE;
3062 infoPtr->needsScrolling = FALSE;
3063 infoPtr->hwndUpDown = 0;
3064 infoPtr->leftmostVisible = 0;
3065 infoPtr->fHeightSet = FALSE;
3066 infoPtr->bUnicode = IsWindowUnicode (hwnd);
3067 infoPtr->cbInfo = sizeof(LPARAM);
3069 TRACE("Created tab control, hwnd [%p]\n", hwnd);
3071 /* The tab control always has the WS_CLIPSIBLINGS style. Even
3072 if you don't specify it in CreateWindow. This is necessary in
3073 order for paint to work correctly. This follows windows behaviour. */
3074 dwStyle = GetWindowLongW(hwnd, GWL_STYLE);
3075 SetWindowLongW(hwnd, GWL_STYLE, dwStyle|WS_CLIPSIBLINGS);
3077 infoPtr->dwStyle = dwStyle | WS_CLIPSIBLINGS;
3078 infoPtr->exStyle = (dwStyle & TCS_FLATBUTTONS) ? TCS_EX_FLATSEPARATORS : 0;
3080 if (infoPtr->dwStyle & TCS_TOOLTIPS) {
3081 /* Create tooltip control */
3082 infoPtr->hwndToolTip =
3083 CreateWindowExW (0, TOOLTIPS_CLASSW, NULL, WS_POPUP,
3084 CW_USEDEFAULT, CW_USEDEFAULT,
3085 CW_USEDEFAULT, CW_USEDEFAULT,
3088 /* Send NM_TOOLTIPSCREATED notification */
3089 if (infoPtr->hwndToolTip) {
3090 NMTOOLTIPSCREATED nmttc;
3092 nmttc.hdr.hwndFrom = hwnd;
3093 nmttc.hdr.idFrom = GetWindowLongPtrW(hwnd, GWLP_ID);
3094 nmttc.hdr.code = NM_TOOLTIPSCREATED;
3095 nmttc.hwndToolTips = infoPtr->hwndToolTip;
3097 SendMessageW (infoPtr->hwndNotify, WM_NOTIFY,
3098 (WPARAM)GetWindowLongPtrW(hwnd, GWLP_ID), (LPARAM)&nmttc);
3102 OpenThemeData (infoPtr->hwnd, themeClass);
3105 * We need to get text information so we need a DC and we need to select
3109 hOldFont = SelectObject (hdc, GetStockObject (SYSTEM_FONT));
3111 /* Use the system font to determine the initial height of a tab. */
3112 GetTextMetricsW(hdc, &fontMetrics);
3115 * Make sure there is enough space for the letters + growing the
3116 * selected item + extra space for the selected item.
3118 infoPtr->tabHeight = fontMetrics.tmHeight + SELECTED_TAB_OFFSET +
3119 ((infoPtr->dwStyle & TCS_BUTTONS) ? 2 : 1) *
3120 infoPtr->uVItemPadding;
3122 /* Initialize the width of a tab. */
3123 if (infoPtr->dwStyle & TCS_FIXEDWIDTH)
3124 infoPtr->tabWidth = GetDeviceCaps(hdc, LOGPIXELSX);
3126 infoPtr->tabMinWidth = -1;
3128 TRACE("tabH=%d, tabW=%d\n", infoPtr->tabHeight, infoPtr->tabWidth);
3130 SelectObject (hdc, hOldFont);
3131 ReleaseDC(hwnd, hdc);
3137 TAB_Destroy (TAB_INFO *infoPtr)
3141 SetWindowLongPtrW(infoPtr->hwnd, 0, 0);
3143 if (infoPtr->items) {
3144 for (iItem = 0; iItem < infoPtr->uNumItem; iItem++) {
3145 Free (TAB_GetItem(infoPtr, iItem)->pszText);
3147 Free (infoPtr->items);
3150 if (infoPtr->hwndToolTip)
3151 DestroyWindow (infoPtr->hwndToolTip);
3153 if (infoPtr->hwndUpDown)
3154 DestroyWindow(infoPtr->hwndUpDown);
3156 if (infoPtr->iHotTracked >= 0)
3157 KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
3159 CloseThemeData (GetWindowTheme (infoPtr->hwnd));
3165 /* update theme after a WM_THEMECHANGED message */
3166 static LRESULT theme_changed(const TAB_INFO *infoPtr)
3168 HTHEME theme = GetWindowTheme (infoPtr->hwnd);
3169 CloseThemeData (theme);
3170 OpenThemeData (infoPtr->hwnd, themeClass);
3174 static LRESULT TAB_NCCalcSize(WPARAM wParam)
3178 return WVR_ALIGNTOP;
3181 static inline LRESULT
3182 TAB_SetItemExtra (TAB_INFO *infoPtr, INT cbInfo)
3184 TRACE("(%p %d)\n", infoPtr, cbInfo);
3189 if (infoPtr->uNumItem)
3191 /* FIXME: MSDN says this is not allowed, but this hasn't been verified */
3195 infoPtr->cbInfo = cbInfo;
3199 static LRESULT TAB_RemoveImage (TAB_INFO *infoPtr, INT image)
3201 TRACE("%p %d\n", infoPtr, image);
3203 if (ImageList_Remove (infoPtr->himl, image))
3208 /* shift indices, repaint items if needed */
3209 for (i = 0; i < infoPtr->uNumItem; i++)
3211 idx = &TAB_GetItem(infoPtr, i)->iImage;
3220 if (TAB_InternalGetItemRect (infoPtr, i, &r, NULL))
3221 InvalidateRect (infoPtr->hwnd, &r, TRUE);
3230 TAB_SetExtendedStyle (TAB_INFO *infoPtr, DWORD exMask, DWORD exStyle)
3232 DWORD prevstyle = infoPtr->exStyle;
3234 /* zero mask means all styles */
3235 if (exMask == 0) exMask = ~0;
3237 if (exMask & TCS_EX_REGISTERDROP)
3239 FIXME("TCS_EX_REGISTERDROP style unimplemented\n");
3240 exMask &= ~TCS_EX_REGISTERDROP;
3241 exStyle &= ~TCS_EX_REGISTERDROP;
3244 if (exMask & TCS_EX_FLATSEPARATORS)
3246 if ((prevstyle ^ exStyle) & TCS_EX_FLATSEPARATORS)
3248 infoPtr->exStyle ^= TCS_EX_FLATSEPARATORS;
3249 TAB_InvalidateTabArea(infoPtr);
3256 static inline LRESULT
3257 TAB_GetExtendedStyle (const TAB_INFO *infoPtr)
3259 return infoPtr->exStyle;
3263 TAB_DeselectAll (TAB_INFO *infoPtr, BOOL excludesel)
3266 INT i, selected = infoPtr->iSelected;
3268 TRACE("(%p, %d)\n", infoPtr, excludesel);
3270 if (!(infoPtr->dwStyle & TCS_BUTTONS))
3273 for (i = 0; i < infoPtr->uNumItem; i++)
3275 if ((TAB_GetItem(infoPtr, i)->dwState & TCIS_BUTTONPRESSED) &&
3278 TAB_GetItem(infoPtr, i)->dwState &= ~TCIS_BUTTONPRESSED;
3283 if (!excludesel && (selected != -1))
3285 TAB_GetItem(infoPtr, selected)->dwState &= ~TCIS_BUTTONPRESSED;
3286 infoPtr->iSelected = -1;
3291 TAB_InvalidateTabArea (infoPtr);
3298 * Processes WM_STYLECHANGED messages.
3301 * [I] infoPtr : valid pointer to the tab data structure
3302 * [I] wStyleType : window style type (normal or extended)
3303 * [I] lpss : window style information
3308 static INT TAB_StyleChanged(TAB_INFO *infoPtr, WPARAM wStyleType,
3309 const STYLESTRUCT *lpss)
3311 TRACE("(styletype=%lx, styleOld=0x%08x, styleNew=0x%08x)\n",
3312 wStyleType, lpss->styleOld, lpss->styleNew);
3314 if (wStyleType != GWL_STYLE) return 0;
3316 infoPtr->dwStyle = lpss->styleNew;
3318 TAB_SetItemBounds (infoPtr);
3319 InvalidateRect(infoPtr->hwnd, NULL, TRUE);
3324 static LRESULT WINAPI
3325 TAB_WindowProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
3327 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
3329 TRACE("hwnd=%p msg=%x wParam=%lx lParam=%lx\n", hwnd, uMsg, wParam, lParam);
3330 if (!infoPtr && (uMsg != WM_CREATE))
3331 return DefWindowProcW (hwnd, uMsg, wParam, lParam);
3335 case TCM_GETIMAGELIST:
3336 return TAB_GetImageList (infoPtr);
3338 case TCM_SETIMAGELIST:
3339 return TAB_SetImageList (infoPtr, (HIMAGELIST)lParam);
3341 case TCM_GETITEMCOUNT:
3342 return TAB_GetItemCount (infoPtr);
3346 return TAB_GetItemT (infoPtr, (INT)wParam, (LPTCITEMW)lParam, uMsg == TCM_GETITEMW);
3350 return TAB_SetItemT (infoPtr, (INT)wParam, (LPTCITEMW)lParam, uMsg == TCM_SETITEMW);
3352 case TCM_DELETEITEM:
3353 return TAB_DeleteItem (infoPtr, (INT)wParam);
3355 case TCM_DELETEALLITEMS:
3356 return TAB_DeleteAllItems (infoPtr);
3358 case TCM_GETITEMRECT:
3359 return TAB_GetItemRect (infoPtr, (INT)wParam, (LPRECT)lParam);
3362 return TAB_GetCurSel (infoPtr);
3365 return TAB_HitTest (infoPtr, (LPTCHITTESTINFO)lParam);
3368 return TAB_SetCurSel (infoPtr, (INT)wParam);
3370 case TCM_INSERTITEMA:
3371 case TCM_INSERTITEMW:
3372 return TAB_InsertItemT (infoPtr, (INT)wParam, (TCITEMW*)lParam, uMsg == TCM_INSERTITEMW);
3374 case TCM_SETITEMEXTRA:
3375 return TAB_SetItemExtra (infoPtr, (INT)wParam);
3377 case TCM_ADJUSTRECT:
3378 return TAB_AdjustRect (infoPtr, (BOOL)wParam, (LPRECT)lParam);
3380 case TCM_SETITEMSIZE:
3381 return TAB_SetItemSize (infoPtr, (INT)LOWORD(lParam), (INT)HIWORD(lParam));
3383 case TCM_REMOVEIMAGE:
3384 return TAB_RemoveImage (infoPtr, (INT)wParam);
3386 case TCM_SETPADDING:
3387 return TAB_SetPadding (infoPtr, lParam);
3389 case TCM_GETROWCOUNT:
3390 return TAB_GetRowCount(infoPtr);
3392 case TCM_GETUNICODEFORMAT:
3393 return TAB_GetUnicodeFormat (infoPtr);
3395 case TCM_SETUNICODEFORMAT:
3396 return TAB_SetUnicodeFormat (infoPtr, (BOOL)wParam);
3398 case TCM_HIGHLIGHTITEM:
3399 return TAB_HighlightItem (infoPtr, (INT)wParam, (BOOL)LOWORD(lParam));
3401 case TCM_GETTOOLTIPS:
3402 return TAB_GetToolTips (infoPtr);
3404 case TCM_SETTOOLTIPS:
3405 return TAB_SetToolTips (infoPtr, (HWND)wParam);
3407 case TCM_GETCURFOCUS:
3408 return TAB_GetCurFocus (infoPtr);
3410 case TCM_SETCURFOCUS:
3411 return TAB_SetCurFocus (infoPtr, (INT)wParam);
3413 case TCM_SETMINTABWIDTH:
3414 return TAB_SetMinTabWidth(infoPtr, (INT)lParam);
3416 case TCM_DESELECTALL:
3417 return TAB_DeselectAll (infoPtr, (BOOL)wParam);
3419 case TCM_GETEXTENDEDSTYLE:
3420 return TAB_GetExtendedStyle (infoPtr);
3422 case TCM_SETEXTENDEDSTYLE:
3423 return TAB_SetExtendedStyle (infoPtr, wParam, lParam);
3426 return TAB_GetFont (infoPtr);
3429 return TAB_SetFont (infoPtr, (HFONT)wParam);
3432 return TAB_Create (hwnd, lParam);
3435 return TAB_Destroy (infoPtr);
3438 return DLGC_WANTARROWS | DLGC_WANTCHARS;
3440 case WM_LBUTTONDOWN:
3441 return TAB_LButtonDown (infoPtr, wParam, lParam);
3444 return TAB_LButtonUp (infoPtr);
3447 return SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, wParam, lParam);
3449 case WM_RBUTTONDOWN:
3450 return TAB_RButtonDown (infoPtr);
3453 return TAB_MouseMove (infoPtr, wParam, lParam);
3455 case WM_PRINTCLIENT:
3457 return TAB_Paint (infoPtr, (HDC)wParam);
3460 return TAB_Size (infoPtr);
3463 return TAB_SetRedraw (infoPtr, (BOOL)wParam);
3466 return TAB_OnHScroll(infoPtr, (int)LOWORD(wParam), (int)HIWORD(wParam));
3468 case WM_STYLECHANGED:
3469 return TAB_StyleChanged(infoPtr, wParam, (LPSTYLESTRUCT)lParam);
3471 case WM_SYSCOLORCHANGE:
3472 COMCTL32_RefreshSysColors();
3475 case WM_THEMECHANGED:
3476 return theme_changed (infoPtr);
3479 TAB_KillFocus(infoPtr);
3481 TAB_FocusChanging(infoPtr);
3482 break; /* Don't disturb normal focus behavior */
3485 return TAB_KeyDown(infoPtr, wParam, lParam);
3488 return TAB_NCHitTest(infoPtr, lParam);
3491 return TAB_NCCalcSize(wParam);
3494 if (uMsg >= WM_USER && uMsg < WM_APP && !COMCTL32_IsReflectedMessage(uMsg))
3495 WARN("unknown msg %04x wp=%08lx lp=%08lx\n",
3496 uMsg, wParam, lParam);
3499 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
3508 ZeroMemory (&wndClass, sizeof(WNDCLASSW));
3509 wndClass.style = CS_GLOBALCLASS | CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
3510 wndClass.lpfnWndProc = TAB_WindowProc;
3511 wndClass.cbClsExtra = 0;
3512 wndClass.cbWndExtra = sizeof(TAB_INFO *);
3513 wndClass.hCursor = LoadCursorW (0, (LPWSTR)IDC_ARROW);
3514 wndClass.hbrBackground = (HBRUSH)(COLOR_BTNFACE+1);
3515 wndClass.lpszClassName = WC_TABCONTROLW;
3517 RegisterClassW (&wndClass);
3522 TAB_Unregister (void)
3524 UnregisterClassW (WC_TABCONTROLW, NULL);