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 infoPtr->iSelected=iItem;
265 TAB_EnsureSelectionVisible(infoPtr);
266 TAB_InvalidateTabArea(infoPtr);
271 static LRESULT TAB_SetCurFocus (TAB_INFO *infoPtr, INT iItem)
273 if (iItem < 0 || iItem >= infoPtr->uNumItem) return 0;
275 if (GetWindowLongW(infoPtr->hwnd, GWL_STYLE) & TCS_BUTTONS) {
276 FIXME("Should set input focus\n");
278 int oldFocus = infoPtr->uFocus;
279 if (infoPtr->iSelected != iItem || oldFocus == -1 ) {
280 infoPtr->uFocus = iItem;
281 if (oldFocus != -1) {
282 if (!TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGING)) {
283 infoPtr->iSelected = iItem;
284 TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGE);
287 infoPtr->iSelected = iItem;
288 TAB_EnsureSelectionVisible(infoPtr);
289 TAB_InvalidateTabArea(infoPtr);
296 static inline LRESULT
297 TAB_SetToolTips (TAB_INFO *infoPtr, HWND hwndToolTip)
300 infoPtr->hwndToolTip = hwndToolTip;
304 static inline LRESULT
305 TAB_SetPadding (TAB_INFO *infoPtr, LPARAM lParam)
309 infoPtr->uHItemPadding_s=LOWORD(lParam);
310 infoPtr->uVItemPadding_s=HIWORD(lParam);
315 /******************************************************************************
316 * TAB_InternalGetItemRect
318 * This method will calculate the rectangle representing a given tab item in
319 * client coordinates. This method takes scrolling into account.
321 * This method returns TRUE if the item is visible in the window and FALSE
322 * if it is completely outside the client area.
324 static BOOL TAB_InternalGetItemRect(
325 const TAB_INFO* infoPtr,
330 RECT tmpItemRect,clientRect;
331 LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
333 /* Perform a sanity check and a trivial visibility check. */
334 if ( (infoPtr->uNumItem <= 0) ||
335 (itemIndex >= infoPtr->uNumItem) ||
336 (!((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)) && (itemIndex < infoPtr->leftmostVisible)) )
338 TRACE("Not Visible\n");
339 /* need to initialize these to empty rects */
342 memset(itemRect,0,sizeof(RECT));
343 itemRect->bottom = infoPtr->tabHeight;
346 memset(selectedRect,0,sizeof(RECT));
351 * Avoid special cases in this procedure by assigning the "out"
352 * parameters if the caller didn't supply them
354 if (itemRect == NULL)
355 itemRect = &tmpItemRect;
357 /* Retrieve the unmodified item rect. */
358 *itemRect = TAB_GetItem(infoPtr,itemIndex)->rect;
360 /* calculate the times bottom and top based on the row */
361 GetClientRect(infoPtr->hwnd, &clientRect);
363 if ((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
365 itemRect->right = clientRect.right - SELECTED_TAB_OFFSET - itemRect->left * infoPtr->tabHeight -
366 ((lStyle & TCS_BUTTONS) ? itemRect->left * BUTTON_SPACINGX : 0);
367 itemRect->left = itemRect->right - infoPtr->tabHeight;
369 else if (lStyle & TCS_VERTICAL)
371 itemRect->left = clientRect.left + SELECTED_TAB_OFFSET + itemRect->left * infoPtr->tabHeight +
372 ((lStyle & TCS_BUTTONS) ? itemRect->left * BUTTON_SPACINGX : 0);
373 itemRect->right = itemRect->left + infoPtr->tabHeight;
375 else if (lStyle & TCS_BOTTOM)
377 itemRect->bottom = clientRect.bottom - itemRect->top * infoPtr->tabHeight -
378 ((lStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : SELECTED_TAB_OFFSET);
379 itemRect->top = itemRect->bottom - infoPtr->tabHeight;
381 else /* not TCS_BOTTOM and not TCS_VERTICAL */
383 itemRect->top = clientRect.top + itemRect->top * infoPtr->tabHeight +
384 ((lStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : SELECTED_TAB_OFFSET);
385 itemRect->bottom = itemRect->top + infoPtr->tabHeight;
389 * "scroll" it to make sure the item at the very left of the
390 * tab control is the leftmost visible tab.
392 if(lStyle & TCS_VERTICAL)
396 -TAB_GetItem(infoPtr, infoPtr->leftmostVisible)->rect.top);
399 * Move the rectangle so the first item is slightly offset from
400 * the bottom of the tab control.
404 SELECTED_TAB_OFFSET);
409 -TAB_GetItem(infoPtr, infoPtr->leftmostVisible)->rect.left,
413 * Move the rectangle so the first item is slightly offset from
414 * the left of the tab control.
420 TRACE("item %d tab h=%d, rect=(%ld,%ld)-(%ld,%ld)\n",
421 itemIndex, infoPtr->tabHeight,
422 itemRect->left, itemRect->top, itemRect->right, itemRect->bottom);
424 /* Now, calculate the position of the item as if it were selected. */
425 if (selectedRect!=NULL)
427 CopyRect(selectedRect, itemRect);
429 /* The rectangle of a selected item is a bit wider. */
430 if(lStyle & TCS_VERTICAL)
431 InflateRect(selectedRect, 0, SELECTED_TAB_OFFSET);
433 InflateRect(selectedRect, SELECTED_TAB_OFFSET, 0);
435 /* If it also a bit higher. */
436 if ((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
438 selectedRect->left -= 2; /* the border is thicker on the right */
439 selectedRect->right += SELECTED_TAB_OFFSET;
441 else if (lStyle & TCS_VERTICAL)
443 selectedRect->left -= SELECTED_TAB_OFFSET;
444 selectedRect->right += 1;
446 else if (lStyle & TCS_BOTTOM)
448 selectedRect->bottom += SELECTED_TAB_OFFSET;
450 else /* not TCS_BOTTOM and not TCS_VERTICAL */
452 selectedRect->top -= SELECTED_TAB_OFFSET;
453 selectedRect->bottom -= 1;
457 /* Check for visibility */
458 if (lStyle & TCS_VERTICAL)
459 return (itemRect->top < clientRect.bottom) && (itemRect->bottom > clientRect.top);
461 return (itemRect->left < clientRect.right) && (itemRect->right > clientRect.left);
465 TAB_GetItemRect(TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
467 return TAB_InternalGetItemRect(infoPtr, (INT)wParam, (LPRECT)lParam, (LPRECT)NULL);
470 /******************************************************************************
473 * This method is called to handle keyboard input
475 static LRESULT TAB_KeyUp(TAB_INFO* infoPtr, WPARAM keyCode)
482 newItem = infoPtr->uFocus - 1;
485 newItem = infoPtr->uFocus + 1;
490 * If we changed to a valid item, change the selection
493 newItem < infoPtr->uNumItem &&
494 infoPtr->uFocus != newItem)
496 if (!TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGING))
498 infoPtr->iSelected = newItem;
499 infoPtr->uFocus = newItem;
500 TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGE);
502 TAB_EnsureSelectionVisible(infoPtr);
503 TAB_InvalidateTabArea(infoPtr);
510 /******************************************************************************
513 * This method is called whenever the focus goes in or out of this control
514 * it is used to update the visual state of the control.
516 static void TAB_FocusChanging(const TAB_INFO *infoPtr)
522 * Get the rectangle for the item.
524 isVisible = TAB_InternalGetItemRect(infoPtr,
530 * If the rectangle is not completely invisible, invalidate that
531 * portion of the window.
535 TRACE("invalidate (%ld,%ld)-(%ld,%ld)\n",
536 selectedRect.left,selectedRect.top,
537 selectedRect.right,selectedRect.bottom);
538 InvalidateRect(infoPtr->hwnd, &selectedRect, TRUE);
542 static INT TAB_InternalHitTest (
551 for (iCount = 0; iCount < infoPtr->uNumItem; iCount++)
553 TAB_InternalGetItemRect(infoPtr, iCount, &rect, NULL);
555 if (PtInRect(&rect, pt))
557 *flags = TCHT_ONITEM;
562 *flags = TCHT_NOWHERE;
566 static inline LRESULT
567 TAB_HitTest (TAB_INFO *infoPtr, LPTCHITTESTINFO lptest)
569 return TAB_InternalHitTest (infoPtr, lptest->pt, &lptest->flags);
572 /******************************************************************************
575 * Napster v2b5 has a tab control for its main navigation which has a client
576 * area that covers the whole area of the dialog pages.
577 * That's why it receives all msgs for that area and the underlying dialog ctrls
579 * So I decided that we should handle WM_NCHITTEST here and return
580 * HTTRANSPARENT if we don't hit the tab control buttons.
581 * FIXME: WM_NCHITTEST handling correct ? Fix it if you know that Windows
582 * doesn't do it that way. Maybe depends on tab control styles ?
584 static inline LRESULT
585 TAB_NCHitTest (TAB_INFO *infoPtr, LPARAM lParam)
590 pt.x = LOWORD(lParam);
591 pt.y = HIWORD(lParam);
592 ScreenToClient(infoPtr->hwnd, &pt);
594 if (TAB_InternalHitTest(infoPtr, pt, &dummyflag) == -1)
595 return HTTRANSPARENT;
601 TAB_LButtonDown (TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
606 if (infoPtr->hwndToolTip)
607 TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
608 WM_LBUTTONDOWN, wParam, lParam);
610 if (GetWindowLongW(infoPtr->hwnd, GWL_STYLE) & TCS_FOCUSONBUTTONDOWN ) {
611 SetFocus (infoPtr->hwnd);
614 if (infoPtr->hwndToolTip)
615 TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
616 WM_LBUTTONDOWN, wParam, lParam);
618 pt.x = (INT)LOWORD(lParam);
619 pt.y = (INT)HIWORD(lParam);
621 newItem = TAB_InternalHitTest (infoPtr, pt, &dummy);
623 TRACE("On Tab, item %d\n", newItem);
625 if (newItem != -1 && infoPtr->iSelected != newItem)
627 if (!TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGING))
629 infoPtr->iSelected = newItem;
630 infoPtr->uFocus = newItem;
631 TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGE);
633 TAB_EnsureSelectionVisible(infoPtr);
635 TAB_InvalidateTabArea(infoPtr);
641 static inline LRESULT
642 TAB_LButtonUp (const TAB_INFO *infoPtr)
644 TAB_SendSimpleNotify(infoPtr, NM_CLICK);
649 static inline LRESULT
650 TAB_RButtonDown (const TAB_INFO *infoPtr)
652 TAB_SendSimpleNotify(infoPtr, NM_RCLICK);
656 /******************************************************************************
657 * TAB_DrawLoneItemInterior
659 * This calls TAB_DrawItemInterior. However, TAB_DrawItemInterior is normally
660 * called by TAB_DrawItem which is normally called by TAB_Refresh which sets
661 * up the device context and font. This routine does the same setup but
662 * only calls TAB_DrawItemInterior for the single specified item.
665 TAB_DrawLoneItemInterior(TAB_INFO* infoPtr, int iItem)
667 HDC hdc = GetDC(infoPtr->hwnd);
670 /* Clip UpDown control to not draw over it */
671 if (infoPtr->needsScrolling)
673 GetWindowRect(infoPtr->hwnd, &rC);
674 GetWindowRect(infoPtr->hwndUpDown, &r);
675 ExcludeClipRect(hdc, r.left - rC.left, r.top - rC.top, r.right - rC.left, r.bottom - rC.top);
677 TAB_DrawItemInterior(infoPtr, hdc, iItem, NULL);
678 ReleaseDC(infoPtr->hwnd, hdc);
681 /* update a tab after hottracking - invalidate it or just redraw the interior,
682 * based on whether theming is used or not */
683 static inline void hottrack_refresh (TAB_INFO* infoPtr, int tabIndex)
685 if (tabIndex == -1) return;
687 if (GetWindowTheme (infoPtr->hwnd))
690 TAB_InternalGetItemRect(infoPtr, tabIndex, &rect, NULL);
691 InvalidateRect (infoPtr->hwnd, &rect, FALSE);
694 TAB_DrawLoneItemInterior(infoPtr, tabIndex);
697 /******************************************************************************
698 * TAB_HotTrackTimerProc
700 * When a mouse-move event causes a tab to be highlighted (hot-tracking), a
701 * timer is setup so we can check if the mouse is moved out of our window.
702 * (We don't get an event when the mouse leaves, the mouse-move events just
703 * stop being delivered to our window and just start being delivered to
704 * another window.) This function is called when the timer triggers so
705 * we can check if the mouse has left our window. If so, we un-highlight
706 * the hot-tracked tab.
709 TAB_HotTrackTimerProc
711 HWND hwnd, /* handle of window for timer messages */
712 UINT uMsg, /* WM_TIMER message */
713 UINT idEvent, /* timer identifier */
714 DWORD dwTime /* current system time */
717 TAB_INFO* infoPtr = TAB_GetInfoPtr(hwnd);
719 if (infoPtr != NULL && infoPtr->iHotTracked >= 0)
724 ** If we can't get the cursor position, or if the cursor is outside our
725 ** window, we un-highlight the hot-tracked tab. Note that the cursor is
726 ** "outside" even if it is within our bounding rect if another window
727 ** overlaps. Note also that the case where the cursor stayed within our
728 ** window but has moved off the hot-tracked tab will be handled by the
729 ** WM_MOUSEMOVE event.
731 if (!GetCursorPos(&pt) || WindowFromPoint(pt) != hwnd)
733 /* Redraw iHotTracked to look normal */
734 INT iRedraw = infoPtr->iHotTracked;
735 infoPtr->iHotTracked = -1;
736 hottrack_refresh (infoPtr, iRedraw);
738 /* Kill this timer */
739 KillTimer(hwnd, TAB_HOTTRACK_TIMER);
744 /******************************************************************************
747 * If a tab control has the TCS_HOTTRACK style, then the tab under the mouse
748 * should be highlighted. This function determines which tab in a tab control,
749 * if any, is under the mouse and records that information. The caller may
750 * supply output parameters to receive the item number of the tab item which
751 * was highlighted but isn't any longer and of the tab item which is now
752 * highlighted but wasn't previously. The caller can use this information to
753 * selectively redraw those tab items.
755 * If the caller has a mouse position, it can supply it through the pos
756 * parameter. For example, TAB_MouseMove does this. Otherwise, the caller
757 * supplies NULL and this function determines the current mouse position
765 int* out_redrawLeave,
772 if (out_redrawLeave != NULL)
773 *out_redrawLeave = -1;
774 if (out_redrawEnter != NULL)
775 *out_redrawEnter = -1;
777 if ((GetWindowLongW(infoPtr->hwnd, GWL_STYLE) & TCS_HOTTRACK)
778 || GetWindowTheme (infoPtr->hwnd))
786 ScreenToClient(infoPtr->hwnd, &pt);
794 item = TAB_InternalHitTest(infoPtr, pt, &flags);
797 if (item != infoPtr->iHotTracked)
799 if (infoPtr->iHotTracked >= 0)
801 /* Mark currently hot-tracked to be redrawn to look normal */
802 if (out_redrawLeave != NULL)
803 *out_redrawLeave = infoPtr->iHotTracked;
807 /* Kill timer which forces recheck of mouse pos */
808 KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
813 /* Start timer so we recheck mouse pos */
814 UINT timerID = SetTimer
818 TAB_HOTTRACK_TIMER_INTERVAL,
819 TAB_HotTrackTimerProc
823 return; /* Hot tracking not available */
826 infoPtr->iHotTracked = item;
830 /* Mark new hot-tracked to be redrawn to look highlighted */
831 if (out_redrawEnter != NULL)
832 *out_redrawEnter = item;
837 /******************************************************************************
840 * Handles the mouse-move event. Updates tooltips. Updates hot-tracking.
843 TAB_MouseMove (TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
848 if (infoPtr->hwndToolTip)
849 TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
850 WM_LBUTTONDOWN, wParam, lParam);
852 /* Determine which tab to highlight. Redraw tabs which change highlight
854 TAB_RecalcHotTrack(infoPtr, &lParam, &redrawLeave, &redrawEnter);
856 hottrack_refresh (infoPtr, redrawLeave);
857 hottrack_refresh (infoPtr, redrawEnter);
862 /******************************************************************************
865 * Calculates the tab control's display area given the window rectangle or
866 * the window rectangle given the requested display rectangle.
868 static LRESULT TAB_AdjustRect(
873 DWORD lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
874 LONG *iRightBottom, *iLeftTop;
876 TRACE ("hwnd=%p fLarger=%d (%ld,%ld)-(%ld,%ld)\n", infoPtr->hwnd, fLarger, prc->left, prc->top, prc->right, prc->bottom);
878 if(lStyle & TCS_VERTICAL)
880 iRightBottom = &(prc->right);
881 iLeftTop = &(prc->left);
885 iRightBottom = &(prc->bottom);
886 iLeftTop = &(prc->top);
889 if (fLarger) /* Go from display rectangle */
891 /* Add the height of the tabs. */
892 if (lStyle & TCS_BOTTOM)
893 *iRightBottom += infoPtr->tabHeight * infoPtr->uNumRows;
895 *iLeftTop -= infoPtr->tabHeight * infoPtr->uNumRows +
896 ((lStyle & TCS_BUTTONS)? 3 * (infoPtr->uNumRows - 1) : 0);
898 /* Inflate the rectangle for the padding */
899 InflateRect(prc, DISPLAY_AREA_PADDINGX, DISPLAY_AREA_PADDINGY);
901 /* Inflate for the border */
902 InflateRect(prc, CONTROL_BORDER_SIZEX, CONTROL_BORDER_SIZEY);
904 else /* Go from window rectangle. */
906 /* Deflate the rectangle for the border */
907 InflateRect(prc, -CONTROL_BORDER_SIZEX, -CONTROL_BORDER_SIZEY);
909 /* Deflate the rectangle for the padding */
910 InflateRect(prc, -DISPLAY_AREA_PADDINGX, -DISPLAY_AREA_PADDINGY);
912 /* Remove the height of the tabs. */
913 if (lStyle & TCS_BOTTOM)
914 *iRightBottom -= infoPtr->tabHeight * infoPtr->uNumRows;
916 *iLeftTop += (infoPtr->tabHeight) * infoPtr->uNumRows +
917 ((lStyle & TCS_BUTTONS)? 3 * (infoPtr->uNumRows - 1) : 0);
923 /******************************************************************************
926 * This method will handle the notification from the scroll control and
927 * perform the scrolling operation on the tab control.
929 static LRESULT TAB_OnHScroll(
935 if(nScrollCode == SB_THUMBPOSITION && nPos != infoPtr->leftmostVisible)
937 if(nPos < infoPtr->leftmostVisible)
938 infoPtr->leftmostVisible--;
940 infoPtr->leftmostVisible++;
942 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
943 TAB_InvalidateTabArea(infoPtr);
944 SendMessageW(infoPtr->hwndUpDown, UDM_SETPOS, 0,
945 MAKELONG(infoPtr->leftmostVisible, 0));
951 /******************************************************************************
954 * This method will check the current scrolling state and make sure the
955 * scrolling control is displayed (or not).
957 static void TAB_SetupScrolling(
960 const RECT* clientRect)
962 static const WCHAR msctls_updown32W[] = { 'm','s','c','t','l','s','_','u','p','d','o','w','n','3','2',0 };
963 static const WCHAR emptyW[] = { 0 };
965 DWORD lStyle = GetWindowLongW(hwnd, GWL_STYLE);
967 if (infoPtr->needsScrolling)
973 * Calculate the position of the scroll control.
975 if(lStyle & TCS_VERTICAL)
977 controlPos.right = clientRect->right;
978 controlPos.left = controlPos.right - 2 * GetSystemMetrics(SM_CXHSCROLL);
980 if (lStyle & TCS_BOTTOM)
982 controlPos.top = clientRect->bottom - infoPtr->tabHeight;
983 controlPos.bottom = controlPos.top + GetSystemMetrics(SM_CYHSCROLL);
987 controlPos.bottom = clientRect->top + infoPtr->tabHeight;
988 controlPos.top = controlPos.bottom - GetSystemMetrics(SM_CYHSCROLL);
993 controlPos.right = clientRect->right;
994 controlPos.left = controlPos.right - 2 * GetSystemMetrics(SM_CXHSCROLL);
996 if (lStyle & TCS_BOTTOM)
998 controlPos.top = clientRect->bottom - infoPtr->tabHeight;
999 controlPos.bottom = controlPos.top + GetSystemMetrics(SM_CYHSCROLL);
1003 controlPos.bottom = clientRect->top + infoPtr->tabHeight;
1004 controlPos.top = controlPos.bottom - GetSystemMetrics(SM_CYHSCROLL);
1009 * If we don't have a scroll control yet, we want to create one.
1010 * If we have one, we want to make sure it's positioned properly.
1012 if (infoPtr->hwndUpDown==0)
1014 infoPtr->hwndUpDown = CreateWindowW(msctls_updown32W, emptyW,
1015 WS_VISIBLE | WS_CHILD | UDS_HORZ,
1016 controlPos.left, controlPos.top,
1017 controlPos.right - controlPos.left,
1018 controlPos.bottom - controlPos.top,
1019 hwnd, NULL, NULL, NULL);
1023 SetWindowPos(infoPtr->hwndUpDown,
1025 controlPos.left, controlPos.top,
1026 controlPos.right - controlPos.left,
1027 controlPos.bottom - controlPos.top,
1028 SWP_SHOWWINDOW | SWP_NOZORDER);
1031 /* Now calculate upper limit of the updown control range.
1032 * We do this by calculating how many tabs will be offscreen when the
1033 * last tab is visible.
1035 if(infoPtr->uNumItem)
1037 vsize = clientRect->right - (controlPos.right - controlPos.left + 1);
1038 maxRange = infoPtr->uNumItem;
1039 tabwidth = TAB_GetItem(infoPtr, infoPtr->uNumItem - 1)->rect.right;
1041 for(; maxRange > 0; maxRange--)
1043 if(tabwidth - TAB_GetItem(infoPtr,maxRange - 1)->rect.left > vsize)
1047 if(maxRange == infoPtr->uNumItem)
1053 /* If we once had a scroll control... hide it */
1054 if (infoPtr->hwndUpDown!=0)
1055 ShowWindow(infoPtr->hwndUpDown, SW_HIDE);
1057 if (infoPtr->hwndUpDown)
1058 SendMessageW(infoPtr->hwndUpDown, UDM_SETRANGE32, 0, maxRange);
1061 /******************************************************************************
1064 * This method will calculate the position rectangles of all the items in the
1065 * control. The rectangle calculated starts at 0 for the first item in the
1066 * list and ignores scrolling and selection.
1067 * It also uses the current font to determine the height of the tab row and
1068 * it checks if all the tabs fit in the client area of the window. If they
1069 * don't, a scrolling control is added.
1071 static void TAB_SetItemBounds (TAB_INFO *infoPtr)
1073 LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1074 TEXTMETRICW fontMetrics;
1077 INT curItemRowCount;
1078 HFONT hFont, hOldFont;
1088 * We need to get text information so we need a DC and we need to select
1091 hdc = GetDC(infoPtr->hwnd);
1093 hFont = infoPtr->hFont ? infoPtr->hFont : GetStockObject (SYSTEM_FONT);
1094 hOldFont = SelectObject (hdc, hFont);
1097 * We will base the rectangle calculations on the client rectangle
1100 GetClientRect(infoPtr->hwnd, &clientRect);
1102 /* if TCS_VERTICAL then swap the height and width so this code places the
1103 tabs along the top of the rectangle and we can just rotate them after
1104 rather than duplicate all of the below code */
1105 if(lStyle & TCS_VERTICAL)
1107 iTemp = clientRect.bottom;
1108 clientRect.bottom = clientRect.right;
1109 clientRect.right = iTemp;
1112 /* Now use hPadding and vPadding */
1113 infoPtr->uHItemPadding = infoPtr->uHItemPadding_s;
1114 infoPtr->uVItemPadding = infoPtr->uVItemPadding_s;
1116 /* The leftmost item will be "0" aligned */
1118 curItemRowCount = infoPtr->uNumItem ? 1 : 0;
1120 if (!(infoPtr->fHeightSet))
1123 int icon_height = 0;
1125 /* Use the current font to determine the height of a tab. */
1126 GetTextMetricsW(hdc, &fontMetrics);
1128 /* Get the icon height */
1130 ImageList_GetIconSize(infoPtr->himl, 0, &icon_height);
1132 /* Take the highest between font or icon */
1133 if (fontMetrics.tmHeight > icon_height)
1134 item_height = fontMetrics.tmHeight + 2;
1136 item_height = icon_height;
1139 * Make sure there is enough space for the letters + icon + growing the
1140 * selected item + extra space for the selected item.
1142 infoPtr->tabHeight = item_height +
1143 ((lStyle & TCS_BUTTONS) ? 2 : 1) *
1144 infoPtr->uVItemPadding;
1146 TRACE("tabH=%d, tmH=%ld, iconh=%d\n",
1147 infoPtr->tabHeight, fontMetrics.tmHeight, icon_height);
1150 TRACE("client right=%ld\n", clientRect.right);
1152 /* Get the icon width */
1155 ImageList_GetIconSize(infoPtr->himl, &icon_width, 0);
1157 if (lStyle & TCS_FIXEDWIDTH)
1160 /* Add padding if icon is present */
1161 icon_width += infoPtr->uHItemPadding;
1164 for (curItem = 0; curItem < infoPtr->uNumItem; curItem++)
1166 TAB_ITEM *curr = TAB_GetItem(infoPtr, curItem);
1168 /* Set the leftmost position of the tab. */
1169 curr->rect.left = curItemLeftPos;
1171 if (lStyle & TCS_FIXEDWIDTH)
1173 curr->rect.right = curr->rect.left +
1174 max(infoPtr->tabWidth, icon_width);
1176 else if (!curr->pszText)
1178 /* If no text use minimum tab width including padding. */
1179 if (infoPtr->tabMinWidth < 0)
1180 curr->rect.right = curr->rect.left + GET_DEFAULT_MIN_TAB_WIDTH(infoPtr);
1183 curr->rect.right = curr->rect.left + infoPtr->tabMinWidth;
1185 /* Add extra padding if icon is present */
1186 if (infoPtr->himl && infoPtr->tabMinWidth > 0 && infoPtr->tabMinWidth < DEFAULT_MIN_TAB_WIDTH
1187 && infoPtr->uHItemPadding > 1)
1188 curr->rect.right += EXTRA_ICON_PADDING * (infoPtr->uHItemPadding-1);
1194 /* Calculate how wide the tab is depending on the text it contains */
1195 GetTextExtentPoint32W(hdc, curr->pszText,
1196 lstrlenW(curr->pszText), &size);
1198 tabwidth = size.cx + icon_width + 2 * infoPtr->uHItemPadding;
1200 if (infoPtr->tabMinWidth < 0)
1201 tabwidth = max(tabwidth, GET_DEFAULT_MIN_TAB_WIDTH(infoPtr));
1203 tabwidth = max(tabwidth, infoPtr->tabMinWidth);
1205 curr->rect.right = curr->rect.left + tabwidth;
1206 TRACE("for <%s>, l,r=%ld,%ld\n",
1207 debugstr_w(curr->pszText), curr->rect.left, curr->rect.right);
1211 * Check if this is a multiline tab control and if so
1212 * check to see if we should wrap the tabs
1214 * Wrap all these tabs. We will arrange them evenly later.
1218 if (((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)) &&
1220 (clientRect.right - CONTROL_BORDER_SIZEX - DISPLAY_AREA_PADDINGX)))
1222 curr->rect.right -= curr->rect.left;
1224 curr->rect.left = 0;
1226 TRACE("wrapping <%s>, l,r=%ld,%ld\n", debugstr_w(curr->pszText),
1227 curr->rect.left, curr->rect.right);
1230 curr->rect.bottom = 0;
1231 curr->rect.top = curItemRowCount - 1;
1233 TRACE("TextSize: %li\n", size.cx);
1234 TRACE("Rect: T %li, L %li, B %li, R %li\n", curr->rect.top,
1235 curr->rect.left, curr->rect.bottom, curr->rect.right);
1238 * The leftmost position of the next item is the rightmost position
1241 if (lStyle & TCS_BUTTONS)
1243 curItemLeftPos = curr->rect.right + BUTTON_SPACINGX;
1244 if (lStyle & TCS_FLATBUTTONS)
1245 curItemLeftPos += FLAT_BTN_SPACINGX;
1248 curItemLeftPos = curr->rect.right;
1251 if (!((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)))
1254 * Check if we need a scrolling control.
1256 infoPtr->needsScrolling = (curItemLeftPos + (2 * SELECTED_TAB_OFFSET) >
1259 /* Don't need scrolling, then update infoPtr->leftmostVisible */
1260 if(!infoPtr->needsScrolling)
1261 infoPtr->leftmostVisible = 0;
1266 * No scrolling in Multiline or Vertical styles.
1268 infoPtr->needsScrolling = FALSE;
1269 infoPtr->leftmostVisible = 0;
1271 TAB_SetupScrolling(infoPtr->hwnd, infoPtr, &clientRect);
1273 /* Set the number of rows */
1274 infoPtr->uNumRows = curItemRowCount;
1276 /* Arrange all tabs evenly if style says so */
1277 if (!(lStyle & TCS_RAGGEDRIGHT) && ((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)) && (infoPtr->uNumItem > 0))
1279 INT tabPerRow,remTab,iRow;
1284 * Ok windows tries to even out the rows. place the same
1285 * number of tabs in each row. So lets give that a shot
1288 tabPerRow = infoPtr->uNumItem / (infoPtr->uNumRows);
1289 remTab = infoPtr->uNumItem % (infoPtr->uNumRows);
1291 for (iItm=0,iRow=0,iCount=0,curItemLeftPos=0;
1292 iItm<infoPtr->uNumItem;
1295 /* normalize the current rect */
1296 TAB_ITEM *curr = TAB_GetItem(infoPtr, iItm);
1298 /* shift the item to the left side of the clientRect */
1299 curr->rect.right -= curr->rect.left;
1300 curr->rect.left = 0;
1302 TRACE("r=%ld, cl=%d, cl.r=%ld, iCount=%d, iRow=%d, uNumRows=%d, remTab=%d, tabPerRow=%d\n",
1303 curr->rect.right, curItemLeftPos, clientRect.right,
1304 iCount, iRow, infoPtr->uNumRows, remTab, tabPerRow);
1306 /* if we have reached the maximum number of tabs on this row */
1307 /* move to the next row, reset our current item left position and */
1308 /* the count of items on this row */
1310 if (lStyle & TCS_VERTICAL) {
1311 /* Vert: Add the remaining tabs in the *last* remainder rows */
1312 if (iCount >= ((iRow>=(INT)infoPtr->uNumRows - remTab)?tabPerRow + 1:tabPerRow)) {
1318 /* Horz: Add the remaining tabs in the *first* remainder rows */
1319 if (iCount >= ((iRow<remTab)?tabPerRow + 1:tabPerRow)) {
1326 /* shift the item to the right to place it as the next item in this row */
1327 curr->rect.left += curItemLeftPos;
1328 curr->rect.right += curItemLeftPos;
1329 curr->rect.top = iRow;
1330 if (lStyle & TCS_BUTTONS)
1332 curItemLeftPos = curr->rect.right + 1;
1333 if (lStyle & TCS_FLATBUTTONS)
1334 curItemLeftPos += FLAT_BTN_SPACINGX;
1337 curItemLeftPos = curr->rect.right;
1339 TRACE("arranging <%s>, l,r=%ld,%ld, row=%ld\n",
1340 debugstr_w(curr->pszText), curr->rect.left,
1341 curr->rect.right, curr->rect.top);
1348 INT widthDiff, iIndexStart=0, iIndexEnd=0;
1352 while(iIndexStart < infoPtr->uNumItem)
1354 TAB_ITEM *start = TAB_GetItem(infoPtr, iIndexStart);
1357 * find the index of the row
1359 /* find the first item on the next row */
1360 for (iIndexEnd=iIndexStart;
1361 (iIndexEnd < infoPtr->uNumItem) &&
1362 (TAB_GetItem(infoPtr, iIndexEnd)->rect.top ==
1365 /* intentionally blank */;
1368 * we need to justify these tabs so they fill the whole given
1372 /* find the amount of space remaining on this row */
1373 widthDiff = clientRect.right - (2 * SELECTED_TAB_OFFSET) -
1374 TAB_GetItem(infoPtr, iIndexEnd - 1)->rect.right;
1376 /* iCount is the number of tab items on this row */
1377 iCount = iIndexEnd - iIndexStart;
1381 remainder = widthDiff % iCount;
1382 widthDiff = widthDiff / iCount;
1383 /* add widthDiff/iCount, or extra space/items on row, to each item on this row */
1384 for (iIndex=iIndexStart, iCount=0; iIndex < iIndexEnd; iIndex++, iCount++)
1386 TAB_ITEM *item = TAB_GetItem(infoPtr, iIndex);
1388 item->rect.left += iCount * widthDiff;
1389 item->rect.right += (iCount + 1) * widthDiff;
1391 TRACE("adjusting 1 <%s>, l,r=%ld,%ld\n",
1392 debugstr_w(item->pszText),
1393 item->rect.left, item->rect.right);
1396 TAB_GetItem(infoPtr, iIndex - 1)->rect.right += remainder;
1398 else /* we have only one item on this row, make it take up the entire row */
1400 start->rect.left = clientRect.left;
1401 start->rect.right = clientRect.right - 4;
1403 TRACE("adjusting 2 <%s>, l,r=%ld,%ld\n",
1404 debugstr_w(start->pszText),
1405 start->rect.left, start->rect.right);
1410 iIndexStart = iIndexEnd;
1415 /* if TCS_VERTICAL rotate the tabs so they are along the side of the clientRect */
1416 if(lStyle & TCS_VERTICAL)
1419 for(iIndex = 0; iIndex < infoPtr->uNumItem; iIndex++)
1421 rcItem = &TAB_GetItem(infoPtr, iIndex)->rect;
1423 rcOriginal = *rcItem;
1425 /* this is rotating the items by 90 degrees clockwise around the center of the control */
1426 rcItem->top = (rcOriginal.left - clientRect.left);
1427 rcItem->bottom = rcItem->top + (rcOriginal.right - rcOriginal.left);
1428 rcItem->left = rcOriginal.top;
1429 rcItem->right = rcOriginal.bottom;
1433 TAB_EnsureSelectionVisible(infoPtr);
1434 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
1437 SelectObject (hdc, hOldFont);
1438 ReleaseDC (infoPtr->hwnd, hdc);
1443 TAB_EraseTabInterior
1451 LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1452 HBRUSH hbr = CreateSolidBrush (comctl32_color.clrBtnFace);
1453 BOOL deleteBrush = TRUE;
1454 RECT rTemp = *drawRect;
1456 InflateRect(&rTemp, -2, -2);
1457 if (lStyle & TCS_BUTTONS)
1459 if (iItem == infoPtr->iSelected)
1461 /* Background color */
1462 if (!(lStyle & TCS_OWNERDRAWFIXED))
1465 hbr = GetSysColorBrush(COLOR_SCROLLBAR);
1467 SetTextColor(hdc, comctl32_color.clr3dFace);
1468 SetBkColor(hdc, comctl32_color.clr3dHilight);
1470 /* if COLOR_WINDOW happens to be the same as COLOR_3DHILIGHT
1471 * we better use 0x55aa bitmap brush to make scrollbar's background
1472 * look different from the window background.
1474 if (comctl32_color.clr3dHilight == comctl32_color.clrWindow)
1475 hbr = COMCTL32_hPattern55AABrush;
1477 deleteBrush = FALSE;
1479 FillRect(hdc, &rTemp, hbr);
1481 else /* ! selected */
1483 if (lStyle & TCS_FLATBUTTONS)
1485 FillRect(hdc, drawRect, hbr);
1486 if (iItem == infoPtr->iHotTracked)
1487 DrawEdge(hdc, drawRect, EDGE_RAISED, BF_SOFT|BF_RECT);
1490 FillRect(hdc, &rTemp, hbr);
1494 else /* !TCS_BUTTONS */
1496 if (!GetWindowTheme (infoPtr->hwnd))
1497 FillRect(hdc, &rTemp, hbr);
1501 if (deleteBrush) DeleteObject(hbr);
1504 /******************************************************************************
1505 * TAB_DrawItemInterior
1507 * This method is used to draw the interior (text and icon) of a single tab
1508 * into the tab control.
1511 TAB_DrawItemInterior
1519 LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1528 /* if (drawRect == NULL) */
1535 * Get the rectangle for the item.
1537 isVisible = TAB_InternalGetItemRect(infoPtr, iItem, &itemRect, &selectedRect);
1542 * Make sure drawRect points to something valid; simplifies code.
1544 drawRect = &localRect;
1547 * This logic copied from the part of TAB_DrawItem which draws
1548 * the tab background. It's important to keep it in sync. I
1549 * would have liked to avoid code duplication, but couldn't figure
1550 * out how without making spaghetti of TAB_DrawItem.
1552 if (iItem == infoPtr->iSelected)
1553 *drawRect = selectedRect;
1555 *drawRect = itemRect;
1557 if (lStyle & TCS_BUTTONS)
1559 if (iItem == infoPtr->iSelected)
1561 drawRect->left += 4;
1563 drawRect->right -= 4;
1564 drawRect->bottom -= 1;
1568 drawRect->left += 2;
1570 drawRect->right -= 2;
1571 drawRect->bottom -= 2;
1576 if ((lStyle & TCS_VERTICAL) && (lStyle & TCS_BOTTOM))
1578 if (iItem != infoPtr->iSelected)
1580 drawRect->left += 2;
1582 drawRect->bottom -= 2;
1585 else if (lStyle & TCS_VERTICAL)
1587 if (iItem == infoPtr->iSelected)
1589 drawRect->right += 1;
1594 drawRect->right -= 2;
1595 drawRect->bottom -= 2;
1598 else if (lStyle & TCS_BOTTOM)
1600 if (iItem == infoPtr->iSelected)
1606 InflateRect(drawRect, -2, -2);
1607 drawRect->bottom += 2;
1612 if (iItem == infoPtr->iSelected)
1614 drawRect->bottom += 3;
1618 drawRect->bottom -= 2;
1619 InflateRect(drawRect, -2, 0);
1624 TRACE("drawRect=(%ld,%ld)-(%ld,%ld)\n",
1625 drawRect->left, drawRect->top, drawRect->right, drawRect->bottom);
1627 /* Clear interior */
1628 TAB_EraseTabInterior (infoPtr, hdc, iItem, drawRect);
1630 /* Draw the focus rectangle */
1631 if (!(lStyle & TCS_FOCUSNEVER) &&
1632 (GetFocus() == infoPtr->hwnd) &&
1633 (iItem == infoPtr->uFocus) )
1635 RECT rFocus = *drawRect;
1636 InflateRect(&rFocus, -3, -3);
1637 if (lStyle & TCS_BOTTOM && !(lStyle & TCS_VERTICAL))
1639 if (lStyle & TCS_BUTTONS)
1645 DrawFocusRect(hdc, &rFocus);
1651 htextPen = CreatePen( PS_SOLID, 1, GetSysColor(COLOR_BTNTEXT) );
1652 holdPen = SelectObject(hdc, htextPen);
1653 hOldFont = SelectObject(hdc, infoPtr->hFont);
1656 * Setup for text output
1658 oldBkMode = SetBkMode(hdc, TRANSPARENT);
1659 if (!GetWindowTheme (infoPtr->hwnd) || (lStyle & TCS_BUTTONS))
1660 SetTextColor(hdc, (((lStyle & TCS_HOTTRACK) && (iItem == infoPtr->iHotTracked)
1661 && !(lStyle & TCS_FLATBUTTONS))
1662 | (TAB_GetItem(infoPtr, iItem)->dwState & TCIS_HIGHLIGHTED)) ?
1663 comctl32_color.clrHighlight : comctl32_color.clrBtnText);
1666 * if owner draw, tell the owner to draw
1668 if ((lStyle & TCS_OWNERDRAWFIXED) && GetParent(infoPtr->hwnd))
1674 drawRect->right -= 1;
1675 if ( iItem == infoPtr->iSelected )
1677 drawRect->right -= 1;
1678 drawRect->left += 1;
1682 * get the control id
1684 id = (UINT)GetWindowLongPtrW( infoPtr->hwnd, GWLP_ID );
1687 * put together the DRAWITEMSTRUCT
1689 dis.CtlType = ODT_TAB;
1692 dis.itemAction = ODA_DRAWENTIRE;
1694 if ( iItem == infoPtr->iSelected )
1695 dis.itemState |= ODS_SELECTED;
1696 if (infoPtr->uFocus == iItem)
1697 dis.itemState |= ODS_FOCUS;
1698 dis.hwndItem = infoPtr->hwnd;
1700 CopyRect(&dis.rcItem,drawRect);
1701 dis.itemData = (ULONG_PTR)TAB_GetItem(infoPtr, iItem)->extra;
1704 * send the draw message
1706 SendMessageW( infoPtr->hwndNotify, WM_DRAWITEM, (WPARAM)id, (LPARAM)&dis );
1710 TAB_ITEM *item = TAB_GetItem(infoPtr, iItem);
1714 /* used to center the icon and text in the tab */
1716 INT center_offset_h, center_offset_v;
1718 /* set rcImage to drawRect, we will use top & left in our ImageList_Draw call */
1719 rcImage = *drawRect;
1723 rcText.left = rcText.top = rcText.right = rcText.bottom = 0;
1725 /* get the rectangle that the text fits in */
1728 DrawTextW(hdc, item->pszText, -1, &rcText, DT_CALCRECT);
1731 * If not owner draw, then do the drawing ourselves.
1735 if (infoPtr->himl && (item->mask & TCIF_IMAGE))
1740 ImageList_GetIconSize(infoPtr->himl, &cx, &cy);
1742 if(lStyle & TCS_VERTICAL)
1744 center_offset_h = ((drawRect->bottom - drawRect->top) - (cy + infoPtr->uHItemPadding + (rcText.right - rcText.left))) / 2;
1745 center_offset_v = ((drawRect->right - drawRect->left) - (cx + infoPtr->uVItemPadding)) / 2;
1749 center_offset_h = ((drawRect->right - drawRect->left) - (cx + infoPtr->uHItemPadding + (rcText.right - rcText.left))) / 2;
1750 center_offset_v = ((drawRect->bottom - drawRect->top) - (cy + infoPtr->uVItemPadding)) / 2;
1753 if (lStyle & TCS_FIXEDWIDTH && lStyle & (TCS_FORCELABELLEFT | TCS_FORCEICONLEFT))
1754 center_offset_h = infoPtr->uHItemPadding;
1756 if (center_offset_h < 2)
1757 center_offset_h = 2;
1759 if (center_offset_v < 0)
1760 center_offset_v = 0;
1762 TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%ld,%ld)-(%ld,%ld), textlen=%ld\n",
1763 debugstr_w(item->pszText), center_offset_h, center_offset_v,
1764 drawRect->left, drawRect->top, drawRect->right, drawRect->bottom,
1765 (rcText.right-rcText.left));
1767 if((lStyle & TCS_VERTICAL) && (lStyle & TCS_BOTTOM))
1769 rcImage.top = drawRect->top + center_offset_h;
1770 /* if tab is TCS_VERTICAL and TCS_BOTTOM, the text is drawn from the */
1771 /* right side of the tab, but the image still uses the left as its x position */
1772 /* this keeps the image always drawn off of the same side of the tab */
1773 rcImage.left = drawRect->right - cx - center_offset_v;
1774 drawRect->top += cy + infoPtr->uHItemPadding;
1776 else if(lStyle & TCS_VERTICAL)
1778 rcImage.top = drawRect->bottom - cy - center_offset_h;
1779 rcImage.left = drawRect->left + center_offset_v;
1780 drawRect->bottom -= cy + infoPtr->uHItemPadding;
1782 else /* normal style, whether TCS_BOTTOM or not */
1784 rcImage.left = drawRect->left + center_offset_h;
1785 rcImage.top = drawRect->top + center_offset_v;
1786 drawRect->left += cx + infoPtr->uHItemPadding;
1789 TRACE("drawing image=%d, left=%ld, top=%ld\n",
1790 item->iImage, rcImage.left, rcImage.top-1);
1802 /* Now position text */
1803 if (lStyle & TCS_FIXEDWIDTH && lStyle & TCS_FORCELABELLEFT)
1804 center_offset_h = infoPtr->uHItemPadding;
1806 if(lStyle & TCS_VERTICAL)
1807 center_offset_h = ((drawRect->bottom - drawRect->top) - (rcText.right - rcText.left)) / 2;
1809 center_offset_h = ((drawRect->right - drawRect->left) - (rcText.right - rcText.left)) / 2;
1811 if(lStyle & TCS_VERTICAL)
1813 if(lStyle & TCS_BOTTOM)
1814 drawRect->top+=center_offset_h;
1816 drawRect->bottom-=center_offset_h;
1818 center_offset_v = ((drawRect->right - drawRect->left) - (rcText.bottom - rcText.top)) / 2;
1822 drawRect->left += center_offset_h;
1823 center_offset_v = ((drawRect->bottom - drawRect->top) - (rcText.bottom - rcText.top)) / 2;
1826 /* if an item is selected, the text is shifted up instead of down */
1827 if (iItem == infoPtr->iSelected)
1828 center_offset_v -= infoPtr->uVItemPadding / 2;
1830 center_offset_v += infoPtr->uVItemPadding / 2;
1832 if (center_offset_v < 0)
1833 center_offset_v = 0;
1835 if(lStyle & TCS_VERTICAL)
1836 drawRect->left += center_offset_v;
1838 drawRect->top += center_offset_v;
1841 if(lStyle & TCS_VERTICAL) /* if we are vertical rotate the text and each character */
1843 static const WCHAR ArialW[] = { 'A','r','i','a','l',0 };
1846 INT nEscapement = 900;
1847 INT nOrientation = 900;
1849 if(lStyle & TCS_BOTTOM)
1852 nOrientation = -900;
1855 /* to get a font with the escapement and orientation we are looking for, we need to */
1856 /* call CreateFontIndirectA, which requires us to set the values of the logfont we pass in */
1857 if (!GetObjectW((infoPtr->hFont) ?
1858 infoPtr->hFont : GetStockObject(SYSTEM_FONT),
1859 sizeof(LOGFONTW),&logfont))
1863 lstrcpyW(logfont.lfFaceName, ArialW);
1864 logfont.lfHeight = -MulDiv(iPointSize, GetDeviceCaps(hdc, LOGPIXELSY),
1866 logfont.lfWeight = FW_NORMAL;
1867 logfont.lfItalic = 0;
1868 logfont.lfUnderline = 0;
1869 logfont.lfStrikeOut = 0;
1872 logfont.lfEscapement = nEscapement;
1873 logfont.lfOrientation = nOrientation;
1874 hFont = CreateFontIndirectW(&logfont);
1875 SelectObject(hdc, hFont);
1880 (lStyle & TCS_BOTTOM) ? drawRect->right : drawRect->left,
1881 (!(lStyle & TCS_BOTTOM)) ? drawRect->bottom : drawRect->top,
1885 lstrlenW(item->pszText),
1889 DeleteObject(hFont);
1893 TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%ld,%ld)-(%ld,%ld), textlen=%ld\n",
1894 debugstr_w(item->pszText), center_offset_h, center_offset_v,
1895 drawRect->left, drawRect->top, drawRect->right, drawRect->bottom,
1896 (rcText.right-rcText.left));
1903 lstrlenW(item->pszText),
1905 DT_LEFT | DT_SINGLELINE
1910 *drawRect = rcTemp; /* restore drawRect */
1916 SelectObject(hdc, hOldFont);
1917 SetBkMode(hdc, oldBkMode);
1918 SelectObject(hdc, holdPen);
1919 DeleteObject( htextPen );
1922 /******************************************************************************
1925 * This method is used to draw a single tab into the tab control.
1927 static void TAB_DrawItem(
1932 LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1936 RECT r, fillRect, r1;
1939 COLORREF bkgnd, corner;
1943 * Get the rectangle for the item.
1945 isVisible = TAB_InternalGetItemRect(infoPtr,
1954 /* Clip UpDown control to not draw over it */
1955 if (infoPtr->needsScrolling)
1957 GetWindowRect(infoPtr->hwnd, &rC);
1958 GetWindowRect(infoPtr->hwndUpDown, &rUD);
1959 ExcludeClipRect(hdc, rUD.left - rC.left, rUD.top - rC.top, rUD.right - rC.left, rUD.bottom - rC.top);
1962 /* If you need to see what the control is doing,
1963 * then override these variables. They will change what
1964 * fill colors are used for filling the tabs, and the
1965 * corners when drawing the edge.
1967 bkgnd = comctl32_color.clrBtnFace;
1968 corner = comctl32_color.clrBtnFace;
1970 if (lStyle & TCS_BUTTONS)
1972 /* Get item rectangle */
1975 /* Separators between flat buttons */
1976 if (lStyle & TCS_FLATBUTTONS)
1979 r1.right += (FLAT_BTN_SPACINGX -2);
1980 DrawEdge(hdc, &r1, EDGE_ETCHED, BF_RIGHT);
1983 if (iItem == infoPtr->iSelected)
1985 DrawEdge(hdc, &r, EDGE_SUNKEN, BF_SOFT|BF_RECT);
1987 OffsetRect(&r, 1, 1);
1989 else /* ! selected */
1991 if (!(lStyle & TCS_FLATBUTTONS))
1992 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_RECT);
1995 else /* !TCS_BUTTONS */
1997 /* We draw a rectangle of different sizes depending on the selection
1999 if (iItem == infoPtr->iSelected) {
2001 GetClientRect (infoPtr->hwnd, &rect);
2002 clRight = rect.right;
2003 clBottom = rect.bottom;
2010 * Erase the background. (Delay it but setup rectangle.)
2011 * This is necessary when drawing the selected item since it is larger
2012 * than the others, it might overlap with stuff already drawn by the
2017 /* Draw themed tabs - but only if they are at the top.
2018 * Windows draws even side or bottom tabs themed, with wacky results.
2019 * However, since in Wine apps may get themed that did not opt in via
2020 * a manifest avoid theming when we know the result will be wrong */
2021 if ((theme = GetWindowTheme (infoPtr->hwnd))
2022 && ((lStyle & (TCS_VERTICAL | TCS_BOTTOM)) == 0))
2024 const static int partIds[8] = {
2027 TABP_TABITEMLEFTEDGE,
2028 TABP_TABITEMRIGHTEDGE,
2029 TABP_TABITEMBOTHEDGE,
2032 TABP_TOPTABITEMLEFTEDGE,
2033 TABP_TOPTABITEMRIGHTEDGE,
2034 TABP_TOPTABITEMBOTHEDGE,
2037 int stateId = TIS_NORMAL;
2039 /* selected and unselected tabs have different parts */
2040 if (iItem == infoPtr->iSelected)
2042 /* The part also differs on the position of a tab on a line.
2043 * "Visually" determining the position works well enough. */
2044 if(selectedRect.left == 0)
2046 if(selectedRect.right == clRight)
2049 if (iItem == infoPtr->iSelected)
2050 stateId = TIS_SELECTED;
2051 else if (iItem == infoPtr->iHotTracked)
2053 else if (iItem == infoPtr->uFocus)
2054 stateId = TIS_FOCUSED;
2056 /* Adjust rectangle for bottommost row */
2057 if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2060 DrawThemeBackground (theme, hdc, partIds[partIndex], stateId, &r, NULL);
2061 GetThemeBackgroundContentRect (theme, hdc, partIds[partIndex], stateId, &r, &r);
2063 else if(lStyle & TCS_VERTICAL)
2065 /* These are for adjusting the drawing of a Selected tab */
2066 /* The initial values are for the normal case of non-Selected */
2067 int ZZ = 1; /* Do not strech if selected */
2068 if (iItem == infoPtr->iSelected) {
2071 /* if leftmost draw the line longer */
2072 if(selectedRect.top == 0)
2073 fillRect.top += CONTROL_BORDER_SIZEY;
2074 /* if rightmost draw the line longer */
2075 if(selectedRect.bottom == clBottom)
2076 fillRect.bottom -= CONTROL_BORDER_SIZEY;
2079 if (lStyle & TCS_BOTTOM)
2081 /* Adjust both rectangles to match native */
2084 TRACE("<right> item=%d, fill=(%ld,%ld)-(%ld,%ld), edge=(%ld,%ld)-(%ld,%ld)\n",
2086 fillRect.left,fillRect.top,fillRect.right,fillRect.bottom,
2087 r.left,r.top,r.right,r.bottom);
2089 /* Clear interior */
2090 SetBkColor(hdc, bkgnd);
2091 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2093 /* Draw rectangular edge around tab */
2094 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_RIGHT|BF_TOP|BF_BOTTOM);
2096 /* Now erase the top corner and draw diagonal edge */
2097 SetBkColor(hdc, corner);
2098 r1.left = r.right - ROUND_CORNER_SIZE - 1;
2101 r1.bottom = r1.top + ROUND_CORNER_SIZE;
2102 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2104 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPLEFT);
2106 /* Now erase the bottom corner and draw diagonal edge */
2107 r1.left = r.right - ROUND_CORNER_SIZE - 1;
2108 r1.bottom = r.bottom;
2110 r1.top = r1.bottom - ROUND_CORNER_SIZE;
2111 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2113 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMLEFT);
2115 if ((iItem == infoPtr->iSelected) && (selectedRect.top == 0)) {
2119 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_TOP);
2125 TRACE("<left> item=%d, fill=(%ld,%ld)-(%ld,%ld), edge=(%ld,%ld)-(%ld,%ld)\n",
2127 fillRect.left,fillRect.top,fillRect.right,fillRect.bottom,
2128 r.left,r.top,r.right,r.bottom);
2130 /* Clear interior */
2131 SetBkColor(hdc, bkgnd);
2132 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2134 /* Draw rectangular edge around tab */
2135 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_TOP|BF_BOTTOM);
2137 /* Now erase the top corner and draw diagonal edge */
2138 SetBkColor(hdc, corner);
2141 r1.right = r1.left + ROUND_CORNER_SIZE + 1;
2142 r1.bottom = r1.top + ROUND_CORNER_SIZE;
2143 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2145 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPRIGHT);
2147 /* Now erase the bottom corner and draw diagonal edge */
2149 r1.bottom = r.bottom;
2150 r1.right = r1.left + ROUND_CORNER_SIZE + 1;
2151 r1.top = r1.bottom - ROUND_CORNER_SIZE;
2152 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2154 DrawEdge(hdc, &r1, EDGE_SUNKEN, BF_DIAGONAL_ENDTOPLEFT);
2157 else /* ! TCS_VERTICAL */
2159 /* These are for adjusting the drawing of a Selected tab */
2160 /* The initial values are for the normal case of non-Selected */
2161 if (iItem == infoPtr->iSelected) {
2162 /* if leftmost draw the line longer */
2163 if(selectedRect.left == 0)
2164 fillRect.left += CONTROL_BORDER_SIZEX;
2165 /* if rightmost draw the line longer */
2166 if(selectedRect.right == clRight)
2167 fillRect.right -= CONTROL_BORDER_SIZEX;
2170 if (lStyle & TCS_BOTTOM)
2172 /* Adjust both rectangles for topmost row */
2173 if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2179 TRACE("<bottom> item=%d, fill=(%ld,%ld)-(%ld,%ld), edge=(%ld,%ld)-(%ld,%ld)\n",
2181 fillRect.left,fillRect.top,fillRect.right,fillRect.bottom,
2182 r.left,r.top,r.right,r.bottom);
2184 /* Clear interior */
2185 SetBkColor(hdc, bkgnd);
2186 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2188 /* Draw rectangular edge around tab */
2189 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_BOTTOM|BF_RIGHT);
2191 /* Now erase the righthand corner and draw diagonal edge */
2192 SetBkColor(hdc, corner);
2193 r1.left = r.right - ROUND_CORNER_SIZE;
2194 r1.bottom = r.bottom;
2196 r1.top = r1.bottom - ROUND_CORNER_SIZE - 1;
2197 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2199 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMLEFT);
2201 /* Now erase the lefthand corner and draw diagonal edge */
2203 r1.bottom = r.bottom;
2204 r1.right = r1.left + ROUND_CORNER_SIZE;
2205 r1.top = r1.bottom - ROUND_CORNER_SIZE - 1;
2206 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2208 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPLEFT);
2210 if (iItem == infoPtr->iSelected)
2214 if (selectedRect.left == 0)
2219 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_LEFT);
2226 /* Adjust both rectangles for bottommost row */
2227 if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2229 fillRect.bottom += 3;
2233 TRACE("<top> item=%d, fill=(%ld,%ld)-(%ld,%ld), edge=(%ld,%ld)-(%ld,%ld)\n",
2235 fillRect.left,fillRect.top,fillRect.right,fillRect.bottom,
2236 r.left,r.top,r.right,r.bottom);
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_TOP|BF_RIGHT);
2245 /* Now erase the righthand corner and draw diagonal edge */
2246 SetBkColor(hdc, corner);
2247 r1.left = r.right - ROUND_CORNER_SIZE;
2250 r1.bottom = r1.top + ROUND_CORNER_SIZE + 1;
2251 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2253 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMRIGHT);
2255 /* Now erase the lefthand corner and draw diagonal edge */
2258 r1.right = r1.left + ROUND_CORNER_SIZE;
2259 r1.bottom = r1.top + ROUND_CORNER_SIZE + 1;
2260 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2262 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPRIGHT);
2267 TAB_DumpItemInternal(infoPtr, iItem);
2269 /* This modifies r to be the text rectangle. */
2270 TAB_DrawItemInterior(infoPtr, hdc, iItem, &r);
2274 /******************************************************************************
2277 * This method is used to draw the raised border around the tab control
2280 static void TAB_DrawBorder (TAB_INFO *infoPtr, HDC hdc)
2283 DWORD lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
2284 HTHEME theme = GetWindowTheme (infoPtr->hwnd);
2286 GetClientRect (infoPtr->hwnd, &rect);
2289 * Adjust for the style
2292 if (infoPtr->uNumItem)
2294 if ((lStyle & TCS_BOTTOM) && !(lStyle & TCS_VERTICAL))
2295 rect.bottom -= infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2296 else if((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
2297 rect.right -= infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2298 else if(lStyle & TCS_VERTICAL)
2299 rect.left += infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2300 else /* not TCS_VERTICAL and not TCS_BOTTOM */
2301 rect.top += infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2304 TRACE("border=(%ld,%ld)-(%ld,%ld)\n",
2305 rect.left, rect.top, rect.right, rect.bottom);
2308 DrawThemeBackground (theme, hdc, TABP_PANE, 0, &rect, NULL);
2310 DrawEdge(hdc, &rect, EDGE_RAISED, BF_SOFT|BF_RECT);
2313 /******************************************************************************
2316 * This method repaints the tab control..
2318 static void TAB_Refresh (TAB_INFO *infoPtr, HDC hdc)
2323 if (!infoPtr->DoRedraw)
2326 hOldFont = SelectObject (hdc, infoPtr->hFont);
2328 if (GetWindowLongW(infoPtr->hwnd, GWL_STYLE) & TCS_BUTTONS)
2330 for (i = 0; i < infoPtr->uNumItem; i++)
2331 TAB_DrawItem (infoPtr, hdc, i);
2335 /* Draw all the non selected item first */
2336 for (i = 0; i < infoPtr->uNumItem; i++)
2338 if (i != infoPtr->iSelected)
2339 TAB_DrawItem (infoPtr, hdc, i);
2342 /* Now, draw the border, draw it before the selected item
2343 * since the selected item overwrites part of the border. */
2344 TAB_DrawBorder (infoPtr, hdc);
2346 /* Then, draw the selected item */
2347 TAB_DrawItem (infoPtr, hdc, infoPtr->iSelected);
2349 /* If we haven't set the current focus yet, set it now.
2350 * Only happens when we first paint the tab controls */
2351 if (infoPtr->uFocus == -1)
2352 TAB_SetCurFocus(infoPtr, infoPtr->iSelected);
2355 SelectObject (hdc, hOldFont);
2358 static inline DWORD TAB_GetRowCount (const TAB_INFO *infoPtr)
2360 return infoPtr->uNumRows;
2363 static inline LRESULT TAB_SetRedraw (TAB_INFO *infoPtr, BOOL doRedraw)
2365 infoPtr->DoRedraw = doRedraw;
2369 /******************************************************************************
2370 * TAB_EnsureSelectionVisible
2372 * This method will make sure that the current selection is completely
2373 * visible by scrolling until it is.
2375 static void TAB_EnsureSelectionVisible(
2378 INT iSelected = infoPtr->iSelected;
2379 LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
2380 INT iOrigLeftmostVisible = infoPtr->leftmostVisible;
2382 /* set the items row to the bottommost row or topmost row depending on
2384 if ((infoPtr->uNumRows > 1) && !(lStyle & TCS_BUTTONS))
2386 TAB_ITEM *selected = TAB_GetItem(infoPtr, iSelected);
2390 if(lStyle & TCS_VERTICAL)
2391 newselected = selected->rect.left;
2393 newselected = selected->rect.top;
2395 /* the target row is always (number of rows - 1)
2396 as row 0 is furthest from the clientRect */
2397 iTargetRow = infoPtr->uNumRows - 1;
2399 if (newselected != iTargetRow)
2402 if(lStyle & TCS_VERTICAL)
2404 for (i=0; i < infoPtr->uNumItem; i++)
2406 /* move everything in the row of the selected item to the iTargetRow */
2407 TAB_ITEM *item = TAB_GetItem(infoPtr, i);
2409 if (item->rect.left == newselected )
2410 item->rect.left = iTargetRow;
2413 if (item->rect.left > newselected)
2420 for (i=0; i < infoPtr->uNumItem; i++)
2422 TAB_ITEM *item = TAB_GetItem(infoPtr, i);
2424 if (item->rect.top == newselected )
2425 item->rect.top = iTargetRow;
2428 if (item->rect.top > newselected)
2433 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
2438 * Do the trivial cases first.
2440 if ( (!infoPtr->needsScrolling) ||
2441 (infoPtr->hwndUpDown==0) || (lStyle & TCS_VERTICAL))
2444 if (infoPtr->leftmostVisible >= iSelected)
2446 infoPtr->leftmostVisible = iSelected;
2450 TAB_ITEM *selected = TAB_GetItem(infoPtr, iSelected);
2455 /* Calculate the part of the client area that is visible */
2456 GetClientRect(infoPtr->hwnd, &r);
2459 GetClientRect(infoPtr->hwndUpDown, &r);
2462 if ((selected->rect.right -
2463 selected->rect.left) >= width )
2465 /* Special case: width of selected item is greater than visible
2468 infoPtr->leftmostVisible = iSelected;
2472 for (i = infoPtr->leftmostVisible; i < infoPtr->uNumItem; i++)
2474 if ((selected->rect.right - TAB_GetItem(infoPtr, i)->rect.left) < width)
2477 infoPtr->leftmostVisible = i;
2481 if (infoPtr->leftmostVisible != iOrigLeftmostVisible)
2482 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
2484 SendMessageW(infoPtr->hwndUpDown, UDM_SETPOS, 0,
2485 MAKELONG(infoPtr->leftmostVisible, 0));
2488 /******************************************************************************
2489 * TAB_InvalidateTabArea
2491 * This method will invalidate the portion of the control that contains the
2492 * tabs. It is called when the state of the control changes and needs
2495 static void TAB_InvalidateTabArea(TAB_INFO* infoPtr)
2497 RECT clientRect, rInvalidate, rAdjClient;
2498 DWORD lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
2499 INT lastRow = infoPtr->uNumRows - 1;
2502 if (lastRow < 0) return;
2504 GetClientRect(infoPtr->hwnd, &clientRect);
2505 rInvalidate = clientRect;
2506 rAdjClient = clientRect;
2508 TAB_AdjustRect(infoPtr, 0, &rAdjClient);
2510 TAB_InternalGetItemRect(infoPtr, infoPtr->uNumItem-1 , &rect, NULL);
2511 if ((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
2513 rInvalidate.left = rAdjClient.right;
2514 if (infoPtr->uNumRows == 1)
2515 rInvalidate.bottom = clientRect.top + rect.bottom + 2 * SELECTED_TAB_OFFSET;
2517 else if(lStyle & TCS_VERTICAL)
2519 rInvalidate.right = rAdjClient.left;
2520 if (infoPtr->uNumRows == 1)
2521 rInvalidate.bottom = clientRect.top + rect.bottom + 2 * SELECTED_TAB_OFFSET;
2523 else if (lStyle & TCS_BOTTOM)
2525 rInvalidate.top = rAdjClient.bottom;
2526 if (infoPtr->uNumRows == 1)
2527 rInvalidate.right = clientRect.left + rect.right + 2 * SELECTED_TAB_OFFSET;
2531 rInvalidate.bottom = rAdjClient.top;
2532 if (infoPtr->uNumRows == 1)
2533 rInvalidate.right = clientRect.left + rect.right + 2 * SELECTED_TAB_OFFSET;
2536 /* Punch out the updown control */
2537 if (infoPtr->needsScrolling && (rInvalidate.right > 0)) {
2539 GetClientRect(infoPtr->hwndUpDown, &r);
2540 if (rInvalidate.right > clientRect.right - r.left)
2541 rInvalidate.right = rInvalidate.right - (r.right - r.left);
2543 rInvalidate.right = clientRect.right - r.left;
2546 TRACE("invalidate (%ld,%ld)-(%ld,%ld)\n",
2547 rInvalidate.left, rInvalidate.top,
2548 rInvalidate.right, rInvalidate.bottom);
2550 InvalidateRect(infoPtr->hwnd, &rInvalidate, TRUE);
2553 static inline LRESULT TAB_Paint (TAB_INFO *infoPtr, HDC hdcPaint)
2562 hdc = BeginPaint (infoPtr->hwnd, &ps);
2563 TRACE("erase %d, rect=(%ld,%ld)-(%ld,%ld)\n",
2565 ps.rcPaint.left,ps.rcPaint.top,ps.rcPaint.right,ps.rcPaint.bottom);
2568 TAB_Refresh (infoPtr, hdc);
2571 EndPaint (infoPtr->hwnd, &ps);
2577 TAB_InsertItemT (TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam, BOOL bUnicode)
2584 GetClientRect (infoPtr->hwnd, &rect);
2585 TRACE("Rect: %p T %li, L %li, B %li, R %li\n", infoPtr->hwnd,
2586 rect.top, rect.left, rect.bottom, rect.right);
2588 pti = (TCITEMW *)lParam;
2589 iItem = (INT)wParam;
2591 if (iItem < 0) return -1;
2592 if (iItem > infoPtr->uNumItem)
2593 iItem = infoPtr->uNumItem;
2595 TAB_DumpItemExternalT(pti, iItem, bUnicode);
2598 if (infoPtr->uNumItem == 0) {
2599 infoPtr->items = Alloc (TAB_ITEM_SIZE(infoPtr));
2600 infoPtr->uNumItem++;
2601 infoPtr->iSelected = 0;
2604 LPBYTE oldItems = (LPBYTE)infoPtr->items;
2606 infoPtr->uNumItem++;
2607 infoPtr->items = Alloc (TAB_ITEM_SIZE(infoPtr) * infoPtr->uNumItem);
2609 /* pre insert copy */
2611 memcpy (infoPtr->items, oldItems,
2612 iItem * TAB_ITEM_SIZE(infoPtr));
2615 /* post insert copy */
2616 if (iItem < infoPtr->uNumItem - 1) {
2617 memcpy (TAB_GetItem(infoPtr, iItem + 1),
2618 oldItems + iItem * TAB_ITEM_SIZE(infoPtr),
2619 (infoPtr->uNumItem - iItem - 1) * TAB_ITEM_SIZE(infoPtr));
2623 if (iItem <= infoPtr->iSelected)
2624 infoPtr->iSelected++;
2629 item = TAB_GetItem(infoPtr, iItem);
2631 item->mask = pti->mask;
2632 item->pszText = NULL;
2634 if (pti->mask & TCIF_TEXT)
2637 Str_SetPtrW (&item->pszText, pti->pszText);
2639 Str_SetPtrAtoW (&item->pszText, (LPSTR)pti->pszText);
2642 if (pti->mask & TCIF_IMAGE)
2643 item->iImage = pti->iImage;
2647 if (pti->mask & TCIF_PARAM)
2648 memcpy(item->extra, &pti->lParam, infoPtr->cbInfo);
2650 memset(item->extra, 0, infoPtr->cbInfo);
2652 TAB_SetItemBounds(infoPtr);
2653 if (infoPtr->uNumItem > 1)
2654 TAB_InvalidateTabArea(infoPtr);
2656 InvalidateRect(infoPtr->hwnd, NULL, TRUE);
2658 TRACE("[%p]: added item %d %s\n",
2659 infoPtr->hwnd, iItem, debugstr_w(item->pszText));
2665 TAB_SetItemSize (TAB_INFO *infoPtr, LPARAM lParam)
2667 LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
2669 BOOL bNeedPaint = FALSE;
2671 lResult = MAKELONG(infoPtr->tabWidth, infoPtr->tabHeight);
2673 /* UNDOCUMENTED: If requested Width or Height is 0 this means that program wants to use auto size. */
2674 if (lStyle & TCS_FIXEDWIDTH && (infoPtr->tabWidth != (INT)LOWORD(lParam)))
2676 infoPtr->tabWidth = (INT)LOWORD(lParam);
2680 if (infoPtr->tabHeight != (INT)HIWORD(lParam))
2682 if ((infoPtr->fHeightSet = ((INT)HIWORD(lParam) != 0)))
2683 infoPtr->tabHeight = (INT)HIWORD(lParam);
2687 TRACE("was h=%d,w=%d, now h=%d,w=%d\n",
2688 HIWORD(lResult), LOWORD(lResult),
2689 infoPtr->tabHeight, infoPtr->tabWidth);
2693 TAB_SetItemBounds(infoPtr);
2694 RedrawWindow(infoPtr->hwnd, NULL, NULL, RDW_ERASE | RDW_INVALIDATE | RDW_UPDATENOW);
2700 static inline LRESULT TAB_SetMinTabWidth (TAB_INFO *infoPtr, INT cx)
2704 TRACE("(%p,%d)\n", infoPtr, cx);
2707 oldcx = infoPtr->tabMinWidth;
2708 infoPtr->tabMinWidth = cx;
2710 TAB_SetItemBounds(infoPtr);
2715 static inline LRESULT
2716 TAB_HighlightItem (TAB_INFO *infoPtr, INT iItem, BOOL fHighlight)
2720 TRACE("(%p,%d,%s)\n", infoPtr, iItem, fHighlight ? "true" : "false");
2722 if (!infoPtr || iItem < 0 || iItem >= infoPtr->uNumItem)
2725 lpState = &TAB_GetItem(infoPtr, iItem)->dwState;
2728 *lpState |= TCIS_HIGHLIGHTED;
2730 *lpState &= ~TCIS_HIGHLIGHTED;
2736 TAB_SetItemT (TAB_INFO *infoPtr, INT iItem, LPTCITEMW tabItem, BOOL bUnicode)
2740 TRACE("(%p,%d,%p,%s)\n", infoPtr, iItem, tabItem, bUnicode ? "true" : "false");
2742 if (iItem < 0 || iItem >= infoPtr->uNumItem)
2745 TAB_DumpItemExternalT(tabItem, iItem, bUnicode);
2747 wineItem = TAB_GetItem(infoPtr, iItem);
2749 if (tabItem->mask & TCIF_IMAGE)
2750 wineItem->iImage = tabItem->iImage;
2752 if (tabItem->mask & TCIF_PARAM)
2753 memcpy(wineItem->extra, &tabItem->lParam, infoPtr->cbInfo);
2755 if (tabItem->mask & TCIF_RTLREADING)
2756 FIXME("TCIF_RTLREADING\n");
2758 if (tabItem->mask & TCIF_STATE)
2759 wineItem->dwState = tabItem->dwState;
2761 if (tabItem->mask & TCIF_TEXT)
2763 if (wineItem->pszText)
2765 Free(wineItem->pszText);
2766 wineItem->pszText = NULL;
2769 Str_SetPtrW(&wineItem->pszText, tabItem->pszText);
2771 Str_SetPtrAtoW(&wineItem->pszText, (LPSTR)tabItem->pszText);
2774 /* Update and repaint tabs */
2775 TAB_SetItemBounds(infoPtr);
2776 TAB_InvalidateTabArea(infoPtr);
2781 static inline LRESULT TAB_GetItemCount (const TAB_INFO *infoPtr)
2783 return infoPtr->uNumItem;
2788 TAB_GetItemT (TAB_INFO *infoPtr, INT iItem, LPTCITEMW tabItem, BOOL bUnicode)
2792 TRACE("(%p,%d,%p,%s)\n", infoPtr, iItem, tabItem, bUnicode ? "true" : "false");
2794 if (iItem < 0 || iItem >= infoPtr->uNumItem)
2797 wineItem = TAB_GetItem(infoPtr, iItem);
2799 if (tabItem->mask & TCIF_IMAGE)
2800 tabItem->iImage = wineItem->iImage;
2802 if (tabItem->mask & TCIF_PARAM)
2803 memcpy(&tabItem->lParam, wineItem->extra, infoPtr->cbInfo);
2805 if (tabItem->mask & TCIF_RTLREADING)
2806 FIXME("TCIF_RTLREADING\n");
2808 if (tabItem->mask & TCIF_STATE)
2809 tabItem->dwState = wineItem->dwState;
2811 if (tabItem->mask & TCIF_TEXT)
2814 Str_GetPtrW (wineItem->pszText, tabItem->pszText, tabItem->cchTextMax);
2816 Str_GetPtrWtoA (wineItem->pszText, (LPSTR)tabItem->pszText, tabItem->cchTextMax);
2819 TAB_DumpItemExternalT(tabItem, iItem, bUnicode);
2825 static LRESULT TAB_DeleteItem (TAB_INFO *infoPtr, INT iItem)
2827 BOOL bResult = FALSE;
2829 TRACE("(%p, %d)\n", infoPtr, iItem);
2831 if ((iItem >= 0) && (iItem < infoPtr->uNumItem))
2833 TAB_ITEM *item = TAB_GetItem(infoPtr, iItem);
2834 LPBYTE oldItems = (LPBYTE)infoPtr->items;
2836 TAB_InvalidateTabArea(infoPtr);
2838 if ((item->mask & TCIF_TEXT) && item->pszText)
2839 Free(item->pszText);
2841 infoPtr->uNumItem--;
2843 if (!infoPtr->uNumItem)
2845 infoPtr->items = NULL;
2846 if (infoPtr->iHotTracked >= 0)
2848 KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
2849 infoPtr->iHotTracked = -1;
2854 infoPtr->items = Alloc(TAB_ITEM_SIZE(infoPtr) * infoPtr->uNumItem);
2857 memcpy(infoPtr->items, oldItems, iItem * TAB_ITEM_SIZE(infoPtr));
2859 if (iItem < infoPtr->uNumItem)
2860 memcpy(TAB_GetItem(infoPtr, iItem),
2861 oldItems + (iItem + 1) * TAB_ITEM_SIZE(infoPtr),
2862 (infoPtr->uNumItem - iItem) * TAB_ITEM_SIZE(infoPtr));
2864 if (iItem <= infoPtr->iHotTracked)
2866 /* When tabs move left/up, the hot track item may change */
2867 FIXME("Recalc hot track");
2872 /* Readjust the selected index */
2873 if ((iItem == infoPtr->iSelected) && (iItem > 0))
2874 infoPtr->iSelected--;
2876 if (iItem < infoPtr->iSelected)
2877 infoPtr->iSelected--;
2879 if (infoPtr->uNumItem == 0)
2880 infoPtr->iSelected = -1;
2882 /* Reposition and repaint tabs */
2883 TAB_SetItemBounds(infoPtr);
2891 static inline LRESULT TAB_DeleteAllItems (TAB_INFO *infoPtr)
2893 TRACE("(%p)\n", infoPtr);
2894 while (infoPtr->uNumItem)
2895 TAB_DeleteItem (infoPtr, 0);
2900 static inline LRESULT TAB_GetFont (const TAB_INFO *infoPtr)
2902 TRACE("(%p) returning %p\n", infoPtr, infoPtr->hFont);
2903 return (LRESULT)infoPtr->hFont;
2906 static inline LRESULT TAB_SetFont (TAB_INFO *infoPtr, HFONT hNewFont)
2908 TRACE("(%p,%p)\n", infoPtr, hNewFont);
2910 infoPtr->hFont = hNewFont;
2912 TAB_SetItemBounds(infoPtr);
2914 TAB_InvalidateTabArea(infoPtr);
2920 static inline LRESULT TAB_GetImageList (const TAB_INFO *infoPtr)
2923 return (LRESULT)infoPtr->himl;
2926 static inline LRESULT TAB_SetImageList (TAB_INFO *infoPtr, HIMAGELIST himlNew)
2928 HIMAGELIST himlPrev = infoPtr->himl;
2930 infoPtr->himl = himlNew;
2931 return (LRESULT)himlPrev;
2934 static inline LRESULT TAB_GetUnicodeFormat (const TAB_INFO *infoPtr)
2936 return infoPtr->bUnicode;
2939 static inline LRESULT TAB_SetUnicodeFormat (TAB_INFO *infoPtr, BOOL bUnicode)
2941 BOOL bTemp = infoPtr->bUnicode;
2943 infoPtr->bUnicode = bUnicode;
2948 static inline LRESULT TAB_Size (TAB_INFO *infoPtr)
2950 /* I'm not really sure what the following code was meant to do.
2951 This is what it is doing:
2952 When WM_SIZE is sent with SIZE_RESTORED, the control
2953 gets positioned in the top left corner.
2957 UINT uPosFlags,cx,cy;
2961 parent = GetParent (hwnd);
2962 GetClientRect(parent, &parent_rect);
2965 if (GetWindowLongW(hwnd, GWL_STYLE) & CCS_NORESIZE)
2966 uPosFlags |= (SWP_NOSIZE | SWP_NOMOVE);
2968 SetWindowPos (hwnd, 0, parent_rect.left, parent_rect.top,
2969 cx, cy, uPosFlags | SWP_NOZORDER);
2971 FIXME("WM_SIZE flag %x %lx not handled\n", wParam, lParam);
2974 /* Recompute the size/position of the tabs. */
2975 TAB_SetItemBounds (infoPtr);
2977 /* Force a repaint of the control. */
2978 InvalidateRect(infoPtr->hwnd, NULL, TRUE);
2984 static LRESULT TAB_Create (HWND hwnd, WPARAM wParam, LPARAM lParam)
2987 TEXTMETRICW fontMetrics;
2992 infoPtr = (TAB_INFO *)Alloc (sizeof(TAB_INFO));
2994 SetWindowLongPtrW(hwnd, 0, (DWORD_PTR)infoPtr);
2996 infoPtr->hwnd = hwnd;
2997 infoPtr->hwndNotify = ((LPCREATESTRUCTW)lParam)->hwndParent;
2998 infoPtr->uNumItem = 0;
2999 infoPtr->uNumRows = 0;
3000 infoPtr->uHItemPadding = 6;
3001 infoPtr->uVItemPadding = 3;
3002 infoPtr->uHItemPadding_s = 6;
3003 infoPtr->uVItemPadding_s = 3;
3006 infoPtr->hcurArrow = LoadCursorW (0, (LPWSTR)IDC_ARROW);
3007 infoPtr->iSelected = -1;
3008 infoPtr->iHotTracked = -1;
3009 infoPtr->uFocus = -1;
3010 infoPtr->hwndToolTip = 0;
3011 infoPtr->DoRedraw = TRUE;
3012 infoPtr->needsScrolling = FALSE;
3013 infoPtr->hwndUpDown = 0;
3014 infoPtr->leftmostVisible = 0;
3015 infoPtr->fHeightSet = FALSE;
3016 infoPtr->bUnicode = IsWindowUnicode (hwnd);
3017 infoPtr->cbInfo = sizeof(LPARAM);
3019 TRACE("Created tab control, hwnd [%p]\n", hwnd);
3021 /* The tab control always has the WS_CLIPSIBLINGS style. Even
3022 if you don't specify it in CreateWindow. This is necessary in
3023 order for paint to work correctly. This follows windows behaviour. */
3024 dwStyle = GetWindowLongW(hwnd, GWL_STYLE);
3025 SetWindowLongW(hwnd, GWL_STYLE, dwStyle|WS_CLIPSIBLINGS);
3027 if (dwStyle & TCS_TOOLTIPS) {
3028 /* Create tooltip control */
3029 infoPtr->hwndToolTip =
3030 CreateWindowExW (0, TOOLTIPS_CLASSW, NULL, 0,
3031 CW_USEDEFAULT, CW_USEDEFAULT,
3032 CW_USEDEFAULT, CW_USEDEFAULT,
3035 /* Send NM_TOOLTIPSCREATED notification */
3036 if (infoPtr->hwndToolTip) {
3037 NMTOOLTIPSCREATED nmttc;
3039 nmttc.hdr.hwndFrom = hwnd;
3040 nmttc.hdr.idFrom = GetWindowLongPtrW(hwnd, GWLP_ID);
3041 nmttc.hdr.code = NM_TOOLTIPSCREATED;
3042 nmttc.hwndToolTips = infoPtr->hwndToolTip;
3044 SendMessageW (infoPtr->hwndNotify, WM_NOTIFY,
3045 (WPARAM)GetWindowLongPtrW(hwnd, GWLP_ID), (LPARAM)&nmttc);
3049 OpenThemeData (infoPtr->hwnd, themeClass);
3052 * We need to get text information so we need a DC and we need to select
3056 hOldFont = SelectObject (hdc, GetStockObject (SYSTEM_FONT));
3058 /* Use the system font to determine the initial height of a tab. */
3059 GetTextMetricsW(hdc, &fontMetrics);
3062 * Make sure there is enough space for the letters + growing the
3063 * selected item + extra space for the selected item.
3065 infoPtr->tabHeight = fontMetrics.tmHeight + SELECTED_TAB_OFFSET +
3066 ((dwStyle & TCS_BUTTONS) ? 2 : 1) *
3067 infoPtr->uVItemPadding;
3069 /* Initialize the width of a tab. */
3070 if (dwStyle & TCS_FIXEDWIDTH)
3071 infoPtr->tabWidth = DEFAULT_TAB_WIDTH_FIXED;
3073 infoPtr->tabMinWidth = -1;
3075 TRACE("tabH=%d, tabW=%d\n", infoPtr->tabHeight, infoPtr->tabWidth);
3077 SelectObject (hdc, hOldFont);
3078 ReleaseDC(hwnd, hdc);
3084 TAB_Destroy (TAB_INFO *infoPtr)
3091 SetWindowLongPtrW(infoPtr->hwnd, 0, 0);
3093 if (infoPtr->items) {
3094 for (iItem = 0; iItem < infoPtr->uNumItem; iItem++) {
3095 if (TAB_GetItem(infoPtr, iItem)->pszText)
3096 Free (TAB_GetItem(infoPtr, iItem)->pszText);
3098 Free (infoPtr->items);
3101 if (infoPtr->hwndToolTip)
3102 DestroyWindow (infoPtr->hwndToolTip);
3104 if (infoPtr->hwndUpDown)
3105 DestroyWindow(infoPtr->hwndUpDown);
3107 if (infoPtr->iHotTracked >= 0)
3108 KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
3110 CloseThemeData (GetWindowTheme (infoPtr->hwnd));
3116 /* update theme after a WM_THEMECHANGED message */
3117 static LRESULT theme_changed (TAB_INFO* infoPtr)
3119 HTHEME theme = GetWindowTheme (infoPtr->hwnd);
3120 CloseThemeData (theme);
3121 OpenThemeData (infoPtr->hwnd, themeClass);
3125 static LRESULT TAB_NCCalcSize(HWND hwnd, WPARAM wParam, LPARAM lParam)
3129 return WVR_ALIGNTOP;
3132 static inline LRESULT
3133 TAB_SetItemExtra (TAB_INFO *infoPtr, INT cbInfo)
3135 if (!infoPtr || cbInfo <= 0)
3138 if (infoPtr->uNumItem)
3140 /* FIXME: MSDN says this is not allowed, but this hasn't been verified */
3144 infoPtr->cbInfo = cbInfo;
3148 static LRESULT WINAPI
3149 TAB_WindowProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
3151 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
3153 TRACE("hwnd=%p msg=%x wParam=%x lParam=%lx\n", hwnd, uMsg, wParam, lParam);
3154 if (!infoPtr && (uMsg != WM_CREATE))
3155 return DefWindowProcW (hwnd, uMsg, wParam, lParam);
3159 case TCM_GETIMAGELIST:
3160 return TAB_GetImageList (infoPtr);
3162 case TCM_SETIMAGELIST:
3163 return TAB_SetImageList (infoPtr, (HIMAGELIST)lParam);
3165 case TCM_GETITEMCOUNT:
3166 return TAB_GetItemCount (infoPtr);
3170 return TAB_GetItemT (infoPtr, (INT)wParam, (LPTCITEMW)lParam, uMsg == TCM_GETITEMW);
3174 return TAB_SetItemT (infoPtr, (INT)wParam, (LPTCITEMW)lParam, uMsg == TCM_SETITEMW);
3176 case TCM_DELETEITEM:
3177 return TAB_DeleteItem (infoPtr, (INT)wParam);
3179 case TCM_DELETEALLITEMS:
3180 return TAB_DeleteAllItems (infoPtr);
3182 case TCM_GETITEMRECT:
3183 return TAB_GetItemRect (infoPtr, wParam, lParam);
3186 return TAB_GetCurSel (infoPtr);
3189 return TAB_HitTest (infoPtr, (LPTCHITTESTINFO)lParam);
3192 return TAB_SetCurSel (infoPtr, (INT)wParam);
3194 case TCM_INSERTITEMA:
3195 case TCM_INSERTITEMW:
3196 return TAB_InsertItemT (infoPtr, wParam, lParam, uMsg == TCM_INSERTITEMW);
3198 case TCM_SETITEMEXTRA:
3199 return TAB_SetItemExtra (infoPtr, (int)wParam);
3201 case TCM_ADJUSTRECT:
3202 return TAB_AdjustRect (infoPtr, (BOOL)wParam, (LPRECT)lParam);
3204 case TCM_SETITEMSIZE:
3205 return TAB_SetItemSize (infoPtr, lParam);
3207 case TCM_REMOVEIMAGE:
3208 FIXME("Unimplemented msg TCM_REMOVEIMAGE\n");
3211 case TCM_SETPADDING:
3212 return TAB_SetPadding (infoPtr, lParam);
3214 case TCM_GETROWCOUNT:
3215 return TAB_GetRowCount(infoPtr);
3217 case TCM_GETUNICODEFORMAT:
3218 return TAB_GetUnicodeFormat (infoPtr);
3220 case TCM_SETUNICODEFORMAT:
3221 return TAB_SetUnicodeFormat (infoPtr, (BOOL)wParam);
3223 case TCM_HIGHLIGHTITEM:
3224 return TAB_HighlightItem (infoPtr, (INT)wParam, (BOOL)LOWORD(lParam));
3226 case TCM_GETTOOLTIPS:
3227 return TAB_GetToolTips (infoPtr);
3229 case TCM_SETTOOLTIPS:
3230 return TAB_SetToolTips (infoPtr, (HWND)wParam);
3232 case TCM_GETCURFOCUS:
3233 return TAB_GetCurFocus (infoPtr);
3235 case TCM_SETCURFOCUS:
3236 return TAB_SetCurFocus (infoPtr, (INT)wParam);
3238 case TCM_SETMINTABWIDTH:
3239 return TAB_SetMinTabWidth(infoPtr, (INT)lParam);
3241 case TCM_DESELECTALL:
3242 FIXME("Unimplemented msg TCM_DESELECTALL\n");
3245 case TCM_GETEXTENDEDSTYLE:
3246 FIXME("Unimplemented msg TCM_GETEXTENDEDSTYLE\n");
3249 case TCM_SETEXTENDEDSTYLE:
3250 FIXME("Unimplemented msg TCM_SETEXTENDEDSTYLE\n");
3254 return TAB_GetFont (infoPtr);
3257 return TAB_SetFont (infoPtr, (HFONT)wParam);
3260 return TAB_Create (hwnd, wParam, lParam);
3263 return TAB_Destroy (infoPtr);
3266 return DLGC_WANTARROWS | DLGC_WANTCHARS;
3268 case WM_LBUTTONDOWN:
3269 return TAB_LButtonDown (infoPtr, wParam, lParam);
3272 return TAB_LButtonUp (infoPtr);
3275 return SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, wParam, lParam);
3277 case WM_RBUTTONDOWN:
3278 return TAB_RButtonDown (infoPtr);
3281 return TAB_MouseMove (infoPtr, wParam, lParam);
3284 return TAB_Paint (infoPtr, (HDC)wParam);
3287 return TAB_Size (infoPtr);
3290 return TAB_SetRedraw (infoPtr, (BOOL)wParam);
3293 return TAB_OnHScroll(infoPtr, (int)LOWORD(wParam), (int)HIWORD(wParam), (HWND)lParam);
3295 case WM_STYLECHANGED:
3296 TAB_SetItemBounds (infoPtr);
3297 InvalidateRect(hwnd, NULL, TRUE);
3300 case WM_SYSCOLORCHANGE:
3301 COMCTL32_RefreshSysColors();
3304 case WM_THEMECHANGED:
3305 return theme_changed (infoPtr);
3309 TAB_FocusChanging(infoPtr);
3310 break; /* Don't disturb normal focus behavior */
3313 return TAB_KeyUp(infoPtr, wParam);
3315 return TAB_NCHitTest(infoPtr, lParam);
3318 return TAB_NCCalcSize(hwnd, wParam, lParam);
3321 if (uMsg >= WM_USER && uMsg < WM_APP)
3322 WARN("unknown msg %04x wp=%08x lp=%08lx\n",
3323 uMsg, wParam, lParam);
3326 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
3335 ZeroMemory (&wndClass, sizeof(WNDCLASSW));
3336 wndClass.style = CS_GLOBALCLASS | CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
3337 wndClass.lpfnWndProc = TAB_WindowProc;
3338 wndClass.cbClsExtra = 0;
3339 wndClass.cbWndExtra = sizeof(TAB_INFO *);
3340 wndClass.hCursor = LoadCursorW (0, (LPWSTR)IDC_ARROW);
3341 wndClass.hbrBackground = (HBRUSH)(COLOR_BTNFACE+1);
3342 wndClass.lpszClassName = WC_TABCONTROLW;
3344 RegisterClassW (&wndClass);
3349 TAB_Unregister (void)
3351 UnregisterClassW (WC_TABCONTROLW, NULL);