4 * Copyright 1998 Anders Carlsson
5 * Copyright 1999 Alex Priem <alexp@sci.kun.nl>
6 * Copyright 1999 Francis Beaudet
7 * Copyright 2003 Vitaliy Margolen
9 * This library is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Lesser General Public
11 * License as published by the Free Software Foundation; either
12 * version 2.1 of the License, or (at your option) any later version.
14 * This library is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Lesser General Public License for more details.
19 * You should have received a copy of the GNU Lesser General Public
20 * License along with this library; if not, write to the Free Software
21 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
25 * This code was audited for completeness against the documented features
26 * of Comctl32.dll version 6.0 on May. 20, 2005, by James Hawkins.
28 * Unless otherwise noted, we believe this code to be complete, as per
29 * the specification mentioned above.
30 * If you discover missing features, or bugs, please note them below.
43 * TCS_EX_FLATSEPARATORS
58 * TCM_GETEXTENDEDSTYLE
59 * TCM_SETEXTENDEDSTYLE
78 #include "wine/debug.h"
81 WINE_DEFAULT_DEBUG_CHANNEL(tab);
89 RECT rect; /* bounding rectangle of the item relative to the
90 * leftmost item (the leftmost item, 0, would have a
91 * "left" member of 0 in this rectangle)
93 * additionally the top member holds the row number
94 * and bottom is unused and should be 0 */
95 BYTE extra[1]; /* Space for caller supplied info, variable size */
98 /* The size of a tab item depends on how much extra data is requested */
99 #define TAB_ITEM_SIZE(infoPtr) (sizeof(TAB_ITEM) - sizeof(BYTE) + infoPtr->cbInfo)
103 HWND hwnd; /* Tab control window */
104 HWND hwndNotify; /* notification window (parent) */
105 UINT uNumItem; /* number of tab items */
106 UINT uNumRows; /* number of tab rows */
107 INT tabHeight; /* height of the tab row */
108 INT tabWidth; /* width of tabs */
109 INT tabMinWidth; /* minimum width of items */
110 USHORT uHItemPadding; /* amount of horizontal padding, in pixels */
111 USHORT uVItemPadding; /* amount of vertical padding, in pixels */
112 USHORT uHItemPadding_s; /* Set amount of horizontal padding, in pixels */
113 USHORT uVItemPadding_s; /* Set amount of vertical padding, in pixels */
114 HFONT hFont; /* handle to the current font */
115 HCURSOR hcurArrow; /* handle to the current cursor */
116 HIMAGELIST himl; /* handle to an image list (may be 0) */
117 HWND hwndToolTip; /* handle to tab's tooltip */
118 INT leftmostVisible; /* Used for scrolling, this member contains
119 * the index of the first visible item */
120 INT iSelected; /* the currently selected item */
121 INT iHotTracked; /* the highlighted item under the mouse */
122 INT uFocus; /* item which has the focus */
123 TAB_ITEM* items; /* pointer to an array of TAB_ITEM's */
124 BOOL DoRedraw; /* flag for redrawing when tab contents is changed*/
125 BOOL needsScrolling; /* TRUE if the size of the tabs is greater than
126 * the size of the control */
127 BOOL fHeightSet; /* was the height of the tabs explicitly set? */
128 BOOL bUnicode; /* Unicode control? */
129 HWND hwndUpDown; /* Updown control used for scrolling */
130 INT cbInfo; /* Number of bytes of caller supplied info per tab */
133 /******************************************************************************
134 * Positioning constants
136 #define SELECTED_TAB_OFFSET 2
137 #define ROUND_CORNER_SIZE 2
138 #define DISPLAY_AREA_PADDINGX 2
139 #define DISPLAY_AREA_PADDINGY 2
140 #define CONTROL_BORDER_SIZEX 2
141 #define CONTROL_BORDER_SIZEY 2
142 #define BUTTON_SPACINGX 3
143 #define BUTTON_SPACINGY 3
144 #define FLAT_BTN_SPACINGX 8
145 #define DEFAULT_MIN_TAB_WIDTH 54
146 #define DEFAULT_TAB_WIDTH_FIXED 96
147 #define DEFAULT_PADDING_X 6
148 #define EXTRA_ICON_PADDING 3
150 #define TAB_GetInfoPtr(hwnd) ((TAB_INFO *)GetWindowLongPtrW(hwnd,0))
151 /* Since items are variable sized, cannot directly access them */
152 #define TAB_GetItem(info,i) \
153 ((TAB_ITEM*)((LPBYTE)info->items + (i) * TAB_ITEM_SIZE(info)))
155 #define GET_DEFAULT_MIN_TAB_WIDTH(infoPtr) (DEFAULT_MIN_TAB_WIDTH - (DEFAULT_PADDING_X - (infoPtr)->uHItemPadding) * 2)
157 /******************************************************************************
158 * Hot-tracking timer constants
160 #define TAB_HOTTRACK_TIMER 1
161 #define TAB_HOTTRACK_TIMER_INTERVAL 100 /* milliseconds */
163 static const WCHAR themeClass[] = { 'T','a','b',0 };
165 /******************************************************************************
168 static void TAB_InvalidateTabArea(TAB_INFO *);
169 static void TAB_EnsureSelectionVisible(TAB_INFO *);
170 static void TAB_DrawItemInterior(TAB_INFO *, HDC, INT, RECT*);
173 TAB_SendSimpleNotify (const TAB_INFO *infoPtr, UINT code)
177 nmhdr.hwndFrom = infoPtr->hwnd;
178 nmhdr.idFrom = GetWindowLongPtrW(infoPtr->hwnd, GWLP_ID);
181 return (BOOL) SendMessageW (infoPtr->hwndNotify, WM_NOTIFY,
182 (WPARAM) nmhdr.idFrom, (LPARAM) &nmhdr);
186 TAB_RelayEvent (HWND hwndTip, HWND hwndMsg, UINT uMsg,
187 WPARAM wParam, LPARAM lParam)
195 msg.time = GetMessageTime ();
196 msg.pt.x = LOWORD(GetMessagePos ());
197 msg.pt.y = HIWORD(GetMessagePos ());
199 SendMessageW (hwndTip, TTM_RELAYEVENT, 0, (LPARAM)&msg);
203 TAB_DumpItemExternalT(TCITEMW *pti, UINT iItem, BOOL isW)
206 TRACE("external tab %d, mask=0x%08x, dwState=0x%08lx, dwStateMask=0x%08lx, cchTextMax=0x%08x\n",
207 iItem, pti->mask, pti->dwState, pti->dwStateMask, pti->cchTextMax);
208 TRACE("external tab %d, iImage=%d, lParam=0x%08lx, pszTextW=%s\n",
209 iItem, pti->iImage, pti->lParam, isW ? debugstr_w(pti->pszText) : debugstr_a((LPSTR)pti->pszText));
214 TAB_DumpItemInternal(TAB_INFO *infoPtr, UINT iItem)
219 ti = TAB_GetItem(infoPtr, iItem);
220 TRACE("tab %d, mask=0x%08x, dwState=0x%08lx, pszText=%s, iImage=%d\n",
221 iItem, ti->mask, ti->dwState, debugstr_w(ti->pszText),
223 TRACE("tab %d, rect.left=%ld, rect.top(row)=%ld\n",
224 iItem, ti->rect.left, ti->rect.top);
229 * the index of the selected tab, or -1 if no tab is selected. */
230 static inline LRESULT TAB_GetCurSel (const TAB_INFO *infoPtr)
232 return infoPtr->iSelected;
236 * the index of the tab item that has the focus
238 * we have not to return negative value
240 * test for windows */
241 static inline LRESULT
242 TAB_GetCurFocus (const TAB_INFO *infoPtr)
244 if (infoPtr->uFocus<0)
246 FIXME("we have not to return negative value");
249 return infoPtr->uFocus;
252 static inline LRESULT TAB_GetToolTips (const TAB_INFO *infoPtr)
254 if (infoPtr == NULL) return 0;
255 return (LRESULT)infoPtr->hwndToolTip;
258 static inline LRESULT TAB_SetCurSel (TAB_INFO *infoPtr, INT iItem)
262 if (iItem >= 0 && iItem < infoPtr->uNumItem) {
263 prevItem=infoPtr->iSelected;
264 if (infoPtr->iSelected != iItem) {
265 infoPtr->iSelected=iItem;
266 TAB_EnsureSelectionVisible(infoPtr);
267 TAB_InvalidateTabArea(infoPtr);
273 static LRESULT TAB_SetCurFocus (TAB_INFO *infoPtr, INT iItem)
275 if (iItem < 0 || iItem >= infoPtr->uNumItem) return 0;
277 if (GetWindowLongW(infoPtr->hwnd, GWL_STYLE) & TCS_BUTTONS) {
278 FIXME("Should set input focus\n");
280 int oldFocus = infoPtr->uFocus;
281 if (infoPtr->iSelected != iItem || oldFocus == -1 ) {
282 infoPtr->uFocus = iItem;
283 if (oldFocus != -1) {
284 if (!TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGING)) {
285 infoPtr->iSelected = iItem;
286 TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGE);
289 infoPtr->iSelected = iItem;
290 TAB_EnsureSelectionVisible(infoPtr);
291 TAB_InvalidateTabArea(infoPtr);
298 static inline LRESULT
299 TAB_SetToolTips (TAB_INFO *infoPtr, HWND hwndToolTip)
302 infoPtr->hwndToolTip = hwndToolTip;
306 static inline LRESULT
307 TAB_SetPadding (TAB_INFO *infoPtr, LPARAM lParam)
311 infoPtr->uHItemPadding_s=LOWORD(lParam);
312 infoPtr->uVItemPadding_s=HIWORD(lParam);
317 /******************************************************************************
318 * TAB_InternalGetItemRect
320 * This method will calculate the rectangle representing a given tab item in
321 * client coordinates. This method takes scrolling into account.
323 * This method returns TRUE if the item is visible in the window and FALSE
324 * if it is completely outside the client area.
326 static BOOL TAB_InternalGetItemRect(
327 const TAB_INFO* infoPtr,
332 RECT tmpItemRect,clientRect;
333 LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
335 /* Perform a sanity check and a trivial visibility check. */
336 if ( (infoPtr->uNumItem <= 0) ||
337 (itemIndex >= infoPtr->uNumItem) ||
338 (!((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)) && (itemIndex < infoPtr->leftmostVisible)) )
340 TRACE("Not Visible\n");
341 /* need to initialize these to empty rects */
344 memset(itemRect,0,sizeof(RECT));
345 itemRect->bottom = infoPtr->tabHeight;
348 memset(selectedRect,0,sizeof(RECT));
353 * Avoid special cases in this procedure by assigning the "out"
354 * parameters if the caller didn't supply them
356 if (itemRect == NULL)
357 itemRect = &tmpItemRect;
359 /* Retrieve the unmodified item rect. */
360 *itemRect = TAB_GetItem(infoPtr,itemIndex)->rect;
362 /* calculate the times bottom and top based on the row */
363 GetClientRect(infoPtr->hwnd, &clientRect);
365 if ((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
367 itemRect->right = clientRect.right - SELECTED_TAB_OFFSET - itemRect->left * infoPtr->tabHeight -
368 ((lStyle & TCS_BUTTONS) ? itemRect->left * BUTTON_SPACINGX : 0);
369 itemRect->left = itemRect->right - infoPtr->tabHeight;
371 else if (lStyle & TCS_VERTICAL)
373 itemRect->left = clientRect.left + SELECTED_TAB_OFFSET + itemRect->left * infoPtr->tabHeight +
374 ((lStyle & TCS_BUTTONS) ? itemRect->left * BUTTON_SPACINGX : 0);
375 itemRect->right = itemRect->left + infoPtr->tabHeight;
377 else if (lStyle & TCS_BOTTOM)
379 itemRect->bottom = clientRect.bottom - itemRect->top * infoPtr->tabHeight -
380 ((lStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : SELECTED_TAB_OFFSET);
381 itemRect->top = itemRect->bottom - infoPtr->tabHeight;
383 else /* not TCS_BOTTOM and not TCS_VERTICAL */
385 itemRect->top = clientRect.top + itemRect->top * infoPtr->tabHeight +
386 ((lStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : SELECTED_TAB_OFFSET);
387 itemRect->bottom = itemRect->top + infoPtr->tabHeight;
391 * "scroll" it to make sure the item at the very left of the
392 * tab control is the leftmost visible tab.
394 if(lStyle & TCS_VERTICAL)
398 -TAB_GetItem(infoPtr, infoPtr->leftmostVisible)->rect.top);
401 * Move the rectangle so the first item is slightly offset from
402 * the bottom of the tab control.
406 SELECTED_TAB_OFFSET);
411 -TAB_GetItem(infoPtr, infoPtr->leftmostVisible)->rect.left,
415 * Move the rectangle so the first item is slightly offset from
416 * the left of the tab control.
422 TRACE("item %d tab h=%d, rect=(%ld,%ld)-(%ld,%ld)\n",
423 itemIndex, infoPtr->tabHeight,
424 itemRect->left, itemRect->top, itemRect->right, itemRect->bottom);
426 /* Now, calculate the position of the item as if it were selected. */
427 if (selectedRect!=NULL)
429 CopyRect(selectedRect, itemRect);
431 /* The rectangle of a selected item is a bit wider. */
432 if(lStyle & TCS_VERTICAL)
433 InflateRect(selectedRect, 0, SELECTED_TAB_OFFSET);
435 InflateRect(selectedRect, SELECTED_TAB_OFFSET, 0);
437 /* If it also a bit higher. */
438 if ((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
440 selectedRect->left -= 2; /* the border is thicker on the right */
441 selectedRect->right += SELECTED_TAB_OFFSET;
443 else if (lStyle & TCS_VERTICAL)
445 selectedRect->left -= SELECTED_TAB_OFFSET;
446 selectedRect->right += 1;
448 else if (lStyle & TCS_BOTTOM)
450 selectedRect->bottom += SELECTED_TAB_OFFSET;
452 else /* not TCS_BOTTOM and not TCS_VERTICAL */
454 selectedRect->top -= SELECTED_TAB_OFFSET;
455 selectedRect->bottom -= 1;
459 /* Check for visibility */
460 if (lStyle & TCS_VERTICAL)
461 return (itemRect->top < clientRect.bottom) && (itemRect->bottom > clientRect.top);
463 return (itemRect->left < clientRect.right) && (itemRect->right > clientRect.left);
467 TAB_GetItemRect(TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
469 return TAB_InternalGetItemRect(infoPtr, (INT)wParam, (LPRECT)lParam, (LPRECT)NULL);
472 /******************************************************************************
475 * This method is called to handle keyboard input
477 static LRESULT TAB_KeyUp(TAB_INFO* infoPtr, WPARAM keyCode)
484 newItem = infoPtr->uFocus - 1;
487 newItem = infoPtr->uFocus + 1;
492 * If we changed to a valid item, change the selection
495 newItem < infoPtr->uNumItem &&
496 infoPtr->uFocus != newItem)
498 if (!TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGING))
500 infoPtr->iSelected = newItem;
501 infoPtr->uFocus = newItem;
502 TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGE);
504 TAB_EnsureSelectionVisible(infoPtr);
505 TAB_InvalidateTabArea(infoPtr);
512 /******************************************************************************
515 * This method is called whenever the focus goes in or out of this control
516 * it is used to update the visual state of the control.
518 static void TAB_FocusChanging(const TAB_INFO *infoPtr)
524 * Get the rectangle for the item.
526 isVisible = TAB_InternalGetItemRect(infoPtr,
532 * If the rectangle is not completely invisible, invalidate that
533 * portion of the window.
537 TRACE("invalidate (%ld,%ld)-(%ld,%ld)\n",
538 selectedRect.left,selectedRect.top,
539 selectedRect.right,selectedRect.bottom);
540 InvalidateRect(infoPtr->hwnd, &selectedRect, TRUE);
544 static INT TAB_InternalHitTest (
553 for (iCount = 0; iCount < infoPtr->uNumItem; iCount++)
555 TAB_InternalGetItemRect(infoPtr, iCount, &rect, NULL);
557 if (PtInRect(&rect, pt))
559 *flags = TCHT_ONITEM;
564 *flags = TCHT_NOWHERE;
568 static inline LRESULT
569 TAB_HitTest (TAB_INFO *infoPtr, LPTCHITTESTINFO lptest)
571 return TAB_InternalHitTest (infoPtr, lptest->pt, &lptest->flags);
574 /******************************************************************************
577 * Napster v2b5 has a tab control for its main navigation which has a client
578 * area that covers the whole area of the dialog pages.
579 * That's why it receives all msgs for that area and the underlying dialog ctrls
581 * So I decided that we should handle WM_NCHITTEST here and return
582 * HTTRANSPARENT if we don't hit the tab control buttons.
583 * FIXME: WM_NCHITTEST handling correct ? Fix it if you know that Windows
584 * doesn't do it that way. Maybe depends on tab control styles ?
586 static inline LRESULT
587 TAB_NCHitTest (TAB_INFO *infoPtr, LPARAM lParam)
592 pt.x = LOWORD(lParam);
593 pt.y = HIWORD(lParam);
594 ScreenToClient(infoPtr->hwnd, &pt);
596 if (TAB_InternalHitTest(infoPtr, pt, &dummyflag) == -1)
597 return HTTRANSPARENT;
603 TAB_LButtonDown (TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
609 if (infoPtr->hwndToolTip)
610 TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
611 WM_LBUTTONDOWN, wParam, lParam);
613 if (GetWindowLongW(infoPtr->hwnd, GWL_STYLE) & TCS_FOCUSONBUTTONDOWN ) {
614 SetFocus (infoPtr->hwnd);
617 if (infoPtr->hwndToolTip)
618 TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
619 WM_LBUTTONDOWN, wParam, lParam);
621 pt.x = (INT)LOWORD(lParam);
622 pt.y = (INT)HIWORD(lParam);
624 newItem = TAB_InternalHitTest (infoPtr, pt, &dummy);
626 TRACE("On Tab, item %d\n", newItem);
628 if (newItem != -1 && infoPtr->iSelected != newItem)
630 if (!TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGING))
632 infoPtr->iSelected = newItem;
633 infoPtr->uFocus = newItem;
634 TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGE);
636 TAB_EnsureSelectionVisible(infoPtr);
638 TAB_InvalidateTabArea(infoPtr);
644 static inline LRESULT
645 TAB_LButtonUp (const TAB_INFO *infoPtr)
647 TAB_SendSimpleNotify(infoPtr, NM_CLICK);
652 static inline LRESULT
653 TAB_RButtonDown (const TAB_INFO *infoPtr)
655 TAB_SendSimpleNotify(infoPtr, NM_RCLICK);
659 /******************************************************************************
660 * TAB_DrawLoneItemInterior
662 * This calls TAB_DrawItemInterior. However, TAB_DrawItemInterior is normally
663 * called by TAB_DrawItem which is normally called by TAB_Refresh which sets
664 * up the device context and font. This routine does the same setup but
665 * only calls TAB_DrawItemInterior for the single specified item.
668 TAB_DrawLoneItemInterior(TAB_INFO* infoPtr, int iItem)
670 HDC hdc = GetDC(infoPtr->hwnd);
673 /* Clip UpDown control to not draw over it */
674 if (infoPtr->needsScrolling)
676 GetWindowRect(infoPtr->hwnd, &rC);
677 GetWindowRect(infoPtr->hwndUpDown, &r);
678 ExcludeClipRect(hdc, r.left - rC.left, r.top - rC.top, r.right - rC.left, r.bottom - rC.top);
680 TAB_DrawItemInterior(infoPtr, hdc, iItem, NULL);
681 ReleaseDC(infoPtr->hwnd, hdc);
684 /* update a tab after hottracking - invalidate it or just redraw the interior,
685 * based on whether theming is used or not */
686 static inline void hottrack_refresh (TAB_INFO* infoPtr, int tabIndex)
688 if (tabIndex == -1) return;
690 if (GetWindowTheme (infoPtr->hwnd))
693 TAB_InternalGetItemRect(infoPtr, tabIndex, &rect, NULL);
694 InvalidateRect (infoPtr->hwnd, &rect, FALSE);
697 TAB_DrawLoneItemInterior(infoPtr, tabIndex);
700 /******************************************************************************
701 * TAB_HotTrackTimerProc
703 * When a mouse-move event causes a tab to be highlighted (hot-tracking), a
704 * timer is setup so we can check if the mouse is moved out of our window.
705 * (We don't get an event when the mouse leaves, the mouse-move events just
706 * stop being delivered to our window and just start being delivered to
707 * another window.) This function is called when the timer triggers so
708 * we can check if the mouse has left our window. If so, we un-highlight
709 * the hot-tracked tab.
712 TAB_HotTrackTimerProc
714 HWND hwnd, /* handle of window for timer messages */
715 UINT uMsg, /* WM_TIMER message */
716 UINT_PTR idEvent, /* timer identifier */
717 DWORD dwTime /* current system time */
720 TAB_INFO* infoPtr = TAB_GetInfoPtr(hwnd);
722 if (infoPtr != NULL && infoPtr->iHotTracked >= 0)
727 ** If we can't get the cursor position, or if the cursor is outside our
728 ** window, we un-highlight the hot-tracked tab. Note that the cursor is
729 ** "outside" even if it is within our bounding rect if another window
730 ** overlaps. Note also that the case where the cursor stayed within our
731 ** window but has moved off the hot-tracked tab will be handled by the
732 ** WM_MOUSEMOVE event.
734 if (!GetCursorPos(&pt) || WindowFromPoint(pt) != hwnd)
736 /* Redraw iHotTracked to look normal */
737 INT iRedraw = infoPtr->iHotTracked;
738 infoPtr->iHotTracked = -1;
739 hottrack_refresh (infoPtr, iRedraw);
741 /* Kill this timer */
742 KillTimer(hwnd, TAB_HOTTRACK_TIMER);
747 /******************************************************************************
750 * If a tab control has the TCS_HOTTRACK style, then the tab under the mouse
751 * should be highlighted. This function determines which tab in a tab control,
752 * if any, is under the mouse and records that information. The caller may
753 * supply output parameters to receive the item number of the tab item which
754 * was highlighted but isn't any longer and of the tab item which is now
755 * highlighted but wasn't previously. The caller can use this information to
756 * selectively redraw those tab items.
758 * If the caller has a mouse position, it can supply it through the pos
759 * parameter. For example, TAB_MouseMove does this. Otherwise, the caller
760 * supplies NULL and this function determines the current mouse position
768 int* out_redrawLeave,
775 if (out_redrawLeave != NULL)
776 *out_redrawLeave = -1;
777 if (out_redrawEnter != NULL)
778 *out_redrawEnter = -1;
780 if ((GetWindowLongW(infoPtr->hwnd, GWL_STYLE) & TCS_HOTTRACK)
781 || GetWindowTheme (infoPtr->hwnd))
789 ScreenToClient(infoPtr->hwnd, &pt);
797 item = TAB_InternalHitTest(infoPtr, pt, &flags);
800 if (item != infoPtr->iHotTracked)
802 if (infoPtr->iHotTracked >= 0)
804 /* Mark currently hot-tracked to be redrawn to look normal */
805 if (out_redrawLeave != NULL)
806 *out_redrawLeave = infoPtr->iHotTracked;
810 /* Kill timer which forces recheck of mouse pos */
811 KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
816 /* Start timer so we recheck mouse pos */
817 UINT timerID = SetTimer
821 TAB_HOTTRACK_TIMER_INTERVAL,
822 TAB_HotTrackTimerProc
826 return; /* Hot tracking not available */
829 infoPtr->iHotTracked = item;
833 /* Mark new hot-tracked to be redrawn to look highlighted */
834 if (out_redrawEnter != NULL)
835 *out_redrawEnter = item;
840 /******************************************************************************
843 * Handles the mouse-move event. Updates tooltips. Updates hot-tracking.
846 TAB_MouseMove (TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
851 if (infoPtr->hwndToolTip)
852 TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
853 WM_LBUTTONDOWN, wParam, lParam);
855 /* Determine which tab to highlight. Redraw tabs which change highlight
857 TAB_RecalcHotTrack(infoPtr, &lParam, &redrawLeave, &redrawEnter);
859 hottrack_refresh (infoPtr, redrawLeave);
860 hottrack_refresh (infoPtr, redrawEnter);
865 /******************************************************************************
868 * Calculates the tab control's display area given the window rectangle or
869 * the window rectangle given the requested display rectangle.
871 static LRESULT TAB_AdjustRect(
876 DWORD lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
877 LONG *iRightBottom, *iLeftTop;
879 TRACE ("hwnd=%p fLarger=%d (%ld,%ld)-(%ld,%ld)\n", infoPtr->hwnd, fLarger, prc->left, prc->top, prc->right, prc->bottom);
881 if(lStyle & TCS_VERTICAL)
883 iRightBottom = &(prc->right);
884 iLeftTop = &(prc->left);
888 iRightBottom = &(prc->bottom);
889 iLeftTop = &(prc->top);
892 if (fLarger) /* Go from display rectangle */
894 /* Add the height of the tabs. */
895 if (lStyle & TCS_BOTTOM)
896 *iRightBottom += infoPtr->tabHeight * infoPtr->uNumRows;
898 *iLeftTop -= infoPtr->tabHeight * infoPtr->uNumRows +
899 ((lStyle & TCS_BUTTONS)? 3 * (infoPtr->uNumRows - 1) : 0);
901 /* Inflate the rectangle for the padding */
902 InflateRect(prc, DISPLAY_AREA_PADDINGX, DISPLAY_AREA_PADDINGY);
904 /* Inflate for the border */
905 InflateRect(prc, CONTROL_BORDER_SIZEX, CONTROL_BORDER_SIZEY);
907 else /* Go from window rectangle. */
909 /* Deflate the rectangle for the border */
910 InflateRect(prc, -CONTROL_BORDER_SIZEX, -CONTROL_BORDER_SIZEY);
912 /* Deflate the rectangle for the padding */
913 InflateRect(prc, -DISPLAY_AREA_PADDINGX, -DISPLAY_AREA_PADDINGY);
915 /* Remove the height of the tabs. */
916 if (lStyle & TCS_BOTTOM)
917 *iRightBottom -= infoPtr->tabHeight * infoPtr->uNumRows;
919 *iLeftTop += (infoPtr->tabHeight) * infoPtr->uNumRows +
920 ((lStyle & TCS_BUTTONS)? 3 * (infoPtr->uNumRows - 1) : 0);
926 /******************************************************************************
929 * This method will handle the notification from the scroll control and
930 * perform the scrolling operation on the tab control.
932 static LRESULT TAB_OnHScroll(
938 if(nScrollCode == SB_THUMBPOSITION && nPos != infoPtr->leftmostVisible)
940 if(nPos < infoPtr->leftmostVisible)
941 infoPtr->leftmostVisible--;
943 infoPtr->leftmostVisible++;
945 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
946 TAB_InvalidateTabArea(infoPtr);
947 SendMessageW(infoPtr->hwndUpDown, UDM_SETPOS, 0,
948 MAKELONG(infoPtr->leftmostVisible, 0));
954 /******************************************************************************
957 * This method will check the current scrolling state and make sure the
958 * scrolling control is displayed (or not).
960 static void TAB_SetupScrolling(
963 const RECT* clientRect)
965 static const WCHAR msctls_updown32W[] = { 'm','s','c','t','l','s','_','u','p','d','o','w','n','3','2',0 };
966 static const WCHAR emptyW[] = { 0 };
968 DWORD lStyle = GetWindowLongW(hwnd, GWL_STYLE);
970 if (infoPtr->needsScrolling)
976 * Calculate the position of the scroll control.
978 if(lStyle & TCS_VERTICAL)
980 controlPos.right = clientRect->right;
981 controlPos.left = controlPos.right - 2 * GetSystemMetrics(SM_CXHSCROLL);
983 if (lStyle & TCS_BOTTOM)
985 controlPos.top = clientRect->bottom - infoPtr->tabHeight;
986 controlPos.bottom = controlPos.top + GetSystemMetrics(SM_CYHSCROLL);
990 controlPos.bottom = clientRect->top + infoPtr->tabHeight;
991 controlPos.top = controlPos.bottom - GetSystemMetrics(SM_CYHSCROLL);
996 controlPos.right = clientRect->right;
997 controlPos.left = controlPos.right - 2 * GetSystemMetrics(SM_CXHSCROLL);
999 if (lStyle & TCS_BOTTOM)
1001 controlPos.top = clientRect->bottom - infoPtr->tabHeight;
1002 controlPos.bottom = controlPos.top + GetSystemMetrics(SM_CYHSCROLL);
1006 controlPos.bottom = clientRect->top + infoPtr->tabHeight;
1007 controlPos.top = controlPos.bottom - GetSystemMetrics(SM_CYHSCROLL);
1012 * If we don't have a scroll control yet, we want to create one.
1013 * If we have one, we want to make sure it's positioned properly.
1015 if (infoPtr->hwndUpDown==0)
1017 infoPtr->hwndUpDown = CreateWindowW(msctls_updown32W, emptyW,
1018 WS_VISIBLE | WS_CHILD | UDS_HORZ,
1019 controlPos.left, controlPos.top,
1020 controlPos.right - controlPos.left,
1021 controlPos.bottom - controlPos.top,
1022 hwnd, NULL, NULL, NULL);
1026 SetWindowPos(infoPtr->hwndUpDown,
1028 controlPos.left, controlPos.top,
1029 controlPos.right - controlPos.left,
1030 controlPos.bottom - controlPos.top,
1031 SWP_SHOWWINDOW | SWP_NOZORDER);
1034 /* Now calculate upper limit of the updown control range.
1035 * We do this by calculating how many tabs will be offscreen when the
1036 * last tab is visible.
1038 if(infoPtr->uNumItem)
1040 vsize = clientRect->right - (controlPos.right - controlPos.left + 1);
1041 maxRange = infoPtr->uNumItem;
1042 tabwidth = TAB_GetItem(infoPtr, infoPtr->uNumItem - 1)->rect.right;
1044 for(; maxRange > 0; maxRange--)
1046 if(tabwidth - TAB_GetItem(infoPtr,maxRange - 1)->rect.left > vsize)
1050 if(maxRange == infoPtr->uNumItem)
1056 /* If we once had a scroll control... hide it */
1057 if (infoPtr->hwndUpDown!=0)
1058 ShowWindow(infoPtr->hwndUpDown, SW_HIDE);
1060 if (infoPtr->hwndUpDown)
1061 SendMessageW(infoPtr->hwndUpDown, UDM_SETRANGE32, 0, maxRange);
1064 /******************************************************************************
1067 * This method will calculate the position rectangles of all the items in the
1068 * control. The rectangle calculated starts at 0 for the first item in the
1069 * list and ignores scrolling and selection.
1070 * It also uses the current font to determine the height of the tab row and
1071 * it checks if all the tabs fit in the client area of the window. If they
1072 * don't, a scrolling control is added.
1074 static void TAB_SetItemBounds (TAB_INFO *infoPtr)
1076 LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1077 TEXTMETRICW fontMetrics;
1080 INT curItemRowCount;
1081 HFONT hFont, hOldFont;
1090 * We need to get text information so we need a DC and we need to select
1093 hdc = GetDC(infoPtr->hwnd);
1095 hFont = infoPtr->hFont ? infoPtr->hFont : GetStockObject (SYSTEM_FONT);
1096 hOldFont = SelectObject (hdc, hFont);
1099 * We will base the rectangle calculations on the client rectangle
1102 GetClientRect(infoPtr->hwnd, &clientRect);
1104 /* if TCS_VERTICAL then swap the height and width so this code places the
1105 tabs along the top of the rectangle and we can just rotate them after
1106 rather than duplicate all of the below code */
1107 if(lStyle & TCS_VERTICAL)
1109 iTemp = clientRect.bottom;
1110 clientRect.bottom = clientRect.right;
1111 clientRect.right = iTemp;
1114 /* Now use hPadding and vPadding */
1115 infoPtr->uHItemPadding = infoPtr->uHItemPadding_s;
1116 infoPtr->uVItemPadding = infoPtr->uVItemPadding_s;
1118 /* The leftmost item will be "0" aligned */
1120 curItemRowCount = infoPtr->uNumItem ? 1 : 0;
1122 if (!(infoPtr->fHeightSet))
1125 int icon_height = 0;
1127 /* Use the current font to determine the height of a tab. */
1128 GetTextMetricsW(hdc, &fontMetrics);
1130 /* Get the icon height */
1132 ImageList_GetIconSize(infoPtr->himl, 0, &icon_height);
1134 /* Take the highest between font or icon */
1135 if (fontMetrics.tmHeight > icon_height)
1136 item_height = fontMetrics.tmHeight + 2;
1138 item_height = icon_height;
1141 * Make sure there is enough space for the letters + icon + growing the
1142 * selected item + extra space for the selected item.
1144 infoPtr->tabHeight = item_height +
1145 ((lStyle & TCS_BUTTONS) ? 2 : 1) *
1146 infoPtr->uVItemPadding;
1148 TRACE("tabH=%d, tmH=%ld, iconh=%d\n",
1149 infoPtr->tabHeight, fontMetrics.tmHeight, icon_height);
1152 TRACE("client right=%ld\n", clientRect.right);
1154 /* Get the icon width */
1157 ImageList_GetIconSize(infoPtr->himl, &icon_width, 0);
1159 if (lStyle & TCS_FIXEDWIDTH)
1162 /* Add padding if icon is present */
1163 icon_width += infoPtr->uHItemPadding;
1166 for (curItem = 0; curItem < infoPtr->uNumItem; curItem++)
1168 TAB_ITEM *curr = TAB_GetItem(infoPtr, curItem);
1170 /* Set the leftmost position of the tab. */
1171 curr->rect.left = curItemLeftPos;
1173 if (lStyle & TCS_FIXEDWIDTH)
1175 curr->rect.right = curr->rect.left +
1176 max(infoPtr->tabWidth, icon_width);
1178 else if (!curr->pszText)
1180 /* If no text use minimum tab width including padding. */
1181 if (infoPtr->tabMinWidth < 0)
1182 curr->rect.right = curr->rect.left + GET_DEFAULT_MIN_TAB_WIDTH(infoPtr);
1185 curr->rect.right = curr->rect.left + infoPtr->tabMinWidth;
1187 /* Add extra padding if icon is present */
1188 if (infoPtr->himl && infoPtr->tabMinWidth > 0 && infoPtr->tabMinWidth < DEFAULT_MIN_TAB_WIDTH
1189 && infoPtr->uHItemPadding > 1)
1190 curr->rect.right += EXTRA_ICON_PADDING * (infoPtr->uHItemPadding-1);
1197 /* Calculate how wide the tab is depending on the text it contains */
1198 GetTextExtentPoint32W(hdc, curr->pszText,
1199 lstrlenW(curr->pszText), &size);
1201 tabwidth = size.cx + icon_width + 2 * infoPtr->uHItemPadding;
1203 if (infoPtr->tabMinWidth < 0)
1204 tabwidth = max(tabwidth, GET_DEFAULT_MIN_TAB_WIDTH(infoPtr));
1206 tabwidth = max(tabwidth, infoPtr->tabMinWidth);
1208 curr->rect.right = curr->rect.left + tabwidth;
1209 TRACE("for <%s>, l,r=%ld,%ld\n",
1210 debugstr_w(curr->pszText), curr->rect.left, curr->rect.right);
1214 * Check if this is a multiline tab control and if so
1215 * check to see if we should wrap the tabs
1217 * Wrap all these tabs. We will arrange them evenly later.
1221 if (((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)) &&
1223 (clientRect.right - CONTROL_BORDER_SIZEX - DISPLAY_AREA_PADDINGX)))
1225 curr->rect.right -= curr->rect.left;
1227 curr->rect.left = 0;
1229 TRACE("wrapping <%s>, l,r=%ld,%ld\n", debugstr_w(curr->pszText),
1230 curr->rect.left, curr->rect.right);
1233 curr->rect.bottom = 0;
1234 curr->rect.top = curItemRowCount - 1;
1236 TRACE("Rect: T %li, L %li, B %li, R %li\n", curr->rect.top,
1237 curr->rect.left, curr->rect.bottom, curr->rect.right);
1240 * The leftmost position of the next item is the rightmost position
1243 if (lStyle & TCS_BUTTONS)
1245 curItemLeftPos = curr->rect.right + BUTTON_SPACINGX;
1246 if (lStyle & TCS_FLATBUTTONS)
1247 curItemLeftPos += FLAT_BTN_SPACINGX;
1250 curItemLeftPos = curr->rect.right;
1253 if (!((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)))
1256 * Check if we need a scrolling control.
1258 infoPtr->needsScrolling = (curItemLeftPos + (2 * SELECTED_TAB_OFFSET) >
1261 /* Don't need scrolling, then update infoPtr->leftmostVisible */
1262 if(!infoPtr->needsScrolling)
1263 infoPtr->leftmostVisible = 0;
1268 * No scrolling in Multiline or Vertical styles.
1270 infoPtr->needsScrolling = FALSE;
1271 infoPtr->leftmostVisible = 0;
1273 TAB_SetupScrolling(infoPtr->hwnd, infoPtr, &clientRect);
1275 /* Set the number of rows */
1276 infoPtr->uNumRows = curItemRowCount;
1278 /* Arrange all tabs evenly if style says so */
1279 if (!(lStyle & TCS_RAGGEDRIGHT) &&
1280 ((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)) &&
1281 (infoPtr->uNumItem > 0) &&
1282 (infoPtr->uNumRows > 1))
1284 INT tabPerRow,remTab,iRow;
1289 * Ok windows tries to even out the rows. place the same
1290 * number of tabs in each row. So lets give that a shot
1293 tabPerRow = infoPtr->uNumItem / (infoPtr->uNumRows);
1294 remTab = infoPtr->uNumItem % (infoPtr->uNumRows);
1296 for (iItm=0,iRow=0,iCount=0,curItemLeftPos=0;
1297 iItm<infoPtr->uNumItem;
1300 /* normalize the current rect */
1301 TAB_ITEM *curr = TAB_GetItem(infoPtr, iItm);
1303 /* shift the item to the left side of the clientRect */
1304 curr->rect.right -= curr->rect.left;
1305 curr->rect.left = 0;
1307 TRACE("r=%ld, cl=%d, cl.r=%ld, iCount=%d, iRow=%d, uNumRows=%d, remTab=%d, tabPerRow=%d\n",
1308 curr->rect.right, curItemLeftPos, clientRect.right,
1309 iCount, iRow, infoPtr->uNumRows, remTab, tabPerRow);
1311 /* if we have reached the maximum number of tabs on this row */
1312 /* move to the next row, reset our current item left position and */
1313 /* the count of items on this row */
1315 if (lStyle & TCS_VERTICAL) {
1316 /* Vert: Add the remaining tabs in the *last* remainder rows */
1317 if (iCount >= ((iRow>=(INT)infoPtr->uNumRows - remTab)?tabPerRow + 1:tabPerRow)) {
1323 /* Horz: Add the remaining tabs in the *first* remainder rows */
1324 if (iCount >= ((iRow<remTab)?tabPerRow + 1:tabPerRow)) {
1331 /* shift the item to the right to place it as the next item in this row */
1332 curr->rect.left += curItemLeftPos;
1333 curr->rect.right += curItemLeftPos;
1334 curr->rect.top = iRow;
1335 if (lStyle & TCS_BUTTONS)
1337 curItemLeftPos = curr->rect.right + 1;
1338 if (lStyle & TCS_FLATBUTTONS)
1339 curItemLeftPos += FLAT_BTN_SPACINGX;
1342 curItemLeftPos = curr->rect.right;
1344 TRACE("arranging <%s>, l,r=%ld,%ld, row=%ld\n",
1345 debugstr_w(curr->pszText), curr->rect.left,
1346 curr->rect.right, curr->rect.top);
1353 INT widthDiff, iIndexStart=0, iIndexEnd=0;
1357 while(iIndexStart < infoPtr->uNumItem)
1359 TAB_ITEM *start = TAB_GetItem(infoPtr, iIndexStart);
1362 * find the index of the row
1364 /* find the first item on the next row */
1365 for (iIndexEnd=iIndexStart;
1366 (iIndexEnd < infoPtr->uNumItem) &&
1367 (TAB_GetItem(infoPtr, iIndexEnd)->rect.top ==
1370 /* intentionally blank */;
1373 * we need to justify these tabs so they fill the whole given
1377 /* find the amount of space remaining on this row */
1378 widthDiff = clientRect.right - (2 * SELECTED_TAB_OFFSET) -
1379 TAB_GetItem(infoPtr, iIndexEnd - 1)->rect.right;
1381 /* iCount is the number of tab items on this row */
1382 iCount = iIndexEnd - iIndexStart;
1386 remainder = widthDiff % iCount;
1387 widthDiff = widthDiff / iCount;
1388 /* add widthDiff/iCount, or extra space/items on row, to each item on this row */
1389 for (iIndex=iIndexStart, iCount=0; iIndex < iIndexEnd; iIndex++, iCount++)
1391 TAB_ITEM *item = TAB_GetItem(infoPtr, iIndex);
1393 item->rect.left += iCount * widthDiff;
1394 item->rect.right += (iCount + 1) * widthDiff;
1396 TRACE("adjusting 1 <%s>, l,r=%ld,%ld\n",
1397 debugstr_w(item->pszText),
1398 item->rect.left, item->rect.right);
1401 TAB_GetItem(infoPtr, iIndex - 1)->rect.right += remainder;
1403 else /* we have only one item on this row, make it take up the entire row */
1405 start->rect.left = clientRect.left;
1406 start->rect.right = clientRect.right - 4;
1408 TRACE("adjusting 2 <%s>, l,r=%ld,%ld\n",
1409 debugstr_w(start->pszText),
1410 start->rect.left, start->rect.right);
1415 iIndexStart = iIndexEnd;
1420 /* if TCS_VERTICAL rotate the tabs so they are along the side of the clientRect */
1421 if(lStyle & TCS_VERTICAL)
1424 for(iIndex = 0; iIndex < infoPtr->uNumItem; iIndex++)
1426 rcItem = &TAB_GetItem(infoPtr, iIndex)->rect;
1428 rcOriginal = *rcItem;
1430 /* this is rotating the items by 90 degrees clockwise around the center of the control */
1431 rcItem->top = (rcOriginal.left - clientRect.left);
1432 rcItem->bottom = rcItem->top + (rcOriginal.right - rcOriginal.left);
1433 rcItem->left = rcOriginal.top;
1434 rcItem->right = rcOriginal.bottom;
1438 TAB_EnsureSelectionVisible(infoPtr);
1439 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
1442 SelectObject (hdc, hOldFont);
1443 ReleaseDC (infoPtr->hwnd, hdc);
1448 TAB_EraseTabInterior
1456 LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1457 HBRUSH hbr = CreateSolidBrush (comctl32_color.clrBtnFace);
1458 BOOL deleteBrush = TRUE;
1459 RECT rTemp = *drawRect;
1461 InflateRect(&rTemp, -2, -2);
1462 if (lStyle & TCS_BUTTONS)
1464 if (iItem == infoPtr->iSelected)
1466 /* Background color */
1467 if (!(lStyle & TCS_OWNERDRAWFIXED))
1470 hbr = GetSysColorBrush(COLOR_SCROLLBAR);
1472 SetTextColor(hdc, comctl32_color.clr3dFace);
1473 SetBkColor(hdc, comctl32_color.clr3dHilight);
1475 /* if COLOR_WINDOW happens to be the same as COLOR_3DHILIGHT
1476 * we better use 0x55aa bitmap brush to make scrollbar's background
1477 * look different from the window background.
1479 if (comctl32_color.clr3dHilight == comctl32_color.clrWindow)
1480 hbr = COMCTL32_hPattern55AABrush;
1482 deleteBrush = FALSE;
1484 FillRect(hdc, &rTemp, hbr);
1486 else /* ! selected */
1488 if (lStyle & TCS_FLATBUTTONS)
1490 FillRect(hdc, drawRect, hbr);
1491 if (iItem == infoPtr->iHotTracked)
1492 DrawEdge(hdc, drawRect, EDGE_RAISED, BF_SOFT|BF_RECT);
1495 FillRect(hdc, &rTemp, hbr);
1499 else /* !TCS_BUTTONS */
1501 if (!GetWindowTheme (infoPtr->hwnd))
1502 FillRect(hdc, &rTemp, hbr);
1506 if (deleteBrush) DeleteObject(hbr);
1509 /******************************************************************************
1510 * TAB_DrawItemInterior
1512 * This method is used to draw the interior (text and icon) of a single tab
1513 * into the tab control.
1516 TAB_DrawItemInterior
1524 LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1533 /* if (drawRect == NULL) */
1540 * Get the rectangle for the item.
1542 isVisible = TAB_InternalGetItemRect(infoPtr, iItem, &itemRect, &selectedRect);
1547 * Make sure drawRect points to something valid; simplifies code.
1549 drawRect = &localRect;
1552 * This logic copied from the part of TAB_DrawItem which draws
1553 * the tab background. It's important to keep it in sync. I
1554 * would have liked to avoid code duplication, but couldn't figure
1555 * out how without making spaghetti of TAB_DrawItem.
1557 if (iItem == infoPtr->iSelected)
1558 *drawRect = selectedRect;
1560 *drawRect = itemRect;
1562 if (lStyle & TCS_BUTTONS)
1564 if (iItem == infoPtr->iSelected)
1566 drawRect->left += 4;
1568 drawRect->right -= 4;
1569 drawRect->bottom -= 1;
1573 drawRect->left += 2;
1575 drawRect->right -= 2;
1576 drawRect->bottom -= 2;
1581 if ((lStyle & TCS_VERTICAL) && (lStyle & TCS_BOTTOM))
1583 if (iItem != infoPtr->iSelected)
1585 drawRect->left += 2;
1587 drawRect->bottom -= 2;
1590 else if (lStyle & TCS_VERTICAL)
1592 if (iItem == infoPtr->iSelected)
1594 drawRect->right += 1;
1599 drawRect->right -= 2;
1600 drawRect->bottom -= 2;
1603 else if (lStyle & TCS_BOTTOM)
1605 if (iItem == infoPtr->iSelected)
1611 InflateRect(drawRect, -2, -2);
1612 drawRect->bottom += 2;
1617 if (iItem == infoPtr->iSelected)
1619 drawRect->bottom += 3;
1623 drawRect->bottom -= 2;
1624 InflateRect(drawRect, -2, 0);
1629 TRACE("drawRect=(%ld,%ld)-(%ld,%ld)\n",
1630 drawRect->left, drawRect->top, drawRect->right, drawRect->bottom);
1632 /* Clear interior */
1633 TAB_EraseTabInterior (infoPtr, hdc, iItem, drawRect);
1635 /* Draw the focus rectangle */
1636 if (!(lStyle & TCS_FOCUSNEVER) &&
1637 (GetFocus() == infoPtr->hwnd) &&
1638 (iItem == infoPtr->uFocus) )
1640 RECT rFocus = *drawRect;
1641 InflateRect(&rFocus, -3, -3);
1642 if (lStyle & TCS_BOTTOM && !(lStyle & TCS_VERTICAL))
1644 if (lStyle & TCS_BUTTONS)
1650 DrawFocusRect(hdc, &rFocus);
1656 htextPen = CreatePen( PS_SOLID, 1, GetSysColor(COLOR_BTNTEXT) );
1657 holdPen = SelectObject(hdc, htextPen);
1658 hOldFont = SelectObject(hdc, infoPtr->hFont);
1661 * Setup for text output
1663 oldBkMode = SetBkMode(hdc, TRANSPARENT);
1664 if (!GetWindowTheme (infoPtr->hwnd) || (lStyle & TCS_BUTTONS))
1665 SetTextColor(hdc, (((lStyle & TCS_HOTTRACK) && (iItem == infoPtr->iHotTracked)
1666 && !(lStyle & TCS_FLATBUTTONS))
1667 | (TAB_GetItem(infoPtr, iItem)->dwState & TCIS_HIGHLIGHTED)) ?
1668 comctl32_color.clrHighlight : comctl32_color.clrBtnText);
1671 * if owner draw, tell the owner to draw
1673 if ((lStyle & TCS_OWNERDRAWFIXED) && GetParent(infoPtr->hwnd))
1679 drawRect->right -= 1;
1680 if ( iItem == infoPtr->iSelected )
1682 drawRect->right -= 1;
1683 drawRect->left += 1;
1687 * get the control id
1689 id = (UINT)GetWindowLongPtrW( infoPtr->hwnd, GWLP_ID );
1692 * put together the DRAWITEMSTRUCT
1694 dis.CtlType = ODT_TAB;
1697 dis.itemAction = ODA_DRAWENTIRE;
1699 if ( iItem == infoPtr->iSelected )
1700 dis.itemState |= ODS_SELECTED;
1701 if (infoPtr->uFocus == iItem)
1702 dis.itemState |= ODS_FOCUS;
1703 dis.hwndItem = infoPtr->hwnd;
1705 CopyRect(&dis.rcItem,drawRect);
1706 dis.itemData = (ULONG_PTR)TAB_GetItem(infoPtr, iItem)->extra;
1709 * send the draw message
1711 SendMessageW( infoPtr->hwndNotify, WM_DRAWITEM, (WPARAM)id, (LPARAM)&dis );
1715 TAB_ITEM *item = TAB_GetItem(infoPtr, iItem);
1719 /* used to center the icon and text in the tab */
1721 INT center_offset_h, center_offset_v;
1723 /* set rcImage to drawRect, we will use top & left in our ImageList_Draw call */
1724 rcImage = *drawRect;
1728 rcText.left = rcText.top = rcText.right = rcText.bottom = 0;
1730 /* get the rectangle that the text fits in */
1733 DrawTextW(hdc, item->pszText, -1, &rcText, DT_CALCRECT);
1736 * If not owner draw, then do the drawing ourselves.
1740 if (infoPtr->himl && (item->mask & TCIF_IMAGE))
1745 ImageList_GetIconSize(infoPtr->himl, &cx, &cy);
1747 if(lStyle & TCS_VERTICAL)
1749 center_offset_h = ((drawRect->bottom - drawRect->top) - (cy + infoPtr->uHItemPadding + (rcText.right - rcText.left))) / 2;
1750 center_offset_v = ((drawRect->right - drawRect->left) - (cx + infoPtr->uVItemPadding)) / 2;
1754 center_offset_h = ((drawRect->right - drawRect->left) - (cx + infoPtr->uHItemPadding + (rcText.right - rcText.left))) / 2;
1755 center_offset_v = ((drawRect->bottom - drawRect->top) - (cy + infoPtr->uVItemPadding)) / 2;
1758 if (lStyle & TCS_FIXEDWIDTH && lStyle & (TCS_FORCELABELLEFT | TCS_FORCEICONLEFT))
1759 center_offset_h = infoPtr->uHItemPadding;
1761 if (center_offset_h < 2)
1762 center_offset_h = 2;
1764 if (center_offset_v < 0)
1765 center_offset_v = 0;
1767 TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%ld,%ld)-(%ld,%ld), textlen=%ld\n",
1768 debugstr_w(item->pszText), center_offset_h, center_offset_v,
1769 drawRect->left, drawRect->top, drawRect->right, drawRect->bottom,
1770 (rcText.right-rcText.left));
1772 if((lStyle & TCS_VERTICAL) && (lStyle & TCS_BOTTOM))
1774 rcImage.top = drawRect->top + center_offset_h;
1775 /* if tab is TCS_VERTICAL and TCS_BOTTOM, the text is drawn from the */
1776 /* right side of the tab, but the image still uses the left as its x position */
1777 /* this keeps the image always drawn off of the same side of the tab */
1778 rcImage.left = drawRect->right - cx - center_offset_v;
1779 drawRect->top += cy + infoPtr->uHItemPadding;
1781 else if(lStyle & TCS_VERTICAL)
1783 rcImage.top = drawRect->bottom - cy - center_offset_h;
1784 rcImage.left = drawRect->left + center_offset_v;
1785 drawRect->bottom -= cy + infoPtr->uHItemPadding;
1787 else /* normal style, whether TCS_BOTTOM or not */
1789 rcImage.left = drawRect->left + center_offset_h;
1790 rcImage.top = drawRect->top + center_offset_v;
1791 drawRect->left += cx + infoPtr->uHItemPadding;
1794 TRACE("drawing image=%d, left=%ld, top=%ld\n",
1795 item->iImage, rcImage.left, rcImage.top-1);
1807 /* Now position text */
1808 if (lStyle & TCS_FIXEDWIDTH && lStyle & TCS_FORCELABELLEFT)
1809 center_offset_h = infoPtr->uHItemPadding;
1811 if(lStyle & TCS_VERTICAL)
1812 center_offset_h = ((drawRect->bottom - drawRect->top) - (rcText.right - rcText.left)) / 2;
1814 center_offset_h = ((drawRect->right - drawRect->left) - (rcText.right - rcText.left)) / 2;
1816 if(lStyle & TCS_VERTICAL)
1818 if(lStyle & TCS_BOTTOM)
1819 drawRect->top+=center_offset_h;
1821 drawRect->bottom-=center_offset_h;
1823 center_offset_v = ((drawRect->right - drawRect->left) - (rcText.bottom - rcText.top)) / 2;
1827 drawRect->left += center_offset_h;
1828 center_offset_v = ((drawRect->bottom - drawRect->top) - (rcText.bottom - rcText.top)) / 2;
1831 /* if an item is selected, the text is shifted up instead of down */
1832 if (iItem == infoPtr->iSelected)
1833 center_offset_v -= infoPtr->uVItemPadding / 2;
1835 center_offset_v += infoPtr->uVItemPadding / 2;
1837 if (center_offset_v < 0)
1838 center_offset_v = 0;
1840 if(lStyle & TCS_VERTICAL)
1841 drawRect->left += center_offset_v;
1843 drawRect->top += center_offset_v;
1846 if(lStyle & TCS_VERTICAL) /* if we are vertical rotate the text and each character */
1848 static const WCHAR ArialW[] = { 'A','r','i','a','l',0 };
1851 INT nEscapement = 900;
1852 INT nOrientation = 900;
1854 if(lStyle & TCS_BOTTOM)
1857 nOrientation = -900;
1860 /* to get a font with the escapement and orientation we are looking for, we need to */
1861 /* call CreateFontIndirectA, which requires us to set the values of the logfont we pass in */
1862 if (!GetObjectW((infoPtr->hFont) ?
1863 infoPtr->hFont : GetStockObject(SYSTEM_FONT),
1864 sizeof(LOGFONTW),&logfont))
1868 lstrcpyW(logfont.lfFaceName, ArialW);
1869 logfont.lfHeight = -MulDiv(iPointSize, GetDeviceCaps(hdc, LOGPIXELSY),
1871 logfont.lfWeight = FW_NORMAL;
1872 logfont.lfItalic = 0;
1873 logfont.lfUnderline = 0;
1874 logfont.lfStrikeOut = 0;
1877 logfont.lfEscapement = nEscapement;
1878 logfont.lfOrientation = nOrientation;
1879 hFont = CreateFontIndirectW(&logfont);
1880 SelectObject(hdc, hFont);
1885 (lStyle & TCS_BOTTOM) ? drawRect->right : drawRect->left,
1886 (!(lStyle & TCS_BOTTOM)) ? drawRect->bottom : drawRect->top,
1890 lstrlenW(item->pszText),
1894 DeleteObject(hFont);
1898 TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%ld,%ld)-(%ld,%ld), textlen=%ld\n",
1899 debugstr_w(item->pszText), center_offset_h, center_offset_v,
1900 drawRect->left, drawRect->top, drawRect->right, drawRect->bottom,
1901 (rcText.right-rcText.left));
1908 lstrlenW(item->pszText),
1910 DT_LEFT | DT_SINGLELINE
1915 *drawRect = rcTemp; /* restore drawRect */
1921 SelectObject(hdc, hOldFont);
1922 SetBkMode(hdc, oldBkMode);
1923 SelectObject(hdc, holdPen);
1924 DeleteObject( htextPen );
1927 /******************************************************************************
1930 * This method is used to draw a single tab into the tab control.
1932 static void TAB_DrawItem(
1937 LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1941 RECT r, fillRect, r1;
1944 COLORREF bkgnd, corner;
1948 * Get the rectangle for the item.
1950 isVisible = TAB_InternalGetItemRect(infoPtr,
1959 /* Clip UpDown control to not draw over it */
1960 if (infoPtr->needsScrolling)
1962 GetWindowRect(infoPtr->hwnd, &rC);
1963 GetWindowRect(infoPtr->hwndUpDown, &rUD);
1964 ExcludeClipRect(hdc, rUD.left - rC.left, rUD.top - rC.top, rUD.right - rC.left, rUD.bottom - rC.top);
1967 /* If you need to see what the control is doing,
1968 * then override these variables. They will change what
1969 * fill colors are used for filling the tabs, and the
1970 * corners when drawing the edge.
1972 bkgnd = comctl32_color.clrBtnFace;
1973 corner = comctl32_color.clrBtnFace;
1975 if (lStyle & TCS_BUTTONS)
1977 /* Get item rectangle */
1980 /* Separators between flat buttons */
1981 if (lStyle & TCS_FLATBUTTONS)
1984 r1.right += (FLAT_BTN_SPACINGX -2);
1985 DrawEdge(hdc, &r1, EDGE_ETCHED, BF_RIGHT);
1988 if (iItem == infoPtr->iSelected)
1990 DrawEdge(hdc, &r, EDGE_SUNKEN, BF_SOFT|BF_RECT);
1992 OffsetRect(&r, 1, 1);
1994 else /* ! selected */
1996 if (!(lStyle & TCS_FLATBUTTONS))
1997 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_RECT);
2000 else /* !TCS_BUTTONS */
2002 /* We draw a rectangle of different sizes depending on the selection
2004 if (iItem == infoPtr->iSelected) {
2006 GetClientRect (infoPtr->hwnd, &rect);
2007 clRight = rect.right;
2008 clBottom = rect.bottom;
2015 * Erase the background. (Delay it but setup rectangle.)
2016 * This is necessary when drawing the selected item since it is larger
2017 * than the others, it might overlap with stuff already drawn by the
2022 /* Draw themed tabs - but only if they are at the top.
2023 * Windows draws even side or bottom tabs themed, with wacky results.
2024 * However, since in Wine apps may get themed that did not opt in via
2025 * a manifest avoid theming when we know the result will be wrong */
2026 if ((theme = GetWindowTheme (infoPtr->hwnd))
2027 && ((lStyle & (TCS_VERTICAL | TCS_BOTTOM)) == 0))
2029 static const int partIds[8] = {
2032 TABP_TABITEMLEFTEDGE,
2033 TABP_TABITEMRIGHTEDGE,
2034 TABP_TABITEMBOTHEDGE,
2037 TABP_TOPTABITEMLEFTEDGE,
2038 TABP_TOPTABITEMRIGHTEDGE,
2039 TABP_TOPTABITEMBOTHEDGE,
2042 int stateId = TIS_NORMAL;
2044 /* selected and unselected tabs have different parts */
2045 if (iItem == infoPtr->iSelected)
2047 /* The part also differs on the position of a tab on a line.
2048 * "Visually" determining the position works well enough. */
2049 if(selectedRect.left == 0)
2051 if(selectedRect.right == clRight)
2054 if (iItem == infoPtr->iSelected)
2055 stateId = TIS_SELECTED;
2056 else if (iItem == infoPtr->iHotTracked)
2058 else if (iItem == infoPtr->uFocus)
2059 stateId = TIS_FOCUSED;
2061 /* Adjust rectangle for bottommost row */
2062 if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2065 DrawThemeBackground (theme, hdc, partIds[partIndex], stateId, &r, NULL);
2066 GetThemeBackgroundContentRect (theme, hdc, partIds[partIndex], stateId, &r, &r);
2068 else if(lStyle & TCS_VERTICAL)
2070 /* These are for adjusting the drawing of a Selected tab */
2071 /* The initial values are for the normal case of non-Selected */
2072 int ZZ = 1; /* Do not strech if selected */
2073 if (iItem == infoPtr->iSelected) {
2076 /* if leftmost draw the line longer */
2077 if(selectedRect.top == 0)
2078 fillRect.top += CONTROL_BORDER_SIZEY;
2079 /* if rightmost draw the line longer */
2080 if(selectedRect.bottom == clBottom)
2081 fillRect.bottom -= CONTROL_BORDER_SIZEY;
2084 if (lStyle & TCS_BOTTOM)
2086 /* Adjust both rectangles to match native */
2089 TRACE("<right> item=%d, fill=(%ld,%ld)-(%ld,%ld), edge=(%ld,%ld)-(%ld,%ld)\n",
2091 fillRect.left,fillRect.top,fillRect.right,fillRect.bottom,
2092 r.left,r.top,r.right,r.bottom);
2094 /* Clear interior */
2095 SetBkColor(hdc, bkgnd);
2096 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2098 /* Draw rectangular edge around tab */
2099 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_RIGHT|BF_TOP|BF_BOTTOM);
2101 /* Now erase the top corner and draw diagonal edge */
2102 SetBkColor(hdc, corner);
2103 r1.left = r.right - ROUND_CORNER_SIZE - 1;
2106 r1.bottom = r1.top + ROUND_CORNER_SIZE;
2107 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2109 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPLEFT);
2111 /* Now erase the bottom corner and draw diagonal edge */
2112 r1.left = r.right - ROUND_CORNER_SIZE - 1;
2113 r1.bottom = r.bottom;
2115 r1.top = r1.bottom - ROUND_CORNER_SIZE;
2116 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2118 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMLEFT);
2120 if ((iItem == infoPtr->iSelected) && (selectedRect.top == 0)) {
2124 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_TOP);
2130 TRACE("<left> item=%d, fill=(%ld,%ld)-(%ld,%ld), edge=(%ld,%ld)-(%ld,%ld)\n",
2132 fillRect.left,fillRect.top,fillRect.right,fillRect.bottom,
2133 r.left,r.top,r.right,r.bottom);
2135 /* Clear interior */
2136 SetBkColor(hdc, bkgnd);
2137 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2139 /* Draw rectangular edge around tab */
2140 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_TOP|BF_BOTTOM);
2142 /* Now erase the top corner and draw diagonal edge */
2143 SetBkColor(hdc, corner);
2146 r1.right = r1.left + ROUND_CORNER_SIZE + 1;
2147 r1.bottom = r1.top + ROUND_CORNER_SIZE;
2148 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2150 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPRIGHT);
2152 /* Now erase the bottom corner and draw diagonal edge */
2154 r1.bottom = r.bottom;
2155 r1.right = r1.left + ROUND_CORNER_SIZE + 1;
2156 r1.top = r1.bottom - ROUND_CORNER_SIZE;
2157 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2159 DrawEdge(hdc, &r1, EDGE_SUNKEN, BF_DIAGONAL_ENDTOPLEFT);
2162 else /* ! TCS_VERTICAL */
2164 /* These are for adjusting the drawing of a Selected tab */
2165 /* The initial values are for the normal case of non-Selected */
2166 if (iItem == infoPtr->iSelected) {
2167 /* if leftmost draw the line longer */
2168 if(selectedRect.left == 0)
2169 fillRect.left += CONTROL_BORDER_SIZEX;
2170 /* if rightmost draw the line longer */
2171 if(selectedRect.right == clRight)
2172 fillRect.right -= CONTROL_BORDER_SIZEX;
2175 if (lStyle & TCS_BOTTOM)
2177 /* Adjust both rectangles for topmost row */
2178 if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2184 TRACE("<bottom> item=%d, fill=(%ld,%ld)-(%ld,%ld), edge=(%ld,%ld)-(%ld,%ld)\n",
2186 fillRect.left,fillRect.top,fillRect.right,fillRect.bottom,
2187 r.left,r.top,r.right,r.bottom);
2189 /* Clear interior */
2190 SetBkColor(hdc, bkgnd);
2191 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2193 /* Draw rectangular edge around tab */
2194 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_BOTTOM|BF_RIGHT);
2196 /* Now erase the righthand corner and draw diagonal edge */
2197 SetBkColor(hdc, corner);
2198 r1.left = r.right - ROUND_CORNER_SIZE;
2199 r1.bottom = r.bottom;
2201 r1.top = r1.bottom - ROUND_CORNER_SIZE - 1;
2202 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2204 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMLEFT);
2206 /* Now erase the lefthand corner and draw diagonal edge */
2208 r1.bottom = r.bottom;
2209 r1.right = r1.left + ROUND_CORNER_SIZE;
2210 r1.top = r1.bottom - ROUND_CORNER_SIZE - 1;
2211 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2213 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPLEFT);
2215 if (iItem == infoPtr->iSelected)
2219 if (selectedRect.left == 0)
2224 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_LEFT);
2231 /* Adjust both rectangles for bottommost row */
2232 if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2234 fillRect.bottom += 3;
2238 TRACE("<top> item=%d, fill=(%ld,%ld)-(%ld,%ld), edge=(%ld,%ld)-(%ld,%ld)\n",
2240 fillRect.left,fillRect.top,fillRect.right,fillRect.bottom,
2241 r.left,r.top,r.right,r.bottom);
2243 /* Clear interior */
2244 SetBkColor(hdc, bkgnd);
2245 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2247 /* Draw rectangular edge around tab */
2248 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_TOP|BF_RIGHT);
2250 /* Now erase the righthand corner and draw diagonal edge */
2251 SetBkColor(hdc, corner);
2252 r1.left = r.right - ROUND_CORNER_SIZE;
2255 r1.bottom = r1.top + ROUND_CORNER_SIZE + 1;
2256 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2258 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMRIGHT);
2260 /* Now erase the lefthand corner and draw diagonal edge */
2263 r1.right = r1.left + ROUND_CORNER_SIZE;
2264 r1.bottom = r1.top + ROUND_CORNER_SIZE + 1;
2265 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2267 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPRIGHT);
2272 TAB_DumpItemInternal(infoPtr, iItem);
2274 /* This modifies r to be the text rectangle. */
2275 TAB_DrawItemInterior(infoPtr, hdc, iItem, &r);
2279 /******************************************************************************
2282 * This method is used to draw the raised border around the tab control
2285 static void TAB_DrawBorder (TAB_INFO *infoPtr, HDC hdc)
2288 DWORD lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
2289 HTHEME theme = GetWindowTheme (infoPtr->hwnd);
2291 GetClientRect (infoPtr->hwnd, &rect);
2294 * Adjust for the style
2297 if (infoPtr->uNumItem)
2299 if ((lStyle & TCS_BOTTOM) && !(lStyle & TCS_VERTICAL))
2300 rect.bottom -= infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2301 else if((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
2302 rect.right -= infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2303 else if(lStyle & TCS_VERTICAL)
2304 rect.left += infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2305 else /* not TCS_VERTICAL and not TCS_BOTTOM */
2306 rect.top += infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2309 TRACE("border=(%ld,%ld)-(%ld,%ld)\n",
2310 rect.left, rect.top, rect.right, rect.bottom);
2313 DrawThemeBackground (theme, hdc, TABP_PANE, 0, &rect, NULL);
2315 DrawEdge(hdc, &rect, EDGE_RAISED, BF_SOFT|BF_RECT);
2318 /******************************************************************************
2321 * This method repaints the tab control..
2323 static void TAB_Refresh (TAB_INFO *infoPtr, HDC hdc)
2328 if (!infoPtr->DoRedraw)
2331 hOldFont = SelectObject (hdc, infoPtr->hFont);
2333 if (GetWindowLongW(infoPtr->hwnd, GWL_STYLE) & TCS_BUTTONS)
2335 for (i = 0; i < infoPtr->uNumItem; i++)
2336 TAB_DrawItem (infoPtr, hdc, i);
2340 /* Draw all the non selected item first */
2341 for (i = 0; i < infoPtr->uNumItem; i++)
2343 if (i != infoPtr->iSelected)
2344 TAB_DrawItem (infoPtr, hdc, i);
2347 /* Now, draw the border, draw it before the selected item
2348 * since the selected item overwrites part of the border. */
2349 TAB_DrawBorder (infoPtr, hdc);
2351 /* Then, draw the selected item */
2352 TAB_DrawItem (infoPtr, hdc, infoPtr->iSelected);
2354 /* If we haven't set the current focus yet, set it now.
2355 * Only happens when we first paint the tab controls */
2356 if (infoPtr->uFocus == -1)
2357 TAB_SetCurFocus(infoPtr, infoPtr->iSelected);
2360 SelectObject (hdc, hOldFont);
2363 static inline DWORD TAB_GetRowCount (const TAB_INFO *infoPtr)
2365 return infoPtr->uNumRows;
2368 static inline LRESULT TAB_SetRedraw (TAB_INFO *infoPtr, BOOL doRedraw)
2370 infoPtr->DoRedraw = doRedraw;
2374 /******************************************************************************
2375 * TAB_EnsureSelectionVisible
2377 * This method will make sure that the current selection is completely
2378 * visible by scrolling until it is.
2380 static void TAB_EnsureSelectionVisible(
2383 INT iSelected = infoPtr->iSelected;
2384 LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
2385 INT iOrigLeftmostVisible = infoPtr->leftmostVisible;
2387 /* set the items row to the bottommost row or topmost row depending on
2389 if ((infoPtr->uNumRows > 1) && !(lStyle & TCS_BUTTONS))
2391 TAB_ITEM *selected = TAB_GetItem(infoPtr, iSelected);
2395 if(lStyle & TCS_VERTICAL)
2396 newselected = selected->rect.left;
2398 newselected = selected->rect.top;
2400 /* the target row is always (number of rows - 1)
2401 as row 0 is furthest from the clientRect */
2402 iTargetRow = infoPtr->uNumRows - 1;
2404 if (newselected != iTargetRow)
2407 if(lStyle & TCS_VERTICAL)
2409 for (i=0; i < infoPtr->uNumItem; i++)
2411 /* move everything in the row of the selected item to the iTargetRow */
2412 TAB_ITEM *item = TAB_GetItem(infoPtr, i);
2414 if (item->rect.left == newselected )
2415 item->rect.left = iTargetRow;
2418 if (item->rect.left > newselected)
2425 for (i=0; i < infoPtr->uNumItem; i++)
2427 TAB_ITEM *item = TAB_GetItem(infoPtr, i);
2429 if (item->rect.top == newselected )
2430 item->rect.top = iTargetRow;
2433 if (item->rect.top > newselected)
2438 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
2443 * Do the trivial cases first.
2445 if ( (!infoPtr->needsScrolling) ||
2446 (infoPtr->hwndUpDown==0) || (lStyle & TCS_VERTICAL))
2449 if (infoPtr->leftmostVisible >= iSelected)
2451 infoPtr->leftmostVisible = iSelected;
2455 TAB_ITEM *selected = TAB_GetItem(infoPtr, iSelected);
2460 /* Calculate the part of the client area that is visible */
2461 GetClientRect(infoPtr->hwnd, &r);
2464 GetClientRect(infoPtr->hwndUpDown, &r);
2467 if ((selected->rect.right -
2468 selected->rect.left) >= width )
2470 /* Special case: width of selected item is greater than visible
2473 infoPtr->leftmostVisible = iSelected;
2477 for (i = infoPtr->leftmostVisible; i < infoPtr->uNumItem; i++)
2479 if ((selected->rect.right - TAB_GetItem(infoPtr, i)->rect.left) < width)
2482 infoPtr->leftmostVisible = i;
2486 if (infoPtr->leftmostVisible != iOrigLeftmostVisible)
2487 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
2489 SendMessageW(infoPtr->hwndUpDown, UDM_SETPOS, 0,
2490 MAKELONG(infoPtr->leftmostVisible, 0));
2493 /******************************************************************************
2494 * TAB_InvalidateTabArea
2496 * This method will invalidate the portion of the control that contains the
2497 * tabs. It is called when the state of the control changes and needs
2500 static void TAB_InvalidateTabArea(TAB_INFO* infoPtr)
2502 RECT clientRect, rInvalidate, rAdjClient;
2503 DWORD lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
2504 INT lastRow = infoPtr->uNumRows - 1;
2507 if (lastRow < 0) return;
2509 GetClientRect(infoPtr->hwnd, &clientRect);
2510 rInvalidate = clientRect;
2511 rAdjClient = clientRect;
2513 TAB_AdjustRect(infoPtr, 0, &rAdjClient);
2515 TAB_InternalGetItemRect(infoPtr, infoPtr->uNumItem-1 , &rect, NULL);
2516 if ((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
2518 rInvalidate.left = rAdjClient.right;
2519 if (infoPtr->uNumRows == 1)
2520 rInvalidate.bottom = clientRect.top + rect.bottom + 2 * SELECTED_TAB_OFFSET;
2522 else if(lStyle & TCS_VERTICAL)
2524 rInvalidate.right = rAdjClient.left;
2525 if (infoPtr->uNumRows == 1)
2526 rInvalidate.bottom = clientRect.top + rect.bottom + 2 * SELECTED_TAB_OFFSET;
2528 else if (lStyle & TCS_BOTTOM)
2530 rInvalidate.top = rAdjClient.bottom;
2531 if (infoPtr->uNumRows == 1)
2532 rInvalidate.right = clientRect.left + rect.right + 2 * SELECTED_TAB_OFFSET;
2536 rInvalidate.bottom = rAdjClient.top;
2537 if (infoPtr->uNumRows == 1)
2538 rInvalidate.right = clientRect.left + rect.right + 2 * SELECTED_TAB_OFFSET;
2541 /* Punch out the updown control */
2542 if (infoPtr->needsScrolling && (rInvalidate.right > 0)) {
2544 GetClientRect(infoPtr->hwndUpDown, &r);
2545 if (rInvalidate.right > clientRect.right - r.left)
2546 rInvalidate.right = rInvalidate.right - (r.right - r.left);
2548 rInvalidate.right = clientRect.right - r.left;
2551 TRACE("invalidate (%ld,%ld)-(%ld,%ld)\n",
2552 rInvalidate.left, rInvalidate.top,
2553 rInvalidate.right, rInvalidate.bottom);
2555 InvalidateRect(infoPtr->hwnd, &rInvalidate, TRUE);
2558 static inline LRESULT TAB_Paint (TAB_INFO *infoPtr, HDC hdcPaint)
2567 hdc = BeginPaint (infoPtr->hwnd, &ps);
2568 TRACE("erase %d, rect=(%ld,%ld)-(%ld,%ld)\n",
2570 ps.rcPaint.left,ps.rcPaint.top,ps.rcPaint.right,ps.rcPaint.bottom);
2573 TAB_Refresh (infoPtr, hdc);
2576 EndPaint (infoPtr->hwnd, &ps);
2582 TAB_InsertItemT (TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam, BOOL bUnicode)
2589 GetClientRect (infoPtr->hwnd, &rect);
2590 TRACE("Rect: %p T %li, L %li, B %li, R %li\n", infoPtr->hwnd,
2591 rect.top, rect.left, rect.bottom, rect.right);
2593 pti = (TCITEMW *)lParam;
2594 iItem = (INT)wParam;
2596 if (iItem < 0) return -1;
2597 if (iItem > infoPtr->uNumItem)
2598 iItem = infoPtr->uNumItem;
2600 TAB_DumpItemExternalT(pti, iItem, bUnicode);
2603 if (infoPtr->uNumItem == 0) {
2604 infoPtr->items = Alloc (TAB_ITEM_SIZE(infoPtr));
2605 infoPtr->uNumItem++;
2606 infoPtr->iSelected = 0;
2609 LPBYTE oldItems = (LPBYTE)infoPtr->items;
2611 infoPtr->uNumItem++;
2612 infoPtr->items = Alloc (TAB_ITEM_SIZE(infoPtr) * infoPtr->uNumItem);
2614 /* pre insert copy */
2616 memcpy (infoPtr->items, oldItems,
2617 iItem * TAB_ITEM_SIZE(infoPtr));
2620 /* post insert copy */
2621 if (iItem < infoPtr->uNumItem - 1) {
2622 memcpy (TAB_GetItem(infoPtr, iItem + 1),
2623 oldItems + iItem * TAB_ITEM_SIZE(infoPtr),
2624 (infoPtr->uNumItem - iItem - 1) * TAB_ITEM_SIZE(infoPtr));
2628 if (iItem <= infoPtr->iSelected)
2629 infoPtr->iSelected++;
2634 item = TAB_GetItem(infoPtr, iItem);
2636 item->mask = pti->mask;
2637 item->pszText = NULL;
2639 if (pti->mask & TCIF_TEXT)
2642 Str_SetPtrW (&item->pszText, pti->pszText);
2644 Str_SetPtrAtoW (&item->pszText, (LPSTR)pti->pszText);
2647 if (pti->mask & TCIF_IMAGE)
2648 item->iImage = pti->iImage;
2652 if (pti->mask & TCIF_PARAM)
2653 memcpy(item->extra, &pti->lParam, infoPtr->cbInfo);
2655 memset(item->extra, 0, infoPtr->cbInfo);
2657 TAB_SetItemBounds(infoPtr);
2658 if (infoPtr->uNumItem > 1)
2659 TAB_InvalidateTabArea(infoPtr);
2661 InvalidateRect(infoPtr->hwnd, NULL, TRUE);
2663 TRACE("[%p]: added item %d %s\n",
2664 infoPtr->hwnd, iItem, debugstr_w(item->pszText));
2670 TAB_SetItemSize (TAB_INFO *infoPtr, LPARAM lParam)
2672 LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
2674 BOOL bNeedPaint = FALSE;
2676 lResult = MAKELONG(infoPtr->tabWidth, infoPtr->tabHeight);
2678 /* UNDOCUMENTED: If requested Width or Height is 0 this means that program wants to use auto size. */
2679 if (lStyle & TCS_FIXEDWIDTH && (infoPtr->tabWidth != (INT)LOWORD(lParam)))
2681 infoPtr->tabWidth = (INT)LOWORD(lParam);
2685 if (infoPtr->tabHeight != (INT)HIWORD(lParam))
2687 if ((infoPtr->fHeightSet = ((INT)HIWORD(lParam) != 0)))
2688 infoPtr->tabHeight = (INT)HIWORD(lParam);
2692 TRACE("was h=%d,w=%d, now h=%d,w=%d\n",
2693 HIWORD(lResult), LOWORD(lResult),
2694 infoPtr->tabHeight, infoPtr->tabWidth);
2698 TAB_SetItemBounds(infoPtr);
2699 RedrawWindow(infoPtr->hwnd, NULL, NULL, RDW_ERASE | RDW_INVALIDATE | RDW_UPDATENOW);
2705 static inline LRESULT TAB_SetMinTabWidth (TAB_INFO *infoPtr, INT cx)
2709 TRACE("(%p,%d)\n", infoPtr, cx);
2712 oldcx = infoPtr->tabMinWidth;
2713 infoPtr->tabMinWidth = cx;
2715 TAB_SetItemBounds(infoPtr);
2720 static inline LRESULT
2721 TAB_HighlightItem (TAB_INFO *infoPtr, INT iItem, BOOL fHighlight)
2725 TRACE("(%p,%d,%s)\n", infoPtr, iItem, fHighlight ? "true" : "false");
2727 if (!infoPtr || iItem < 0 || iItem >= infoPtr->uNumItem)
2730 lpState = &TAB_GetItem(infoPtr, iItem)->dwState;
2733 *lpState |= TCIS_HIGHLIGHTED;
2735 *lpState &= ~TCIS_HIGHLIGHTED;
2741 TAB_SetItemT (TAB_INFO *infoPtr, INT iItem, LPTCITEMW tabItem, BOOL bUnicode)
2745 TRACE("(%p,%d,%p,%s)\n", infoPtr, iItem, tabItem, bUnicode ? "true" : "false");
2747 if (iItem < 0 || iItem >= infoPtr->uNumItem)
2750 TAB_DumpItemExternalT(tabItem, iItem, bUnicode);
2752 wineItem = TAB_GetItem(infoPtr, iItem);
2754 if (tabItem->mask & TCIF_IMAGE)
2755 wineItem->iImage = tabItem->iImage;
2757 if (tabItem->mask & TCIF_PARAM)
2758 memcpy(wineItem->extra, &tabItem->lParam, infoPtr->cbInfo);
2760 if (tabItem->mask & TCIF_RTLREADING)
2761 FIXME("TCIF_RTLREADING\n");
2763 if (tabItem->mask & TCIF_STATE)
2764 wineItem->dwState = tabItem->dwState;
2766 if (tabItem->mask & TCIF_TEXT)
2768 if (wineItem->pszText)
2770 Free(wineItem->pszText);
2771 wineItem->pszText = NULL;
2774 Str_SetPtrW(&wineItem->pszText, tabItem->pszText);
2776 Str_SetPtrAtoW(&wineItem->pszText, (LPSTR)tabItem->pszText);
2779 /* Update and repaint tabs */
2780 TAB_SetItemBounds(infoPtr);
2781 TAB_InvalidateTabArea(infoPtr);
2786 static inline LRESULT TAB_GetItemCount (const TAB_INFO *infoPtr)
2788 return infoPtr->uNumItem;
2793 TAB_GetItemT (TAB_INFO *infoPtr, INT iItem, LPTCITEMW tabItem, BOOL bUnicode)
2797 TRACE("(%p,%d,%p,%s)\n", infoPtr, iItem, tabItem, bUnicode ? "true" : "false");
2799 if (iItem < 0 || iItem >= infoPtr->uNumItem)
2802 wineItem = TAB_GetItem(infoPtr, iItem);
2804 if (tabItem->mask & TCIF_IMAGE)
2805 tabItem->iImage = wineItem->iImage;
2807 if (tabItem->mask & TCIF_PARAM)
2808 memcpy(&tabItem->lParam, wineItem->extra, infoPtr->cbInfo);
2810 if (tabItem->mask & TCIF_RTLREADING)
2811 FIXME("TCIF_RTLREADING\n");
2813 if (tabItem->mask & TCIF_STATE)
2814 tabItem->dwState = wineItem->dwState;
2816 if (tabItem->mask & TCIF_TEXT)
2819 Str_GetPtrW (wineItem->pszText, tabItem->pszText, tabItem->cchTextMax);
2821 Str_GetPtrWtoA (wineItem->pszText, (LPSTR)tabItem->pszText, tabItem->cchTextMax);
2824 TAB_DumpItemExternalT(tabItem, iItem, bUnicode);
2830 static LRESULT TAB_DeleteItem (TAB_INFO *infoPtr, INT iItem)
2832 BOOL bResult = FALSE;
2834 TRACE("(%p, %d)\n", infoPtr, iItem);
2836 if ((iItem >= 0) && (iItem < infoPtr->uNumItem))
2838 TAB_ITEM *item = TAB_GetItem(infoPtr, iItem);
2839 LPBYTE oldItems = (LPBYTE)infoPtr->items;
2841 TAB_InvalidateTabArea(infoPtr);
2843 if ((item->mask & TCIF_TEXT) && item->pszText)
2844 Free(item->pszText);
2846 infoPtr->uNumItem--;
2848 if (!infoPtr->uNumItem)
2850 infoPtr->items = NULL;
2851 if (infoPtr->iHotTracked >= 0)
2853 KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
2854 infoPtr->iHotTracked = -1;
2859 infoPtr->items = Alloc(TAB_ITEM_SIZE(infoPtr) * infoPtr->uNumItem);
2862 memcpy(infoPtr->items, oldItems, iItem * TAB_ITEM_SIZE(infoPtr));
2864 if (iItem < infoPtr->uNumItem)
2865 memcpy(TAB_GetItem(infoPtr, iItem),
2866 oldItems + (iItem + 1) * TAB_ITEM_SIZE(infoPtr),
2867 (infoPtr->uNumItem - iItem) * TAB_ITEM_SIZE(infoPtr));
2869 if (iItem <= infoPtr->iHotTracked)
2871 /* When tabs move left/up, the hot track item may change */
2872 FIXME("Recalc hot track");
2877 /* Readjust the selected index */
2878 if ((iItem == infoPtr->iSelected) && (iItem > 0))
2879 infoPtr->iSelected--;
2881 if (iItem < infoPtr->iSelected)
2882 infoPtr->iSelected--;
2884 if (infoPtr->uNumItem == 0)
2885 infoPtr->iSelected = -1;
2887 /* Reposition and repaint tabs */
2888 TAB_SetItemBounds(infoPtr);
2896 static inline LRESULT TAB_DeleteAllItems (TAB_INFO *infoPtr)
2898 TRACE("(%p)\n", infoPtr);
2899 while (infoPtr->uNumItem)
2900 TAB_DeleteItem (infoPtr, 0);
2905 static inline LRESULT TAB_GetFont (const TAB_INFO *infoPtr)
2907 TRACE("(%p) returning %p\n", infoPtr, infoPtr->hFont);
2908 return (LRESULT)infoPtr->hFont;
2911 static inline LRESULT TAB_SetFont (TAB_INFO *infoPtr, HFONT hNewFont)
2913 TRACE("(%p,%p)\n", infoPtr, hNewFont);
2915 infoPtr->hFont = hNewFont;
2917 TAB_SetItemBounds(infoPtr);
2919 TAB_InvalidateTabArea(infoPtr);
2925 static inline LRESULT TAB_GetImageList (const TAB_INFO *infoPtr)
2928 return (LRESULT)infoPtr->himl;
2931 static inline LRESULT TAB_SetImageList (TAB_INFO *infoPtr, HIMAGELIST himlNew)
2933 HIMAGELIST himlPrev = infoPtr->himl;
2935 infoPtr->himl = himlNew;
2936 return (LRESULT)himlPrev;
2939 static inline LRESULT TAB_GetUnicodeFormat (const TAB_INFO *infoPtr)
2941 return infoPtr->bUnicode;
2944 static inline LRESULT TAB_SetUnicodeFormat (TAB_INFO *infoPtr, BOOL bUnicode)
2946 BOOL bTemp = infoPtr->bUnicode;
2948 infoPtr->bUnicode = bUnicode;
2953 static inline LRESULT TAB_Size (TAB_INFO *infoPtr)
2955 /* I'm not really sure what the following code was meant to do.
2956 This is what it is doing:
2957 When WM_SIZE is sent with SIZE_RESTORED, the control
2958 gets positioned in the top left corner.
2962 UINT uPosFlags,cx,cy;
2966 parent = GetParent (hwnd);
2967 GetClientRect(parent, &parent_rect);
2970 if (GetWindowLongW(hwnd, GWL_STYLE) & CCS_NORESIZE)
2971 uPosFlags |= (SWP_NOSIZE | SWP_NOMOVE);
2973 SetWindowPos (hwnd, 0, parent_rect.left, parent_rect.top,
2974 cx, cy, uPosFlags | SWP_NOZORDER);
2976 FIXME("WM_SIZE flag %x %lx not handled\n", wParam, lParam);
2979 /* Recompute the size/position of the tabs. */
2980 TAB_SetItemBounds (infoPtr);
2982 /* Force a repaint of the control. */
2983 InvalidateRect(infoPtr->hwnd, NULL, TRUE);
2989 static LRESULT TAB_Create (HWND hwnd, WPARAM wParam, LPARAM lParam)
2992 TEXTMETRICW fontMetrics;
2997 infoPtr = (TAB_INFO *)Alloc (sizeof(TAB_INFO));
2999 SetWindowLongPtrW(hwnd, 0, (DWORD_PTR)infoPtr);
3001 infoPtr->hwnd = hwnd;
3002 infoPtr->hwndNotify = ((LPCREATESTRUCTW)lParam)->hwndParent;
3003 infoPtr->uNumItem = 0;
3004 infoPtr->uNumRows = 0;
3005 infoPtr->uHItemPadding = 6;
3006 infoPtr->uVItemPadding = 3;
3007 infoPtr->uHItemPadding_s = 6;
3008 infoPtr->uVItemPadding_s = 3;
3011 infoPtr->hcurArrow = LoadCursorW (0, (LPWSTR)IDC_ARROW);
3012 infoPtr->iSelected = -1;
3013 infoPtr->iHotTracked = -1;
3014 infoPtr->uFocus = -1;
3015 infoPtr->hwndToolTip = 0;
3016 infoPtr->DoRedraw = TRUE;
3017 infoPtr->needsScrolling = FALSE;
3018 infoPtr->hwndUpDown = 0;
3019 infoPtr->leftmostVisible = 0;
3020 infoPtr->fHeightSet = FALSE;
3021 infoPtr->bUnicode = IsWindowUnicode (hwnd);
3022 infoPtr->cbInfo = sizeof(LPARAM);
3024 TRACE("Created tab control, hwnd [%p]\n", hwnd);
3026 /* The tab control always has the WS_CLIPSIBLINGS style. Even
3027 if you don't specify it in CreateWindow. This is necessary in
3028 order for paint to work correctly. This follows windows behaviour. */
3029 dwStyle = GetWindowLongW(hwnd, GWL_STYLE);
3030 SetWindowLongW(hwnd, GWL_STYLE, dwStyle|WS_CLIPSIBLINGS);
3032 if (dwStyle & TCS_TOOLTIPS) {
3033 /* Create tooltip control */
3034 infoPtr->hwndToolTip =
3035 CreateWindowExW (0, TOOLTIPS_CLASSW, NULL, 0,
3036 CW_USEDEFAULT, CW_USEDEFAULT,
3037 CW_USEDEFAULT, CW_USEDEFAULT,
3040 /* Send NM_TOOLTIPSCREATED notification */
3041 if (infoPtr->hwndToolTip) {
3042 NMTOOLTIPSCREATED nmttc;
3044 nmttc.hdr.hwndFrom = hwnd;
3045 nmttc.hdr.idFrom = GetWindowLongPtrW(hwnd, GWLP_ID);
3046 nmttc.hdr.code = NM_TOOLTIPSCREATED;
3047 nmttc.hwndToolTips = infoPtr->hwndToolTip;
3049 SendMessageW (infoPtr->hwndNotify, WM_NOTIFY,
3050 (WPARAM)GetWindowLongPtrW(hwnd, GWLP_ID), (LPARAM)&nmttc);
3054 OpenThemeData (infoPtr->hwnd, themeClass);
3057 * We need to get text information so we need a DC and we need to select
3061 hOldFont = SelectObject (hdc, GetStockObject (SYSTEM_FONT));
3063 /* Use the system font to determine the initial height of a tab. */
3064 GetTextMetricsW(hdc, &fontMetrics);
3067 * Make sure there is enough space for the letters + growing the
3068 * selected item + extra space for the selected item.
3070 infoPtr->tabHeight = fontMetrics.tmHeight + SELECTED_TAB_OFFSET +
3071 ((dwStyle & TCS_BUTTONS) ? 2 : 1) *
3072 infoPtr->uVItemPadding;
3074 /* Initialize the width of a tab. */
3075 if (dwStyle & TCS_FIXEDWIDTH)
3076 infoPtr->tabWidth = DEFAULT_TAB_WIDTH_FIXED;
3078 infoPtr->tabMinWidth = -1;
3080 TRACE("tabH=%d, tabW=%d\n", infoPtr->tabHeight, infoPtr->tabWidth);
3082 SelectObject (hdc, hOldFont);
3083 ReleaseDC(hwnd, hdc);
3089 TAB_Destroy (TAB_INFO *infoPtr)
3096 SetWindowLongPtrW(infoPtr->hwnd, 0, 0);
3098 if (infoPtr->items) {
3099 for (iItem = 0; iItem < infoPtr->uNumItem; iItem++) {
3100 if (TAB_GetItem(infoPtr, iItem)->pszText)
3101 Free (TAB_GetItem(infoPtr, iItem)->pszText);
3103 Free (infoPtr->items);
3106 if (infoPtr->hwndToolTip)
3107 DestroyWindow (infoPtr->hwndToolTip);
3109 if (infoPtr->hwndUpDown)
3110 DestroyWindow(infoPtr->hwndUpDown);
3112 if (infoPtr->iHotTracked >= 0)
3113 KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
3115 CloseThemeData (GetWindowTheme (infoPtr->hwnd));
3121 /* update theme after a WM_THEMECHANGED message */
3122 static LRESULT theme_changed (TAB_INFO* infoPtr)
3124 HTHEME theme = GetWindowTheme (infoPtr->hwnd);
3125 CloseThemeData (theme);
3126 OpenThemeData (infoPtr->hwnd, themeClass);
3130 static LRESULT TAB_NCCalcSize(HWND hwnd, WPARAM wParam, LPARAM lParam)
3134 return WVR_ALIGNTOP;
3137 static inline LRESULT
3138 TAB_SetItemExtra (TAB_INFO *infoPtr, INT cbInfo)
3140 if (!infoPtr || cbInfo <= 0)
3143 if (infoPtr->uNumItem)
3145 /* FIXME: MSDN says this is not allowed, but this hasn't been verified */
3149 infoPtr->cbInfo = cbInfo;
3153 static LRESULT WINAPI
3154 TAB_WindowProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
3156 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
3158 TRACE("hwnd=%p msg=%x wParam=%x lParam=%lx\n", hwnd, uMsg, wParam, lParam);
3159 if (!infoPtr && (uMsg != WM_CREATE))
3160 return DefWindowProcW (hwnd, uMsg, wParam, lParam);
3164 case TCM_GETIMAGELIST:
3165 return TAB_GetImageList (infoPtr);
3167 case TCM_SETIMAGELIST:
3168 return TAB_SetImageList (infoPtr, (HIMAGELIST)lParam);
3170 case TCM_GETITEMCOUNT:
3171 return TAB_GetItemCount (infoPtr);
3175 return TAB_GetItemT (infoPtr, (INT)wParam, (LPTCITEMW)lParam, uMsg == TCM_GETITEMW);
3179 return TAB_SetItemT (infoPtr, (INT)wParam, (LPTCITEMW)lParam, uMsg == TCM_SETITEMW);
3181 case TCM_DELETEITEM:
3182 return TAB_DeleteItem (infoPtr, (INT)wParam);
3184 case TCM_DELETEALLITEMS:
3185 return TAB_DeleteAllItems (infoPtr);
3187 case TCM_GETITEMRECT:
3188 return TAB_GetItemRect (infoPtr, wParam, lParam);
3191 return TAB_GetCurSel (infoPtr);
3194 return TAB_HitTest (infoPtr, (LPTCHITTESTINFO)lParam);
3197 return TAB_SetCurSel (infoPtr, (INT)wParam);
3199 case TCM_INSERTITEMA:
3200 case TCM_INSERTITEMW:
3201 return TAB_InsertItemT (infoPtr, wParam, lParam, uMsg == TCM_INSERTITEMW);
3203 case TCM_SETITEMEXTRA:
3204 return TAB_SetItemExtra (infoPtr, (int)wParam);
3206 case TCM_ADJUSTRECT:
3207 return TAB_AdjustRect (infoPtr, (BOOL)wParam, (LPRECT)lParam);
3209 case TCM_SETITEMSIZE:
3210 return TAB_SetItemSize (infoPtr, lParam);
3212 case TCM_REMOVEIMAGE:
3213 FIXME("Unimplemented msg TCM_REMOVEIMAGE\n");
3216 case TCM_SETPADDING:
3217 return TAB_SetPadding (infoPtr, lParam);
3219 case TCM_GETROWCOUNT:
3220 return TAB_GetRowCount(infoPtr);
3222 case TCM_GETUNICODEFORMAT:
3223 return TAB_GetUnicodeFormat (infoPtr);
3225 case TCM_SETUNICODEFORMAT:
3226 return TAB_SetUnicodeFormat (infoPtr, (BOOL)wParam);
3228 case TCM_HIGHLIGHTITEM:
3229 return TAB_HighlightItem (infoPtr, (INT)wParam, (BOOL)LOWORD(lParam));
3231 case TCM_GETTOOLTIPS:
3232 return TAB_GetToolTips (infoPtr);
3234 case TCM_SETTOOLTIPS:
3235 return TAB_SetToolTips (infoPtr, (HWND)wParam);
3237 case TCM_GETCURFOCUS:
3238 return TAB_GetCurFocus (infoPtr);
3240 case TCM_SETCURFOCUS:
3241 return TAB_SetCurFocus (infoPtr, (INT)wParam);
3243 case TCM_SETMINTABWIDTH:
3244 return TAB_SetMinTabWidth(infoPtr, (INT)lParam);
3246 case TCM_DESELECTALL:
3247 FIXME("Unimplemented msg TCM_DESELECTALL\n");
3250 case TCM_GETEXTENDEDSTYLE:
3251 FIXME("Unimplemented msg TCM_GETEXTENDEDSTYLE\n");
3254 case TCM_SETEXTENDEDSTYLE:
3255 FIXME("Unimplemented msg TCM_SETEXTENDEDSTYLE\n");
3259 return TAB_GetFont (infoPtr);
3262 return TAB_SetFont (infoPtr, (HFONT)wParam);
3265 return TAB_Create (hwnd, wParam, lParam);
3268 return TAB_Destroy (infoPtr);
3271 return DLGC_WANTARROWS | DLGC_WANTCHARS;
3273 case WM_LBUTTONDOWN:
3274 return TAB_LButtonDown (infoPtr, wParam, lParam);
3277 return TAB_LButtonUp (infoPtr);
3280 return SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, wParam, lParam);
3282 case WM_RBUTTONDOWN:
3283 return TAB_RButtonDown (infoPtr);
3286 return TAB_MouseMove (infoPtr, wParam, lParam);
3288 case WM_PRINTCLIENT:
3290 return TAB_Paint (infoPtr, (HDC)wParam);
3293 return TAB_Size (infoPtr);
3296 return TAB_SetRedraw (infoPtr, (BOOL)wParam);
3299 return TAB_OnHScroll(infoPtr, (int)LOWORD(wParam), (int)HIWORD(wParam), (HWND)lParam);
3301 case WM_STYLECHANGED:
3302 TAB_SetItemBounds (infoPtr);
3303 InvalidateRect(hwnd, NULL, TRUE);
3306 case WM_SYSCOLORCHANGE:
3307 COMCTL32_RefreshSysColors();
3310 case WM_THEMECHANGED:
3311 return theme_changed (infoPtr);
3315 TAB_FocusChanging(infoPtr);
3316 break; /* Don't disturb normal focus behavior */
3319 return TAB_KeyUp(infoPtr, wParam);
3321 return TAB_NCHitTest(infoPtr, lParam);
3324 return TAB_NCCalcSize(hwnd, wParam, lParam);
3327 if (uMsg >= WM_USER && uMsg < WM_APP)
3328 WARN("unknown msg %04x wp=%08x lp=%08lx\n",
3329 uMsg, wParam, lParam);
3332 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
3341 ZeroMemory (&wndClass, sizeof(WNDCLASSW));
3342 wndClass.style = CS_GLOBALCLASS | CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
3343 wndClass.lpfnWndProc = TAB_WindowProc;
3344 wndClass.cbClsExtra = 0;
3345 wndClass.cbWndExtra = sizeof(TAB_INFO *);
3346 wndClass.hCursor = LoadCursorW (0, (LPWSTR)IDC_ARROW);
3347 wndClass.hbrBackground = (HBRUSH)(COLOR_BTNFACE+1);
3348 wndClass.lpszClassName = WC_TABCONTROLW;
3350 RegisterClassW (&wndClass);
3355 TAB_Unregister (void)
3357 UnregisterClassW (WC_TABCONTROLW, NULL);