4 * Copyright 1998 Anders Carlsson
5 * Copyright 1999 Alex Priem <alexp@sci.kun.nl>
6 * Copyright 1999 Francis Beaudet
7 * Copyright 2003 Vitaliy Margolen
9 * This library is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Lesser General Public
11 * License as published by the Free Software Foundation; either
12 * version 2.1 of the License, or (at your option) any later version.
14 * This library is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Lesser General Public License for more details.
19 * You should have received a copy of the GNU Lesser General Public
20 * License along with this library; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
25 * This code was audited for completeness against the documented features
26 * of Comctl32.dll version 6.0 on May. 20, 2005, by James Hawkins.
28 * Unless otherwise noted, we believe this code to be complete, as per
29 * the specification mentioned above.
30 * If you discover missing features, or bugs, please note them below.
35 * TCS_MULTISELECT - implement for VK_SPACE selection
68 #include "wine/debug.h"
71 WINE_DEFAULT_DEBUG_CHANNEL(tab);
78 RECT rect; /* bounding rectangle of the item relative to the
79 * leftmost item (the leftmost item, 0, would have a
80 * "left" member of 0 in this rectangle)
82 * additionally the top member holds the row number
83 * and bottom is unused and should be 0 */
84 BYTE extra[1]; /* Space for caller supplied info, variable size */
87 /* The size of a tab item depends on how much extra data is requested.
88 TCM_INSERTITEM always stores at least LPARAM sized data. */
89 #define EXTRA_ITEM_SIZE(infoPtr) (max((infoPtr)->cbInfo, sizeof(LPARAM)))
90 #define TAB_ITEM_SIZE(infoPtr) FIELD_OFFSET(TAB_ITEM, extra[EXTRA_ITEM_SIZE(infoPtr)])
94 HWND hwnd; /* Tab control window */
95 HWND hwndNotify; /* notification window (parent) */
96 UINT uNumItem; /* number of tab items */
97 UINT uNumRows; /* number of tab rows */
98 INT tabHeight; /* height of the tab row */
99 INT tabWidth; /* width of tabs */
100 INT tabMinWidth; /* minimum width of items */
101 USHORT uHItemPadding; /* amount of horizontal padding, in pixels */
102 USHORT uVItemPadding; /* amount of vertical padding, in pixels */
103 USHORT uHItemPadding_s; /* Set amount of horizontal padding, in pixels */
104 USHORT uVItemPadding_s; /* Set amount of vertical padding, in pixels */
105 HFONT hFont; /* handle to the current font */
106 HCURSOR hcurArrow; /* handle to the current cursor */
107 HIMAGELIST himl; /* handle to an image list (may be 0) */
108 HWND hwndToolTip; /* handle to tab's tooltip */
109 INT leftmostVisible; /* Used for scrolling, this member contains
110 * the index of the first visible item */
111 INT iSelected; /* the currently selected item */
112 INT iHotTracked; /* the highlighted item under the mouse */
113 INT uFocus; /* item which has the focus */
114 BOOL DoRedraw; /* flag for redrawing when tab contents is changed*/
115 BOOL needsScrolling; /* TRUE if the size of the tabs is greater than
116 * the size of the control */
117 BOOL fHeightSet; /* was the height of the tabs explicitly set? */
118 BOOL bUnicode; /* Unicode control? */
119 HWND hwndUpDown; /* Updown control used for scrolling */
120 INT cbInfo; /* Number of bytes of caller supplied info per tab */
122 DWORD exStyle; /* Extended style used, currently:
123 TCS_EX_FLATSEPARATORS, TCS_EX_REGISTERDROP */
124 DWORD dwStyle; /* the cached window GWL_STYLE */
126 HDPA items; /* dynamic array of TAB_ITEM* pointers */
129 /******************************************************************************
130 * Positioning constants
132 #define SELECTED_TAB_OFFSET 2
133 #define ROUND_CORNER_SIZE 2
134 #define DISPLAY_AREA_PADDINGX 2
135 #define DISPLAY_AREA_PADDINGY 2
136 #define CONTROL_BORDER_SIZEX 2
137 #define CONTROL_BORDER_SIZEY 2
138 #define BUTTON_SPACINGX 3
139 #define BUTTON_SPACINGY 3
140 #define FLAT_BTN_SPACINGX 8
141 #define DEFAULT_MIN_TAB_WIDTH 54
142 #define DEFAULT_PADDING_X 6
143 #define EXTRA_ICON_PADDING 3
145 #define TAB_GetInfoPtr(hwnd) ((TAB_INFO *)GetWindowLongPtrW(hwnd,0))
147 #define GET_DEFAULT_MIN_TAB_WIDTH(infoPtr) (DEFAULT_MIN_TAB_WIDTH - (DEFAULT_PADDING_X - (infoPtr)->uHItemPadding) * 2)
149 /******************************************************************************
150 * Hot-tracking timer constants
152 #define TAB_HOTTRACK_TIMER 1
153 #define TAB_HOTTRACK_TIMER_INTERVAL 100 /* milliseconds */
155 static const WCHAR themeClass[] = { 'T','a','b',0 };
157 static inline TAB_ITEM* TAB_GetItem(const TAB_INFO *infoPtr, INT i)
159 assert(i >= 0 && i < infoPtr->uNumItem);
160 return DPA_GetPtr(infoPtr->items, i);
163 /******************************************************************************
166 static void TAB_InvalidateTabArea(const TAB_INFO *);
167 static void TAB_EnsureSelectionVisible(TAB_INFO *);
168 static void TAB_DrawItemInterior(const TAB_INFO *, HDC, INT, RECT*);
169 static LRESULT TAB_DeselectAll(TAB_INFO *, BOOL);
170 static BOOL TAB_InternalGetItemRect(const TAB_INFO *, INT, RECT*, 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 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 = (short)LOWORD(GetMessagePos ());
197 msg.pt.y = (short)HIWORD(GetMessagePos ());
199 SendMessageW (hwndTip, TTM_RELAYEVENT, 0, (LPARAM)&msg);
203 TAB_DumpItemExternalT(const TCITEMW *pti, UINT iItem, BOOL isW)
206 TRACE("external tab %d, mask=0x%08x, dwState=0x%08x, dwStateMask=0x%08x, 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(const TAB_INFO *infoPtr, UINT iItem)
217 TAB_ITEM *ti = TAB_GetItem(infoPtr, iItem);
219 TRACE("tab %d, dwState=0x%08x, pszText=%s, iImage=%d\n",
220 iItem, ti->dwState, debugstr_w(ti->pszText), ti->iImage);
221 TRACE("tab %d, rect.left=%d, rect.top(row)=%d\n",
222 iItem, ti->rect.left, ti->rect.top);
227 * the index of the selected tab, or -1 if no tab is selected. */
228 static inline LRESULT TAB_GetCurSel (const TAB_INFO *infoPtr)
230 TRACE("(%p)\n", infoPtr);
231 return infoPtr->iSelected;
235 * the index of the tab item that has the focus. */
236 static inline LRESULT
237 TAB_GetCurFocus (const TAB_INFO *infoPtr)
239 TRACE("(%p)\n", infoPtr);
240 return infoPtr->uFocus;
243 static inline LRESULT TAB_GetToolTips (const TAB_INFO *infoPtr)
245 TRACE("(%p)\n", infoPtr);
246 return (LRESULT)infoPtr->hwndToolTip;
249 static inline LRESULT TAB_SetCurSel (TAB_INFO *infoPtr, INT iItem)
251 INT prevItem = infoPtr->iSelected;
253 TRACE("(%p %d)\n", infoPtr, iItem);
256 infoPtr->iSelected = -1;
257 else if (iItem >= infoPtr->uNumItem)
260 if (prevItem != iItem) {
262 TAB_GetItem(infoPtr, prevItem)->dwState &= ~TCIS_BUTTONPRESSED;
263 TAB_GetItem(infoPtr, iItem)->dwState |= TCIS_BUTTONPRESSED;
265 infoPtr->iSelected = iItem;
266 infoPtr->uFocus = iItem;
267 TAB_EnsureSelectionVisible(infoPtr);
268 TAB_InvalidateTabArea(infoPtr);
274 static LRESULT TAB_SetCurFocus (TAB_INFO *infoPtr, INT iItem)
276 TRACE("(%p %d)\n", infoPtr, iItem);
279 infoPtr->uFocus = -1;
280 if (infoPtr->iSelected != -1) {
281 infoPtr->iSelected = -1;
282 TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGE);
283 TAB_InvalidateTabArea(infoPtr);
286 else if (iItem < infoPtr->uNumItem) {
287 if (infoPtr->dwStyle & TCS_BUTTONS) {
288 /* set focus to new item, leave selection as is */
289 if (infoPtr->uFocus != iItem) {
290 INT prev_focus = infoPtr->uFocus;
293 infoPtr->uFocus = iItem;
295 if (prev_focus != infoPtr->iSelected) {
296 if (TAB_InternalGetItemRect(infoPtr, prev_focus, &r, NULL))
297 InvalidateRect(infoPtr->hwnd, &r, FALSE);
300 if (TAB_InternalGetItemRect(infoPtr, iItem, &r, NULL))
301 InvalidateRect(infoPtr->hwnd, &r, FALSE);
303 TAB_SendSimpleNotify(infoPtr, TCN_FOCUSCHANGE);
306 INT oldFocus = infoPtr->uFocus;
307 if (infoPtr->iSelected != iItem || oldFocus == -1 ) {
308 infoPtr->uFocus = iItem;
309 if (oldFocus != -1) {
310 if (!TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGING)) {
311 infoPtr->iSelected = iItem;
312 TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGE);
315 infoPtr->iSelected = iItem;
316 TAB_EnsureSelectionVisible(infoPtr);
317 TAB_InvalidateTabArea(infoPtr);
325 static inline LRESULT
326 TAB_SetToolTips (TAB_INFO *infoPtr, HWND hwndToolTip)
328 TRACE("%p %p\n", infoPtr, hwndToolTip);
329 infoPtr->hwndToolTip = hwndToolTip;
333 static inline LRESULT
334 TAB_SetPadding (TAB_INFO *infoPtr, LPARAM lParam)
336 TRACE("(%p %d %d)\n", infoPtr, LOWORD(lParam), HIWORD(lParam));
337 infoPtr->uHItemPadding_s = LOWORD(lParam);
338 infoPtr->uVItemPadding_s = HIWORD(lParam);
343 /******************************************************************************
344 * TAB_InternalGetItemRect
346 * This method will calculate the rectangle representing a given tab item in
347 * client coordinates. This method takes scrolling into account.
349 * This method returns TRUE if the item is visible in the window and FALSE
350 * if it is completely outside the client area.
352 static BOOL TAB_InternalGetItemRect(
353 const TAB_INFO* infoPtr,
358 RECT tmpItemRect,clientRect;
360 /* Perform a sanity check and a trivial visibility check. */
361 if ( (infoPtr->uNumItem <= 0) ||
362 (itemIndex >= infoPtr->uNumItem) ||
363 (!(((infoPtr->dwStyle & TCS_MULTILINE) || (infoPtr->dwStyle & TCS_VERTICAL))) &&
364 (itemIndex < infoPtr->leftmostVisible)))
366 TRACE("Not Visible\n");
367 /* need to initialize these to empty rects */
370 memset(itemRect,0,sizeof(RECT));
371 itemRect->bottom = infoPtr->tabHeight;
374 memset(selectedRect,0,sizeof(RECT));
379 * Avoid special cases in this procedure by assigning the "out"
380 * parameters if the caller didn't supply them
382 if (itemRect == NULL)
383 itemRect = &tmpItemRect;
385 /* Retrieve the unmodified item rect. */
386 *itemRect = TAB_GetItem(infoPtr,itemIndex)->rect;
388 /* calculate the times bottom and top based on the row */
389 GetClientRect(infoPtr->hwnd, &clientRect);
391 if ((infoPtr->dwStyle & TCS_BOTTOM) && (infoPtr->dwStyle & TCS_VERTICAL))
393 itemRect->right = clientRect.right - SELECTED_TAB_OFFSET - itemRect->left * infoPtr->tabHeight -
394 ((infoPtr->dwStyle & TCS_BUTTONS) ? itemRect->left * BUTTON_SPACINGX : 0);
395 itemRect->left = itemRect->right - infoPtr->tabHeight;
397 else if (infoPtr->dwStyle & TCS_VERTICAL)
399 itemRect->left = clientRect.left + SELECTED_TAB_OFFSET + itemRect->left * infoPtr->tabHeight +
400 ((infoPtr->dwStyle & TCS_BUTTONS) ? itemRect->left * BUTTON_SPACINGX : 0);
401 itemRect->right = itemRect->left + infoPtr->tabHeight;
403 else if (infoPtr->dwStyle & TCS_BOTTOM)
405 itemRect->bottom = clientRect.bottom - itemRect->top * infoPtr->tabHeight -
406 ((infoPtr->dwStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : SELECTED_TAB_OFFSET);
407 itemRect->top = itemRect->bottom - infoPtr->tabHeight;
409 else /* not TCS_BOTTOM and not TCS_VERTICAL */
411 itemRect->top = clientRect.top + itemRect->top * infoPtr->tabHeight +
412 ((infoPtr->dwStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : SELECTED_TAB_OFFSET);
413 itemRect->bottom = itemRect->top + infoPtr->tabHeight;
417 * "scroll" it to make sure the item at the very left of the
418 * tab control is the leftmost visible tab.
420 if(infoPtr->dwStyle & TCS_VERTICAL)
424 -TAB_GetItem(infoPtr, infoPtr->leftmostVisible)->rect.top);
427 * Move the rectangle so the first item is slightly offset from
428 * the bottom of the tab control.
432 SELECTED_TAB_OFFSET);
437 -TAB_GetItem(infoPtr, infoPtr->leftmostVisible)->rect.left,
441 * Move the rectangle so the first item is slightly offset from
442 * the left of the tab control.
448 TRACE("item %d tab h=%d, rect=(%s)\n",
449 itemIndex, infoPtr->tabHeight, wine_dbgstr_rect(itemRect));
451 /* Now, calculate the position of the item as if it were selected. */
452 if (selectedRect!=NULL)
454 CopyRect(selectedRect, itemRect);
456 /* The rectangle of a selected item is a bit wider. */
457 if(infoPtr->dwStyle & TCS_VERTICAL)
458 InflateRect(selectedRect, 0, SELECTED_TAB_OFFSET);
460 InflateRect(selectedRect, SELECTED_TAB_OFFSET, 0);
462 /* If it also a bit higher. */
463 if ((infoPtr->dwStyle & TCS_BOTTOM) && (infoPtr->dwStyle & TCS_VERTICAL))
465 selectedRect->left -= 2; /* the border is thicker on the right */
466 selectedRect->right += SELECTED_TAB_OFFSET;
468 else if (infoPtr->dwStyle & TCS_VERTICAL)
470 selectedRect->left -= SELECTED_TAB_OFFSET;
471 selectedRect->right += 1;
473 else if (infoPtr->dwStyle & TCS_BOTTOM)
475 selectedRect->bottom += SELECTED_TAB_OFFSET;
477 else /* not TCS_BOTTOM and not TCS_VERTICAL */
479 selectedRect->top -= SELECTED_TAB_OFFSET;
480 selectedRect->bottom -= 1;
484 /* Check for visibility */
485 if (infoPtr->dwStyle & TCS_VERTICAL)
486 return (itemRect->top < clientRect.bottom) && (itemRect->bottom > clientRect.top);
488 return (itemRect->left < clientRect.right) && (itemRect->right > clientRect.left);
492 TAB_GetItemRect(const TAB_INFO *infoPtr, INT item, RECT *rect)
494 TRACE("(%p, %d, %p)\n", infoPtr, item, rect);
495 return TAB_InternalGetItemRect(infoPtr, item, rect, NULL);
498 /******************************************************************************
501 * This method is called to handle keyboard input
503 static LRESULT TAB_KeyDown(TAB_INFO* infoPtr, WPARAM keyCode, LPARAM lParam)
508 /* TCN_KEYDOWN notification sent always */
509 nm.hdr.hwndFrom = infoPtr->hwnd;
510 nm.hdr.idFrom = GetWindowLongPtrW(infoPtr->hwnd, GWLP_ID);
511 nm.hdr.code = TCN_KEYDOWN;
514 SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, nm.hdr.idFrom, (LPARAM)&nm);
519 newItem = infoPtr->uFocus - 1;
522 newItem = infoPtr->uFocus + 1;
526 /* If we changed to a valid item, change focused item */
527 if (newItem >= 0 && newItem < infoPtr->uNumItem && infoPtr->uFocus != newItem)
528 TAB_SetCurFocus(infoPtr, newItem);
534 * WM_KILLFOCUS handler
536 static void TAB_KillFocus(TAB_INFO *infoPtr)
538 /* clear current focused item back to selected for TCS_BUTTONS */
539 if ((infoPtr->dwStyle & TCS_BUTTONS) && (infoPtr->uFocus != infoPtr->iSelected))
543 if (TAB_InternalGetItemRect(infoPtr, infoPtr->uFocus, &r, NULL))
544 InvalidateRect(infoPtr->hwnd, &r, FALSE);
546 infoPtr->uFocus = infoPtr->iSelected;
550 /******************************************************************************
553 * This method is called whenever the focus goes in or out of this control
554 * it is used to update the visual state of the control.
556 static void TAB_FocusChanging(const TAB_INFO *infoPtr)
562 * Get the rectangle for the item.
564 isVisible = TAB_InternalGetItemRect(infoPtr,
570 * If the rectangle is not completely invisible, invalidate that
571 * portion of the window.
575 TRACE("invalidate (%s)\n", wine_dbgstr_rect(&selectedRect));
576 InvalidateRect(infoPtr->hwnd, &selectedRect, TRUE);
580 static INT TAB_InternalHitTest (const TAB_INFO *infoPtr, POINT pt, UINT *flags)
585 for (iCount = 0; iCount < infoPtr->uNumItem; iCount++)
587 TAB_InternalGetItemRect(infoPtr, iCount, &rect, NULL);
589 if (PtInRect(&rect, pt))
591 *flags = TCHT_ONITEM;
596 *flags = TCHT_NOWHERE;
600 static inline LRESULT
601 TAB_HitTest (const TAB_INFO *infoPtr, LPTCHITTESTINFO lptest)
603 TRACE("(%p, %p)\n", infoPtr, lptest);
604 return TAB_InternalHitTest (infoPtr, lptest->pt, &lptest->flags);
607 /******************************************************************************
610 * Napster v2b5 has a tab control for its main navigation which has a client
611 * area that covers the whole area of the dialog pages.
612 * That's why it receives all msgs for that area and the underlying dialog ctrls
614 * So I decided that we should handle WM_NCHITTEST here and return
615 * HTTRANSPARENT if we don't hit the tab control buttons.
616 * FIXME: WM_NCHITTEST handling correct ? Fix it if you know that Windows
617 * doesn't do it that way. Maybe depends on tab control styles ?
619 static inline LRESULT
620 TAB_NCHitTest (const TAB_INFO *infoPtr, LPARAM lParam)
625 pt.x = (short)LOWORD(lParam);
626 pt.y = (short)HIWORD(lParam);
627 ScreenToClient(infoPtr->hwnd, &pt);
629 if (TAB_InternalHitTest(infoPtr, pt, &dummyflag) == -1)
630 return HTTRANSPARENT;
636 TAB_LButtonDown (TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
642 if (infoPtr->hwndToolTip)
643 TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
644 WM_LBUTTONDOWN, wParam, lParam);
646 if (!(infoPtr->dwStyle & TCS_FOCUSNEVER)) {
647 SetFocus (infoPtr->hwnd);
650 if (infoPtr->hwndToolTip)
651 TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
652 WM_LBUTTONDOWN, wParam, lParam);
654 pt.x = (short)LOWORD(lParam);
655 pt.y = (short)HIWORD(lParam);
657 newItem = TAB_InternalHitTest (infoPtr, pt, &dummy);
659 TRACE("On Tab, item %d\n", newItem);
661 if ((newItem != -1) && (infoPtr->iSelected != newItem))
663 if ((infoPtr->dwStyle & TCS_BUTTONS) && (infoPtr->dwStyle & TCS_MULTISELECT) &&
664 (wParam & MK_CONTROL))
668 /* toggle multiselection */
669 TAB_GetItem(infoPtr, newItem)->dwState ^= TCIS_BUTTONPRESSED;
670 if (TAB_InternalGetItemRect (infoPtr, newItem, &r, NULL))
671 InvalidateRect (infoPtr->hwnd, &r, TRUE);
676 BOOL pressed = FALSE;
678 /* any button pressed ? */
679 for (i = 0; i < infoPtr->uNumItem; i++)
680 if ((TAB_GetItem (infoPtr, i)->dwState & TCIS_BUTTONPRESSED) &&
681 (infoPtr->iSelected != i))
687 TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGING);
690 TAB_DeselectAll (infoPtr, FALSE);
692 TAB_SetCurSel(infoPtr, newItem);
694 TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGE);
701 static inline LRESULT
702 TAB_LButtonUp (const TAB_INFO *infoPtr)
704 TAB_SendSimpleNotify(infoPtr, NM_CLICK);
709 static inline LRESULT
710 TAB_RButtonDown (const TAB_INFO *infoPtr)
712 TAB_SendSimpleNotify(infoPtr, NM_RCLICK);
716 /******************************************************************************
717 * TAB_DrawLoneItemInterior
719 * This calls TAB_DrawItemInterior. However, TAB_DrawItemInterior is normally
720 * called by TAB_DrawItem which is normally called by TAB_Refresh which sets
721 * up the device context and font. This routine does the same setup but
722 * only calls TAB_DrawItemInterior for the single specified item.
725 TAB_DrawLoneItemInterior(const TAB_INFO* infoPtr, int iItem)
727 HDC hdc = GetDC(infoPtr->hwnd);
730 /* Clip UpDown control to not draw over it */
731 if (infoPtr->needsScrolling)
733 GetWindowRect(infoPtr->hwnd, &rC);
734 GetWindowRect(infoPtr->hwndUpDown, &r);
735 ExcludeClipRect(hdc, r.left - rC.left, r.top - rC.top, r.right - rC.left, r.bottom - rC.top);
737 TAB_DrawItemInterior(infoPtr, hdc, iItem, NULL);
738 ReleaseDC(infoPtr->hwnd, hdc);
741 /* update a tab after hottracking - invalidate it or just redraw the interior,
742 * based on whether theming is used or not */
743 static inline void hottrack_refresh(const TAB_INFO *infoPtr, int tabIndex)
745 if (tabIndex == -1) return;
747 if (GetWindowTheme (infoPtr->hwnd))
750 TAB_InternalGetItemRect(infoPtr, tabIndex, &rect, NULL);
751 InvalidateRect (infoPtr->hwnd, &rect, FALSE);
754 TAB_DrawLoneItemInterior(infoPtr, tabIndex);
757 /******************************************************************************
758 * TAB_HotTrackTimerProc
760 * When a mouse-move event causes a tab to be highlighted (hot-tracking), a
761 * timer is setup so we can check if the mouse is moved out of our window.
762 * (We don't get an event when the mouse leaves, the mouse-move events just
763 * stop being delivered to our window and just start being delivered to
764 * another window.) This function is called when the timer triggers so
765 * we can check if the mouse has left our window. If so, we un-highlight
766 * the hot-tracked tab.
769 TAB_HotTrackTimerProc
771 HWND hwnd, /* handle of window for timer messages */
772 UINT uMsg, /* WM_TIMER message */
773 UINT_PTR idEvent, /* timer identifier */
774 DWORD dwTime /* current system time */
777 TAB_INFO* infoPtr = TAB_GetInfoPtr(hwnd);
779 if (infoPtr != NULL && infoPtr->iHotTracked >= 0)
784 ** If we can't get the cursor position, or if the cursor is outside our
785 ** window, we un-highlight the hot-tracked tab. Note that the cursor is
786 ** "outside" even if it is within our bounding rect if another window
787 ** overlaps. Note also that the case where the cursor stayed within our
788 ** window but has moved off the hot-tracked tab will be handled by the
789 ** WM_MOUSEMOVE event.
791 if (!GetCursorPos(&pt) || WindowFromPoint(pt) != hwnd)
793 /* Redraw iHotTracked to look normal */
794 INT iRedraw = infoPtr->iHotTracked;
795 infoPtr->iHotTracked = -1;
796 hottrack_refresh (infoPtr, iRedraw);
798 /* Kill this timer */
799 KillTimer(hwnd, TAB_HOTTRACK_TIMER);
804 /******************************************************************************
807 * If a tab control has the TCS_HOTTRACK style, then the tab under the mouse
808 * should be highlighted. This function determines which tab in a tab control,
809 * if any, is under the mouse and records that information. The caller may
810 * supply output parameters to receive the item number of the tab item which
811 * was highlighted but isn't any longer and of the tab item which is now
812 * highlighted but wasn't previously. The caller can use this information to
813 * selectively redraw those tab items.
815 * If the caller has a mouse position, it can supply it through the pos
816 * parameter. For example, TAB_MouseMove does this. Otherwise, the caller
817 * supplies NULL and this function determines the current mouse position
825 int* out_redrawLeave,
832 if (out_redrawLeave != NULL)
833 *out_redrawLeave = -1;
834 if (out_redrawEnter != NULL)
835 *out_redrawEnter = -1;
837 if ((infoPtr->dwStyle & TCS_HOTTRACK) || GetWindowTheme(infoPtr->hwnd))
845 ScreenToClient(infoPtr->hwnd, &pt);
849 pt.x = (short)LOWORD(*pos);
850 pt.y = (short)HIWORD(*pos);
853 item = TAB_InternalHitTest(infoPtr, pt, &flags);
856 if (item != infoPtr->iHotTracked)
858 if (infoPtr->iHotTracked >= 0)
860 /* Mark currently hot-tracked to be redrawn to look normal */
861 if (out_redrawLeave != NULL)
862 *out_redrawLeave = infoPtr->iHotTracked;
866 /* Kill timer which forces recheck of mouse pos */
867 KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
872 /* Start timer so we recheck mouse pos */
873 UINT timerID = SetTimer
877 TAB_HOTTRACK_TIMER_INTERVAL,
878 TAB_HotTrackTimerProc
882 return; /* Hot tracking not available */
885 infoPtr->iHotTracked = item;
889 /* Mark new hot-tracked to be redrawn to look highlighted */
890 if (out_redrawEnter != NULL)
891 *out_redrawEnter = item;
896 /******************************************************************************
899 * Handles the mouse-move event. Updates tooltips. Updates hot-tracking.
902 TAB_MouseMove (TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
907 if (infoPtr->hwndToolTip)
908 TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
909 WM_LBUTTONDOWN, wParam, lParam);
911 /* Determine which tab to highlight. Redraw tabs which change highlight
913 TAB_RecalcHotTrack(infoPtr, &lParam, &redrawLeave, &redrawEnter);
915 hottrack_refresh (infoPtr, redrawLeave);
916 hottrack_refresh (infoPtr, redrawEnter);
921 /******************************************************************************
924 * Calculates the tab control's display area given the window rectangle or
925 * the window rectangle given the requested display rectangle.
927 static LRESULT TAB_AdjustRect(const TAB_INFO *infoPtr, WPARAM fLarger, LPRECT prc)
929 LONG *iRightBottom, *iLeftTop;
931 TRACE ("hwnd=%p fLarger=%ld (%s)\n", infoPtr->hwnd, fLarger,
932 wine_dbgstr_rect(prc));
936 if(infoPtr->dwStyle & TCS_VERTICAL)
938 iRightBottom = &(prc->right);
939 iLeftTop = &(prc->left);
943 iRightBottom = &(prc->bottom);
944 iLeftTop = &(prc->top);
947 if (fLarger) /* Go from display rectangle */
949 /* Add the height of the tabs. */
950 if (infoPtr->dwStyle & TCS_BOTTOM)
951 *iRightBottom += infoPtr->tabHeight * infoPtr->uNumRows;
953 *iLeftTop -= infoPtr->tabHeight * infoPtr->uNumRows +
954 ((infoPtr->dwStyle & TCS_BUTTONS)? 3 * (infoPtr->uNumRows - 1) : 0);
956 /* Inflate the rectangle for the padding */
957 InflateRect(prc, DISPLAY_AREA_PADDINGX, DISPLAY_AREA_PADDINGY);
959 /* Inflate for the border */
960 InflateRect(prc, CONTROL_BORDER_SIZEX, CONTROL_BORDER_SIZEY);
962 else /* Go from window rectangle. */
964 /* Deflate the rectangle for the border */
965 InflateRect(prc, -CONTROL_BORDER_SIZEX, -CONTROL_BORDER_SIZEY);
967 /* Deflate the rectangle for the padding */
968 InflateRect(prc, -DISPLAY_AREA_PADDINGX, -DISPLAY_AREA_PADDINGY);
970 /* Remove the height of the tabs. */
971 if (infoPtr->dwStyle & TCS_BOTTOM)
972 *iRightBottom -= infoPtr->tabHeight * infoPtr->uNumRows;
974 *iLeftTop += (infoPtr->tabHeight) * infoPtr->uNumRows +
975 ((infoPtr->dwStyle & TCS_BUTTONS)? 3 * (infoPtr->uNumRows - 1) : 0);
981 /******************************************************************************
984 * This method will handle the notification from the scroll control and
985 * perform the scrolling operation on the tab control.
987 static LRESULT TAB_OnHScroll(TAB_INFO *infoPtr, int nScrollCode, int nPos)
989 if(nScrollCode == SB_THUMBPOSITION && nPos != infoPtr->leftmostVisible)
991 if(nPos < infoPtr->leftmostVisible)
992 infoPtr->leftmostVisible--;
994 infoPtr->leftmostVisible++;
996 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
997 TAB_InvalidateTabArea(infoPtr);
998 SendMessageW(infoPtr->hwndUpDown, UDM_SETPOS, 0,
999 MAKELONG(infoPtr->leftmostVisible, 0));
1005 /******************************************************************************
1006 * TAB_SetupScrolling
1008 * This method will check the current scrolling state and make sure the
1009 * scrolling control is displayed (or not).
1011 static void TAB_SetupScrolling(
1013 const RECT* clientRect)
1015 static const WCHAR emptyW[] = { 0 };
1018 if (infoPtr->needsScrolling)
1021 INT vsize, tabwidth;
1024 * Calculate the position of the scroll control.
1026 if(infoPtr->dwStyle & TCS_VERTICAL)
1028 controlPos.right = clientRect->right;
1029 controlPos.left = controlPos.right - 2 * GetSystemMetrics(SM_CXHSCROLL);
1031 if (infoPtr->dwStyle & TCS_BOTTOM)
1033 controlPos.top = clientRect->bottom - infoPtr->tabHeight;
1034 controlPos.bottom = controlPos.top + GetSystemMetrics(SM_CYHSCROLL);
1038 controlPos.bottom = clientRect->top + infoPtr->tabHeight;
1039 controlPos.top = controlPos.bottom - GetSystemMetrics(SM_CYHSCROLL);
1044 controlPos.right = clientRect->right;
1045 controlPos.left = controlPos.right - 2 * GetSystemMetrics(SM_CXHSCROLL);
1047 if (infoPtr->dwStyle & TCS_BOTTOM)
1049 controlPos.top = clientRect->bottom - infoPtr->tabHeight;
1050 controlPos.bottom = controlPos.top + GetSystemMetrics(SM_CYHSCROLL);
1054 controlPos.bottom = clientRect->top + infoPtr->tabHeight;
1055 controlPos.top = controlPos.bottom - GetSystemMetrics(SM_CYHSCROLL);
1060 * If we don't have a scroll control yet, we want to create one.
1061 * If we have one, we want to make sure it's positioned properly.
1063 if (infoPtr->hwndUpDown==0)
1065 infoPtr->hwndUpDown = CreateWindowW(UPDOWN_CLASSW, emptyW,
1066 WS_VISIBLE | WS_CHILD | UDS_HORZ,
1067 controlPos.left, controlPos.top,
1068 controlPos.right - controlPos.left,
1069 controlPos.bottom - controlPos.top,
1070 infoPtr->hwnd, NULL, NULL, NULL);
1074 SetWindowPos(infoPtr->hwndUpDown,
1076 controlPos.left, controlPos.top,
1077 controlPos.right - controlPos.left,
1078 controlPos.bottom - controlPos.top,
1079 SWP_SHOWWINDOW | SWP_NOZORDER);
1082 /* Now calculate upper limit of the updown control range.
1083 * We do this by calculating how many tabs will be offscreen when the
1084 * last tab is visible.
1086 if(infoPtr->uNumItem)
1088 vsize = clientRect->right - (controlPos.right - controlPos.left + 1);
1089 maxRange = infoPtr->uNumItem;
1090 tabwidth = TAB_GetItem(infoPtr, infoPtr->uNumItem - 1)->rect.right;
1092 for(; maxRange > 0; maxRange--)
1094 if(tabwidth - TAB_GetItem(infoPtr,maxRange - 1)->rect.left > vsize)
1098 if(maxRange == infoPtr->uNumItem)
1104 /* If we once had a scroll control... hide it */
1105 if (infoPtr->hwndUpDown)
1106 ShowWindow(infoPtr->hwndUpDown, SW_HIDE);
1108 if (infoPtr->hwndUpDown)
1109 SendMessageW(infoPtr->hwndUpDown, UDM_SETRANGE32, 0, maxRange);
1112 /******************************************************************************
1115 * This method will calculate the position rectangles of all the items in the
1116 * control. The rectangle calculated starts at 0 for the first item in the
1117 * list and ignores scrolling and selection.
1118 * It also uses the current font to determine the height of the tab row and
1119 * it checks if all the tabs fit in the client area of the window. If they
1120 * don't, a scrolling control is added.
1122 static void TAB_SetItemBounds (TAB_INFO *infoPtr)
1124 TEXTMETRICW fontMetrics;
1127 INT curItemRowCount;
1128 HFONT hFont, hOldFont;
1137 * We need to get text information so we need a DC and we need to select
1140 hdc = GetDC(infoPtr->hwnd);
1142 hFont = infoPtr->hFont ? infoPtr->hFont : GetStockObject (SYSTEM_FONT);
1143 hOldFont = SelectObject (hdc, hFont);
1146 * We will base the rectangle calculations on the client rectangle
1149 GetClientRect(infoPtr->hwnd, &clientRect);
1151 /* if TCS_VERTICAL then swap the height and width so this code places the
1152 tabs along the top of the rectangle and we can just rotate them after
1153 rather than duplicate all of the below code */
1154 if(infoPtr->dwStyle & TCS_VERTICAL)
1156 iTemp = clientRect.bottom;
1157 clientRect.bottom = clientRect.right;
1158 clientRect.right = iTemp;
1161 /* Now use hPadding and vPadding */
1162 infoPtr->uHItemPadding = infoPtr->uHItemPadding_s;
1163 infoPtr->uVItemPadding = infoPtr->uVItemPadding_s;
1165 /* The leftmost item will be "0" aligned */
1167 curItemRowCount = infoPtr->uNumItem ? 1 : 0;
1169 if (!(infoPtr->fHeightSet))
1172 INT icon_height = 0, cx;
1174 /* Use the current font to determine the height of a tab. */
1175 GetTextMetricsW(hdc, &fontMetrics);
1177 /* Get the icon height */
1179 ImageList_GetIconSize(infoPtr->himl, &cx, &icon_height);
1181 /* Take the highest between font or icon */
1182 if (fontMetrics.tmHeight > icon_height)
1183 item_height = fontMetrics.tmHeight + 2;
1185 item_height = icon_height;
1188 * Make sure there is enough space for the letters + icon + growing the
1189 * selected item + extra space for the selected item.
1191 infoPtr->tabHeight = item_height +
1192 ((infoPtr->dwStyle & TCS_BUTTONS) ? 2 : 1) *
1193 infoPtr->uVItemPadding;
1195 TRACE("tabH=%d, tmH=%d, iconh=%d\n",
1196 infoPtr->tabHeight, fontMetrics.tmHeight, icon_height);
1199 TRACE("client right=%d\n", clientRect.right);
1201 /* Get the icon width */
1206 ImageList_GetIconSize(infoPtr->himl, &icon_width, &cy);
1208 if (infoPtr->dwStyle & TCS_FIXEDWIDTH)
1211 /* Add padding if icon is present */
1212 icon_width += infoPtr->uHItemPadding;
1215 for (curItem = 0; curItem < infoPtr->uNumItem; curItem++)
1217 TAB_ITEM *curr = TAB_GetItem(infoPtr, curItem);
1219 /* Set the leftmost position of the tab. */
1220 curr->rect.left = curItemLeftPos;
1222 if (infoPtr->dwStyle & TCS_FIXEDWIDTH)
1224 curr->rect.right = curr->rect.left +
1225 max(infoPtr->tabWidth, icon_width);
1227 else if (!curr->pszText)
1229 /* If no text use minimum tab width including padding. */
1230 if (infoPtr->tabMinWidth < 0)
1231 curr->rect.right = curr->rect.left + GET_DEFAULT_MIN_TAB_WIDTH(infoPtr);
1234 curr->rect.right = curr->rect.left + infoPtr->tabMinWidth;
1236 /* Add extra padding if icon is present */
1237 if (infoPtr->himl && infoPtr->tabMinWidth > 0 && infoPtr->tabMinWidth < DEFAULT_MIN_TAB_WIDTH
1238 && infoPtr->uHItemPadding > 1)
1239 curr->rect.right += EXTRA_ICON_PADDING * (infoPtr->uHItemPadding-1);
1246 /* Calculate how wide the tab is depending on the text it contains */
1247 GetTextExtentPoint32W(hdc, curr->pszText,
1248 lstrlenW(curr->pszText), &size);
1250 tabwidth = size.cx + icon_width + 2 * infoPtr->uHItemPadding;
1252 if (infoPtr->tabMinWidth < 0)
1253 tabwidth = max(tabwidth, GET_DEFAULT_MIN_TAB_WIDTH(infoPtr));
1255 tabwidth = max(tabwidth, infoPtr->tabMinWidth);
1257 curr->rect.right = curr->rect.left + tabwidth;
1258 TRACE("for <%s>, l,r=%d,%d\n",
1259 debugstr_w(curr->pszText), curr->rect.left, curr->rect.right);
1263 * Check if this is a multiline tab control and if so
1264 * check to see if we should wrap the tabs
1266 * Wrap all these tabs. We will arrange them evenly later.
1270 if (((infoPtr->dwStyle & TCS_MULTILINE) || (infoPtr->dwStyle & TCS_VERTICAL)) &&
1272 (clientRect.right - CONTROL_BORDER_SIZEX - DISPLAY_AREA_PADDINGX)))
1274 curr->rect.right -= curr->rect.left;
1276 curr->rect.left = 0;
1278 TRACE("wrapping <%s>, l,r=%d,%d\n", debugstr_w(curr->pszText),
1279 curr->rect.left, curr->rect.right);
1282 curr->rect.bottom = 0;
1283 curr->rect.top = curItemRowCount - 1;
1285 TRACE("Rect: %s\n", wine_dbgstr_rect(&curr->rect));
1288 * The leftmost position of the next item is the rightmost position
1291 if (infoPtr->dwStyle & TCS_BUTTONS)
1293 curItemLeftPos = curr->rect.right + BUTTON_SPACINGX;
1294 if (infoPtr->dwStyle & TCS_FLATBUTTONS)
1295 curItemLeftPos += FLAT_BTN_SPACINGX;
1298 curItemLeftPos = curr->rect.right;
1301 if (!((infoPtr->dwStyle & TCS_MULTILINE) || (infoPtr->dwStyle & TCS_VERTICAL)))
1304 * Check if we need a scrolling control.
1306 infoPtr->needsScrolling = (curItemLeftPos + (2 * SELECTED_TAB_OFFSET) >
1309 /* Don't need scrolling, then update infoPtr->leftmostVisible */
1310 if(!infoPtr->needsScrolling)
1311 infoPtr->leftmostVisible = 0;
1316 * No scrolling in Multiline or Vertical styles.
1318 infoPtr->needsScrolling = FALSE;
1319 infoPtr->leftmostVisible = 0;
1321 TAB_SetupScrolling(infoPtr, &clientRect);
1323 /* Set the number of rows */
1324 infoPtr->uNumRows = curItemRowCount;
1326 /* Arrange all tabs evenly if style says so */
1327 if (!(infoPtr->dwStyle & TCS_RAGGEDRIGHT) &&
1328 ((infoPtr->dwStyle & TCS_MULTILINE) || (infoPtr->dwStyle & TCS_VERTICAL)) &&
1329 (infoPtr->uNumItem > 0) &&
1330 (infoPtr->uNumRows > 1))
1332 INT tabPerRow,remTab,iRow;
1337 * Ok windows tries to even out the rows. place the same
1338 * number of tabs in each row. So lets give that a shot
1341 tabPerRow = infoPtr->uNumItem / (infoPtr->uNumRows);
1342 remTab = infoPtr->uNumItem % (infoPtr->uNumRows);
1344 for (iItm=0,iRow=0,iCount=0,curItemLeftPos=0;
1345 iItm<infoPtr->uNumItem;
1348 /* normalize the current rect */
1349 TAB_ITEM *curr = TAB_GetItem(infoPtr, iItm);
1351 /* shift the item to the left side of the clientRect */
1352 curr->rect.right -= curr->rect.left;
1353 curr->rect.left = 0;
1355 TRACE("r=%d, cl=%d, cl.r=%d, iCount=%d, iRow=%d, uNumRows=%d, remTab=%d, tabPerRow=%d\n",
1356 curr->rect.right, curItemLeftPos, clientRect.right,
1357 iCount, iRow, infoPtr->uNumRows, remTab, tabPerRow);
1359 /* if we have reached the maximum number of tabs on this row */
1360 /* move to the next row, reset our current item left position and */
1361 /* the count of items on this row */
1363 if (infoPtr->dwStyle & TCS_VERTICAL) {
1364 /* Vert: Add the remaining tabs in the *last* remainder rows */
1365 if (iCount >= ((iRow>=(INT)infoPtr->uNumRows - remTab)?tabPerRow + 1:tabPerRow)) {
1371 /* Horz: Add the remaining tabs in the *first* remainder rows */
1372 if (iCount >= ((iRow<remTab)?tabPerRow + 1:tabPerRow)) {
1379 /* shift the item to the right to place it as the next item in this row */
1380 curr->rect.left += curItemLeftPos;
1381 curr->rect.right += curItemLeftPos;
1382 curr->rect.top = iRow;
1383 if (infoPtr->dwStyle & TCS_BUTTONS)
1385 curItemLeftPos = curr->rect.right + 1;
1386 if (infoPtr->dwStyle & TCS_FLATBUTTONS)
1387 curItemLeftPos += FLAT_BTN_SPACINGX;
1390 curItemLeftPos = curr->rect.right;
1392 TRACE("arranging <%s>, l,r=%d,%d, row=%d\n",
1393 debugstr_w(curr->pszText), curr->rect.left,
1394 curr->rect.right, curr->rect.top);
1401 INT widthDiff, iIndexStart=0, iIndexEnd=0;
1405 while(iIndexStart < infoPtr->uNumItem)
1407 TAB_ITEM *start = TAB_GetItem(infoPtr, iIndexStart);
1410 * find the index of the row
1412 /* find the first item on the next row */
1413 for (iIndexEnd=iIndexStart;
1414 (iIndexEnd < infoPtr->uNumItem) &&
1415 (TAB_GetItem(infoPtr, iIndexEnd)->rect.top ==
1418 /* intentionally blank */;
1421 * we need to justify these tabs so they fill the whole given
1425 /* find the amount of space remaining on this row */
1426 widthDiff = clientRect.right - (2 * SELECTED_TAB_OFFSET) -
1427 TAB_GetItem(infoPtr, iIndexEnd - 1)->rect.right;
1429 /* iCount is the number of tab items on this row */
1430 iCount = iIndexEnd - iIndexStart;
1434 remainder = widthDiff % iCount;
1435 widthDiff = widthDiff / iCount;
1436 /* add widthDiff/iCount, or extra space/items on row, to each item on this row */
1437 for (iIndex=iIndexStart, iCount=0; iIndex < iIndexEnd; iIndex++, iCount++)
1439 TAB_ITEM *item = TAB_GetItem(infoPtr, iIndex);
1441 item->rect.left += iCount * widthDiff;
1442 item->rect.right += (iCount + 1) * widthDiff;
1444 TRACE("adjusting 1 <%s>, l,r=%d,%d\n",
1445 debugstr_w(item->pszText),
1446 item->rect.left, item->rect.right);
1449 TAB_GetItem(infoPtr, iIndex - 1)->rect.right += remainder;
1451 else /* we have only one item on this row, make it take up the entire row */
1453 start->rect.left = clientRect.left;
1454 start->rect.right = clientRect.right - 4;
1456 TRACE("adjusting 2 <%s>, l,r=%d,%d\n",
1457 debugstr_w(start->pszText),
1458 start->rect.left, start->rect.right);
1463 iIndexStart = iIndexEnd;
1468 /* if TCS_VERTICAL rotate the tabs so they are along the side of the clientRect */
1469 if(infoPtr->dwStyle & TCS_VERTICAL)
1472 for(iIndex = 0; iIndex < infoPtr->uNumItem; iIndex++)
1474 rcItem = &TAB_GetItem(infoPtr, iIndex)->rect;
1476 rcOriginal = *rcItem;
1478 /* this is rotating the items by 90 degrees clockwise around the center of the control */
1479 rcItem->top = (rcOriginal.left - clientRect.left);
1480 rcItem->bottom = rcItem->top + (rcOriginal.right - rcOriginal.left);
1481 rcItem->left = rcOriginal.top;
1482 rcItem->right = rcOriginal.bottom;
1486 TAB_EnsureSelectionVisible(infoPtr);
1487 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
1490 SelectObject (hdc, hOldFont);
1491 ReleaseDC (infoPtr->hwnd, hdc);
1496 TAB_EraseTabInterior(const TAB_INFO *infoPtr, HDC hdc, INT iItem, const RECT *drawRect)
1498 HBRUSH hbr = CreateSolidBrush (comctl32_color.clrBtnFace);
1499 BOOL deleteBrush = TRUE;
1500 RECT rTemp = *drawRect;
1502 if (infoPtr->dwStyle & TCS_BUTTONS)
1504 if (iItem == infoPtr->iSelected)
1506 /* Background color */
1507 if (!(infoPtr->dwStyle & TCS_OWNERDRAWFIXED))
1510 hbr = GetSysColorBrush(COLOR_SCROLLBAR);
1512 SetTextColor(hdc, comctl32_color.clr3dFace);
1513 SetBkColor(hdc, comctl32_color.clr3dHilight);
1515 /* if COLOR_WINDOW happens to be the same as COLOR_3DHILIGHT
1516 * we better use 0x55aa bitmap brush to make scrollbar's background
1517 * look different from the window background.
1519 if (comctl32_color.clr3dHilight == comctl32_color.clrWindow)
1520 hbr = COMCTL32_hPattern55AABrush;
1522 deleteBrush = FALSE;
1524 FillRect(hdc, &rTemp, hbr);
1526 else /* ! selected */
1528 if (infoPtr->dwStyle & TCS_FLATBUTTONS)
1530 InflateRect(&rTemp, 2, 2);
1531 FillRect(hdc, &rTemp, hbr);
1532 if (iItem == infoPtr->iHotTracked ||
1533 (iItem != infoPtr->iSelected && iItem == infoPtr->uFocus))
1534 DrawEdge(hdc, &rTemp, BDR_RAISEDINNER, BF_RECT);
1537 FillRect(hdc, &rTemp, hbr);
1541 else /* !TCS_BUTTONS */
1543 InflateRect(&rTemp, -2, -2);
1544 if (!GetWindowTheme (infoPtr->hwnd))
1545 FillRect(hdc, &rTemp, hbr);
1548 /* highlighting is drawn on top of previous fills */
1549 if (TAB_GetItem(infoPtr, iItem)->dwState & TCIS_HIGHLIGHTED)
1554 deleteBrush = FALSE;
1556 hbr = GetSysColorBrush(COLOR_HIGHLIGHT);
1557 FillRect(hdc, &rTemp, hbr);
1561 if (deleteBrush) DeleteObject(hbr);
1564 /******************************************************************************
1565 * TAB_DrawItemInterior
1567 * This method is used to draw the interior (text and icon) of a single tab
1568 * into the tab control.
1571 TAB_DrawItemInterior(const TAB_INFO *infoPtr, HDC hdc, INT iItem, RECT *drawRect)
1580 /* if (drawRect == NULL) */
1587 * Get the rectangle for the item.
1589 isVisible = TAB_InternalGetItemRect(infoPtr, iItem, &itemRect, &selectedRect);
1594 * Make sure drawRect points to something valid; simplifies code.
1596 drawRect = &localRect;
1599 * This logic copied from the part of TAB_DrawItem which draws
1600 * the tab background. It's important to keep it in sync. I
1601 * would have liked to avoid code duplication, but couldn't figure
1602 * out how without making spaghetti of TAB_DrawItem.
1604 if (iItem == infoPtr->iSelected)
1605 *drawRect = selectedRect;
1607 *drawRect = itemRect;
1609 if (infoPtr->dwStyle & TCS_BUTTONS)
1611 if (iItem == infoPtr->iSelected)
1613 drawRect->left += 4;
1615 drawRect->right -= 4;
1617 if (infoPtr->dwStyle & TCS_VERTICAL)
1619 if (!(infoPtr->dwStyle & TCS_BOTTOM)) drawRect->right += 1;
1620 drawRect->bottom -= 4;
1624 if (infoPtr->dwStyle & TCS_BOTTOM)
1627 drawRect->bottom -= 4;
1630 drawRect->bottom -= 1;
1635 drawRect->left += 2;
1637 drawRect->right -= 2;
1638 drawRect->bottom -= 2;
1643 if ((infoPtr->dwStyle & TCS_VERTICAL) && (infoPtr->dwStyle & TCS_BOTTOM))
1645 if (iItem != infoPtr->iSelected)
1647 drawRect->left += 2;
1649 drawRect->bottom -= 2;
1652 else if (infoPtr->dwStyle & TCS_VERTICAL)
1654 if (iItem == infoPtr->iSelected)
1656 drawRect->right += 1;
1661 drawRect->right -= 2;
1662 drawRect->bottom -= 2;
1665 else if (infoPtr->dwStyle & TCS_BOTTOM)
1667 if (iItem == infoPtr->iSelected)
1673 InflateRect(drawRect, -2, -2);
1674 drawRect->bottom += 2;
1679 if (iItem == infoPtr->iSelected)
1681 drawRect->bottom += 3;
1685 drawRect->bottom -= 2;
1686 InflateRect(drawRect, -2, 0);
1691 TRACE("drawRect=(%s)\n", wine_dbgstr_rect(drawRect));
1693 /* Clear interior */
1694 TAB_EraseTabInterior (infoPtr, hdc, iItem, drawRect);
1696 /* Draw the focus rectangle */
1697 if (!(infoPtr->dwStyle & TCS_FOCUSNEVER) &&
1698 (GetFocus() == infoPtr->hwnd) &&
1699 (iItem == infoPtr->uFocus) )
1701 RECT rFocus = *drawRect;
1703 if (!(infoPtr->dwStyle & TCS_BUTTONS)) InflateRect(&rFocus, -3, -3);
1704 if (infoPtr->dwStyle & TCS_BOTTOM && !(infoPtr->dwStyle & TCS_VERTICAL))
1707 /* focus should stay on selected item for TCS_BUTTONS style */
1708 if (!((infoPtr->dwStyle & TCS_BUTTONS) && (infoPtr->iSelected != iItem)))
1709 DrawFocusRect(hdc, &rFocus);
1715 htextPen = CreatePen( PS_SOLID, 1, comctl32_color.clrBtnText );
1716 holdPen = SelectObject(hdc, htextPen);
1717 hOldFont = SelectObject(hdc, infoPtr->hFont);
1720 * Setup for text output
1722 oldBkMode = SetBkMode(hdc, TRANSPARENT);
1723 if (!GetWindowTheme (infoPtr->hwnd) || (infoPtr->dwStyle & TCS_BUTTONS))
1725 if ((infoPtr->dwStyle & TCS_HOTTRACK) && (iItem == infoPtr->iHotTracked) &&
1726 !(infoPtr->dwStyle & TCS_FLATBUTTONS))
1727 SetTextColor(hdc, comctl32_color.clrHighlight);
1728 else if (TAB_GetItem(infoPtr, iItem)->dwState & TCIS_HIGHLIGHTED)
1729 SetTextColor(hdc, comctl32_color.clrHighlightText);
1731 SetTextColor(hdc, comctl32_color.clrBtnText);
1735 * if owner draw, tell the owner to draw
1737 if ((infoPtr->dwStyle & TCS_OWNERDRAWFIXED) && IsWindow(infoPtr->hwndNotify))
1743 drawRect->right -= 1;
1744 if ( iItem == infoPtr->iSelected )
1746 drawRect->right -= 1;
1747 drawRect->left += 1;
1750 id = (UINT)GetWindowLongPtrW( infoPtr->hwnd, GWLP_ID );
1752 /* fill DRAWITEMSTRUCT */
1753 dis.CtlType = ODT_TAB;
1756 dis.itemAction = ODA_DRAWENTIRE;
1758 if ( iItem == infoPtr->iSelected )
1759 dis.itemState |= ODS_SELECTED;
1760 if (infoPtr->uFocus == iItem)
1761 dis.itemState |= ODS_FOCUS;
1762 dis.hwndItem = infoPtr->hwnd;
1764 CopyRect(&dis.rcItem,drawRect);
1766 /* when extra data fits ULONG_PTR, store it directly */
1767 if (infoPtr->cbInfo > sizeof(LPARAM))
1768 dis.itemData = (ULONG_PTR) TAB_GetItem(infoPtr, iItem)->extra;
1771 /* this could be considered broken on 64 bit, but that's how it works -
1772 only first 4 bytes are copied */
1773 memcpy(&dis.itemData, (ULONG_PTR*)TAB_GetItem(infoPtr, iItem)->extra, 4);
1776 /* draw notification */
1777 SendMessageW( infoPtr->hwndNotify, WM_DRAWITEM, id, (LPARAM)&dis );
1781 TAB_ITEM *item = TAB_GetItem(infoPtr, iItem);
1785 /* used to center the icon and text in the tab */
1787 INT center_offset_h, center_offset_v;
1789 /* set rcImage to drawRect, we will use top & left in our ImageList_Draw call */
1790 rcImage = *drawRect;
1794 rcText.left = rcText.top = rcText.right = rcText.bottom = 0;
1796 /* get the rectangle that the text fits in */
1799 DrawTextW(hdc, item->pszText, -1, &rcText, DT_CALCRECT);
1802 * If not owner draw, then do the drawing ourselves.
1806 if (infoPtr->himl && item->iImage != -1)
1811 ImageList_GetIconSize(infoPtr->himl, &cx, &cy);
1813 if(infoPtr->dwStyle & TCS_VERTICAL)
1815 center_offset_h = ((drawRect->bottom - drawRect->top) - (cy + infoPtr->uHItemPadding + (rcText.right - rcText.left))) / 2;
1816 center_offset_v = ((drawRect->right - drawRect->left) - cx) / 2;
1820 center_offset_h = ((drawRect->right - drawRect->left) - (cx + infoPtr->uHItemPadding + (rcText.right - rcText.left))) / 2;
1821 center_offset_v = ((drawRect->bottom - drawRect->top) - cy) / 2;
1824 /* if an item is selected, the icon is shifted up instead of down */
1825 if (iItem == infoPtr->iSelected)
1826 center_offset_v -= infoPtr->uVItemPadding / 2;
1828 center_offset_v += infoPtr->uVItemPadding / 2;
1830 if (infoPtr->dwStyle & TCS_FIXEDWIDTH && infoPtr->dwStyle & (TCS_FORCELABELLEFT | TCS_FORCEICONLEFT))
1831 center_offset_h = infoPtr->uHItemPadding;
1833 if (center_offset_h < 2)
1834 center_offset_h = 2;
1836 if (center_offset_v < 0)
1837 center_offset_v = 0;
1839 TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%s), textlen=%d\n",
1840 debugstr_w(item->pszText), center_offset_h, center_offset_v,
1841 wine_dbgstr_rect(drawRect), (rcText.right-rcText.left));
1843 if((infoPtr->dwStyle & TCS_VERTICAL) && (infoPtr->dwStyle & TCS_BOTTOM))
1845 rcImage.top = drawRect->top + center_offset_h;
1846 /* if tab is TCS_VERTICAL and TCS_BOTTOM, the text is drawn from the */
1847 /* right side of the tab, but the image still uses the left as its x position */
1848 /* this keeps the image always drawn off of the same side of the tab */
1849 rcImage.left = drawRect->right - cx - center_offset_v;
1850 drawRect->top += cy + infoPtr->uHItemPadding;
1852 else if(infoPtr->dwStyle & TCS_VERTICAL)
1854 rcImage.top = drawRect->bottom - cy - center_offset_h;
1855 rcImage.left = drawRect->left + center_offset_v;
1856 drawRect->bottom -= cy + infoPtr->uHItemPadding;
1858 else /* normal style, whether TCS_BOTTOM or not */
1860 rcImage.left = drawRect->left + center_offset_h;
1861 rcImage.top = drawRect->top + center_offset_v;
1862 drawRect->left += cx + infoPtr->uHItemPadding;
1865 TRACE("drawing image=%d, left=%d, top=%d\n",
1866 item->iImage, rcImage.left, rcImage.top-1);
1878 /* Now position text */
1879 if (infoPtr->dwStyle & TCS_FIXEDWIDTH && infoPtr->dwStyle & TCS_FORCELABELLEFT)
1880 center_offset_h = infoPtr->uHItemPadding;
1882 if(infoPtr->dwStyle & TCS_VERTICAL)
1883 center_offset_h = ((drawRect->bottom - drawRect->top) - (rcText.right - rcText.left)) / 2;
1885 center_offset_h = ((drawRect->right - drawRect->left) - (rcText.right - rcText.left)) / 2;
1887 if(infoPtr->dwStyle & TCS_VERTICAL)
1889 if(infoPtr->dwStyle & TCS_BOTTOM)
1890 drawRect->top+=center_offset_h;
1892 drawRect->bottom-=center_offset_h;
1894 center_offset_v = ((drawRect->right - drawRect->left) - (rcText.bottom - rcText.top)) / 2;
1898 drawRect->left += center_offset_h;
1899 center_offset_v = ((drawRect->bottom - drawRect->top) - (rcText.bottom - rcText.top)) / 2;
1902 /* if an item is selected, the text is shifted up instead of down */
1903 if (iItem == infoPtr->iSelected)
1904 center_offset_v -= infoPtr->uVItemPadding / 2;
1906 center_offset_v += infoPtr->uVItemPadding / 2;
1908 if (center_offset_v < 0)
1909 center_offset_v = 0;
1911 if(infoPtr->dwStyle & TCS_VERTICAL)
1912 drawRect->left += center_offset_v;
1914 drawRect->top += center_offset_v;
1917 if(infoPtr->dwStyle & TCS_VERTICAL) /* if we are vertical rotate the text and each character */
1919 static const WCHAR ArialW[] = { 'A','r','i','a','l',0 };
1922 INT nEscapement = 900;
1923 INT nOrientation = 900;
1925 if(infoPtr->dwStyle & TCS_BOTTOM)
1928 nOrientation = -900;
1931 /* to get a font with the escapement and orientation we are looking for, we need to */
1932 /* call CreateFontIndirectA, which requires us to set the values of the logfont we pass in */
1933 if (!GetObjectW((infoPtr->hFont) ?
1934 infoPtr->hFont : GetStockObject(SYSTEM_FONT),
1935 sizeof(LOGFONTW),&logfont))
1939 lstrcpyW(logfont.lfFaceName, ArialW);
1940 logfont.lfHeight = -MulDiv(iPointSize, GetDeviceCaps(hdc, LOGPIXELSY),
1942 logfont.lfWeight = FW_NORMAL;
1943 logfont.lfItalic = 0;
1944 logfont.lfUnderline = 0;
1945 logfont.lfStrikeOut = 0;
1948 logfont.lfEscapement = nEscapement;
1949 logfont.lfOrientation = nOrientation;
1950 hFont = CreateFontIndirectW(&logfont);
1951 SelectObject(hdc, hFont);
1956 (infoPtr->dwStyle & TCS_BOTTOM) ? drawRect->right : drawRect->left,
1957 (!(infoPtr->dwStyle & TCS_BOTTOM)) ? drawRect->bottom : drawRect->top,
1961 lstrlenW(item->pszText),
1965 DeleteObject(hFont);
1969 TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%s), textlen=%d\n",
1970 debugstr_w(item->pszText), center_offset_h, center_offset_v,
1971 wine_dbgstr_rect(drawRect), (rcText.right-rcText.left));
1978 lstrlenW(item->pszText),
1980 DT_LEFT | DT_SINGLELINE
1985 *drawRect = rcTemp; /* restore drawRect */
1991 SelectObject(hdc, hOldFont);
1992 SetBkMode(hdc, oldBkMode);
1993 SelectObject(hdc, holdPen);
1994 DeleteObject( htextPen );
1997 /******************************************************************************
2000 * This method is used to draw a single tab into the tab control.
2002 static void TAB_DrawItem(const TAB_INFO *infoPtr, HDC hdc, INT iItem)
2007 RECT r, fillRect, r1;
2010 COLORREF bkgnd, corner;
2014 * Get the rectangle for the item.
2016 isVisible = TAB_InternalGetItemRect(infoPtr,
2025 /* Clip UpDown control to not draw over it */
2026 if (infoPtr->needsScrolling)
2028 GetWindowRect(infoPtr->hwnd, &rC);
2029 GetWindowRect(infoPtr->hwndUpDown, &rUD);
2030 ExcludeClipRect(hdc, rUD.left - rC.left, rUD.top - rC.top, rUD.right - rC.left, rUD.bottom - rC.top);
2033 /* If you need to see what the control is doing,
2034 * then override these variables. They will change what
2035 * fill colors are used for filling the tabs, and the
2036 * corners when drawing the edge.
2038 bkgnd = comctl32_color.clrBtnFace;
2039 corner = comctl32_color.clrBtnFace;
2041 if (infoPtr->dwStyle & TCS_BUTTONS)
2043 /* Get item rectangle */
2046 /* Separators between flat buttons */
2047 if ((infoPtr->dwStyle & TCS_FLATBUTTONS) && (infoPtr->exStyle & TCS_EX_FLATSEPARATORS))
2050 r1.right += (FLAT_BTN_SPACINGX -2);
2051 DrawEdge(hdc, &r1, EDGE_ETCHED, BF_RIGHT);
2054 if (iItem == infoPtr->iSelected)
2056 DrawEdge(hdc, &r, EDGE_SUNKEN, BF_SOFT|BF_RECT);
2058 OffsetRect(&r, 1, 1);
2060 else /* ! selected */
2062 DWORD state = TAB_GetItem(infoPtr, iItem)->dwState;
2064 if ((state & TCIS_BUTTONPRESSED) || (iItem == infoPtr->uFocus))
2065 DrawEdge(hdc, &r, EDGE_SUNKEN, BF_SOFT|BF_RECT);
2067 if (!(infoPtr->dwStyle & TCS_FLATBUTTONS))
2068 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_RECT);
2071 else /* !TCS_BUTTONS */
2073 /* We draw a rectangle of different sizes depending on the selection
2075 if (iItem == infoPtr->iSelected) {
2077 GetClientRect (infoPtr->hwnd, &rect);
2078 clRight = rect.right;
2079 clBottom = rect.bottom;
2086 * Erase the background. (Delay it but setup rectangle.)
2087 * This is necessary when drawing the selected item since it is larger
2088 * than the others, it might overlap with stuff already drawn by the
2093 /* Draw themed tabs - but only if they are at the top.
2094 * Windows draws even side or bottom tabs themed, with wacky results.
2095 * However, since in Wine apps may get themed that did not opt in via
2096 * a manifest avoid theming when we know the result will be wrong */
2097 if ((theme = GetWindowTheme (infoPtr->hwnd))
2098 && ((infoPtr->dwStyle & (TCS_VERTICAL | TCS_BOTTOM)) == 0))
2100 static const int partIds[8] = {
2103 TABP_TABITEMLEFTEDGE,
2104 TABP_TABITEMRIGHTEDGE,
2105 TABP_TABITEMBOTHEDGE,
2108 TABP_TOPTABITEMLEFTEDGE,
2109 TABP_TOPTABITEMRIGHTEDGE,
2110 TABP_TOPTABITEMBOTHEDGE,
2113 int stateId = TIS_NORMAL;
2115 /* selected and unselected tabs have different parts */
2116 if (iItem == infoPtr->iSelected)
2118 /* The part also differs on the position of a tab on a line.
2119 * "Visually" determining the position works well enough. */
2120 if(selectedRect.left == 0)
2122 if(selectedRect.right == clRight)
2125 if (iItem == infoPtr->iSelected)
2126 stateId = TIS_SELECTED;
2127 else if (iItem == infoPtr->iHotTracked)
2129 else if (iItem == infoPtr->uFocus)
2130 stateId = TIS_FOCUSED;
2132 /* Adjust rectangle for bottommost row */
2133 if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2136 DrawThemeBackground (theme, hdc, partIds[partIndex], stateId, &r, NULL);
2137 GetThemeBackgroundContentRect (theme, hdc, partIds[partIndex], stateId, &r, &r);
2139 else if(infoPtr->dwStyle & TCS_VERTICAL)
2141 /* These are for adjusting the drawing of a Selected tab */
2142 /* The initial values are for the normal case of non-Selected */
2143 int ZZ = 1; /* Do not stretch if selected */
2144 if (iItem == infoPtr->iSelected) {
2147 /* if leftmost draw the line longer */
2148 if(selectedRect.top == 0)
2149 fillRect.top += CONTROL_BORDER_SIZEY;
2150 /* if rightmost draw the line longer */
2151 if(selectedRect.bottom == clBottom)
2152 fillRect.bottom -= CONTROL_BORDER_SIZEY;
2155 if (infoPtr->dwStyle & TCS_BOTTOM)
2157 /* Adjust both rectangles to match native */
2160 TRACE("<right> item=%d, fill=(%s), edge=(%s)\n",
2161 iItem, wine_dbgstr_rect(&fillRect), wine_dbgstr_rect(&r));
2163 /* Clear interior */
2164 SetBkColor(hdc, bkgnd);
2165 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2167 /* Draw rectangular edge around tab */
2168 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_RIGHT|BF_TOP|BF_BOTTOM);
2170 /* Now erase the top corner and draw diagonal edge */
2171 SetBkColor(hdc, corner);
2172 r1.left = r.right - ROUND_CORNER_SIZE - 1;
2175 r1.bottom = r1.top + ROUND_CORNER_SIZE;
2176 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2178 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPLEFT);
2180 /* Now erase the bottom corner and draw diagonal edge */
2181 r1.left = r.right - ROUND_CORNER_SIZE - 1;
2182 r1.bottom = r.bottom;
2184 r1.top = r1.bottom - ROUND_CORNER_SIZE;
2185 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2187 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMLEFT);
2189 if ((iItem == infoPtr->iSelected) && (selectedRect.top == 0)) {
2193 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_TOP);
2199 TRACE("<left> item=%d, fill=(%s), edge=(%s)\n",
2200 iItem, wine_dbgstr_rect(&fillRect), wine_dbgstr_rect(&r));
2202 /* Clear interior */
2203 SetBkColor(hdc, bkgnd);
2204 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2206 /* Draw rectangular edge around tab */
2207 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_TOP|BF_BOTTOM);
2209 /* Now erase the top corner and draw diagonal edge */
2210 SetBkColor(hdc, corner);
2213 r1.right = r1.left + ROUND_CORNER_SIZE + 1;
2214 r1.bottom = r1.top + ROUND_CORNER_SIZE;
2215 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2217 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPRIGHT);
2219 /* Now erase the bottom corner and draw diagonal edge */
2221 r1.bottom = r.bottom;
2222 r1.right = r1.left + ROUND_CORNER_SIZE + 1;
2223 r1.top = r1.bottom - ROUND_CORNER_SIZE;
2224 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2226 DrawEdge(hdc, &r1, EDGE_SUNKEN, BF_DIAGONAL_ENDTOPLEFT);
2229 else /* ! TCS_VERTICAL */
2231 /* These are for adjusting the drawing of a Selected tab */
2232 /* The initial values are for the normal case of non-Selected */
2233 if (iItem == infoPtr->iSelected) {
2234 /* if leftmost draw the line longer */
2235 if(selectedRect.left == 0)
2236 fillRect.left += CONTROL_BORDER_SIZEX;
2237 /* if rightmost draw the line longer */
2238 if(selectedRect.right == clRight)
2239 fillRect.right -= CONTROL_BORDER_SIZEX;
2242 if (infoPtr->dwStyle & TCS_BOTTOM)
2244 /* Adjust both rectangles for topmost row */
2245 if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2251 TRACE("<bottom> item=%d, fill=(%s), edge=(%s)\n",
2252 iItem, wine_dbgstr_rect(&fillRect), wine_dbgstr_rect(&r));
2254 /* Clear interior */
2255 SetBkColor(hdc, bkgnd);
2256 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2258 /* Draw rectangular edge around tab */
2259 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_BOTTOM|BF_RIGHT);
2261 /* Now erase the righthand corner and draw diagonal edge */
2262 SetBkColor(hdc, corner);
2263 r1.left = r.right - ROUND_CORNER_SIZE;
2264 r1.bottom = r.bottom;
2266 r1.top = r1.bottom - ROUND_CORNER_SIZE - 1;
2267 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2269 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMLEFT);
2271 /* Now erase the lefthand corner and draw diagonal edge */
2273 r1.bottom = r.bottom;
2274 r1.right = r1.left + ROUND_CORNER_SIZE;
2275 r1.top = r1.bottom - ROUND_CORNER_SIZE - 1;
2276 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2278 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPLEFT);
2280 if (iItem == infoPtr->iSelected)
2284 if (selectedRect.left == 0)
2289 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_LEFT);
2296 /* Adjust both rectangles for bottommost row */
2297 if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2299 fillRect.bottom += 3;
2303 TRACE("<top> item=%d, fill=(%s), edge=(%s)\n",
2304 iItem, wine_dbgstr_rect(&fillRect), wine_dbgstr_rect(&r));
2306 /* Clear interior */
2307 SetBkColor(hdc, bkgnd);
2308 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2310 /* Draw rectangular edge around tab */
2311 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_TOP|BF_RIGHT);
2313 /* Now erase the righthand corner and draw diagonal edge */
2314 SetBkColor(hdc, corner);
2315 r1.left = r.right - ROUND_CORNER_SIZE;
2318 r1.bottom = r1.top + ROUND_CORNER_SIZE + 1;
2319 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2321 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMRIGHT);
2323 /* Now erase the lefthand corner and draw diagonal edge */
2326 r1.right = r1.left + ROUND_CORNER_SIZE;
2327 r1.bottom = r1.top + ROUND_CORNER_SIZE + 1;
2328 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2330 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPRIGHT);
2335 TAB_DumpItemInternal(infoPtr, iItem);
2337 /* This modifies r to be the text rectangle. */
2338 TAB_DrawItemInterior(infoPtr, hdc, iItem, &r);
2342 /******************************************************************************
2345 * This method is used to draw the raised border around the tab control
2348 static void TAB_DrawBorder(const TAB_INFO *infoPtr, HDC hdc)
2351 HTHEME theme = GetWindowTheme (infoPtr->hwnd);
2353 GetClientRect (infoPtr->hwnd, &rect);
2356 * Adjust for the style
2359 if (infoPtr->uNumItem)
2361 if ((infoPtr->dwStyle & TCS_BOTTOM) && !(infoPtr->dwStyle & TCS_VERTICAL))
2362 rect.bottom -= infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2363 else if((infoPtr->dwStyle & TCS_BOTTOM) && (infoPtr->dwStyle & TCS_VERTICAL))
2364 rect.right -= infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2365 else if(infoPtr->dwStyle & TCS_VERTICAL)
2366 rect.left += infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2367 else /* not TCS_VERTICAL and not TCS_BOTTOM */
2368 rect.top += infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2371 TRACE("border=(%s)\n", wine_dbgstr_rect(&rect));
2374 DrawThemeBackground (theme, hdc, TABP_PANE, 0, &rect, NULL);
2376 DrawEdge(hdc, &rect, EDGE_RAISED, BF_SOFT|BF_RECT);
2379 /******************************************************************************
2382 * This method repaints the tab control..
2384 static void TAB_Refresh (const TAB_INFO *infoPtr, HDC hdc)
2389 if (!infoPtr->DoRedraw)
2392 hOldFont = SelectObject (hdc, infoPtr->hFont);
2394 if (infoPtr->dwStyle & TCS_BUTTONS)
2396 for (i = 0; i < infoPtr->uNumItem; i++)
2397 TAB_DrawItem (infoPtr, hdc, i);
2401 /* Draw all the non selected item first */
2402 for (i = 0; i < infoPtr->uNumItem; i++)
2404 if (i != infoPtr->iSelected)
2405 TAB_DrawItem (infoPtr, hdc, i);
2408 /* Now, draw the border, draw it before the selected item
2409 * since the selected item overwrites part of the border. */
2410 TAB_DrawBorder (infoPtr, hdc);
2412 /* Then, draw the selected item */
2413 TAB_DrawItem (infoPtr, hdc, infoPtr->iSelected);
2416 SelectObject (hdc, hOldFont);
2419 static inline DWORD TAB_GetRowCount (const TAB_INFO *infoPtr)
2421 TRACE("(%p)\n", infoPtr);
2422 return infoPtr->uNumRows;
2425 static inline LRESULT TAB_SetRedraw (TAB_INFO *infoPtr, BOOL doRedraw)
2427 infoPtr->DoRedraw = doRedraw;
2431 /******************************************************************************
2432 * TAB_EnsureSelectionVisible
2434 * This method will make sure that the current selection is completely
2435 * visible by scrolling until it is.
2437 static void TAB_EnsureSelectionVisible(
2440 INT iSelected = infoPtr->iSelected;
2441 INT iOrigLeftmostVisible = infoPtr->leftmostVisible;
2443 /* set the items row to the bottommost row or topmost row depending on
2445 if ((infoPtr->uNumRows > 1) && !(infoPtr->dwStyle & TCS_BUTTONS))
2447 TAB_ITEM *selected = TAB_GetItem(infoPtr, iSelected);
2451 if(infoPtr->dwStyle & TCS_VERTICAL)
2452 newselected = selected->rect.left;
2454 newselected = selected->rect.top;
2456 /* the target row is always (number of rows - 1)
2457 as row 0 is furthest from the clientRect */
2458 iTargetRow = infoPtr->uNumRows - 1;
2460 if (newselected != iTargetRow)
2463 if(infoPtr->dwStyle & TCS_VERTICAL)
2465 for (i=0; i < infoPtr->uNumItem; i++)
2467 /* move everything in the row of the selected item to the iTargetRow */
2468 TAB_ITEM *item = TAB_GetItem(infoPtr, i);
2470 if (item->rect.left == newselected )
2471 item->rect.left = iTargetRow;
2474 if (item->rect.left > newselected)
2481 for (i=0; i < infoPtr->uNumItem; i++)
2483 TAB_ITEM *item = TAB_GetItem(infoPtr, i);
2485 if (item->rect.top == newselected )
2486 item->rect.top = iTargetRow;
2489 if (item->rect.top > newselected)
2494 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
2499 * Do the trivial cases first.
2501 if ( (!infoPtr->needsScrolling) ||
2502 (infoPtr->hwndUpDown==0) || (infoPtr->dwStyle & TCS_VERTICAL))
2505 if (infoPtr->leftmostVisible >= iSelected)
2507 if (iSelected >= 0) infoPtr->leftmostVisible = iSelected;
2511 TAB_ITEM *selected = TAB_GetItem(infoPtr, iSelected);
2516 /* Calculate the part of the client area that is visible */
2517 GetClientRect(infoPtr->hwnd, &r);
2520 GetClientRect(infoPtr->hwndUpDown, &r);
2523 if ((selected->rect.right -
2524 selected->rect.left) >= width )
2526 /* Special case: width of selected item is greater than visible
2529 infoPtr->leftmostVisible = iSelected;
2533 for (i = infoPtr->leftmostVisible; i < infoPtr->uNumItem; i++)
2535 if ((selected->rect.right - TAB_GetItem(infoPtr, i)->rect.left) < width)
2538 infoPtr->leftmostVisible = i;
2542 if (infoPtr->leftmostVisible != iOrigLeftmostVisible)
2543 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
2545 SendMessageW(infoPtr->hwndUpDown, UDM_SETPOS, 0,
2546 MAKELONG(infoPtr->leftmostVisible, 0));
2549 /******************************************************************************
2550 * TAB_InvalidateTabArea
2552 * This method will invalidate the portion of the control that contains the
2553 * tabs. It is called when the state of the control changes and needs
2556 static void TAB_InvalidateTabArea(const TAB_INFO *infoPtr)
2558 RECT clientRect, rInvalidate, rAdjClient;
2559 INT lastRow = infoPtr->uNumRows - 1;
2562 if (lastRow < 0) return;
2564 GetClientRect(infoPtr->hwnd, &clientRect);
2565 rInvalidate = clientRect;
2566 rAdjClient = clientRect;
2568 TAB_AdjustRect(infoPtr, 0, &rAdjClient);
2570 TAB_InternalGetItemRect(infoPtr, infoPtr->uNumItem-1 , &rect, NULL);
2571 if ((infoPtr->dwStyle & TCS_BOTTOM) && (infoPtr->dwStyle & TCS_VERTICAL))
2573 rInvalidate.left = rAdjClient.right;
2574 if (infoPtr->uNumRows == 1)
2575 rInvalidate.bottom = clientRect.top + rect.bottom + 2 * SELECTED_TAB_OFFSET;
2577 else if(infoPtr->dwStyle & TCS_VERTICAL)
2579 rInvalidate.right = rAdjClient.left;
2580 if (infoPtr->uNumRows == 1)
2581 rInvalidate.bottom = clientRect.top + rect.bottom + 2 * SELECTED_TAB_OFFSET;
2583 else if (infoPtr->dwStyle & TCS_BOTTOM)
2585 rInvalidate.top = rAdjClient.bottom;
2586 if (infoPtr->uNumRows == 1)
2587 rInvalidate.right = clientRect.left + rect.right + 2 * SELECTED_TAB_OFFSET;
2591 rInvalidate.bottom = rAdjClient.top;
2592 if (infoPtr->uNumRows == 1)
2593 rInvalidate.right = clientRect.left + rect.right + 2 * SELECTED_TAB_OFFSET;
2596 /* Punch out the updown control */
2597 if (infoPtr->needsScrolling && (rInvalidate.right > 0)) {
2599 GetClientRect(infoPtr->hwndUpDown, &r);
2600 if (rInvalidate.right > clientRect.right - r.left)
2601 rInvalidate.right = rInvalidate.right - (r.right - r.left);
2603 rInvalidate.right = clientRect.right - r.left;
2606 TRACE("invalidate (%s)\n", wine_dbgstr_rect(&rInvalidate));
2608 InvalidateRect(infoPtr->hwnd, &rInvalidate, TRUE);
2611 static inline LRESULT TAB_Paint (TAB_INFO *infoPtr, HDC hdcPaint)
2620 hdc = BeginPaint (infoPtr->hwnd, &ps);
2621 TRACE("erase %d, rect=(%s)\n", ps.fErase, wine_dbgstr_rect(&ps.rcPaint));
2624 TAB_Refresh (infoPtr, hdc);
2627 EndPaint (infoPtr->hwnd, &ps);
2633 TAB_InsertItemT (TAB_INFO *infoPtr, INT iItem, const TCITEMW *pti, BOOL bUnicode)
2638 GetClientRect (infoPtr->hwnd, &rect);
2639 TRACE("Rect: %p %s\n", infoPtr->hwnd, wine_dbgstr_rect(&rect));
2641 if (iItem < 0) return -1;
2642 if (iItem > infoPtr->uNumItem)
2643 iItem = infoPtr->uNumItem;
2645 TAB_DumpItemExternalT(pti, iItem, bUnicode);
2647 if (!(item = Alloc(sizeof(TAB_ITEM_SIZE(infoPtr))))) return FALSE;
2648 if (DPA_InsertPtr(infoPtr->items, iItem, item) == -1)
2654 if (infoPtr->uNumItem == 0)
2655 infoPtr->iSelected = 0;
2656 else if (iItem <= infoPtr->iSelected)
2657 infoPtr->iSelected++;
2659 infoPtr->uNumItem++;
2661 item->pszText = NULL;
2662 if (pti->mask & TCIF_TEXT)
2665 Str_SetPtrW (&item->pszText, pti->pszText);
2667 Str_SetPtrAtoW (&item->pszText, (LPSTR)pti->pszText);
2670 if (pti->mask & TCIF_IMAGE)
2671 item->iImage = pti->iImage;
2675 if (pti->mask & TCIF_PARAM)
2676 memcpy(item->extra, &pti->lParam, EXTRA_ITEM_SIZE(infoPtr));
2678 memset(item->extra, 0, EXTRA_ITEM_SIZE(infoPtr));
2680 TAB_SetItemBounds(infoPtr);
2681 if (infoPtr->uNumItem > 1)
2682 TAB_InvalidateTabArea(infoPtr);
2684 InvalidateRect(infoPtr->hwnd, NULL, TRUE);
2686 TRACE("[%p]: added item %d %s\n",
2687 infoPtr->hwnd, iItem, debugstr_w(item->pszText));
2689 /* If we haven't set the current focus yet, set it now. */
2690 if (infoPtr->uFocus == -1)
2691 TAB_SetCurFocus(infoPtr, iItem);
2697 TAB_SetItemSize (TAB_INFO *infoPtr, INT cx, INT cy)
2700 BOOL bNeedPaint = FALSE;
2702 lResult = MAKELONG(infoPtr->tabWidth, infoPtr->tabHeight);
2704 /* UNDOCUMENTED: If requested Width or Height is 0 this means that program wants to use auto size. */
2705 if (infoPtr->dwStyle & TCS_FIXEDWIDTH && (infoPtr->tabWidth != cx))
2707 infoPtr->tabWidth = cx;
2711 if (infoPtr->tabHeight != cy)
2713 if ((infoPtr->fHeightSet = (cy != 0)))
2714 infoPtr->tabHeight = cy;
2718 TRACE("was h=%d,w=%d, now h=%d,w=%d\n",
2719 HIWORD(lResult), LOWORD(lResult),
2720 infoPtr->tabHeight, infoPtr->tabWidth);
2724 TAB_SetItemBounds(infoPtr);
2725 RedrawWindow(infoPtr->hwnd, NULL, NULL, RDW_ERASE | RDW_INVALIDATE | RDW_UPDATENOW);
2731 static inline LRESULT TAB_SetMinTabWidth (TAB_INFO *infoPtr, INT cx)
2735 TRACE("(%p,%d)\n", infoPtr, cx);
2737 if (infoPtr->tabMinWidth < 0)
2738 oldcx = DEFAULT_MIN_TAB_WIDTH;
2740 oldcx = infoPtr->tabMinWidth;
2741 infoPtr->tabMinWidth = cx;
2742 TAB_SetItemBounds(infoPtr);
2746 static inline LRESULT
2747 TAB_HighlightItem (TAB_INFO *infoPtr, INT iItem, BOOL fHighlight)
2753 TRACE("(%p,%d,%s)\n", infoPtr, iItem, fHighlight ? "true" : "false");
2755 if (iItem < 0 || iItem >= infoPtr->uNumItem)
2758 lpState = &TAB_GetItem(infoPtr, iItem)->dwState;
2759 oldState = *lpState;
2762 *lpState |= TCIS_HIGHLIGHTED;
2764 *lpState &= ~TCIS_HIGHLIGHTED;
2766 if ((oldState != *lpState) && TAB_InternalGetItemRect (infoPtr, iItem, &r, NULL))
2767 InvalidateRect (infoPtr->hwnd, &r, TRUE);
2773 TAB_SetItemT (TAB_INFO *infoPtr, INT iItem, LPTCITEMW tabItem, BOOL bUnicode)
2777 TRACE("(%p,%d,%p,%s)\n", infoPtr, iItem, tabItem, bUnicode ? "true" : "false");
2779 if (iItem < 0 || iItem >= infoPtr->uNumItem)
2782 TAB_DumpItemExternalT(tabItem, iItem, bUnicode);
2784 wineItem = TAB_GetItem(infoPtr, iItem);
2786 if (tabItem->mask & TCIF_IMAGE)
2787 wineItem->iImage = tabItem->iImage;
2789 if (tabItem->mask & TCIF_PARAM)
2790 memcpy(wineItem->extra, &tabItem->lParam, infoPtr->cbInfo);
2792 if (tabItem->mask & TCIF_RTLREADING)
2793 FIXME("TCIF_RTLREADING\n");
2795 if (tabItem->mask & TCIF_STATE)
2796 wineItem->dwState = (wineItem->dwState & ~tabItem->dwStateMask) |
2797 ( tabItem->dwState & tabItem->dwStateMask);
2799 if (tabItem->mask & TCIF_TEXT)
2801 Free(wineItem->pszText);
2802 wineItem->pszText = NULL;
2804 Str_SetPtrW(&wineItem->pszText, tabItem->pszText);
2806 Str_SetPtrAtoW(&wineItem->pszText, (LPSTR)tabItem->pszText);
2809 /* Update and repaint tabs */
2810 TAB_SetItemBounds(infoPtr);
2811 TAB_InvalidateTabArea(infoPtr);
2816 static inline LRESULT TAB_GetItemCount (const TAB_INFO *infoPtr)
2819 return infoPtr->uNumItem;
2824 TAB_GetItemT (TAB_INFO *infoPtr, INT iItem, LPTCITEMW tabItem, BOOL bUnicode)
2828 TRACE("(%p,%d,%p,%s)\n", infoPtr, iItem, tabItem, bUnicode ? "true" : "false");
2830 if (!tabItem) return FALSE;
2832 if (iItem < 0 || iItem >= infoPtr->uNumItem)
2834 /* init requested fields */
2835 if (tabItem->mask & TCIF_IMAGE) tabItem->iImage = 0;
2836 if (tabItem->mask & TCIF_PARAM) tabItem->lParam = 0;
2837 if (tabItem->mask & TCIF_STATE) tabItem->dwState = 0;
2841 wineItem = TAB_GetItem(infoPtr, iItem);
2843 if (tabItem->mask & TCIF_IMAGE)
2844 tabItem->iImage = wineItem->iImage;
2846 if (tabItem->mask & TCIF_PARAM)
2847 memcpy(&tabItem->lParam, wineItem->extra, infoPtr->cbInfo);
2849 if (tabItem->mask & TCIF_RTLREADING)
2850 FIXME("TCIF_RTLREADING\n");
2852 if (tabItem->mask & TCIF_STATE)
2853 tabItem->dwState = wineItem->dwState & tabItem->dwStateMask;
2855 if (tabItem->mask & TCIF_TEXT)
2858 Str_GetPtrW (wineItem->pszText, tabItem->pszText, tabItem->cchTextMax);
2860 Str_GetPtrWtoA (wineItem->pszText, (LPSTR)tabItem->pszText, tabItem->cchTextMax);
2863 TAB_DumpItemExternalT(tabItem, iItem, bUnicode);
2869 static LRESULT TAB_DeleteItem (TAB_INFO *infoPtr, INT iItem)
2873 TRACE("(%p, %d)\n", infoPtr, iItem);
2875 if (iItem < 0 || iItem >= infoPtr->uNumItem) return FALSE;
2877 item = TAB_GetItem(infoPtr, iItem);
2878 Free(item->pszText);
2880 infoPtr->uNumItem--;
2881 DPA_DeletePtr(infoPtr->items, iItem);
2883 TAB_InvalidateTabArea(infoPtr);
2885 if (infoPtr->uNumItem == 0)
2887 if (infoPtr->iHotTracked >= 0)
2889 KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
2890 infoPtr->iHotTracked = -1;
2893 infoPtr->iSelected = -1;
2897 if (iItem <= infoPtr->iHotTracked)
2899 /* When tabs move left/up, the hot track item may change */
2900 FIXME("Recalc hot track\n");
2904 /* adjust the selected index */
2905 if (iItem == infoPtr->iSelected)
2906 infoPtr->iSelected = -1;
2907 else if (iItem < infoPtr->iSelected)
2908 infoPtr->iSelected--;
2910 /* reposition and repaint tabs */
2911 TAB_SetItemBounds(infoPtr);
2916 static inline LRESULT TAB_DeleteAllItems (TAB_INFO *infoPtr)
2918 TRACE("(%p)\n", infoPtr);
2919 while (infoPtr->uNumItem)
2920 TAB_DeleteItem (infoPtr, 0);
2925 static inline LRESULT TAB_GetFont (const TAB_INFO *infoPtr)
2927 TRACE("(%p) returning %p\n", infoPtr, infoPtr->hFont);
2928 return (LRESULT)infoPtr->hFont;
2931 static inline LRESULT TAB_SetFont (TAB_INFO *infoPtr, HFONT hNewFont)
2933 TRACE("(%p,%p)\n", infoPtr, hNewFont);
2935 infoPtr->hFont = hNewFont;
2937 TAB_SetItemBounds(infoPtr);
2939 TAB_InvalidateTabArea(infoPtr);
2945 static inline LRESULT TAB_GetImageList (const TAB_INFO *infoPtr)
2948 return (LRESULT)infoPtr->himl;
2951 static inline LRESULT TAB_SetImageList (TAB_INFO *infoPtr, HIMAGELIST himlNew)
2953 HIMAGELIST himlPrev = infoPtr->himl;
2954 TRACE("himl=%p\n", himlNew);
2955 infoPtr->himl = himlNew;
2956 TAB_SetItemBounds(infoPtr);
2957 InvalidateRect(infoPtr->hwnd, NULL, TRUE);
2958 return (LRESULT)himlPrev;
2961 static inline LRESULT TAB_GetUnicodeFormat (const TAB_INFO *infoPtr)
2963 TRACE("(%p)\n", infoPtr);
2964 return infoPtr->bUnicode;
2967 static inline LRESULT TAB_SetUnicodeFormat (TAB_INFO *infoPtr, BOOL bUnicode)
2969 BOOL bTemp = infoPtr->bUnicode;
2971 TRACE("(%p %d)\n", infoPtr, bUnicode);
2972 infoPtr->bUnicode = bUnicode;
2977 static inline LRESULT TAB_Size (TAB_INFO *infoPtr)
2979 /* I'm not really sure what the following code was meant to do.
2980 This is what it is doing:
2981 When WM_SIZE is sent with SIZE_RESTORED, the control
2982 gets positioned in the top left corner.
2986 UINT uPosFlags,cx,cy;
2990 parent = GetParent (hwnd);
2991 GetClientRect(parent, &parent_rect);
2994 if (GetWindowLongW(hwnd, GWL_STYLE) & CCS_NORESIZE)
2995 uPosFlags |= (SWP_NOSIZE | SWP_NOMOVE);
2997 SetWindowPos (hwnd, 0, parent_rect.left, parent_rect.top,
2998 cx, cy, uPosFlags | SWP_NOZORDER);
3000 FIXME("WM_SIZE flag %x %lx not handled\n", wParam, lParam);
3003 /* Recompute the size/position of the tabs. */
3004 TAB_SetItemBounds (infoPtr);
3006 /* Force a repaint of the control. */
3007 InvalidateRect(infoPtr->hwnd, NULL, TRUE);
3013 static LRESULT TAB_Create (HWND hwnd, LPARAM lParam)
3016 TEXTMETRICW fontMetrics;
3021 infoPtr = Alloc (sizeof(TAB_INFO));
3023 SetWindowLongPtrW(hwnd, 0, (DWORD_PTR)infoPtr);
3025 infoPtr->hwnd = hwnd;
3026 infoPtr->hwndNotify = ((LPCREATESTRUCTW)lParam)->hwndParent;
3027 infoPtr->uNumItem = 0;
3028 infoPtr->uNumRows = 0;
3029 infoPtr->uHItemPadding = 6;
3030 infoPtr->uVItemPadding = 3;
3031 infoPtr->uHItemPadding_s = 6;
3032 infoPtr->uVItemPadding_s = 3;
3034 infoPtr->items = DPA_Create(8);
3035 infoPtr->hcurArrow = LoadCursorW (0, (LPWSTR)IDC_ARROW);
3036 infoPtr->iSelected = -1;
3037 infoPtr->iHotTracked = -1;
3038 infoPtr->uFocus = -1;
3039 infoPtr->hwndToolTip = 0;
3040 infoPtr->DoRedraw = TRUE;
3041 infoPtr->needsScrolling = FALSE;
3042 infoPtr->hwndUpDown = 0;
3043 infoPtr->leftmostVisible = 0;
3044 infoPtr->fHeightSet = FALSE;
3045 infoPtr->bUnicode = IsWindowUnicode (hwnd);
3046 infoPtr->cbInfo = sizeof(LPARAM);
3048 TRACE("Created tab control, hwnd [%p]\n", hwnd);
3050 /* The tab control always has the WS_CLIPSIBLINGS style. Even
3051 if you don't specify it in CreateWindow. This is necessary in
3052 order for paint to work correctly. This follows windows behaviour. */
3053 dwStyle = GetWindowLongW(hwnd, GWL_STYLE);
3054 SetWindowLongW(hwnd, GWL_STYLE, dwStyle|WS_CLIPSIBLINGS);
3056 infoPtr->dwStyle = dwStyle | WS_CLIPSIBLINGS;
3057 infoPtr->exStyle = (dwStyle & TCS_FLATBUTTONS) ? TCS_EX_FLATSEPARATORS : 0;
3059 if (infoPtr->dwStyle & TCS_TOOLTIPS) {
3060 /* Create tooltip control */
3061 infoPtr->hwndToolTip =
3062 CreateWindowExW (0, TOOLTIPS_CLASSW, NULL, WS_POPUP,
3063 CW_USEDEFAULT, CW_USEDEFAULT,
3064 CW_USEDEFAULT, CW_USEDEFAULT,
3067 /* Send NM_TOOLTIPSCREATED notification */
3068 if (infoPtr->hwndToolTip) {
3069 NMTOOLTIPSCREATED nmttc;
3071 nmttc.hdr.hwndFrom = hwnd;
3072 nmttc.hdr.idFrom = GetWindowLongPtrW(hwnd, GWLP_ID);
3073 nmttc.hdr.code = NM_TOOLTIPSCREATED;
3074 nmttc.hwndToolTips = infoPtr->hwndToolTip;
3076 SendMessageW (infoPtr->hwndNotify, WM_NOTIFY,
3077 GetWindowLongPtrW(hwnd, GWLP_ID), (LPARAM)&nmttc);
3081 OpenThemeData (infoPtr->hwnd, themeClass);
3084 * We need to get text information so we need a DC and we need to select
3088 hOldFont = SelectObject (hdc, GetStockObject (SYSTEM_FONT));
3090 /* Use the system font to determine the initial height of a tab. */
3091 GetTextMetricsW(hdc, &fontMetrics);
3094 * Make sure there is enough space for the letters + growing the
3095 * selected item + extra space for the selected item.
3097 infoPtr->tabHeight = fontMetrics.tmHeight + SELECTED_TAB_OFFSET +
3098 ((infoPtr->dwStyle & TCS_BUTTONS) ? 2 : 1) *
3099 infoPtr->uVItemPadding;
3101 /* Initialize the width of a tab. */
3102 if (infoPtr->dwStyle & TCS_FIXEDWIDTH)
3103 infoPtr->tabWidth = GetDeviceCaps(hdc, LOGPIXELSX);
3105 infoPtr->tabMinWidth = -1;
3107 TRACE("tabH=%d, tabW=%d\n", infoPtr->tabHeight, infoPtr->tabWidth);
3109 SelectObject (hdc, hOldFont);
3110 ReleaseDC(hwnd, hdc);
3116 TAB_Destroy (TAB_INFO *infoPtr)
3120 SetWindowLongPtrW(infoPtr->hwnd, 0, 0);
3122 for (iItem = infoPtr->uNumItem - 1; iItem >= 0; iItem--)
3124 TAB_ITEM *tab = TAB_GetItem(infoPtr, iItem);
3126 DPA_DeletePtr(infoPtr->items, iItem);
3127 infoPtr->uNumItem--;
3132 DPA_Destroy(infoPtr->items);
3133 infoPtr->items = NULL;
3135 if (infoPtr->hwndToolTip)
3136 DestroyWindow (infoPtr->hwndToolTip);
3138 if (infoPtr->hwndUpDown)
3139 DestroyWindow(infoPtr->hwndUpDown);
3141 if (infoPtr->iHotTracked >= 0)
3142 KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
3144 CloseThemeData (GetWindowTheme (infoPtr->hwnd));
3150 /* update theme after a WM_THEMECHANGED message */
3151 static LRESULT theme_changed(const TAB_INFO *infoPtr)
3153 HTHEME theme = GetWindowTheme (infoPtr->hwnd);
3154 CloseThemeData (theme);
3155 OpenThemeData (infoPtr->hwnd, themeClass);
3159 static LRESULT TAB_NCCalcSize(WPARAM wParam)
3163 return WVR_ALIGNTOP;
3166 static inline LRESULT
3167 TAB_SetItemExtra (TAB_INFO *infoPtr, INT cbInfo)
3169 TRACE("(%p %d)\n", infoPtr, cbInfo);
3171 if (cbInfo < 0 || infoPtr->uNumItem) return FALSE;
3173 infoPtr->cbInfo = cbInfo;
3177 static LRESULT TAB_RemoveImage (TAB_INFO *infoPtr, INT image)
3179 TRACE("%p %d\n", infoPtr, image);
3181 if (ImageList_Remove (infoPtr->himl, image))
3186 /* shift indices, repaint items if needed */
3187 for (i = 0; i < infoPtr->uNumItem; i++)
3189 idx = &TAB_GetItem(infoPtr, i)->iImage;
3198 if (TAB_InternalGetItemRect (infoPtr, i, &r, NULL))
3199 InvalidateRect (infoPtr->hwnd, &r, TRUE);
3208 TAB_SetExtendedStyle (TAB_INFO *infoPtr, DWORD exMask, DWORD exStyle)
3210 DWORD prevstyle = infoPtr->exStyle;
3212 /* zero mask means all styles */
3213 if (exMask == 0) exMask = ~0;
3215 if (exMask & TCS_EX_REGISTERDROP)
3217 FIXME("TCS_EX_REGISTERDROP style unimplemented\n");
3218 exMask &= ~TCS_EX_REGISTERDROP;
3219 exStyle &= ~TCS_EX_REGISTERDROP;
3222 if (exMask & TCS_EX_FLATSEPARATORS)
3224 if ((prevstyle ^ exStyle) & TCS_EX_FLATSEPARATORS)
3226 infoPtr->exStyle ^= TCS_EX_FLATSEPARATORS;
3227 TAB_InvalidateTabArea(infoPtr);
3234 static inline LRESULT
3235 TAB_GetExtendedStyle (const TAB_INFO *infoPtr)
3237 return infoPtr->exStyle;
3241 TAB_DeselectAll (TAB_INFO *infoPtr, BOOL excludesel)
3244 INT i, selected = infoPtr->iSelected;
3246 TRACE("(%p, %d)\n", infoPtr, excludesel);
3248 if (!(infoPtr->dwStyle & TCS_BUTTONS))
3251 for (i = 0; i < infoPtr->uNumItem; i++)
3253 if ((TAB_GetItem(infoPtr, i)->dwState & TCIS_BUTTONPRESSED) &&
3256 TAB_GetItem(infoPtr, i)->dwState &= ~TCIS_BUTTONPRESSED;
3261 if (!excludesel && (selected != -1))
3263 TAB_GetItem(infoPtr, selected)->dwState &= ~TCIS_BUTTONPRESSED;
3264 infoPtr->iSelected = -1;
3269 TAB_InvalidateTabArea (infoPtr);
3276 * Processes WM_STYLECHANGED messages.
3279 * [I] infoPtr : valid pointer to the tab data structure
3280 * [I] wStyleType : window style type (normal or extended)
3281 * [I] lpss : window style information
3286 static INT TAB_StyleChanged(TAB_INFO *infoPtr, WPARAM wStyleType,
3287 const STYLESTRUCT *lpss)
3289 TRACE("(styletype=%lx, styleOld=0x%08x, styleNew=0x%08x)\n",
3290 wStyleType, lpss->styleOld, lpss->styleNew);
3292 if (wStyleType != GWL_STYLE) return 0;
3294 infoPtr->dwStyle = lpss->styleNew;
3296 TAB_SetItemBounds (infoPtr);
3297 InvalidateRect(infoPtr->hwnd, NULL, TRUE);
3302 static LRESULT WINAPI
3303 TAB_WindowProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
3305 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
3307 TRACE("hwnd=%p msg=%x wParam=%lx lParam=%lx\n", hwnd, uMsg, wParam, lParam);
3308 if (!infoPtr && (uMsg != WM_CREATE))
3309 return DefWindowProcW (hwnd, uMsg, wParam, lParam);
3313 case TCM_GETIMAGELIST:
3314 return TAB_GetImageList (infoPtr);
3316 case TCM_SETIMAGELIST:
3317 return TAB_SetImageList (infoPtr, (HIMAGELIST)lParam);
3319 case TCM_GETITEMCOUNT:
3320 return TAB_GetItemCount (infoPtr);
3324 return TAB_GetItemT (infoPtr, (INT)wParam, (LPTCITEMW)lParam, uMsg == TCM_GETITEMW);
3328 return TAB_SetItemT (infoPtr, (INT)wParam, (LPTCITEMW)lParam, uMsg == TCM_SETITEMW);
3330 case TCM_DELETEITEM:
3331 return TAB_DeleteItem (infoPtr, (INT)wParam);
3333 case TCM_DELETEALLITEMS:
3334 return TAB_DeleteAllItems (infoPtr);
3336 case TCM_GETITEMRECT:
3337 return TAB_GetItemRect (infoPtr, (INT)wParam, (LPRECT)lParam);
3340 return TAB_GetCurSel (infoPtr);
3343 return TAB_HitTest (infoPtr, (LPTCHITTESTINFO)lParam);
3346 return TAB_SetCurSel (infoPtr, (INT)wParam);
3348 case TCM_INSERTITEMA:
3349 case TCM_INSERTITEMW:
3350 return TAB_InsertItemT (infoPtr, (INT)wParam, (TCITEMW*)lParam, uMsg == TCM_INSERTITEMW);
3352 case TCM_SETITEMEXTRA:
3353 return TAB_SetItemExtra (infoPtr, (INT)wParam);
3355 case TCM_ADJUSTRECT:
3356 return TAB_AdjustRect (infoPtr, (BOOL)wParam, (LPRECT)lParam);
3358 case TCM_SETITEMSIZE:
3359 return TAB_SetItemSize (infoPtr, (INT)LOWORD(lParam), (INT)HIWORD(lParam));
3361 case TCM_REMOVEIMAGE:
3362 return TAB_RemoveImage (infoPtr, (INT)wParam);
3364 case TCM_SETPADDING:
3365 return TAB_SetPadding (infoPtr, lParam);
3367 case TCM_GETROWCOUNT:
3368 return TAB_GetRowCount(infoPtr);
3370 case TCM_GETUNICODEFORMAT:
3371 return TAB_GetUnicodeFormat (infoPtr);
3373 case TCM_SETUNICODEFORMAT:
3374 return TAB_SetUnicodeFormat (infoPtr, (BOOL)wParam);
3376 case TCM_HIGHLIGHTITEM:
3377 return TAB_HighlightItem (infoPtr, (INT)wParam, (BOOL)LOWORD(lParam));
3379 case TCM_GETTOOLTIPS:
3380 return TAB_GetToolTips (infoPtr);
3382 case TCM_SETTOOLTIPS:
3383 return TAB_SetToolTips (infoPtr, (HWND)wParam);
3385 case TCM_GETCURFOCUS:
3386 return TAB_GetCurFocus (infoPtr);
3388 case TCM_SETCURFOCUS:
3389 return TAB_SetCurFocus (infoPtr, (INT)wParam);
3391 case TCM_SETMINTABWIDTH:
3392 return TAB_SetMinTabWidth(infoPtr, (INT)lParam);
3394 case TCM_DESELECTALL:
3395 return TAB_DeselectAll (infoPtr, (BOOL)wParam);
3397 case TCM_GETEXTENDEDSTYLE:
3398 return TAB_GetExtendedStyle (infoPtr);
3400 case TCM_SETEXTENDEDSTYLE:
3401 return TAB_SetExtendedStyle (infoPtr, wParam, lParam);
3404 return TAB_GetFont (infoPtr);
3407 return TAB_SetFont (infoPtr, (HFONT)wParam);
3410 return TAB_Create (hwnd, lParam);
3413 return TAB_Destroy (infoPtr);
3416 return DLGC_WANTARROWS | DLGC_WANTCHARS;
3418 case WM_LBUTTONDOWN:
3419 return TAB_LButtonDown (infoPtr, wParam, lParam);
3422 return TAB_LButtonUp (infoPtr);
3425 return SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, wParam, lParam);
3427 case WM_RBUTTONDOWN:
3428 return TAB_RButtonDown (infoPtr);
3431 return TAB_MouseMove (infoPtr, wParam, lParam);
3433 case WM_PRINTCLIENT:
3435 return TAB_Paint (infoPtr, (HDC)wParam);
3438 return TAB_Size (infoPtr);
3441 return TAB_SetRedraw (infoPtr, (BOOL)wParam);
3444 return TAB_OnHScroll(infoPtr, (int)LOWORD(wParam), (int)HIWORD(wParam));
3446 case WM_STYLECHANGED:
3447 return TAB_StyleChanged(infoPtr, wParam, (LPSTYLESTRUCT)lParam);
3449 case WM_SYSCOLORCHANGE:
3450 COMCTL32_RefreshSysColors();
3453 case WM_THEMECHANGED:
3454 return theme_changed (infoPtr);
3457 TAB_KillFocus(infoPtr);
3459 TAB_FocusChanging(infoPtr);
3460 break; /* Don't disturb normal focus behavior */
3463 return TAB_KeyDown(infoPtr, wParam, lParam);
3466 return TAB_NCHitTest(infoPtr, lParam);
3469 return TAB_NCCalcSize(wParam);
3472 if (uMsg >= WM_USER && uMsg < WM_APP && !COMCTL32_IsReflectedMessage(uMsg))
3473 WARN("unknown msg %04x wp=%08lx lp=%08lx\n",
3474 uMsg, wParam, lParam);
3477 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
3486 ZeroMemory (&wndClass, sizeof(WNDCLASSW));
3487 wndClass.style = CS_GLOBALCLASS | CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
3488 wndClass.lpfnWndProc = TAB_WindowProc;
3489 wndClass.cbClsExtra = 0;
3490 wndClass.cbWndExtra = sizeof(TAB_INFO *);
3491 wndClass.hCursor = LoadCursorW (0, (LPWSTR)IDC_ARROW);
3492 wndClass.hbrBackground = (HBRUSH)(COLOR_BTNFACE+1);
3493 wndClass.lpszClassName = WC_TABCONTROLW;
3495 RegisterClassW (&wndClass);
3500 TAB_Unregister (void)
3502 UnregisterClassW (WC_TABCONTROLW, NULL);