comctl32: Add support for MSAA events on tab controls.
[wine.git] / dlls / comctl32 / tab.c
blob13abbd8078d4f3258281cce92feb2f8416747a67
1 /*
2 * Tab control
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
23 * NOTES
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.
32 * TODO:
34 * Styles:
35 * TCS_MULTISELECT - implement for VK_SPACE selection
36 * TCS_RIGHT
37 * TCS_RIGHTJUSTIFY
38 * TCS_SCROLLOPPOSITE
39 * TCS_SINGLELINE
40 * TCIF_RTLREADING
42 * Extended Styles:
43 * TCS_EX_REGISTERDROP
45 * Notifications:
46 * NM_RELEASEDCAPTURE
47 * TCN_FOCUSCHANGE
48 * TCN_GETOBJECT
50 * Macros:
51 * TabCtrl_AdjustRect
55 #include <assert.h>
56 #include <stdarg.h>
57 #include <string.h>
59 #include "windef.h"
60 #include "winbase.h"
61 #include "wingdi.h"
62 #include "winuser.h"
63 #include "winnls.h"
64 #include "commctrl.h"
65 #include "comctl32.h"
66 #include "uxtheme.h"
67 #include "vssym32.h"
68 #include "wine/debug.h"
69 #include <math.h>
71 WINE_DEFAULT_DEBUG_CHANNEL(tab);
73 typedef struct
75 DWORD dwState;
76 LPWSTR pszText;
77 INT iImage;
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 */
85 } TAB_ITEM;
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)])
92 typedef struct
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 */
127 } TAB_INFO;
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[] = L"Tab";
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 /******************************************************************************
164 * Prototypes
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*);
172 static BOOL
173 TAB_SendSimpleNotify (const TAB_INFO *infoPtr, UINT code)
175 NMHDR nmhdr;
177 nmhdr.hwndFrom = infoPtr->hwnd;
178 nmhdr.idFrom = GetWindowLongPtrW(infoPtr->hwnd, GWLP_ID);
179 nmhdr.code = code;
181 return (BOOL) SendMessageW (infoPtr->hwndNotify, WM_NOTIFY,
182 nmhdr.idFrom, (LPARAM) &nmhdr);
185 static void
186 TAB_RelayEvent (HWND hwndTip, HWND hwndMsg, UINT uMsg,
187 WPARAM wParam, LPARAM lParam)
189 MSG msg;
191 msg.hwnd = hwndMsg;
192 msg.message = uMsg;
193 msg.wParam = wParam;
194 msg.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);
202 static void
203 TAB_DumpItemExternalT(const TCITEMW *pti, UINT iItem, BOOL isW)
205 if (TRACE_ON(tab)) {
206 TRACE("external tab %d, mask=0x%08x, dwState %#lx, dwStateMask %#lx, cchTextMax=0x%08x\n",
207 iItem, pti->mask, pti->dwState, pti->dwStateMask, pti->cchTextMax);
208 TRACE("external tab %d, iImage=%d, lParam %Ix, pszTextW=%s\n",
209 iItem, pti->iImage, pti->lParam, isW ? debugstr_w(pti->pszText) : debugstr_a((LPSTR)pti->pszText));
213 static void
214 TAB_DumpItemInternal(const TAB_INFO *infoPtr, UINT iItem)
216 if (TRACE_ON(tab)) {
217 TAB_ITEM *ti = TAB_GetItem(infoPtr, iItem);
219 TRACE("tab %d, dwState %#lx, pszText %s, iImage %d\n", iItem, ti->dwState, debugstr_w(ti->pszText), ti->iImage);
220 TRACE("tab %d, rect.left=%ld, rect.top(row)=%ld\n", iItem, ti->rect.left, ti->rect.top);
224 /* RETURNS
225 * the index of the selected tab, or -1 if no tab is selected. */
226 static inline LRESULT TAB_GetCurSel (const TAB_INFO *infoPtr)
228 TRACE("(%p)\n", infoPtr);
229 return infoPtr->iSelected;
232 /* RETURNS
233 * the index of the tab item that has the focus. */
234 static inline LRESULT
235 TAB_GetCurFocus (const TAB_INFO *infoPtr)
237 TRACE("(%p)\n", infoPtr);
238 return infoPtr->uFocus;
241 static inline LRESULT TAB_GetToolTips (const TAB_INFO *infoPtr)
243 TRACE("(%p)\n", infoPtr);
244 return (LRESULT)infoPtr->hwndToolTip;
247 static inline LRESULT TAB_SetCurSel (TAB_INFO *infoPtr, INT iItem)
249 INT prevItem = infoPtr->iSelected;
251 TRACE("(%p %d)\n", infoPtr, iItem);
253 if (iItem >= (INT)infoPtr->uNumItem)
254 return -1;
256 if (prevItem != iItem) {
257 if (prevItem != -1)
258 TAB_GetItem(infoPtr, prevItem)->dwState &= ~TCIS_BUTTONPRESSED;
260 if (iItem >= 0)
262 TAB_GetItem(infoPtr, iItem)->dwState |= TCIS_BUTTONPRESSED;
263 infoPtr->iSelected = iItem;
264 infoPtr->uFocus = iItem;
266 else
268 infoPtr->iSelected = -1;
269 infoPtr->uFocus = -1;
272 TAB_EnsureSelectionVisible(infoPtr);
273 TAB_InvalidateTabArea(infoPtr);
274 NotifyWinEvent(EVENT_OBJECT_SELECTION, infoPtr->hwnd, OBJID_CLIENT, infoPtr->iSelected + 1);
277 return prevItem;
280 static LRESULT TAB_SetCurFocus (TAB_INFO *infoPtr, INT iItem)
282 TRACE("(%p %d)\n", infoPtr, iItem);
284 if (iItem < 0) {
285 infoPtr->uFocus = -1;
286 if (infoPtr->iSelected != -1) {
287 infoPtr->iSelected = -1;
288 TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGE);
289 TAB_InvalidateTabArea(infoPtr);
290 if (!(infoPtr->dwStyle & TCS_BUTTONS))
291 NotifyWinEvent(EVENT_OBJECT_SELECTION, infoPtr->hwnd, OBJID_CLIENT, 0);
294 else if (iItem < infoPtr->uNumItem) {
295 if (infoPtr->dwStyle & TCS_BUTTONS) {
296 /* set focus to new item, leave selection as is */
297 if (infoPtr->uFocus != iItem) {
298 INT prev_focus = infoPtr->uFocus;
299 RECT r;
301 infoPtr->uFocus = iItem;
303 if (prev_focus != infoPtr->iSelected) {
304 if (TAB_InternalGetItemRect(infoPtr, prev_focus, &r, NULL))
305 InvalidateRect(infoPtr->hwnd, &r, FALSE);
308 if (TAB_InternalGetItemRect(infoPtr, iItem, &r, NULL))
309 InvalidateRect(infoPtr->hwnd, &r, FALSE);
311 TAB_SendSimpleNotify(infoPtr, TCN_FOCUSCHANGE);
312 NotifyWinEvent(EVENT_OBJECT_FOCUS, infoPtr->hwnd, OBJID_CLIENT, iItem + 1);
314 } else {
315 INT oldFocus = infoPtr->uFocus;
316 if (infoPtr->iSelected != iItem || oldFocus == -1 ) {
317 infoPtr->uFocus = iItem;
318 if (oldFocus != -1) {
319 if (!TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGING)) {
320 infoPtr->iSelected = iItem;
321 TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGE);
323 else
324 infoPtr->iSelected = iItem;
325 TAB_EnsureSelectionVisible(infoPtr);
326 TAB_InvalidateTabArea(infoPtr);
327 NotifyWinEvent(EVENT_OBJECT_SELECTION, infoPtr->hwnd, OBJID_CLIENT, iItem + 1);
332 return 0;
335 static inline LRESULT
336 TAB_SetToolTips (TAB_INFO *infoPtr, HWND hwndToolTip)
338 TRACE("%p %p\n", infoPtr, hwndToolTip);
339 infoPtr->hwndToolTip = hwndToolTip;
340 return 0;
343 static inline LRESULT
344 TAB_SetPadding (TAB_INFO *infoPtr, LPARAM lParam)
346 TRACE("(%p %d %d)\n", infoPtr, LOWORD(lParam), HIWORD(lParam));
347 infoPtr->uHItemPadding_s = LOWORD(lParam);
348 infoPtr->uVItemPadding_s = HIWORD(lParam);
350 return 0;
353 /******************************************************************************
354 * TAB_InternalGetItemRect
356 * This method will calculate the rectangle representing a given tab item in
357 * client coordinates. This method takes scrolling into account.
359 * This method returns TRUE if the item is visible in the window and FALSE
360 * if it is completely outside the client area.
362 static BOOL TAB_InternalGetItemRect(
363 const TAB_INFO* infoPtr,
364 INT itemIndex,
365 RECT* itemRect,
366 RECT* selectedRect)
368 RECT tmpItemRect,clientRect;
370 /* Perform a sanity check and a trivial visibility check. */
371 if ( (infoPtr->uNumItem <= 0) ||
372 (itemIndex >= infoPtr->uNumItem) ||
373 (!(((infoPtr->dwStyle & TCS_MULTILINE) || (infoPtr->dwStyle & TCS_VERTICAL))) &&
374 (itemIndex < infoPtr->leftmostVisible)))
376 TRACE("Not Visible\n");
377 SetRect(itemRect, 0, 0, 0, infoPtr->tabHeight);
378 SetRectEmpty(selectedRect);
379 return FALSE;
383 * Avoid special cases in this procedure by assigning the "out"
384 * parameters if the caller didn't supply them
386 if (itemRect == NULL)
387 itemRect = &tmpItemRect;
389 /* Retrieve the unmodified item rect. */
390 *itemRect = TAB_GetItem(infoPtr,itemIndex)->rect;
392 /* calculate the times bottom and top based on the row */
393 GetClientRect(infoPtr->hwnd, &clientRect);
395 if ((infoPtr->dwStyle & TCS_BOTTOM) && (infoPtr->dwStyle & TCS_VERTICAL))
397 itemRect->right = clientRect.right - SELECTED_TAB_OFFSET - itemRect->left * infoPtr->tabHeight -
398 ((infoPtr->dwStyle & TCS_BUTTONS) ? itemRect->left * BUTTON_SPACINGX : 0);
399 itemRect->left = itemRect->right - infoPtr->tabHeight;
401 else if (infoPtr->dwStyle & TCS_VERTICAL)
403 itemRect->left = clientRect.left + SELECTED_TAB_OFFSET + itemRect->left * infoPtr->tabHeight +
404 ((infoPtr->dwStyle & TCS_BUTTONS) ? itemRect->left * BUTTON_SPACINGX : 0);
405 itemRect->right = itemRect->left + infoPtr->tabHeight;
407 else if (infoPtr->dwStyle & TCS_BOTTOM)
409 itemRect->bottom = clientRect.bottom - itemRect->top * infoPtr->tabHeight -
410 ((infoPtr->dwStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : SELECTED_TAB_OFFSET);
411 itemRect->top = itemRect->bottom - infoPtr->tabHeight;
413 else /* not TCS_BOTTOM and not TCS_VERTICAL */
415 itemRect->top = clientRect.top + itemRect->top * infoPtr->tabHeight +
416 ((infoPtr->dwStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : SELECTED_TAB_OFFSET);
417 itemRect->bottom = itemRect->top + infoPtr->tabHeight;
421 * "scroll" it to make sure the item at the very left of the
422 * tab control is the leftmost visible tab.
424 if(infoPtr->dwStyle & TCS_VERTICAL)
426 OffsetRect(itemRect,
428 -TAB_GetItem(infoPtr, infoPtr->leftmostVisible)->rect.top);
431 * Move the rectangle so the first item is slightly offset from
432 * the bottom of the tab control.
434 OffsetRect(itemRect,
436 SELECTED_TAB_OFFSET);
438 } else
440 OffsetRect(itemRect,
441 -TAB_GetItem(infoPtr, infoPtr->leftmostVisible)->rect.left,
445 * Move the rectangle so the first item is slightly offset from
446 * the left of the tab control.
448 OffsetRect(itemRect,
449 SELECTED_TAB_OFFSET,
452 TRACE("item %d tab h=%d, rect=(%s)\n",
453 itemIndex, infoPtr->tabHeight, wine_dbgstr_rect(itemRect));
455 /* Now, calculate the position of the item as if it were selected. */
456 if (selectedRect!=NULL)
458 *selectedRect = *itemRect;
460 /* The rectangle of a selected item is a bit wider. */
461 if(infoPtr->dwStyle & TCS_VERTICAL)
462 InflateRect(selectedRect, 0, SELECTED_TAB_OFFSET);
463 else
464 InflateRect(selectedRect, SELECTED_TAB_OFFSET, 0);
466 /* If it also a bit higher. */
467 if ((infoPtr->dwStyle & TCS_BOTTOM) && (infoPtr->dwStyle & TCS_VERTICAL))
469 selectedRect->left -= 2; /* the border is thicker on the right */
470 selectedRect->right += SELECTED_TAB_OFFSET;
472 else if (infoPtr->dwStyle & TCS_VERTICAL)
474 selectedRect->left -= SELECTED_TAB_OFFSET;
475 selectedRect->right += 1;
477 else if (infoPtr->dwStyle & TCS_BOTTOM)
479 selectedRect->bottom += SELECTED_TAB_OFFSET;
481 else /* not TCS_BOTTOM and not TCS_VERTICAL */
483 selectedRect->top -= SELECTED_TAB_OFFSET;
484 selectedRect->bottom -= 1;
488 /* Check for visibility */
489 if (infoPtr->dwStyle & TCS_VERTICAL)
490 return (itemRect->top < clientRect.bottom) && (itemRect->bottom > clientRect.top);
491 else
492 return (itemRect->left < clientRect.right) && (itemRect->right > clientRect.left);
495 static inline BOOL
496 TAB_GetItemRect(const TAB_INFO *infoPtr, INT item, RECT *rect)
498 TRACE("(%p, %d, %p)\n", infoPtr, item, rect);
499 return TAB_InternalGetItemRect(infoPtr, item, rect, NULL);
502 /******************************************************************************
503 * TAB_KeyDown
505 * This method is called to handle keyboard input
507 static LRESULT TAB_KeyDown(TAB_INFO* infoPtr, WPARAM keyCode, LPARAM lParam)
509 INT newItem = -1;
510 NMTCKEYDOWN nm;
512 /* TCN_KEYDOWN notification sent always */
513 nm.hdr.hwndFrom = infoPtr->hwnd;
514 nm.hdr.idFrom = GetWindowLongPtrW(infoPtr->hwnd, GWLP_ID);
515 nm.hdr.code = TCN_KEYDOWN;
516 nm.wVKey = keyCode;
517 nm.flags = lParam;
518 SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, nm.hdr.idFrom, (LPARAM)&nm);
520 switch (keyCode)
522 case VK_LEFT:
523 newItem = infoPtr->uFocus - 1;
524 break;
525 case VK_RIGHT:
526 newItem = infoPtr->uFocus + 1;
527 break;
530 /* If we changed to a valid item, change focused item */
531 if (newItem >= 0 && newItem < infoPtr->uNumItem && infoPtr->uFocus != newItem)
532 TAB_SetCurFocus(infoPtr, newItem);
534 return 0;
538 * WM_KILLFOCUS handler
540 static void TAB_KillFocus(TAB_INFO *infoPtr)
542 /* clear current focused item back to selected for TCS_BUTTONS */
543 if ((infoPtr->dwStyle & TCS_BUTTONS) && (infoPtr->uFocus != infoPtr->iSelected))
545 RECT r;
547 if (TAB_InternalGetItemRect(infoPtr, infoPtr->uFocus, &r, NULL))
548 InvalidateRect(infoPtr->hwnd, &r, FALSE);
550 infoPtr->uFocus = infoPtr->iSelected;
554 /******************************************************************************
555 * TAB_FocusChanging
557 * This method is called whenever the focus goes in or out of this control
558 * it is used to update the visual state of the control.
560 static void TAB_FocusChanging(const TAB_INFO *infoPtr)
562 RECT selectedRect;
563 BOOL isVisible;
566 * Get the rectangle for the item.
568 isVisible = TAB_InternalGetItemRect(infoPtr,
569 infoPtr->uFocus,
570 NULL,
571 &selectedRect);
574 * If the rectangle is not completely invisible, invalidate that
575 * portion of the window.
577 if (isVisible)
579 TRACE("invalidate (%s)\n", wine_dbgstr_rect(&selectedRect));
580 InvalidateRect(infoPtr->hwnd, &selectedRect, TRUE);
584 static INT TAB_InternalHitTest (const TAB_INFO *infoPtr, POINT pt, UINT *flags)
586 RECT rect;
587 INT iCount;
589 for (iCount = 0; iCount < infoPtr->uNumItem; iCount++)
591 TAB_InternalGetItemRect(infoPtr, iCount, &rect, NULL);
593 if (PtInRect(&rect, pt))
595 *flags = TCHT_ONITEM;
596 return iCount;
600 *flags = TCHT_NOWHERE;
601 return -1;
604 static inline LRESULT
605 TAB_HitTest (const TAB_INFO *infoPtr, LPTCHITTESTINFO lptest)
607 TRACE("(%p, %p)\n", infoPtr, lptest);
608 return TAB_InternalHitTest (infoPtr, lptest->pt, &lptest->flags);
611 /******************************************************************************
612 * TAB_NCHitTest
614 * Napster v2b5 has a tab control for its main navigation which has a client
615 * area that covers the whole area of the dialog pages.
616 * That's why it receives all msgs for that area and the underlying dialog ctrls
617 * are dead.
618 * So I decided that we should handle WM_NCHITTEST here and return
619 * HTTRANSPARENT if we don't hit the tab control buttons.
620 * FIXME: WM_NCHITTEST handling correct ? Fix it if you know that Windows
621 * doesn't do it that way. Maybe depends on tab control styles ?
623 static inline LRESULT
624 TAB_NCHitTest (const TAB_INFO *infoPtr, LPARAM lParam)
626 POINT pt;
627 UINT dummyflag;
629 pt.x = (short)LOWORD(lParam);
630 pt.y = (short)HIWORD(lParam);
631 ScreenToClient(infoPtr->hwnd, &pt);
633 if (TAB_InternalHitTest(infoPtr, pt, &dummyflag) == -1)
634 return HTTRANSPARENT;
635 else
636 return HTCLIENT;
639 static LRESULT
640 TAB_LButtonDown (TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
642 POINT pt;
643 INT newItem;
644 UINT dummy;
646 if (infoPtr->hwndToolTip)
647 TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
648 WM_LBUTTONDOWN, wParam, lParam);
650 if (!(infoPtr->dwStyle & TCS_FOCUSNEVER)) {
651 SetFocus (infoPtr->hwnd);
654 if (infoPtr->hwndToolTip)
655 TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
656 WM_LBUTTONDOWN, wParam, lParam);
658 pt.x = (short)LOWORD(lParam);
659 pt.y = (short)HIWORD(lParam);
661 newItem = TAB_InternalHitTest (infoPtr, pt, &dummy);
663 TRACE("On Tab, item %d\n", newItem);
665 if ((newItem != -1) && (infoPtr->iSelected != newItem))
667 if ((infoPtr->dwStyle & TCS_BUTTONS) && (infoPtr->dwStyle & TCS_MULTISELECT) &&
668 (wParam & MK_CONTROL))
670 RECT r;
672 /* toggle multiselection */
673 TAB_GetItem(infoPtr, newItem)->dwState ^= TCIS_BUTTONPRESSED;
674 if (TAB_InternalGetItemRect (infoPtr, newItem, &r, NULL))
675 InvalidateRect (infoPtr->hwnd, &r, TRUE);
677 else
679 INT i;
680 BOOL pressed = FALSE;
682 /* any button pressed ? */
683 for (i = 0; i < infoPtr->uNumItem; i++)
684 if ((TAB_GetItem (infoPtr, i)->dwState & TCIS_BUTTONPRESSED) &&
685 (infoPtr->iSelected != i))
687 pressed = TRUE;
688 break;
691 if (TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGING))
692 return 0;
694 if (pressed)
695 TAB_DeselectAll (infoPtr, FALSE);
696 else
697 TAB_SetCurSel(infoPtr, newItem);
699 TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGE);
703 return 0;
706 static inline LRESULT
707 TAB_LButtonUp (const TAB_INFO *infoPtr)
709 TAB_SendSimpleNotify(infoPtr, NM_CLICK);
711 return 0;
714 static inline void
715 TAB_RButtonUp (const TAB_INFO *infoPtr)
717 TAB_SendSimpleNotify(infoPtr, NM_RCLICK);
720 /******************************************************************************
721 * TAB_DrawLoneItemInterior
723 * This calls TAB_DrawItemInterior. However, TAB_DrawItemInterior is normally
724 * called by TAB_DrawItem which is normally called by TAB_Refresh which sets
725 * up the device context and font. This routine does the same setup but
726 * only calls TAB_DrawItemInterior for the single specified item.
728 static void
729 TAB_DrawLoneItemInterior(const TAB_INFO* infoPtr, int iItem)
731 HDC hdc = GetDC(infoPtr->hwnd);
732 RECT r, rC;
734 /* Clip UpDown control to not draw over it */
735 if (infoPtr->needsScrolling)
737 GetWindowRect(infoPtr->hwnd, &rC);
738 GetWindowRect(infoPtr->hwndUpDown, &r);
739 ExcludeClipRect(hdc, r.left - rC.left, r.top - rC.top, r.right - rC.left, r.bottom - rC.top);
741 TAB_DrawItemInterior(infoPtr, hdc, iItem, NULL);
742 ReleaseDC(infoPtr->hwnd, hdc);
745 /* update a tab after hottracking - invalidate it or just redraw the interior,
746 * based on whether theming is used or not */
747 static inline void hottrack_refresh(const TAB_INFO *infoPtr, int tabIndex)
749 if (tabIndex == -1) return;
751 if (GetWindowTheme (infoPtr->hwnd))
753 RECT rect;
754 TAB_InternalGetItemRect(infoPtr, tabIndex, &rect, NULL);
755 InvalidateRect (infoPtr->hwnd, &rect, FALSE);
757 else
758 TAB_DrawLoneItemInterior(infoPtr, tabIndex);
761 /******************************************************************************
762 * TAB_HotTrackTimerProc
764 * When a mouse-move event causes a tab to be highlighted (hot-tracking), a
765 * timer is setup so we can check if the mouse is moved out of our window.
766 * (We don't get an event when the mouse leaves, the mouse-move events just
767 * stop being delivered to our window and just start being delivered to
768 * another window.) This function is called when the timer triggers so
769 * we can check if the mouse has left our window. If so, we un-highlight
770 * the hot-tracked tab.
772 static void CALLBACK
773 TAB_HotTrackTimerProc
775 HWND hwnd, /* handle of window for timer messages */
776 UINT uMsg, /* WM_TIMER message */
777 UINT_PTR idEvent, /* timer identifier */
778 DWORD dwTime /* current system time */
781 TAB_INFO* infoPtr = TAB_GetInfoPtr(hwnd);
783 if (infoPtr != NULL && infoPtr->iHotTracked >= 0)
785 POINT pt;
788 ** If we can't get the cursor position, or if the cursor is outside our
789 ** window, we un-highlight the hot-tracked tab. Note that the cursor is
790 ** "outside" even if it is within our bounding rect if another window
791 ** overlaps. Note also that the case where the cursor stayed within our
792 ** window but has moved off the hot-tracked tab will be handled by the
793 ** WM_MOUSEMOVE event.
795 if (!GetCursorPos(&pt) || WindowFromPoint(pt) != hwnd)
797 /* Redraw iHotTracked to look normal */
798 INT iRedraw = infoPtr->iHotTracked;
799 infoPtr->iHotTracked = -1;
800 hottrack_refresh (infoPtr, iRedraw);
802 /* Kill this timer */
803 KillTimer(hwnd, TAB_HOTTRACK_TIMER);
808 /******************************************************************************
809 * TAB_RecalcHotTrack
811 * If a tab control has the TCS_HOTTRACK style, then the tab under the mouse
812 * should be highlighted. This function determines which tab in a tab control,
813 * if any, is under the mouse and records that information. The caller may
814 * supply output parameters to receive the item number of the tab item which
815 * was highlighted but isn't any longer and of the tab item which is now
816 * highlighted but wasn't previously. The caller can use this information to
817 * selectively redraw those tab items.
819 * If the caller has a mouse position, it can supply it through the pos
820 * parameter. For example, TAB_MouseMove does this. Otherwise, the caller
821 * supplies NULL and this function determines the current mouse position
822 * itself.
824 static void
825 TAB_RecalcHotTrack
827 TAB_INFO* infoPtr,
828 const LPARAM* pos,
829 int* out_redrawLeave,
830 int* out_redrawEnter
833 int item = -1;
836 if (out_redrawLeave != NULL)
837 *out_redrawLeave = -1;
838 if (out_redrawEnter != NULL)
839 *out_redrawEnter = -1;
841 if ((infoPtr->dwStyle & TCS_HOTTRACK) || GetWindowTheme(infoPtr->hwnd))
843 POINT pt;
844 UINT flags;
846 if (pos == NULL)
848 GetCursorPos(&pt);
849 ScreenToClient(infoPtr->hwnd, &pt);
851 else
853 pt.x = (short)LOWORD(*pos);
854 pt.y = (short)HIWORD(*pos);
857 item = TAB_InternalHitTest(infoPtr, pt, &flags);
860 if (item != infoPtr->iHotTracked)
862 if (infoPtr->iHotTracked >= 0)
864 /* Mark currently hot-tracked to be redrawn to look normal */
865 if (out_redrawLeave != NULL)
866 *out_redrawLeave = infoPtr->iHotTracked;
868 if (item < 0)
870 /* Kill timer which forces recheck of mouse pos */
871 KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
874 else
876 /* Start timer so we recheck mouse pos */
877 UINT timerID = SetTimer
879 infoPtr->hwnd,
880 TAB_HOTTRACK_TIMER,
881 TAB_HOTTRACK_TIMER_INTERVAL,
882 TAB_HotTrackTimerProc
885 if (timerID == 0)
886 return; /* Hot tracking not available */
889 infoPtr->iHotTracked = item;
891 if (item >= 0)
893 /* Mark new hot-tracked to be redrawn to look highlighted */
894 if (out_redrawEnter != NULL)
895 *out_redrawEnter = item;
900 /******************************************************************************
901 * TAB_MouseMove
903 * Handles the mouse-move event. Updates tooltips. Updates hot-tracking.
905 static LRESULT
906 TAB_MouseMove (TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
908 int redrawLeave;
909 int redrawEnter;
911 if (infoPtr->hwndToolTip)
912 TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
913 WM_LBUTTONDOWN, wParam, lParam);
915 /* Determine which tab to highlight. Redraw tabs which change highlight
916 ** status. */
917 TAB_RecalcHotTrack(infoPtr, &lParam, &redrawLeave, &redrawEnter);
919 hottrack_refresh (infoPtr, redrawLeave);
920 hottrack_refresh (infoPtr, redrawEnter);
922 return 0;
925 /******************************************************************************
926 * TAB_AdjustRect
928 * Calculates the tab control's display area given the window rectangle or
929 * the window rectangle given the requested display rectangle.
931 static LRESULT TAB_AdjustRect(const TAB_INFO *infoPtr, WPARAM fLarger, LPRECT prc)
933 LONG *iRightBottom, *iLeftTop;
935 TRACE("hwnd %p, fLarger %Id, (%s)\n", infoPtr->hwnd, fLarger, wine_dbgstr_rect(prc));
937 if (!prc) return -1;
939 if(infoPtr->dwStyle & TCS_VERTICAL)
941 iRightBottom = &(prc->right);
942 iLeftTop = &(prc->left);
944 else
946 iRightBottom = &(prc->bottom);
947 iLeftTop = &(prc->top);
950 if (fLarger) /* Go from display rectangle */
952 /* Add the height of the tabs. */
953 if (infoPtr->dwStyle & TCS_BOTTOM)
954 *iRightBottom += infoPtr->tabHeight * infoPtr->uNumRows;
955 else
956 *iLeftTop -= infoPtr->tabHeight * infoPtr->uNumRows +
957 ((infoPtr->dwStyle & TCS_BUTTONS)? 3 * (infoPtr->uNumRows - 1) : 0);
959 /* Inflate the rectangle for the padding */
960 InflateRect(prc, DISPLAY_AREA_PADDINGX, DISPLAY_AREA_PADDINGY);
962 /* Inflate for the border */
963 InflateRect(prc, CONTROL_BORDER_SIZEX, CONTROL_BORDER_SIZEY);
965 else /* Go from window rectangle. */
967 /* Deflate the rectangle for the border */
968 InflateRect(prc, -CONTROL_BORDER_SIZEX, -CONTROL_BORDER_SIZEY);
970 /* Deflate the rectangle for the padding */
971 InflateRect(prc, -DISPLAY_AREA_PADDINGX, -DISPLAY_AREA_PADDINGY);
973 /* Remove the height of the tabs. */
974 if (infoPtr->dwStyle & TCS_BOTTOM)
975 *iRightBottom -= infoPtr->tabHeight * infoPtr->uNumRows;
976 else
977 *iLeftTop += (infoPtr->tabHeight) * infoPtr->uNumRows +
978 ((infoPtr->dwStyle & TCS_BUTTONS)? 3 * (infoPtr->uNumRows - 1) : 0);
981 return 0;
984 /******************************************************************************
985 * TAB_OnHScroll
987 * This method will handle the notification from the scroll control and
988 * perform the scrolling operation on the tab control.
990 static LRESULT TAB_OnHScroll(TAB_INFO *infoPtr, int nScrollCode, int nPos)
992 if(nScrollCode == SB_THUMBPOSITION && nPos != infoPtr->leftmostVisible)
994 if(nPos < infoPtr->leftmostVisible)
995 infoPtr->leftmostVisible--;
996 else
997 infoPtr->leftmostVisible++;
999 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
1000 TAB_InvalidateTabArea(infoPtr);
1001 SendMessageW(infoPtr->hwndUpDown, UDM_SETPOS, 0,
1002 MAKELONG(infoPtr->leftmostVisible, 0));
1005 return 0;
1008 /******************************************************************************
1009 * TAB_SetupScrolling
1011 * This method will check the current scrolling state and make sure the
1012 * scrolling control is displayed (or not).
1014 static void TAB_SetupScrolling(
1015 TAB_INFO* infoPtr,
1016 const RECT* clientRect)
1018 INT maxRange = 0;
1020 if (infoPtr->needsScrolling)
1022 RECT controlPos;
1023 INT vsize, tabwidth;
1026 * Calculate the position of the scroll control.
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);
1036 else
1038 controlPos.bottom = clientRect->top + infoPtr->tabHeight;
1039 controlPos.top = controlPos.bottom - GetSystemMetrics(SM_CYHSCROLL);
1043 * If we don't have a scroll control yet, we want to create one.
1044 * If we have one, we want to make sure it's positioned properly.
1046 if (infoPtr->hwndUpDown==0)
1048 infoPtr->hwndUpDown = CreateWindowW(UPDOWN_CLASSW, L"",
1049 WS_VISIBLE | WS_CHILD | UDS_HORZ,
1050 controlPos.left, controlPos.top,
1051 controlPos.right - controlPos.left,
1052 controlPos.bottom - controlPos.top,
1053 infoPtr->hwnd, NULL, NULL, NULL);
1055 else
1057 SetWindowPos(infoPtr->hwndUpDown,
1058 NULL,
1059 controlPos.left, controlPos.top,
1060 controlPos.right - controlPos.left,
1061 controlPos.bottom - controlPos.top,
1062 SWP_SHOWWINDOW | SWP_NOZORDER);
1065 /* Now calculate upper limit of the updown control range.
1066 * We do this by calculating how many tabs will be offscreen when the
1067 * last tab is visible.
1069 if(infoPtr->uNumItem)
1071 vsize = clientRect->right - (controlPos.right - controlPos.left + 1);
1072 maxRange = infoPtr->uNumItem;
1073 tabwidth = TAB_GetItem(infoPtr, infoPtr->uNumItem - 1)->rect.right;
1075 for(; maxRange > 0; maxRange--)
1077 if(tabwidth - TAB_GetItem(infoPtr,maxRange - 1)->rect.left > vsize)
1078 break;
1081 if(maxRange == infoPtr->uNumItem)
1082 maxRange--;
1085 else
1087 /* If we once had a scroll control... hide it */
1088 if (infoPtr->hwndUpDown)
1089 ShowWindow(infoPtr->hwndUpDown, SW_HIDE);
1091 if (infoPtr->hwndUpDown)
1092 SendMessageW(infoPtr->hwndUpDown, UDM_SETRANGE32, 0, maxRange);
1095 /******************************************************************************
1096 * TAB_SetItemBounds
1098 * This method will calculate the position rectangles of all the items in the
1099 * control. The rectangle calculated starts at 0 for the first item in the
1100 * list and ignores scrolling and selection.
1101 * It also uses the current font to determine the height of the tab row and
1102 * it checks if all the tabs fit in the client area of the window. If they
1103 * don't, a scrolling control is added.
1105 static void TAB_SetItemBounds (TAB_INFO *infoPtr)
1107 TEXTMETRICW fontMetrics;
1108 UINT curItem;
1109 INT curItemLeftPos;
1110 INT curItemRowCount;
1111 HFONT hFont, hOldFont;
1112 HDC hdc;
1113 RECT clientRect;
1114 INT iTemp;
1115 RECT* rcItem;
1116 INT iIndex;
1117 INT icon_width = 0;
1120 * We need to get text information so we need a DC and we need to select
1121 * a font.
1123 hdc = GetDC(infoPtr->hwnd);
1125 hFont = infoPtr->hFont ? infoPtr->hFont : GetStockObject (SYSTEM_FONT);
1126 hOldFont = SelectObject (hdc, hFont);
1129 * We will base the rectangle calculations on the client rectangle
1130 * of the control.
1132 GetClientRect(infoPtr->hwnd, &clientRect);
1134 /* if TCS_VERTICAL then swap the height and width so this code places the
1135 tabs along the top of the rectangle and we can just rotate them after
1136 rather than duplicate all of the below code */
1137 if(infoPtr->dwStyle & TCS_VERTICAL)
1139 iTemp = clientRect.bottom;
1140 clientRect.bottom = clientRect.right;
1141 clientRect.right = iTemp;
1144 /* Now use hPadding and vPadding */
1145 infoPtr->uHItemPadding = infoPtr->uHItemPadding_s;
1146 infoPtr->uVItemPadding = infoPtr->uVItemPadding_s;
1148 /* The leftmost item will be "0" aligned */
1149 curItemLeftPos = 0;
1150 curItemRowCount = infoPtr->uNumItem ? 1 : 0;
1152 if (!(infoPtr->fHeightSet))
1154 int item_height;
1155 INT icon_height = 0, cx;
1157 /* Use the current font to determine the height of a tab. */
1158 GetTextMetricsW(hdc, &fontMetrics);
1160 /* Get the icon height */
1161 if (infoPtr->himl)
1162 ImageList_GetIconSize(infoPtr->himl, &cx, &icon_height);
1164 /* Take the highest between font or icon */
1165 if (fontMetrics.tmHeight > icon_height)
1166 item_height = fontMetrics.tmHeight + 2;
1167 else
1168 item_height = icon_height;
1171 * Make sure there is enough space for the letters + icon + growing the
1172 * selected item + extra space for the selected item.
1174 infoPtr->tabHeight = item_height +
1175 ((infoPtr->dwStyle & TCS_BUTTONS) ? 2 : 1) *
1176 infoPtr->uVItemPadding;
1178 TRACE("tabH=%d, tmH %ld, iconh %d\n", infoPtr->tabHeight, fontMetrics.tmHeight, icon_height);
1181 TRACE("client right %ld\n", clientRect.right);
1183 /* Get the icon width */
1184 if (infoPtr->himl)
1186 INT cy;
1188 ImageList_GetIconSize(infoPtr->himl, &icon_width, &cy);
1190 if (infoPtr->dwStyle & TCS_FIXEDWIDTH)
1191 icon_width += 4;
1192 else
1193 /* Add padding if icon is present */
1194 icon_width += infoPtr->uHItemPadding;
1197 for (curItem = 0; curItem < infoPtr->uNumItem; curItem++)
1199 TAB_ITEM *curr = TAB_GetItem(infoPtr, curItem);
1201 /* Set the leftmost position of the tab. */
1202 curr->rect.left = curItemLeftPos;
1204 if (infoPtr->dwStyle & TCS_FIXEDWIDTH)
1206 curr->rect.right = curr->rect.left +
1207 max(infoPtr->tabWidth, icon_width);
1209 else if (!curr->pszText)
1211 /* If no text use minimum tab width including padding. */
1212 if (infoPtr->tabMinWidth < 0)
1213 curr->rect.right = curr->rect.left + GET_DEFAULT_MIN_TAB_WIDTH(infoPtr);
1214 else
1216 curr->rect.right = curr->rect.left + infoPtr->tabMinWidth;
1218 /* Add extra padding if icon is present */
1219 if (infoPtr->himl && infoPtr->tabMinWidth > 0 && infoPtr->tabMinWidth < DEFAULT_MIN_TAB_WIDTH
1220 && infoPtr->uHItemPadding > 1)
1221 curr->rect.right += EXTRA_ICON_PADDING * (infoPtr->uHItemPadding-1);
1224 else
1226 int tabwidth;
1227 SIZE size;
1228 /* Calculate how wide the tab is depending on the text it contains */
1229 GetTextExtentPoint32W(hdc, curr->pszText,
1230 lstrlenW(curr->pszText), &size);
1232 tabwidth = size.cx + icon_width + 2 * infoPtr->uHItemPadding;
1234 if (infoPtr->tabMinWidth < 0)
1235 tabwidth = max(tabwidth, GET_DEFAULT_MIN_TAB_WIDTH(infoPtr));
1236 else
1237 tabwidth = max(tabwidth, infoPtr->tabMinWidth);
1239 curr->rect.right = curr->rect.left + tabwidth;
1240 TRACE("for <%s>, rect %s\n", debugstr_w(curr->pszText), wine_dbgstr_rect(&curr->rect));
1244 * Check if this is a multiline tab control and if so
1245 * check to see if we should wrap the tabs
1247 * Wrap all these tabs. We will arrange them evenly later.
1251 if (((infoPtr->dwStyle & TCS_MULTILINE) || (infoPtr->dwStyle & TCS_VERTICAL)) &&
1252 (curr->rect.right >
1253 (clientRect.right - CONTROL_BORDER_SIZEX - DISPLAY_AREA_PADDINGX)))
1255 curr->rect.right -= curr->rect.left;
1257 curr->rect.left = 0;
1258 curItemRowCount++;
1259 TRACE("wrapping <%s>, rect %s\n", debugstr_w(curr->pszText), wine_dbgstr_rect(&curr->rect));
1262 curr->rect.bottom = 0;
1263 curr->rect.top = curItemRowCount - 1;
1265 TRACE("Rect: %s\n", wine_dbgstr_rect(&curr->rect));
1268 * The leftmost position of the next item is the rightmost position
1269 * of this one.
1271 if (infoPtr->dwStyle & TCS_BUTTONS)
1273 curItemLeftPos = curr->rect.right + BUTTON_SPACINGX;
1274 if (infoPtr->dwStyle & TCS_FLATBUTTONS)
1275 curItemLeftPos += FLAT_BTN_SPACINGX;
1277 else
1278 curItemLeftPos = curr->rect.right;
1281 if (!((infoPtr->dwStyle & TCS_MULTILINE) || (infoPtr->dwStyle & TCS_VERTICAL)))
1284 * Check if we need a scrolling control.
1286 infoPtr->needsScrolling = (curItemLeftPos + (2 * SELECTED_TAB_OFFSET) >
1287 clientRect.right);
1289 /* Don't need scrolling, then update infoPtr->leftmostVisible */
1290 if(!infoPtr->needsScrolling)
1291 infoPtr->leftmostVisible = 0;
1293 else
1296 * No scrolling in Multiline or Vertical styles.
1298 infoPtr->needsScrolling = FALSE;
1299 infoPtr->leftmostVisible = 0;
1301 TAB_SetupScrolling(infoPtr, &clientRect);
1303 /* Set the number of rows */
1304 infoPtr->uNumRows = curItemRowCount;
1306 /* Arrange all tabs evenly if style says so */
1307 if (!(infoPtr->dwStyle & TCS_RAGGEDRIGHT) &&
1308 ((infoPtr->dwStyle & TCS_MULTILINE) || (infoPtr->dwStyle & TCS_VERTICAL)) &&
1309 (infoPtr->uNumItem > 0) &&
1310 (infoPtr->uNumRows > 1))
1312 INT tabPerRow,remTab,iRow;
1313 UINT iItm;
1314 INT iCount=0;
1317 * Ok windows tries to even out the rows. place the same
1318 * number of tabs in each row. So lets give that a shot
1321 tabPerRow = infoPtr->uNumItem / (infoPtr->uNumRows);
1322 remTab = infoPtr->uNumItem % (infoPtr->uNumRows);
1324 for (iItm=0,iRow=0,iCount=0,curItemLeftPos=0;
1325 iItm<infoPtr->uNumItem;
1326 iItm++,iCount++)
1328 /* normalize the current rect */
1329 TAB_ITEM *curr = TAB_GetItem(infoPtr, iItm);
1331 /* shift the item to the left side of the clientRect */
1332 curr->rect.right -= curr->rect.left;
1333 curr->rect.left = 0;
1335 TRACE("r=%ld, cl=%d, cl.r=%ld, iCount=%d, iRow=%d, uNumRows=%d, remTab=%d, tabPerRow=%d\n",
1336 curr->rect.right, curItemLeftPos, clientRect.right,
1337 iCount, iRow, infoPtr->uNumRows, remTab, tabPerRow);
1339 /* if we have reached the maximum number of tabs on this row */
1340 /* move to the next row, reset our current item left position and */
1341 /* the count of items on this row */
1343 if (infoPtr->dwStyle & TCS_VERTICAL) {
1344 /* Vert: Add the remaining tabs in the *last* remainder rows */
1345 if (iCount >= ((iRow>=(INT)infoPtr->uNumRows - remTab)?tabPerRow + 1:tabPerRow)) {
1346 iRow++;
1347 curItemLeftPos = 0;
1348 iCount = 0;
1350 } else {
1351 /* Horz: Add the remaining tabs in the *first* remainder rows */
1352 if (iCount >= ((iRow<remTab)?tabPerRow + 1:tabPerRow)) {
1353 iRow++;
1354 curItemLeftPos = 0;
1355 iCount = 0;
1359 /* shift the item to the right to place it as the next item in this row */
1360 curr->rect.left += curItemLeftPos;
1361 curr->rect.right += curItemLeftPos;
1362 curr->rect.top = iRow;
1363 if (infoPtr->dwStyle & TCS_BUTTONS)
1365 curItemLeftPos = curr->rect.right + 1;
1366 if (infoPtr->dwStyle & TCS_FLATBUTTONS)
1367 curItemLeftPos += FLAT_BTN_SPACINGX;
1369 else
1370 curItemLeftPos = curr->rect.right;
1372 TRACE("arranging <%s>, rect %s\n", debugstr_w(curr->pszText), wine_dbgstr_rect(&curr->rect));
1376 * Justify the rows
1379 INT widthDiff, iIndexStart=0, iIndexEnd=0;
1380 INT remainder;
1381 INT iCount=0;
1383 while(iIndexStart < infoPtr->uNumItem)
1385 TAB_ITEM *start = TAB_GetItem(infoPtr, iIndexStart);
1388 * find the index of the row
1390 /* find the first item on the next row */
1391 for (iIndexEnd=iIndexStart;
1392 (iIndexEnd < infoPtr->uNumItem) &&
1393 (TAB_GetItem(infoPtr, iIndexEnd)->rect.top ==
1394 start->rect.top) ;
1395 iIndexEnd++)
1396 /* intentionally blank */;
1399 * we need to justify these tabs so they fill the whole given
1400 * client area
1403 /* find the amount of space remaining on this row */
1404 widthDiff = clientRect.right - (2 * SELECTED_TAB_OFFSET) -
1405 TAB_GetItem(infoPtr, iIndexEnd - 1)->rect.right;
1407 /* iCount is the number of tab items on this row */
1408 iCount = iIndexEnd - iIndexStart;
1410 if (iCount > 1)
1412 remainder = widthDiff % iCount;
1413 widthDiff = widthDiff / iCount;
1414 /* add widthDiff/iCount, or extra space/items on row, to each item on this row */
1415 for (iIndex=iIndexStart, iCount=0; iIndex < iIndexEnd; iIndex++, iCount++)
1417 TAB_ITEM *item = TAB_GetItem(infoPtr, iIndex);
1419 item->rect.left += iCount * widthDiff;
1420 item->rect.right += (iCount + 1) * widthDiff;
1422 TRACE("adjusting 1 <%s>, rect %s\n", debugstr_w(item->pszText), wine_dbgstr_rect(&item->rect));
1425 TAB_GetItem(infoPtr, iIndex - 1)->rect.right += remainder;
1427 else /* we have only one item on this row, make it take up the entire row */
1429 start->rect.left = clientRect.left;
1430 start->rect.right = clientRect.right - 4;
1432 TRACE("adjusting 2 <%s>, rect %s\n", debugstr_w(start->pszText), wine_dbgstr_rect(&start->rect));
1435 iIndexStart = iIndexEnd;
1440 /* if TCS_VERTICAL rotate the tabs so they are along the side of the clientRect */
1441 if(infoPtr->dwStyle & TCS_VERTICAL)
1443 RECT rcOriginal;
1444 for(iIndex = 0; iIndex < infoPtr->uNumItem; iIndex++)
1446 rcItem = &TAB_GetItem(infoPtr, iIndex)->rect;
1448 rcOriginal = *rcItem;
1450 /* this is rotating the items by 90 degrees clockwise around the center of the control */
1451 rcItem->top = (rcOriginal.left - clientRect.left);
1452 rcItem->bottom = rcItem->top + (rcOriginal.right - rcOriginal.left);
1453 rcItem->left = rcOriginal.top;
1454 rcItem->right = rcOriginal.bottom;
1458 TAB_EnsureSelectionVisible(infoPtr);
1459 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
1461 /* Cleanup */
1462 SelectObject (hdc, hOldFont);
1463 ReleaseDC (infoPtr->hwnd, hdc);
1467 static void
1468 TAB_EraseTabInterior(const TAB_INFO *infoPtr, HDC hdc, INT iItem, const RECT *drawRect)
1470 HBRUSH hbr = CreateSolidBrush (comctl32_color.clrBtnFace);
1471 BOOL deleteBrush = TRUE;
1472 RECT rTemp = *drawRect;
1474 if (infoPtr->dwStyle & TCS_BUTTONS)
1476 if (iItem == infoPtr->iSelected)
1478 /* Background color */
1479 if (!(infoPtr->dwStyle & TCS_OWNERDRAWFIXED))
1481 DeleteObject(hbr);
1482 hbr = GetSysColorBrush(COLOR_SCROLLBAR);
1484 SetTextColor(hdc, comctl32_color.clr3dFace);
1485 SetBkColor(hdc, comctl32_color.clr3dHilight);
1487 /* if COLOR_WINDOW happens to be the same as COLOR_3DHILIGHT
1488 * we better use 0x55aa bitmap brush to make scrollbar's background
1489 * look different from the window background.
1491 if (comctl32_color.clr3dHilight == comctl32_color.clrWindow)
1492 hbr = COMCTL32_hPattern55AABrush;
1494 deleteBrush = FALSE;
1496 FillRect(hdc, &rTemp, hbr);
1498 else /* ! selected */
1500 if (infoPtr->dwStyle & TCS_FLATBUTTONS)
1502 InflateRect(&rTemp, 2, 2);
1503 FillRect(hdc, &rTemp, hbr);
1504 if (iItem == infoPtr->iHotTracked ||
1505 (iItem != infoPtr->iSelected && iItem == infoPtr->uFocus))
1506 DrawEdge(hdc, &rTemp, BDR_RAISEDINNER, BF_RECT);
1508 else
1509 FillRect(hdc, &rTemp, hbr);
1513 else /* !TCS_BUTTONS */
1515 InflateRect(&rTemp, -2, -2);
1516 if (!GetWindowTheme (infoPtr->hwnd))
1517 FillRect(hdc, &rTemp, hbr);
1520 /* highlighting is drawn on top of previous fills */
1521 if (TAB_GetItem(infoPtr, iItem)->dwState & TCIS_HIGHLIGHTED)
1523 if (deleteBrush)
1525 DeleteObject(hbr);
1526 deleteBrush = FALSE;
1528 hbr = GetSysColorBrush(COLOR_HIGHLIGHT);
1529 FillRect(hdc, &rTemp, hbr);
1532 /* Cleanup */
1533 if (deleteBrush) DeleteObject(hbr);
1536 /******************************************************************************
1537 * TAB_DrawItemInterior
1539 * This method is used to draw the interior (text and icon) of a single tab
1540 * into the tab control.
1542 static void
1543 TAB_DrawItemInterior(const TAB_INFO *infoPtr, HDC hdc, INT iItem, RECT *drawRect)
1545 RECT localRect;
1547 HPEN htextPen;
1548 HPEN holdPen;
1549 INT oldBkMode;
1550 HFONT hOldFont;
1552 /* if (drawRect == NULL) */
1554 BOOL isVisible;
1555 RECT itemRect;
1556 RECT selectedRect;
1559 * Get the rectangle for the item.
1561 isVisible = TAB_InternalGetItemRect(infoPtr, iItem, &itemRect, &selectedRect);
1562 if (!isVisible)
1563 return;
1566 * Make sure drawRect points to something valid; simplifies code.
1568 drawRect = &localRect;
1571 * This logic copied from the part of TAB_DrawItem which draws
1572 * the tab background. It's important to keep it in sync. I
1573 * would have liked to avoid code duplication, but couldn't figure
1574 * out how without making spaghetti of TAB_DrawItem.
1576 if (iItem == infoPtr->iSelected)
1577 *drawRect = selectedRect;
1578 else
1579 *drawRect = itemRect;
1581 if (infoPtr->dwStyle & TCS_BUTTONS)
1583 if (iItem == infoPtr->iSelected)
1585 drawRect->left += 4;
1586 drawRect->top += 4;
1587 drawRect->right -= 4;
1589 if (infoPtr->dwStyle & TCS_VERTICAL)
1591 if (!(infoPtr->dwStyle & TCS_BOTTOM)) drawRect->right += 1;
1592 drawRect->bottom -= 4;
1594 else
1596 if (infoPtr->dwStyle & TCS_BOTTOM)
1598 drawRect->top -= 2;
1599 drawRect->bottom -= 4;
1601 else
1602 drawRect->bottom -= 1;
1605 else
1606 InflateRect(drawRect, -2, -2);
1608 else
1610 if ((infoPtr->dwStyle & TCS_VERTICAL) && (infoPtr->dwStyle & TCS_BOTTOM))
1612 if (iItem != infoPtr->iSelected)
1614 drawRect->left += 2;
1615 InflateRect(drawRect, 0, -2);
1618 else if (infoPtr->dwStyle & TCS_VERTICAL)
1620 if (iItem == infoPtr->iSelected)
1622 drawRect->right += 1;
1624 else
1626 drawRect->right -= 2;
1627 InflateRect(drawRect, 0, -2);
1630 else if (infoPtr->dwStyle & TCS_BOTTOM)
1632 if (iItem == infoPtr->iSelected)
1634 drawRect->top -= 2;
1636 else
1638 InflateRect(drawRect, -2, -2);
1639 drawRect->bottom += 2;
1642 else
1644 if (iItem == infoPtr->iSelected)
1646 drawRect->bottom += 3;
1648 else
1650 drawRect->bottom -= 2;
1651 InflateRect(drawRect, -2, 0);
1656 TRACE("drawRect=(%s)\n", wine_dbgstr_rect(drawRect));
1658 /* Clear interior */
1659 TAB_EraseTabInterior (infoPtr, hdc, iItem, drawRect);
1661 /* Draw the focus rectangle */
1662 if (!(infoPtr->dwStyle & TCS_FOCUSNEVER) &&
1663 (GetFocus() == infoPtr->hwnd) &&
1664 (iItem == infoPtr->uFocus) )
1666 RECT rFocus = *drawRect;
1668 if (!(infoPtr->dwStyle & TCS_BUTTONS)) InflateRect(&rFocus, -3, -3);
1669 if (infoPtr->dwStyle & TCS_BOTTOM && !(infoPtr->dwStyle & TCS_VERTICAL))
1670 rFocus.top -= 3;
1672 /* focus should stay on selected item for TCS_BUTTONS style */
1673 if (!((infoPtr->dwStyle & TCS_BUTTONS) && (infoPtr->iSelected != iItem)))
1674 DrawFocusRect(hdc, &rFocus);
1678 * Text pen
1680 htextPen = CreatePen( PS_SOLID, 1, comctl32_color.clrBtnText );
1681 holdPen = SelectObject(hdc, htextPen);
1682 hOldFont = SelectObject(hdc, infoPtr->hFont);
1685 * Setup for text output
1687 oldBkMode = SetBkMode(hdc, TRANSPARENT);
1688 if (!GetWindowTheme (infoPtr->hwnd) || (infoPtr->dwStyle & TCS_BUTTONS))
1690 if ((infoPtr->dwStyle & TCS_HOTTRACK) && (iItem == infoPtr->iHotTracked) &&
1691 !(infoPtr->dwStyle & TCS_FLATBUTTONS))
1692 SetTextColor(hdc, comctl32_color.clrHighlight);
1693 else if (TAB_GetItem(infoPtr, iItem)->dwState & TCIS_HIGHLIGHTED)
1694 SetTextColor(hdc, comctl32_color.clrHighlightText);
1695 else
1696 SetTextColor(hdc, comctl32_color.clrBtnText);
1700 * if owner draw, tell the owner to draw
1702 if ((infoPtr->dwStyle & TCS_OWNERDRAWFIXED) && IsWindow(infoPtr->hwndNotify))
1704 DRAWITEMSTRUCT dis;
1705 UINT id;
1707 drawRect->top += 2;
1708 drawRect->right -= 1;
1709 if ( iItem == infoPtr->iSelected )
1710 InflateRect(drawRect, -1, 0);
1712 id = (UINT)GetWindowLongPtrW( infoPtr->hwnd, GWLP_ID );
1714 /* fill DRAWITEMSTRUCT */
1715 dis.CtlType = ODT_TAB;
1716 dis.CtlID = id;
1717 dis.itemID = iItem;
1718 dis.itemAction = ODA_DRAWENTIRE;
1719 dis.itemState = 0;
1720 if ( iItem == infoPtr->iSelected )
1721 dis.itemState |= ODS_SELECTED;
1722 if (infoPtr->uFocus == iItem)
1723 dis.itemState |= ODS_FOCUS;
1724 dis.hwndItem = infoPtr->hwnd;
1725 dis.hDC = hdc;
1726 dis.rcItem = *drawRect;
1728 /* when extra data fits ULONG_PTR, store it directly */
1729 if (infoPtr->cbInfo > sizeof(LPARAM))
1730 dis.itemData = (ULONG_PTR) TAB_GetItem(infoPtr, iItem)->extra;
1731 else
1733 /* this could be considered broken on 64 bit, but that's how it works -
1734 only first 4 bytes are copied */
1735 dis.itemData = 0;
1736 memcpy(&dis.itemData, (ULONG_PTR*)TAB_GetItem(infoPtr, iItem)->extra, 4);
1739 /* draw notification */
1740 SendMessageW( infoPtr->hwndNotify, WM_DRAWITEM, id, (LPARAM)&dis );
1742 else
1744 TAB_ITEM *item = TAB_GetItem(infoPtr, iItem);
1745 RECT rcTemp;
1746 RECT rcImage;
1748 /* used to center the icon and text in the tab */
1749 RECT rcText;
1750 INT center_offset_h, center_offset_v;
1752 /* set rcImage to drawRect, we will use top & left in our ImageList_Draw call */
1753 rcImage = *drawRect;
1755 rcTemp = *drawRect;
1756 SetRectEmpty(&rcText);
1758 /* get the rectangle that the text fits in */
1759 if (item->pszText)
1761 DrawTextW(hdc, item->pszText, -1, &rcText, DT_CALCRECT);
1764 * If not owner draw, then do the drawing ourselves.
1766 * Draw the icon.
1768 if (infoPtr->himl && item->iImage != -1)
1770 INT cx;
1771 INT cy;
1773 ImageList_GetIconSize(infoPtr->himl, &cx, &cy);
1775 if(infoPtr->dwStyle & TCS_VERTICAL)
1777 center_offset_h = ((drawRect->bottom - drawRect->top) - (cy + infoPtr->uHItemPadding + (rcText.right - rcText.left))) / 2;
1778 center_offset_v = ((drawRect->right - drawRect->left) - cx) / 2;
1780 else
1782 center_offset_h = ((drawRect->right - drawRect->left) - (cx + infoPtr->uHItemPadding + (rcText.right - rcText.left))) / 2;
1783 center_offset_v = ((drawRect->bottom - drawRect->top) - cy) / 2;
1786 /* if an item is selected, the icon is shifted up instead of down */
1787 if (iItem == infoPtr->iSelected)
1788 center_offset_v -= infoPtr->uVItemPadding / 2;
1789 else
1790 center_offset_v += infoPtr->uVItemPadding / 2;
1792 if (infoPtr->dwStyle & TCS_FIXEDWIDTH && infoPtr->dwStyle & (TCS_FORCELABELLEFT | TCS_FORCEICONLEFT))
1793 center_offset_h = infoPtr->uHItemPadding;
1795 if (center_offset_h < 2)
1796 center_offset_h = 2;
1798 if (center_offset_v < 0)
1799 center_offset_v = 0;
1801 TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%s), textlen=%ld\n",
1802 debugstr_w(item->pszText), center_offset_h, center_offset_v,
1803 wine_dbgstr_rect(drawRect), (rcText.right-rcText.left));
1805 if((infoPtr->dwStyle & TCS_VERTICAL) && (infoPtr->dwStyle & TCS_BOTTOM))
1807 rcImage.top = drawRect->top + center_offset_h;
1808 /* if tab is TCS_VERTICAL and TCS_BOTTOM, the text is drawn from the */
1809 /* right side of the tab, but the image still uses the left as its x position */
1810 /* this keeps the image always drawn off of the same side of the tab */
1811 rcImage.left = drawRect->right - cx - center_offset_v;
1812 drawRect->top += cy + infoPtr->uHItemPadding;
1814 else if(infoPtr->dwStyle & TCS_VERTICAL)
1816 rcImage.top = drawRect->bottom - cy - center_offset_h;
1817 rcImage.left = drawRect->left + center_offset_v;
1818 drawRect->bottom -= cy + infoPtr->uHItemPadding;
1820 else /* normal style, whether TCS_BOTTOM or not */
1822 rcImage.left = drawRect->left + center_offset_h;
1823 rcImage.top = drawRect->top + center_offset_v;
1824 drawRect->left += cx + infoPtr->uHItemPadding;
1827 TRACE("drawing image %d, left %ld, top %ld\n", item->iImage, rcImage.left, rcImage.top-1);
1828 ImageList_Draw
1830 infoPtr->himl,
1831 item->iImage,
1832 hdc,
1833 rcImage.left,
1834 rcImage.top,
1835 ILD_NORMAL
1839 /* Now position text */
1840 if (infoPtr->dwStyle & TCS_FIXEDWIDTH && infoPtr->dwStyle & TCS_FORCELABELLEFT)
1841 center_offset_h = infoPtr->uHItemPadding;
1842 else
1843 if(infoPtr->dwStyle & TCS_VERTICAL)
1844 center_offset_h = ((drawRect->bottom - drawRect->top) - (rcText.right - rcText.left)) / 2;
1845 else
1846 center_offset_h = ((drawRect->right - drawRect->left) - (rcText.right - rcText.left)) / 2;
1848 if(infoPtr->dwStyle & TCS_VERTICAL)
1850 if(infoPtr->dwStyle & TCS_BOTTOM)
1851 drawRect->top+=center_offset_h;
1852 else
1853 drawRect->bottom-=center_offset_h;
1855 center_offset_v = ((drawRect->right - drawRect->left) - (rcText.bottom - rcText.top)) / 2;
1857 else
1859 drawRect->left += center_offset_h;
1860 center_offset_v = ((drawRect->bottom - drawRect->top) - (rcText.bottom - rcText.top)) / 2;
1863 /* if an item is selected, the text is shifted up instead of down */
1864 if (iItem == infoPtr->iSelected)
1865 center_offset_v -= infoPtr->uVItemPadding / 2;
1866 else
1867 center_offset_v += infoPtr->uVItemPadding / 2;
1869 if (center_offset_v < 0)
1870 center_offset_v = 0;
1872 if(infoPtr->dwStyle & TCS_VERTICAL)
1873 drawRect->left += center_offset_v;
1874 else
1875 drawRect->top += center_offset_v;
1877 /* Draw the text */
1878 if(infoPtr->dwStyle & TCS_VERTICAL) /* if we are vertical rotate the text and each character */
1880 LOGFONTW logfont;
1881 HFONT hFont;
1882 INT nEscapement = 900;
1883 INT nOrientation = 900;
1885 if(infoPtr->dwStyle & TCS_BOTTOM)
1887 nEscapement = -900;
1888 nOrientation = -900;
1891 /* to get a font with the escapement and orientation we are looking for, we need to */
1892 /* call CreateFontIndirect, which requires us to set the values of the logfont we pass in */
1893 if (!GetObjectW(infoPtr->hFont, sizeof(logfont), &logfont))
1894 GetObjectW(GetStockObject(DEFAULT_GUI_FONT), sizeof(logfont), &logfont);
1896 logfont.lfEscapement = nEscapement;
1897 logfont.lfOrientation = nOrientation;
1898 hFont = CreateFontIndirectW(&logfont);
1899 SelectObject(hdc, hFont);
1901 if (item->pszText)
1903 ExtTextOutW(hdc,
1904 (infoPtr->dwStyle & TCS_BOTTOM) ? drawRect->right : drawRect->left,
1905 (!(infoPtr->dwStyle & TCS_BOTTOM)) ? drawRect->bottom : drawRect->top,
1906 ETO_CLIPPED,
1907 drawRect,
1908 item->pszText,
1909 lstrlenW(item->pszText),
1913 DeleteObject(hFont);
1915 else
1917 TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%s), textlen=%ld\n",
1918 debugstr_w(item->pszText), center_offset_h, center_offset_v,
1919 wine_dbgstr_rect(drawRect), (rcText.right-rcText.left));
1920 if (item->pszText)
1922 DrawTextW
1924 hdc,
1925 item->pszText,
1926 lstrlenW(item->pszText),
1927 drawRect,
1928 DT_LEFT | DT_SINGLELINE
1933 *drawRect = rcTemp; /* restore drawRect */
1937 * Cleanup
1939 SelectObject(hdc, hOldFont);
1940 SetBkMode(hdc, oldBkMode);
1941 SelectObject(hdc, holdPen);
1942 DeleteObject( htextPen );
1945 /******************************************************************************
1946 * TAB_DrawItem
1948 * This method is used to draw a single tab into the tab control.
1950 static void TAB_DrawItem(const TAB_INFO *infoPtr, HDC hdc, INT iItem)
1952 RECT itemRect;
1953 RECT selectedRect;
1954 BOOL isVisible;
1955 RECT r, fillRect, r1;
1956 INT clRight = 0;
1957 INT clBottom = 0;
1958 COLORREF bkgnd, corner;
1959 HTHEME theme;
1962 * Get the rectangle for the item.
1964 isVisible = TAB_InternalGetItemRect(infoPtr,
1965 iItem,
1966 &itemRect,
1967 &selectedRect);
1969 if (isVisible)
1971 RECT rUD, rC;
1973 /* Clip UpDown control to not draw over it */
1974 if (infoPtr->needsScrolling)
1976 GetWindowRect(infoPtr->hwnd, &rC);
1977 GetWindowRect(infoPtr->hwndUpDown, &rUD);
1978 ExcludeClipRect(hdc, rUD.left - rC.left, rUD.top - rC.top, rUD.right - rC.left, rUD.bottom - rC.top);
1981 /* If you need to see what the control is doing,
1982 * then override these variables. They will change what
1983 * fill colors are used for filling the tabs, and the
1984 * corners when drawing the edge.
1986 bkgnd = comctl32_color.clrBtnFace;
1987 corner = comctl32_color.clrBtnFace;
1989 if (infoPtr->dwStyle & TCS_BUTTONS)
1991 /* Get item rectangle */
1992 r = itemRect;
1994 /* Separators between flat buttons */
1995 if ((infoPtr->dwStyle & TCS_FLATBUTTONS) && (infoPtr->exStyle & TCS_EX_FLATSEPARATORS))
1997 r1 = r;
1998 r1.right += (FLAT_BTN_SPACINGX -2);
1999 DrawEdge(hdc, &r1, EDGE_ETCHED, BF_RIGHT);
2002 if (iItem == infoPtr->iSelected)
2004 DrawEdge(hdc, &r, EDGE_SUNKEN, BF_SOFT|BF_RECT);
2006 OffsetRect(&r, 1, 1);
2008 else /* ! selected */
2010 DWORD state = TAB_GetItem(infoPtr, iItem)->dwState;
2012 if ((state & TCIS_BUTTONPRESSED) || (iItem == infoPtr->uFocus))
2013 DrawEdge(hdc, &r, EDGE_SUNKEN, BF_SOFT|BF_RECT);
2014 else
2015 if (!(infoPtr->dwStyle & TCS_FLATBUTTONS))
2016 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_RECT);
2019 else /* !TCS_BUTTONS */
2021 /* We draw a rectangle of different sizes depending on the selection
2022 * state. */
2023 if (iItem == infoPtr->iSelected) {
2024 RECT rect;
2025 GetClientRect (infoPtr->hwnd, &rect);
2026 clRight = rect.right;
2027 clBottom = rect.bottom;
2028 r = selectedRect;
2030 else
2031 r = itemRect;
2034 * Erase the background. (Delay it but setup rectangle.)
2035 * This is necessary when drawing the selected item since it is larger
2036 * than the others, it might overlap with stuff already drawn by the
2037 * other tabs
2039 fillRect = r;
2041 /* Draw themed tabs - but only if they are at the top.
2042 * Windows draws even side or bottom tabs themed, with wacky results.
2043 * However, since in Wine apps may get themed that did not opt in via
2044 * a manifest avoid theming when we know the result will be wrong */
2045 if ((theme = GetWindowTheme (infoPtr->hwnd))
2046 && ((infoPtr->dwStyle & (TCS_VERTICAL | TCS_BOTTOM)) == 0))
2048 static const int partIds[8] = {
2049 /* Normal item */
2050 TABP_TABITEM,
2051 TABP_TABITEMLEFTEDGE,
2052 TABP_TABITEMRIGHTEDGE,
2053 TABP_TABITEMBOTHEDGE,
2054 /* Selected tab */
2055 TABP_TOPTABITEM,
2056 TABP_TOPTABITEMLEFTEDGE,
2057 TABP_TOPTABITEMRIGHTEDGE,
2058 TABP_TOPTABITEMBOTHEDGE,
2060 int partIndex = 0;
2061 int stateId = TIS_NORMAL;
2063 /* selected and unselected tabs have different parts */
2064 if (iItem == infoPtr->iSelected)
2065 partIndex += 4;
2066 /* The part also differs on the position of a tab on a line.
2067 * "Visually" determining the position works well enough. */
2068 GetClientRect(infoPtr->hwnd, &r1);
2069 if(selectedRect.left == 0)
2070 partIndex += 1;
2071 if(selectedRect.right == r1.right)
2072 partIndex += 2;
2074 if (iItem == infoPtr->iSelected)
2075 stateId = TIS_SELECTED;
2076 else if (iItem == infoPtr->iHotTracked)
2077 stateId = TIS_HOT;
2078 else if (iItem == infoPtr->uFocus)
2079 stateId = TIS_FOCUSED;
2081 /* Adjust rectangle for bottommost row */
2082 if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2083 r.bottom += 3;
2085 DrawThemeBackground (theme, hdc, partIds[partIndex], stateId, &r, NULL);
2086 GetThemeBackgroundContentRect (theme, hdc, partIds[partIndex], stateId, &r, &r);
2088 else if(infoPtr->dwStyle & TCS_VERTICAL)
2090 /* These are for adjusting the drawing of a Selected tab */
2091 /* The initial values are for the normal case of non-Selected */
2092 int ZZ = 1; /* Do not stretch if selected */
2093 if (iItem == infoPtr->iSelected) {
2094 ZZ = 0;
2096 /* if leftmost draw the line longer */
2097 if(selectedRect.top == 0)
2098 fillRect.top += CONTROL_BORDER_SIZEY;
2099 /* if rightmost draw the line longer */
2100 if(selectedRect.bottom == clBottom)
2101 fillRect.bottom -= CONTROL_BORDER_SIZEY;
2104 if (infoPtr->dwStyle & TCS_BOTTOM)
2106 /* Adjust both rectangles to match native */
2107 r.left += (1-ZZ);
2109 TRACE("<right> item=%d, fill=(%s), edge=(%s)\n",
2110 iItem, wine_dbgstr_rect(&fillRect), wine_dbgstr_rect(&r));
2112 /* Clear interior */
2113 SetBkColor(hdc, bkgnd);
2114 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2116 /* Draw rectangular edge around tab */
2117 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_RIGHT|BF_TOP|BF_BOTTOM);
2119 /* Now erase the top corner and draw diagonal edge */
2120 SetBkColor(hdc, corner);
2121 r1.left = r.right - ROUND_CORNER_SIZE - 1;
2122 r1.top = r.top;
2123 r1.right = r.right;
2124 r1.bottom = r1.top + ROUND_CORNER_SIZE;
2125 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2126 r1.right--;
2127 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPLEFT);
2129 /* Now erase the bottom corner and draw diagonal edge */
2130 r1.left = r.right - ROUND_CORNER_SIZE - 1;
2131 r1.bottom = r.bottom;
2132 r1.right = r.right;
2133 r1.top = r1.bottom - ROUND_CORNER_SIZE;
2134 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2135 r1.right--;
2136 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMLEFT);
2138 if ((iItem == infoPtr->iSelected) && (selectedRect.top == 0)) {
2139 r1 = r;
2140 r1.right = r1.left;
2141 r1.left--;
2142 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_TOP);
2146 else
2148 TRACE("<left> item=%d, fill=(%s), edge=(%s)\n",
2149 iItem, wine_dbgstr_rect(&fillRect), wine_dbgstr_rect(&r));
2151 /* Clear interior */
2152 SetBkColor(hdc, bkgnd);
2153 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2155 /* Draw rectangular edge around tab */
2156 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_TOP|BF_BOTTOM);
2158 /* Now erase the top corner and draw diagonal edge */
2159 SetBkColor(hdc, corner);
2160 r1.left = r.left;
2161 r1.top = r.top;
2162 r1.right = r1.left + ROUND_CORNER_SIZE + 1;
2163 r1.bottom = r1.top + ROUND_CORNER_SIZE;
2164 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2165 r1.left++;
2166 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPRIGHT);
2168 /* Now erase the bottom corner and draw diagonal edge */
2169 r1.left = r.left;
2170 r1.bottom = r.bottom;
2171 r1.right = r1.left + ROUND_CORNER_SIZE + 1;
2172 r1.top = r1.bottom - ROUND_CORNER_SIZE;
2173 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2174 r1.left++;
2175 DrawEdge(hdc, &r1, EDGE_SUNKEN, BF_DIAGONAL_ENDTOPLEFT);
2178 else /* ! TCS_VERTICAL */
2180 /* These are for adjusting the drawing of a Selected tab */
2181 /* The initial values are for the normal case of non-Selected */
2182 if (iItem == infoPtr->iSelected) {
2183 /* if leftmost draw the line longer */
2184 if(selectedRect.left == 0)
2185 fillRect.left += CONTROL_BORDER_SIZEX;
2186 /* if rightmost draw the line longer */
2187 if(selectedRect.right == clRight)
2188 fillRect.right -= CONTROL_BORDER_SIZEX;
2191 if (infoPtr->dwStyle & TCS_BOTTOM)
2193 /* Adjust both rectangles for topmost row */
2194 if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2196 fillRect.top -= 2;
2197 r.top -= 1;
2200 TRACE("<bottom> item=%d, fill=(%s), edge=(%s)\n",
2201 iItem, wine_dbgstr_rect(&fillRect), wine_dbgstr_rect(&r));
2203 /* Clear interior */
2204 SetBkColor(hdc, bkgnd);
2205 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2207 /* Draw rectangular edge around tab */
2208 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_BOTTOM|BF_RIGHT);
2210 /* Now erase the righthand corner and draw diagonal edge */
2211 SetBkColor(hdc, corner);
2212 r1.left = r.right - ROUND_CORNER_SIZE;
2213 r1.bottom = r.bottom;
2214 r1.right = r.right;
2215 r1.top = r1.bottom - ROUND_CORNER_SIZE - 1;
2216 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2217 r1.bottom--;
2218 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMLEFT);
2220 /* Now erase the lefthand corner and draw diagonal edge */
2221 r1.left = r.left;
2222 r1.bottom = r.bottom;
2223 r1.right = r1.left + ROUND_CORNER_SIZE;
2224 r1.top = r1.bottom - ROUND_CORNER_SIZE - 1;
2225 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2226 r1.bottom--;
2227 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPLEFT);
2229 if (iItem == infoPtr->iSelected)
2231 r.top += 2;
2232 r.left += 1;
2233 if (selectedRect.left == 0)
2235 r1 = r;
2236 r1.bottom = r1.top;
2237 r1.top--;
2238 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_LEFT);
2243 else
2245 /* Adjust both rectangles for bottommost row */
2246 if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2248 fillRect.bottom += 3;
2249 r.bottom += 2;
2252 TRACE("<top> item=%d, fill=(%s), edge=(%s)\n",
2253 iItem, wine_dbgstr_rect(&fillRect), wine_dbgstr_rect(&r));
2255 /* Clear interior */
2256 SetBkColor(hdc, bkgnd);
2257 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2259 /* Draw rectangular edge around tab */
2260 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_TOP|BF_RIGHT);
2262 /* Now erase the righthand corner and draw diagonal edge */
2263 SetBkColor(hdc, corner);
2264 r1.left = r.right - ROUND_CORNER_SIZE;
2265 r1.top = r.top;
2266 r1.right = r.right;
2267 r1.bottom = r1.top + ROUND_CORNER_SIZE + 1;
2268 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2269 r1.top++;
2270 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMRIGHT);
2272 /* Now erase the lefthand corner and draw diagonal edge */
2273 r1.left = r.left;
2274 r1.top = r.top;
2275 r1.right = r1.left + ROUND_CORNER_SIZE;
2276 r1.bottom = r1.top + ROUND_CORNER_SIZE + 1;
2277 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2278 r1.top++;
2279 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPRIGHT);
2284 TAB_DumpItemInternal(infoPtr, iItem);
2286 /* This modifies r to be the text rectangle. */
2287 TAB_DrawItemInterior(infoPtr, hdc, iItem, &r);
2291 /******************************************************************************
2292 * TAB_DrawBorder
2294 * This method is used to draw the raised border around the tab control
2295 * "content" area.
2297 static void TAB_DrawBorder(const TAB_INFO *infoPtr, HDC hdc)
2299 RECT rect;
2300 HTHEME theme = GetWindowTheme (infoPtr->hwnd);
2302 GetClientRect (infoPtr->hwnd, &rect);
2305 * Adjust for the style
2308 if (infoPtr->uNumItem)
2310 if ((infoPtr->dwStyle & TCS_BOTTOM) && !(infoPtr->dwStyle & TCS_VERTICAL))
2311 rect.bottom -= infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2312 else if((infoPtr->dwStyle & TCS_BOTTOM) && (infoPtr->dwStyle & TCS_VERTICAL))
2313 rect.right -= infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2314 else if(infoPtr->dwStyle & TCS_VERTICAL)
2315 rect.left += infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2316 else /* not TCS_VERTICAL and not TCS_BOTTOM */
2317 rect.top += infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2320 TRACE("border=(%s)\n", wine_dbgstr_rect(&rect));
2322 if (theme)
2324 DrawThemeParentBackground(infoPtr->hwnd, hdc, &rect);
2325 DrawThemeBackground (theme, hdc, TABP_PANE, 0, &rect, NULL);
2327 else
2329 DrawEdge(hdc, &rect, EDGE_RAISED, BF_SOFT|BF_RECT);
2333 /******************************************************************************
2334 * TAB_Refresh
2336 * This method repaints the tab control..
2338 static void TAB_Refresh (const TAB_INFO *infoPtr, HDC hdc)
2340 HFONT hOldFont;
2341 INT i;
2343 if (!infoPtr->DoRedraw)
2344 return;
2346 hOldFont = SelectObject (hdc, infoPtr->hFont);
2348 if (infoPtr->dwStyle & TCS_BUTTONS)
2350 for (i = 0; i < infoPtr->uNumItem; i++)
2351 TAB_DrawItem (infoPtr, hdc, i);
2353 else
2355 /* Draw all the non selected item first */
2356 for (i = 0; i < infoPtr->uNumItem; i++)
2358 if (i != infoPtr->iSelected)
2359 TAB_DrawItem (infoPtr, hdc, i);
2362 /* Now, draw the border, draw it before the selected item
2363 * since the selected item overwrites part of the border. */
2364 TAB_DrawBorder (infoPtr, hdc);
2366 /* Then, draw the selected item */
2367 TAB_DrawItem (infoPtr, hdc, infoPtr->iSelected);
2370 SelectObject (hdc, hOldFont);
2373 static inline DWORD TAB_GetRowCount (const TAB_INFO *infoPtr)
2375 TRACE("(%p)\n", infoPtr);
2376 return infoPtr->uNumRows;
2379 static inline LRESULT TAB_SetRedraw (TAB_INFO *infoPtr, BOOL doRedraw)
2381 infoPtr->DoRedraw = doRedraw;
2382 return 0;
2385 /******************************************************************************
2386 * TAB_EnsureSelectionVisible
2388 * This method will make sure that the current selection is completely
2389 * visible by scrolling until it is.
2391 static void TAB_EnsureSelectionVisible(
2392 TAB_INFO* infoPtr)
2394 INT iSelected = infoPtr->iSelected;
2395 INT iOrigLeftmostVisible = infoPtr->leftmostVisible;
2397 if (iSelected < 0)
2398 return;
2400 /* set the items row to the bottommost row or topmost row depending on
2401 * style */
2402 if ((infoPtr->uNumRows > 1) && !(infoPtr->dwStyle & TCS_BUTTONS))
2404 TAB_ITEM *selected = TAB_GetItem(infoPtr, iSelected);
2405 INT newselected;
2406 INT iTargetRow;
2408 if(infoPtr->dwStyle & TCS_VERTICAL)
2409 newselected = selected->rect.left;
2410 else
2411 newselected = selected->rect.top;
2413 /* the target row is always (number of rows - 1)
2414 as row 0 is furthest from the clientRect */
2415 iTargetRow = infoPtr->uNumRows - 1;
2417 if (newselected != iTargetRow)
2419 UINT i;
2420 if(infoPtr->dwStyle & TCS_VERTICAL)
2422 for (i=0; i < infoPtr->uNumItem; i++)
2424 /* move everything in the row of the selected item to the iTargetRow */
2425 TAB_ITEM *item = TAB_GetItem(infoPtr, i);
2427 if (item->rect.left == newselected )
2428 item->rect.left = iTargetRow;
2429 else
2431 if (item->rect.left > newselected)
2432 item->rect.left-=1;
2436 else
2438 for (i=0; i < infoPtr->uNumItem; i++)
2440 TAB_ITEM *item = TAB_GetItem(infoPtr, i);
2442 if (item->rect.top == newselected )
2443 item->rect.top = iTargetRow;
2444 else
2446 if (item->rect.top > newselected)
2447 item->rect.top-=1;
2451 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
2456 * Do the trivial cases first.
2458 if ( (!infoPtr->needsScrolling) ||
2459 (infoPtr->hwndUpDown==0) || (infoPtr->dwStyle & TCS_VERTICAL))
2460 return;
2462 if (infoPtr->leftmostVisible >= iSelected)
2464 infoPtr->leftmostVisible = iSelected;
2466 else
2468 TAB_ITEM *selected = TAB_GetItem(infoPtr, iSelected);
2469 RECT r;
2470 INT width;
2471 UINT i;
2473 /* Calculate the part of the client area that is visible */
2474 GetClientRect(infoPtr->hwnd, &r);
2475 width = r.right;
2477 GetClientRect(infoPtr->hwndUpDown, &r);
2478 width -= r.right;
2480 if ((selected->rect.right -
2481 selected->rect.left) >= width )
2483 /* Special case: width of selected item is greater than visible
2484 * part of control.
2486 infoPtr->leftmostVisible = iSelected;
2488 else
2490 for (i = infoPtr->leftmostVisible; i < infoPtr->uNumItem; i++)
2492 if ((selected->rect.right - TAB_GetItem(infoPtr, i)->rect.left) < width)
2493 break;
2495 infoPtr->leftmostVisible = i;
2499 if (infoPtr->leftmostVisible != iOrigLeftmostVisible)
2500 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
2502 SendMessageW(infoPtr->hwndUpDown, UDM_SETPOS, 0,
2503 MAKELONG(infoPtr->leftmostVisible, 0));
2506 /******************************************************************************
2507 * TAB_InvalidateTabArea
2509 * This method will invalidate the portion of the control that contains the
2510 * tabs. It is called when the state of the control changes and needs
2511 * to be redisplayed
2513 static void TAB_InvalidateTabArea(const TAB_INFO *infoPtr)
2515 RECT clientRect, rInvalidate, rAdjClient;
2516 INT lastRow = infoPtr->uNumRows - 1;
2517 RECT rect;
2519 if (lastRow < 0) return;
2521 GetClientRect(infoPtr->hwnd, &clientRect);
2522 rInvalidate = clientRect;
2523 rAdjClient = clientRect;
2525 TAB_AdjustRect(infoPtr, 0, &rAdjClient);
2527 TAB_InternalGetItemRect(infoPtr, infoPtr->uNumItem-1 , &rect, NULL);
2528 if ((infoPtr->dwStyle & TCS_BOTTOM) && (infoPtr->dwStyle & TCS_VERTICAL))
2530 rInvalidate.left = rAdjClient.right;
2531 if (infoPtr->uNumRows == 1)
2532 rInvalidate.bottom = clientRect.top + rect.bottom + 2 * SELECTED_TAB_OFFSET;
2534 else if(infoPtr->dwStyle & TCS_VERTICAL)
2536 rInvalidate.right = rAdjClient.left;
2537 if (infoPtr->uNumRows == 1)
2538 rInvalidate.bottom = clientRect.top + rect.bottom + 2 * SELECTED_TAB_OFFSET;
2540 else if (infoPtr->dwStyle & TCS_BOTTOM)
2542 rInvalidate.top = rAdjClient.bottom;
2543 if (infoPtr->uNumRows == 1)
2544 rInvalidate.right = clientRect.left + rect.right + 2 * SELECTED_TAB_OFFSET;
2546 else
2548 rInvalidate.bottom = rAdjClient.top;
2549 if (infoPtr->uNumRows == 1)
2550 rInvalidate.right = clientRect.left + rect.right + 2 * SELECTED_TAB_OFFSET;
2553 /* Punch out the updown control */
2554 if (infoPtr->needsScrolling && (rInvalidate.right > 0)) {
2555 RECT r;
2556 GetClientRect(infoPtr->hwndUpDown, &r);
2557 if (rInvalidate.right > clientRect.right - r.left)
2558 rInvalidate.right = rInvalidate.right - (r.right - r.left);
2559 else
2560 rInvalidate.right = clientRect.right - r.left;
2563 TRACE("invalidate (%s)\n", wine_dbgstr_rect(&rInvalidate));
2565 InvalidateRect(infoPtr->hwnd, &rInvalidate, TRUE);
2568 static inline LRESULT TAB_Paint (TAB_INFO *infoPtr, HDC hdcPaint)
2570 HDC hdc;
2571 PAINTSTRUCT ps;
2573 if (hdcPaint)
2574 hdc = hdcPaint;
2575 else
2577 hdc = BeginPaint (infoPtr->hwnd, &ps);
2578 TRACE("erase %d, rect=(%s)\n", ps.fErase, wine_dbgstr_rect(&ps.rcPaint));
2581 TAB_Refresh (infoPtr, hdc);
2583 if (!hdcPaint)
2584 EndPaint (infoPtr->hwnd, &ps);
2586 return 0;
2589 static LRESULT
2590 TAB_InsertItemT (TAB_INFO *infoPtr, INT iItem, const TCITEMW *pti, BOOL bUnicode)
2592 TAB_ITEM *item;
2593 RECT rect;
2595 GetClientRect (infoPtr->hwnd, &rect);
2596 TRACE("Rect: %p %s\n", infoPtr->hwnd, wine_dbgstr_rect(&rect));
2598 if (iItem < 0) return -1;
2599 if (iItem > infoPtr->uNumItem)
2600 iItem = infoPtr->uNumItem;
2602 TAB_DumpItemExternalT(pti, iItem, bUnicode);
2604 if (!(item = Alloc(TAB_ITEM_SIZE(infoPtr)))) return FALSE;
2605 if (DPA_InsertPtr(infoPtr->items, iItem, item) == -1)
2607 Free(item);
2608 return FALSE;
2611 if (infoPtr->uNumItem == 0)
2612 infoPtr->iSelected = 0;
2613 else if (iItem <= infoPtr->iSelected)
2614 infoPtr->iSelected++;
2616 infoPtr->uNumItem++;
2618 item->pszText = NULL;
2619 if (pti->mask & TCIF_TEXT)
2621 if (bUnicode)
2622 Str_SetPtrW (&item->pszText, pti->pszText);
2623 else
2624 Str_SetPtrAtoW (&item->pszText, (LPSTR)pti->pszText);
2627 if (pti->mask & TCIF_IMAGE)
2628 item->iImage = pti->iImage;
2629 else
2630 item->iImage = -1;
2632 if (pti->mask & TCIF_PARAM)
2633 memcpy(item->extra, &pti->lParam, EXTRA_ITEM_SIZE(infoPtr));
2634 else
2635 memset(item->extra, 0, EXTRA_ITEM_SIZE(infoPtr));
2637 TAB_SetItemBounds(infoPtr);
2638 if (infoPtr->uNumItem > 1)
2639 TAB_InvalidateTabArea(infoPtr);
2640 else
2641 InvalidateRect(infoPtr->hwnd, NULL, TRUE);
2643 /* The last item is always the "new" MSAA object. */
2644 NotifyWinEvent(EVENT_OBJECT_CREATE, infoPtr->hwnd, OBJID_CLIENT, infoPtr->uNumItem);
2646 TRACE("[%p]: added item %d %s\n",
2647 infoPtr->hwnd, iItem, debugstr_w(item->pszText));
2649 /* If we haven't set the current focus yet, set it now. */
2650 if (infoPtr->uFocus == -1)
2651 TAB_SetCurFocus(infoPtr, iItem);
2653 return iItem;
2656 static LRESULT
2657 TAB_SetItemSize (TAB_INFO *infoPtr, INT cx, INT cy)
2659 LONG lResult = 0;
2660 BOOL bNeedPaint = FALSE;
2662 lResult = MAKELONG(infoPtr->tabWidth, infoPtr->tabHeight);
2664 /* UNDOCUMENTED: If requested Width or Height is 0 this means that program wants to use auto size. */
2665 if (infoPtr->dwStyle & TCS_FIXEDWIDTH && (infoPtr->tabWidth != cx))
2667 infoPtr->tabWidth = cx;
2668 bNeedPaint = TRUE;
2671 if (infoPtr->tabHeight != cy)
2673 if ((infoPtr->fHeightSet = (cy != 0)))
2674 infoPtr->tabHeight = cy;
2676 bNeedPaint = TRUE;
2678 TRACE("was h=%d,w=%d, now h=%d,w=%d\n",
2679 HIWORD(lResult), LOWORD(lResult),
2680 infoPtr->tabHeight, infoPtr->tabWidth);
2682 if (bNeedPaint)
2684 TAB_SetItemBounds(infoPtr);
2685 RedrawWindow(infoPtr->hwnd, NULL, NULL, RDW_ERASE | RDW_INVALIDATE | RDW_UPDATENOW);
2688 return lResult;
2691 static inline LRESULT TAB_SetMinTabWidth (TAB_INFO *infoPtr, INT cx)
2693 INT oldcx = 0;
2695 TRACE("(%p,%d)\n", infoPtr, cx);
2697 if (infoPtr->tabMinWidth < 0)
2698 oldcx = DEFAULT_MIN_TAB_WIDTH;
2699 else
2700 oldcx = infoPtr->tabMinWidth;
2701 infoPtr->tabMinWidth = cx;
2702 TAB_SetItemBounds(infoPtr);
2703 return oldcx;
2706 static inline LRESULT
2707 TAB_HighlightItem (TAB_INFO *infoPtr, INT iItem, BOOL fHighlight)
2709 LPDWORD lpState;
2710 DWORD oldState;
2711 RECT r;
2713 TRACE("(%p,%d,%s)\n", infoPtr, iItem, fHighlight ? "true" : "false");
2715 if (iItem < 0 || iItem >= infoPtr->uNumItem)
2716 return FALSE;
2718 lpState = &TAB_GetItem(infoPtr, iItem)->dwState;
2719 oldState = *lpState;
2721 if (fHighlight)
2722 *lpState |= TCIS_HIGHLIGHTED;
2723 else
2724 *lpState &= ~TCIS_HIGHLIGHTED;
2726 if ((oldState != *lpState) && TAB_InternalGetItemRect (infoPtr, iItem, &r, NULL))
2727 InvalidateRect (infoPtr->hwnd, &r, TRUE);
2729 return TRUE;
2732 static LRESULT
2733 TAB_SetItemT (TAB_INFO *infoPtr, INT iItem, LPTCITEMW tabItem, BOOL bUnicode)
2735 TAB_ITEM *wineItem;
2737 TRACE("(%p,%d,%p,%s)\n", infoPtr, iItem, tabItem, bUnicode ? "true" : "false");
2739 if (iItem < 0 || iItem >= infoPtr->uNumItem)
2740 return FALSE;
2742 TAB_DumpItemExternalT(tabItem, iItem, bUnicode);
2744 wineItem = TAB_GetItem(infoPtr, iItem);
2746 if (tabItem->mask & TCIF_IMAGE)
2747 wineItem->iImage = tabItem->iImage;
2749 if (tabItem->mask & TCIF_PARAM)
2750 memcpy(wineItem->extra, &tabItem->lParam, infoPtr->cbInfo);
2752 if (tabItem->mask & TCIF_RTLREADING)
2753 FIXME("TCIF_RTLREADING\n");
2755 if (tabItem->mask & TCIF_STATE)
2756 wineItem->dwState = (wineItem->dwState & ~tabItem->dwStateMask) |
2757 ( tabItem->dwState & tabItem->dwStateMask);
2759 if (tabItem->mask & TCIF_TEXT)
2761 Free(wineItem->pszText);
2762 wineItem->pszText = NULL;
2763 if (bUnicode)
2764 Str_SetPtrW(&wineItem->pszText, tabItem->pszText);
2765 else
2766 Str_SetPtrAtoW(&wineItem->pszText, (LPSTR)tabItem->pszText);
2769 /* Update and repaint tabs */
2770 TAB_SetItemBounds(infoPtr);
2771 TAB_InvalidateTabArea(infoPtr);
2773 return TRUE;
2776 static inline LRESULT TAB_GetItemCount (const TAB_INFO *infoPtr)
2778 TRACE("\n");
2779 return infoPtr->uNumItem;
2783 static LRESULT
2784 TAB_GetItemT (TAB_INFO *infoPtr, INT iItem, LPTCITEMW tabItem, BOOL bUnicode)
2786 TAB_ITEM *wineItem;
2788 TRACE("(%p,%d,%p,%s)\n", infoPtr, iItem, tabItem, bUnicode ? "true" : "false");
2790 if (!tabItem) return FALSE;
2792 if (iItem < 0 || iItem >= infoPtr->uNumItem)
2794 /* init requested fields */
2795 if (tabItem->mask & TCIF_IMAGE) tabItem->iImage = 0;
2796 if (tabItem->mask & TCIF_PARAM) tabItem->lParam = 0;
2797 if (tabItem->mask & TCIF_STATE) tabItem->dwState = 0;
2798 return FALSE;
2801 wineItem = TAB_GetItem(infoPtr, iItem);
2803 if (tabItem->mask & TCIF_IMAGE)
2804 tabItem->iImage = wineItem->iImage;
2806 if (tabItem->mask & TCIF_PARAM)
2807 memcpy(&tabItem->lParam, wineItem->extra, infoPtr->cbInfo);
2809 if (tabItem->mask & TCIF_RTLREADING)
2810 FIXME("TCIF_RTLREADING\n");
2812 if (tabItem->mask & TCIF_STATE)
2813 tabItem->dwState = wineItem->dwState & tabItem->dwStateMask;
2815 if (tabItem->mask & TCIF_TEXT)
2817 if (bUnicode)
2818 Str_GetPtrW (wineItem->pszText, tabItem->pszText, tabItem->cchTextMax);
2819 else
2820 Str_GetPtrWtoA (wineItem->pszText, (LPSTR)tabItem->pszText, tabItem->cchTextMax);
2823 TAB_DumpItemExternalT(tabItem, iItem, bUnicode);
2825 return TRUE;
2829 static LRESULT TAB_DeleteItem (TAB_INFO *infoPtr, INT iItem)
2831 TAB_ITEM *item;
2833 TRACE("(%p, %d)\n", infoPtr, iItem);
2835 if (iItem < 0 || iItem >= infoPtr->uNumItem) return FALSE;
2837 TAB_InvalidateTabArea(infoPtr);
2838 item = TAB_GetItem(infoPtr, iItem);
2839 Free(item->pszText);
2840 Free(item);
2841 infoPtr->uNumItem--;
2842 DPA_DeletePtr(infoPtr->items, iItem);
2844 if (infoPtr->uNumItem == 0)
2846 if (infoPtr->iHotTracked >= 0)
2848 KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
2849 infoPtr->iHotTracked = -1;
2852 infoPtr->iSelected = -1;
2854 else
2856 if (iItem <= infoPtr->iHotTracked)
2858 /* When tabs move left/up, the hot track item may change */
2859 FIXME("Recalc hot track\n");
2863 /* adjust the selected index */
2864 if (iItem == infoPtr->iSelected)
2865 infoPtr->iSelected = -1;
2866 else if (iItem < infoPtr->iSelected)
2867 infoPtr->iSelected--;
2869 /* reposition and repaint tabs */
2870 TAB_SetItemBounds(infoPtr);
2872 /* The last item is always the destroyed MSAA object */
2873 NotifyWinEvent(EVENT_OBJECT_DESTROY, infoPtr->hwnd, OBJID_CLIENT, infoPtr->uNumItem + 1);
2875 return TRUE;
2878 static inline LRESULT TAB_DeleteAllItems (TAB_INFO *infoPtr)
2880 TRACE("(%p)\n", infoPtr);
2881 while (infoPtr->uNumItem)
2882 TAB_DeleteItem (infoPtr, 0);
2883 return TRUE;
2887 static inline LRESULT TAB_GetFont (const TAB_INFO *infoPtr)
2889 TRACE("(%p) returning %p\n", infoPtr, infoPtr->hFont);
2890 return (LRESULT)infoPtr->hFont;
2893 static inline LRESULT TAB_SetFont (TAB_INFO *infoPtr, HFONT hNewFont)
2895 TRACE("(%p,%p)\n", infoPtr, hNewFont);
2897 infoPtr->hFont = hNewFont;
2899 TAB_SetItemBounds(infoPtr);
2901 TAB_InvalidateTabArea(infoPtr);
2903 return 0;
2907 static inline LRESULT TAB_GetImageList (const TAB_INFO *infoPtr)
2909 TRACE("\n");
2910 return (LRESULT)infoPtr->himl;
2913 static inline LRESULT TAB_SetImageList (TAB_INFO *infoPtr, HIMAGELIST himlNew)
2915 HIMAGELIST himlPrev = infoPtr->himl;
2916 TRACE("himl=%p\n", himlNew);
2917 infoPtr->himl = himlNew;
2918 TAB_SetItemBounds(infoPtr);
2919 InvalidateRect(infoPtr->hwnd, NULL, TRUE);
2920 return (LRESULT)himlPrev;
2923 static inline LRESULT TAB_GetUnicodeFormat (const TAB_INFO *infoPtr)
2925 TRACE("(%p)\n", infoPtr);
2926 return infoPtr->bUnicode;
2929 static inline LRESULT TAB_SetUnicodeFormat (TAB_INFO *infoPtr, BOOL bUnicode)
2931 BOOL bTemp = infoPtr->bUnicode;
2933 TRACE("(%p %d)\n", infoPtr, bUnicode);
2934 infoPtr->bUnicode = bUnicode;
2936 return bTemp;
2939 static inline LRESULT TAB_Size (TAB_INFO *infoPtr)
2941 /* I'm not really sure what the following code was meant to do.
2942 This is what it is doing:
2943 When WM_SIZE is sent with SIZE_RESTORED, the control
2944 gets positioned in the top left corner.
2946 RECT parent_rect;
2947 HWND parent;
2948 UINT uPosFlags,cx,cy;
2950 uPosFlags=0;
2951 if (!wParam) {
2952 parent = GetParent (hwnd);
2953 GetClientRect(parent, &parent_rect);
2954 cx=LOWORD (lParam);
2955 cy=HIWORD (lParam);
2956 if (GetWindowLongW(hwnd, GWL_STYLE) & CCS_NORESIZE)
2957 uPosFlags |= (SWP_NOSIZE | SWP_NOMOVE);
2959 SetWindowPos (hwnd, 0, parent_rect.left, parent_rect.top,
2960 cx, cy, uPosFlags | SWP_NOZORDER);
2961 } else {
2962 FIXME("WM_SIZE flag %x %lx not handled\n", wParam, lParam);
2963 } */
2965 /* Recompute the size/position of the tabs. */
2966 TAB_SetItemBounds (infoPtr);
2968 /* Force a repaint of the control. */
2969 InvalidateRect(infoPtr->hwnd, NULL, TRUE);
2971 return 0;
2975 static LRESULT TAB_Create (HWND hwnd, LPARAM lParam)
2977 TAB_INFO *infoPtr;
2978 TEXTMETRICW fontMetrics;
2979 HDC hdc;
2980 HFONT hOldFont;
2981 DWORD style;
2983 infoPtr = Alloc (sizeof(TAB_INFO));
2985 SetWindowLongPtrW(hwnd, 0, (DWORD_PTR)infoPtr);
2987 infoPtr->hwnd = hwnd;
2988 infoPtr->hwndNotify = ((LPCREATESTRUCTW)lParam)->hwndParent;
2989 infoPtr->uNumItem = 0;
2990 infoPtr->uNumRows = 0;
2991 infoPtr->uHItemPadding = 6;
2992 infoPtr->uVItemPadding = 3;
2993 infoPtr->uHItemPadding_s = 6;
2994 infoPtr->uVItemPadding_s = 3;
2995 infoPtr->hFont = 0;
2996 infoPtr->items = DPA_Create(8);
2997 infoPtr->hcurArrow = LoadCursorW (0, (LPWSTR)IDC_ARROW);
2998 infoPtr->iSelected = -1;
2999 infoPtr->iHotTracked = -1;
3000 infoPtr->uFocus = -1;
3001 infoPtr->hwndToolTip = 0;
3002 infoPtr->DoRedraw = TRUE;
3003 infoPtr->needsScrolling = FALSE;
3004 infoPtr->hwndUpDown = 0;
3005 infoPtr->leftmostVisible = 0;
3006 infoPtr->fHeightSet = FALSE;
3007 infoPtr->bUnicode = IsWindowUnicode (hwnd);
3008 infoPtr->cbInfo = sizeof(LPARAM);
3010 TRACE("Created tab control, hwnd [%p]\n", hwnd);
3012 /* The tab control always has the WS_CLIPSIBLINGS style. Even
3013 if you don't specify it in CreateWindow. This is necessary in
3014 order for paint to work correctly. This follows windows behaviour. */
3015 style = GetWindowLongW(hwnd, GWL_STYLE);
3016 if (style & TCS_VERTICAL) style |= TCS_MULTILINE;
3017 style |= WS_CLIPSIBLINGS;
3018 SetWindowLongW(hwnd, GWL_STYLE, style);
3020 infoPtr->dwStyle = style;
3021 infoPtr->exStyle = (style & TCS_FLATBUTTONS) ? TCS_EX_FLATSEPARATORS : 0;
3023 if (infoPtr->dwStyle & TCS_TOOLTIPS) {
3024 /* Create tooltip control */
3025 infoPtr->hwndToolTip =
3026 CreateWindowExW (0, TOOLTIPS_CLASSW, NULL, WS_POPUP,
3027 CW_USEDEFAULT, CW_USEDEFAULT,
3028 CW_USEDEFAULT, CW_USEDEFAULT,
3029 hwnd, 0, 0, 0);
3031 /* Send NM_TOOLTIPSCREATED notification */
3032 if (infoPtr->hwndToolTip) {
3033 NMTOOLTIPSCREATED nmttc;
3035 nmttc.hdr.hwndFrom = hwnd;
3036 nmttc.hdr.idFrom = GetWindowLongPtrW(hwnd, GWLP_ID);
3037 nmttc.hdr.code = NM_TOOLTIPSCREATED;
3038 nmttc.hwndToolTips = infoPtr->hwndToolTip;
3040 SendMessageW (infoPtr->hwndNotify, WM_NOTIFY,
3041 GetWindowLongPtrW(hwnd, GWLP_ID), (LPARAM)&nmttc);
3045 OpenThemeData (infoPtr->hwnd, themeClass);
3048 * We need to get text information so we need a DC and we need to select
3049 * a font.
3051 hdc = GetDC(hwnd);
3052 hOldFont = SelectObject (hdc, GetStockObject (SYSTEM_FONT));
3054 /* Use the system font to determine the initial height of a tab. */
3055 GetTextMetricsW(hdc, &fontMetrics);
3058 * Make sure there is enough space for the letters + growing the
3059 * selected item + extra space for the selected item.
3061 infoPtr->tabHeight = fontMetrics.tmHeight + SELECTED_TAB_OFFSET +
3062 ((infoPtr->dwStyle & TCS_BUTTONS) ? 2 : 1) *
3063 infoPtr->uVItemPadding;
3065 /* Initialize the width of a tab. */
3066 if (infoPtr->dwStyle & TCS_FIXEDWIDTH)
3067 infoPtr->tabWidth = GetDeviceCaps(hdc, LOGPIXELSX);
3069 infoPtr->tabMinWidth = -1;
3071 TRACE("tabH=%d, tabW=%d\n", infoPtr->tabHeight, infoPtr->tabWidth);
3073 SelectObject (hdc, hOldFont);
3074 ReleaseDC(hwnd, hdc);
3076 return 0;
3079 static LRESULT
3080 TAB_Destroy (TAB_INFO *infoPtr)
3082 INT iItem;
3084 SetWindowLongPtrW(infoPtr->hwnd, 0, 0);
3086 for (iItem = infoPtr->uNumItem - 1; iItem >= 0; iItem--)
3088 TAB_ITEM *tab = TAB_GetItem(infoPtr, iItem);
3090 DPA_DeletePtr(infoPtr->items, iItem);
3091 infoPtr->uNumItem--;
3093 Free(tab->pszText);
3094 Free(tab);
3096 DPA_Destroy(infoPtr->items);
3097 infoPtr->items = NULL;
3099 if (infoPtr->hwndToolTip)
3100 DestroyWindow (infoPtr->hwndToolTip);
3102 if (infoPtr->hwndUpDown)
3103 DestroyWindow(infoPtr->hwndUpDown);
3105 if (infoPtr->iHotTracked >= 0)
3106 KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
3108 CloseThemeData (GetWindowTheme (infoPtr->hwnd));
3110 Free (infoPtr);
3111 return 0;
3114 /* update theme after a WM_THEMECHANGED message */
3115 static LRESULT theme_changed(const TAB_INFO *infoPtr)
3117 HTHEME theme = GetWindowTheme (infoPtr->hwnd);
3118 CloseThemeData (theme);
3119 OpenThemeData (infoPtr->hwnd, themeClass);
3120 InvalidateRect (infoPtr->hwnd, NULL, TRUE);
3121 return 0;
3124 static LRESULT TAB_NCCalcSize(WPARAM wParam)
3126 if (!wParam)
3127 return 0;
3128 return WVR_ALIGNTOP;
3131 static inline LRESULT
3132 TAB_SetItemExtra (TAB_INFO *infoPtr, INT cbInfo)
3134 TRACE("(%p %d)\n", infoPtr, cbInfo);
3136 if (cbInfo < 0 || infoPtr->uNumItem) return FALSE;
3138 infoPtr->cbInfo = cbInfo;
3139 return TRUE;
3142 static LRESULT TAB_RemoveImage (TAB_INFO *infoPtr, INT image)
3144 TRACE("%p %d\n", infoPtr, image);
3146 if (ImageList_Remove (infoPtr->himl, image))
3148 INT i, *idx;
3149 RECT r;
3151 /* shift indices, repaint items if needed */
3152 for (i = 0; i < infoPtr->uNumItem; i++)
3154 idx = &TAB_GetItem(infoPtr, i)->iImage;
3155 if (*idx >= image)
3157 if (*idx == image)
3158 *idx = -1;
3159 else
3160 (*idx)--;
3162 /* repaint item */
3163 if (TAB_InternalGetItemRect (infoPtr, i, &r, NULL))
3164 InvalidateRect (infoPtr->hwnd, &r, TRUE);
3169 return 0;
3172 static LRESULT
3173 TAB_SetExtendedStyle (TAB_INFO *infoPtr, DWORD exMask, DWORD exStyle)
3175 DWORD prevstyle = infoPtr->exStyle;
3177 /* zero mask means all styles */
3178 if (exMask == 0) exMask = ~0;
3180 if (exMask & TCS_EX_REGISTERDROP)
3182 FIXME("TCS_EX_REGISTERDROP style unimplemented\n");
3183 exMask &= ~TCS_EX_REGISTERDROP;
3184 exStyle &= ~TCS_EX_REGISTERDROP;
3187 if (exMask & TCS_EX_FLATSEPARATORS)
3189 if ((prevstyle ^ exStyle) & TCS_EX_FLATSEPARATORS)
3191 infoPtr->exStyle ^= TCS_EX_FLATSEPARATORS;
3192 TAB_InvalidateTabArea(infoPtr);
3196 return prevstyle;
3199 static inline LRESULT
3200 TAB_GetExtendedStyle (const TAB_INFO *infoPtr)
3202 return infoPtr->exStyle;
3205 static LRESULT
3206 TAB_DeselectAll (TAB_INFO *infoPtr, BOOL excludesel)
3208 BOOL paint = FALSE;
3209 INT i, selected = infoPtr->iSelected;
3211 TRACE("(%p, %d)\n", infoPtr, excludesel);
3213 if (!(infoPtr->dwStyle & TCS_BUTTONS))
3214 return 0;
3216 for (i = 0; i < infoPtr->uNumItem; i++)
3218 if ((TAB_GetItem(infoPtr, i)->dwState & TCIS_BUTTONPRESSED) &&
3219 (selected != i))
3221 TAB_GetItem(infoPtr, i)->dwState &= ~TCIS_BUTTONPRESSED;
3222 paint = TRUE;
3226 if (!excludesel && (selected != -1))
3228 TAB_GetItem(infoPtr, selected)->dwState &= ~TCIS_BUTTONPRESSED;
3229 infoPtr->iSelected = -1;
3230 paint = TRUE;
3233 if (paint)
3234 TAB_InvalidateTabArea (infoPtr);
3236 return 0;
3239 /***
3240 * DESCRIPTION:
3241 * Processes WM_STYLECHANGED messages.
3243 * PARAMETER(S):
3244 * [I] infoPtr : valid pointer to the tab data structure
3245 * [I] wStyleType : window style type (normal or extended)
3246 * [I] lpss : window style information
3248 * RETURN:
3249 * Zero
3251 static INT TAB_StyleChanged(TAB_INFO *infoPtr, WPARAM wStyleType,
3252 const STYLESTRUCT *lpss)
3254 TRACE("style type %Ix, styleOld %#lx, styleNew %#lx\n", wStyleType, lpss->styleOld, lpss->styleNew);
3256 if (wStyleType != GWL_STYLE) return 0;
3258 infoPtr->dwStyle = lpss->styleNew;
3260 TAB_SetItemBounds (infoPtr);
3262 return 0;
3265 static LRESULT WINAPI
3266 TAB_WindowProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
3268 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
3270 TRACE("hwnd %p, msg %x, wParam %Ix, lParam %Ix\n", hwnd, uMsg, wParam, lParam);
3272 if (!infoPtr && (uMsg != WM_CREATE))
3273 return DefWindowProcW (hwnd, uMsg, wParam, lParam);
3275 switch (uMsg)
3277 case TCM_GETIMAGELIST:
3278 return TAB_GetImageList (infoPtr);
3280 case TCM_SETIMAGELIST:
3281 return TAB_SetImageList (infoPtr, (HIMAGELIST)lParam);
3283 case TCM_GETITEMCOUNT:
3284 return TAB_GetItemCount (infoPtr);
3286 case TCM_GETITEMA:
3287 case TCM_GETITEMW:
3288 return TAB_GetItemT (infoPtr, (INT)wParam, (LPTCITEMW)lParam, uMsg == TCM_GETITEMW);
3290 case TCM_SETITEMA:
3291 case TCM_SETITEMW:
3292 return TAB_SetItemT (infoPtr, (INT)wParam, (LPTCITEMW)lParam, uMsg == TCM_SETITEMW);
3294 case TCM_DELETEITEM:
3295 return TAB_DeleteItem (infoPtr, (INT)wParam);
3297 case TCM_DELETEALLITEMS:
3298 return TAB_DeleteAllItems (infoPtr);
3300 case TCM_GETITEMRECT:
3301 return TAB_GetItemRect (infoPtr, (INT)wParam, (LPRECT)lParam);
3303 case TCM_GETCURSEL:
3304 return TAB_GetCurSel (infoPtr);
3306 case TCM_HITTEST:
3307 return TAB_HitTest (infoPtr, (LPTCHITTESTINFO)lParam);
3309 case TCM_SETCURSEL:
3310 return TAB_SetCurSel (infoPtr, (INT)wParam);
3312 case TCM_INSERTITEMA:
3313 case TCM_INSERTITEMW:
3314 return TAB_InsertItemT (infoPtr, (INT)wParam, (TCITEMW*)lParam, uMsg == TCM_INSERTITEMW);
3316 case TCM_SETITEMEXTRA:
3317 return TAB_SetItemExtra (infoPtr, (INT)wParam);
3319 case TCM_ADJUSTRECT:
3320 return TAB_AdjustRect (infoPtr, (BOOL)wParam, (LPRECT)lParam);
3322 case TCM_SETITEMSIZE:
3323 return TAB_SetItemSize (infoPtr, (INT)LOWORD(lParam), (INT)HIWORD(lParam));
3325 case TCM_REMOVEIMAGE:
3326 return TAB_RemoveImage (infoPtr, (INT)wParam);
3328 case TCM_SETPADDING:
3329 return TAB_SetPadding (infoPtr, lParam);
3331 case TCM_GETROWCOUNT:
3332 return TAB_GetRowCount(infoPtr);
3334 case TCM_GETUNICODEFORMAT:
3335 return TAB_GetUnicodeFormat (infoPtr);
3337 case TCM_SETUNICODEFORMAT:
3338 return TAB_SetUnicodeFormat (infoPtr, (BOOL)wParam);
3340 case TCM_HIGHLIGHTITEM:
3341 return TAB_HighlightItem (infoPtr, (INT)wParam, (BOOL)LOWORD(lParam));
3343 case TCM_GETTOOLTIPS:
3344 return TAB_GetToolTips (infoPtr);
3346 case TCM_SETTOOLTIPS:
3347 return TAB_SetToolTips (infoPtr, (HWND)wParam);
3349 case TCM_GETCURFOCUS:
3350 return TAB_GetCurFocus (infoPtr);
3352 case TCM_SETCURFOCUS:
3353 return TAB_SetCurFocus (infoPtr, (INT)wParam);
3355 case TCM_SETMINTABWIDTH:
3356 return TAB_SetMinTabWidth(infoPtr, (INT)lParam);
3358 case TCM_DESELECTALL:
3359 return TAB_DeselectAll (infoPtr, (BOOL)wParam);
3361 case TCM_GETEXTENDEDSTYLE:
3362 return TAB_GetExtendedStyle (infoPtr);
3364 case TCM_SETEXTENDEDSTYLE:
3365 return TAB_SetExtendedStyle (infoPtr, wParam, lParam);
3367 case WM_GETFONT:
3368 return TAB_GetFont (infoPtr);
3370 case WM_SETFONT:
3371 return TAB_SetFont (infoPtr, (HFONT)wParam);
3373 case WM_CREATE:
3374 return TAB_Create (hwnd, lParam);
3376 case WM_NCDESTROY:
3377 return TAB_Destroy (infoPtr);
3379 case WM_GETDLGCODE:
3380 return DLGC_WANTARROWS | DLGC_WANTCHARS;
3382 case WM_LBUTTONDOWN:
3383 return TAB_LButtonDown (infoPtr, wParam, lParam);
3385 case WM_LBUTTONUP:
3386 return TAB_LButtonUp (infoPtr);
3388 case WM_NOTIFY:
3389 return SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, wParam, lParam);
3391 case WM_RBUTTONUP:
3392 TAB_RButtonUp (infoPtr);
3393 return DefWindowProcW (hwnd, uMsg, wParam, lParam);
3395 case WM_MOUSEMOVE:
3396 return TAB_MouseMove (infoPtr, wParam, lParam);
3398 case WM_PRINTCLIENT:
3399 case WM_PAINT:
3400 return TAB_Paint (infoPtr, (HDC)wParam);
3402 case WM_SIZE:
3403 return TAB_Size (infoPtr);
3405 case WM_SETREDRAW:
3406 return TAB_SetRedraw (infoPtr, (BOOL)wParam);
3408 case WM_HSCROLL:
3409 return TAB_OnHScroll(infoPtr, (int)LOWORD(wParam), (int)HIWORD(wParam));
3411 case WM_STYLECHANGED:
3412 return TAB_StyleChanged(infoPtr, wParam, (LPSTYLESTRUCT)lParam);
3414 case WM_SYSCOLORCHANGE:
3415 COMCTL32_RefreshSysColors();
3416 return 0;
3418 case WM_THEMECHANGED:
3419 return theme_changed (infoPtr);
3421 case WM_KILLFOCUS:
3422 TAB_KillFocus(infoPtr);
3423 case WM_SETFOCUS:
3424 TAB_FocusChanging(infoPtr);
3425 break; /* Don't disturb normal focus behavior */
3427 case WM_KEYDOWN:
3428 return TAB_KeyDown(infoPtr, wParam, lParam);
3430 case WM_NCHITTEST:
3431 return TAB_NCHitTest(infoPtr, lParam);
3433 case WM_NCCALCSIZE:
3434 return TAB_NCCalcSize(wParam);
3436 default:
3437 if (uMsg >= WM_USER && uMsg < WM_APP && !COMCTL32_IsReflectedMessage(uMsg))
3438 WARN("unknown msg %04x wp %Ix, lp %Ix\n", uMsg, wParam, lParam);
3439 break;
3441 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
3445 void
3446 TAB_Register (void)
3448 WNDCLASSW wndClass;
3450 ZeroMemory (&wndClass, sizeof(WNDCLASSW));
3451 wndClass.style = CS_GLOBALCLASS | CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
3452 wndClass.lpfnWndProc = TAB_WindowProc;
3453 wndClass.cbClsExtra = 0;
3454 wndClass.cbWndExtra = sizeof(TAB_INFO *);
3455 wndClass.hCursor = LoadCursorW (0, (LPWSTR)IDC_ARROW);
3456 wndClass.hbrBackground = (HBRUSH)(COLOR_BTNFACE+1);
3457 wndClass.lpszClassName = WC_TABCONTROLW;
3459 RegisterClassW (&wndClass);
3463 void
3464 TAB_Unregister (void)
3466 UnregisterClassW (WC_TABCONTROLW, NULL);