mfplat: Remove duplicated GUID entry from attribute tracing.
[wine/zf.git] / dlls / comctl32 / tab.c
blob3bb5310d1f3d4af58da60167cff78671cd863f2d
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=0x%08x, dwStateMask=0x%08x, cchTextMax=0x%08x\n",
207 iItem, pti->mask, pti->dwState, pti->dwStateMask, pti->cchTextMax);
208 TRACE("external tab %d, iImage=%d, lParam=0x%08lx, pszTextW=%s\n",
209 iItem, pti->iImage, pti->lParam, isW ? debugstr_w(pti->pszText) : debugstr_a((LPSTR)pti->pszText));
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=0x%08x, pszText=%s, iImage=%d\n",
220 iItem, ti->dwState, debugstr_w(ti->pszText), ti->iImage);
221 TRACE("tab %d, rect.left=%d, rect.top(row)=%d\n",
222 iItem, ti->rect.left, ti->rect.top);
226 /* RETURNS
227 * the index of the selected tab, or -1 if no tab is selected. */
228 static inline LRESULT TAB_GetCurSel (const TAB_INFO *infoPtr)
230 TRACE("(%p)\n", infoPtr);
231 return infoPtr->iSelected;
234 /* RETURNS
235 * the index of the tab item that has the focus. */
236 static inline LRESULT
237 TAB_GetCurFocus (const TAB_INFO *infoPtr)
239 TRACE("(%p)\n", infoPtr);
240 return infoPtr->uFocus;
243 static inline LRESULT TAB_GetToolTips (const TAB_INFO *infoPtr)
245 TRACE("(%p)\n", infoPtr);
246 return (LRESULT)infoPtr->hwndToolTip;
249 static inline LRESULT TAB_SetCurSel (TAB_INFO *infoPtr, INT iItem)
251 INT prevItem = infoPtr->iSelected;
253 TRACE("(%p %d)\n", infoPtr, iItem);
255 if (iItem >= (INT)infoPtr->uNumItem)
256 return -1;
258 if (prevItem != iItem) {
259 if (prevItem != -1)
260 TAB_GetItem(infoPtr, prevItem)->dwState &= ~TCIS_BUTTONPRESSED;
262 if (iItem >= 0)
264 TAB_GetItem(infoPtr, iItem)->dwState |= TCIS_BUTTONPRESSED;
265 infoPtr->iSelected = iItem;
266 infoPtr->uFocus = iItem;
268 else
270 infoPtr->iSelected = -1;
271 infoPtr->uFocus = -1;
274 TAB_EnsureSelectionVisible(infoPtr);
275 TAB_InvalidateTabArea(infoPtr);
278 return prevItem;
281 static LRESULT TAB_SetCurFocus (TAB_INFO *infoPtr, INT iItem)
283 TRACE("(%p %d)\n", infoPtr, iItem);
285 if (iItem < 0) {
286 infoPtr->uFocus = -1;
287 if (infoPtr->iSelected != -1) {
288 infoPtr->iSelected = -1;
289 TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGE);
290 TAB_InvalidateTabArea(infoPtr);
293 else if (iItem < infoPtr->uNumItem) {
294 if (infoPtr->dwStyle & TCS_BUTTONS) {
295 /* set focus to new item, leave selection as is */
296 if (infoPtr->uFocus != iItem) {
297 INT prev_focus = infoPtr->uFocus;
298 RECT r;
300 infoPtr->uFocus = iItem;
302 if (prev_focus != infoPtr->iSelected) {
303 if (TAB_InternalGetItemRect(infoPtr, prev_focus, &r, NULL))
304 InvalidateRect(infoPtr->hwnd, &r, FALSE);
307 if (TAB_InternalGetItemRect(infoPtr, iItem, &r, NULL))
308 InvalidateRect(infoPtr->hwnd, &r, FALSE);
310 TAB_SendSimpleNotify(infoPtr, TCN_FOCUSCHANGE);
312 } else {
313 INT oldFocus = infoPtr->uFocus;
314 if (infoPtr->iSelected != iItem || oldFocus == -1 ) {
315 infoPtr->uFocus = iItem;
316 if (oldFocus != -1) {
317 if (!TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGING)) {
318 infoPtr->iSelected = iItem;
319 TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGE);
321 else
322 infoPtr->iSelected = iItem;
323 TAB_EnsureSelectionVisible(infoPtr);
324 TAB_InvalidateTabArea(infoPtr);
329 return 0;
332 static inline LRESULT
333 TAB_SetToolTips (TAB_INFO *infoPtr, HWND hwndToolTip)
335 TRACE("%p %p\n", infoPtr, hwndToolTip);
336 infoPtr->hwndToolTip = hwndToolTip;
337 return 0;
340 static inline LRESULT
341 TAB_SetPadding (TAB_INFO *infoPtr, LPARAM lParam)
343 TRACE("(%p %d %d)\n", infoPtr, LOWORD(lParam), HIWORD(lParam));
344 infoPtr->uHItemPadding_s = LOWORD(lParam);
345 infoPtr->uVItemPadding_s = HIWORD(lParam);
347 return 0;
350 /******************************************************************************
351 * TAB_InternalGetItemRect
353 * This method will calculate the rectangle representing a given tab item in
354 * client coordinates. This method takes scrolling into account.
356 * This method returns TRUE if the item is visible in the window and FALSE
357 * if it is completely outside the client area.
359 static BOOL TAB_InternalGetItemRect(
360 const TAB_INFO* infoPtr,
361 INT itemIndex,
362 RECT* itemRect,
363 RECT* selectedRect)
365 RECT tmpItemRect,clientRect;
367 /* Perform a sanity check and a trivial visibility check. */
368 if ( (infoPtr->uNumItem <= 0) ||
369 (itemIndex >= infoPtr->uNumItem) ||
370 (!(((infoPtr->dwStyle & TCS_MULTILINE) || (infoPtr->dwStyle & TCS_VERTICAL))) &&
371 (itemIndex < infoPtr->leftmostVisible)))
373 TRACE("Not Visible\n");
374 SetRect(itemRect, 0, 0, 0, infoPtr->tabHeight);
375 SetRectEmpty(selectedRect);
376 return FALSE;
380 * Avoid special cases in this procedure by assigning the "out"
381 * parameters if the caller didn't supply them
383 if (itemRect == NULL)
384 itemRect = &tmpItemRect;
386 /* Retrieve the unmodified item rect. */
387 *itemRect = TAB_GetItem(infoPtr,itemIndex)->rect;
389 /* calculate the times bottom and top based on the row */
390 GetClientRect(infoPtr->hwnd, &clientRect);
392 if ((infoPtr->dwStyle & TCS_BOTTOM) && (infoPtr->dwStyle & TCS_VERTICAL))
394 itemRect->right = clientRect.right - SELECTED_TAB_OFFSET - itemRect->left * infoPtr->tabHeight -
395 ((infoPtr->dwStyle & TCS_BUTTONS) ? itemRect->left * BUTTON_SPACINGX : 0);
396 itemRect->left = itemRect->right - infoPtr->tabHeight;
398 else if (infoPtr->dwStyle & TCS_VERTICAL)
400 itemRect->left = clientRect.left + SELECTED_TAB_OFFSET + itemRect->left * infoPtr->tabHeight +
401 ((infoPtr->dwStyle & TCS_BUTTONS) ? itemRect->left * BUTTON_SPACINGX : 0);
402 itemRect->right = itemRect->left + infoPtr->tabHeight;
404 else if (infoPtr->dwStyle & TCS_BOTTOM)
406 itemRect->bottom = clientRect.bottom - itemRect->top * infoPtr->tabHeight -
407 ((infoPtr->dwStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : SELECTED_TAB_OFFSET);
408 itemRect->top = itemRect->bottom - infoPtr->tabHeight;
410 else /* not TCS_BOTTOM and not TCS_VERTICAL */
412 itemRect->top = clientRect.top + itemRect->top * infoPtr->tabHeight +
413 ((infoPtr->dwStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : SELECTED_TAB_OFFSET);
414 itemRect->bottom = itemRect->top + infoPtr->tabHeight;
418 * "scroll" it to make sure the item at the very left of the
419 * tab control is the leftmost visible tab.
421 if(infoPtr->dwStyle & TCS_VERTICAL)
423 OffsetRect(itemRect,
425 -TAB_GetItem(infoPtr, infoPtr->leftmostVisible)->rect.top);
428 * Move the rectangle so the first item is slightly offset from
429 * the bottom of the tab control.
431 OffsetRect(itemRect,
433 SELECTED_TAB_OFFSET);
435 } else
437 OffsetRect(itemRect,
438 -TAB_GetItem(infoPtr, infoPtr->leftmostVisible)->rect.left,
442 * Move the rectangle so the first item is slightly offset from
443 * the left of the tab control.
445 OffsetRect(itemRect,
446 SELECTED_TAB_OFFSET,
449 TRACE("item %d tab h=%d, rect=(%s)\n",
450 itemIndex, infoPtr->tabHeight, wine_dbgstr_rect(itemRect));
452 /* Now, calculate the position of the item as if it were selected. */
453 if (selectedRect!=NULL)
455 *selectedRect = *itemRect;
457 /* The rectangle of a selected item is a bit wider. */
458 if(infoPtr->dwStyle & TCS_VERTICAL)
459 InflateRect(selectedRect, 0, SELECTED_TAB_OFFSET);
460 else
461 InflateRect(selectedRect, SELECTED_TAB_OFFSET, 0);
463 /* If it also a bit higher. */
464 if ((infoPtr->dwStyle & TCS_BOTTOM) && (infoPtr->dwStyle & TCS_VERTICAL))
466 selectedRect->left -= 2; /* the border is thicker on the right */
467 selectedRect->right += SELECTED_TAB_OFFSET;
469 else if (infoPtr->dwStyle & TCS_VERTICAL)
471 selectedRect->left -= SELECTED_TAB_OFFSET;
472 selectedRect->right += 1;
474 else if (infoPtr->dwStyle & TCS_BOTTOM)
476 selectedRect->bottom += SELECTED_TAB_OFFSET;
478 else /* not TCS_BOTTOM and not TCS_VERTICAL */
480 selectedRect->top -= SELECTED_TAB_OFFSET;
481 selectedRect->bottom -= 1;
485 /* Check for visibility */
486 if (infoPtr->dwStyle & TCS_VERTICAL)
487 return (itemRect->top < clientRect.bottom) && (itemRect->bottom > clientRect.top);
488 else
489 return (itemRect->left < clientRect.right) && (itemRect->right > clientRect.left);
492 static inline BOOL
493 TAB_GetItemRect(const TAB_INFO *infoPtr, INT item, RECT *rect)
495 TRACE("(%p, %d, %p)\n", infoPtr, item, rect);
496 return TAB_InternalGetItemRect(infoPtr, item, rect, NULL);
499 /******************************************************************************
500 * TAB_KeyDown
502 * This method is called to handle keyboard input
504 static LRESULT TAB_KeyDown(TAB_INFO* infoPtr, WPARAM keyCode, LPARAM lParam)
506 INT newItem = -1;
507 NMTCKEYDOWN nm;
509 /* TCN_KEYDOWN notification sent always */
510 nm.hdr.hwndFrom = infoPtr->hwnd;
511 nm.hdr.idFrom = GetWindowLongPtrW(infoPtr->hwnd, GWLP_ID);
512 nm.hdr.code = TCN_KEYDOWN;
513 nm.wVKey = keyCode;
514 nm.flags = lParam;
515 SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, nm.hdr.idFrom, (LPARAM)&nm);
517 switch (keyCode)
519 case VK_LEFT:
520 newItem = infoPtr->uFocus - 1;
521 break;
522 case VK_RIGHT:
523 newItem = infoPtr->uFocus + 1;
524 break;
527 /* If we changed to a valid item, change focused item */
528 if (newItem >= 0 && newItem < infoPtr->uNumItem && infoPtr->uFocus != newItem)
529 TAB_SetCurFocus(infoPtr, newItem);
531 return 0;
535 * WM_KILLFOCUS handler
537 static void TAB_KillFocus(TAB_INFO *infoPtr)
539 /* clear current focused item back to selected for TCS_BUTTONS */
540 if ((infoPtr->dwStyle & TCS_BUTTONS) && (infoPtr->uFocus != infoPtr->iSelected))
542 RECT r;
544 if (TAB_InternalGetItemRect(infoPtr, infoPtr->uFocus, &r, NULL))
545 InvalidateRect(infoPtr->hwnd, &r, FALSE);
547 infoPtr->uFocus = infoPtr->iSelected;
551 /******************************************************************************
552 * TAB_FocusChanging
554 * This method is called whenever the focus goes in or out of this control
555 * it is used to update the visual state of the control.
557 static void TAB_FocusChanging(const TAB_INFO *infoPtr)
559 RECT selectedRect;
560 BOOL isVisible;
563 * Get the rectangle for the item.
565 isVisible = TAB_InternalGetItemRect(infoPtr,
566 infoPtr->uFocus,
567 NULL,
568 &selectedRect);
571 * If the rectangle is not completely invisible, invalidate that
572 * portion of the window.
574 if (isVisible)
576 TRACE("invalidate (%s)\n", wine_dbgstr_rect(&selectedRect));
577 InvalidateRect(infoPtr->hwnd, &selectedRect, TRUE);
581 static INT TAB_InternalHitTest (const TAB_INFO *infoPtr, POINT pt, UINT *flags)
583 RECT rect;
584 INT iCount;
586 for (iCount = 0; iCount < infoPtr->uNumItem; iCount++)
588 TAB_InternalGetItemRect(infoPtr, iCount, &rect, NULL);
590 if (PtInRect(&rect, pt))
592 *flags = TCHT_ONITEM;
593 return iCount;
597 *flags = TCHT_NOWHERE;
598 return -1;
601 static inline LRESULT
602 TAB_HitTest (const TAB_INFO *infoPtr, LPTCHITTESTINFO lptest)
604 TRACE("(%p, %p)\n", infoPtr, lptest);
605 return TAB_InternalHitTest (infoPtr, lptest->pt, &lptest->flags);
608 /******************************************************************************
609 * TAB_NCHitTest
611 * Napster v2b5 has a tab control for its main navigation which has a client
612 * area that covers the whole area of the dialog pages.
613 * That's why it receives all msgs for that area and the underlying dialog ctrls
614 * are dead.
615 * So I decided that we should handle WM_NCHITTEST here and return
616 * HTTRANSPARENT if we don't hit the tab control buttons.
617 * FIXME: WM_NCHITTEST handling correct ? Fix it if you know that Windows
618 * doesn't do it that way. Maybe depends on tab control styles ?
620 static inline LRESULT
621 TAB_NCHitTest (const TAB_INFO *infoPtr, LPARAM lParam)
623 POINT pt;
624 UINT dummyflag;
626 pt.x = (short)LOWORD(lParam);
627 pt.y = (short)HIWORD(lParam);
628 ScreenToClient(infoPtr->hwnd, &pt);
630 if (TAB_InternalHitTest(infoPtr, pt, &dummyflag) == -1)
631 return HTTRANSPARENT;
632 else
633 return HTCLIENT;
636 static LRESULT
637 TAB_LButtonDown (TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
639 POINT pt;
640 INT newItem;
641 UINT dummy;
643 if (infoPtr->hwndToolTip)
644 TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
645 WM_LBUTTONDOWN, wParam, lParam);
647 if (!(infoPtr->dwStyle & TCS_FOCUSNEVER)) {
648 SetFocus (infoPtr->hwnd);
651 if (infoPtr->hwndToolTip)
652 TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
653 WM_LBUTTONDOWN, wParam, lParam);
655 pt.x = (short)LOWORD(lParam);
656 pt.y = (short)HIWORD(lParam);
658 newItem = TAB_InternalHitTest (infoPtr, pt, &dummy);
660 TRACE("On Tab, item %d\n", newItem);
662 if ((newItem != -1) && (infoPtr->iSelected != newItem))
664 if ((infoPtr->dwStyle & TCS_BUTTONS) && (infoPtr->dwStyle & TCS_MULTISELECT) &&
665 (wParam & MK_CONTROL))
667 RECT r;
669 /* toggle multiselection */
670 TAB_GetItem(infoPtr, newItem)->dwState ^= TCIS_BUTTONPRESSED;
671 if (TAB_InternalGetItemRect (infoPtr, newItem, &r, NULL))
672 InvalidateRect (infoPtr->hwnd, &r, TRUE);
674 else
676 INT i;
677 BOOL pressed = FALSE;
679 /* any button pressed ? */
680 for (i = 0; i < infoPtr->uNumItem; i++)
681 if ((TAB_GetItem (infoPtr, i)->dwState & TCIS_BUTTONPRESSED) &&
682 (infoPtr->iSelected != i))
684 pressed = TRUE;
685 break;
688 if (TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGING))
689 return 0;
691 if (pressed)
692 TAB_DeselectAll (infoPtr, FALSE);
693 else
694 TAB_SetCurSel(infoPtr, newItem);
696 TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGE);
700 return 0;
703 static inline LRESULT
704 TAB_LButtonUp (const TAB_INFO *infoPtr)
706 TAB_SendSimpleNotify(infoPtr, NM_CLICK);
708 return 0;
711 static inline void
712 TAB_RButtonUp (const TAB_INFO *infoPtr)
714 TAB_SendSimpleNotify(infoPtr, NM_RCLICK);
717 /******************************************************************************
718 * TAB_DrawLoneItemInterior
720 * This calls TAB_DrawItemInterior. However, TAB_DrawItemInterior is normally
721 * called by TAB_DrawItem which is normally called by TAB_Refresh which sets
722 * up the device context and font. This routine does the same setup but
723 * only calls TAB_DrawItemInterior for the single specified item.
725 static void
726 TAB_DrawLoneItemInterior(const TAB_INFO* infoPtr, int iItem)
728 HDC hdc = GetDC(infoPtr->hwnd);
729 RECT r, rC;
731 /* Clip UpDown control to not draw over it */
732 if (infoPtr->needsScrolling)
734 GetWindowRect(infoPtr->hwnd, &rC);
735 GetWindowRect(infoPtr->hwndUpDown, &r);
736 ExcludeClipRect(hdc, r.left - rC.left, r.top - rC.top, r.right - rC.left, r.bottom - rC.top);
738 TAB_DrawItemInterior(infoPtr, hdc, iItem, NULL);
739 ReleaseDC(infoPtr->hwnd, hdc);
742 /* update a tab after hottracking - invalidate it or just redraw the interior,
743 * based on whether theming is used or not */
744 static inline void hottrack_refresh(const TAB_INFO *infoPtr, int tabIndex)
746 if (tabIndex == -1) return;
748 if (GetWindowTheme (infoPtr->hwnd))
750 RECT rect;
751 TAB_InternalGetItemRect(infoPtr, tabIndex, &rect, NULL);
752 InvalidateRect (infoPtr->hwnd, &rect, FALSE);
754 else
755 TAB_DrawLoneItemInterior(infoPtr, tabIndex);
758 /******************************************************************************
759 * TAB_HotTrackTimerProc
761 * When a mouse-move event causes a tab to be highlighted (hot-tracking), a
762 * timer is setup so we can check if the mouse is moved out of our window.
763 * (We don't get an event when the mouse leaves, the mouse-move events just
764 * stop being delivered to our window and just start being delivered to
765 * another window.) This function is called when the timer triggers so
766 * we can check if the mouse has left our window. If so, we un-highlight
767 * the hot-tracked tab.
769 static void CALLBACK
770 TAB_HotTrackTimerProc
772 HWND hwnd, /* handle of window for timer messages */
773 UINT uMsg, /* WM_TIMER message */
774 UINT_PTR idEvent, /* timer identifier */
775 DWORD dwTime /* current system time */
778 TAB_INFO* infoPtr = TAB_GetInfoPtr(hwnd);
780 if (infoPtr != NULL && infoPtr->iHotTracked >= 0)
782 POINT pt;
785 ** If we can't get the cursor position, or if the cursor is outside our
786 ** window, we un-highlight the hot-tracked tab. Note that the cursor is
787 ** "outside" even if it is within our bounding rect if another window
788 ** overlaps. Note also that the case where the cursor stayed within our
789 ** window but has moved off the hot-tracked tab will be handled by the
790 ** WM_MOUSEMOVE event.
792 if (!GetCursorPos(&pt) || WindowFromPoint(pt) != hwnd)
794 /* Redraw iHotTracked to look normal */
795 INT iRedraw = infoPtr->iHotTracked;
796 infoPtr->iHotTracked = -1;
797 hottrack_refresh (infoPtr, iRedraw);
799 /* Kill this timer */
800 KillTimer(hwnd, TAB_HOTTRACK_TIMER);
805 /******************************************************************************
806 * TAB_RecalcHotTrack
808 * If a tab control has the TCS_HOTTRACK style, then the tab under the mouse
809 * should be highlighted. This function determines which tab in a tab control,
810 * if any, is under the mouse and records that information. The caller may
811 * supply output parameters to receive the item number of the tab item which
812 * was highlighted but isn't any longer and of the tab item which is now
813 * highlighted but wasn't previously. The caller can use this information to
814 * selectively redraw those tab items.
816 * If the caller has a mouse position, it can supply it through the pos
817 * parameter. For example, TAB_MouseMove does this. Otherwise, the caller
818 * supplies NULL and this function determines the current mouse position
819 * itself.
821 static void
822 TAB_RecalcHotTrack
824 TAB_INFO* infoPtr,
825 const LPARAM* pos,
826 int* out_redrawLeave,
827 int* out_redrawEnter
830 int item = -1;
833 if (out_redrawLeave != NULL)
834 *out_redrawLeave = -1;
835 if (out_redrawEnter != NULL)
836 *out_redrawEnter = -1;
838 if ((infoPtr->dwStyle & TCS_HOTTRACK) || GetWindowTheme(infoPtr->hwnd))
840 POINT pt;
841 UINT flags;
843 if (pos == NULL)
845 GetCursorPos(&pt);
846 ScreenToClient(infoPtr->hwnd, &pt);
848 else
850 pt.x = (short)LOWORD(*pos);
851 pt.y = (short)HIWORD(*pos);
854 item = TAB_InternalHitTest(infoPtr, pt, &flags);
857 if (item != infoPtr->iHotTracked)
859 if (infoPtr->iHotTracked >= 0)
861 /* Mark currently hot-tracked to be redrawn to look normal */
862 if (out_redrawLeave != NULL)
863 *out_redrawLeave = infoPtr->iHotTracked;
865 if (item < 0)
867 /* Kill timer which forces recheck of mouse pos */
868 KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
871 else
873 /* Start timer so we recheck mouse pos */
874 UINT timerID = SetTimer
876 infoPtr->hwnd,
877 TAB_HOTTRACK_TIMER,
878 TAB_HOTTRACK_TIMER_INTERVAL,
879 TAB_HotTrackTimerProc
882 if (timerID == 0)
883 return; /* Hot tracking not available */
886 infoPtr->iHotTracked = item;
888 if (item >= 0)
890 /* Mark new hot-tracked to be redrawn to look highlighted */
891 if (out_redrawEnter != NULL)
892 *out_redrawEnter = item;
897 /******************************************************************************
898 * TAB_MouseMove
900 * Handles the mouse-move event. Updates tooltips. Updates hot-tracking.
902 static LRESULT
903 TAB_MouseMove (TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
905 int redrawLeave;
906 int redrawEnter;
908 if (infoPtr->hwndToolTip)
909 TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
910 WM_LBUTTONDOWN, wParam, lParam);
912 /* Determine which tab to highlight. Redraw tabs which change highlight
913 ** status. */
914 TAB_RecalcHotTrack(infoPtr, &lParam, &redrawLeave, &redrawEnter);
916 hottrack_refresh (infoPtr, redrawLeave);
917 hottrack_refresh (infoPtr, redrawEnter);
919 return 0;
922 /******************************************************************************
923 * TAB_AdjustRect
925 * Calculates the tab control's display area given the window rectangle or
926 * the window rectangle given the requested display rectangle.
928 static LRESULT TAB_AdjustRect(const TAB_INFO *infoPtr, WPARAM fLarger, LPRECT prc)
930 LONG *iRightBottom, *iLeftTop;
932 TRACE ("hwnd=%p fLarger=%ld (%s)\n", infoPtr->hwnd, fLarger,
933 wine_dbgstr_rect(prc));
935 if (!prc) return -1;
937 if(infoPtr->dwStyle & TCS_VERTICAL)
939 iRightBottom = &(prc->right);
940 iLeftTop = &(prc->left);
942 else
944 iRightBottom = &(prc->bottom);
945 iLeftTop = &(prc->top);
948 if (fLarger) /* Go from display rectangle */
950 /* Add the height of the tabs. */
951 if (infoPtr->dwStyle & TCS_BOTTOM)
952 *iRightBottom += infoPtr->tabHeight * infoPtr->uNumRows;
953 else
954 *iLeftTop -= infoPtr->tabHeight * infoPtr->uNumRows +
955 ((infoPtr->dwStyle & TCS_BUTTONS)? 3 * (infoPtr->uNumRows - 1) : 0);
957 /* Inflate the rectangle for the padding */
958 InflateRect(prc, DISPLAY_AREA_PADDINGX, DISPLAY_AREA_PADDINGY);
960 /* Inflate for the border */
961 InflateRect(prc, CONTROL_BORDER_SIZEX, CONTROL_BORDER_SIZEY);
963 else /* Go from window rectangle. */
965 /* Deflate the rectangle for the border */
966 InflateRect(prc, -CONTROL_BORDER_SIZEX, -CONTROL_BORDER_SIZEY);
968 /* Deflate the rectangle for the padding */
969 InflateRect(prc, -DISPLAY_AREA_PADDINGX, -DISPLAY_AREA_PADDINGY);
971 /* Remove the height of the tabs. */
972 if (infoPtr->dwStyle & TCS_BOTTOM)
973 *iRightBottom -= infoPtr->tabHeight * infoPtr->uNumRows;
974 else
975 *iLeftTop += (infoPtr->tabHeight) * infoPtr->uNumRows +
976 ((infoPtr->dwStyle & TCS_BUTTONS)? 3 * (infoPtr->uNumRows - 1) : 0);
979 return 0;
982 /******************************************************************************
983 * TAB_OnHScroll
985 * This method will handle the notification from the scroll control and
986 * perform the scrolling operation on the tab control.
988 static LRESULT TAB_OnHScroll(TAB_INFO *infoPtr, int nScrollCode, int nPos)
990 if(nScrollCode == SB_THUMBPOSITION && nPos != infoPtr->leftmostVisible)
992 if(nPos < infoPtr->leftmostVisible)
993 infoPtr->leftmostVisible--;
994 else
995 infoPtr->leftmostVisible++;
997 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
998 TAB_InvalidateTabArea(infoPtr);
999 SendMessageW(infoPtr->hwndUpDown, UDM_SETPOS, 0,
1000 MAKELONG(infoPtr->leftmostVisible, 0));
1003 return 0;
1006 /******************************************************************************
1007 * TAB_SetupScrolling
1009 * This method will check the current scrolling state and make sure the
1010 * scrolling control is displayed (or not).
1012 static void TAB_SetupScrolling(
1013 TAB_INFO* infoPtr,
1014 const RECT* clientRect)
1016 INT maxRange = 0;
1018 if (infoPtr->needsScrolling)
1020 RECT controlPos;
1021 INT vsize, tabwidth;
1024 * Calculate the position of the scroll control.
1026 controlPos.right = clientRect->right;
1027 controlPos.left = controlPos.right - 2 * GetSystemMetrics(SM_CXHSCROLL);
1029 if (infoPtr->dwStyle & TCS_BOTTOM)
1031 controlPos.top = clientRect->bottom - infoPtr->tabHeight;
1032 controlPos.bottom = controlPos.top + GetSystemMetrics(SM_CYHSCROLL);
1034 else
1036 controlPos.bottom = clientRect->top + infoPtr->tabHeight;
1037 controlPos.top = controlPos.bottom - GetSystemMetrics(SM_CYHSCROLL);
1041 * If we don't have a scroll control yet, we want to create one.
1042 * If we have one, we want to make sure it's positioned properly.
1044 if (infoPtr->hwndUpDown==0)
1046 infoPtr->hwndUpDown = CreateWindowW(UPDOWN_CLASSW, L"",
1047 WS_VISIBLE | WS_CHILD | UDS_HORZ,
1048 controlPos.left, controlPos.top,
1049 controlPos.right - controlPos.left,
1050 controlPos.bottom - controlPos.top,
1051 infoPtr->hwnd, NULL, NULL, NULL);
1053 else
1055 SetWindowPos(infoPtr->hwndUpDown,
1056 NULL,
1057 controlPos.left, controlPos.top,
1058 controlPos.right - controlPos.left,
1059 controlPos.bottom - controlPos.top,
1060 SWP_SHOWWINDOW | SWP_NOZORDER);
1063 /* Now calculate upper limit of the updown control range.
1064 * We do this by calculating how many tabs will be offscreen when the
1065 * last tab is visible.
1067 if(infoPtr->uNumItem)
1069 vsize = clientRect->right - (controlPos.right - controlPos.left + 1);
1070 maxRange = infoPtr->uNumItem;
1071 tabwidth = TAB_GetItem(infoPtr, infoPtr->uNumItem - 1)->rect.right;
1073 for(; maxRange > 0; maxRange--)
1075 if(tabwidth - TAB_GetItem(infoPtr,maxRange - 1)->rect.left > vsize)
1076 break;
1079 if(maxRange == infoPtr->uNumItem)
1080 maxRange--;
1083 else
1085 /* If we once had a scroll control... hide it */
1086 if (infoPtr->hwndUpDown)
1087 ShowWindow(infoPtr->hwndUpDown, SW_HIDE);
1089 if (infoPtr->hwndUpDown)
1090 SendMessageW(infoPtr->hwndUpDown, UDM_SETRANGE32, 0, maxRange);
1093 /******************************************************************************
1094 * TAB_SetItemBounds
1096 * This method will calculate the position rectangles of all the items in the
1097 * control. The rectangle calculated starts at 0 for the first item in the
1098 * list and ignores scrolling and selection.
1099 * It also uses the current font to determine the height of the tab row and
1100 * it checks if all the tabs fit in the client area of the window. If they
1101 * don't, a scrolling control is added.
1103 static void TAB_SetItemBounds (TAB_INFO *infoPtr)
1105 TEXTMETRICW fontMetrics;
1106 UINT curItem;
1107 INT curItemLeftPos;
1108 INT curItemRowCount;
1109 HFONT hFont, hOldFont;
1110 HDC hdc;
1111 RECT clientRect;
1112 INT iTemp;
1113 RECT* rcItem;
1114 INT iIndex;
1115 INT icon_width = 0;
1118 * We need to get text information so we need a DC and we need to select
1119 * a font.
1121 hdc = GetDC(infoPtr->hwnd);
1123 hFont = infoPtr->hFont ? infoPtr->hFont : GetStockObject (SYSTEM_FONT);
1124 hOldFont = SelectObject (hdc, hFont);
1127 * We will base the rectangle calculations on the client rectangle
1128 * of the control.
1130 GetClientRect(infoPtr->hwnd, &clientRect);
1132 /* if TCS_VERTICAL then swap the height and width so this code places the
1133 tabs along the top of the rectangle and we can just rotate them after
1134 rather than duplicate all of the below code */
1135 if(infoPtr->dwStyle & TCS_VERTICAL)
1137 iTemp = clientRect.bottom;
1138 clientRect.bottom = clientRect.right;
1139 clientRect.right = iTemp;
1142 /* Now use hPadding and vPadding */
1143 infoPtr->uHItemPadding = infoPtr->uHItemPadding_s;
1144 infoPtr->uVItemPadding = infoPtr->uVItemPadding_s;
1146 /* The leftmost item will be "0" aligned */
1147 curItemLeftPos = 0;
1148 curItemRowCount = infoPtr->uNumItem ? 1 : 0;
1150 if (!(infoPtr->fHeightSet))
1152 int item_height;
1153 INT icon_height = 0, cx;
1155 /* Use the current font to determine the height of a tab. */
1156 GetTextMetricsW(hdc, &fontMetrics);
1158 /* Get the icon height */
1159 if (infoPtr->himl)
1160 ImageList_GetIconSize(infoPtr->himl, &cx, &icon_height);
1162 /* Take the highest between font or icon */
1163 if (fontMetrics.tmHeight > icon_height)
1164 item_height = fontMetrics.tmHeight + 2;
1165 else
1166 item_height = icon_height;
1169 * Make sure there is enough space for the letters + icon + growing the
1170 * selected item + extra space for the selected item.
1172 infoPtr->tabHeight = item_height +
1173 ((infoPtr->dwStyle & TCS_BUTTONS) ? 2 : 1) *
1174 infoPtr->uVItemPadding;
1176 TRACE("tabH=%d, tmH=%d, iconh=%d\n",
1177 infoPtr->tabHeight, fontMetrics.tmHeight, icon_height);
1180 TRACE("client right=%d\n", clientRect.right);
1182 /* Get the icon width */
1183 if (infoPtr->himl)
1185 INT cy;
1187 ImageList_GetIconSize(infoPtr->himl, &icon_width, &cy);
1189 if (infoPtr->dwStyle & TCS_FIXEDWIDTH)
1190 icon_width += 4;
1191 else
1192 /* Add padding if icon is present */
1193 icon_width += infoPtr->uHItemPadding;
1196 for (curItem = 0; curItem < infoPtr->uNumItem; curItem++)
1198 TAB_ITEM *curr = TAB_GetItem(infoPtr, curItem);
1200 /* Set the leftmost position of the tab. */
1201 curr->rect.left = curItemLeftPos;
1203 if (infoPtr->dwStyle & TCS_FIXEDWIDTH)
1205 curr->rect.right = curr->rect.left +
1206 max(infoPtr->tabWidth, icon_width);
1208 else if (!curr->pszText)
1210 /* If no text use minimum tab width including padding. */
1211 if (infoPtr->tabMinWidth < 0)
1212 curr->rect.right = curr->rect.left + GET_DEFAULT_MIN_TAB_WIDTH(infoPtr);
1213 else
1215 curr->rect.right = curr->rect.left + infoPtr->tabMinWidth;
1217 /* Add extra padding if icon is present */
1218 if (infoPtr->himl && infoPtr->tabMinWidth > 0 && infoPtr->tabMinWidth < DEFAULT_MIN_TAB_WIDTH
1219 && infoPtr->uHItemPadding > 1)
1220 curr->rect.right += EXTRA_ICON_PADDING * (infoPtr->uHItemPadding-1);
1223 else
1225 int tabwidth;
1226 SIZE size;
1227 /* Calculate how wide the tab is depending on the text it contains */
1228 GetTextExtentPoint32W(hdc, curr->pszText,
1229 lstrlenW(curr->pszText), &size);
1231 tabwidth = size.cx + icon_width + 2 * infoPtr->uHItemPadding;
1233 if (infoPtr->tabMinWidth < 0)
1234 tabwidth = max(tabwidth, GET_DEFAULT_MIN_TAB_WIDTH(infoPtr));
1235 else
1236 tabwidth = max(tabwidth, infoPtr->tabMinWidth);
1238 curr->rect.right = curr->rect.left + tabwidth;
1239 TRACE("for <%s>, rect %s\n", debugstr_w(curr->pszText), wine_dbgstr_rect(&curr->rect));
1243 * Check if this is a multiline tab control and if so
1244 * check to see if we should wrap the tabs
1246 * Wrap all these tabs. We will arrange them evenly later.
1250 if (((infoPtr->dwStyle & TCS_MULTILINE) || (infoPtr->dwStyle & TCS_VERTICAL)) &&
1251 (curr->rect.right >
1252 (clientRect.right - CONTROL_BORDER_SIZEX - DISPLAY_AREA_PADDINGX)))
1254 curr->rect.right -= curr->rect.left;
1256 curr->rect.left = 0;
1257 curItemRowCount++;
1258 TRACE("wrapping <%s>, rect %s\n", debugstr_w(curr->pszText), wine_dbgstr_rect(&curr->rect));
1261 curr->rect.bottom = 0;
1262 curr->rect.top = curItemRowCount - 1;
1264 TRACE("Rect: %s\n", wine_dbgstr_rect(&curr->rect));
1267 * The leftmost position of the next item is the rightmost position
1268 * of this one.
1270 if (infoPtr->dwStyle & TCS_BUTTONS)
1272 curItemLeftPos = curr->rect.right + BUTTON_SPACINGX;
1273 if (infoPtr->dwStyle & TCS_FLATBUTTONS)
1274 curItemLeftPos += FLAT_BTN_SPACINGX;
1276 else
1277 curItemLeftPos = curr->rect.right;
1280 if (!((infoPtr->dwStyle & TCS_MULTILINE) || (infoPtr->dwStyle & TCS_VERTICAL)))
1283 * Check if we need a scrolling control.
1285 infoPtr->needsScrolling = (curItemLeftPos + (2 * SELECTED_TAB_OFFSET) >
1286 clientRect.right);
1288 /* Don't need scrolling, then update infoPtr->leftmostVisible */
1289 if(!infoPtr->needsScrolling)
1290 infoPtr->leftmostVisible = 0;
1292 else
1295 * No scrolling in Multiline or Vertical styles.
1297 infoPtr->needsScrolling = FALSE;
1298 infoPtr->leftmostVisible = 0;
1300 TAB_SetupScrolling(infoPtr, &clientRect);
1302 /* Set the number of rows */
1303 infoPtr->uNumRows = curItemRowCount;
1305 /* Arrange all tabs evenly if style says so */
1306 if (!(infoPtr->dwStyle & TCS_RAGGEDRIGHT) &&
1307 ((infoPtr->dwStyle & TCS_MULTILINE) || (infoPtr->dwStyle & TCS_VERTICAL)) &&
1308 (infoPtr->uNumItem > 0) &&
1309 (infoPtr->uNumRows > 1))
1311 INT tabPerRow,remTab,iRow;
1312 UINT iItm;
1313 INT iCount=0;
1316 * Ok windows tries to even out the rows. place the same
1317 * number of tabs in each row. So lets give that a shot
1320 tabPerRow = infoPtr->uNumItem / (infoPtr->uNumRows);
1321 remTab = infoPtr->uNumItem % (infoPtr->uNumRows);
1323 for (iItm=0,iRow=0,iCount=0,curItemLeftPos=0;
1324 iItm<infoPtr->uNumItem;
1325 iItm++,iCount++)
1327 /* normalize the current rect */
1328 TAB_ITEM *curr = TAB_GetItem(infoPtr, iItm);
1330 /* shift the item to the left side of the clientRect */
1331 curr->rect.right -= curr->rect.left;
1332 curr->rect.left = 0;
1334 TRACE("r=%d, cl=%d, cl.r=%d, iCount=%d, iRow=%d, uNumRows=%d, remTab=%d, tabPerRow=%d\n",
1335 curr->rect.right, curItemLeftPos, clientRect.right,
1336 iCount, iRow, infoPtr->uNumRows, remTab, tabPerRow);
1338 /* if we have reached the maximum number of tabs on this row */
1339 /* move to the next row, reset our current item left position and */
1340 /* the count of items on this row */
1342 if (infoPtr->dwStyle & TCS_VERTICAL) {
1343 /* Vert: Add the remaining tabs in the *last* remainder rows */
1344 if (iCount >= ((iRow>=(INT)infoPtr->uNumRows - remTab)?tabPerRow + 1:tabPerRow)) {
1345 iRow++;
1346 curItemLeftPos = 0;
1347 iCount = 0;
1349 } else {
1350 /* Horz: Add the remaining tabs in the *first* remainder rows */
1351 if (iCount >= ((iRow<remTab)?tabPerRow + 1:tabPerRow)) {
1352 iRow++;
1353 curItemLeftPos = 0;
1354 iCount = 0;
1358 /* shift the item to the right to place it as the next item in this row */
1359 curr->rect.left += curItemLeftPos;
1360 curr->rect.right += curItemLeftPos;
1361 curr->rect.top = iRow;
1362 if (infoPtr->dwStyle & TCS_BUTTONS)
1364 curItemLeftPos = curr->rect.right + 1;
1365 if (infoPtr->dwStyle & TCS_FLATBUTTONS)
1366 curItemLeftPos += FLAT_BTN_SPACINGX;
1368 else
1369 curItemLeftPos = curr->rect.right;
1371 TRACE("arranging <%s>, rect %s\n", debugstr_w(curr->pszText), wine_dbgstr_rect(&curr->rect));
1375 * Justify the rows
1378 INT widthDiff, iIndexStart=0, iIndexEnd=0;
1379 INT remainder;
1380 INT iCount=0;
1382 while(iIndexStart < infoPtr->uNumItem)
1384 TAB_ITEM *start = TAB_GetItem(infoPtr, iIndexStart);
1387 * find the index of the row
1389 /* find the first item on the next row */
1390 for (iIndexEnd=iIndexStart;
1391 (iIndexEnd < infoPtr->uNumItem) &&
1392 (TAB_GetItem(infoPtr, iIndexEnd)->rect.top ==
1393 start->rect.top) ;
1394 iIndexEnd++)
1395 /* intentionally blank */;
1398 * we need to justify these tabs so they fill the whole given
1399 * client area
1402 /* find the amount of space remaining on this row */
1403 widthDiff = clientRect.right - (2 * SELECTED_TAB_OFFSET) -
1404 TAB_GetItem(infoPtr, iIndexEnd - 1)->rect.right;
1406 /* iCount is the number of tab items on this row */
1407 iCount = iIndexEnd - iIndexStart;
1409 if (iCount > 1)
1411 remainder = widthDiff % iCount;
1412 widthDiff = widthDiff / iCount;
1413 /* add widthDiff/iCount, or extra space/items on row, to each item on this row */
1414 for (iIndex=iIndexStart, iCount=0; iIndex < iIndexEnd; iIndex++, iCount++)
1416 TAB_ITEM *item = TAB_GetItem(infoPtr, iIndex);
1418 item->rect.left += iCount * widthDiff;
1419 item->rect.right += (iCount + 1) * widthDiff;
1421 TRACE("adjusting 1 <%s>, rect %s\n", debugstr_w(item->pszText), wine_dbgstr_rect(&item->rect));
1424 TAB_GetItem(infoPtr, iIndex - 1)->rect.right += remainder;
1426 else /* we have only one item on this row, make it take up the entire row */
1428 start->rect.left = clientRect.left;
1429 start->rect.right = clientRect.right - 4;
1431 TRACE("adjusting 2 <%s>, rect %s\n", debugstr_w(start->pszText), wine_dbgstr_rect(&start->rect));
1434 iIndexStart = iIndexEnd;
1439 /* if TCS_VERTICAL rotate the tabs so they are along the side of the clientRect */
1440 if(infoPtr->dwStyle & TCS_VERTICAL)
1442 RECT rcOriginal;
1443 for(iIndex = 0; iIndex < infoPtr->uNumItem; iIndex++)
1445 rcItem = &TAB_GetItem(infoPtr, iIndex)->rect;
1447 rcOriginal = *rcItem;
1449 /* this is rotating the items by 90 degrees clockwise around the center of the control */
1450 rcItem->top = (rcOriginal.left - clientRect.left);
1451 rcItem->bottom = rcItem->top + (rcOriginal.right - rcOriginal.left);
1452 rcItem->left = rcOriginal.top;
1453 rcItem->right = rcOriginal.bottom;
1457 TAB_EnsureSelectionVisible(infoPtr);
1458 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
1460 /* Cleanup */
1461 SelectObject (hdc, hOldFont);
1462 ReleaseDC (infoPtr->hwnd, hdc);
1466 static void
1467 TAB_EraseTabInterior(const TAB_INFO *infoPtr, HDC hdc, INT iItem, const RECT *drawRect)
1469 HBRUSH hbr = CreateSolidBrush (comctl32_color.clrBtnFace);
1470 BOOL deleteBrush = TRUE;
1471 RECT rTemp = *drawRect;
1473 if (infoPtr->dwStyle & TCS_BUTTONS)
1475 if (iItem == infoPtr->iSelected)
1477 /* Background color */
1478 if (!(infoPtr->dwStyle & TCS_OWNERDRAWFIXED))
1480 DeleteObject(hbr);
1481 hbr = GetSysColorBrush(COLOR_SCROLLBAR);
1483 SetTextColor(hdc, comctl32_color.clr3dFace);
1484 SetBkColor(hdc, comctl32_color.clr3dHilight);
1486 /* if COLOR_WINDOW happens to be the same as COLOR_3DHILIGHT
1487 * we better use 0x55aa bitmap brush to make scrollbar's background
1488 * look different from the window background.
1490 if (comctl32_color.clr3dHilight == comctl32_color.clrWindow)
1491 hbr = COMCTL32_hPattern55AABrush;
1493 deleteBrush = FALSE;
1495 FillRect(hdc, &rTemp, hbr);
1497 else /* ! selected */
1499 if (infoPtr->dwStyle & TCS_FLATBUTTONS)
1501 InflateRect(&rTemp, 2, 2);
1502 FillRect(hdc, &rTemp, hbr);
1503 if (iItem == infoPtr->iHotTracked ||
1504 (iItem != infoPtr->iSelected && iItem == infoPtr->uFocus))
1505 DrawEdge(hdc, &rTemp, BDR_RAISEDINNER, BF_RECT);
1507 else
1508 FillRect(hdc, &rTemp, hbr);
1512 else /* !TCS_BUTTONS */
1514 InflateRect(&rTemp, -2, -2);
1515 if (!GetWindowTheme (infoPtr->hwnd))
1516 FillRect(hdc, &rTemp, hbr);
1519 /* highlighting is drawn on top of previous fills */
1520 if (TAB_GetItem(infoPtr, iItem)->dwState & TCIS_HIGHLIGHTED)
1522 if (deleteBrush)
1524 DeleteObject(hbr);
1525 deleteBrush = FALSE;
1527 hbr = GetSysColorBrush(COLOR_HIGHLIGHT);
1528 FillRect(hdc, &rTemp, hbr);
1531 /* Cleanup */
1532 if (deleteBrush) DeleteObject(hbr);
1535 /******************************************************************************
1536 * TAB_DrawItemInterior
1538 * This method is used to draw the interior (text and icon) of a single tab
1539 * into the tab control.
1541 static void
1542 TAB_DrawItemInterior(const TAB_INFO *infoPtr, HDC hdc, INT iItem, RECT *drawRect)
1544 RECT localRect;
1546 HPEN htextPen;
1547 HPEN holdPen;
1548 INT oldBkMode;
1549 HFONT hOldFont;
1551 /* if (drawRect == NULL) */
1553 BOOL isVisible;
1554 RECT itemRect;
1555 RECT selectedRect;
1558 * Get the rectangle for the item.
1560 isVisible = TAB_InternalGetItemRect(infoPtr, iItem, &itemRect, &selectedRect);
1561 if (!isVisible)
1562 return;
1565 * Make sure drawRect points to something valid; simplifies code.
1567 drawRect = &localRect;
1570 * This logic copied from the part of TAB_DrawItem which draws
1571 * the tab background. It's important to keep it in sync. I
1572 * would have liked to avoid code duplication, but couldn't figure
1573 * out how without making spaghetti of TAB_DrawItem.
1575 if (iItem == infoPtr->iSelected)
1576 *drawRect = selectedRect;
1577 else
1578 *drawRect = itemRect;
1580 if (infoPtr->dwStyle & TCS_BUTTONS)
1582 if (iItem == infoPtr->iSelected)
1584 drawRect->left += 4;
1585 drawRect->top += 4;
1586 drawRect->right -= 4;
1588 if (infoPtr->dwStyle & TCS_VERTICAL)
1590 if (!(infoPtr->dwStyle & TCS_BOTTOM)) drawRect->right += 1;
1591 drawRect->bottom -= 4;
1593 else
1595 if (infoPtr->dwStyle & TCS_BOTTOM)
1597 drawRect->top -= 2;
1598 drawRect->bottom -= 4;
1600 else
1601 drawRect->bottom -= 1;
1604 else
1605 InflateRect(drawRect, -2, -2);
1607 else
1609 if ((infoPtr->dwStyle & TCS_VERTICAL) && (infoPtr->dwStyle & TCS_BOTTOM))
1611 if (iItem != infoPtr->iSelected)
1613 drawRect->left += 2;
1614 InflateRect(drawRect, 0, -2);
1617 else if (infoPtr->dwStyle & TCS_VERTICAL)
1619 if (iItem == infoPtr->iSelected)
1621 drawRect->right += 1;
1623 else
1625 drawRect->right -= 2;
1626 InflateRect(drawRect, 0, -2);
1629 else if (infoPtr->dwStyle & TCS_BOTTOM)
1631 if (iItem == infoPtr->iSelected)
1633 drawRect->top -= 2;
1635 else
1637 InflateRect(drawRect, -2, -2);
1638 drawRect->bottom += 2;
1641 else
1643 if (iItem == infoPtr->iSelected)
1645 drawRect->bottom += 3;
1647 else
1649 drawRect->bottom -= 2;
1650 InflateRect(drawRect, -2, 0);
1655 TRACE("drawRect=(%s)\n", wine_dbgstr_rect(drawRect));
1657 /* Clear interior */
1658 TAB_EraseTabInterior (infoPtr, hdc, iItem, drawRect);
1660 /* Draw the focus rectangle */
1661 if (!(infoPtr->dwStyle & TCS_FOCUSNEVER) &&
1662 (GetFocus() == infoPtr->hwnd) &&
1663 (iItem == infoPtr->uFocus) )
1665 RECT rFocus = *drawRect;
1667 if (!(infoPtr->dwStyle & TCS_BUTTONS)) InflateRect(&rFocus, -3, -3);
1668 if (infoPtr->dwStyle & TCS_BOTTOM && !(infoPtr->dwStyle & TCS_VERTICAL))
1669 rFocus.top -= 3;
1671 /* focus should stay on selected item for TCS_BUTTONS style */
1672 if (!((infoPtr->dwStyle & TCS_BUTTONS) && (infoPtr->iSelected != iItem)))
1673 DrawFocusRect(hdc, &rFocus);
1677 * Text pen
1679 htextPen = CreatePen( PS_SOLID, 1, comctl32_color.clrBtnText );
1680 holdPen = SelectObject(hdc, htextPen);
1681 hOldFont = SelectObject(hdc, infoPtr->hFont);
1684 * Setup for text output
1686 oldBkMode = SetBkMode(hdc, TRANSPARENT);
1687 if (!GetWindowTheme (infoPtr->hwnd) || (infoPtr->dwStyle & TCS_BUTTONS))
1689 if ((infoPtr->dwStyle & TCS_HOTTRACK) && (iItem == infoPtr->iHotTracked) &&
1690 !(infoPtr->dwStyle & TCS_FLATBUTTONS))
1691 SetTextColor(hdc, comctl32_color.clrHighlight);
1692 else if (TAB_GetItem(infoPtr, iItem)->dwState & TCIS_HIGHLIGHTED)
1693 SetTextColor(hdc, comctl32_color.clrHighlightText);
1694 else
1695 SetTextColor(hdc, comctl32_color.clrBtnText);
1699 * if owner draw, tell the owner to draw
1701 if ((infoPtr->dwStyle & TCS_OWNERDRAWFIXED) && IsWindow(infoPtr->hwndNotify))
1703 DRAWITEMSTRUCT dis;
1704 UINT id;
1706 drawRect->top += 2;
1707 drawRect->right -= 1;
1708 if ( iItem == infoPtr->iSelected )
1709 InflateRect(drawRect, -1, 0);
1711 id = (UINT)GetWindowLongPtrW( infoPtr->hwnd, GWLP_ID );
1713 /* fill DRAWITEMSTRUCT */
1714 dis.CtlType = ODT_TAB;
1715 dis.CtlID = id;
1716 dis.itemID = iItem;
1717 dis.itemAction = ODA_DRAWENTIRE;
1718 dis.itemState = 0;
1719 if ( iItem == infoPtr->iSelected )
1720 dis.itemState |= ODS_SELECTED;
1721 if (infoPtr->uFocus == iItem)
1722 dis.itemState |= ODS_FOCUS;
1723 dis.hwndItem = infoPtr->hwnd;
1724 dis.hDC = hdc;
1725 dis.rcItem = *drawRect;
1727 /* when extra data fits ULONG_PTR, store it directly */
1728 if (infoPtr->cbInfo > sizeof(LPARAM))
1729 dis.itemData = (ULONG_PTR) TAB_GetItem(infoPtr, iItem)->extra;
1730 else
1732 /* this could be considered broken on 64 bit, but that's how it works -
1733 only first 4 bytes are copied */
1734 dis.itemData = 0;
1735 memcpy(&dis.itemData, (ULONG_PTR*)TAB_GetItem(infoPtr, iItem)->extra, 4);
1738 /* draw notification */
1739 SendMessageW( infoPtr->hwndNotify, WM_DRAWITEM, id, (LPARAM)&dis );
1741 else
1743 TAB_ITEM *item = TAB_GetItem(infoPtr, iItem);
1744 RECT rcTemp;
1745 RECT rcImage;
1747 /* used to center the icon and text in the tab */
1748 RECT rcText;
1749 INT center_offset_h, center_offset_v;
1751 /* set rcImage to drawRect, we will use top & left in our ImageList_Draw call */
1752 rcImage = *drawRect;
1754 rcTemp = *drawRect;
1755 SetRectEmpty(&rcText);
1757 /* get the rectangle that the text fits in */
1758 if (item->pszText)
1760 DrawTextW(hdc, item->pszText, -1, &rcText, DT_CALCRECT);
1763 * If not owner draw, then do the drawing ourselves.
1765 * Draw the icon.
1767 if (infoPtr->himl && item->iImage != -1)
1769 INT cx;
1770 INT cy;
1772 ImageList_GetIconSize(infoPtr->himl, &cx, &cy);
1774 if(infoPtr->dwStyle & TCS_VERTICAL)
1776 center_offset_h = ((drawRect->bottom - drawRect->top) - (cy + infoPtr->uHItemPadding + (rcText.right - rcText.left))) / 2;
1777 center_offset_v = ((drawRect->right - drawRect->left) - cx) / 2;
1779 else
1781 center_offset_h = ((drawRect->right - drawRect->left) - (cx + infoPtr->uHItemPadding + (rcText.right - rcText.left))) / 2;
1782 center_offset_v = ((drawRect->bottom - drawRect->top) - cy) / 2;
1785 /* if an item is selected, the icon is shifted up instead of down */
1786 if (iItem == infoPtr->iSelected)
1787 center_offset_v -= infoPtr->uVItemPadding / 2;
1788 else
1789 center_offset_v += infoPtr->uVItemPadding / 2;
1791 if (infoPtr->dwStyle & TCS_FIXEDWIDTH && infoPtr->dwStyle & (TCS_FORCELABELLEFT | TCS_FORCEICONLEFT))
1792 center_offset_h = infoPtr->uHItemPadding;
1794 if (center_offset_h < 2)
1795 center_offset_h = 2;
1797 if (center_offset_v < 0)
1798 center_offset_v = 0;
1800 TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%s), textlen=%d\n",
1801 debugstr_w(item->pszText), center_offset_h, center_offset_v,
1802 wine_dbgstr_rect(drawRect), (rcText.right-rcText.left));
1804 if((infoPtr->dwStyle & TCS_VERTICAL) && (infoPtr->dwStyle & TCS_BOTTOM))
1806 rcImage.top = drawRect->top + center_offset_h;
1807 /* if tab is TCS_VERTICAL and TCS_BOTTOM, the text is drawn from the */
1808 /* right side of the tab, but the image still uses the left as its x position */
1809 /* this keeps the image always drawn off of the same side of the tab */
1810 rcImage.left = drawRect->right - cx - center_offset_v;
1811 drawRect->top += cy + infoPtr->uHItemPadding;
1813 else if(infoPtr->dwStyle & TCS_VERTICAL)
1815 rcImage.top = drawRect->bottom - cy - center_offset_h;
1816 rcImage.left = drawRect->left + center_offset_v;
1817 drawRect->bottom -= cy + infoPtr->uHItemPadding;
1819 else /* normal style, whether TCS_BOTTOM or not */
1821 rcImage.left = drawRect->left + center_offset_h;
1822 rcImage.top = drawRect->top + center_offset_v;
1823 drawRect->left += cx + infoPtr->uHItemPadding;
1826 TRACE("drawing image=%d, left=%d, top=%d\n",
1827 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=%d\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)
2323 DrawThemeBackground (theme, hdc, TABP_PANE, 0, &rect, NULL);
2324 else
2325 DrawEdge(hdc, &rect, EDGE_RAISED, BF_SOFT|BF_RECT);
2328 /******************************************************************************
2329 * TAB_Refresh
2331 * This method repaints the tab control..
2333 static void TAB_Refresh (const TAB_INFO *infoPtr, HDC hdc)
2335 HFONT hOldFont;
2336 INT i;
2338 if (!infoPtr->DoRedraw)
2339 return;
2341 hOldFont = SelectObject (hdc, infoPtr->hFont);
2343 if (infoPtr->dwStyle & TCS_BUTTONS)
2345 for (i = 0; i < infoPtr->uNumItem; i++)
2346 TAB_DrawItem (infoPtr, hdc, i);
2348 else
2350 /* Draw all the non selected item first */
2351 for (i = 0; i < infoPtr->uNumItem; i++)
2353 if (i != infoPtr->iSelected)
2354 TAB_DrawItem (infoPtr, hdc, i);
2357 /* Now, draw the border, draw it before the selected item
2358 * since the selected item overwrites part of the border. */
2359 TAB_DrawBorder (infoPtr, hdc);
2361 /* Then, draw the selected item */
2362 TAB_DrawItem (infoPtr, hdc, infoPtr->iSelected);
2365 SelectObject (hdc, hOldFont);
2368 static inline DWORD TAB_GetRowCount (const TAB_INFO *infoPtr)
2370 TRACE("(%p)\n", infoPtr);
2371 return infoPtr->uNumRows;
2374 static inline LRESULT TAB_SetRedraw (TAB_INFO *infoPtr, BOOL doRedraw)
2376 infoPtr->DoRedraw = doRedraw;
2377 return 0;
2380 /******************************************************************************
2381 * TAB_EnsureSelectionVisible
2383 * This method will make sure that the current selection is completely
2384 * visible by scrolling until it is.
2386 static void TAB_EnsureSelectionVisible(
2387 TAB_INFO* infoPtr)
2389 INT iSelected = infoPtr->iSelected;
2390 INT iOrigLeftmostVisible = infoPtr->leftmostVisible;
2392 if (iSelected < 0)
2393 return;
2395 /* set the items row to the bottommost row or topmost row depending on
2396 * style */
2397 if ((infoPtr->uNumRows > 1) && !(infoPtr->dwStyle & TCS_BUTTONS))
2399 TAB_ITEM *selected = TAB_GetItem(infoPtr, iSelected);
2400 INT newselected;
2401 INT iTargetRow;
2403 if(infoPtr->dwStyle & TCS_VERTICAL)
2404 newselected = selected->rect.left;
2405 else
2406 newselected = selected->rect.top;
2408 /* the target row is always (number of rows - 1)
2409 as row 0 is furthest from the clientRect */
2410 iTargetRow = infoPtr->uNumRows - 1;
2412 if (newselected != iTargetRow)
2414 UINT i;
2415 if(infoPtr->dwStyle & TCS_VERTICAL)
2417 for (i=0; i < infoPtr->uNumItem; i++)
2419 /* move everything in the row of the selected item to the iTargetRow */
2420 TAB_ITEM *item = TAB_GetItem(infoPtr, i);
2422 if (item->rect.left == newselected )
2423 item->rect.left = iTargetRow;
2424 else
2426 if (item->rect.left > newselected)
2427 item->rect.left-=1;
2431 else
2433 for (i=0; i < infoPtr->uNumItem; i++)
2435 TAB_ITEM *item = TAB_GetItem(infoPtr, i);
2437 if (item->rect.top == newselected )
2438 item->rect.top = iTargetRow;
2439 else
2441 if (item->rect.top > newselected)
2442 item->rect.top-=1;
2446 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
2451 * Do the trivial cases first.
2453 if ( (!infoPtr->needsScrolling) ||
2454 (infoPtr->hwndUpDown==0) || (infoPtr->dwStyle & TCS_VERTICAL))
2455 return;
2457 if (infoPtr->leftmostVisible >= iSelected)
2459 infoPtr->leftmostVisible = iSelected;
2461 else
2463 TAB_ITEM *selected = TAB_GetItem(infoPtr, iSelected);
2464 RECT r;
2465 INT width;
2466 UINT i;
2468 /* Calculate the part of the client area that is visible */
2469 GetClientRect(infoPtr->hwnd, &r);
2470 width = r.right;
2472 GetClientRect(infoPtr->hwndUpDown, &r);
2473 width -= r.right;
2475 if ((selected->rect.right -
2476 selected->rect.left) >= width )
2478 /* Special case: width of selected item is greater than visible
2479 * part of control.
2481 infoPtr->leftmostVisible = iSelected;
2483 else
2485 for (i = infoPtr->leftmostVisible; i < infoPtr->uNumItem; i++)
2487 if ((selected->rect.right - TAB_GetItem(infoPtr, i)->rect.left) < width)
2488 break;
2490 infoPtr->leftmostVisible = i;
2494 if (infoPtr->leftmostVisible != iOrigLeftmostVisible)
2495 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
2497 SendMessageW(infoPtr->hwndUpDown, UDM_SETPOS, 0,
2498 MAKELONG(infoPtr->leftmostVisible, 0));
2501 /******************************************************************************
2502 * TAB_InvalidateTabArea
2504 * This method will invalidate the portion of the control that contains the
2505 * tabs. It is called when the state of the control changes and needs
2506 * to be redisplayed
2508 static void TAB_InvalidateTabArea(const TAB_INFO *infoPtr)
2510 RECT clientRect, rInvalidate, rAdjClient;
2511 INT lastRow = infoPtr->uNumRows - 1;
2512 RECT rect;
2514 if (lastRow < 0) return;
2516 GetClientRect(infoPtr->hwnd, &clientRect);
2517 rInvalidate = clientRect;
2518 rAdjClient = clientRect;
2520 TAB_AdjustRect(infoPtr, 0, &rAdjClient);
2522 TAB_InternalGetItemRect(infoPtr, infoPtr->uNumItem-1 , &rect, NULL);
2523 if ((infoPtr->dwStyle & TCS_BOTTOM) && (infoPtr->dwStyle & TCS_VERTICAL))
2525 rInvalidate.left = rAdjClient.right;
2526 if (infoPtr->uNumRows == 1)
2527 rInvalidate.bottom = clientRect.top + rect.bottom + 2 * SELECTED_TAB_OFFSET;
2529 else if(infoPtr->dwStyle & TCS_VERTICAL)
2531 rInvalidate.right = rAdjClient.left;
2532 if (infoPtr->uNumRows == 1)
2533 rInvalidate.bottom = clientRect.top + rect.bottom + 2 * SELECTED_TAB_OFFSET;
2535 else if (infoPtr->dwStyle & TCS_BOTTOM)
2537 rInvalidate.top = rAdjClient.bottom;
2538 if (infoPtr->uNumRows == 1)
2539 rInvalidate.right = clientRect.left + rect.right + 2 * SELECTED_TAB_OFFSET;
2541 else
2543 rInvalidate.bottom = rAdjClient.top;
2544 if (infoPtr->uNumRows == 1)
2545 rInvalidate.right = clientRect.left + rect.right + 2 * SELECTED_TAB_OFFSET;
2548 /* Punch out the updown control */
2549 if (infoPtr->needsScrolling && (rInvalidate.right > 0)) {
2550 RECT r;
2551 GetClientRect(infoPtr->hwndUpDown, &r);
2552 if (rInvalidate.right > clientRect.right - r.left)
2553 rInvalidate.right = rInvalidate.right - (r.right - r.left);
2554 else
2555 rInvalidate.right = clientRect.right - r.left;
2558 TRACE("invalidate (%s)\n", wine_dbgstr_rect(&rInvalidate));
2560 InvalidateRect(infoPtr->hwnd, &rInvalidate, TRUE);
2563 static inline LRESULT TAB_Paint (TAB_INFO *infoPtr, HDC hdcPaint)
2565 HDC hdc;
2566 PAINTSTRUCT ps;
2568 if (hdcPaint)
2569 hdc = hdcPaint;
2570 else
2572 hdc = BeginPaint (infoPtr->hwnd, &ps);
2573 TRACE("erase %d, rect=(%s)\n", ps.fErase, wine_dbgstr_rect(&ps.rcPaint));
2576 TAB_Refresh (infoPtr, hdc);
2578 if (!hdcPaint)
2579 EndPaint (infoPtr->hwnd, &ps);
2581 return 0;
2584 static LRESULT
2585 TAB_InsertItemT (TAB_INFO *infoPtr, INT iItem, const TCITEMW *pti, BOOL bUnicode)
2587 TAB_ITEM *item;
2588 RECT rect;
2590 GetClientRect (infoPtr->hwnd, &rect);
2591 TRACE("Rect: %p %s\n", infoPtr->hwnd, wine_dbgstr_rect(&rect));
2593 if (iItem < 0) return -1;
2594 if (iItem > infoPtr->uNumItem)
2595 iItem = infoPtr->uNumItem;
2597 TAB_DumpItemExternalT(pti, iItem, bUnicode);
2599 if (!(item = Alloc(TAB_ITEM_SIZE(infoPtr)))) return FALSE;
2600 if (DPA_InsertPtr(infoPtr->items, iItem, item) == -1)
2602 Free(item);
2603 return FALSE;
2606 if (infoPtr->uNumItem == 0)
2607 infoPtr->iSelected = 0;
2608 else if (iItem <= infoPtr->iSelected)
2609 infoPtr->iSelected++;
2611 infoPtr->uNumItem++;
2613 item->pszText = NULL;
2614 if (pti->mask & TCIF_TEXT)
2616 if (bUnicode)
2617 Str_SetPtrW (&item->pszText, pti->pszText);
2618 else
2619 Str_SetPtrAtoW (&item->pszText, (LPSTR)pti->pszText);
2622 if (pti->mask & TCIF_IMAGE)
2623 item->iImage = pti->iImage;
2624 else
2625 item->iImage = -1;
2627 if (pti->mask & TCIF_PARAM)
2628 memcpy(item->extra, &pti->lParam, EXTRA_ITEM_SIZE(infoPtr));
2629 else
2630 memset(item->extra, 0, EXTRA_ITEM_SIZE(infoPtr));
2632 TAB_SetItemBounds(infoPtr);
2633 if (infoPtr->uNumItem > 1)
2634 TAB_InvalidateTabArea(infoPtr);
2635 else
2636 InvalidateRect(infoPtr->hwnd, NULL, TRUE);
2638 TRACE("[%p]: added item %d %s\n",
2639 infoPtr->hwnd, iItem, debugstr_w(item->pszText));
2641 /* If we haven't set the current focus yet, set it now. */
2642 if (infoPtr->uFocus == -1)
2643 TAB_SetCurFocus(infoPtr, iItem);
2645 return iItem;
2648 static LRESULT
2649 TAB_SetItemSize (TAB_INFO *infoPtr, INT cx, INT cy)
2651 LONG lResult = 0;
2652 BOOL bNeedPaint = FALSE;
2654 lResult = MAKELONG(infoPtr->tabWidth, infoPtr->tabHeight);
2656 /* UNDOCUMENTED: If requested Width or Height is 0 this means that program wants to use auto size. */
2657 if (infoPtr->dwStyle & TCS_FIXEDWIDTH && (infoPtr->tabWidth != cx))
2659 infoPtr->tabWidth = cx;
2660 bNeedPaint = TRUE;
2663 if (infoPtr->tabHeight != cy)
2665 if ((infoPtr->fHeightSet = (cy != 0)))
2666 infoPtr->tabHeight = cy;
2668 bNeedPaint = TRUE;
2670 TRACE("was h=%d,w=%d, now h=%d,w=%d\n",
2671 HIWORD(lResult), LOWORD(lResult),
2672 infoPtr->tabHeight, infoPtr->tabWidth);
2674 if (bNeedPaint)
2676 TAB_SetItemBounds(infoPtr);
2677 RedrawWindow(infoPtr->hwnd, NULL, NULL, RDW_ERASE | RDW_INVALIDATE | RDW_UPDATENOW);
2680 return lResult;
2683 static inline LRESULT TAB_SetMinTabWidth (TAB_INFO *infoPtr, INT cx)
2685 INT oldcx = 0;
2687 TRACE("(%p,%d)\n", infoPtr, cx);
2689 if (infoPtr->tabMinWidth < 0)
2690 oldcx = DEFAULT_MIN_TAB_WIDTH;
2691 else
2692 oldcx = infoPtr->tabMinWidth;
2693 infoPtr->tabMinWidth = cx;
2694 TAB_SetItemBounds(infoPtr);
2695 return oldcx;
2698 static inline LRESULT
2699 TAB_HighlightItem (TAB_INFO *infoPtr, INT iItem, BOOL fHighlight)
2701 LPDWORD lpState;
2702 DWORD oldState;
2703 RECT r;
2705 TRACE("(%p,%d,%s)\n", infoPtr, iItem, fHighlight ? "true" : "false");
2707 if (iItem < 0 || iItem >= infoPtr->uNumItem)
2708 return FALSE;
2710 lpState = &TAB_GetItem(infoPtr, iItem)->dwState;
2711 oldState = *lpState;
2713 if (fHighlight)
2714 *lpState |= TCIS_HIGHLIGHTED;
2715 else
2716 *lpState &= ~TCIS_HIGHLIGHTED;
2718 if ((oldState != *lpState) && TAB_InternalGetItemRect (infoPtr, iItem, &r, NULL))
2719 InvalidateRect (infoPtr->hwnd, &r, TRUE);
2721 return TRUE;
2724 static LRESULT
2725 TAB_SetItemT (TAB_INFO *infoPtr, INT iItem, LPTCITEMW tabItem, BOOL bUnicode)
2727 TAB_ITEM *wineItem;
2729 TRACE("(%p,%d,%p,%s)\n", infoPtr, iItem, tabItem, bUnicode ? "true" : "false");
2731 if (iItem < 0 || iItem >= infoPtr->uNumItem)
2732 return FALSE;
2734 TAB_DumpItemExternalT(tabItem, iItem, bUnicode);
2736 wineItem = TAB_GetItem(infoPtr, iItem);
2738 if (tabItem->mask & TCIF_IMAGE)
2739 wineItem->iImage = tabItem->iImage;
2741 if (tabItem->mask & TCIF_PARAM)
2742 memcpy(wineItem->extra, &tabItem->lParam, infoPtr->cbInfo);
2744 if (tabItem->mask & TCIF_RTLREADING)
2745 FIXME("TCIF_RTLREADING\n");
2747 if (tabItem->mask & TCIF_STATE)
2748 wineItem->dwState = (wineItem->dwState & ~tabItem->dwStateMask) |
2749 ( tabItem->dwState & tabItem->dwStateMask);
2751 if (tabItem->mask & TCIF_TEXT)
2753 Free(wineItem->pszText);
2754 wineItem->pszText = NULL;
2755 if (bUnicode)
2756 Str_SetPtrW(&wineItem->pszText, tabItem->pszText);
2757 else
2758 Str_SetPtrAtoW(&wineItem->pszText, (LPSTR)tabItem->pszText);
2761 /* Update and repaint tabs */
2762 TAB_SetItemBounds(infoPtr);
2763 TAB_InvalidateTabArea(infoPtr);
2765 return TRUE;
2768 static inline LRESULT TAB_GetItemCount (const TAB_INFO *infoPtr)
2770 TRACE("\n");
2771 return infoPtr->uNumItem;
2775 static LRESULT
2776 TAB_GetItemT (TAB_INFO *infoPtr, INT iItem, LPTCITEMW tabItem, BOOL bUnicode)
2778 TAB_ITEM *wineItem;
2780 TRACE("(%p,%d,%p,%s)\n", infoPtr, iItem, tabItem, bUnicode ? "true" : "false");
2782 if (!tabItem) return FALSE;
2784 if (iItem < 0 || iItem >= infoPtr->uNumItem)
2786 /* init requested fields */
2787 if (tabItem->mask & TCIF_IMAGE) tabItem->iImage = 0;
2788 if (tabItem->mask & TCIF_PARAM) tabItem->lParam = 0;
2789 if (tabItem->mask & TCIF_STATE) tabItem->dwState = 0;
2790 return FALSE;
2793 wineItem = TAB_GetItem(infoPtr, iItem);
2795 if (tabItem->mask & TCIF_IMAGE)
2796 tabItem->iImage = wineItem->iImage;
2798 if (tabItem->mask & TCIF_PARAM)
2799 memcpy(&tabItem->lParam, wineItem->extra, infoPtr->cbInfo);
2801 if (tabItem->mask & TCIF_RTLREADING)
2802 FIXME("TCIF_RTLREADING\n");
2804 if (tabItem->mask & TCIF_STATE)
2805 tabItem->dwState = wineItem->dwState & tabItem->dwStateMask;
2807 if (tabItem->mask & TCIF_TEXT)
2809 if (bUnicode)
2810 Str_GetPtrW (wineItem->pszText, tabItem->pszText, tabItem->cchTextMax);
2811 else
2812 Str_GetPtrWtoA (wineItem->pszText, (LPSTR)tabItem->pszText, tabItem->cchTextMax);
2815 TAB_DumpItemExternalT(tabItem, iItem, bUnicode);
2817 return TRUE;
2821 static LRESULT TAB_DeleteItem (TAB_INFO *infoPtr, INT iItem)
2823 TAB_ITEM *item;
2825 TRACE("(%p, %d)\n", infoPtr, iItem);
2827 if (iItem < 0 || iItem >= infoPtr->uNumItem) return FALSE;
2829 TAB_InvalidateTabArea(infoPtr);
2830 item = TAB_GetItem(infoPtr, iItem);
2831 Free(item->pszText);
2832 Free(item);
2833 infoPtr->uNumItem--;
2834 DPA_DeletePtr(infoPtr->items, iItem);
2836 if (infoPtr->uNumItem == 0)
2838 if (infoPtr->iHotTracked >= 0)
2840 KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
2841 infoPtr->iHotTracked = -1;
2844 infoPtr->iSelected = -1;
2846 else
2848 if (iItem <= infoPtr->iHotTracked)
2850 /* When tabs move left/up, the hot track item may change */
2851 FIXME("Recalc hot track\n");
2855 /* adjust the selected index */
2856 if (iItem == infoPtr->iSelected)
2857 infoPtr->iSelected = -1;
2858 else if (iItem < infoPtr->iSelected)
2859 infoPtr->iSelected--;
2861 /* reposition and repaint tabs */
2862 TAB_SetItemBounds(infoPtr);
2864 return TRUE;
2867 static inline LRESULT TAB_DeleteAllItems (TAB_INFO *infoPtr)
2869 TRACE("(%p)\n", infoPtr);
2870 while (infoPtr->uNumItem)
2871 TAB_DeleteItem (infoPtr, 0);
2872 return TRUE;
2876 static inline LRESULT TAB_GetFont (const TAB_INFO *infoPtr)
2878 TRACE("(%p) returning %p\n", infoPtr, infoPtr->hFont);
2879 return (LRESULT)infoPtr->hFont;
2882 static inline LRESULT TAB_SetFont (TAB_INFO *infoPtr, HFONT hNewFont)
2884 TRACE("(%p,%p)\n", infoPtr, hNewFont);
2886 infoPtr->hFont = hNewFont;
2888 TAB_SetItemBounds(infoPtr);
2890 TAB_InvalidateTabArea(infoPtr);
2892 return 0;
2896 static inline LRESULT TAB_GetImageList (const TAB_INFO *infoPtr)
2898 TRACE("\n");
2899 return (LRESULT)infoPtr->himl;
2902 static inline LRESULT TAB_SetImageList (TAB_INFO *infoPtr, HIMAGELIST himlNew)
2904 HIMAGELIST himlPrev = infoPtr->himl;
2905 TRACE("himl=%p\n", himlNew);
2906 infoPtr->himl = himlNew;
2907 TAB_SetItemBounds(infoPtr);
2908 InvalidateRect(infoPtr->hwnd, NULL, TRUE);
2909 return (LRESULT)himlPrev;
2912 static inline LRESULT TAB_GetUnicodeFormat (const TAB_INFO *infoPtr)
2914 TRACE("(%p)\n", infoPtr);
2915 return infoPtr->bUnicode;
2918 static inline LRESULT TAB_SetUnicodeFormat (TAB_INFO *infoPtr, BOOL bUnicode)
2920 BOOL bTemp = infoPtr->bUnicode;
2922 TRACE("(%p %d)\n", infoPtr, bUnicode);
2923 infoPtr->bUnicode = bUnicode;
2925 return bTemp;
2928 static inline LRESULT TAB_Size (TAB_INFO *infoPtr)
2930 /* I'm not really sure what the following code was meant to do.
2931 This is what it is doing:
2932 When WM_SIZE is sent with SIZE_RESTORED, the control
2933 gets positioned in the top left corner.
2935 RECT parent_rect;
2936 HWND parent;
2937 UINT uPosFlags,cx,cy;
2939 uPosFlags=0;
2940 if (!wParam) {
2941 parent = GetParent (hwnd);
2942 GetClientRect(parent, &parent_rect);
2943 cx=LOWORD (lParam);
2944 cy=HIWORD (lParam);
2945 if (GetWindowLongW(hwnd, GWL_STYLE) & CCS_NORESIZE)
2946 uPosFlags |= (SWP_NOSIZE | SWP_NOMOVE);
2948 SetWindowPos (hwnd, 0, parent_rect.left, parent_rect.top,
2949 cx, cy, uPosFlags | SWP_NOZORDER);
2950 } else {
2951 FIXME("WM_SIZE flag %x %lx not handled\n", wParam, lParam);
2952 } */
2954 /* Recompute the size/position of the tabs. */
2955 TAB_SetItemBounds (infoPtr);
2957 /* Force a repaint of the control. */
2958 InvalidateRect(infoPtr->hwnd, NULL, TRUE);
2960 return 0;
2964 static LRESULT TAB_Create (HWND hwnd, LPARAM lParam)
2966 TAB_INFO *infoPtr;
2967 TEXTMETRICW fontMetrics;
2968 HDC hdc;
2969 HFONT hOldFont;
2970 DWORD style;
2972 infoPtr = Alloc (sizeof(TAB_INFO));
2974 SetWindowLongPtrW(hwnd, 0, (DWORD_PTR)infoPtr);
2976 infoPtr->hwnd = hwnd;
2977 infoPtr->hwndNotify = ((LPCREATESTRUCTW)lParam)->hwndParent;
2978 infoPtr->uNumItem = 0;
2979 infoPtr->uNumRows = 0;
2980 infoPtr->uHItemPadding = 6;
2981 infoPtr->uVItemPadding = 3;
2982 infoPtr->uHItemPadding_s = 6;
2983 infoPtr->uVItemPadding_s = 3;
2984 infoPtr->hFont = 0;
2985 infoPtr->items = DPA_Create(8);
2986 infoPtr->hcurArrow = LoadCursorW (0, (LPWSTR)IDC_ARROW);
2987 infoPtr->iSelected = -1;
2988 infoPtr->iHotTracked = -1;
2989 infoPtr->uFocus = -1;
2990 infoPtr->hwndToolTip = 0;
2991 infoPtr->DoRedraw = TRUE;
2992 infoPtr->needsScrolling = FALSE;
2993 infoPtr->hwndUpDown = 0;
2994 infoPtr->leftmostVisible = 0;
2995 infoPtr->fHeightSet = FALSE;
2996 infoPtr->bUnicode = IsWindowUnicode (hwnd);
2997 infoPtr->cbInfo = sizeof(LPARAM);
2999 TRACE("Created tab control, hwnd [%p]\n", hwnd);
3001 /* The tab control always has the WS_CLIPSIBLINGS style. Even
3002 if you don't specify it in CreateWindow. This is necessary in
3003 order for paint to work correctly. This follows windows behaviour. */
3004 style = GetWindowLongW(hwnd, GWL_STYLE);
3005 if (style & TCS_VERTICAL) style |= TCS_MULTILINE;
3006 style |= WS_CLIPSIBLINGS;
3007 SetWindowLongW(hwnd, GWL_STYLE, style);
3009 infoPtr->dwStyle = style;
3010 infoPtr->exStyle = (style & TCS_FLATBUTTONS) ? TCS_EX_FLATSEPARATORS : 0;
3012 if (infoPtr->dwStyle & TCS_TOOLTIPS) {
3013 /* Create tooltip control */
3014 infoPtr->hwndToolTip =
3015 CreateWindowExW (0, TOOLTIPS_CLASSW, NULL, WS_POPUP,
3016 CW_USEDEFAULT, CW_USEDEFAULT,
3017 CW_USEDEFAULT, CW_USEDEFAULT,
3018 hwnd, 0, 0, 0);
3020 /* Send NM_TOOLTIPSCREATED notification */
3021 if (infoPtr->hwndToolTip) {
3022 NMTOOLTIPSCREATED nmttc;
3024 nmttc.hdr.hwndFrom = hwnd;
3025 nmttc.hdr.idFrom = GetWindowLongPtrW(hwnd, GWLP_ID);
3026 nmttc.hdr.code = NM_TOOLTIPSCREATED;
3027 nmttc.hwndToolTips = infoPtr->hwndToolTip;
3029 SendMessageW (infoPtr->hwndNotify, WM_NOTIFY,
3030 GetWindowLongPtrW(hwnd, GWLP_ID), (LPARAM)&nmttc);
3034 OpenThemeData (infoPtr->hwnd, themeClass);
3037 * We need to get text information so we need a DC and we need to select
3038 * a font.
3040 hdc = GetDC(hwnd);
3041 hOldFont = SelectObject (hdc, GetStockObject (SYSTEM_FONT));
3043 /* Use the system font to determine the initial height of a tab. */
3044 GetTextMetricsW(hdc, &fontMetrics);
3047 * Make sure there is enough space for the letters + growing the
3048 * selected item + extra space for the selected item.
3050 infoPtr->tabHeight = fontMetrics.tmHeight + SELECTED_TAB_OFFSET +
3051 ((infoPtr->dwStyle & TCS_BUTTONS) ? 2 : 1) *
3052 infoPtr->uVItemPadding;
3054 /* Initialize the width of a tab. */
3055 if (infoPtr->dwStyle & TCS_FIXEDWIDTH)
3056 infoPtr->tabWidth = GetDeviceCaps(hdc, LOGPIXELSX);
3058 infoPtr->tabMinWidth = -1;
3060 TRACE("tabH=%d, tabW=%d\n", infoPtr->tabHeight, infoPtr->tabWidth);
3062 SelectObject (hdc, hOldFont);
3063 ReleaseDC(hwnd, hdc);
3065 return 0;
3068 static LRESULT
3069 TAB_Destroy (TAB_INFO *infoPtr)
3071 INT iItem;
3073 SetWindowLongPtrW(infoPtr->hwnd, 0, 0);
3075 for (iItem = infoPtr->uNumItem - 1; iItem >= 0; iItem--)
3077 TAB_ITEM *tab = TAB_GetItem(infoPtr, iItem);
3079 DPA_DeletePtr(infoPtr->items, iItem);
3080 infoPtr->uNumItem--;
3082 Free(tab->pszText);
3083 Free(tab);
3085 DPA_Destroy(infoPtr->items);
3086 infoPtr->items = NULL;
3088 if (infoPtr->hwndToolTip)
3089 DestroyWindow (infoPtr->hwndToolTip);
3091 if (infoPtr->hwndUpDown)
3092 DestroyWindow(infoPtr->hwndUpDown);
3094 if (infoPtr->iHotTracked >= 0)
3095 KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
3097 CloseThemeData (GetWindowTheme (infoPtr->hwnd));
3099 Free (infoPtr);
3100 return 0;
3103 /* update theme after a WM_THEMECHANGED message */
3104 static LRESULT theme_changed(const TAB_INFO *infoPtr)
3106 HTHEME theme = GetWindowTheme (infoPtr->hwnd);
3107 CloseThemeData (theme);
3108 OpenThemeData (infoPtr->hwnd, themeClass);
3109 return 0;
3112 static LRESULT TAB_NCCalcSize(WPARAM wParam)
3114 if (!wParam)
3115 return 0;
3116 return WVR_ALIGNTOP;
3119 static inline LRESULT
3120 TAB_SetItemExtra (TAB_INFO *infoPtr, INT cbInfo)
3122 TRACE("(%p %d)\n", infoPtr, cbInfo);
3124 if (cbInfo < 0 || infoPtr->uNumItem) return FALSE;
3126 infoPtr->cbInfo = cbInfo;
3127 return TRUE;
3130 static LRESULT TAB_RemoveImage (TAB_INFO *infoPtr, INT image)
3132 TRACE("%p %d\n", infoPtr, image);
3134 if (ImageList_Remove (infoPtr->himl, image))
3136 INT i, *idx;
3137 RECT r;
3139 /* shift indices, repaint items if needed */
3140 for (i = 0; i < infoPtr->uNumItem; i++)
3142 idx = &TAB_GetItem(infoPtr, i)->iImage;
3143 if (*idx >= image)
3145 if (*idx == image)
3146 *idx = -1;
3147 else
3148 (*idx)--;
3150 /* repaint item */
3151 if (TAB_InternalGetItemRect (infoPtr, i, &r, NULL))
3152 InvalidateRect (infoPtr->hwnd, &r, TRUE);
3157 return 0;
3160 static LRESULT
3161 TAB_SetExtendedStyle (TAB_INFO *infoPtr, DWORD exMask, DWORD exStyle)
3163 DWORD prevstyle = infoPtr->exStyle;
3165 /* zero mask means all styles */
3166 if (exMask == 0) exMask = ~0;
3168 if (exMask & TCS_EX_REGISTERDROP)
3170 FIXME("TCS_EX_REGISTERDROP style unimplemented\n");
3171 exMask &= ~TCS_EX_REGISTERDROP;
3172 exStyle &= ~TCS_EX_REGISTERDROP;
3175 if (exMask & TCS_EX_FLATSEPARATORS)
3177 if ((prevstyle ^ exStyle) & TCS_EX_FLATSEPARATORS)
3179 infoPtr->exStyle ^= TCS_EX_FLATSEPARATORS;
3180 TAB_InvalidateTabArea(infoPtr);
3184 return prevstyle;
3187 static inline LRESULT
3188 TAB_GetExtendedStyle (const TAB_INFO *infoPtr)
3190 return infoPtr->exStyle;
3193 static LRESULT
3194 TAB_DeselectAll (TAB_INFO *infoPtr, BOOL excludesel)
3196 BOOL paint = FALSE;
3197 INT i, selected = infoPtr->iSelected;
3199 TRACE("(%p, %d)\n", infoPtr, excludesel);
3201 if (!(infoPtr->dwStyle & TCS_BUTTONS))
3202 return 0;
3204 for (i = 0; i < infoPtr->uNumItem; i++)
3206 if ((TAB_GetItem(infoPtr, i)->dwState & TCIS_BUTTONPRESSED) &&
3207 (selected != i))
3209 TAB_GetItem(infoPtr, i)->dwState &= ~TCIS_BUTTONPRESSED;
3210 paint = TRUE;
3214 if (!excludesel && (selected != -1))
3216 TAB_GetItem(infoPtr, selected)->dwState &= ~TCIS_BUTTONPRESSED;
3217 infoPtr->iSelected = -1;
3218 paint = TRUE;
3221 if (paint)
3222 TAB_InvalidateTabArea (infoPtr);
3224 return 0;
3227 /***
3228 * DESCRIPTION:
3229 * Processes WM_STYLECHANGED messages.
3231 * PARAMETER(S):
3232 * [I] infoPtr : valid pointer to the tab data structure
3233 * [I] wStyleType : window style type (normal or extended)
3234 * [I] lpss : window style information
3236 * RETURN:
3237 * Zero
3239 static INT TAB_StyleChanged(TAB_INFO *infoPtr, WPARAM wStyleType,
3240 const STYLESTRUCT *lpss)
3242 TRACE("(styletype=%lx, styleOld=0x%08x, styleNew=0x%08x)\n",
3243 wStyleType, lpss->styleOld, lpss->styleNew);
3245 if (wStyleType != GWL_STYLE) return 0;
3247 infoPtr->dwStyle = lpss->styleNew;
3249 TAB_SetItemBounds (infoPtr);
3250 InvalidateRect(infoPtr->hwnd, NULL, TRUE);
3252 return 0;
3255 static LRESULT WINAPI
3256 TAB_WindowProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
3258 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
3260 TRACE("hwnd=%p msg=%x wParam=%lx lParam=%lx\n", hwnd, uMsg, wParam, lParam);
3261 if (!infoPtr && (uMsg != WM_CREATE))
3262 return DefWindowProcW (hwnd, uMsg, wParam, lParam);
3264 switch (uMsg)
3266 case TCM_GETIMAGELIST:
3267 return TAB_GetImageList (infoPtr);
3269 case TCM_SETIMAGELIST:
3270 return TAB_SetImageList (infoPtr, (HIMAGELIST)lParam);
3272 case TCM_GETITEMCOUNT:
3273 return TAB_GetItemCount (infoPtr);
3275 case TCM_GETITEMA:
3276 case TCM_GETITEMW:
3277 return TAB_GetItemT (infoPtr, (INT)wParam, (LPTCITEMW)lParam, uMsg == TCM_GETITEMW);
3279 case TCM_SETITEMA:
3280 case TCM_SETITEMW:
3281 return TAB_SetItemT (infoPtr, (INT)wParam, (LPTCITEMW)lParam, uMsg == TCM_SETITEMW);
3283 case TCM_DELETEITEM:
3284 return TAB_DeleteItem (infoPtr, (INT)wParam);
3286 case TCM_DELETEALLITEMS:
3287 return TAB_DeleteAllItems (infoPtr);
3289 case TCM_GETITEMRECT:
3290 return TAB_GetItemRect (infoPtr, (INT)wParam, (LPRECT)lParam);
3292 case TCM_GETCURSEL:
3293 return TAB_GetCurSel (infoPtr);
3295 case TCM_HITTEST:
3296 return TAB_HitTest (infoPtr, (LPTCHITTESTINFO)lParam);
3298 case TCM_SETCURSEL:
3299 return TAB_SetCurSel (infoPtr, (INT)wParam);
3301 case TCM_INSERTITEMA:
3302 case TCM_INSERTITEMW:
3303 return TAB_InsertItemT (infoPtr, (INT)wParam, (TCITEMW*)lParam, uMsg == TCM_INSERTITEMW);
3305 case TCM_SETITEMEXTRA:
3306 return TAB_SetItemExtra (infoPtr, (INT)wParam);
3308 case TCM_ADJUSTRECT:
3309 return TAB_AdjustRect (infoPtr, (BOOL)wParam, (LPRECT)lParam);
3311 case TCM_SETITEMSIZE:
3312 return TAB_SetItemSize (infoPtr, (INT)LOWORD(lParam), (INT)HIWORD(lParam));
3314 case TCM_REMOVEIMAGE:
3315 return TAB_RemoveImage (infoPtr, (INT)wParam);
3317 case TCM_SETPADDING:
3318 return TAB_SetPadding (infoPtr, lParam);
3320 case TCM_GETROWCOUNT:
3321 return TAB_GetRowCount(infoPtr);
3323 case TCM_GETUNICODEFORMAT:
3324 return TAB_GetUnicodeFormat (infoPtr);
3326 case TCM_SETUNICODEFORMAT:
3327 return TAB_SetUnicodeFormat (infoPtr, (BOOL)wParam);
3329 case TCM_HIGHLIGHTITEM:
3330 return TAB_HighlightItem (infoPtr, (INT)wParam, (BOOL)LOWORD(lParam));
3332 case TCM_GETTOOLTIPS:
3333 return TAB_GetToolTips (infoPtr);
3335 case TCM_SETTOOLTIPS:
3336 return TAB_SetToolTips (infoPtr, (HWND)wParam);
3338 case TCM_GETCURFOCUS:
3339 return TAB_GetCurFocus (infoPtr);
3341 case TCM_SETCURFOCUS:
3342 return TAB_SetCurFocus (infoPtr, (INT)wParam);
3344 case TCM_SETMINTABWIDTH:
3345 return TAB_SetMinTabWidth(infoPtr, (INT)lParam);
3347 case TCM_DESELECTALL:
3348 return TAB_DeselectAll (infoPtr, (BOOL)wParam);
3350 case TCM_GETEXTENDEDSTYLE:
3351 return TAB_GetExtendedStyle (infoPtr);
3353 case TCM_SETEXTENDEDSTYLE:
3354 return TAB_SetExtendedStyle (infoPtr, wParam, lParam);
3356 case WM_GETFONT:
3357 return TAB_GetFont (infoPtr);
3359 case WM_SETFONT:
3360 return TAB_SetFont (infoPtr, (HFONT)wParam);
3362 case WM_CREATE:
3363 return TAB_Create (hwnd, lParam);
3365 case WM_NCDESTROY:
3366 return TAB_Destroy (infoPtr);
3368 case WM_GETDLGCODE:
3369 return DLGC_WANTARROWS | DLGC_WANTCHARS;
3371 case WM_LBUTTONDOWN:
3372 return TAB_LButtonDown (infoPtr, wParam, lParam);
3374 case WM_LBUTTONUP:
3375 return TAB_LButtonUp (infoPtr);
3377 case WM_NOTIFY:
3378 return SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, wParam, lParam);
3380 case WM_RBUTTONUP:
3381 TAB_RButtonUp (infoPtr);
3382 return DefWindowProcW (hwnd, uMsg, wParam, lParam);
3384 case WM_MOUSEMOVE:
3385 return TAB_MouseMove (infoPtr, wParam, lParam);
3387 case WM_PRINTCLIENT:
3388 case WM_PAINT:
3389 return TAB_Paint (infoPtr, (HDC)wParam);
3391 case WM_SIZE:
3392 return TAB_Size (infoPtr);
3394 case WM_SETREDRAW:
3395 return TAB_SetRedraw (infoPtr, (BOOL)wParam);
3397 case WM_HSCROLL:
3398 return TAB_OnHScroll(infoPtr, (int)LOWORD(wParam), (int)HIWORD(wParam));
3400 case WM_STYLECHANGED:
3401 return TAB_StyleChanged(infoPtr, wParam, (LPSTYLESTRUCT)lParam);
3403 case WM_SYSCOLORCHANGE:
3404 COMCTL32_RefreshSysColors();
3405 return 0;
3407 case WM_THEMECHANGED:
3408 return theme_changed (infoPtr);
3410 case WM_KILLFOCUS:
3411 TAB_KillFocus(infoPtr);
3412 case WM_SETFOCUS:
3413 TAB_FocusChanging(infoPtr);
3414 break; /* Don't disturb normal focus behavior */
3416 case WM_KEYDOWN:
3417 return TAB_KeyDown(infoPtr, wParam, lParam);
3419 case WM_NCHITTEST:
3420 return TAB_NCHitTest(infoPtr, lParam);
3422 case WM_NCCALCSIZE:
3423 return TAB_NCCalcSize(wParam);
3425 default:
3426 if (uMsg >= WM_USER && uMsg < WM_APP && !COMCTL32_IsReflectedMessage(uMsg))
3427 WARN("unknown msg %04x wp=%08lx lp=%08lx\n",
3428 uMsg, wParam, lParam);
3429 break;
3431 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
3435 void
3436 TAB_Register (void)
3438 WNDCLASSW wndClass;
3440 ZeroMemory (&wndClass, sizeof(WNDCLASSW));
3441 wndClass.style = CS_GLOBALCLASS | CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
3442 wndClass.lpfnWndProc = TAB_WindowProc;
3443 wndClass.cbClsExtra = 0;
3444 wndClass.cbWndExtra = sizeof(TAB_INFO *);
3445 wndClass.hCursor = LoadCursorW (0, (LPWSTR)IDC_ARROW);
3446 wndClass.hbrBackground = (HBRUSH)(COLOR_BTNFACE+1);
3447 wndClass.lpszClassName = WC_TABCONTROLW;
3449 RegisterClassW (&wndClass);
3453 void
3454 TAB_Unregister (void)
3456 UnregisterClassW (WC_TABCONTROLW, NULL);