windowscodecs: Make the IWICPalette implementation thread-safe.
[wine/hacks.git] / dlls / comctl32 / tab.c
blobc99153eea3126cd3594d7005b9783ea44c09b14c
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 <stdarg.h>
56 #include <string.h>
58 #include "windef.h"
59 #include "winbase.h"
60 #include "wingdi.h"
61 #include "winuser.h"
62 #include "winnls.h"
63 #include "commctrl.h"
64 #include "comctl32.h"
65 #include "uxtheme.h"
66 #include "tmschema.h"
67 #include "wine/debug.h"
68 #include <math.h>
70 WINE_DEFAULT_DEBUG_CHANNEL(tab);
72 typedef struct
74 DWORD dwState;
75 LPWSTR pszText;
76 INT iImage;
77 RECT rect; /* bounding rectangle of the item relative to the
78 * leftmost item (the leftmost item, 0, would have a
79 * "left" member of 0 in this rectangle)
81 * additionally the top member holds the row number
82 * and bottom is unused and should be 0 */
83 BYTE extra[1]; /* Space for caller supplied info, variable size */
84 } TAB_ITEM;
86 /* The size of a tab item depends on how much extra data is requested */
87 #define TAB_ITEM_SIZE(infoPtr) (FIELD_OFFSET(TAB_ITEM, extra[(infoPtr)->cbInfo]))
89 typedef struct
91 HWND hwnd; /* Tab control window */
92 HWND hwndNotify; /* notification window (parent) */
93 UINT uNumItem; /* number of tab items */
94 UINT uNumRows; /* number of tab rows */
95 INT tabHeight; /* height of the tab row */
96 INT tabWidth; /* width of tabs */
97 INT tabMinWidth; /* minimum width of items */
98 USHORT uHItemPadding; /* amount of horizontal padding, in pixels */
99 USHORT uVItemPadding; /* amount of vertical padding, in pixels */
100 USHORT uHItemPadding_s; /* Set amount of horizontal padding, in pixels */
101 USHORT uVItemPadding_s; /* Set amount of vertical padding, in pixels */
102 HFONT hFont; /* handle to the current font */
103 HCURSOR hcurArrow; /* handle to the current cursor */
104 HIMAGELIST himl; /* handle to an image list (may be 0) */
105 HWND hwndToolTip; /* handle to tab's tooltip */
106 INT leftmostVisible; /* Used for scrolling, this member contains
107 * the index of the first visible item */
108 INT iSelected; /* the currently selected item */
109 INT iHotTracked; /* the highlighted item under the mouse */
110 INT uFocus; /* item which has the focus */
111 TAB_ITEM* items; /* pointer to an array of TAB_ITEM's */
112 BOOL DoRedraw; /* flag for redrawing when tab contents is changed*/
113 BOOL needsScrolling; /* TRUE if the size of the tabs is greater than
114 * the size of the control */
115 BOOL fHeightSet; /* was the height of the tabs explicitly set? */
116 BOOL bUnicode; /* Unicode control? */
117 HWND hwndUpDown; /* Updown control used for scrolling */
118 INT cbInfo; /* Number of bytes of caller supplied info per tab */
120 DWORD exStyle; /* Extended style used, currently:
121 TCS_EX_FLATSEPARATORS, TCS_EX_REGISTERDROP */
122 DWORD dwStyle; /* the cached window GWL_STYLE */
123 } TAB_INFO;
125 /******************************************************************************
126 * Positioning constants
128 #define SELECTED_TAB_OFFSET 2
129 #define ROUND_CORNER_SIZE 2
130 #define DISPLAY_AREA_PADDINGX 2
131 #define DISPLAY_AREA_PADDINGY 2
132 #define CONTROL_BORDER_SIZEX 2
133 #define CONTROL_BORDER_SIZEY 2
134 #define BUTTON_SPACINGX 3
135 #define BUTTON_SPACINGY 3
136 #define FLAT_BTN_SPACINGX 8
137 #define DEFAULT_MIN_TAB_WIDTH 54
138 #define DEFAULT_PADDING_X 6
139 #define EXTRA_ICON_PADDING 3
141 #define TAB_GetInfoPtr(hwnd) ((TAB_INFO *)GetWindowLongPtrW(hwnd,0))
142 /* Since items are variable sized, cannot directly access them */
143 #define TAB_GetItem(info,i) \
144 ((TAB_ITEM*)((LPBYTE)info->items + (i) * TAB_ITEM_SIZE(info)))
146 #define GET_DEFAULT_MIN_TAB_WIDTH(infoPtr) (DEFAULT_MIN_TAB_WIDTH - (DEFAULT_PADDING_X - (infoPtr)->uHItemPadding) * 2)
148 /******************************************************************************
149 * Hot-tracking timer constants
151 #define TAB_HOTTRACK_TIMER 1
152 #define TAB_HOTTRACK_TIMER_INTERVAL 100 /* milliseconds */
154 static const WCHAR themeClass[] = { 'T','a','b',0 };
156 /******************************************************************************
157 * Prototypes
159 static void TAB_InvalidateTabArea(const TAB_INFO *);
160 static void TAB_EnsureSelectionVisible(TAB_INFO *);
161 static void TAB_DrawItemInterior(const TAB_INFO *, HDC, INT, RECT*);
162 static LRESULT TAB_DeselectAll(TAB_INFO *, BOOL);
163 static BOOL TAB_InternalGetItemRect(const TAB_INFO *, INT, RECT*, RECT*);
165 static BOOL
166 TAB_SendSimpleNotify (const TAB_INFO *infoPtr, UINT code)
168 NMHDR nmhdr;
170 nmhdr.hwndFrom = infoPtr->hwnd;
171 nmhdr.idFrom = GetWindowLongPtrW(infoPtr->hwnd, GWLP_ID);
172 nmhdr.code = code;
174 return (BOOL) SendMessageW (infoPtr->hwndNotify, WM_NOTIFY,
175 nmhdr.idFrom, (LPARAM) &nmhdr);
178 static void
179 TAB_RelayEvent (HWND hwndTip, HWND hwndMsg, UINT uMsg,
180 WPARAM wParam, LPARAM lParam)
182 MSG msg;
184 msg.hwnd = hwndMsg;
185 msg.message = uMsg;
186 msg.wParam = wParam;
187 msg.lParam = lParam;
188 msg.time = GetMessageTime ();
189 msg.pt.x = (short)LOWORD(GetMessagePos ());
190 msg.pt.y = (short)HIWORD(GetMessagePos ());
192 SendMessageW (hwndTip, TTM_RELAYEVENT, 0, (LPARAM)&msg);
195 static void
196 TAB_DumpItemExternalT(const TCITEMW *pti, UINT iItem, BOOL isW)
198 if (TRACE_ON(tab)) {
199 TRACE("external tab %d, mask=0x%08x, dwState=0x%08x, dwStateMask=0x%08x, cchTextMax=0x%08x\n",
200 iItem, pti->mask, pti->dwState, pti->dwStateMask, pti->cchTextMax);
201 TRACE("external tab %d, iImage=%d, lParam=0x%08lx, pszTextW=%s\n",
202 iItem, pti->iImage, pti->lParam, isW ? debugstr_w(pti->pszText) : debugstr_a((LPSTR)pti->pszText));
206 static void
207 TAB_DumpItemInternal(const TAB_INFO *infoPtr, UINT iItem)
209 if (TRACE_ON(tab)) {
210 TAB_ITEM *ti;
212 ti = TAB_GetItem(infoPtr, iItem);
213 TRACE("tab %d, dwState=0x%08x, pszText=%s, iImage=%d\n",
214 iItem, ti->dwState, debugstr_w(ti->pszText), ti->iImage);
215 TRACE("tab %d, rect.left=%d, rect.top(row)=%d\n",
216 iItem, ti->rect.left, ti->rect.top);
220 /* RETURNS
221 * the index of the selected tab, or -1 if no tab is selected. */
222 static inline LRESULT TAB_GetCurSel (const TAB_INFO *infoPtr)
224 TRACE("(%p)\n", infoPtr);
225 return infoPtr->iSelected;
228 /* RETURNS
229 * the index of the tab item that has the focus. */
230 static inline LRESULT
231 TAB_GetCurFocus (const TAB_INFO *infoPtr)
233 TRACE("(%p)\n", infoPtr);
234 return infoPtr->uFocus;
237 static inline LRESULT TAB_GetToolTips (const TAB_INFO *infoPtr)
239 TRACE("(%p)\n", infoPtr);
240 return (LRESULT)infoPtr->hwndToolTip;
243 static inline LRESULT TAB_SetCurSel (TAB_INFO *infoPtr, INT iItem)
245 INT prevItem = infoPtr->iSelected;
247 TRACE("(%p %d)\n", infoPtr, iItem);
249 if (iItem < 0)
250 infoPtr->iSelected = -1;
251 else if (iItem >= infoPtr->uNumItem)
252 return -1;
253 else {
254 if (prevItem != iItem) {
255 if (prevItem != -1)
256 TAB_GetItem(infoPtr, prevItem)->dwState &= ~TCIS_BUTTONPRESSED;
257 TAB_GetItem(infoPtr, iItem)->dwState |= TCIS_BUTTONPRESSED;
259 infoPtr->iSelected = iItem;
260 infoPtr->uFocus = iItem;
261 TAB_EnsureSelectionVisible(infoPtr);
262 TAB_InvalidateTabArea(infoPtr);
265 return prevItem;
268 static LRESULT TAB_SetCurFocus (TAB_INFO *infoPtr, INT iItem)
270 TRACE("(%p %d)\n", infoPtr, iItem);
272 if (iItem < 0) {
273 infoPtr->uFocus = -1;
274 if (infoPtr->iSelected != -1) {
275 infoPtr->iSelected = -1;
276 TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGE);
277 TAB_InvalidateTabArea(infoPtr);
280 else if (iItem < infoPtr->uNumItem) {
281 if (infoPtr->dwStyle & TCS_BUTTONS) {
282 /* set focus to new item, leave selection as is */
283 if (infoPtr->uFocus != iItem) {
284 INT prev_focus = infoPtr->uFocus;
285 RECT r;
287 infoPtr->uFocus = iItem;
289 if (prev_focus != infoPtr->iSelected) {
290 if (TAB_InternalGetItemRect(infoPtr, prev_focus, &r, NULL))
291 InvalidateRect(infoPtr->hwnd, &r, FALSE);
294 if (TAB_InternalGetItemRect(infoPtr, iItem, &r, NULL))
295 InvalidateRect(infoPtr->hwnd, &r, FALSE);
297 TAB_SendSimpleNotify(infoPtr, TCN_FOCUSCHANGE);
299 } else {
300 INT oldFocus = infoPtr->uFocus;
301 if (infoPtr->iSelected != iItem || oldFocus == -1 ) {
302 infoPtr->uFocus = iItem;
303 if (oldFocus != -1) {
304 if (!TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGING)) {
305 infoPtr->iSelected = iItem;
306 TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGE);
308 else
309 infoPtr->iSelected = iItem;
310 TAB_EnsureSelectionVisible(infoPtr);
311 TAB_InvalidateTabArea(infoPtr);
316 return 0;
319 static inline LRESULT
320 TAB_SetToolTips (TAB_INFO *infoPtr, HWND hwndToolTip)
322 TRACE("%p %p\n", infoPtr, hwndToolTip);
323 infoPtr->hwndToolTip = hwndToolTip;
324 return 0;
327 static inline LRESULT
328 TAB_SetPadding (TAB_INFO *infoPtr, LPARAM lParam)
330 TRACE("(%p %d %d)\n", infoPtr, LOWORD(lParam), HIWORD(lParam));
331 infoPtr->uHItemPadding_s = LOWORD(lParam);
332 infoPtr->uVItemPadding_s = HIWORD(lParam);
334 return 0;
337 /******************************************************************************
338 * TAB_InternalGetItemRect
340 * This method will calculate the rectangle representing a given tab item in
341 * client coordinates. This method takes scrolling into account.
343 * This method returns TRUE if the item is visible in the window and FALSE
344 * if it is completely outside the client area.
346 static BOOL TAB_InternalGetItemRect(
347 const TAB_INFO* infoPtr,
348 INT itemIndex,
349 RECT* itemRect,
350 RECT* selectedRect)
352 RECT tmpItemRect,clientRect;
354 /* Perform a sanity check and a trivial visibility check. */
355 if ( (infoPtr->uNumItem <= 0) ||
356 (itemIndex >= infoPtr->uNumItem) ||
357 (!(((infoPtr->dwStyle & TCS_MULTILINE) || (infoPtr->dwStyle & TCS_VERTICAL))) &&
358 (itemIndex < infoPtr->leftmostVisible)))
360 TRACE("Not Visible\n");
361 /* need to initialize these to empty rects */
362 if (itemRect)
364 memset(itemRect,0,sizeof(RECT));
365 itemRect->bottom = infoPtr->tabHeight;
367 if (selectedRect)
368 memset(selectedRect,0,sizeof(RECT));
369 return FALSE;
373 * Avoid special cases in this procedure by assigning the "out"
374 * parameters if the caller didn't supply them
376 if (itemRect == NULL)
377 itemRect = &tmpItemRect;
379 /* Retrieve the unmodified item rect. */
380 *itemRect = TAB_GetItem(infoPtr,itemIndex)->rect;
382 /* calculate the times bottom and top based on the row */
383 GetClientRect(infoPtr->hwnd, &clientRect);
385 if ((infoPtr->dwStyle & TCS_BOTTOM) && (infoPtr->dwStyle & TCS_VERTICAL))
387 itemRect->right = clientRect.right - SELECTED_TAB_OFFSET - itemRect->left * infoPtr->tabHeight -
388 ((infoPtr->dwStyle & TCS_BUTTONS) ? itemRect->left * BUTTON_SPACINGX : 0);
389 itemRect->left = itemRect->right - infoPtr->tabHeight;
391 else if (infoPtr->dwStyle & TCS_VERTICAL)
393 itemRect->left = clientRect.left + SELECTED_TAB_OFFSET + itemRect->left * infoPtr->tabHeight +
394 ((infoPtr->dwStyle & TCS_BUTTONS) ? itemRect->left * BUTTON_SPACINGX : 0);
395 itemRect->right = itemRect->left + infoPtr->tabHeight;
397 else if (infoPtr->dwStyle & TCS_BOTTOM)
399 itemRect->bottom = clientRect.bottom - itemRect->top * infoPtr->tabHeight -
400 ((infoPtr->dwStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : SELECTED_TAB_OFFSET);
401 itemRect->top = itemRect->bottom - infoPtr->tabHeight;
403 else /* not TCS_BOTTOM and not TCS_VERTICAL */
405 itemRect->top = clientRect.top + itemRect->top * infoPtr->tabHeight +
406 ((infoPtr->dwStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : SELECTED_TAB_OFFSET);
407 itemRect->bottom = itemRect->top + infoPtr->tabHeight;
411 * "scroll" it to make sure the item at the very left of the
412 * tab control is the leftmost visible tab.
414 if(infoPtr->dwStyle & TCS_VERTICAL)
416 OffsetRect(itemRect,
418 -TAB_GetItem(infoPtr, infoPtr->leftmostVisible)->rect.top);
421 * Move the rectangle so the first item is slightly offset from
422 * the bottom of the tab control.
424 OffsetRect(itemRect,
426 SELECTED_TAB_OFFSET);
428 } else
430 OffsetRect(itemRect,
431 -TAB_GetItem(infoPtr, infoPtr->leftmostVisible)->rect.left,
435 * Move the rectangle so the first item is slightly offset from
436 * the left of the tab control.
438 OffsetRect(itemRect,
439 SELECTED_TAB_OFFSET,
442 TRACE("item %d tab h=%d, rect=(%s)\n",
443 itemIndex, infoPtr->tabHeight, wine_dbgstr_rect(itemRect));
445 /* Now, calculate the position of the item as if it were selected. */
446 if (selectedRect!=NULL)
448 CopyRect(selectedRect, itemRect);
450 /* The rectangle of a selected item is a bit wider. */
451 if(infoPtr->dwStyle & TCS_VERTICAL)
452 InflateRect(selectedRect, 0, SELECTED_TAB_OFFSET);
453 else
454 InflateRect(selectedRect, SELECTED_TAB_OFFSET, 0);
456 /* If it also a bit higher. */
457 if ((infoPtr->dwStyle & TCS_BOTTOM) && (infoPtr->dwStyle & TCS_VERTICAL))
459 selectedRect->left -= 2; /* the border is thicker on the right */
460 selectedRect->right += SELECTED_TAB_OFFSET;
462 else if (infoPtr->dwStyle & TCS_VERTICAL)
464 selectedRect->left -= SELECTED_TAB_OFFSET;
465 selectedRect->right += 1;
467 else if (infoPtr->dwStyle & TCS_BOTTOM)
469 selectedRect->bottom += SELECTED_TAB_OFFSET;
471 else /* not TCS_BOTTOM and not TCS_VERTICAL */
473 selectedRect->top -= SELECTED_TAB_OFFSET;
474 selectedRect->bottom -= 1;
478 /* Check for visibility */
479 if (infoPtr->dwStyle & TCS_VERTICAL)
480 return (itemRect->top < clientRect.bottom) && (itemRect->bottom > clientRect.top);
481 else
482 return (itemRect->left < clientRect.right) && (itemRect->right > clientRect.left);
485 static inline BOOL
486 TAB_GetItemRect(const TAB_INFO *infoPtr, INT item, RECT *rect)
488 TRACE("(%p, %d, %p)\n", infoPtr, item, rect);
489 return TAB_InternalGetItemRect(infoPtr, item, rect, NULL);
492 /******************************************************************************
493 * TAB_KeyDown
495 * This method is called to handle keyboard input
497 static LRESULT TAB_KeyDown(TAB_INFO* infoPtr, WPARAM keyCode, LPARAM lParam)
499 INT newItem = -1;
500 NMTCKEYDOWN nm;
502 /* TCN_KEYDOWN notification sent always */
503 nm.hdr.hwndFrom = infoPtr->hwnd;
504 nm.hdr.idFrom = GetWindowLongPtrW(infoPtr->hwnd, GWLP_ID);
505 nm.hdr.code = TCN_KEYDOWN;
506 nm.wVKey = keyCode;
507 nm.flags = lParam;
508 SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, nm.hdr.idFrom, (LPARAM)&nm);
510 switch (keyCode)
512 case VK_LEFT:
513 newItem = infoPtr->uFocus - 1;
514 break;
515 case VK_RIGHT:
516 newItem = infoPtr->uFocus + 1;
517 break;
520 /* If we changed to a valid item, change focused item */
521 if (newItem >= 0 && newItem < infoPtr->uNumItem && infoPtr->uFocus != newItem)
522 TAB_SetCurFocus(infoPtr, newItem);
524 return 0;
528 * WM_KILLFOCUS handler
530 static void TAB_KillFocus(TAB_INFO *infoPtr)
532 /* clear current focused item back to selected for TCS_BUTTONS */
533 if ((infoPtr->dwStyle & TCS_BUTTONS) && (infoPtr->uFocus != infoPtr->iSelected))
535 RECT r;
537 if (TAB_InternalGetItemRect(infoPtr, infoPtr->uFocus, &r, NULL))
538 InvalidateRect(infoPtr->hwnd, &r, FALSE);
540 infoPtr->uFocus = infoPtr->iSelected;
544 /******************************************************************************
545 * TAB_FocusChanging
547 * This method is called whenever the focus goes in or out of this control
548 * it is used to update the visual state of the control.
550 static void TAB_FocusChanging(const TAB_INFO *infoPtr)
552 RECT selectedRect;
553 BOOL isVisible;
556 * Get the rectangle for the item.
558 isVisible = TAB_InternalGetItemRect(infoPtr,
559 infoPtr->uFocus,
560 NULL,
561 &selectedRect);
564 * If the rectangle is not completely invisible, invalidate that
565 * portion of the window.
567 if (isVisible)
569 TRACE("invalidate (%s)\n", wine_dbgstr_rect(&selectedRect));
570 InvalidateRect(infoPtr->hwnd, &selectedRect, TRUE);
574 static INT TAB_InternalHitTest (const TAB_INFO *infoPtr, POINT pt, UINT *flags)
576 RECT rect;
577 INT iCount;
579 for (iCount = 0; iCount < infoPtr->uNumItem; iCount++)
581 TAB_InternalGetItemRect(infoPtr, iCount, &rect, NULL);
583 if (PtInRect(&rect, pt))
585 *flags = TCHT_ONITEM;
586 return iCount;
590 *flags = TCHT_NOWHERE;
591 return -1;
594 static inline LRESULT
595 TAB_HitTest (const TAB_INFO *infoPtr, LPTCHITTESTINFO lptest)
597 TRACE("(%p, %p)\n", infoPtr, lptest);
598 return TAB_InternalHitTest (infoPtr, lptest->pt, &lptest->flags);
601 /******************************************************************************
602 * TAB_NCHitTest
604 * Napster v2b5 has a tab control for its main navigation which has a client
605 * area that covers the whole area of the dialog pages.
606 * That's why it receives all msgs for that area and the underlying dialog ctrls
607 * are dead.
608 * So I decided that we should handle WM_NCHITTEST here and return
609 * HTTRANSPARENT if we don't hit the tab control buttons.
610 * FIXME: WM_NCHITTEST handling correct ? Fix it if you know that Windows
611 * doesn't do it that way. Maybe depends on tab control styles ?
613 static inline LRESULT
614 TAB_NCHitTest (const TAB_INFO *infoPtr, LPARAM lParam)
616 POINT pt;
617 UINT dummyflag;
619 pt.x = (short)LOWORD(lParam);
620 pt.y = (short)HIWORD(lParam);
621 ScreenToClient(infoPtr->hwnd, &pt);
623 if (TAB_InternalHitTest(infoPtr, pt, &dummyflag) == -1)
624 return HTTRANSPARENT;
625 else
626 return HTCLIENT;
629 static LRESULT
630 TAB_LButtonDown (TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
632 POINT pt;
633 INT newItem;
634 UINT dummy;
636 if (infoPtr->hwndToolTip)
637 TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
638 WM_LBUTTONDOWN, wParam, lParam);
640 if (!(infoPtr->dwStyle & TCS_FOCUSNEVER)) {
641 SetFocus (infoPtr->hwnd);
644 if (infoPtr->hwndToolTip)
645 TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
646 WM_LBUTTONDOWN, wParam, lParam);
648 pt.x = (short)LOWORD(lParam);
649 pt.y = (short)HIWORD(lParam);
651 newItem = TAB_InternalHitTest (infoPtr, pt, &dummy);
653 TRACE("On Tab, item %d\n", newItem);
655 if ((newItem != -1) && (infoPtr->iSelected != newItem))
657 if ((infoPtr->dwStyle & TCS_BUTTONS) && (infoPtr->dwStyle & TCS_MULTISELECT) &&
658 (wParam & MK_CONTROL))
660 RECT r;
662 /* toggle multiselection */
663 TAB_GetItem(infoPtr, newItem)->dwState ^= TCIS_BUTTONPRESSED;
664 if (TAB_InternalGetItemRect (infoPtr, newItem, &r, NULL))
665 InvalidateRect (infoPtr->hwnd, &r, TRUE);
667 else
669 INT i;
670 BOOL pressed = FALSE;
672 /* any button pressed ? */
673 for (i = 0; i < infoPtr->uNumItem; i++)
674 if ((TAB_GetItem (infoPtr, i)->dwState & TCIS_BUTTONPRESSED) &&
675 (infoPtr->iSelected != i))
677 pressed = TRUE;
678 break;
681 TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGING);
683 if (pressed)
684 TAB_DeselectAll (infoPtr, FALSE);
685 else
686 TAB_SetCurSel(infoPtr, newItem);
688 TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGE);
692 return 0;
695 static inline LRESULT
696 TAB_LButtonUp (const TAB_INFO *infoPtr)
698 TAB_SendSimpleNotify(infoPtr, NM_CLICK);
700 return 0;
703 static inline LRESULT
704 TAB_RButtonDown (const TAB_INFO *infoPtr)
706 TAB_SendSimpleNotify(infoPtr, NM_RCLICK);
707 return 0;
710 /******************************************************************************
711 * TAB_DrawLoneItemInterior
713 * This calls TAB_DrawItemInterior. However, TAB_DrawItemInterior is normally
714 * called by TAB_DrawItem which is normally called by TAB_Refresh which sets
715 * up the device context and font. This routine does the same setup but
716 * only calls TAB_DrawItemInterior for the single specified item.
718 static void
719 TAB_DrawLoneItemInterior(const TAB_INFO* infoPtr, int iItem)
721 HDC hdc = GetDC(infoPtr->hwnd);
722 RECT r, rC;
724 /* Clip UpDown control to not draw over it */
725 if (infoPtr->needsScrolling)
727 GetWindowRect(infoPtr->hwnd, &rC);
728 GetWindowRect(infoPtr->hwndUpDown, &r);
729 ExcludeClipRect(hdc, r.left - rC.left, r.top - rC.top, r.right - rC.left, r.bottom - rC.top);
731 TAB_DrawItemInterior(infoPtr, hdc, iItem, NULL);
732 ReleaseDC(infoPtr->hwnd, hdc);
735 /* update a tab after hottracking - invalidate it or just redraw the interior,
736 * based on whether theming is used or not */
737 static inline void hottrack_refresh(const TAB_INFO *infoPtr, int tabIndex)
739 if (tabIndex == -1) return;
741 if (GetWindowTheme (infoPtr->hwnd))
743 RECT rect;
744 TAB_InternalGetItemRect(infoPtr, tabIndex, &rect, NULL);
745 InvalidateRect (infoPtr->hwnd, &rect, FALSE);
747 else
748 TAB_DrawLoneItemInterior(infoPtr, tabIndex);
751 /******************************************************************************
752 * TAB_HotTrackTimerProc
754 * When a mouse-move event causes a tab to be highlighted (hot-tracking), a
755 * timer is setup so we can check if the mouse is moved out of our window.
756 * (We don't get an event when the mouse leaves, the mouse-move events just
757 * stop being delivered to our window and just start being delivered to
758 * another window.) This function is called when the timer triggers so
759 * we can check if the mouse has left our window. If so, we un-highlight
760 * the hot-tracked tab.
762 static void CALLBACK
763 TAB_HotTrackTimerProc
765 HWND hwnd, /* handle of window for timer messages */
766 UINT uMsg, /* WM_TIMER message */
767 UINT_PTR idEvent, /* timer identifier */
768 DWORD dwTime /* current system time */
771 TAB_INFO* infoPtr = TAB_GetInfoPtr(hwnd);
773 if (infoPtr != NULL && infoPtr->iHotTracked >= 0)
775 POINT pt;
778 ** If we can't get the cursor position, or if the cursor is outside our
779 ** window, we un-highlight the hot-tracked tab. Note that the cursor is
780 ** "outside" even if it is within our bounding rect if another window
781 ** overlaps. Note also that the case where the cursor stayed within our
782 ** window but has moved off the hot-tracked tab will be handled by the
783 ** WM_MOUSEMOVE event.
785 if (!GetCursorPos(&pt) || WindowFromPoint(pt) != hwnd)
787 /* Redraw iHotTracked to look normal */
788 INT iRedraw = infoPtr->iHotTracked;
789 infoPtr->iHotTracked = -1;
790 hottrack_refresh (infoPtr, iRedraw);
792 /* Kill this timer */
793 KillTimer(hwnd, TAB_HOTTRACK_TIMER);
798 /******************************************************************************
799 * TAB_RecalcHotTrack
801 * If a tab control has the TCS_HOTTRACK style, then the tab under the mouse
802 * should be highlighted. This function determines which tab in a tab control,
803 * if any, is under the mouse and records that information. The caller may
804 * supply output parameters to receive the item number of the tab item which
805 * was highlighted but isn't any longer and of the tab item which is now
806 * highlighted but wasn't previously. The caller can use this information to
807 * selectively redraw those tab items.
809 * If the caller has a mouse position, it can supply it through the pos
810 * parameter. For example, TAB_MouseMove does this. Otherwise, the caller
811 * supplies NULL and this function determines the current mouse position
812 * itself.
814 static void
815 TAB_RecalcHotTrack
817 TAB_INFO* infoPtr,
818 const LPARAM* pos,
819 int* out_redrawLeave,
820 int* out_redrawEnter
823 int item = -1;
826 if (out_redrawLeave != NULL)
827 *out_redrawLeave = -1;
828 if (out_redrawEnter != NULL)
829 *out_redrawEnter = -1;
831 if ((infoPtr->dwStyle & TCS_HOTTRACK) || GetWindowTheme(infoPtr->hwnd))
833 POINT pt;
834 UINT flags;
836 if (pos == NULL)
838 GetCursorPos(&pt);
839 ScreenToClient(infoPtr->hwnd, &pt);
841 else
843 pt.x = (short)LOWORD(*pos);
844 pt.y = (short)HIWORD(*pos);
847 item = TAB_InternalHitTest(infoPtr, pt, &flags);
850 if (item != infoPtr->iHotTracked)
852 if (infoPtr->iHotTracked >= 0)
854 /* Mark currently hot-tracked to be redrawn to look normal */
855 if (out_redrawLeave != NULL)
856 *out_redrawLeave = infoPtr->iHotTracked;
858 if (item < 0)
860 /* Kill timer which forces recheck of mouse pos */
861 KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
864 else
866 /* Start timer so we recheck mouse pos */
867 UINT timerID = SetTimer
869 infoPtr->hwnd,
870 TAB_HOTTRACK_TIMER,
871 TAB_HOTTRACK_TIMER_INTERVAL,
872 TAB_HotTrackTimerProc
875 if (timerID == 0)
876 return; /* Hot tracking not available */
879 infoPtr->iHotTracked = item;
881 if (item >= 0)
883 /* Mark new hot-tracked to be redrawn to look highlighted */
884 if (out_redrawEnter != NULL)
885 *out_redrawEnter = item;
890 /******************************************************************************
891 * TAB_MouseMove
893 * Handles the mouse-move event. Updates tooltips. Updates hot-tracking.
895 static LRESULT
896 TAB_MouseMove (TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
898 int redrawLeave;
899 int redrawEnter;
901 if (infoPtr->hwndToolTip)
902 TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
903 WM_LBUTTONDOWN, wParam, lParam);
905 /* Determine which tab to highlight. Redraw tabs which change highlight
906 ** status. */
907 TAB_RecalcHotTrack(infoPtr, &lParam, &redrawLeave, &redrawEnter);
909 hottrack_refresh (infoPtr, redrawLeave);
910 hottrack_refresh (infoPtr, redrawEnter);
912 return 0;
915 /******************************************************************************
916 * TAB_AdjustRect
918 * Calculates the tab control's display area given the window rectangle or
919 * the window rectangle given the requested display rectangle.
921 static LRESULT TAB_AdjustRect(const TAB_INFO *infoPtr, WPARAM fLarger, LPRECT prc)
923 LONG *iRightBottom, *iLeftTop;
925 TRACE ("hwnd=%p fLarger=%ld (%s)\n", infoPtr->hwnd, fLarger,
926 wine_dbgstr_rect(prc));
928 if (!prc) return -1;
930 if(infoPtr->dwStyle & TCS_VERTICAL)
932 iRightBottom = &(prc->right);
933 iLeftTop = &(prc->left);
935 else
937 iRightBottom = &(prc->bottom);
938 iLeftTop = &(prc->top);
941 if (fLarger) /* Go from display rectangle */
943 /* Add the height of the tabs. */
944 if (infoPtr->dwStyle & TCS_BOTTOM)
945 *iRightBottom += infoPtr->tabHeight * infoPtr->uNumRows;
946 else
947 *iLeftTop -= infoPtr->tabHeight * infoPtr->uNumRows +
948 ((infoPtr->dwStyle & TCS_BUTTONS)? 3 * (infoPtr->uNumRows - 1) : 0);
950 /* Inflate the rectangle for the padding */
951 InflateRect(prc, DISPLAY_AREA_PADDINGX, DISPLAY_AREA_PADDINGY);
953 /* Inflate for the border */
954 InflateRect(prc, CONTROL_BORDER_SIZEX, CONTROL_BORDER_SIZEY);
956 else /* Go from window rectangle. */
958 /* Deflate the rectangle for the border */
959 InflateRect(prc, -CONTROL_BORDER_SIZEX, -CONTROL_BORDER_SIZEY);
961 /* Deflate the rectangle for the padding */
962 InflateRect(prc, -DISPLAY_AREA_PADDINGX, -DISPLAY_AREA_PADDINGY);
964 /* Remove the height of the tabs. */
965 if (infoPtr->dwStyle & TCS_BOTTOM)
966 *iRightBottom -= infoPtr->tabHeight * infoPtr->uNumRows;
967 else
968 *iLeftTop += (infoPtr->tabHeight) * infoPtr->uNumRows +
969 ((infoPtr->dwStyle & TCS_BUTTONS)? 3 * (infoPtr->uNumRows - 1) : 0);
972 return 0;
975 /******************************************************************************
976 * TAB_OnHScroll
978 * This method will handle the notification from the scroll control and
979 * perform the scrolling operation on the tab control.
981 static LRESULT TAB_OnHScroll(TAB_INFO *infoPtr, int nScrollCode, int nPos)
983 if(nScrollCode == SB_THUMBPOSITION && nPos != infoPtr->leftmostVisible)
985 if(nPos < infoPtr->leftmostVisible)
986 infoPtr->leftmostVisible--;
987 else
988 infoPtr->leftmostVisible++;
990 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
991 TAB_InvalidateTabArea(infoPtr);
992 SendMessageW(infoPtr->hwndUpDown, UDM_SETPOS, 0,
993 MAKELONG(infoPtr->leftmostVisible, 0));
996 return 0;
999 /******************************************************************************
1000 * TAB_SetupScrolling
1002 * This method will check the current scrolling state and make sure the
1003 * scrolling control is displayed (or not).
1005 static void TAB_SetupScrolling(
1006 TAB_INFO* infoPtr,
1007 const RECT* clientRect)
1009 static const WCHAR emptyW[] = { 0 };
1010 INT maxRange = 0;
1012 if (infoPtr->needsScrolling)
1014 RECT controlPos;
1015 INT vsize, tabwidth;
1018 * Calculate the position of the scroll control.
1020 if(infoPtr->dwStyle & TCS_VERTICAL)
1022 controlPos.right = clientRect->right;
1023 controlPos.left = controlPos.right - 2 * GetSystemMetrics(SM_CXHSCROLL);
1025 if (infoPtr->dwStyle & TCS_BOTTOM)
1027 controlPos.top = clientRect->bottom - infoPtr->tabHeight;
1028 controlPos.bottom = controlPos.top + GetSystemMetrics(SM_CYHSCROLL);
1030 else
1032 controlPos.bottom = clientRect->top + infoPtr->tabHeight;
1033 controlPos.top = controlPos.bottom - GetSystemMetrics(SM_CYHSCROLL);
1036 else
1038 controlPos.right = clientRect->right;
1039 controlPos.left = controlPos.right - 2 * GetSystemMetrics(SM_CXHSCROLL);
1041 if (infoPtr->dwStyle & TCS_BOTTOM)
1043 controlPos.top = clientRect->bottom - infoPtr->tabHeight;
1044 controlPos.bottom = controlPos.top + GetSystemMetrics(SM_CYHSCROLL);
1046 else
1048 controlPos.bottom = clientRect->top + infoPtr->tabHeight;
1049 controlPos.top = controlPos.bottom - GetSystemMetrics(SM_CYHSCROLL);
1054 * If we don't have a scroll control yet, we want to create one.
1055 * If we have one, we want to make sure it's positioned properly.
1057 if (infoPtr->hwndUpDown==0)
1059 infoPtr->hwndUpDown = CreateWindowW(UPDOWN_CLASSW, emptyW,
1060 WS_VISIBLE | WS_CHILD | UDS_HORZ,
1061 controlPos.left, controlPos.top,
1062 controlPos.right - controlPos.left,
1063 controlPos.bottom - controlPos.top,
1064 infoPtr->hwnd, NULL, NULL, NULL);
1066 else
1068 SetWindowPos(infoPtr->hwndUpDown,
1069 NULL,
1070 controlPos.left, controlPos.top,
1071 controlPos.right - controlPos.left,
1072 controlPos.bottom - controlPos.top,
1073 SWP_SHOWWINDOW | SWP_NOZORDER);
1076 /* Now calculate upper limit of the updown control range.
1077 * We do this by calculating how many tabs will be offscreen when the
1078 * last tab is visible.
1080 if(infoPtr->uNumItem)
1082 vsize = clientRect->right - (controlPos.right - controlPos.left + 1);
1083 maxRange = infoPtr->uNumItem;
1084 tabwidth = TAB_GetItem(infoPtr, infoPtr->uNumItem - 1)->rect.right;
1086 for(; maxRange > 0; maxRange--)
1088 if(tabwidth - TAB_GetItem(infoPtr,maxRange - 1)->rect.left > vsize)
1089 break;
1092 if(maxRange == infoPtr->uNumItem)
1093 maxRange--;
1096 else
1098 /* If we once had a scroll control... hide it */
1099 if (infoPtr->hwndUpDown)
1100 ShowWindow(infoPtr->hwndUpDown, SW_HIDE);
1102 if (infoPtr->hwndUpDown)
1103 SendMessageW(infoPtr->hwndUpDown, UDM_SETRANGE32, 0, maxRange);
1106 /******************************************************************************
1107 * TAB_SetItemBounds
1109 * This method will calculate the position rectangles of all the items in the
1110 * control. The rectangle calculated starts at 0 for the first item in the
1111 * list and ignores scrolling and selection.
1112 * It also uses the current font to determine the height of the tab row and
1113 * it checks if all the tabs fit in the client area of the window. If they
1114 * don't, a scrolling control is added.
1116 static void TAB_SetItemBounds (TAB_INFO *infoPtr)
1118 TEXTMETRICW fontMetrics;
1119 UINT curItem;
1120 INT curItemLeftPos;
1121 INT curItemRowCount;
1122 HFONT hFont, hOldFont;
1123 HDC hdc;
1124 RECT clientRect;
1125 INT iTemp;
1126 RECT* rcItem;
1127 INT iIndex;
1128 INT icon_width = 0;
1131 * We need to get text information so we need a DC and we need to select
1132 * a font.
1134 hdc = GetDC(infoPtr->hwnd);
1136 hFont = infoPtr->hFont ? infoPtr->hFont : GetStockObject (SYSTEM_FONT);
1137 hOldFont = SelectObject (hdc, hFont);
1140 * We will base the rectangle calculations on the client rectangle
1141 * of the control.
1143 GetClientRect(infoPtr->hwnd, &clientRect);
1145 /* if TCS_VERTICAL then swap the height and width so this code places the
1146 tabs along the top of the rectangle and we can just rotate them after
1147 rather than duplicate all of the below code */
1148 if(infoPtr->dwStyle & TCS_VERTICAL)
1150 iTemp = clientRect.bottom;
1151 clientRect.bottom = clientRect.right;
1152 clientRect.right = iTemp;
1155 /* Now use hPadding and vPadding */
1156 infoPtr->uHItemPadding = infoPtr->uHItemPadding_s;
1157 infoPtr->uVItemPadding = infoPtr->uVItemPadding_s;
1159 /* The leftmost item will be "0" aligned */
1160 curItemLeftPos = 0;
1161 curItemRowCount = infoPtr->uNumItem ? 1 : 0;
1163 if (!(infoPtr->fHeightSet))
1165 int item_height;
1166 int icon_height = 0;
1168 /* Use the current font to determine the height of a tab. */
1169 GetTextMetricsW(hdc, &fontMetrics);
1171 /* Get the icon height */
1172 if (infoPtr->himl)
1173 ImageList_GetIconSize(infoPtr->himl, 0, &icon_height);
1175 /* Take the highest between font or icon */
1176 if (fontMetrics.tmHeight > icon_height)
1177 item_height = fontMetrics.tmHeight + 2;
1178 else
1179 item_height = icon_height;
1182 * Make sure there is enough space for the letters + icon + growing the
1183 * selected item + extra space for the selected item.
1185 infoPtr->tabHeight = item_height +
1186 ((infoPtr->dwStyle & TCS_BUTTONS) ? 2 : 1) *
1187 infoPtr->uVItemPadding;
1189 TRACE("tabH=%d, tmH=%d, iconh=%d\n",
1190 infoPtr->tabHeight, fontMetrics.tmHeight, icon_height);
1193 TRACE("client right=%d\n", clientRect.right);
1195 /* Get the icon width */
1196 if (infoPtr->himl)
1198 ImageList_GetIconSize(infoPtr->himl, &icon_width, 0);
1200 if (infoPtr->dwStyle & TCS_FIXEDWIDTH)
1201 icon_width += 4;
1202 else
1203 /* Add padding if icon is present */
1204 icon_width += infoPtr->uHItemPadding;
1207 for (curItem = 0; curItem < infoPtr->uNumItem; curItem++)
1209 TAB_ITEM *curr = TAB_GetItem(infoPtr, curItem);
1211 /* Set the leftmost position of the tab. */
1212 curr->rect.left = curItemLeftPos;
1214 if (infoPtr->dwStyle & TCS_FIXEDWIDTH)
1216 curr->rect.right = curr->rect.left +
1217 max(infoPtr->tabWidth, icon_width);
1219 else if (!curr->pszText)
1221 /* If no text use minimum tab width including padding. */
1222 if (infoPtr->tabMinWidth < 0)
1223 curr->rect.right = curr->rect.left + GET_DEFAULT_MIN_TAB_WIDTH(infoPtr);
1224 else
1226 curr->rect.right = curr->rect.left + infoPtr->tabMinWidth;
1228 /* Add extra padding if icon is present */
1229 if (infoPtr->himl && infoPtr->tabMinWidth > 0 && infoPtr->tabMinWidth < DEFAULT_MIN_TAB_WIDTH
1230 && infoPtr->uHItemPadding > 1)
1231 curr->rect.right += EXTRA_ICON_PADDING * (infoPtr->uHItemPadding-1);
1234 else
1236 int tabwidth;
1237 SIZE size;
1238 /* Calculate how wide the tab is depending on the text it contains */
1239 GetTextExtentPoint32W(hdc, curr->pszText,
1240 lstrlenW(curr->pszText), &size);
1242 tabwidth = size.cx + icon_width + 2 * infoPtr->uHItemPadding;
1244 if (infoPtr->tabMinWidth < 0)
1245 tabwidth = max(tabwidth, GET_DEFAULT_MIN_TAB_WIDTH(infoPtr));
1246 else
1247 tabwidth = max(tabwidth, infoPtr->tabMinWidth);
1249 curr->rect.right = curr->rect.left + tabwidth;
1250 TRACE("for <%s>, l,r=%d,%d\n",
1251 debugstr_w(curr->pszText), curr->rect.left, curr->rect.right);
1255 * Check if this is a multiline tab control and if so
1256 * check to see if we should wrap the tabs
1258 * Wrap all these tabs. We will arrange them evenly later.
1262 if (((infoPtr->dwStyle & TCS_MULTILINE) || (infoPtr->dwStyle & TCS_VERTICAL)) &&
1263 (curr->rect.right >
1264 (clientRect.right - CONTROL_BORDER_SIZEX - DISPLAY_AREA_PADDINGX)))
1266 curr->rect.right -= curr->rect.left;
1268 curr->rect.left = 0;
1269 curItemRowCount++;
1270 TRACE("wrapping <%s>, l,r=%d,%d\n", debugstr_w(curr->pszText),
1271 curr->rect.left, curr->rect.right);
1274 curr->rect.bottom = 0;
1275 curr->rect.top = curItemRowCount - 1;
1277 TRACE("Rect: %s\n", wine_dbgstr_rect(&curr->rect));
1280 * The leftmost position of the next item is the rightmost position
1281 * of this one.
1283 if (infoPtr->dwStyle & TCS_BUTTONS)
1285 curItemLeftPos = curr->rect.right + BUTTON_SPACINGX;
1286 if (infoPtr->dwStyle & TCS_FLATBUTTONS)
1287 curItemLeftPos += FLAT_BTN_SPACINGX;
1289 else
1290 curItemLeftPos = curr->rect.right;
1293 if (!((infoPtr->dwStyle & TCS_MULTILINE) || (infoPtr->dwStyle & TCS_VERTICAL)))
1296 * Check if we need a scrolling control.
1298 infoPtr->needsScrolling = (curItemLeftPos + (2 * SELECTED_TAB_OFFSET) >
1299 clientRect.right);
1301 /* Don't need scrolling, then update infoPtr->leftmostVisible */
1302 if(!infoPtr->needsScrolling)
1303 infoPtr->leftmostVisible = 0;
1305 else
1308 * No scrolling in Multiline or Vertical styles.
1310 infoPtr->needsScrolling = FALSE;
1311 infoPtr->leftmostVisible = 0;
1313 TAB_SetupScrolling(infoPtr, &clientRect);
1315 /* Set the number of rows */
1316 infoPtr->uNumRows = curItemRowCount;
1318 /* Arrange all tabs evenly if style says so */
1319 if (!(infoPtr->dwStyle & TCS_RAGGEDRIGHT) &&
1320 ((infoPtr->dwStyle & TCS_MULTILINE) || (infoPtr->dwStyle & TCS_VERTICAL)) &&
1321 (infoPtr->uNumItem > 0) &&
1322 (infoPtr->uNumRows > 1))
1324 INT tabPerRow,remTab,iRow;
1325 UINT iItm;
1326 INT iCount=0;
1329 * Ok windows tries to even out the rows. place the same
1330 * number of tabs in each row. So lets give that a shot
1333 tabPerRow = infoPtr->uNumItem / (infoPtr->uNumRows);
1334 remTab = infoPtr->uNumItem % (infoPtr->uNumRows);
1336 for (iItm=0,iRow=0,iCount=0,curItemLeftPos=0;
1337 iItm<infoPtr->uNumItem;
1338 iItm++,iCount++)
1340 /* normalize the current rect */
1341 TAB_ITEM *curr = TAB_GetItem(infoPtr, iItm);
1343 /* shift the item to the left side of the clientRect */
1344 curr->rect.right -= curr->rect.left;
1345 curr->rect.left = 0;
1347 TRACE("r=%d, cl=%d, cl.r=%d, iCount=%d, iRow=%d, uNumRows=%d, remTab=%d, tabPerRow=%d\n",
1348 curr->rect.right, curItemLeftPos, clientRect.right,
1349 iCount, iRow, infoPtr->uNumRows, remTab, tabPerRow);
1351 /* if we have reached the maximum number of tabs on this row */
1352 /* move to the next row, reset our current item left position and */
1353 /* the count of items on this row */
1355 if (infoPtr->dwStyle & TCS_VERTICAL) {
1356 /* Vert: Add the remaining tabs in the *last* remainder rows */
1357 if (iCount >= ((iRow>=(INT)infoPtr->uNumRows - remTab)?tabPerRow + 1:tabPerRow)) {
1358 iRow++;
1359 curItemLeftPos = 0;
1360 iCount = 0;
1362 } else {
1363 /* Horz: Add the remaining tabs in the *first* remainder rows */
1364 if (iCount >= ((iRow<remTab)?tabPerRow + 1:tabPerRow)) {
1365 iRow++;
1366 curItemLeftPos = 0;
1367 iCount = 0;
1371 /* shift the item to the right to place it as the next item in this row */
1372 curr->rect.left += curItemLeftPos;
1373 curr->rect.right += curItemLeftPos;
1374 curr->rect.top = iRow;
1375 if (infoPtr->dwStyle & TCS_BUTTONS)
1377 curItemLeftPos = curr->rect.right + 1;
1378 if (infoPtr->dwStyle & TCS_FLATBUTTONS)
1379 curItemLeftPos += FLAT_BTN_SPACINGX;
1381 else
1382 curItemLeftPos = curr->rect.right;
1384 TRACE("arranging <%s>, l,r=%d,%d, row=%d\n",
1385 debugstr_w(curr->pszText), curr->rect.left,
1386 curr->rect.right, curr->rect.top);
1390 * Justify the rows
1393 INT widthDiff, iIndexStart=0, iIndexEnd=0;
1394 INT remainder;
1395 INT iCount=0;
1397 while(iIndexStart < infoPtr->uNumItem)
1399 TAB_ITEM *start = TAB_GetItem(infoPtr, iIndexStart);
1402 * find the index of the row
1404 /* find the first item on the next row */
1405 for (iIndexEnd=iIndexStart;
1406 (iIndexEnd < infoPtr->uNumItem) &&
1407 (TAB_GetItem(infoPtr, iIndexEnd)->rect.top ==
1408 start->rect.top) ;
1409 iIndexEnd++)
1410 /* intentionally blank */;
1413 * we need to justify these tabs so they fill the whole given
1414 * client area
1417 /* find the amount of space remaining on this row */
1418 widthDiff = clientRect.right - (2 * SELECTED_TAB_OFFSET) -
1419 TAB_GetItem(infoPtr, iIndexEnd - 1)->rect.right;
1421 /* iCount is the number of tab items on this row */
1422 iCount = iIndexEnd - iIndexStart;
1424 if (iCount > 1)
1426 remainder = widthDiff % iCount;
1427 widthDiff = widthDiff / iCount;
1428 /* add widthDiff/iCount, or extra space/items on row, to each item on this row */
1429 for (iIndex=iIndexStart, iCount=0; iIndex < iIndexEnd; iIndex++, iCount++)
1431 TAB_ITEM *item = TAB_GetItem(infoPtr, iIndex);
1433 item->rect.left += iCount * widthDiff;
1434 item->rect.right += (iCount + 1) * widthDiff;
1436 TRACE("adjusting 1 <%s>, l,r=%d,%d\n",
1437 debugstr_w(item->pszText),
1438 item->rect.left, item->rect.right);
1441 TAB_GetItem(infoPtr, iIndex - 1)->rect.right += remainder;
1443 else /* we have only one item on this row, make it take up the entire row */
1445 start->rect.left = clientRect.left;
1446 start->rect.right = clientRect.right - 4;
1448 TRACE("adjusting 2 <%s>, l,r=%d,%d\n",
1449 debugstr_w(start->pszText),
1450 start->rect.left, start->rect.right);
1455 iIndexStart = iIndexEnd;
1460 /* if TCS_VERTICAL rotate the tabs so they are along the side of the clientRect */
1461 if(infoPtr->dwStyle & TCS_VERTICAL)
1463 RECT rcOriginal;
1464 for(iIndex = 0; iIndex < infoPtr->uNumItem; iIndex++)
1466 rcItem = &TAB_GetItem(infoPtr, iIndex)->rect;
1468 rcOriginal = *rcItem;
1470 /* this is rotating the items by 90 degrees clockwise around the center of the control */
1471 rcItem->top = (rcOriginal.left - clientRect.left);
1472 rcItem->bottom = rcItem->top + (rcOriginal.right - rcOriginal.left);
1473 rcItem->left = rcOriginal.top;
1474 rcItem->right = rcOriginal.bottom;
1478 TAB_EnsureSelectionVisible(infoPtr);
1479 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
1481 /* Cleanup */
1482 SelectObject (hdc, hOldFont);
1483 ReleaseDC (infoPtr->hwnd, hdc);
1487 static void
1488 TAB_EraseTabInterior(const TAB_INFO *infoPtr, HDC hdc, INT iItem, const RECT *drawRect)
1490 HBRUSH hbr = CreateSolidBrush (comctl32_color.clrBtnFace);
1491 BOOL deleteBrush = TRUE;
1492 RECT rTemp = *drawRect;
1494 if (infoPtr->dwStyle & TCS_BUTTONS)
1496 if (iItem == infoPtr->iSelected)
1498 /* Background color */
1499 if (!(infoPtr->dwStyle & TCS_OWNERDRAWFIXED))
1501 DeleteObject(hbr);
1502 hbr = GetSysColorBrush(COLOR_SCROLLBAR);
1504 SetTextColor(hdc, comctl32_color.clr3dFace);
1505 SetBkColor(hdc, comctl32_color.clr3dHilight);
1507 /* if COLOR_WINDOW happens to be the same as COLOR_3DHILIGHT
1508 * we better use 0x55aa bitmap brush to make scrollbar's background
1509 * look different from the window background.
1511 if (comctl32_color.clr3dHilight == comctl32_color.clrWindow)
1512 hbr = COMCTL32_hPattern55AABrush;
1514 deleteBrush = FALSE;
1516 FillRect(hdc, &rTemp, hbr);
1518 else /* ! selected */
1520 if (infoPtr->dwStyle & TCS_FLATBUTTONS)
1522 InflateRect(&rTemp, 2, 2);
1523 FillRect(hdc, &rTemp, hbr);
1524 if (iItem == infoPtr->iHotTracked ||
1525 (iItem != infoPtr->iSelected && iItem == infoPtr->uFocus))
1526 DrawEdge(hdc, &rTemp, BDR_RAISEDINNER, BF_RECT);
1528 else
1529 FillRect(hdc, &rTemp, hbr);
1533 else /* !TCS_BUTTONS */
1535 InflateRect(&rTemp, -2, -2);
1536 if (!GetWindowTheme (infoPtr->hwnd))
1537 FillRect(hdc, &rTemp, hbr);
1540 /* highlighting is drawn on top of previous fills */
1541 if (TAB_GetItem(infoPtr, iItem)->dwState & TCIS_HIGHLIGHTED)
1543 if (deleteBrush)
1545 DeleteObject(hbr);
1546 deleteBrush = FALSE;
1548 hbr = GetSysColorBrush(COLOR_HIGHLIGHT);
1549 FillRect(hdc, &rTemp, hbr);
1552 /* Cleanup */
1553 if (deleteBrush) DeleteObject(hbr);
1556 /******************************************************************************
1557 * TAB_DrawItemInterior
1559 * This method is used to draw the interior (text and icon) of a single tab
1560 * into the tab control.
1562 static void
1563 TAB_DrawItemInterior(const TAB_INFO *infoPtr, HDC hdc, INT iItem, RECT *drawRect)
1565 RECT localRect;
1567 HPEN htextPen;
1568 HPEN holdPen;
1569 INT oldBkMode;
1570 HFONT hOldFont;
1572 /* if (drawRect == NULL) */
1574 BOOL isVisible;
1575 RECT itemRect;
1576 RECT selectedRect;
1579 * Get the rectangle for the item.
1581 isVisible = TAB_InternalGetItemRect(infoPtr, iItem, &itemRect, &selectedRect);
1582 if (!isVisible)
1583 return;
1586 * Make sure drawRect points to something valid; simplifies code.
1588 drawRect = &localRect;
1591 * This logic copied from the part of TAB_DrawItem which draws
1592 * the tab background. It's important to keep it in sync. I
1593 * would have liked to avoid code duplication, but couldn't figure
1594 * out how without making spaghetti of TAB_DrawItem.
1596 if (iItem == infoPtr->iSelected)
1597 *drawRect = selectedRect;
1598 else
1599 *drawRect = itemRect;
1601 if (infoPtr->dwStyle & TCS_BUTTONS)
1603 if (iItem == infoPtr->iSelected)
1605 drawRect->left += 4;
1606 drawRect->top += 4;
1607 drawRect->right -= 4;
1609 if (infoPtr->dwStyle & TCS_VERTICAL)
1611 if (!(infoPtr->dwStyle & TCS_BOTTOM)) drawRect->right += 1;
1612 drawRect->bottom -= 4;
1614 else
1616 if (infoPtr->dwStyle & TCS_BOTTOM)
1618 drawRect->top -= 2;
1619 drawRect->bottom -= 4;
1621 else
1622 drawRect->bottom -= 1;
1625 else
1627 drawRect->left += 2;
1628 drawRect->top += 2;
1629 drawRect->right -= 2;
1630 drawRect->bottom -= 2;
1633 else
1635 if ((infoPtr->dwStyle & TCS_VERTICAL) && (infoPtr->dwStyle & TCS_BOTTOM))
1637 if (iItem != infoPtr->iSelected)
1639 drawRect->left += 2;
1640 drawRect->top += 2;
1641 drawRect->bottom -= 2;
1644 else if (infoPtr->dwStyle & TCS_VERTICAL)
1646 if (iItem == infoPtr->iSelected)
1648 drawRect->right += 1;
1650 else
1652 drawRect->top += 2;
1653 drawRect->right -= 2;
1654 drawRect->bottom -= 2;
1657 else if (infoPtr->dwStyle & TCS_BOTTOM)
1659 if (iItem == infoPtr->iSelected)
1661 drawRect->top -= 2;
1663 else
1665 InflateRect(drawRect, -2, -2);
1666 drawRect->bottom += 2;
1669 else
1671 if (iItem == infoPtr->iSelected)
1673 drawRect->bottom += 3;
1675 else
1677 drawRect->bottom -= 2;
1678 InflateRect(drawRect, -2, 0);
1683 TRACE("drawRect=(%s)\n", wine_dbgstr_rect(drawRect));
1685 /* Clear interior */
1686 TAB_EraseTabInterior (infoPtr, hdc, iItem, drawRect);
1688 /* Draw the focus rectangle */
1689 if (!(infoPtr->dwStyle & TCS_FOCUSNEVER) &&
1690 (GetFocus() == infoPtr->hwnd) &&
1691 (iItem == infoPtr->uFocus) )
1693 RECT rFocus = *drawRect;
1695 if (!(infoPtr->dwStyle & TCS_BUTTONS)) InflateRect(&rFocus, -3, -3);
1696 if (infoPtr->dwStyle & TCS_BOTTOM && !(infoPtr->dwStyle & TCS_VERTICAL))
1697 rFocus.top -= 3;
1699 /* focus should stay on selected item for TCS_BUTTONS style */
1700 if (!((infoPtr->dwStyle & TCS_BUTTONS) && (infoPtr->iSelected != iItem)))
1701 DrawFocusRect(hdc, &rFocus);
1705 * Text pen
1707 htextPen = CreatePen( PS_SOLID, 1, comctl32_color.clrBtnText );
1708 holdPen = SelectObject(hdc, htextPen);
1709 hOldFont = SelectObject(hdc, infoPtr->hFont);
1712 * Setup for text output
1714 oldBkMode = SetBkMode(hdc, TRANSPARENT);
1715 if (!GetWindowTheme (infoPtr->hwnd) || (infoPtr->dwStyle & TCS_BUTTONS))
1717 if ((infoPtr->dwStyle & TCS_HOTTRACK) && (iItem == infoPtr->iHotTracked) &&
1718 !(infoPtr->dwStyle & TCS_FLATBUTTONS))
1719 SetTextColor(hdc, comctl32_color.clrHighlight);
1720 else if (TAB_GetItem(infoPtr, iItem)->dwState & TCIS_HIGHLIGHTED)
1721 SetTextColor(hdc, comctl32_color.clrHighlightText);
1722 else
1723 SetTextColor(hdc, comctl32_color.clrBtnText);
1727 * if owner draw, tell the owner to draw
1729 if ((infoPtr->dwStyle & TCS_OWNERDRAWFIXED) && GetParent(infoPtr->hwnd))
1731 DRAWITEMSTRUCT dis;
1732 UINT id;
1734 drawRect->top += 2;
1735 drawRect->right -= 1;
1736 if ( iItem == infoPtr->iSelected )
1738 drawRect->right -= 1;
1739 drawRect->left += 1;
1743 * get the control id
1745 id = (UINT)GetWindowLongPtrW( infoPtr->hwnd, GWLP_ID );
1748 * put together the DRAWITEMSTRUCT
1750 dis.CtlType = ODT_TAB;
1751 dis.CtlID = id;
1752 dis.itemID = iItem;
1753 dis.itemAction = ODA_DRAWENTIRE;
1754 dis.itemState = 0;
1755 if ( iItem == infoPtr->iSelected )
1756 dis.itemState |= ODS_SELECTED;
1757 if (infoPtr->uFocus == iItem)
1758 dis.itemState |= ODS_FOCUS;
1759 dis.hwndItem = infoPtr->hwnd;
1760 dis.hDC = hdc;
1761 CopyRect(&dis.rcItem,drawRect);
1762 dis.itemData = (ULONG_PTR)TAB_GetItem(infoPtr, iItem)->extra;
1765 * send the draw message
1767 SendMessageW( infoPtr->hwndNotify, WM_DRAWITEM, id, (LPARAM)&dis );
1769 else
1771 TAB_ITEM *item = TAB_GetItem(infoPtr, iItem);
1772 RECT rcTemp;
1773 RECT rcImage;
1775 /* used to center the icon and text in the tab */
1776 RECT rcText;
1777 INT center_offset_h, center_offset_v;
1779 /* set rcImage to drawRect, we will use top & left in our ImageList_Draw call */
1780 rcImage = *drawRect;
1782 rcTemp = *drawRect;
1784 rcText.left = rcText.top = rcText.right = rcText.bottom = 0;
1786 /* get the rectangle that the text fits in */
1787 if (item->pszText)
1789 DrawTextW(hdc, item->pszText, -1, &rcText, DT_CALCRECT);
1792 * If not owner draw, then do the drawing ourselves.
1794 * Draw the icon.
1796 if (infoPtr->himl && item->iImage != -1)
1798 INT cx;
1799 INT cy;
1801 ImageList_GetIconSize(infoPtr->himl, &cx, &cy);
1803 if(infoPtr->dwStyle & TCS_VERTICAL)
1805 center_offset_h = ((drawRect->bottom - drawRect->top) - (cy + infoPtr->uHItemPadding + (rcText.right - rcText.left))) / 2;
1806 center_offset_v = ((drawRect->right - drawRect->left) - cx) / 2;
1808 else
1810 center_offset_h = ((drawRect->right - drawRect->left) - (cx + infoPtr->uHItemPadding + (rcText.right - rcText.left))) / 2;
1811 center_offset_v = ((drawRect->bottom - drawRect->top) - cy) / 2;
1814 /* if an item is selected, the icon is shifted up instead of down */
1815 if (iItem == infoPtr->iSelected)
1816 center_offset_v -= infoPtr->uVItemPadding / 2;
1817 else
1818 center_offset_v += infoPtr->uVItemPadding / 2;
1820 if (infoPtr->dwStyle & TCS_FIXEDWIDTH && infoPtr->dwStyle & (TCS_FORCELABELLEFT | TCS_FORCEICONLEFT))
1821 center_offset_h = infoPtr->uHItemPadding;
1823 if (center_offset_h < 2)
1824 center_offset_h = 2;
1826 if (center_offset_v < 0)
1827 center_offset_v = 0;
1829 TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%s), textlen=%d\n",
1830 debugstr_w(item->pszText), center_offset_h, center_offset_v,
1831 wine_dbgstr_rect(drawRect), (rcText.right-rcText.left));
1833 if((infoPtr->dwStyle & TCS_VERTICAL) && (infoPtr->dwStyle & TCS_BOTTOM))
1835 rcImage.top = drawRect->top + center_offset_h;
1836 /* if tab is TCS_VERTICAL and TCS_BOTTOM, the text is drawn from the */
1837 /* right side of the tab, but the image still uses the left as its x position */
1838 /* this keeps the image always drawn off of the same side of the tab */
1839 rcImage.left = drawRect->right - cx - center_offset_v;
1840 drawRect->top += cy + infoPtr->uHItemPadding;
1842 else if(infoPtr->dwStyle & TCS_VERTICAL)
1844 rcImage.top = drawRect->bottom - cy - center_offset_h;
1845 rcImage.left = drawRect->left + center_offset_v;
1846 drawRect->bottom -= cy + infoPtr->uHItemPadding;
1848 else /* normal style, whether TCS_BOTTOM or not */
1850 rcImage.left = drawRect->left + center_offset_h;
1851 rcImage.top = drawRect->top + center_offset_v;
1852 drawRect->left += cx + infoPtr->uHItemPadding;
1855 TRACE("drawing image=%d, left=%d, top=%d\n",
1856 item->iImage, rcImage.left, rcImage.top-1);
1857 ImageList_Draw
1859 infoPtr->himl,
1860 item->iImage,
1861 hdc,
1862 rcImage.left,
1863 rcImage.top,
1864 ILD_NORMAL
1868 /* Now position text */
1869 if (infoPtr->dwStyle & TCS_FIXEDWIDTH && infoPtr->dwStyle & TCS_FORCELABELLEFT)
1870 center_offset_h = infoPtr->uHItemPadding;
1871 else
1872 if(infoPtr->dwStyle & TCS_VERTICAL)
1873 center_offset_h = ((drawRect->bottom - drawRect->top) - (rcText.right - rcText.left)) / 2;
1874 else
1875 center_offset_h = ((drawRect->right - drawRect->left) - (rcText.right - rcText.left)) / 2;
1877 if(infoPtr->dwStyle & TCS_VERTICAL)
1879 if(infoPtr->dwStyle & TCS_BOTTOM)
1880 drawRect->top+=center_offset_h;
1881 else
1882 drawRect->bottom-=center_offset_h;
1884 center_offset_v = ((drawRect->right - drawRect->left) - (rcText.bottom - rcText.top)) / 2;
1886 else
1888 drawRect->left += center_offset_h;
1889 center_offset_v = ((drawRect->bottom - drawRect->top) - (rcText.bottom - rcText.top)) / 2;
1892 /* if an item is selected, the text is shifted up instead of down */
1893 if (iItem == infoPtr->iSelected)
1894 center_offset_v -= infoPtr->uVItemPadding / 2;
1895 else
1896 center_offset_v += infoPtr->uVItemPadding / 2;
1898 if (center_offset_v < 0)
1899 center_offset_v = 0;
1901 if(infoPtr->dwStyle & TCS_VERTICAL)
1902 drawRect->left += center_offset_v;
1903 else
1904 drawRect->top += center_offset_v;
1906 /* Draw the text */
1907 if(infoPtr->dwStyle & TCS_VERTICAL) /* if we are vertical rotate the text and each character */
1909 static const WCHAR ArialW[] = { 'A','r','i','a','l',0 };
1910 LOGFONTW logfont;
1911 HFONT hFont = 0;
1912 INT nEscapement = 900;
1913 INT nOrientation = 900;
1915 if(infoPtr->dwStyle & TCS_BOTTOM)
1917 nEscapement = -900;
1918 nOrientation = -900;
1921 /* to get a font with the escapement and orientation we are looking for, we need to */
1922 /* call CreateFontIndirectA, which requires us to set the values of the logfont we pass in */
1923 if (!GetObjectW((infoPtr->hFont) ?
1924 infoPtr->hFont : GetStockObject(SYSTEM_FONT),
1925 sizeof(LOGFONTW),&logfont))
1927 INT iPointSize = 9;
1929 lstrcpyW(logfont.lfFaceName, ArialW);
1930 logfont.lfHeight = -MulDiv(iPointSize, GetDeviceCaps(hdc, LOGPIXELSY),
1931 72);
1932 logfont.lfWeight = FW_NORMAL;
1933 logfont.lfItalic = 0;
1934 logfont.lfUnderline = 0;
1935 logfont.lfStrikeOut = 0;
1938 logfont.lfEscapement = nEscapement;
1939 logfont.lfOrientation = nOrientation;
1940 hFont = CreateFontIndirectW(&logfont);
1941 SelectObject(hdc, hFont);
1943 if (item->pszText)
1945 ExtTextOutW(hdc,
1946 (infoPtr->dwStyle & TCS_BOTTOM) ? drawRect->right : drawRect->left,
1947 (!(infoPtr->dwStyle & TCS_BOTTOM)) ? drawRect->bottom : drawRect->top,
1948 ETO_CLIPPED,
1949 drawRect,
1950 item->pszText,
1951 lstrlenW(item->pszText),
1955 DeleteObject(hFont);
1957 else
1959 TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%s), textlen=%d\n",
1960 debugstr_w(item->pszText), center_offset_h, center_offset_v,
1961 wine_dbgstr_rect(drawRect), (rcText.right-rcText.left));
1962 if (item->pszText)
1964 DrawTextW
1966 hdc,
1967 item->pszText,
1968 lstrlenW(item->pszText),
1969 drawRect,
1970 DT_LEFT | DT_SINGLELINE
1975 *drawRect = rcTemp; /* restore drawRect */
1979 * Cleanup
1981 SelectObject(hdc, hOldFont);
1982 SetBkMode(hdc, oldBkMode);
1983 SelectObject(hdc, holdPen);
1984 DeleteObject( htextPen );
1987 /******************************************************************************
1988 * TAB_DrawItem
1990 * This method is used to draw a single tab into the tab control.
1992 static void TAB_DrawItem(const TAB_INFO *infoPtr, HDC hdc, INT iItem)
1994 RECT itemRect;
1995 RECT selectedRect;
1996 BOOL isVisible;
1997 RECT r, fillRect, r1;
1998 INT clRight = 0;
1999 INT clBottom = 0;
2000 COLORREF bkgnd, corner;
2001 HTHEME theme;
2004 * Get the rectangle for the item.
2006 isVisible = TAB_InternalGetItemRect(infoPtr,
2007 iItem,
2008 &itemRect,
2009 &selectedRect);
2011 if (isVisible)
2013 RECT rUD, rC;
2015 /* Clip UpDown control to not draw over it */
2016 if (infoPtr->needsScrolling)
2018 GetWindowRect(infoPtr->hwnd, &rC);
2019 GetWindowRect(infoPtr->hwndUpDown, &rUD);
2020 ExcludeClipRect(hdc, rUD.left - rC.left, rUD.top - rC.top, rUD.right - rC.left, rUD.bottom - rC.top);
2023 /* If you need to see what the control is doing,
2024 * then override these variables. They will change what
2025 * fill colors are used for filling the tabs, and the
2026 * corners when drawing the edge.
2028 bkgnd = comctl32_color.clrBtnFace;
2029 corner = comctl32_color.clrBtnFace;
2031 if (infoPtr->dwStyle & TCS_BUTTONS)
2033 /* Get item rectangle */
2034 r = itemRect;
2036 /* Separators between flat buttons */
2037 if ((infoPtr->dwStyle & TCS_FLATBUTTONS) && (infoPtr->exStyle & TCS_EX_FLATSEPARATORS))
2039 r1 = r;
2040 r1.right += (FLAT_BTN_SPACINGX -2);
2041 DrawEdge(hdc, &r1, EDGE_ETCHED, BF_RIGHT);
2044 if (iItem == infoPtr->iSelected)
2046 DrawEdge(hdc, &r, EDGE_SUNKEN, BF_SOFT|BF_RECT);
2048 OffsetRect(&r, 1, 1);
2050 else /* ! selected */
2052 DWORD state = TAB_GetItem(infoPtr, iItem)->dwState;
2054 if ((state & TCIS_BUTTONPRESSED) || (iItem == infoPtr->uFocus))
2055 DrawEdge(hdc, &r, EDGE_SUNKEN, BF_SOFT|BF_RECT);
2056 else
2057 if (!(infoPtr->dwStyle & TCS_FLATBUTTONS))
2058 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_RECT);
2061 else /* !TCS_BUTTONS */
2063 /* We draw a rectangle of different sizes depending on the selection
2064 * state. */
2065 if (iItem == infoPtr->iSelected) {
2066 RECT rect;
2067 GetClientRect (infoPtr->hwnd, &rect);
2068 clRight = rect.right;
2069 clBottom = rect.bottom;
2070 r = selectedRect;
2072 else
2073 r = itemRect;
2076 * Erase the background. (Delay it but setup rectangle.)
2077 * This is necessary when drawing the selected item since it is larger
2078 * than the others, it might overlap with stuff already drawn by the
2079 * other tabs
2081 fillRect = r;
2083 /* Draw themed tabs - but only if they are at the top.
2084 * Windows draws even side or bottom tabs themed, with wacky results.
2085 * However, since in Wine apps may get themed that did not opt in via
2086 * a manifest avoid theming when we know the result will be wrong */
2087 if ((theme = GetWindowTheme (infoPtr->hwnd))
2088 && ((infoPtr->dwStyle & (TCS_VERTICAL | TCS_BOTTOM)) == 0))
2090 static const int partIds[8] = {
2091 /* Normal item */
2092 TABP_TABITEM,
2093 TABP_TABITEMLEFTEDGE,
2094 TABP_TABITEMRIGHTEDGE,
2095 TABP_TABITEMBOTHEDGE,
2096 /* Selected tab */
2097 TABP_TOPTABITEM,
2098 TABP_TOPTABITEMLEFTEDGE,
2099 TABP_TOPTABITEMRIGHTEDGE,
2100 TABP_TOPTABITEMBOTHEDGE,
2102 int partIndex = 0;
2103 int stateId = TIS_NORMAL;
2105 /* selected and unselected tabs have different parts */
2106 if (iItem == infoPtr->iSelected)
2107 partIndex += 4;
2108 /* The part also differs on the position of a tab on a line.
2109 * "Visually" determining the position works well enough. */
2110 if(selectedRect.left == 0)
2111 partIndex += 1;
2112 if(selectedRect.right == clRight)
2113 partIndex += 2;
2115 if (iItem == infoPtr->iSelected)
2116 stateId = TIS_SELECTED;
2117 else if (iItem == infoPtr->iHotTracked)
2118 stateId = TIS_HOT;
2119 else if (iItem == infoPtr->uFocus)
2120 stateId = TIS_FOCUSED;
2122 /* Adjust rectangle for bottommost row */
2123 if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2124 r.bottom += 3;
2126 DrawThemeBackground (theme, hdc, partIds[partIndex], stateId, &r, NULL);
2127 GetThemeBackgroundContentRect (theme, hdc, partIds[partIndex], stateId, &r, &r);
2129 else if(infoPtr->dwStyle & TCS_VERTICAL)
2131 /* These are for adjusting the drawing of a Selected tab */
2132 /* The initial values are for the normal case of non-Selected */
2133 int ZZ = 1; /* Do not stretch if selected */
2134 if (iItem == infoPtr->iSelected) {
2135 ZZ = 0;
2137 /* if leftmost draw the line longer */
2138 if(selectedRect.top == 0)
2139 fillRect.top += CONTROL_BORDER_SIZEY;
2140 /* if rightmost draw the line longer */
2141 if(selectedRect.bottom == clBottom)
2142 fillRect.bottom -= CONTROL_BORDER_SIZEY;
2145 if (infoPtr->dwStyle & TCS_BOTTOM)
2147 /* Adjust both rectangles to match native */
2148 r.left += (1-ZZ);
2150 TRACE("<right> item=%d, fill=(%s), edge=(%s)\n",
2151 iItem, wine_dbgstr_rect(&fillRect), wine_dbgstr_rect(&r));
2153 /* Clear interior */
2154 SetBkColor(hdc, bkgnd);
2155 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2157 /* Draw rectangular edge around tab */
2158 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_RIGHT|BF_TOP|BF_BOTTOM);
2160 /* Now erase the top corner and draw diagonal edge */
2161 SetBkColor(hdc, corner);
2162 r1.left = r.right - ROUND_CORNER_SIZE - 1;
2163 r1.top = r.top;
2164 r1.right = r.right;
2165 r1.bottom = r1.top + ROUND_CORNER_SIZE;
2166 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2167 r1.right--;
2168 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPLEFT);
2170 /* Now erase the bottom corner and draw diagonal edge */
2171 r1.left = r.right - ROUND_CORNER_SIZE - 1;
2172 r1.bottom = r.bottom;
2173 r1.right = r.right;
2174 r1.top = r1.bottom - ROUND_CORNER_SIZE;
2175 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2176 r1.right--;
2177 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMLEFT);
2179 if ((iItem == infoPtr->iSelected) && (selectedRect.top == 0)) {
2180 r1 = r;
2181 r1.right = r1.left;
2182 r1.left--;
2183 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_TOP);
2187 else
2189 TRACE("<left> item=%d, fill=(%s), edge=(%s)\n",
2190 iItem, wine_dbgstr_rect(&fillRect), wine_dbgstr_rect(&r));
2192 /* Clear interior */
2193 SetBkColor(hdc, bkgnd);
2194 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2196 /* Draw rectangular edge around tab */
2197 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_TOP|BF_BOTTOM);
2199 /* Now erase the top corner and draw diagonal edge */
2200 SetBkColor(hdc, corner);
2201 r1.left = r.left;
2202 r1.top = r.top;
2203 r1.right = r1.left + ROUND_CORNER_SIZE + 1;
2204 r1.bottom = r1.top + ROUND_CORNER_SIZE;
2205 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2206 r1.left++;
2207 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPRIGHT);
2209 /* Now erase the bottom corner and draw diagonal edge */
2210 r1.left = r.left;
2211 r1.bottom = r.bottom;
2212 r1.right = r1.left + ROUND_CORNER_SIZE + 1;
2213 r1.top = r1.bottom - ROUND_CORNER_SIZE;
2214 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2215 r1.left++;
2216 DrawEdge(hdc, &r1, EDGE_SUNKEN, BF_DIAGONAL_ENDTOPLEFT);
2219 else /* ! TCS_VERTICAL */
2221 /* These are for adjusting the drawing of a Selected tab */
2222 /* The initial values are for the normal case of non-Selected */
2223 if (iItem == infoPtr->iSelected) {
2224 /* if leftmost draw the line longer */
2225 if(selectedRect.left == 0)
2226 fillRect.left += CONTROL_BORDER_SIZEX;
2227 /* if rightmost draw the line longer */
2228 if(selectedRect.right == clRight)
2229 fillRect.right -= CONTROL_BORDER_SIZEX;
2232 if (infoPtr->dwStyle & TCS_BOTTOM)
2234 /* Adjust both rectangles for topmost row */
2235 if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2237 fillRect.top -= 2;
2238 r.top -= 1;
2241 TRACE("<bottom> item=%d, fill=(%s), edge=(%s)\n",
2242 iItem, wine_dbgstr_rect(&fillRect), wine_dbgstr_rect(&r));
2244 /* Clear interior */
2245 SetBkColor(hdc, bkgnd);
2246 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2248 /* Draw rectangular edge around tab */
2249 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_BOTTOM|BF_RIGHT);
2251 /* Now erase the righthand corner and draw diagonal edge */
2252 SetBkColor(hdc, corner);
2253 r1.left = r.right - ROUND_CORNER_SIZE;
2254 r1.bottom = r.bottom;
2255 r1.right = r.right;
2256 r1.top = r1.bottom - ROUND_CORNER_SIZE - 1;
2257 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2258 r1.bottom--;
2259 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMLEFT);
2261 /* Now erase the lefthand corner and draw diagonal edge */
2262 r1.left = r.left;
2263 r1.bottom = r.bottom;
2264 r1.right = r1.left + ROUND_CORNER_SIZE;
2265 r1.top = r1.bottom - ROUND_CORNER_SIZE - 1;
2266 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2267 r1.bottom--;
2268 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPLEFT);
2270 if (iItem == infoPtr->iSelected)
2272 r.top += 2;
2273 r.left += 1;
2274 if (selectedRect.left == 0)
2276 r1 = r;
2277 r1.bottom = r1.top;
2278 r1.top--;
2279 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_LEFT);
2284 else
2286 /* Adjust both rectangles for bottommost row */
2287 if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2289 fillRect.bottom += 3;
2290 r.bottom += 2;
2293 TRACE("<top> item=%d, fill=(%s), edge=(%s)\n",
2294 iItem, wine_dbgstr_rect(&fillRect), wine_dbgstr_rect(&r));
2296 /* Clear interior */
2297 SetBkColor(hdc, bkgnd);
2298 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2300 /* Draw rectangular edge around tab */
2301 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_TOP|BF_RIGHT);
2303 /* Now erase the righthand corner and draw diagonal edge */
2304 SetBkColor(hdc, corner);
2305 r1.left = r.right - ROUND_CORNER_SIZE;
2306 r1.top = r.top;
2307 r1.right = r.right;
2308 r1.bottom = r1.top + ROUND_CORNER_SIZE + 1;
2309 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2310 r1.top++;
2311 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMRIGHT);
2313 /* Now erase the lefthand corner and draw diagonal edge */
2314 r1.left = r.left;
2315 r1.top = r.top;
2316 r1.right = r1.left + ROUND_CORNER_SIZE;
2317 r1.bottom = r1.top + ROUND_CORNER_SIZE + 1;
2318 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2319 r1.top++;
2320 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPRIGHT);
2325 TAB_DumpItemInternal(infoPtr, iItem);
2327 /* This modifies r to be the text rectangle. */
2328 TAB_DrawItemInterior(infoPtr, hdc, iItem, &r);
2332 /******************************************************************************
2333 * TAB_DrawBorder
2335 * This method is used to draw the raised border around the tab control
2336 * "content" area.
2338 static void TAB_DrawBorder(const TAB_INFO *infoPtr, HDC hdc)
2340 RECT rect;
2341 HTHEME theme = GetWindowTheme (infoPtr->hwnd);
2343 GetClientRect (infoPtr->hwnd, &rect);
2346 * Adjust for the style
2349 if (infoPtr->uNumItem)
2351 if ((infoPtr->dwStyle & TCS_BOTTOM) && !(infoPtr->dwStyle & TCS_VERTICAL))
2352 rect.bottom -= infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2353 else if((infoPtr->dwStyle & TCS_BOTTOM) && (infoPtr->dwStyle & TCS_VERTICAL))
2354 rect.right -= infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2355 else if(infoPtr->dwStyle & TCS_VERTICAL)
2356 rect.left += infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2357 else /* not TCS_VERTICAL and not TCS_BOTTOM */
2358 rect.top += infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2361 TRACE("border=(%s)\n", wine_dbgstr_rect(&rect));
2363 if (theme)
2364 DrawThemeBackground (theme, hdc, TABP_PANE, 0, &rect, NULL);
2365 else
2366 DrawEdge(hdc, &rect, EDGE_RAISED, BF_SOFT|BF_RECT);
2369 /******************************************************************************
2370 * TAB_Refresh
2372 * This method repaints the tab control..
2374 static void TAB_Refresh (const TAB_INFO *infoPtr, HDC hdc)
2376 HFONT hOldFont;
2377 INT i;
2379 if (!infoPtr->DoRedraw)
2380 return;
2382 hOldFont = SelectObject (hdc, infoPtr->hFont);
2384 if (infoPtr->dwStyle & TCS_BUTTONS)
2386 for (i = 0; i < infoPtr->uNumItem; i++)
2387 TAB_DrawItem (infoPtr, hdc, i);
2389 else
2391 /* Draw all the non selected item first */
2392 for (i = 0; i < infoPtr->uNumItem; i++)
2394 if (i != infoPtr->iSelected)
2395 TAB_DrawItem (infoPtr, hdc, i);
2398 /* Now, draw the border, draw it before the selected item
2399 * since the selected item overwrites part of the border. */
2400 TAB_DrawBorder (infoPtr, hdc);
2402 /* Then, draw the selected item */
2403 TAB_DrawItem (infoPtr, hdc, infoPtr->iSelected);
2406 SelectObject (hdc, hOldFont);
2409 static inline DWORD TAB_GetRowCount (const TAB_INFO *infoPtr)
2411 TRACE("(%p)\n", infoPtr);
2412 return infoPtr->uNumRows;
2415 static inline LRESULT TAB_SetRedraw (TAB_INFO *infoPtr, BOOL doRedraw)
2417 infoPtr->DoRedraw = doRedraw;
2418 return 0;
2421 /******************************************************************************
2422 * TAB_EnsureSelectionVisible
2424 * This method will make sure that the current selection is completely
2425 * visible by scrolling until it is.
2427 static void TAB_EnsureSelectionVisible(
2428 TAB_INFO* infoPtr)
2430 INT iSelected = infoPtr->iSelected;
2431 INT iOrigLeftmostVisible = infoPtr->leftmostVisible;
2433 /* set the items row to the bottommost row or topmost row depending on
2434 * style */
2435 if ((infoPtr->uNumRows > 1) && !(infoPtr->dwStyle & TCS_BUTTONS))
2437 TAB_ITEM *selected = TAB_GetItem(infoPtr, iSelected);
2438 INT newselected;
2439 INT iTargetRow;
2441 if(infoPtr->dwStyle & TCS_VERTICAL)
2442 newselected = selected->rect.left;
2443 else
2444 newselected = selected->rect.top;
2446 /* the target row is always (number of rows - 1)
2447 as row 0 is furthest from the clientRect */
2448 iTargetRow = infoPtr->uNumRows - 1;
2450 if (newselected != iTargetRow)
2452 UINT i;
2453 if(infoPtr->dwStyle & TCS_VERTICAL)
2455 for (i=0; i < infoPtr->uNumItem; i++)
2457 /* move everything in the row of the selected item to the iTargetRow */
2458 TAB_ITEM *item = TAB_GetItem(infoPtr, i);
2460 if (item->rect.left == newselected )
2461 item->rect.left = iTargetRow;
2462 else
2464 if (item->rect.left > newselected)
2465 item->rect.left-=1;
2469 else
2471 for (i=0; i < infoPtr->uNumItem; i++)
2473 TAB_ITEM *item = TAB_GetItem(infoPtr, i);
2475 if (item->rect.top == newselected )
2476 item->rect.top = iTargetRow;
2477 else
2479 if (item->rect.top > newselected)
2480 item->rect.top-=1;
2484 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
2489 * Do the trivial cases first.
2491 if ( (!infoPtr->needsScrolling) ||
2492 (infoPtr->hwndUpDown==0) || (infoPtr->dwStyle & TCS_VERTICAL))
2493 return;
2495 if (infoPtr->leftmostVisible >= iSelected)
2497 infoPtr->leftmostVisible = iSelected;
2499 else
2501 TAB_ITEM *selected = TAB_GetItem(infoPtr, iSelected);
2502 RECT r;
2503 INT width;
2504 UINT i;
2506 /* Calculate the part of the client area that is visible */
2507 GetClientRect(infoPtr->hwnd, &r);
2508 width = r.right;
2510 GetClientRect(infoPtr->hwndUpDown, &r);
2511 width -= r.right;
2513 if ((selected->rect.right -
2514 selected->rect.left) >= width )
2516 /* Special case: width of selected item is greater than visible
2517 * part of control.
2519 infoPtr->leftmostVisible = iSelected;
2521 else
2523 for (i = infoPtr->leftmostVisible; i < infoPtr->uNumItem; i++)
2525 if ((selected->rect.right - TAB_GetItem(infoPtr, i)->rect.left) < width)
2526 break;
2528 infoPtr->leftmostVisible = i;
2532 if (infoPtr->leftmostVisible != iOrigLeftmostVisible)
2533 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
2535 SendMessageW(infoPtr->hwndUpDown, UDM_SETPOS, 0,
2536 MAKELONG(infoPtr->leftmostVisible, 0));
2539 /******************************************************************************
2540 * TAB_InvalidateTabArea
2542 * This method will invalidate the portion of the control that contains the
2543 * tabs. It is called when the state of the control changes and needs
2544 * to be redisplayed
2546 static void TAB_InvalidateTabArea(const TAB_INFO *infoPtr)
2548 RECT clientRect, rInvalidate, rAdjClient;
2549 INT lastRow = infoPtr->uNumRows - 1;
2550 RECT rect;
2552 if (lastRow < 0) return;
2554 GetClientRect(infoPtr->hwnd, &clientRect);
2555 rInvalidate = clientRect;
2556 rAdjClient = clientRect;
2558 TAB_AdjustRect(infoPtr, 0, &rAdjClient);
2560 TAB_InternalGetItemRect(infoPtr, infoPtr->uNumItem-1 , &rect, NULL);
2561 if ((infoPtr->dwStyle & TCS_BOTTOM) && (infoPtr->dwStyle & TCS_VERTICAL))
2563 rInvalidate.left = rAdjClient.right;
2564 if (infoPtr->uNumRows == 1)
2565 rInvalidate.bottom = clientRect.top + rect.bottom + 2 * SELECTED_TAB_OFFSET;
2567 else if(infoPtr->dwStyle & TCS_VERTICAL)
2569 rInvalidate.right = rAdjClient.left;
2570 if (infoPtr->uNumRows == 1)
2571 rInvalidate.bottom = clientRect.top + rect.bottom + 2 * SELECTED_TAB_OFFSET;
2573 else if (infoPtr->dwStyle & TCS_BOTTOM)
2575 rInvalidate.top = rAdjClient.bottom;
2576 if (infoPtr->uNumRows == 1)
2577 rInvalidate.right = clientRect.left + rect.right + 2 * SELECTED_TAB_OFFSET;
2579 else
2581 rInvalidate.bottom = rAdjClient.top;
2582 if (infoPtr->uNumRows == 1)
2583 rInvalidate.right = clientRect.left + rect.right + 2 * SELECTED_TAB_OFFSET;
2586 /* Punch out the updown control */
2587 if (infoPtr->needsScrolling && (rInvalidate.right > 0)) {
2588 RECT r;
2589 GetClientRect(infoPtr->hwndUpDown, &r);
2590 if (rInvalidate.right > clientRect.right - r.left)
2591 rInvalidate.right = rInvalidate.right - (r.right - r.left);
2592 else
2593 rInvalidate.right = clientRect.right - r.left;
2596 TRACE("invalidate (%s)\n", wine_dbgstr_rect(&rInvalidate));
2598 InvalidateRect(infoPtr->hwnd, &rInvalidate, TRUE);
2601 static inline LRESULT TAB_Paint (TAB_INFO *infoPtr, HDC hdcPaint)
2603 HDC hdc;
2604 PAINTSTRUCT ps;
2606 if (hdcPaint)
2607 hdc = hdcPaint;
2608 else
2610 hdc = BeginPaint (infoPtr->hwnd, &ps);
2611 TRACE("erase %d, rect=(%s)\n", ps.fErase, wine_dbgstr_rect(&ps.rcPaint));
2614 TAB_Refresh (infoPtr, hdc);
2616 if (!hdcPaint)
2617 EndPaint (infoPtr->hwnd, &ps);
2619 return 0;
2622 static LRESULT
2623 TAB_InsertItemT (TAB_INFO *infoPtr, INT iItem, TCITEMW *pti, BOOL bUnicode)
2625 TAB_ITEM *item;
2626 RECT rect;
2628 GetClientRect (infoPtr->hwnd, &rect);
2629 TRACE("Rect: %p %s\n", infoPtr->hwnd, wine_dbgstr_rect(&rect));
2631 if (iItem < 0) return -1;
2632 if (iItem > infoPtr->uNumItem)
2633 iItem = infoPtr->uNumItem;
2635 TAB_DumpItemExternalT(pti, iItem, bUnicode);
2638 if (infoPtr->uNumItem == 0) {
2639 infoPtr->items = Alloc (TAB_ITEM_SIZE(infoPtr));
2640 infoPtr->uNumItem++;
2641 infoPtr->iSelected = 0;
2643 else {
2644 LPBYTE oldItems = (LPBYTE)infoPtr->items;
2646 infoPtr->uNumItem++;
2647 infoPtr->items = Alloc (TAB_ITEM_SIZE(infoPtr) * infoPtr->uNumItem);
2649 /* pre insert copy */
2650 if (iItem > 0) {
2651 memcpy (infoPtr->items, oldItems,
2652 iItem * TAB_ITEM_SIZE(infoPtr));
2655 /* post insert copy */
2656 if (iItem < infoPtr->uNumItem - 1) {
2657 memcpy (TAB_GetItem(infoPtr, iItem + 1),
2658 oldItems + iItem * TAB_ITEM_SIZE(infoPtr),
2659 (infoPtr->uNumItem - iItem - 1) * TAB_ITEM_SIZE(infoPtr));
2663 if (iItem <= infoPtr->iSelected)
2664 infoPtr->iSelected++;
2666 Free (oldItems);
2669 item = TAB_GetItem(infoPtr, iItem);
2671 item->pszText = NULL;
2673 if (pti->mask & TCIF_TEXT)
2675 if (bUnicode)
2676 Str_SetPtrW (&item->pszText, pti->pszText);
2677 else
2678 Str_SetPtrAtoW (&item->pszText, (LPSTR)pti->pszText);
2681 if (pti->mask & TCIF_IMAGE)
2682 item->iImage = pti->iImage;
2683 else
2684 item->iImage = -1;
2686 if (pti->mask & TCIF_PARAM)
2687 memcpy(item->extra, &pti->lParam, infoPtr->cbInfo);
2688 else
2689 memset(item->extra, 0, infoPtr->cbInfo);
2691 TAB_SetItemBounds(infoPtr);
2692 if (infoPtr->uNumItem > 1)
2693 TAB_InvalidateTabArea(infoPtr);
2694 else
2695 InvalidateRect(infoPtr->hwnd, NULL, TRUE);
2697 TRACE("[%p]: added item %d %s\n",
2698 infoPtr->hwnd, iItem, debugstr_w(item->pszText));
2700 /* If we haven't set the current focus yet, set it now. */
2701 if (infoPtr->uFocus == -1)
2702 TAB_SetCurFocus(infoPtr, iItem);
2704 return iItem;
2707 static LRESULT
2708 TAB_SetItemSize (TAB_INFO *infoPtr, INT cx, INT cy)
2710 LONG lResult = 0;
2711 BOOL bNeedPaint = FALSE;
2713 lResult = MAKELONG(infoPtr->tabWidth, infoPtr->tabHeight);
2715 /* UNDOCUMENTED: If requested Width or Height is 0 this means that program wants to use auto size. */
2716 if (infoPtr->dwStyle & TCS_FIXEDWIDTH && (infoPtr->tabWidth != cx))
2718 infoPtr->tabWidth = cx;
2719 bNeedPaint = TRUE;
2722 if (infoPtr->tabHeight != cy)
2724 if ((infoPtr->fHeightSet = (cy != 0)))
2725 infoPtr->tabHeight = cy;
2727 bNeedPaint = TRUE;
2729 TRACE("was h=%d,w=%d, now h=%d,w=%d\n",
2730 HIWORD(lResult), LOWORD(lResult),
2731 infoPtr->tabHeight, infoPtr->tabWidth);
2733 if (bNeedPaint)
2735 TAB_SetItemBounds(infoPtr);
2736 RedrawWindow(infoPtr->hwnd, NULL, NULL, RDW_ERASE | RDW_INVALIDATE | RDW_UPDATENOW);
2739 return lResult;
2742 static inline LRESULT TAB_SetMinTabWidth (TAB_INFO *infoPtr, INT cx)
2744 INT oldcx = 0;
2746 TRACE("(%p,%d)\n", infoPtr, cx);
2748 if (infoPtr->tabMinWidth < 0)
2749 oldcx = DEFAULT_MIN_TAB_WIDTH;
2750 else
2751 oldcx = infoPtr->tabMinWidth;
2752 infoPtr->tabMinWidth = cx;
2753 TAB_SetItemBounds(infoPtr);
2754 return oldcx;
2757 static inline LRESULT
2758 TAB_HighlightItem (TAB_INFO *infoPtr, INT iItem, BOOL fHighlight)
2760 LPDWORD lpState;
2761 DWORD oldState;
2762 RECT r;
2764 TRACE("(%p,%d,%s)\n", infoPtr, iItem, fHighlight ? "true" : "false");
2766 if (iItem < 0 || iItem >= infoPtr->uNumItem)
2767 return FALSE;
2769 lpState = &TAB_GetItem(infoPtr, iItem)->dwState;
2770 oldState = *lpState;
2772 if (fHighlight)
2773 *lpState |= TCIS_HIGHLIGHTED;
2774 else
2775 *lpState &= ~TCIS_HIGHLIGHTED;
2777 if ((oldState != *lpState) && TAB_InternalGetItemRect (infoPtr, iItem, &r, NULL))
2778 InvalidateRect (infoPtr->hwnd, &r, TRUE);
2780 return TRUE;
2783 static LRESULT
2784 TAB_SetItemT (TAB_INFO *infoPtr, INT iItem, LPTCITEMW tabItem, BOOL bUnicode)
2786 TAB_ITEM *wineItem;
2788 TRACE("(%p,%d,%p,%s)\n", infoPtr, iItem, tabItem, bUnicode ? "true" : "false");
2790 if (iItem < 0 || iItem >= infoPtr->uNumItem)
2791 return FALSE;
2793 TAB_DumpItemExternalT(tabItem, iItem, bUnicode);
2795 wineItem = TAB_GetItem(infoPtr, iItem);
2797 if (tabItem->mask & TCIF_IMAGE)
2798 wineItem->iImage = tabItem->iImage;
2800 if (tabItem->mask & TCIF_PARAM)
2801 memcpy(wineItem->extra, &tabItem->lParam, infoPtr->cbInfo);
2803 if (tabItem->mask & TCIF_RTLREADING)
2804 FIXME("TCIF_RTLREADING\n");
2806 if (tabItem->mask & TCIF_STATE)
2807 wineItem->dwState = (wineItem->dwState & ~tabItem->dwStateMask) |
2808 ( tabItem->dwState & tabItem->dwStateMask);
2810 if (tabItem->mask & TCIF_TEXT)
2812 Free(wineItem->pszText);
2813 wineItem->pszText = NULL;
2814 if (bUnicode)
2815 Str_SetPtrW(&wineItem->pszText, tabItem->pszText);
2816 else
2817 Str_SetPtrAtoW(&wineItem->pszText, (LPSTR)tabItem->pszText);
2820 /* Update and repaint tabs */
2821 TAB_SetItemBounds(infoPtr);
2822 TAB_InvalidateTabArea(infoPtr);
2824 return TRUE;
2827 static inline LRESULT TAB_GetItemCount (const TAB_INFO *infoPtr)
2829 TRACE("\n");
2830 return infoPtr->uNumItem;
2834 static LRESULT
2835 TAB_GetItemT (TAB_INFO *infoPtr, INT iItem, LPTCITEMW tabItem, BOOL bUnicode)
2837 TAB_ITEM *wineItem;
2839 TRACE("(%p,%d,%p,%s)\n", infoPtr, iItem, tabItem, bUnicode ? "true" : "false");
2841 if (!tabItem) return FALSE;
2843 if (iItem < 0 || iItem >= infoPtr->uNumItem)
2845 /* init requested fields */
2846 if (tabItem->mask & TCIF_IMAGE) tabItem->iImage = 0;
2847 if (tabItem->mask & TCIF_PARAM) tabItem->lParam = 0;
2848 if (tabItem->mask & TCIF_STATE) tabItem->dwState = 0;
2849 return FALSE;
2852 wineItem = TAB_GetItem(infoPtr, iItem);
2854 if (tabItem->mask & TCIF_IMAGE)
2855 tabItem->iImage = wineItem->iImage;
2857 if (tabItem->mask & TCIF_PARAM)
2858 memcpy(&tabItem->lParam, wineItem->extra, infoPtr->cbInfo);
2860 if (tabItem->mask & TCIF_RTLREADING)
2861 FIXME("TCIF_RTLREADING\n");
2863 if (tabItem->mask & TCIF_STATE)
2864 tabItem->dwState = wineItem->dwState & tabItem->dwStateMask;
2866 if (tabItem->mask & TCIF_TEXT)
2868 if (bUnicode)
2869 Str_GetPtrW (wineItem->pszText, tabItem->pszText, tabItem->cchTextMax);
2870 else
2871 Str_GetPtrWtoA (wineItem->pszText, (LPSTR)tabItem->pszText, tabItem->cchTextMax);
2874 TAB_DumpItemExternalT(tabItem, iItem, bUnicode);
2876 return TRUE;
2880 static LRESULT TAB_DeleteItem (TAB_INFO *infoPtr, INT iItem)
2882 BOOL bResult = FALSE;
2884 TRACE("(%p, %d)\n", infoPtr, iItem);
2886 if ((iItem >= 0) && (iItem < infoPtr->uNumItem))
2888 TAB_ITEM *item = TAB_GetItem(infoPtr, iItem);
2889 LPBYTE oldItems = (LPBYTE)infoPtr->items;
2891 TAB_InvalidateTabArea(infoPtr);
2892 Free(item->pszText);
2893 infoPtr->uNumItem--;
2895 if (!infoPtr->uNumItem)
2897 infoPtr->items = NULL;
2898 if (infoPtr->iHotTracked >= 0)
2900 KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
2901 infoPtr->iHotTracked = -1;
2904 else
2906 infoPtr->items = Alloc(TAB_ITEM_SIZE(infoPtr) * infoPtr->uNumItem);
2908 if (iItem > 0)
2909 memcpy(infoPtr->items, oldItems, iItem * TAB_ITEM_SIZE(infoPtr));
2911 if (iItem < infoPtr->uNumItem)
2912 memcpy(TAB_GetItem(infoPtr, iItem),
2913 oldItems + (iItem + 1) * TAB_ITEM_SIZE(infoPtr),
2914 (infoPtr->uNumItem - iItem) * TAB_ITEM_SIZE(infoPtr));
2916 if (iItem <= infoPtr->iHotTracked)
2918 /* When tabs move left/up, the hot track item may change */
2919 FIXME("Recalc hot track\n");
2922 Free(oldItems);
2924 /* Readjust the selected index */
2925 if (iItem == infoPtr->iSelected)
2926 infoPtr->iSelected = -1;
2927 else if (iItem < infoPtr->iSelected)
2928 infoPtr->iSelected--;
2930 if (infoPtr->uNumItem == 0)
2931 infoPtr->iSelected = -1;
2933 /* Reposition and repaint tabs */
2934 TAB_SetItemBounds(infoPtr);
2936 bResult = TRUE;
2939 return bResult;
2942 static inline LRESULT TAB_DeleteAllItems (TAB_INFO *infoPtr)
2944 TRACE("(%p)\n", infoPtr);
2945 while (infoPtr->uNumItem)
2946 TAB_DeleteItem (infoPtr, 0);
2947 return TRUE;
2951 static inline LRESULT TAB_GetFont (const TAB_INFO *infoPtr)
2953 TRACE("(%p) returning %p\n", infoPtr, infoPtr->hFont);
2954 return (LRESULT)infoPtr->hFont;
2957 static inline LRESULT TAB_SetFont (TAB_INFO *infoPtr, HFONT hNewFont)
2959 TRACE("(%p,%p)\n", infoPtr, hNewFont);
2961 infoPtr->hFont = hNewFont;
2963 TAB_SetItemBounds(infoPtr);
2965 TAB_InvalidateTabArea(infoPtr);
2967 return 0;
2971 static inline LRESULT TAB_GetImageList (const TAB_INFO *infoPtr)
2973 TRACE("\n");
2974 return (LRESULT)infoPtr->himl;
2977 static inline LRESULT TAB_SetImageList (TAB_INFO *infoPtr, HIMAGELIST himlNew)
2979 HIMAGELIST himlPrev = infoPtr->himl;
2980 TRACE("himl=%p\n", himlNew);
2981 infoPtr->himl = himlNew;
2982 TAB_SetItemBounds(infoPtr);
2983 InvalidateRect(infoPtr->hwnd, NULL, TRUE);
2984 return (LRESULT)himlPrev;
2987 static inline LRESULT TAB_GetUnicodeFormat (const TAB_INFO *infoPtr)
2989 TRACE("(%p)\n", infoPtr);
2990 return infoPtr->bUnicode;
2993 static inline LRESULT TAB_SetUnicodeFormat (TAB_INFO *infoPtr, BOOL bUnicode)
2995 BOOL bTemp = infoPtr->bUnicode;
2997 TRACE("(%p %d)\n", infoPtr, bUnicode);
2998 infoPtr->bUnicode = bUnicode;
3000 return bTemp;
3003 static inline LRESULT TAB_Size (TAB_INFO *infoPtr)
3005 /* I'm not really sure what the following code was meant to do.
3006 This is what it is doing:
3007 When WM_SIZE is sent with SIZE_RESTORED, the control
3008 gets positioned in the top left corner.
3010 RECT parent_rect;
3011 HWND parent;
3012 UINT uPosFlags,cx,cy;
3014 uPosFlags=0;
3015 if (!wParam) {
3016 parent = GetParent (hwnd);
3017 GetClientRect(parent, &parent_rect);
3018 cx=LOWORD (lParam);
3019 cy=HIWORD (lParam);
3020 if (GetWindowLongW(hwnd, GWL_STYLE) & CCS_NORESIZE)
3021 uPosFlags |= (SWP_NOSIZE | SWP_NOMOVE);
3023 SetWindowPos (hwnd, 0, parent_rect.left, parent_rect.top,
3024 cx, cy, uPosFlags | SWP_NOZORDER);
3025 } else {
3026 FIXME("WM_SIZE flag %x %lx not handled\n", wParam, lParam);
3027 } */
3029 /* Recompute the size/position of the tabs. */
3030 TAB_SetItemBounds (infoPtr);
3032 /* Force a repaint of the control. */
3033 InvalidateRect(infoPtr->hwnd, NULL, TRUE);
3035 return 0;
3039 static LRESULT TAB_Create (HWND hwnd, LPARAM lParam)
3041 TAB_INFO *infoPtr;
3042 TEXTMETRICW fontMetrics;
3043 HDC hdc;
3044 HFONT hOldFont;
3045 DWORD dwStyle;
3047 infoPtr = Alloc (sizeof(TAB_INFO));
3049 SetWindowLongPtrW(hwnd, 0, (DWORD_PTR)infoPtr);
3051 infoPtr->hwnd = hwnd;
3052 infoPtr->hwndNotify = ((LPCREATESTRUCTW)lParam)->hwndParent;
3053 infoPtr->uNumItem = 0;
3054 infoPtr->uNumRows = 0;
3055 infoPtr->uHItemPadding = 6;
3056 infoPtr->uVItemPadding = 3;
3057 infoPtr->uHItemPadding_s = 6;
3058 infoPtr->uVItemPadding_s = 3;
3059 infoPtr->hFont = 0;
3060 infoPtr->items = 0;
3061 infoPtr->hcurArrow = LoadCursorW (0, (LPWSTR)IDC_ARROW);
3062 infoPtr->iSelected = -1;
3063 infoPtr->iHotTracked = -1;
3064 infoPtr->uFocus = -1;
3065 infoPtr->hwndToolTip = 0;
3066 infoPtr->DoRedraw = TRUE;
3067 infoPtr->needsScrolling = FALSE;
3068 infoPtr->hwndUpDown = 0;
3069 infoPtr->leftmostVisible = 0;
3070 infoPtr->fHeightSet = FALSE;
3071 infoPtr->bUnicode = IsWindowUnicode (hwnd);
3072 infoPtr->cbInfo = sizeof(LPARAM);
3074 TRACE("Created tab control, hwnd [%p]\n", hwnd);
3076 /* The tab control always has the WS_CLIPSIBLINGS style. Even
3077 if you don't specify it in CreateWindow. This is necessary in
3078 order for paint to work correctly. This follows windows behaviour. */
3079 dwStyle = GetWindowLongW(hwnd, GWL_STYLE);
3080 SetWindowLongW(hwnd, GWL_STYLE, dwStyle|WS_CLIPSIBLINGS);
3082 infoPtr->dwStyle = dwStyle | WS_CLIPSIBLINGS;
3083 infoPtr->exStyle = (dwStyle & TCS_FLATBUTTONS) ? TCS_EX_FLATSEPARATORS : 0;
3085 if (infoPtr->dwStyle & TCS_TOOLTIPS) {
3086 /* Create tooltip control */
3087 infoPtr->hwndToolTip =
3088 CreateWindowExW (0, TOOLTIPS_CLASSW, NULL, WS_POPUP,
3089 CW_USEDEFAULT, CW_USEDEFAULT,
3090 CW_USEDEFAULT, CW_USEDEFAULT,
3091 hwnd, 0, 0, 0);
3093 /* Send NM_TOOLTIPSCREATED notification */
3094 if (infoPtr->hwndToolTip) {
3095 NMTOOLTIPSCREATED nmttc;
3097 nmttc.hdr.hwndFrom = hwnd;
3098 nmttc.hdr.idFrom = GetWindowLongPtrW(hwnd, GWLP_ID);
3099 nmttc.hdr.code = NM_TOOLTIPSCREATED;
3100 nmttc.hwndToolTips = infoPtr->hwndToolTip;
3102 SendMessageW (infoPtr->hwndNotify, WM_NOTIFY,
3103 GetWindowLongPtrW(hwnd, GWLP_ID), (LPARAM)&nmttc);
3107 OpenThemeData (infoPtr->hwnd, themeClass);
3110 * We need to get text information so we need a DC and we need to select
3111 * a font.
3113 hdc = GetDC(hwnd);
3114 hOldFont = SelectObject (hdc, GetStockObject (SYSTEM_FONT));
3116 /* Use the system font to determine the initial height of a tab. */
3117 GetTextMetricsW(hdc, &fontMetrics);
3120 * Make sure there is enough space for the letters + growing the
3121 * selected item + extra space for the selected item.
3123 infoPtr->tabHeight = fontMetrics.tmHeight + SELECTED_TAB_OFFSET +
3124 ((infoPtr->dwStyle & TCS_BUTTONS) ? 2 : 1) *
3125 infoPtr->uVItemPadding;
3127 /* Initialize the width of a tab. */
3128 if (infoPtr->dwStyle & TCS_FIXEDWIDTH)
3129 infoPtr->tabWidth = GetDeviceCaps(hdc, LOGPIXELSX);
3131 infoPtr->tabMinWidth = -1;
3133 TRACE("tabH=%d, tabW=%d\n", infoPtr->tabHeight, infoPtr->tabWidth);
3135 SelectObject (hdc, hOldFont);
3136 ReleaseDC(hwnd, hdc);
3138 return 0;
3141 static LRESULT
3142 TAB_Destroy (TAB_INFO *infoPtr)
3144 UINT iItem;
3146 SetWindowLongPtrW(infoPtr->hwnd, 0, 0);
3148 if (infoPtr->items) {
3149 for (iItem = 0; iItem < infoPtr->uNumItem; iItem++) {
3150 Free (TAB_GetItem(infoPtr, iItem)->pszText);
3152 Free (infoPtr->items);
3155 if (infoPtr->hwndToolTip)
3156 DestroyWindow (infoPtr->hwndToolTip);
3158 if (infoPtr->hwndUpDown)
3159 DestroyWindow(infoPtr->hwndUpDown);
3161 if (infoPtr->iHotTracked >= 0)
3162 KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
3164 CloseThemeData (GetWindowTheme (infoPtr->hwnd));
3166 Free (infoPtr);
3167 return 0;
3170 /* update theme after a WM_THEMECHANGED message */
3171 static LRESULT theme_changed(const TAB_INFO *infoPtr)
3173 HTHEME theme = GetWindowTheme (infoPtr->hwnd);
3174 CloseThemeData (theme);
3175 OpenThemeData (infoPtr->hwnd, themeClass);
3176 return 0;
3179 static LRESULT TAB_NCCalcSize(WPARAM wParam)
3181 if (!wParam)
3182 return 0;
3183 return WVR_ALIGNTOP;
3186 static inline LRESULT
3187 TAB_SetItemExtra (TAB_INFO *infoPtr, INT cbInfo)
3189 TRACE("(%p %d)\n", infoPtr, cbInfo);
3191 if (cbInfo <= 0)
3192 return FALSE;
3194 if (infoPtr->uNumItem)
3196 /* FIXME: MSDN says this is not allowed, but this hasn't been verified */
3197 return FALSE;
3200 infoPtr->cbInfo = cbInfo;
3201 return TRUE;
3204 static LRESULT TAB_RemoveImage (TAB_INFO *infoPtr, INT image)
3206 TRACE("%p %d\n", infoPtr, image);
3208 if (ImageList_Remove (infoPtr->himl, image))
3210 INT i, *idx;
3211 RECT r;
3213 /* shift indices, repaint items if needed */
3214 for (i = 0; i < infoPtr->uNumItem; i++)
3216 idx = &TAB_GetItem(infoPtr, i)->iImage;
3217 if (*idx >= image)
3219 if (*idx == image)
3220 *idx = -1;
3221 else
3222 (*idx)--;
3224 /* repaint item */
3225 if (TAB_InternalGetItemRect (infoPtr, i, &r, NULL))
3226 InvalidateRect (infoPtr->hwnd, &r, TRUE);
3231 return 0;
3234 static LRESULT
3235 TAB_SetExtendedStyle (TAB_INFO *infoPtr, DWORD exMask, DWORD exStyle)
3237 DWORD prevstyle = infoPtr->exStyle;
3239 /* zero mask means all styles */
3240 if (exMask == 0) exMask = ~0;
3242 if (exMask & TCS_EX_REGISTERDROP)
3244 FIXME("TCS_EX_REGISTERDROP style unimplemented\n");
3245 exMask &= ~TCS_EX_REGISTERDROP;
3246 exStyle &= ~TCS_EX_REGISTERDROP;
3249 if (exMask & TCS_EX_FLATSEPARATORS)
3251 if ((prevstyle ^ exStyle) & TCS_EX_FLATSEPARATORS)
3253 infoPtr->exStyle ^= TCS_EX_FLATSEPARATORS;
3254 TAB_InvalidateTabArea(infoPtr);
3258 return prevstyle;
3261 static inline LRESULT
3262 TAB_GetExtendedStyle (const TAB_INFO *infoPtr)
3264 return infoPtr->exStyle;
3267 static LRESULT
3268 TAB_DeselectAll (TAB_INFO *infoPtr, BOOL excludesel)
3270 BOOL paint = FALSE;
3271 INT i, selected = infoPtr->iSelected;
3273 TRACE("(%p, %d)\n", infoPtr, excludesel);
3275 if (!(infoPtr->dwStyle & TCS_BUTTONS))
3276 return 0;
3278 for (i = 0; i < infoPtr->uNumItem; i++)
3280 if ((TAB_GetItem(infoPtr, i)->dwState & TCIS_BUTTONPRESSED) &&
3281 (selected != i))
3283 TAB_GetItem(infoPtr, i)->dwState &= ~TCIS_BUTTONPRESSED;
3284 paint = TRUE;
3288 if (!excludesel && (selected != -1))
3290 TAB_GetItem(infoPtr, selected)->dwState &= ~TCIS_BUTTONPRESSED;
3291 infoPtr->iSelected = -1;
3292 paint = TRUE;
3295 if (paint)
3296 TAB_InvalidateTabArea (infoPtr);
3298 return 0;
3301 /***
3302 * DESCRIPTION:
3303 * Processes WM_STYLECHANGED messages.
3305 * PARAMETER(S):
3306 * [I] infoPtr : valid pointer to the tab data structure
3307 * [I] wStyleType : window style type (normal or extended)
3308 * [I] lpss : window style information
3310 * RETURN:
3311 * Zero
3313 static INT TAB_StyleChanged(TAB_INFO *infoPtr, WPARAM wStyleType,
3314 const STYLESTRUCT *lpss)
3316 TRACE("(styletype=%lx, styleOld=0x%08x, styleNew=0x%08x)\n",
3317 wStyleType, lpss->styleOld, lpss->styleNew);
3319 if (wStyleType != GWL_STYLE) return 0;
3321 infoPtr->dwStyle = lpss->styleNew;
3323 TAB_SetItemBounds (infoPtr);
3324 InvalidateRect(infoPtr->hwnd, NULL, TRUE);
3326 return 0;
3329 static LRESULT WINAPI
3330 TAB_WindowProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
3332 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
3334 TRACE("hwnd=%p msg=%x wParam=%lx lParam=%lx\n", hwnd, uMsg, wParam, lParam);
3335 if (!infoPtr && (uMsg != WM_CREATE))
3336 return DefWindowProcW (hwnd, uMsg, wParam, lParam);
3338 switch (uMsg)
3340 case TCM_GETIMAGELIST:
3341 return TAB_GetImageList (infoPtr);
3343 case TCM_SETIMAGELIST:
3344 return TAB_SetImageList (infoPtr, (HIMAGELIST)lParam);
3346 case TCM_GETITEMCOUNT:
3347 return TAB_GetItemCount (infoPtr);
3349 case TCM_GETITEMA:
3350 case TCM_GETITEMW:
3351 return TAB_GetItemT (infoPtr, (INT)wParam, (LPTCITEMW)lParam, uMsg == TCM_GETITEMW);
3353 case TCM_SETITEMA:
3354 case TCM_SETITEMW:
3355 return TAB_SetItemT (infoPtr, (INT)wParam, (LPTCITEMW)lParam, uMsg == TCM_SETITEMW);
3357 case TCM_DELETEITEM:
3358 return TAB_DeleteItem (infoPtr, (INT)wParam);
3360 case TCM_DELETEALLITEMS:
3361 return TAB_DeleteAllItems (infoPtr);
3363 case TCM_GETITEMRECT:
3364 return TAB_GetItemRect (infoPtr, (INT)wParam, (LPRECT)lParam);
3366 case TCM_GETCURSEL:
3367 return TAB_GetCurSel (infoPtr);
3369 case TCM_HITTEST:
3370 return TAB_HitTest (infoPtr, (LPTCHITTESTINFO)lParam);
3372 case TCM_SETCURSEL:
3373 return TAB_SetCurSel (infoPtr, (INT)wParam);
3375 case TCM_INSERTITEMA:
3376 case TCM_INSERTITEMW:
3377 return TAB_InsertItemT (infoPtr, (INT)wParam, (TCITEMW*)lParam, uMsg == TCM_INSERTITEMW);
3379 case TCM_SETITEMEXTRA:
3380 return TAB_SetItemExtra (infoPtr, (INT)wParam);
3382 case TCM_ADJUSTRECT:
3383 return TAB_AdjustRect (infoPtr, (BOOL)wParam, (LPRECT)lParam);
3385 case TCM_SETITEMSIZE:
3386 return TAB_SetItemSize (infoPtr, (INT)LOWORD(lParam), (INT)HIWORD(lParam));
3388 case TCM_REMOVEIMAGE:
3389 return TAB_RemoveImage (infoPtr, (INT)wParam);
3391 case TCM_SETPADDING:
3392 return TAB_SetPadding (infoPtr, lParam);
3394 case TCM_GETROWCOUNT:
3395 return TAB_GetRowCount(infoPtr);
3397 case TCM_GETUNICODEFORMAT:
3398 return TAB_GetUnicodeFormat (infoPtr);
3400 case TCM_SETUNICODEFORMAT:
3401 return TAB_SetUnicodeFormat (infoPtr, (BOOL)wParam);
3403 case TCM_HIGHLIGHTITEM:
3404 return TAB_HighlightItem (infoPtr, (INT)wParam, (BOOL)LOWORD(lParam));
3406 case TCM_GETTOOLTIPS:
3407 return TAB_GetToolTips (infoPtr);
3409 case TCM_SETTOOLTIPS:
3410 return TAB_SetToolTips (infoPtr, (HWND)wParam);
3412 case TCM_GETCURFOCUS:
3413 return TAB_GetCurFocus (infoPtr);
3415 case TCM_SETCURFOCUS:
3416 return TAB_SetCurFocus (infoPtr, (INT)wParam);
3418 case TCM_SETMINTABWIDTH:
3419 return TAB_SetMinTabWidth(infoPtr, (INT)lParam);
3421 case TCM_DESELECTALL:
3422 return TAB_DeselectAll (infoPtr, (BOOL)wParam);
3424 case TCM_GETEXTENDEDSTYLE:
3425 return TAB_GetExtendedStyle (infoPtr);
3427 case TCM_SETEXTENDEDSTYLE:
3428 return TAB_SetExtendedStyle (infoPtr, wParam, lParam);
3430 case WM_GETFONT:
3431 return TAB_GetFont (infoPtr);
3433 case WM_SETFONT:
3434 return TAB_SetFont (infoPtr, (HFONT)wParam);
3436 case WM_CREATE:
3437 return TAB_Create (hwnd, lParam);
3439 case WM_NCDESTROY:
3440 return TAB_Destroy (infoPtr);
3442 case WM_GETDLGCODE:
3443 return DLGC_WANTARROWS | DLGC_WANTCHARS;
3445 case WM_LBUTTONDOWN:
3446 return TAB_LButtonDown (infoPtr, wParam, lParam);
3448 case WM_LBUTTONUP:
3449 return TAB_LButtonUp (infoPtr);
3451 case WM_NOTIFY:
3452 return SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, wParam, lParam);
3454 case WM_RBUTTONDOWN:
3455 return TAB_RButtonDown (infoPtr);
3457 case WM_MOUSEMOVE:
3458 return TAB_MouseMove (infoPtr, wParam, lParam);
3460 case WM_PRINTCLIENT:
3461 case WM_PAINT:
3462 return TAB_Paint (infoPtr, (HDC)wParam);
3464 case WM_SIZE:
3465 return TAB_Size (infoPtr);
3467 case WM_SETREDRAW:
3468 return TAB_SetRedraw (infoPtr, (BOOL)wParam);
3470 case WM_HSCROLL:
3471 return TAB_OnHScroll(infoPtr, (int)LOWORD(wParam), (int)HIWORD(wParam));
3473 case WM_STYLECHANGED:
3474 return TAB_StyleChanged(infoPtr, wParam, (LPSTYLESTRUCT)lParam);
3476 case WM_SYSCOLORCHANGE:
3477 COMCTL32_RefreshSysColors();
3478 return 0;
3480 case WM_THEMECHANGED:
3481 return theme_changed (infoPtr);
3483 case WM_KILLFOCUS:
3484 TAB_KillFocus(infoPtr);
3485 case WM_SETFOCUS:
3486 TAB_FocusChanging(infoPtr);
3487 break; /* Don't disturb normal focus behavior */
3489 case WM_KEYDOWN:
3490 return TAB_KeyDown(infoPtr, wParam, lParam);
3492 case WM_NCHITTEST:
3493 return TAB_NCHitTest(infoPtr, lParam);
3495 case WM_NCCALCSIZE:
3496 return TAB_NCCalcSize(wParam);
3498 default:
3499 if (uMsg >= WM_USER && uMsg < WM_APP && !COMCTL32_IsReflectedMessage(uMsg))
3500 WARN("unknown msg %04x wp=%08lx lp=%08lx\n",
3501 uMsg, wParam, lParam);
3502 break;
3504 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
3508 void
3509 TAB_Register (void)
3511 WNDCLASSW wndClass;
3513 ZeroMemory (&wndClass, sizeof(WNDCLASSW));
3514 wndClass.style = CS_GLOBALCLASS | CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
3515 wndClass.lpfnWndProc = TAB_WindowProc;
3516 wndClass.cbClsExtra = 0;
3517 wndClass.cbWndExtra = sizeof(TAB_INFO *);
3518 wndClass.hCursor = LoadCursorW (0, (LPWSTR)IDC_ARROW);
3519 wndClass.hbrBackground = (HBRUSH)(COLOR_BTNFACE+1);
3520 wndClass.lpszClassName = WC_TABCONTROLW;
3522 RegisterClassW (&wndClass);
3526 void
3527 TAB_Unregister (void)
3529 UnregisterClassW (WC_TABCONTROLW, NULL);