comctl32/edit: Force update on focus change.
[wine.git] / dlls / comctl32 / tab.c
blobf8a54935ca3220d68bf77a921fbf91127f560bf0
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[] = { 'T','a','b',0 };
157 static inline TAB_ITEM* TAB_GetItem(const TAB_INFO *infoPtr, INT i)
159 assert(i >= 0 && i < infoPtr->uNumItem);
160 return DPA_GetPtr(infoPtr->items, i);
163 /******************************************************************************
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 static const WCHAR emptyW[] = { 0 };
1017 INT maxRange = 0;
1019 if (infoPtr->needsScrolling)
1021 RECT controlPos;
1022 INT vsize, tabwidth;
1025 * Calculate the position of the scroll control.
1027 controlPos.right = clientRect->right;
1028 controlPos.left = controlPos.right - 2 * GetSystemMetrics(SM_CXHSCROLL);
1030 if (infoPtr->dwStyle & TCS_BOTTOM)
1032 controlPos.top = clientRect->bottom - infoPtr->tabHeight;
1033 controlPos.bottom = controlPos.top + GetSystemMetrics(SM_CYHSCROLL);
1035 else
1037 controlPos.bottom = clientRect->top + infoPtr->tabHeight;
1038 controlPos.top = controlPos.bottom - GetSystemMetrics(SM_CYHSCROLL);
1042 * If we don't have a scroll control yet, we want to create one.
1043 * If we have one, we want to make sure it's positioned properly.
1045 if (infoPtr->hwndUpDown==0)
1047 infoPtr->hwndUpDown = CreateWindowW(UPDOWN_CLASSW, emptyW,
1048 WS_VISIBLE | WS_CHILD | UDS_HORZ,
1049 controlPos.left, controlPos.top,
1050 controlPos.right - controlPos.left,
1051 controlPos.bottom - controlPos.top,
1052 infoPtr->hwnd, NULL, NULL, NULL);
1054 else
1056 SetWindowPos(infoPtr->hwndUpDown,
1057 NULL,
1058 controlPos.left, controlPos.top,
1059 controlPos.right - controlPos.left,
1060 controlPos.bottom - controlPos.top,
1061 SWP_SHOWWINDOW | SWP_NOZORDER);
1064 /* Now calculate upper limit of the updown control range.
1065 * We do this by calculating how many tabs will be offscreen when the
1066 * last tab is visible.
1068 if(infoPtr->uNumItem)
1070 vsize = clientRect->right - (controlPos.right - controlPos.left + 1);
1071 maxRange = infoPtr->uNumItem;
1072 tabwidth = TAB_GetItem(infoPtr, infoPtr->uNumItem - 1)->rect.right;
1074 for(; maxRange > 0; maxRange--)
1076 if(tabwidth - TAB_GetItem(infoPtr,maxRange - 1)->rect.left > vsize)
1077 break;
1080 if(maxRange == infoPtr->uNumItem)
1081 maxRange--;
1084 else
1086 /* If we once had a scroll control... hide it */
1087 if (infoPtr->hwndUpDown)
1088 ShowWindow(infoPtr->hwndUpDown, SW_HIDE);
1090 if (infoPtr->hwndUpDown)
1091 SendMessageW(infoPtr->hwndUpDown, UDM_SETRANGE32, 0, maxRange);
1094 /******************************************************************************
1095 * TAB_SetItemBounds
1097 * This method will calculate the position rectangles of all the items in the
1098 * control. The rectangle calculated starts at 0 for the first item in the
1099 * list and ignores scrolling and selection.
1100 * It also uses the current font to determine the height of the tab row and
1101 * it checks if all the tabs fit in the client area of the window. If they
1102 * don't, a scrolling control is added.
1104 static void TAB_SetItemBounds (TAB_INFO *infoPtr)
1106 TEXTMETRICW fontMetrics;
1107 UINT curItem;
1108 INT curItemLeftPos;
1109 INT curItemRowCount;
1110 HFONT hFont, hOldFont;
1111 HDC hdc;
1112 RECT clientRect;
1113 INT iTemp;
1114 RECT* rcItem;
1115 INT iIndex;
1116 INT icon_width = 0;
1119 * We need to get text information so we need a DC and we need to select
1120 * a font.
1122 hdc = GetDC(infoPtr->hwnd);
1124 hFont = infoPtr->hFont ? infoPtr->hFont : GetStockObject (SYSTEM_FONT);
1125 hOldFont = SelectObject (hdc, hFont);
1128 * We will base the rectangle calculations on the client rectangle
1129 * of the control.
1131 GetClientRect(infoPtr->hwnd, &clientRect);
1133 /* if TCS_VERTICAL then swap the height and width so this code places the
1134 tabs along the top of the rectangle and we can just rotate them after
1135 rather than duplicate all of the below code */
1136 if(infoPtr->dwStyle & TCS_VERTICAL)
1138 iTemp = clientRect.bottom;
1139 clientRect.bottom = clientRect.right;
1140 clientRect.right = iTemp;
1143 /* Now use hPadding and vPadding */
1144 infoPtr->uHItemPadding = infoPtr->uHItemPadding_s;
1145 infoPtr->uVItemPadding = infoPtr->uVItemPadding_s;
1147 /* The leftmost item will be "0" aligned */
1148 curItemLeftPos = 0;
1149 curItemRowCount = infoPtr->uNumItem ? 1 : 0;
1151 if (!(infoPtr->fHeightSet))
1153 int item_height;
1154 INT icon_height = 0, cx;
1156 /* Use the current font to determine the height of a tab. */
1157 GetTextMetricsW(hdc, &fontMetrics);
1159 /* Get the icon height */
1160 if (infoPtr->himl)
1161 ImageList_GetIconSize(infoPtr->himl, &cx, &icon_height);
1163 /* Take the highest between font or icon */
1164 if (fontMetrics.tmHeight > icon_height)
1165 item_height = fontMetrics.tmHeight + 2;
1166 else
1167 item_height = icon_height;
1170 * Make sure there is enough space for the letters + icon + growing the
1171 * selected item + extra space for the selected item.
1173 infoPtr->tabHeight = item_height +
1174 ((infoPtr->dwStyle & TCS_BUTTONS) ? 2 : 1) *
1175 infoPtr->uVItemPadding;
1177 TRACE("tabH=%d, tmH=%d, iconh=%d\n",
1178 infoPtr->tabHeight, fontMetrics.tmHeight, icon_height);
1181 TRACE("client right=%d\n", clientRect.right);
1183 /* Get the icon width */
1184 if (infoPtr->himl)
1186 INT cy;
1188 ImageList_GetIconSize(infoPtr->himl, &icon_width, &cy);
1190 if (infoPtr->dwStyle & TCS_FIXEDWIDTH)
1191 icon_width += 4;
1192 else
1193 /* Add padding if icon is present */
1194 icon_width += infoPtr->uHItemPadding;
1197 for (curItem = 0; curItem < infoPtr->uNumItem; curItem++)
1199 TAB_ITEM *curr = TAB_GetItem(infoPtr, curItem);
1201 /* Set the leftmost position of the tab. */
1202 curr->rect.left = curItemLeftPos;
1204 if (infoPtr->dwStyle & TCS_FIXEDWIDTH)
1206 curr->rect.right = curr->rect.left +
1207 max(infoPtr->tabWidth, icon_width);
1209 else if (!curr->pszText)
1211 /* If no text use minimum tab width including padding. */
1212 if (infoPtr->tabMinWidth < 0)
1213 curr->rect.right = curr->rect.left + GET_DEFAULT_MIN_TAB_WIDTH(infoPtr);
1214 else
1216 curr->rect.right = curr->rect.left + infoPtr->tabMinWidth;
1218 /* Add extra padding if icon is present */
1219 if (infoPtr->himl && infoPtr->tabMinWidth > 0 && infoPtr->tabMinWidth < DEFAULT_MIN_TAB_WIDTH
1220 && infoPtr->uHItemPadding > 1)
1221 curr->rect.right += EXTRA_ICON_PADDING * (infoPtr->uHItemPadding-1);
1224 else
1226 int tabwidth;
1227 SIZE size;
1228 /* Calculate how wide the tab is depending on the text it contains */
1229 GetTextExtentPoint32W(hdc, curr->pszText,
1230 lstrlenW(curr->pszText), &size);
1232 tabwidth = size.cx + icon_width + 2 * infoPtr->uHItemPadding;
1234 if (infoPtr->tabMinWidth < 0)
1235 tabwidth = max(tabwidth, GET_DEFAULT_MIN_TAB_WIDTH(infoPtr));
1236 else
1237 tabwidth = max(tabwidth, infoPtr->tabMinWidth);
1239 curr->rect.right = curr->rect.left + tabwidth;
1240 TRACE("for <%s>, rect %s\n", debugstr_w(curr->pszText), wine_dbgstr_rect(&curr->rect));
1244 * Check if this is a multiline tab control and if so
1245 * check to see if we should wrap the tabs
1247 * Wrap all these tabs. We will arrange them evenly later.
1251 if (((infoPtr->dwStyle & TCS_MULTILINE) || (infoPtr->dwStyle & TCS_VERTICAL)) &&
1252 (curr->rect.right >
1253 (clientRect.right - CONTROL_BORDER_SIZEX - DISPLAY_AREA_PADDINGX)))
1255 curr->rect.right -= curr->rect.left;
1257 curr->rect.left = 0;
1258 curItemRowCount++;
1259 TRACE("wrapping <%s>, rect %s\n", debugstr_w(curr->pszText), wine_dbgstr_rect(&curr->rect));
1262 curr->rect.bottom = 0;
1263 curr->rect.top = curItemRowCount - 1;
1265 TRACE("Rect: %s\n", wine_dbgstr_rect(&curr->rect));
1268 * The leftmost position of the next item is the rightmost position
1269 * of this one.
1271 if (infoPtr->dwStyle & TCS_BUTTONS)
1273 curItemLeftPos = curr->rect.right + BUTTON_SPACINGX;
1274 if (infoPtr->dwStyle & TCS_FLATBUTTONS)
1275 curItemLeftPos += FLAT_BTN_SPACINGX;
1277 else
1278 curItemLeftPos = curr->rect.right;
1281 if (!((infoPtr->dwStyle & TCS_MULTILINE) || (infoPtr->dwStyle & TCS_VERTICAL)))
1284 * Check if we need a scrolling control.
1286 infoPtr->needsScrolling = (curItemLeftPos + (2 * SELECTED_TAB_OFFSET) >
1287 clientRect.right);
1289 /* Don't need scrolling, then update infoPtr->leftmostVisible */
1290 if(!infoPtr->needsScrolling)
1291 infoPtr->leftmostVisible = 0;
1293 else
1296 * No scrolling in Multiline or Vertical styles.
1298 infoPtr->needsScrolling = FALSE;
1299 infoPtr->leftmostVisible = 0;
1301 TAB_SetupScrolling(infoPtr, &clientRect);
1303 /* Set the number of rows */
1304 infoPtr->uNumRows = curItemRowCount;
1306 /* Arrange all tabs evenly if style says so */
1307 if (!(infoPtr->dwStyle & TCS_RAGGEDRIGHT) &&
1308 ((infoPtr->dwStyle & TCS_MULTILINE) || (infoPtr->dwStyle & TCS_VERTICAL)) &&
1309 (infoPtr->uNumItem > 0) &&
1310 (infoPtr->uNumRows > 1))
1312 INT tabPerRow,remTab,iRow;
1313 UINT iItm;
1314 INT iCount=0;
1317 * Ok windows tries to even out the rows. place the same
1318 * number of tabs in each row. So lets give that a shot
1321 tabPerRow = infoPtr->uNumItem / (infoPtr->uNumRows);
1322 remTab = infoPtr->uNumItem % (infoPtr->uNumRows);
1324 for (iItm=0,iRow=0,iCount=0,curItemLeftPos=0;
1325 iItm<infoPtr->uNumItem;
1326 iItm++,iCount++)
1328 /* normalize the current rect */
1329 TAB_ITEM *curr = TAB_GetItem(infoPtr, iItm);
1331 /* shift the item to the left side of the clientRect */
1332 curr->rect.right -= curr->rect.left;
1333 curr->rect.left = 0;
1335 TRACE("r=%d, cl=%d, cl.r=%d, iCount=%d, iRow=%d, uNumRows=%d, remTab=%d, tabPerRow=%d\n",
1336 curr->rect.right, curItemLeftPos, clientRect.right,
1337 iCount, iRow, infoPtr->uNumRows, remTab, tabPerRow);
1339 /* if we have reached the maximum number of tabs on this row */
1340 /* move to the next row, reset our current item left position and */
1341 /* the count of items on this row */
1343 if (infoPtr->dwStyle & TCS_VERTICAL) {
1344 /* Vert: Add the remaining tabs in the *last* remainder rows */
1345 if (iCount >= ((iRow>=(INT)infoPtr->uNumRows - remTab)?tabPerRow + 1:tabPerRow)) {
1346 iRow++;
1347 curItemLeftPos = 0;
1348 iCount = 0;
1350 } else {
1351 /* Horz: Add the remaining tabs in the *first* remainder rows */
1352 if (iCount >= ((iRow<remTab)?tabPerRow + 1:tabPerRow)) {
1353 iRow++;
1354 curItemLeftPos = 0;
1355 iCount = 0;
1359 /* shift the item to the right to place it as the next item in this row */
1360 curr->rect.left += curItemLeftPos;
1361 curr->rect.right += curItemLeftPos;
1362 curr->rect.top = iRow;
1363 if (infoPtr->dwStyle & TCS_BUTTONS)
1365 curItemLeftPos = curr->rect.right + 1;
1366 if (infoPtr->dwStyle & TCS_FLATBUTTONS)
1367 curItemLeftPos += FLAT_BTN_SPACINGX;
1369 else
1370 curItemLeftPos = curr->rect.right;
1372 TRACE("arranging <%s>, rect %s\n", debugstr_w(curr->pszText), wine_dbgstr_rect(&curr->rect));
1376 * Justify the rows
1379 INT widthDiff, iIndexStart=0, iIndexEnd=0;
1380 INT remainder;
1381 INT iCount=0;
1383 while(iIndexStart < infoPtr->uNumItem)
1385 TAB_ITEM *start = TAB_GetItem(infoPtr, iIndexStart);
1388 * find the index of the row
1390 /* find the first item on the next row */
1391 for (iIndexEnd=iIndexStart;
1392 (iIndexEnd < infoPtr->uNumItem) &&
1393 (TAB_GetItem(infoPtr, iIndexEnd)->rect.top ==
1394 start->rect.top) ;
1395 iIndexEnd++)
1396 /* intentionally blank */;
1399 * we need to justify these tabs so they fill the whole given
1400 * client area
1403 /* find the amount of space remaining on this row */
1404 widthDiff = clientRect.right - (2 * SELECTED_TAB_OFFSET) -
1405 TAB_GetItem(infoPtr, iIndexEnd - 1)->rect.right;
1407 /* iCount is the number of tab items on this row */
1408 iCount = iIndexEnd - iIndexStart;
1410 if (iCount > 1)
1412 remainder = widthDiff % iCount;
1413 widthDiff = widthDiff / iCount;
1414 /* add widthDiff/iCount, or extra space/items on row, to each item on this row */
1415 for (iIndex=iIndexStart, iCount=0; iIndex < iIndexEnd; iIndex++, iCount++)
1417 TAB_ITEM *item = TAB_GetItem(infoPtr, iIndex);
1419 item->rect.left += iCount * widthDiff;
1420 item->rect.right += (iCount + 1) * widthDiff;
1422 TRACE("adjusting 1 <%s>, rect %s\n", debugstr_w(item->pszText), wine_dbgstr_rect(&item->rect));
1425 TAB_GetItem(infoPtr, iIndex - 1)->rect.right += remainder;
1427 else /* we have only one item on this row, make it take up the entire row */
1429 start->rect.left = clientRect.left;
1430 start->rect.right = clientRect.right - 4;
1432 TRACE("adjusting 2 <%s>, rect %s\n", debugstr_w(start->pszText), wine_dbgstr_rect(&start->rect));
1435 iIndexStart = iIndexEnd;
1440 /* if TCS_VERTICAL rotate the tabs so they are along the side of the clientRect */
1441 if(infoPtr->dwStyle & TCS_VERTICAL)
1443 RECT rcOriginal;
1444 for(iIndex = 0; iIndex < infoPtr->uNumItem; iIndex++)
1446 rcItem = &TAB_GetItem(infoPtr, iIndex)->rect;
1448 rcOriginal = *rcItem;
1450 /* this is rotating the items by 90 degrees clockwise around the center of the control */
1451 rcItem->top = (rcOriginal.left - clientRect.left);
1452 rcItem->bottom = rcItem->top + (rcOriginal.right - rcOriginal.left);
1453 rcItem->left = rcOriginal.top;
1454 rcItem->right = rcOriginal.bottom;
1458 TAB_EnsureSelectionVisible(infoPtr);
1459 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
1461 /* Cleanup */
1462 SelectObject (hdc, hOldFont);
1463 ReleaseDC (infoPtr->hwnd, hdc);
1467 static void
1468 TAB_EraseTabInterior(const TAB_INFO *infoPtr, HDC hdc, INT iItem, const RECT *drawRect)
1470 HBRUSH hbr = CreateSolidBrush (comctl32_color.clrBtnFace);
1471 BOOL deleteBrush = TRUE;
1472 RECT rTemp = *drawRect;
1474 if (infoPtr->dwStyle & TCS_BUTTONS)
1476 if (iItem == infoPtr->iSelected)
1478 /* Background color */
1479 if (!(infoPtr->dwStyle & TCS_OWNERDRAWFIXED))
1481 DeleteObject(hbr);
1482 hbr = GetSysColorBrush(COLOR_SCROLLBAR);
1484 SetTextColor(hdc, comctl32_color.clr3dFace);
1485 SetBkColor(hdc, comctl32_color.clr3dHilight);
1487 /* if COLOR_WINDOW happens to be the same as COLOR_3DHILIGHT
1488 * we better use 0x55aa bitmap brush to make scrollbar's background
1489 * look different from the window background.
1491 if (comctl32_color.clr3dHilight == comctl32_color.clrWindow)
1492 hbr = COMCTL32_hPattern55AABrush;
1494 deleteBrush = FALSE;
1496 FillRect(hdc, &rTemp, hbr);
1498 else /* ! selected */
1500 if (infoPtr->dwStyle & TCS_FLATBUTTONS)
1502 InflateRect(&rTemp, 2, 2);
1503 FillRect(hdc, &rTemp, hbr);
1504 if (iItem == infoPtr->iHotTracked ||
1505 (iItem != infoPtr->iSelected && iItem == infoPtr->uFocus))
1506 DrawEdge(hdc, &rTemp, BDR_RAISEDINNER, BF_RECT);
1508 else
1509 FillRect(hdc, &rTemp, hbr);
1513 else /* !TCS_BUTTONS */
1515 InflateRect(&rTemp, -2, -2);
1516 if (!GetWindowTheme (infoPtr->hwnd))
1517 FillRect(hdc, &rTemp, hbr);
1520 /* highlighting is drawn on top of previous fills */
1521 if (TAB_GetItem(infoPtr, iItem)->dwState & TCIS_HIGHLIGHTED)
1523 if (deleteBrush)
1525 DeleteObject(hbr);
1526 deleteBrush = FALSE;
1528 hbr = GetSysColorBrush(COLOR_HIGHLIGHT);
1529 FillRect(hdc, &rTemp, hbr);
1532 /* Cleanup */
1533 if (deleteBrush) DeleteObject(hbr);
1536 /******************************************************************************
1537 * TAB_DrawItemInterior
1539 * This method is used to draw the interior (text and icon) of a single tab
1540 * into the tab control.
1542 static void
1543 TAB_DrawItemInterior(const TAB_INFO *infoPtr, HDC hdc, INT iItem, RECT *drawRect)
1545 RECT localRect;
1547 HPEN htextPen;
1548 HPEN holdPen;
1549 INT oldBkMode;
1550 HFONT hOldFont;
1552 /* if (drawRect == NULL) */
1554 BOOL isVisible;
1555 RECT itemRect;
1556 RECT selectedRect;
1559 * Get the rectangle for the item.
1561 isVisible = TAB_InternalGetItemRect(infoPtr, iItem, &itemRect, &selectedRect);
1562 if (!isVisible)
1563 return;
1566 * Make sure drawRect points to something valid; simplifies code.
1568 drawRect = &localRect;
1571 * This logic copied from the part of TAB_DrawItem which draws
1572 * the tab background. It's important to keep it in sync. I
1573 * would have liked to avoid code duplication, but couldn't figure
1574 * out how without making spaghetti of TAB_DrawItem.
1576 if (iItem == infoPtr->iSelected)
1577 *drawRect = selectedRect;
1578 else
1579 *drawRect = itemRect;
1581 if (infoPtr->dwStyle & TCS_BUTTONS)
1583 if (iItem == infoPtr->iSelected)
1585 drawRect->left += 4;
1586 drawRect->top += 4;
1587 drawRect->right -= 4;
1589 if (infoPtr->dwStyle & TCS_VERTICAL)
1591 if (!(infoPtr->dwStyle & TCS_BOTTOM)) drawRect->right += 1;
1592 drawRect->bottom -= 4;
1594 else
1596 if (infoPtr->dwStyle & TCS_BOTTOM)
1598 drawRect->top -= 2;
1599 drawRect->bottom -= 4;
1601 else
1602 drawRect->bottom -= 1;
1605 else
1606 InflateRect(drawRect, -2, -2);
1608 else
1610 if ((infoPtr->dwStyle & TCS_VERTICAL) && (infoPtr->dwStyle & TCS_BOTTOM))
1612 if (iItem != infoPtr->iSelected)
1614 drawRect->left += 2;
1615 InflateRect(drawRect, 0, -2);
1618 else if (infoPtr->dwStyle & TCS_VERTICAL)
1620 if (iItem == infoPtr->iSelected)
1622 drawRect->right += 1;
1624 else
1626 drawRect->right -= 2;
1627 InflateRect(drawRect, 0, -2);
1630 else if (infoPtr->dwStyle & TCS_BOTTOM)
1632 if (iItem == infoPtr->iSelected)
1634 drawRect->top -= 2;
1636 else
1638 InflateRect(drawRect, -2, -2);
1639 drawRect->bottom += 2;
1642 else
1644 if (iItem == infoPtr->iSelected)
1646 drawRect->bottom += 3;
1648 else
1650 drawRect->bottom -= 2;
1651 InflateRect(drawRect, -2, 0);
1656 TRACE("drawRect=(%s)\n", wine_dbgstr_rect(drawRect));
1658 /* Clear interior */
1659 TAB_EraseTabInterior (infoPtr, hdc, iItem, drawRect);
1661 /* Draw the focus rectangle */
1662 if (!(infoPtr->dwStyle & TCS_FOCUSNEVER) &&
1663 (GetFocus() == infoPtr->hwnd) &&
1664 (iItem == infoPtr->uFocus) )
1666 RECT rFocus = *drawRect;
1668 if (!(infoPtr->dwStyle & TCS_BUTTONS)) InflateRect(&rFocus, -3, -3);
1669 if (infoPtr->dwStyle & TCS_BOTTOM && !(infoPtr->dwStyle & TCS_VERTICAL))
1670 rFocus.top -= 3;
1672 /* focus should stay on selected item for TCS_BUTTONS style */
1673 if (!((infoPtr->dwStyle & TCS_BUTTONS) && (infoPtr->iSelected != iItem)))
1674 DrawFocusRect(hdc, &rFocus);
1678 * Text pen
1680 htextPen = CreatePen( PS_SOLID, 1, comctl32_color.clrBtnText );
1681 holdPen = SelectObject(hdc, htextPen);
1682 hOldFont = SelectObject(hdc, infoPtr->hFont);
1685 * Setup for text output
1687 oldBkMode = SetBkMode(hdc, TRANSPARENT);
1688 if (!GetWindowTheme (infoPtr->hwnd) || (infoPtr->dwStyle & TCS_BUTTONS))
1690 if ((infoPtr->dwStyle & TCS_HOTTRACK) && (iItem == infoPtr->iHotTracked) &&
1691 !(infoPtr->dwStyle & TCS_FLATBUTTONS))
1692 SetTextColor(hdc, comctl32_color.clrHighlight);
1693 else if (TAB_GetItem(infoPtr, iItem)->dwState & TCIS_HIGHLIGHTED)
1694 SetTextColor(hdc, comctl32_color.clrHighlightText);
1695 else
1696 SetTextColor(hdc, comctl32_color.clrBtnText);
1700 * if owner draw, tell the owner to draw
1702 if ((infoPtr->dwStyle & TCS_OWNERDRAWFIXED) && IsWindow(infoPtr->hwndNotify))
1704 DRAWITEMSTRUCT dis;
1705 UINT id;
1707 drawRect->top += 2;
1708 drawRect->right -= 1;
1709 if ( iItem == infoPtr->iSelected )
1710 InflateRect(drawRect, -1, 0);
1712 id = (UINT)GetWindowLongPtrW( infoPtr->hwnd, GWLP_ID );
1714 /* fill DRAWITEMSTRUCT */
1715 dis.CtlType = ODT_TAB;
1716 dis.CtlID = id;
1717 dis.itemID = iItem;
1718 dis.itemAction = ODA_DRAWENTIRE;
1719 dis.itemState = 0;
1720 if ( iItem == infoPtr->iSelected )
1721 dis.itemState |= ODS_SELECTED;
1722 if (infoPtr->uFocus == iItem)
1723 dis.itemState |= ODS_FOCUS;
1724 dis.hwndItem = infoPtr->hwnd;
1725 dis.hDC = hdc;
1726 dis.rcItem = *drawRect;
1728 /* when extra data fits ULONG_PTR, store it directly */
1729 if (infoPtr->cbInfo > sizeof(LPARAM))
1730 dis.itemData = (ULONG_PTR) TAB_GetItem(infoPtr, iItem)->extra;
1731 else
1733 /* this could be considered broken on 64 bit, but that's how it works -
1734 only first 4 bytes are copied */
1735 dis.itemData = 0;
1736 memcpy(&dis.itemData, (ULONG_PTR*)TAB_GetItem(infoPtr, iItem)->extra, 4);
1739 /* draw notification */
1740 SendMessageW( infoPtr->hwndNotify, WM_DRAWITEM, id, (LPARAM)&dis );
1742 else
1744 TAB_ITEM *item = TAB_GetItem(infoPtr, iItem);
1745 RECT rcTemp;
1746 RECT rcImage;
1748 /* used to center the icon and text in the tab */
1749 RECT rcText;
1750 INT center_offset_h, center_offset_v;
1752 /* set rcImage to drawRect, we will use top & left in our ImageList_Draw call */
1753 rcImage = *drawRect;
1755 rcTemp = *drawRect;
1756 SetRectEmpty(&rcText);
1758 /* get the rectangle that the text fits in */
1759 if (item->pszText)
1761 DrawTextW(hdc, item->pszText, -1, &rcText, DT_CALCRECT);
1764 * If not owner draw, then do the drawing ourselves.
1766 * Draw the icon.
1768 if (infoPtr->himl && item->iImage != -1)
1770 INT cx;
1771 INT cy;
1773 ImageList_GetIconSize(infoPtr->himl, &cx, &cy);
1775 if(infoPtr->dwStyle & TCS_VERTICAL)
1777 center_offset_h = ((drawRect->bottom - drawRect->top) - (cy + infoPtr->uHItemPadding + (rcText.right - rcText.left))) / 2;
1778 center_offset_v = ((drawRect->right - drawRect->left) - cx) / 2;
1780 else
1782 center_offset_h = ((drawRect->right - drawRect->left) - (cx + infoPtr->uHItemPadding + (rcText.right - rcText.left))) / 2;
1783 center_offset_v = ((drawRect->bottom - drawRect->top) - cy) / 2;
1786 /* if an item is selected, the icon is shifted up instead of down */
1787 if (iItem == infoPtr->iSelected)
1788 center_offset_v -= infoPtr->uVItemPadding / 2;
1789 else
1790 center_offset_v += infoPtr->uVItemPadding / 2;
1792 if (infoPtr->dwStyle & TCS_FIXEDWIDTH && infoPtr->dwStyle & (TCS_FORCELABELLEFT | TCS_FORCEICONLEFT))
1793 center_offset_h = infoPtr->uHItemPadding;
1795 if (center_offset_h < 2)
1796 center_offset_h = 2;
1798 if (center_offset_v < 0)
1799 center_offset_v = 0;
1801 TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%s), textlen=%d\n",
1802 debugstr_w(item->pszText), center_offset_h, center_offset_v,
1803 wine_dbgstr_rect(drawRect), (rcText.right-rcText.left));
1805 if((infoPtr->dwStyle & TCS_VERTICAL) && (infoPtr->dwStyle & TCS_BOTTOM))
1807 rcImage.top = drawRect->top + center_offset_h;
1808 /* if tab is TCS_VERTICAL and TCS_BOTTOM, the text is drawn from the */
1809 /* right side of the tab, but the image still uses the left as its x position */
1810 /* this keeps the image always drawn off of the same side of the tab */
1811 rcImage.left = drawRect->right - cx - center_offset_v;
1812 drawRect->top += cy + infoPtr->uHItemPadding;
1814 else if(infoPtr->dwStyle & TCS_VERTICAL)
1816 rcImage.top = drawRect->bottom - cy - center_offset_h;
1817 rcImage.left = drawRect->left + center_offset_v;
1818 drawRect->bottom -= cy + infoPtr->uHItemPadding;
1820 else /* normal style, whether TCS_BOTTOM or not */
1822 rcImage.left = drawRect->left + center_offset_h;
1823 rcImage.top = drawRect->top + center_offset_v;
1824 drawRect->left += cx + infoPtr->uHItemPadding;
1827 TRACE("drawing image=%d, left=%d, top=%d\n",
1828 item->iImage, rcImage.left, rcImage.top-1);
1829 ImageList_Draw
1831 infoPtr->himl,
1832 item->iImage,
1833 hdc,
1834 rcImage.left,
1835 rcImage.top,
1836 ILD_NORMAL
1840 /* Now position text */
1841 if (infoPtr->dwStyle & TCS_FIXEDWIDTH && infoPtr->dwStyle & TCS_FORCELABELLEFT)
1842 center_offset_h = infoPtr->uHItemPadding;
1843 else
1844 if(infoPtr->dwStyle & TCS_VERTICAL)
1845 center_offset_h = ((drawRect->bottom - drawRect->top) - (rcText.right - rcText.left)) / 2;
1846 else
1847 center_offset_h = ((drawRect->right - drawRect->left) - (rcText.right - rcText.left)) / 2;
1849 if(infoPtr->dwStyle & TCS_VERTICAL)
1851 if(infoPtr->dwStyle & TCS_BOTTOM)
1852 drawRect->top+=center_offset_h;
1853 else
1854 drawRect->bottom-=center_offset_h;
1856 center_offset_v = ((drawRect->right - drawRect->left) - (rcText.bottom - rcText.top)) / 2;
1858 else
1860 drawRect->left += center_offset_h;
1861 center_offset_v = ((drawRect->bottom - drawRect->top) - (rcText.bottom - rcText.top)) / 2;
1864 /* if an item is selected, the text is shifted up instead of down */
1865 if (iItem == infoPtr->iSelected)
1866 center_offset_v -= infoPtr->uVItemPadding / 2;
1867 else
1868 center_offset_v += infoPtr->uVItemPadding / 2;
1870 if (center_offset_v < 0)
1871 center_offset_v = 0;
1873 if(infoPtr->dwStyle & TCS_VERTICAL)
1874 drawRect->left += center_offset_v;
1875 else
1876 drawRect->top += center_offset_v;
1878 /* Draw the text */
1879 if(infoPtr->dwStyle & TCS_VERTICAL) /* if we are vertical rotate the text and each character */
1881 LOGFONTW logfont;
1882 HFONT hFont;
1883 INT nEscapement = 900;
1884 INT nOrientation = 900;
1886 if(infoPtr->dwStyle & TCS_BOTTOM)
1888 nEscapement = -900;
1889 nOrientation = -900;
1892 /* to get a font with the escapement and orientation we are looking for, we need to */
1893 /* call CreateFontIndirect, which requires us to set the values of the logfont we pass in */
1894 if (!GetObjectW(infoPtr->hFont, sizeof(logfont), &logfont))
1895 GetObjectW(GetStockObject(DEFAULT_GUI_FONT), sizeof(logfont), &logfont);
1897 logfont.lfEscapement = nEscapement;
1898 logfont.lfOrientation = nOrientation;
1899 hFont = CreateFontIndirectW(&logfont);
1900 SelectObject(hdc, hFont);
1902 if (item->pszText)
1904 ExtTextOutW(hdc,
1905 (infoPtr->dwStyle & TCS_BOTTOM) ? drawRect->right : drawRect->left,
1906 (!(infoPtr->dwStyle & TCS_BOTTOM)) ? drawRect->bottom : drawRect->top,
1907 ETO_CLIPPED,
1908 drawRect,
1909 item->pszText,
1910 lstrlenW(item->pszText),
1914 DeleteObject(hFont);
1916 else
1918 TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%s), textlen=%d\n",
1919 debugstr_w(item->pszText), center_offset_h, center_offset_v,
1920 wine_dbgstr_rect(drawRect), (rcText.right-rcText.left));
1921 if (item->pszText)
1923 DrawTextW
1925 hdc,
1926 item->pszText,
1927 lstrlenW(item->pszText),
1928 drawRect,
1929 DT_LEFT | DT_SINGLELINE
1934 *drawRect = rcTemp; /* restore drawRect */
1938 * Cleanup
1940 SelectObject(hdc, hOldFont);
1941 SetBkMode(hdc, oldBkMode);
1942 SelectObject(hdc, holdPen);
1943 DeleteObject( htextPen );
1946 /******************************************************************************
1947 * TAB_DrawItem
1949 * This method is used to draw a single tab into the tab control.
1951 static void TAB_DrawItem(const TAB_INFO *infoPtr, HDC hdc, INT iItem)
1953 RECT itemRect;
1954 RECT selectedRect;
1955 BOOL isVisible;
1956 RECT r, fillRect, r1;
1957 INT clRight = 0;
1958 INT clBottom = 0;
1959 COLORREF bkgnd, corner;
1960 HTHEME theme;
1963 * Get the rectangle for the item.
1965 isVisible = TAB_InternalGetItemRect(infoPtr,
1966 iItem,
1967 &itemRect,
1968 &selectedRect);
1970 if (isVisible)
1972 RECT rUD, rC;
1974 /* Clip UpDown control to not draw over it */
1975 if (infoPtr->needsScrolling)
1977 GetWindowRect(infoPtr->hwnd, &rC);
1978 GetWindowRect(infoPtr->hwndUpDown, &rUD);
1979 ExcludeClipRect(hdc, rUD.left - rC.left, rUD.top - rC.top, rUD.right - rC.left, rUD.bottom - rC.top);
1982 /* If you need to see what the control is doing,
1983 * then override these variables. They will change what
1984 * fill colors are used for filling the tabs, and the
1985 * corners when drawing the edge.
1987 bkgnd = comctl32_color.clrBtnFace;
1988 corner = comctl32_color.clrBtnFace;
1990 if (infoPtr->dwStyle & TCS_BUTTONS)
1992 /* Get item rectangle */
1993 r = itemRect;
1995 /* Separators between flat buttons */
1996 if ((infoPtr->dwStyle & TCS_FLATBUTTONS) && (infoPtr->exStyle & TCS_EX_FLATSEPARATORS))
1998 r1 = r;
1999 r1.right += (FLAT_BTN_SPACINGX -2);
2000 DrawEdge(hdc, &r1, EDGE_ETCHED, BF_RIGHT);
2003 if (iItem == infoPtr->iSelected)
2005 DrawEdge(hdc, &r, EDGE_SUNKEN, BF_SOFT|BF_RECT);
2007 OffsetRect(&r, 1, 1);
2009 else /* ! selected */
2011 DWORD state = TAB_GetItem(infoPtr, iItem)->dwState;
2013 if ((state & TCIS_BUTTONPRESSED) || (iItem == infoPtr->uFocus))
2014 DrawEdge(hdc, &r, EDGE_SUNKEN, BF_SOFT|BF_RECT);
2015 else
2016 if (!(infoPtr->dwStyle & TCS_FLATBUTTONS))
2017 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_RECT);
2020 else /* !TCS_BUTTONS */
2022 /* We draw a rectangle of different sizes depending on the selection
2023 * state. */
2024 if (iItem == infoPtr->iSelected) {
2025 RECT rect;
2026 GetClientRect (infoPtr->hwnd, &rect);
2027 clRight = rect.right;
2028 clBottom = rect.bottom;
2029 r = selectedRect;
2031 else
2032 r = itemRect;
2035 * Erase the background. (Delay it but setup rectangle.)
2036 * This is necessary when drawing the selected item since it is larger
2037 * than the others, it might overlap with stuff already drawn by the
2038 * other tabs
2040 fillRect = r;
2042 /* Draw themed tabs - but only if they are at the top.
2043 * Windows draws even side or bottom tabs themed, with wacky results.
2044 * However, since in Wine apps may get themed that did not opt in via
2045 * a manifest avoid theming when we know the result will be wrong */
2046 if ((theme = GetWindowTheme (infoPtr->hwnd))
2047 && ((infoPtr->dwStyle & (TCS_VERTICAL | TCS_BOTTOM)) == 0))
2049 static const int partIds[8] = {
2050 /* Normal item */
2051 TABP_TABITEM,
2052 TABP_TABITEMLEFTEDGE,
2053 TABP_TABITEMRIGHTEDGE,
2054 TABP_TABITEMBOTHEDGE,
2055 /* Selected tab */
2056 TABP_TOPTABITEM,
2057 TABP_TOPTABITEMLEFTEDGE,
2058 TABP_TOPTABITEMRIGHTEDGE,
2059 TABP_TOPTABITEMBOTHEDGE,
2061 int partIndex = 0;
2062 int stateId = TIS_NORMAL;
2064 /* selected and unselected tabs have different parts */
2065 if (iItem == infoPtr->iSelected)
2066 partIndex += 4;
2067 /* The part also differs on the position of a tab on a line.
2068 * "Visually" determining the position works well enough. */
2069 GetClientRect(infoPtr->hwnd, &r1);
2070 if(selectedRect.left == 0)
2071 partIndex += 1;
2072 if(selectedRect.right == r1.right)
2073 partIndex += 2;
2075 if (iItem == infoPtr->iSelected)
2076 stateId = TIS_SELECTED;
2077 else if (iItem == infoPtr->iHotTracked)
2078 stateId = TIS_HOT;
2079 else if (iItem == infoPtr->uFocus)
2080 stateId = TIS_FOCUSED;
2082 /* Adjust rectangle for bottommost row */
2083 if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2084 r.bottom += 3;
2086 DrawThemeBackground (theme, hdc, partIds[partIndex], stateId, &r, NULL);
2087 GetThemeBackgroundContentRect (theme, hdc, partIds[partIndex], stateId, &r, &r);
2089 else if(infoPtr->dwStyle & TCS_VERTICAL)
2091 /* These are for adjusting the drawing of a Selected tab */
2092 /* The initial values are for the normal case of non-Selected */
2093 int ZZ = 1; /* Do not stretch if selected */
2094 if (iItem == infoPtr->iSelected) {
2095 ZZ = 0;
2097 /* if leftmost draw the line longer */
2098 if(selectedRect.top == 0)
2099 fillRect.top += CONTROL_BORDER_SIZEY;
2100 /* if rightmost draw the line longer */
2101 if(selectedRect.bottom == clBottom)
2102 fillRect.bottom -= CONTROL_BORDER_SIZEY;
2105 if (infoPtr->dwStyle & TCS_BOTTOM)
2107 /* Adjust both rectangles to match native */
2108 r.left += (1-ZZ);
2110 TRACE("<right> item=%d, fill=(%s), edge=(%s)\n",
2111 iItem, wine_dbgstr_rect(&fillRect), wine_dbgstr_rect(&r));
2113 /* Clear interior */
2114 SetBkColor(hdc, bkgnd);
2115 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2117 /* Draw rectangular edge around tab */
2118 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_RIGHT|BF_TOP|BF_BOTTOM);
2120 /* Now erase the top corner and draw diagonal edge */
2121 SetBkColor(hdc, corner);
2122 r1.left = r.right - ROUND_CORNER_SIZE - 1;
2123 r1.top = r.top;
2124 r1.right = r.right;
2125 r1.bottom = r1.top + ROUND_CORNER_SIZE;
2126 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2127 r1.right--;
2128 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPLEFT);
2130 /* Now erase the bottom corner and draw diagonal edge */
2131 r1.left = r.right - ROUND_CORNER_SIZE - 1;
2132 r1.bottom = r.bottom;
2133 r1.right = r.right;
2134 r1.top = r1.bottom - ROUND_CORNER_SIZE;
2135 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2136 r1.right--;
2137 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMLEFT);
2139 if ((iItem == infoPtr->iSelected) && (selectedRect.top == 0)) {
2140 r1 = r;
2141 r1.right = r1.left;
2142 r1.left--;
2143 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_TOP);
2147 else
2149 TRACE("<left> item=%d, fill=(%s), edge=(%s)\n",
2150 iItem, wine_dbgstr_rect(&fillRect), wine_dbgstr_rect(&r));
2152 /* Clear interior */
2153 SetBkColor(hdc, bkgnd);
2154 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2156 /* Draw rectangular edge around tab */
2157 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_TOP|BF_BOTTOM);
2159 /* Now erase the top corner and draw diagonal edge */
2160 SetBkColor(hdc, corner);
2161 r1.left = r.left;
2162 r1.top = r.top;
2163 r1.right = r1.left + ROUND_CORNER_SIZE + 1;
2164 r1.bottom = r1.top + ROUND_CORNER_SIZE;
2165 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2166 r1.left++;
2167 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPRIGHT);
2169 /* Now erase the bottom corner and draw diagonal edge */
2170 r1.left = r.left;
2171 r1.bottom = r.bottom;
2172 r1.right = r1.left + ROUND_CORNER_SIZE + 1;
2173 r1.top = r1.bottom - ROUND_CORNER_SIZE;
2174 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2175 r1.left++;
2176 DrawEdge(hdc, &r1, EDGE_SUNKEN, BF_DIAGONAL_ENDTOPLEFT);
2179 else /* ! TCS_VERTICAL */
2181 /* These are for adjusting the drawing of a Selected tab */
2182 /* The initial values are for the normal case of non-Selected */
2183 if (iItem == infoPtr->iSelected) {
2184 /* if leftmost draw the line longer */
2185 if(selectedRect.left == 0)
2186 fillRect.left += CONTROL_BORDER_SIZEX;
2187 /* if rightmost draw the line longer */
2188 if(selectedRect.right == clRight)
2189 fillRect.right -= CONTROL_BORDER_SIZEX;
2192 if (infoPtr->dwStyle & TCS_BOTTOM)
2194 /* Adjust both rectangles for topmost row */
2195 if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2197 fillRect.top -= 2;
2198 r.top -= 1;
2201 TRACE("<bottom> item=%d, fill=(%s), edge=(%s)\n",
2202 iItem, wine_dbgstr_rect(&fillRect), wine_dbgstr_rect(&r));
2204 /* Clear interior */
2205 SetBkColor(hdc, bkgnd);
2206 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2208 /* Draw rectangular edge around tab */
2209 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_BOTTOM|BF_RIGHT);
2211 /* Now erase the righthand corner and draw diagonal edge */
2212 SetBkColor(hdc, corner);
2213 r1.left = r.right - ROUND_CORNER_SIZE;
2214 r1.bottom = r.bottom;
2215 r1.right = r.right;
2216 r1.top = r1.bottom - ROUND_CORNER_SIZE - 1;
2217 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2218 r1.bottom--;
2219 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMLEFT);
2221 /* Now erase the lefthand corner and draw diagonal edge */
2222 r1.left = r.left;
2223 r1.bottom = r.bottom;
2224 r1.right = r1.left + ROUND_CORNER_SIZE;
2225 r1.top = r1.bottom - ROUND_CORNER_SIZE - 1;
2226 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2227 r1.bottom--;
2228 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPLEFT);
2230 if (iItem == infoPtr->iSelected)
2232 r.top += 2;
2233 r.left += 1;
2234 if (selectedRect.left == 0)
2236 r1 = r;
2237 r1.bottom = r1.top;
2238 r1.top--;
2239 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_LEFT);
2244 else
2246 /* Adjust both rectangles for bottommost row */
2247 if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2249 fillRect.bottom += 3;
2250 r.bottom += 2;
2253 TRACE("<top> item=%d, fill=(%s), edge=(%s)\n",
2254 iItem, wine_dbgstr_rect(&fillRect), wine_dbgstr_rect(&r));
2256 /* Clear interior */
2257 SetBkColor(hdc, bkgnd);
2258 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2260 /* Draw rectangular edge around tab */
2261 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_TOP|BF_RIGHT);
2263 /* Now erase the righthand corner and draw diagonal edge */
2264 SetBkColor(hdc, corner);
2265 r1.left = r.right - ROUND_CORNER_SIZE;
2266 r1.top = r.top;
2267 r1.right = r.right;
2268 r1.bottom = r1.top + ROUND_CORNER_SIZE + 1;
2269 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2270 r1.top++;
2271 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMRIGHT);
2273 /* Now erase the lefthand corner and draw diagonal edge */
2274 r1.left = r.left;
2275 r1.top = r.top;
2276 r1.right = r1.left + ROUND_CORNER_SIZE;
2277 r1.bottom = r1.top + ROUND_CORNER_SIZE + 1;
2278 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2279 r1.top++;
2280 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPRIGHT);
2285 TAB_DumpItemInternal(infoPtr, iItem);
2287 /* This modifies r to be the text rectangle. */
2288 TAB_DrawItemInterior(infoPtr, hdc, iItem, &r);
2292 /******************************************************************************
2293 * TAB_DrawBorder
2295 * This method is used to draw the raised border around the tab control
2296 * "content" area.
2298 static void TAB_DrawBorder(const TAB_INFO *infoPtr, HDC hdc)
2300 RECT rect;
2301 HTHEME theme = GetWindowTheme (infoPtr->hwnd);
2303 GetClientRect (infoPtr->hwnd, &rect);
2306 * Adjust for the style
2309 if (infoPtr->uNumItem)
2311 if ((infoPtr->dwStyle & TCS_BOTTOM) && !(infoPtr->dwStyle & TCS_VERTICAL))
2312 rect.bottom -= infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2313 else if((infoPtr->dwStyle & TCS_BOTTOM) && (infoPtr->dwStyle & TCS_VERTICAL))
2314 rect.right -= infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2315 else if(infoPtr->dwStyle & TCS_VERTICAL)
2316 rect.left += infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2317 else /* not TCS_VERTICAL and not TCS_BOTTOM */
2318 rect.top += infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2321 TRACE("border=(%s)\n", wine_dbgstr_rect(&rect));
2323 if (theme)
2324 DrawThemeBackground (theme, hdc, TABP_PANE, 0, &rect, NULL);
2325 else
2326 DrawEdge(hdc, &rect, EDGE_RAISED, BF_SOFT|BF_RECT);
2329 /******************************************************************************
2330 * TAB_Refresh
2332 * This method repaints the tab control..
2334 static void TAB_Refresh (const TAB_INFO *infoPtr, HDC hdc)
2336 HFONT hOldFont;
2337 INT i;
2339 if (!infoPtr->DoRedraw)
2340 return;
2342 hOldFont = SelectObject (hdc, infoPtr->hFont);
2344 if (infoPtr->dwStyle & TCS_BUTTONS)
2346 for (i = 0; i < infoPtr->uNumItem; i++)
2347 TAB_DrawItem (infoPtr, hdc, i);
2349 else
2351 /* Draw all the non selected item first */
2352 for (i = 0; i < infoPtr->uNumItem; i++)
2354 if (i != infoPtr->iSelected)
2355 TAB_DrawItem (infoPtr, hdc, i);
2358 /* Now, draw the border, draw it before the selected item
2359 * since the selected item overwrites part of the border. */
2360 TAB_DrawBorder (infoPtr, hdc);
2362 /* Then, draw the selected item */
2363 TAB_DrawItem (infoPtr, hdc, infoPtr->iSelected);
2366 SelectObject (hdc, hOldFont);
2369 static inline DWORD TAB_GetRowCount (const TAB_INFO *infoPtr)
2371 TRACE("(%p)\n", infoPtr);
2372 return infoPtr->uNumRows;
2375 static inline LRESULT TAB_SetRedraw (TAB_INFO *infoPtr, BOOL doRedraw)
2377 infoPtr->DoRedraw = doRedraw;
2378 return 0;
2381 /******************************************************************************
2382 * TAB_EnsureSelectionVisible
2384 * This method will make sure that the current selection is completely
2385 * visible by scrolling until it is.
2387 static void TAB_EnsureSelectionVisible(
2388 TAB_INFO* infoPtr)
2390 INT iSelected = infoPtr->iSelected;
2391 INT iOrigLeftmostVisible = infoPtr->leftmostVisible;
2393 if (iSelected < 0)
2394 return;
2396 /* set the items row to the bottommost row or topmost row depending on
2397 * style */
2398 if ((infoPtr->uNumRows > 1) && !(infoPtr->dwStyle & TCS_BUTTONS))
2400 TAB_ITEM *selected = TAB_GetItem(infoPtr, iSelected);
2401 INT newselected;
2402 INT iTargetRow;
2404 if(infoPtr->dwStyle & TCS_VERTICAL)
2405 newselected = selected->rect.left;
2406 else
2407 newselected = selected->rect.top;
2409 /* the target row is always (number of rows - 1)
2410 as row 0 is furthest from the clientRect */
2411 iTargetRow = infoPtr->uNumRows - 1;
2413 if (newselected != iTargetRow)
2415 UINT i;
2416 if(infoPtr->dwStyle & TCS_VERTICAL)
2418 for (i=0; i < infoPtr->uNumItem; i++)
2420 /* move everything in the row of the selected item to the iTargetRow */
2421 TAB_ITEM *item = TAB_GetItem(infoPtr, i);
2423 if (item->rect.left == newselected )
2424 item->rect.left = iTargetRow;
2425 else
2427 if (item->rect.left > newselected)
2428 item->rect.left-=1;
2432 else
2434 for (i=0; i < infoPtr->uNumItem; i++)
2436 TAB_ITEM *item = TAB_GetItem(infoPtr, i);
2438 if (item->rect.top == newselected )
2439 item->rect.top = iTargetRow;
2440 else
2442 if (item->rect.top > newselected)
2443 item->rect.top-=1;
2447 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
2452 * Do the trivial cases first.
2454 if ( (!infoPtr->needsScrolling) ||
2455 (infoPtr->hwndUpDown==0) || (infoPtr->dwStyle & TCS_VERTICAL))
2456 return;
2458 if (infoPtr->leftmostVisible >= iSelected)
2460 infoPtr->leftmostVisible = iSelected;
2462 else
2464 TAB_ITEM *selected = TAB_GetItem(infoPtr, iSelected);
2465 RECT r;
2466 INT width;
2467 UINT i;
2469 /* Calculate the part of the client area that is visible */
2470 GetClientRect(infoPtr->hwnd, &r);
2471 width = r.right;
2473 GetClientRect(infoPtr->hwndUpDown, &r);
2474 width -= r.right;
2476 if ((selected->rect.right -
2477 selected->rect.left) >= width )
2479 /* Special case: width of selected item is greater than visible
2480 * part of control.
2482 infoPtr->leftmostVisible = iSelected;
2484 else
2486 for (i = infoPtr->leftmostVisible; i < infoPtr->uNumItem; i++)
2488 if ((selected->rect.right - TAB_GetItem(infoPtr, i)->rect.left) < width)
2489 break;
2491 infoPtr->leftmostVisible = i;
2495 if (infoPtr->leftmostVisible != iOrigLeftmostVisible)
2496 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
2498 SendMessageW(infoPtr->hwndUpDown, UDM_SETPOS, 0,
2499 MAKELONG(infoPtr->leftmostVisible, 0));
2502 /******************************************************************************
2503 * TAB_InvalidateTabArea
2505 * This method will invalidate the portion of the control that contains the
2506 * tabs. It is called when the state of the control changes and needs
2507 * to be redisplayed
2509 static void TAB_InvalidateTabArea(const TAB_INFO *infoPtr)
2511 RECT clientRect, rInvalidate, rAdjClient;
2512 INT lastRow = infoPtr->uNumRows - 1;
2513 RECT rect;
2515 if (lastRow < 0) return;
2517 GetClientRect(infoPtr->hwnd, &clientRect);
2518 rInvalidate = clientRect;
2519 rAdjClient = clientRect;
2521 TAB_AdjustRect(infoPtr, 0, &rAdjClient);
2523 TAB_InternalGetItemRect(infoPtr, infoPtr->uNumItem-1 , &rect, NULL);
2524 if ((infoPtr->dwStyle & TCS_BOTTOM) && (infoPtr->dwStyle & TCS_VERTICAL))
2526 rInvalidate.left = rAdjClient.right;
2527 if (infoPtr->uNumRows == 1)
2528 rInvalidate.bottom = clientRect.top + rect.bottom + 2 * SELECTED_TAB_OFFSET;
2530 else if(infoPtr->dwStyle & TCS_VERTICAL)
2532 rInvalidate.right = rAdjClient.left;
2533 if (infoPtr->uNumRows == 1)
2534 rInvalidate.bottom = clientRect.top + rect.bottom + 2 * SELECTED_TAB_OFFSET;
2536 else if (infoPtr->dwStyle & TCS_BOTTOM)
2538 rInvalidate.top = rAdjClient.bottom;
2539 if (infoPtr->uNumRows == 1)
2540 rInvalidate.right = clientRect.left + rect.right + 2 * SELECTED_TAB_OFFSET;
2542 else
2544 rInvalidate.bottom = rAdjClient.top;
2545 if (infoPtr->uNumRows == 1)
2546 rInvalidate.right = clientRect.left + rect.right + 2 * SELECTED_TAB_OFFSET;
2549 /* Punch out the updown control */
2550 if (infoPtr->needsScrolling && (rInvalidate.right > 0)) {
2551 RECT r;
2552 GetClientRect(infoPtr->hwndUpDown, &r);
2553 if (rInvalidate.right > clientRect.right - r.left)
2554 rInvalidate.right = rInvalidate.right - (r.right - r.left);
2555 else
2556 rInvalidate.right = clientRect.right - r.left;
2559 TRACE("invalidate (%s)\n", wine_dbgstr_rect(&rInvalidate));
2561 InvalidateRect(infoPtr->hwnd, &rInvalidate, TRUE);
2564 static inline LRESULT TAB_Paint (TAB_INFO *infoPtr, HDC hdcPaint)
2566 HDC hdc;
2567 PAINTSTRUCT ps;
2569 if (hdcPaint)
2570 hdc = hdcPaint;
2571 else
2573 hdc = BeginPaint (infoPtr->hwnd, &ps);
2574 TRACE("erase %d, rect=(%s)\n", ps.fErase, wine_dbgstr_rect(&ps.rcPaint));
2577 TAB_Refresh (infoPtr, hdc);
2579 if (!hdcPaint)
2580 EndPaint (infoPtr->hwnd, &ps);
2582 return 0;
2585 static LRESULT
2586 TAB_InsertItemT (TAB_INFO *infoPtr, INT iItem, const TCITEMW *pti, BOOL bUnicode)
2588 TAB_ITEM *item;
2589 RECT rect;
2591 GetClientRect (infoPtr->hwnd, &rect);
2592 TRACE("Rect: %p %s\n", infoPtr->hwnd, wine_dbgstr_rect(&rect));
2594 if (iItem < 0) return -1;
2595 if (iItem > infoPtr->uNumItem)
2596 iItem = infoPtr->uNumItem;
2598 TAB_DumpItemExternalT(pti, iItem, bUnicode);
2600 if (!(item = Alloc(TAB_ITEM_SIZE(infoPtr)))) return FALSE;
2601 if (DPA_InsertPtr(infoPtr->items, iItem, item) == -1)
2603 Free(item);
2604 return FALSE;
2607 if (infoPtr->uNumItem == 0)
2608 infoPtr->iSelected = 0;
2609 else if (iItem <= infoPtr->iSelected)
2610 infoPtr->iSelected++;
2612 infoPtr->uNumItem++;
2614 item->pszText = NULL;
2615 if (pti->mask & TCIF_TEXT)
2617 if (bUnicode)
2618 Str_SetPtrW (&item->pszText, pti->pszText);
2619 else
2620 Str_SetPtrAtoW (&item->pszText, (LPSTR)pti->pszText);
2623 if (pti->mask & TCIF_IMAGE)
2624 item->iImage = pti->iImage;
2625 else
2626 item->iImage = -1;
2628 if (pti->mask & TCIF_PARAM)
2629 memcpy(item->extra, &pti->lParam, EXTRA_ITEM_SIZE(infoPtr));
2630 else
2631 memset(item->extra, 0, EXTRA_ITEM_SIZE(infoPtr));
2633 TAB_SetItemBounds(infoPtr);
2634 if (infoPtr->uNumItem > 1)
2635 TAB_InvalidateTabArea(infoPtr);
2636 else
2637 InvalidateRect(infoPtr->hwnd, NULL, TRUE);
2639 TRACE("[%p]: added item %d %s\n",
2640 infoPtr->hwnd, iItem, debugstr_w(item->pszText));
2642 /* If we haven't set the current focus yet, set it now. */
2643 if (infoPtr->uFocus == -1)
2644 TAB_SetCurFocus(infoPtr, iItem);
2646 return iItem;
2649 static LRESULT
2650 TAB_SetItemSize (TAB_INFO *infoPtr, INT cx, INT cy)
2652 LONG lResult = 0;
2653 BOOL bNeedPaint = FALSE;
2655 lResult = MAKELONG(infoPtr->tabWidth, infoPtr->tabHeight);
2657 /* UNDOCUMENTED: If requested Width or Height is 0 this means that program wants to use auto size. */
2658 if (infoPtr->dwStyle & TCS_FIXEDWIDTH && (infoPtr->tabWidth != cx))
2660 infoPtr->tabWidth = cx;
2661 bNeedPaint = TRUE;
2664 if (infoPtr->tabHeight != cy)
2666 if ((infoPtr->fHeightSet = (cy != 0)))
2667 infoPtr->tabHeight = cy;
2669 bNeedPaint = TRUE;
2671 TRACE("was h=%d,w=%d, now h=%d,w=%d\n",
2672 HIWORD(lResult), LOWORD(lResult),
2673 infoPtr->tabHeight, infoPtr->tabWidth);
2675 if (bNeedPaint)
2677 TAB_SetItemBounds(infoPtr);
2678 RedrawWindow(infoPtr->hwnd, NULL, NULL, RDW_ERASE | RDW_INVALIDATE | RDW_UPDATENOW);
2681 return lResult;
2684 static inline LRESULT TAB_SetMinTabWidth (TAB_INFO *infoPtr, INT cx)
2686 INT oldcx = 0;
2688 TRACE("(%p,%d)\n", infoPtr, cx);
2690 if (infoPtr->tabMinWidth < 0)
2691 oldcx = DEFAULT_MIN_TAB_WIDTH;
2692 else
2693 oldcx = infoPtr->tabMinWidth;
2694 infoPtr->tabMinWidth = cx;
2695 TAB_SetItemBounds(infoPtr);
2696 return oldcx;
2699 static inline LRESULT
2700 TAB_HighlightItem (TAB_INFO *infoPtr, INT iItem, BOOL fHighlight)
2702 LPDWORD lpState;
2703 DWORD oldState;
2704 RECT r;
2706 TRACE("(%p,%d,%s)\n", infoPtr, iItem, fHighlight ? "true" : "false");
2708 if (iItem < 0 || iItem >= infoPtr->uNumItem)
2709 return FALSE;
2711 lpState = &TAB_GetItem(infoPtr, iItem)->dwState;
2712 oldState = *lpState;
2714 if (fHighlight)
2715 *lpState |= TCIS_HIGHLIGHTED;
2716 else
2717 *lpState &= ~TCIS_HIGHLIGHTED;
2719 if ((oldState != *lpState) && TAB_InternalGetItemRect (infoPtr, iItem, &r, NULL))
2720 InvalidateRect (infoPtr->hwnd, &r, TRUE);
2722 return TRUE;
2725 static LRESULT
2726 TAB_SetItemT (TAB_INFO *infoPtr, INT iItem, LPTCITEMW tabItem, BOOL bUnicode)
2728 TAB_ITEM *wineItem;
2730 TRACE("(%p,%d,%p,%s)\n", infoPtr, iItem, tabItem, bUnicode ? "true" : "false");
2732 if (iItem < 0 || iItem >= infoPtr->uNumItem)
2733 return FALSE;
2735 TAB_DumpItemExternalT(tabItem, iItem, bUnicode);
2737 wineItem = TAB_GetItem(infoPtr, iItem);
2739 if (tabItem->mask & TCIF_IMAGE)
2740 wineItem->iImage = tabItem->iImage;
2742 if (tabItem->mask & TCIF_PARAM)
2743 memcpy(wineItem->extra, &tabItem->lParam, infoPtr->cbInfo);
2745 if (tabItem->mask & TCIF_RTLREADING)
2746 FIXME("TCIF_RTLREADING\n");
2748 if (tabItem->mask & TCIF_STATE)
2749 wineItem->dwState = (wineItem->dwState & ~tabItem->dwStateMask) |
2750 ( tabItem->dwState & tabItem->dwStateMask);
2752 if (tabItem->mask & TCIF_TEXT)
2754 Free(wineItem->pszText);
2755 wineItem->pszText = NULL;
2756 if (bUnicode)
2757 Str_SetPtrW(&wineItem->pszText, tabItem->pszText);
2758 else
2759 Str_SetPtrAtoW(&wineItem->pszText, (LPSTR)tabItem->pszText);
2762 /* Update and repaint tabs */
2763 TAB_SetItemBounds(infoPtr);
2764 TAB_InvalidateTabArea(infoPtr);
2766 return TRUE;
2769 static inline LRESULT TAB_GetItemCount (const TAB_INFO *infoPtr)
2771 TRACE("\n");
2772 return infoPtr->uNumItem;
2776 static LRESULT
2777 TAB_GetItemT (TAB_INFO *infoPtr, INT iItem, LPTCITEMW tabItem, BOOL bUnicode)
2779 TAB_ITEM *wineItem;
2781 TRACE("(%p,%d,%p,%s)\n", infoPtr, iItem, tabItem, bUnicode ? "true" : "false");
2783 if (!tabItem) return FALSE;
2785 if (iItem < 0 || iItem >= infoPtr->uNumItem)
2787 /* init requested fields */
2788 if (tabItem->mask & TCIF_IMAGE) tabItem->iImage = 0;
2789 if (tabItem->mask & TCIF_PARAM) tabItem->lParam = 0;
2790 if (tabItem->mask & TCIF_STATE) tabItem->dwState = 0;
2791 return FALSE;
2794 wineItem = TAB_GetItem(infoPtr, iItem);
2796 if (tabItem->mask & TCIF_IMAGE)
2797 tabItem->iImage = wineItem->iImage;
2799 if (tabItem->mask & TCIF_PARAM)
2800 memcpy(&tabItem->lParam, wineItem->extra, infoPtr->cbInfo);
2802 if (tabItem->mask & TCIF_RTLREADING)
2803 FIXME("TCIF_RTLREADING\n");
2805 if (tabItem->mask & TCIF_STATE)
2806 tabItem->dwState = wineItem->dwState & tabItem->dwStateMask;
2808 if (tabItem->mask & TCIF_TEXT)
2810 if (bUnicode)
2811 Str_GetPtrW (wineItem->pszText, tabItem->pszText, tabItem->cchTextMax);
2812 else
2813 Str_GetPtrWtoA (wineItem->pszText, (LPSTR)tabItem->pszText, tabItem->cchTextMax);
2816 TAB_DumpItemExternalT(tabItem, iItem, bUnicode);
2818 return TRUE;
2822 static LRESULT TAB_DeleteItem (TAB_INFO *infoPtr, INT iItem)
2824 TAB_ITEM *item;
2826 TRACE("(%p, %d)\n", infoPtr, iItem);
2828 if (iItem < 0 || iItem >= infoPtr->uNumItem) return FALSE;
2830 TAB_InvalidateTabArea(infoPtr);
2831 item = TAB_GetItem(infoPtr, iItem);
2832 Free(item->pszText);
2833 Free(item);
2834 infoPtr->uNumItem--;
2835 DPA_DeletePtr(infoPtr->items, iItem);
2837 if (infoPtr->uNumItem == 0)
2839 if (infoPtr->iHotTracked >= 0)
2841 KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
2842 infoPtr->iHotTracked = -1;
2845 infoPtr->iSelected = -1;
2847 else
2849 if (iItem <= infoPtr->iHotTracked)
2851 /* When tabs move left/up, the hot track item may change */
2852 FIXME("Recalc hot track\n");
2856 /* adjust the selected index */
2857 if (iItem == infoPtr->iSelected)
2858 infoPtr->iSelected = -1;
2859 else if (iItem < infoPtr->iSelected)
2860 infoPtr->iSelected--;
2862 /* reposition and repaint tabs */
2863 TAB_SetItemBounds(infoPtr);
2865 return TRUE;
2868 static inline LRESULT TAB_DeleteAllItems (TAB_INFO *infoPtr)
2870 TRACE("(%p)\n", infoPtr);
2871 while (infoPtr->uNumItem)
2872 TAB_DeleteItem (infoPtr, 0);
2873 return TRUE;
2877 static inline LRESULT TAB_GetFont (const TAB_INFO *infoPtr)
2879 TRACE("(%p) returning %p\n", infoPtr, infoPtr->hFont);
2880 return (LRESULT)infoPtr->hFont;
2883 static inline LRESULT TAB_SetFont (TAB_INFO *infoPtr, HFONT hNewFont)
2885 TRACE("(%p,%p)\n", infoPtr, hNewFont);
2887 infoPtr->hFont = hNewFont;
2889 TAB_SetItemBounds(infoPtr);
2891 TAB_InvalidateTabArea(infoPtr);
2893 return 0;
2897 static inline LRESULT TAB_GetImageList (const TAB_INFO *infoPtr)
2899 TRACE("\n");
2900 return (LRESULT)infoPtr->himl;
2903 static inline LRESULT TAB_SetImageList (TAB_INFO *infoPtr, HIMAGELIST himlNew)
2905 HIMAGELIST himlPrev = infoPtr->himl;
2906 TRACE("himl=%p\n", himlNew);
2907 infoPtr->himl = himlNew;
2908 TAB_SetItemBounds(infoPtr);
2909 InvalidateRect(infoPtr->hwnd, NULL, TRUE);
2910 return (LRESULT)himlPrev;
2913 static inline LRESULT TAB_GetUnicodeFormat (const TAB_INFO *infoPtr)
2915 TRACE("(%p)\n", infoPtr);
2916 return infoPtr->bUnicode;
2919 static inline LRESULT TAB_SetUnicodeFormat (TAB_INFO *infoPtr, BOOL bUnicode)
2921 BOOL bTemp = infoPtr->bUnicode;
2923 TRACE("(%p %d)\n", infoPtr, bUnicode);
2924 infoPtr->bUnicode = bUnicode;
2926 return bTemp;
2929 static inline LRESULT TAB_Size (TAB_INFO *infoPtr)
2931 /* I'm not really sure what the following code was meant to do.
2932 This is what it is doing:
2933 When WM_SIZE is sent with SIZE_RESTORED, the control
2934 gets positioned in the top left corner.
2936 RECT parent_rect;
2937 HWND parent;
2938 UINT uPosFlags,cx,cy;
2940 uPosFlags=0;
2941 if (!wParam) {
2942 parent = GetParent (hwnd);
2943 GetClientRect(parent, &parent_rect);
2944 cx=LOWORD (lParam);
2945 cy=HIWORD (lParam);
2946 if (GetWindowLongW(hwnd, GWL_STYLE) & CCS_NORESIZE)
2947 uPosFlags |= (SWP_NOSIZE | SWP_NOMOVE);
2949 SetWindowPos (hwnd, 0, parent_rect.left, parent_rect.top,
2950 cx, cy, uPosFlags | SWP_NOZORDER);
2951 } else {
2952 FIXME("WM_SIZE flag %x %lx not handled\n", wParam, lParam);
2953 } */
2955 /* Recompute the size/position of the tabs. */
2956 TAB_SetItemBounds (infoPtr);
2958 /* Force a repaint of the control. */
2959 InvalidateRect(infoPtr->hwnd, NULL, TRUE);
2961 return 0;
2965 static LRESULT TAB_Create (HWND hwnd, LPARAM lParam)
2967 TAB_INFO *infoPtr;
2968 TEXTMETRICW fontMetrics;
2969 HDC hdc;
2970 HFONT hOldFont;
2971 DWORD style;
2973 infoPtr = Alloc (sizeof(TAB_INFO));
2975 SetWindowLongPtrW(hwnd, 0, (DWORD_PTR)infoPtr);
2977 infoPtr->hwnd = hwnd;
2978 infoPtr->hwndNotify = ((LPCREATESTRUCTW)lParam)->hwndParent;
2979 infoPtr->uNumItem = 0;
2980 infoPtr->uNumRows = 0;
2981 infoPtr->uHItemPadding = 6;
2982 infoPtr->uVItemPadding = 3;
2983 infoPtr->uHItemPadding_s = 6;
2984 infoPtr->uVItemPadding_s = 3;
2985 infoPtr->hFont = 0;
2986 infoPtr->items = DPA_Create(8);
2987 infoPtr->hcurArrow = LoadCursorW (0, (LPWSTR)IDC_ARROW);
2988 infoPtr->iSelected = -1;
2989 infoPtr->iHotTracked = -1;
2990 infoPtr->uFocus = -1;
2991 infoPtr->hwndToolTip = 0;
2992 infoPtr->DoRedraw = TRUE;
2993 infoPtr->needsScrolling = FALSE;
2994 infoPtr->hwndUpDown = 0;
2995 infoPtr->leftmostVisible = 0;
2996 infoPtr->fHeightSet = FALSE;
2997 infoPtr->bUnicode = IsWindowUnicode (hwnd);
2998 infoPtr->cbInfo = sizeof(LPARAM);
3000 TRACE("Created tab control, hwnd [%p]\n", hwnd);
3002 /* The tab control always has the WS_CLIPSIBLINGS style. Even
3003 if you don't specify it in CreateWindow. This is necessary in
3004 order for paint to work correctly. This follows windows behaviour. */
3005 style = GetWindowLongW(hwnd, GWL_STYLE);
3006 if (style & TCS_VERTICAL) style |= TCS_MULTILINE;
3007 style |= WS_CLIPSIBLINGS;
3008 SetWindowLongW(hwnd, GWL_STYLE, style);
3010 infoPtr->dwStyle = style;
3011 infoPtr->exStyle = (style & TCS_FLATBUTTONS) ? TCS_EX_FLATSEPARATORS : 0;
3013 if (infoPtr->dwStyle & TCS_TOOLTIPS) {
3014 /* Create tooltip control */
3015 infoPtr->hwndToolTip =
3016 CreateWindowExW (0, TOOLTIPS_CLASSW, NULL, WS_POPUP,
3017 CW_USEDEFAULT, CW_USEDEFAULT,
3018 CW_USEDEFAULT, CW_USEDEFAULT,
3019 hwnd, 0, 0, 0);
3021 /* Send NM_TOOLTIPSCREATED notification */
3022 if (infoPtr->hwndToolTip) {
3023 NMTOOLTIPSCREATED nmttc;
3025 nmttc.hdr.hwndFrom = hwnd;
3026 nmttc.hdr.idFrom = GetWindowLongPtrW(hwnd, GWLP_ID);
3027 nmttc.hdr.code = NM_TOOLTIPSCREATED;
3028 nmttc.hwndToolTips = infoPtr->hwndToolTip;
3030 SendMessageW (infoPtr->hwndNotify, WM_NOTIFY,
3031 GetWindowLongPtrW(hwnd, GWLP_ID), (LPARAM)&nmttc);
3035 OpenThemeData (infoPtr->hwnd, themeClass);
3038 * We need to get text information so we need a DC and we need to select
3039 * a font.
3041 hdc = GetDC(hwnd);
3042 hOldFont = SelectObject (hdc, GetStockObject (SYSTEM_FONT));
3044 /* Use the system font to determine the initial height of a tab. */
3045 GetTextMetricsW(hdc, &fontMetrics);
3048 * Make sure there is enough space for the letters + growing the
3049 * selected item + extra space for the selected item.
3051 infoPtr->tabHeight = fontMetrics.tmHeight + SELECTED_TAB_OFFSET +
3052 ((infoPtr->dwStyle & TCS_BUTTONS) ? 2 : 1) *
3053 infoPtr->uVItemPadding;
3055 /* Initialize the width of a tab. */
3056 if (infoPtr->dwStyle & TCS_FIXEDWIDTH)
3057 infoPtr->tabWidth = GetDeviceCaps(hdc, LOGPIXELSX);
3059 infoPtr->tabMinWidth = -1;
3061 TRACE("tabH=%d, tabW=%d\n", infoPtr->tabHeight, infoPtr->tabWidth);
3063 SelectObject (hdc, hOldFont);
3064 ReleaseDC(hwnd, hdc);
3066 return 0;
3069 static LRESULT
3070 TAB_Destroy (TAB_INFO *infoPtr)
3072 INT iItem;
3074 SetWindowLongPtrW(infoPtr->hwnd, 0, 0);
3076 for (iItem = infoPtr->uNumItem - 1; iItem >= 0; iItem--)
3078 TAB_ITEM *tab = TAB_GetItem(infoPtr, iItem);
3080 DPA_DeletePtr(infoPtr->items, iItem);
3081 infoPtr->uNumItem--;
3083 Free(tab->pszText);
3084 Free(tab);
3086 DPA_Destroy(infoPtr->items);
3087 infoPtr->items = NULL;
3089 if (infoPtr->hwndToolTip)
3090 DestroyWindow (infoPtr->hwndToolTip);
3092 if (infoPtr->hwndUpDown)
3093 DestroyWindow(infoPtr->hwndUpDown);
3095 if (infoPtr->iHotTracked >= 0)
3096 KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
3098 CloseThemeData (GetWindowTheme (infoPtr->hwnd));
3100 Free (infoPtr);
3101 return 0;
3104 /* update theme after a WM_THEMECHANGED message */
3105 static LRESULT theme_changed(const TAB_INFO *infoPtr)
3107 HTHEME theme = GetWindowTheme (infoPtr->hwnd);
3108 CloseThemeData (theme);
3109 OpenThemeData (infoPtr->hwnd, themeClass);
3110 return 0;
3113 static LRESULT TAB_NCCalcSize(WPARAM wParam)
3115 if (!wParam)
3116 return 0;
3117 return WVR_ALIGNTOP;
3120 static inline LRESULT
3121 TAB_SetItemExtra (TAB_INFO *infoPtr, INT cbInfo)
3123 TRACE("(%p %d)\n", infoPtr, cbInfo);
3125 if (cbInfo < 0 || infoPtr->uNumItem) return FALSE;
3127 infoPtr->cbInfo = cbInfo;
3128 return TRUE;
3131 static LRESULT TAB_RemoveImage (TAB_INFO *infoPtr, INT image)
3133 TRACE("%p %d\n", infoPtr, image);
3135 if (ImageList_Remove (infoPtr->himl, image))
3137 INT i, *idx;
3138 RECT r;
3140 /* shift indices, repaint items if needed */
3141 for (i = 0; i < infoPtr->uNumItem; i++)
3143 idx = &TAB_GetItem(infoPtr, i)->iImage;
3144 if (*idx >= image)
3146 if (*idx == image)
3147 *idx = -1;
3148 else
3149 (*idx)--;
3151 /* repaint item */
3152 if (TAB_InternalGetItemRect (infoPtr, i, &r, NULL))
3153 InvalidateRect (infoPtr->hwnd, &r, TRUE);
3158 return 0;
3161 static LRESULT
3162 TAB_SetExtendedStyle (TAB_INFO *infoPtr, DWORD exMask, DWORD exStyle)
3164 DWORD prevstyle = infoPtr->exStyle;
3166 /* zero mask means all styles */
3167 if (exMask == 0) exMask = ~0;
3169 if (exMask & TCS_EX_REGISTERDROP)
3171 FIXME("TCS_EX_REGISTERDROP style unimplemented\n");
3172 exMask &= ~TCS_EX_REGISTERDROP;
3173 exStyle &= ~TCS_EX_REGISTERDROP;
3176 if (exMask & TCS_EX_FLATSEPARATORS)
3178 if ((prevstyle ^ exStyle) & TCS_EX_FLATSEPARATORS)
3180 infoPtr->exStyle ^= TCS_EX_FLATSEPARATORS;
3181 TAB_InvalidateTabArea(infoPtr);
3185 return prevstyle;
3188 static inline LRESULT
3189 TAB_GetExtendedStyle (const TAB_INFO *infoPtr)
3191 return infoPtr->exStyle;
3194 static LRESULT
3195 TAB_DeselectAll (TAB_INFO *infoPtr, BOOL excludesel)
3197 BOOL paint = FALSE;
3198 INT i, selected = infoPtr->iSelected;
3200 TRACE("(%p, %d)\n", infoPtr, excludesel);
3202 if (!(infoPtr->dwStyle & TCS_BUTTONS))
3203 return 0;
3205 for (i = 0; i < infoPtr->uNumItem; i++)
3207 if ((TAB_GetItem(infoPtr, i)->dwState & TCIS_BUTTONPRESSED) &&
3208 (selected != i))
3210 TAB_GetItem(infoPtr, i)->dwState &= ~TCIS_BUTTONPRESSED;
3211 paint = TRUE;
3215 if (!excludesel && (selected != -1))
3217 TAB_GetItem(infoPtr, selected)->dwState &= ~TCIS_BUTTONPRESSED;
3218 infoPtr->iSelected = -1;
3219 paint = TRUE;
3222 if (paint)
3223 TAB_InvalidateTabArea (infoPtr);
3225 return 0;
3228 /***
3229 * DESCRIPTION:
3230 * Processes WM_STYLECHANGED messages.
3232 * PARAMETER(S):
3233 * [I] infoPtr : valid pointer to the tab data structure
3234 * [I] wStyleType : window style type (normal or extended)
3235 * [I] lpss : window style information
3237 * RETURN:
3238 * Zero
3240 static INT TAB_StyleChanged(TAB_INFO *infoPtr, WPARAM wStyleType,
3241 const STYLESTRUCT *lpss)
3243 TRACE("(styletype=%lx, styleOld=0x%08x, styleNew=0x%08x)\n",
3244 wStyleType, lpss->styleOld, lpss->styleNew);
3246 if (wStyleType != GWL_STYLE) return 0;
3248 infoPtr->dwStyle = lpss->styleNew;
3250 TAB_SetItemBounds (infoPtr);
3251 InvalidateRect(infoPtr->hwnd, NULL, TRUE);
3253 return 0;
3256 static LRESULT WINAPI
3257 TAB_WindowProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
3259 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
3261 TRACE("hwnd=%p msg=%x wParam=%lx lParam=%lx\n", hwnd, uMsg, wParam, lParam);
3262 if (!infoPtr && (uMsg != WM_CREATE))
3263 return DefWindowProcW (hwnd, uMsg, wParam, lParam);
3265 switch (uMsg)
3267 case TCM_GETIMAGELIST:
3268 return TAB_GetImageList (infoPtr);
3270 case TCM_SETIMAGELIST:
3271 return TAB_SetImageList (infoPtr, (HIMAGELIST)lParam);
3273 case TCM_GETITEMCOUNT:
3274 return TAB_GetItemCount (infoPtr);
3276 case TCM_GETITEMA:
3277 case TCM_GETITEMW:
3278 return TAB_GetItemT (infoPtr, (INT)wParam, (LPTCITEMW)lParam, uMsg == TCM_GETITEMW);
3280 case TCM_SETITEMA:
3281 case TCM_SETITEMW:
3282 return TAB_SetItemT (infoPtr, (INT)wParam, (LPTCITEMW)lParam, uMsg == TCM_SETITEMW);
3284 case TCM_DELETEITEM:
3285 return TAB_DeleteItem (infoPtr, (INT)wParam);
3287 case TCM_DELETEALLITEMS:
3288 return TAB_DeleteAllItems (infoPtr);
3290 case TCM_GETITEMRECT:
3291 return TAB_GetItemRect (infoPtr, (INT)wParam, (LPRECT)lParam);
3293 case TCM_GETCURSEL:
3294 return TAB_GetCurSel (infoPtr);
3296 case TCM_HITTEST:
3297 return TAB_HitTest (infoPtr, (LPTCHITTESTINFO)lParam);
3299 case TCM_SETCURSEL:
3300 return TAB_SetCurSel (infoPtr, (INT)wParam);
3302 case TCM_INSERTITEMA:
3303 case TCM_INSERTITEMW:
3304 return TAB_InsertItemT (infoPtr, (INT)wParam, (TCITEMW*)lParam, uMsg == TCM_INSERTITEMW);
3306 case TCM_SETITEMEXTRA:
3307 return TAB_SetItemExtra (infoPtr, (INT)wParam);
3309 case TCM_ADJUSTRECT:
3310 return TAB_AdjustRect (infoPtr, (BOOL)wParam, (LPRECT)lParam);
3312 case TCM_SETITEMSIZE:
3313 return TAB_SetItemSize (infoPtr, (INT)LOWORD(lParam), (INT)HIWORD(lParam));
3315 case TCM_REMOVEIMAGE:
3316 return TAB_RemoveImage (infoPtr, (INT)wParam);
3318 case TCM_SETPADDING:
3319 return TAB_SetPadding (infoPtr, lParam);
3321 case TCM_GETROWCOUNT:
3322 return TAB_GetRowCount(infoPtr);
3324 case TCM_GETUNICODEFORMAT:
3325 return TAB_GetUnicodeFormat (infoPtr);
3327 case TCM_SETUNICODEFORMAT:
3328 return TAB_SetUnicodeFormat (infoPtr, (BOOL)wParam);
3330 case TCM_HIGHLIGHTITEM:
3331 return TAB_HighlightItem (infoPtr, (INT)wParam, (BOOL)LOWORD(lParam));
3333 case TCM_GETTOOLTIPS:
3334 return TAB_GetToolTips (infoPtr);
3336 case TCM_SETTOOLTIPS:
3337 return TAB_SetToolTips (infoPtr, (HWND)wParam);
3339 case TCM_GETCURFOCUS:
3340 return TAB_GetCurFocus (infoPtr);
3342 case TCM_SETCURFOCUS:
3343 return TAB_SetCurFocus (infoPtr, (INT)wParam);
3345 case TCM_SETMINTABWIDTH:
3346 return TAB_SetMinTabWidth(infoPtr, (INT)lParam);
3348 case TCM_DESELECTALL:
3349 return TAB_DeselectAll (infoPtr, (BOOL)wParam);
3351 case TCM_GETEXTENDEDSTYLE:
3352 return TAB_GetExtendedStyle (infoPtr);
3354 case TCM_SETEXTENDEDSTYLE:
3355 return TAB_SetExtendedStyle (infoPtr, wParam, lParam);
3357 case WM_GETFONT:
3358 return TAB_GetFont (infoPtr);
3360 case WM_SETFONT:
3361 return TAB_SetFont (infoPtr, (HFONT)wParam);
3363 case WM_CREATE:
3364 return TAB_Create (hwnd, lParam);
3366 case WM_NCDESTROY:
3367 return TAB_Destroy (infoPtr);
3369 case WM_GETDLGCODE:
3370 return DLGC_WANTARROWS | DLGC_WANTCHARS;
3372 case WM_LBUTTONDOWN:
3373 return TAB_LButtonDown (infoPtr, wParam, lParam);
3375 case WM_LBUTTONUP:
3376 return TAB_LButtonUp (infoPtr);
3378 case WM_NOTIFY:
3379 return SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, wParam, lParam);
3381 case WM_RBUTTONUP:
3382 TAB_RButtonUp (infoPtr);
3383 return DefWindowProcW (hwnd, uMsg, wParam, lParam);
3385 case WM_MOUSEMOVE:
3386 return TAB_MouseMove (infoPtr, wParam, lParam);
3388 case WM_PRINTCLIENT:
3389 case WM_PAINT:
3390 return TAB_Paint (infoPtr, (HDC)wParam);
3392 case WM_SIZE:
3393 return TAB_Size (infoPtr);
3395 case WM_SETREDRAW:
3396 return TAB_SetRedraw (infoPtr, (BOOL)wParam);
3398 case WM_HSCROLL:
3399 return TAB_OnHScroll(infoPtr, (int)LOWORD(wParam), (int)HIWORD(wParam));
3401 case WM_STYLECHANGED:
3402 return TAB_StyleChanged(infoPtr, wParam, (LPSTYLESTRUCT)lParam);
3404 case WM_SYSCOLORCHANGE:
3405 COMCTL32_RefreshSysColors();
3406 return 0;
3408 case WM_THEMECHANGED:
3409 return theme_changed (infoPtr);
3411 case WM_KILLFOCUS:
3412 TAB_KillFocus(infoPtr);
3413 case WM_SETFOCUS:
3414 TAB_FocusChanging(infoPtr);
3415 break; /* Don't disturb normal focus behavior */
3417 case WM_KEYDOWN:
3418 return TAB_KeyDown(infoPtr, wParam, lParam);
3420 case WM_NCHITTEST:
3421 return TAB_NCHitTest(infoPtr, lParam);
3423 case WM_NCCALCSIZE:
3424 return TAB_NCCalcSize(wParam);
3426 default:
3427 if (uMsg >= WM_USER && uMsg < WM_APP && !COMCTL32_IsReflectedMessage(uMsg))
3428 WARN("unknown msg %04x wp=%08lx lp=%08lx\n",
3429 uMsg, wParam, lParam);
3430 break;
3432 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
3436 void
3437 TAB_Register (void)
3439 WNDCLASSW wndClass;
3441 ZeroMemory (&wndClass, sizeof(WNDCLASSW));
3442 wndClass.style = CS_GLOBALCLASS | CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
3443 wndClass.lpfnWndProc = TAB_WindowProc;
3444 wndClass.cbClsExtra = 0;
3445 wndClass.cbWndExtra = sizeof(TAB_INFO *);
3446 wndClass.hCursor = LoadCursorW (0, (LPWSTR)IDC_ARROW);
3447 wndClass.hbrBackground = (HBRUSH)(COLOR_BTNFACE+1);
3448 wndClass.lpszClassName = WC_TABCONTROLW;
3450 RegisterClassW (&wndClass);
3454 void
3455 TAB_Unregister (void)
3457 UnregisterClassW (WC_TABCONTROLW, NULL);