kernel32: Do not omit mandatory argument to DeviceIoControl.
[wine.git] / dlls / comctl32 / tab.c
bloba9c3b7b94afd1684f573e2ce6c0fd7131eaefe89
1 /*
2 * Tab control
4 * Copyright 1998 Anders Carlsson
5 * Copyright 1999 Alex Priem <alexp@sci.kun.nl>
6 * Copyright 1999 Francis Beaudet
7 * Copyright 2003 Vitaliy Margolen
9 * This library is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Lesser General Public
11 * License as published by the Free Software Foundation; either
12 * version 2.1 of the License, or (at your option) any later version.
14 * This library is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Lesser General Public License for more details.
19 * You should have received a copy of the GNU Lesser General Public
20 * License along with this library; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
23 * NOTES
25 * This code was audited for completeness against the documented features
26 * of Comctl32.dll version 6.0 on May. 20, 2005, by James Hawkins.
28 * Unless otherwise noted, we believe this code to be complete, as per
29 * the specification mentioned above.
30 * If you discover missing features, or bugs, please note them below.
32 * TODO:
34 * Styles:
35 * TCS_MULTISELECT - implement for VK_SPACE selection
36 * TCS_RIGHT
37 * TCS_RIGHTJUSTIFY
38 * TCS_SCROLLOPPOSITE
39 * TCS_SINGLELINE
40 * TCIF_RTLREADING
42 * Extended Styles:
43 * TCS_EX_REGISTERDROP
45 * Notifications:
46 * NM_RELEASEDCAPTURE
47 * TCN_FOCUSCHANGE
48 * TCN_GETOBJECT
50 * Macros:
51 * TabCtrl_AdjustRect
55 #include <assert.h>
56 #include <stdarg.h>
57 #include <string.h>
59 #include "windef.h"
60 #include "winbase.h"
61 #include "wingdi.h"
62 #include "winuser.h"
63 #include "winnls.h"
64 #include "commctrl.h"
65 #include "comctl32.h"
66 #include "uxtheme.h"
67 #include "vssym32.h"
68 #include "wine/debug.h"
69 #include <math.h>
71 WINE_DEFAULT_DEBUG_CHANNEL(tab);
73 typedef struct
75 DWORD dwState;
76 LPWSTR pszText;
77 INT iImage;
78 RECT rect; /* bounding rectangle of the item relative to the
79 * leftmost item (the leftmost item, 0, would have a
80 * "left" member of 0 in this rectangle)
82 * additionally the top member holds the row number
83 * and bottom is unused and should be 0 */
84 BYTE extra[1]; /* Space for caller supplied info, variable size */
85 } TAB_ITEM;
87 /* The size of a tab item depends on how much extra data is requested.
88 TCM_INSERTITEM always stores at least LPARAM sized data. */
89 #define EXTRA_ITEM_SIZE(infoPtr) (max((infoPtr)->cbInfo, sizeof(LPARAM)))
90 #define TAB_ITEM_SIZE(infoPtr) FIELD_OFFSET(TAB_ITEM, extra[EXTRA_ITEM_SIZE(infoPtr)])
92 typedef struct
94 HWND hwnd; /* Tab control window */
95 HWND hwndNotify; /* notification window (parent) */
96 UINT uNumItem; /* number of tab items */
97 UINT uNumRows; /* number of tab rows */
98 INT tabHeight; /* height of the tab row */
99 INT tabWidth; /* width of tabs */
100 INT tabMinWidth; /* minimum width of items */
101 USHORT uHItemPadding; /* amount of horizontal padding, in pixels */
102 USHORT uVItemPadding; /* amount of vertical padding, in pixels */
103 USHORT uHItemPadding_s; /* Set amount of horizontal padding, in pixels */
104 USHORT uVItemPadding_s; /* Set amount of vertical padding, in pixels */
105 HFONT hFont; /* handle to the current font */
106 HCURSOR hcurArrow; /* handle to the current cursor */
107 HIMAGELIST himl; /* handle to an image list (may be 0) */
108 HWND hwndToolTip; /* handle to tab's tooltip */
109 INT leftmostVisible; /* Used for scrolling, this member contains
110 * the index of the first visible item */
111 INT iSelected; /* the currently selected item */
112 INT iHotTracked; /* the highlighted item under the mouse */
113 INT uFocus; /* item which has the focus */
114 BOOL DoRedraw; /* flag for redrawing when tab contents is changed*/
115 BOOL needsScrolling; /* TRUE if the size of the tabs is greater than
116 * the size of the control */
117 BOOL fHeightSet; /* was the height of the tabs explicitly set? */
118 BOOL bUnicode; /* Unicode control? */
119 HWND hwndUpDown; /* Updown control used for scrolling */
120 INT cbInfo; /* Number of bytes of caller supplied info per tab */
122 DWORD exStyle; /* Extended style used, currently:
123 TCS_EX_FLATSEPARATORS, TCS_EX_REGISTERDROP */
124 DWORD dwStyle; /* the cached window GWL_STYLE */
126 HDPA items; /* dynamic array of TAB_ITEM* pointers */
127 } TAB_INFO;
129 /******************************************************************************
130 * Positioning constants
132 #define SELECTED_TAB_OFFSET 2
133 #define ROUND_CORNER_SIZE 2
134 #define DISPLAY_AREA_PADDINGX 2
135 #define DISPLAY_AREA_PADDINGY 2
136 #define CONTROL_BORDER_SIZEX 2
137 #define CONTROL_BORDER_SIZEY 2
138 #define BUTTON_SPACINGX 3
139 #define BUTTON_SPACINGY 3
140 #define FLAT_BTN_SPACINGX 8
141 #define DEFAULT_MIN_TAB_WIDTH 54
142 #define DEFAULT_PADDING_X 6
143 #define EXTRA_ICON_PADDING 3
145 #define TAB_GetInfoPtr(hwnd) ((TAB_INFO *)GetWindowLongPtrW(hwnd,0))
147 #define GET_DEFAULT_MIN_TAB_WIDTH(infoPtr) (DEFAULT_MIN_TAB_WIDTH - (DEFAULT_PADDING_X - (infoPtr)->uHItemPadding) * 2)
149 /******************************************************************************
150 * Hot-tracking timer constants
152 #define TAB_HOTTRACK_TIMER 1
153 #define TAB_HOTTRACK_TIMER_INTERVAL 100 /* milliseconds */
155 static const WCHAR themeClass[] = { 'T','a','b',0 };
157 static inline TAB_ITEM* TAB_GetItem(const TAB_INFO *infoPtr, INT i)
159 assert(i >= 0 && i < infoPtr->uNumItem);
160 return DPA_GetPtr(infoPtr->items, i);
163 /******************************************************************************
164 * Prototypes
166 static void TAB_InvalidateTabArea(const TAB_INFO *);
167 static void TAB_EnsureSelectionVisible(TAB_INFO *);
168 static void TAB_DrawItemInterior(const TAB_INFO *, HDC, INT, RECT*);
169 static LRESULT TAB_DeselectAll(TAB_INFO *, BOOL);
170 static BOOL TAB_InternalGetItemRect(const TAB_INFO *, INT, RECT*, RECT*);
172 static BOOL
173 TAB_SendSimpleNotify (const TAB_INFO *infoPtr, UINT code)
175 NMHDR nmhdr;
177 nmhdr.hwndFrom = infoPtr->hwnd;
178 nmhdr.idFrom = GetWindowLongPtrW(infoPtr->hwnd, GWLP_ID);
179 nmhdr.code = code;
181 return (BOOL) SendMessageW (infoPtr->hwndNotify, WM_NOTIFY,
182 nmhdr.idFrom, (LPARAM) &nmhdr);
185 static void
186 TAB_RelayEvent (HWND hwndTip, HWND hwndMsg, UINT uMsg,
187 WPARAM wParam, LPARAM lParam)
189 MSG msg;
191 msg.hwnd = hwndMsg;
192 msg.message = uMsg;
193 msg.wParam = wParam;
194 msg.lParam = lParam;
195 msg.time = GetMessageTime ();
196 msg.pt.x = (short)LOWORD(GetMessagePos ());
197 msg.pt.y = (short)HIWORD(GetMessagePos ());
199 SendMessageW (hwndTip, TTM_RELAYEVENT, 0, (LPARAM)&msg);
202 static void
203 TAB_DumpItemExternalT(const TCITEMW *pti, UINT iItem, BOOL isW)
205 if (TRACE_ON(tab)) {
206 TRACE("external tab %d, mask=0x%08x, dwState=0x%08x, dwStateMask=0x%08x, cchTextMax=0x%08x\n",
207 iItem, pti->mask, pti->dwState, pti->dwStateMask, pti->cchTextMax);
208 TRACE("external tab %d, iImage=%d, lParam=0x%08lx, pszTextW=%s\n",
209 iItem, pti->iImage, pti->lParam, isW ? debugstr_w(pti->pszText) : debugstr_a((LPSTR)pti->pszText));
213 static void
214 TAB_DumpItemInternal(const TAB_INFO *infoPtr, UINT iItem)
216 if (TRACE_ON(tab)) {
217 TAB_ITEM *ti = TAB_GetItem(infoPtr, iItem);
219 TRACE("tab %d, dwState=0x%08x, pszText=%s, iImage=%d\n",
220 iItem, ti->dwState, debugstr_w(ti->pszText), ti->iImage);
221 TRACE("tab %d, rect.left=%d, rect.top(row)=%d\n",
222 iItem, ti->rect.left, ti->rect.top);
226 /* RETURNS
227 * the index of the selected tab, or -1 if no tab is selected. */
228 static inline LRESULT TAB_GetCurSel (const TAB_INFO *infoPtr)
230 TRACE("(%p)\n", infoPtr);
231 return infoPtr->iSelected;
234 /* RETURNS
235 * the index of the tab item that has the focus. */
236 static inline LRESULT
237 TAB_GetCurFocus (const TAB_INFO *infoPtr)
239 TRACE("(%p)\n", infoPtr);
240 return infoPtr->uFocus;
243 static inline LRESULT TAB_GetToolTips (const TAB_INFO *infoPtr)
245 TRACE("(%p)\n", infoPtr);
246 return (LRESULT)infoPtr->hwndToolTip;
249 static inline LRESULT TAB_SetCurSel (TAB_INFO *infoPtr, INT iItem)
251 INT prevItem = infoPtr->iSelected;
253 TRACE("(%p %d)\n", infoPtr, iItem);
255 if (iItem >= (INT)infoPtr->uNumItem)
256 return -1;
258 if (prevItem != iItem) {
259 if (prevItem != -1)
260 TAB_GetItem(infoPtr, prevItem)->dwState &= ~TCIS_BUTTONPRESSED;
262 if (iItem >= 0)
264 TAB_GetItem(infoPtr, iItem)->dwState |= TCIS_BUTTONPRESSED;
265 infoPtr->iSelected = iItem;
266 infoPtr->uFocus = iItem;
268 else
270 infoPtr->iSelected = -1;
271 infoPtr->uFocus = -1;
274 TAB_EnsureSelectionVisible(infoPtr);
275 TAB_InvalidateTabArea(infoPtr);
278 return prevItem;
281 static LRESULT TAB_SetCurFocus (TAB_INFO *infoPtr, INT iItem)
283 TRACE("(%p %d)\n", infoPtr, iItem);
285 if (iItem < 0) {
286 infoPtr->uFocus = -1;
287 if (infoPtr->iSelected != -1) {
288 infoPtr->iSelected = -1;
289 TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGE);
290 TAB_InvalidateTabArea(infoPtr);
293 else if (iItem < infoPtr->uNumItem) {
294 if (infoPtr->dwStyle & TCS_BUTTONS) {
295 /* set focus to new item, leave selection as is */
296 if (infoPtr->uFocus != iItem) {
297 INT prev_focus = infoPtr->uFocus;
298 RECT r;
300 infoPtr->uFocus = iItem;
302 if (prev_focus != infoPtr->iSelected) {
303 if (TAB_InternalGetItemRect(infoPtr, prev_focus, &r, NULL))
304 InvalidateRect(infoPtr->hwnd, &r, FALSE);
307 if (TAB_InternalGetItemRect(infoPtr, iItem, &r, NULL))
308 InvalidateRect(infoPtr->hwnd, &r, FALSE);
310 TAB_SendSimpleNotify(infoPtr, TCN_FOCUSCHANGE);
312 } else {
313 INT oldFocus = infoPtr->uFocus;
314 if (infoPtr->iSelected != iItem || oldFocus == -1 ) {
315 infoPtr->uFocus = iItem;
316 if (oldFocus != -1) {
317 if (!TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGING)) {
318 infoPtr->iSelected = iItem;
319 TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGE);
321 else
322 infoPtr->iSelected = iItem;
323 TAB_EnsureSelectionVisible(infoPtr);
324 TAB_InvalidateTabArea(infoPtr);
329 return 0;
332 static inline LRESULT
333 TAB_SetToolTips (TAB_INFO *infoPtr, HWND hwndToolTip)
335 TRACE("%p %p\n", infoPtr, hwndToolTip);
336 infoPtr->hwndToolTip = hwndToolTip;
337 return 0;
340 static inline LRESULT
341 TAB_SetPadding (TAB_INFO *infoPtr, LPARAM lParam)
343 TRACE("(%p %d %d)\n", infoPtr, LOWORD(lParam), HIWORD(lParam));
344 infoPtr->uHItemPadding_s = LOWORD(lParam);
345 infoPtr->uVItemPadding_s = HIWORD(lParam);
347 return 0;
350 /******************************************************************************
351 * TAB_InternalGetItemRect
353 * This method will calculate the rectangle representing a given tab item in
354 * client coordinates. This method takes scrolling into account.
356 * This method returns TRUE if the item is visible in the window and FALSE
357 * if it is completely outside the client area.
359 static BOOL TAB_InternalGetItemRect(
360 const TAB_INFO* infoPtr,
361 INT itemIndex,
362 RECT* itemRect,
363 RECT* selectedRect)
365 RECT tmpItemRect,clientRect;
367 /* Perform a sanity check and a trivial visibility check. */
368 if ( (infoPtr->uNumItem <= 0) ||
369 (itemIndex >= infoPtr->uNumItem) ||
370 (!(((infoPtr->dwStyle & TCS_MULTILINE) || (infoPtr->dwStyle & TCS_VERTICAL))) &&
371 (itemIndex < infoPtr->leftmostVisible)))
373 TRACE("Not Visible\n");
374 /* need to initialize these to empty rects */
375 if (itemRect)
377 memset(itemRect,0,sizeof(RECT));
378 itemRect->bottom = infoPtr->tabHeight;
380 if (selectedRect)
381 memset(selectedRect,0,sizeof(RECT));
382 return FALSE;
386 * Avoid special cases in this procedure by assigning the "out"
387 * parameters if the caller didn't supply them
389 if (itemRect == NULL)
390 itemRect = &tmpItemRect;
392 /* Retrieve the unmodified item rect. */
393 *itemRect = TAB_GetItem(infoPtr,itemIndex)->rect;
395 /* calculate the times bottom and top based on the row */
396 GetClientRect(infoPtr->hwnd, &clientRect);
398 if ((infoPtr->dwStyle & TCS_BOTTOM) && (infoPtr->dwStyle & TCS_VERTICAL))
400 itemRect->right = clientRect.right - SELECTED_TAB_OFFSET - itemRect->left * infoPtr->tabHeight -
401 ((infoPtr->dwStyle & TCS_BUTTONS) ? itemRect->left * BUTTON_SPACINGX : 0);
402 itemRect->left = itemRect->right - infoPtr->tabHeight;
404 else if (infoPtr->dwStyle & TCS_VERTICAL)
406 itemRect->left = clientRect.left + SELECTED_TAB_OFFSET + itemRect->left * infoPtr->tabHeight +
407 ((infoPtr->dwStyle & TCS_BUTTONS) ? itemRect->left * BUTTON_SPACINGX : 0);
408 itemRect->right = itemRect->left + infoPtr->tabHeight;
410 else if (infoPtr->dwStyle & TCS_BOTTOM)
412 itemRect->bottom = clientRect.bottom - itemRect->top * infoPtr->tabHeight -
413 ((infoPtr->dwStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : SELECTED_TAB_OFFSET);
414 itemRect->top = itemRect->bottom - infoPtr->tabHeight;
416 else /* not TCS_BOTTOM and not TCS_VERTICAL */
418 itemRect->top = clientRect.top + itemRect->top * infoPtr->tabHeight +
419 ((infoPtr->dwStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : SELECTED_TAB_OFFSET);
420 itemRect->bottom = itemRect->top + infoPtr->tabHeight;
424 * "scroll" it to make sure the item at the very left of the
425 * tab control is the leftmost visible tab.
427 if(infoPtr->dwStyle & TCS_VERTICAL)
429 OffsetRect(itemRect,
431 -TAB_GetItem(infoPtr, infoPtr->leftmostVisible)->rect.top);
434 * Move the rectangle so the first item is slightly offset from
435 * the bottom of the tab control.
437 OffsetRect(itemRect,
439 SELECTED_TAB_OFFSET);
441 } else
443 OffsetRect(itemRect,
444 -TAB_GetItem(infoPtr, infoPtr->leftmostVisible)->rect.left,
448 * Move the rectangle so the first item is slightly offset from
449 * the left of the tab control.
451 OffsetRect(itemRect,
452 SELECTED_TAB_OFFSET,
455 TRACE("item %d tab h=%d, rect=(%s)\n",
456 itemIndex, infoPtr->tabHeight, wine_dbgstr_rect(itemRect));
458 /* Now, calculate the position of the item as if it were selected. */
459 if (selectedRect!=NULL)
461 CopyRect(selectedRect, itemRect);
463 /* The rectangle of a selected item is a bit wider. */
464 if(infoPtr->dwStyle & TCS_VERTICAL)
465 InflateRect(selectedRect, 0, SELECTED_TAB_OFFSET);
466 else
467 InflateRect(selectedRect, SELECTED_TAB_OFFSET, 0);
469 /* If it also a bit higher. */
470 if ((infoPtr->dwStyle & TCS_BOTTOM) && (infoPtr->dwStyle & TCS_VERTICAL))
472 selectedRect->left -= 2; /* the border is thicker on the right */
473 selectedRect->right += SELECTED_TAB_OFFSET;
475 else if (infoPtr->dwStyle & TCS_VERTICAL)
477 selectedRect->left -= SELECTED_TAB_OFFSET;
478 selectedRect->right += 1;
480 else if (infoPtr->dwStyle & TCS_BOTTOM)
482 selectedRect->bottom += SELECTED_TAB_OFFSET;
484 else /* not TCS_BOTTOM and not TCS_VERTICAL */
486 selectedRect->top -= SELECTED_TAB_OFFSET;
487 selectedRect->bottom -= 1;
491 /* Check for visibility */
492 if (infoPtr->dwStyle & TCS_VERTICAL)
493 return (itemRect->top < clientRect.bottom) && (itemRect->bottom > clientRect.top);
494 else
495 return (itemRect->left < clientRect.right) && (itemRect->right > clientRect.left);
498 static inline BOOL
499 TAB_GetItemRect(const TAB_INFO *infoPtr, INT item, RECT *rect)
501 TRACE("(%p, %d, %p)\n", infoPtr, item, rect);
502 return TAB_InternalGetItemRect(infoPtr, item, rect, NULL);
505 /******************************************************************************
506 * TAB_KeyDown
508 * This method is called to handle keyboard input
510 static LRESULT TAB_KeyDown(TAB_INFO* infoPtr, WPARAM keyCode, LPARAM lParam)
512 INT newItem = -1;
513 NMTCKEYDOWN nm;
515 /* TCN_KEYDOWN notification sent always */
516 nm.hdr.hwndFrom = infoPtr->hwnd;
517 nm.hdr.idFrom = GetWindowLongPtrW(infoPtr->hwnd, GWLP_ID);
518 nm.hdr.code = TCN_KEYDOWN;
519 nm.wVKey = keyCode;
520 nm.flags = lParam;
521 SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, nm.hdr.idFrom, (LPARAM)&nm);
523 switch (keyCode)
525 case VK_LEFT:
526 newItem = infoPtr->uFocus - 1;
527 break;
528 case VK_RIGHT:
529 newItem = infoPtr->uFocus + 1;
530 break;
533 /* If we changed to a valid item, change focused item */
534 if (newItem >= 0 && newItem < infoPtr->uNumItem && infoPtr->uFocus != newItem)
535 TAB_SetCurFocus(infoPtr, newItem);
537 return 0;
541 * WM_KILLFOCUS handler
543 static void TAB_KillFocus(TAB_INFO *infoPtr)
545 /* clear current focused item back to selected for TCS_BUTTONS */
546 if ((infoPtr->dwStyle & TCS_BUTTONS) && (infoPtr->uFocus != infoPtr->iSelected))
548 RECT r;
550 if (TAB_InternalGetItemRect(infoPtr, infoPtr->uFocus, &r, NULL))
551 InvalidateRect(infoPtr->hwnd, &r, FALSE);
553 infoPtr->uFocus = infoPtr->iSelected;
557 /******************************************************************************
558 * TAB_FocusChanging
560 * This method is called whenever the focus goes in or out of this control
561 * it is used to update the visual state of the control.
563 static void TAB_FocusChanging(const TAB_INFO *infoPtr)
565 RECT selectedRect;
566 BOOL isVisible;
569 * Get the rectangle for the item.
571 isVisible = TAB_InternalGetItemRect(infoPtr,
572 infoPtr->uFocus,
573 NULL,
574 &selectedRect);
577 * If the rectangle is not completely invisible, invalidate that
578 * portion of the window.
580 if (isVisible)
582 TRACE("invalidate (%s)\n", wine_dbgstr_rect(&selectedRect));
583 InvalidateRect(infoPtr->hwnd, &selectedRect, TRUE);
587 static INT TAB_InternalHitTest (const TAB_INFO *infoPtr, POINT pt, UINT *flags)
589 RECT rect;
590 INT iCount;
592 for (iCount = 0; iCount < infoPtr->uNumItem; iCount++)
594 TAB_InternalGetItemRect(infoPtr, iCount, &rect, NULL);
596 if (PtInRect(&rect, pt))
598 *flags = TCHT_ONITEM;
599 return iCount;
603 *flags = TCHT_NOWHERE;
604 return -1;
607 static inline LRESULT
608 TAB_HitTest (const TAB_INFO *infoPtr, LPTCHITTESTINFO lptest)
610 TRACE("(%p, %p)\n", infoPtr, lptest);
611 return TAB_InternalHitTest (infoPtr, lptest->pt, &lptest->flags);
614 /******************************************************************************
615 * TAB_NCHitTest
617 * Napster v2b5 has a tab control for its main navigation which has a client
618 * area that covers the whole area of the dialog pages.
619 * That's why it receives all msgs for that area and the underlying dialog ctrls
620 * are dead.
621 * So I decided that we should handle WM_NCHITTEST here and return
622 * HTTRANSPARENT if we don't hit the tab control buttons.
623 * FIXME: WM_NCHITTEST handling correct ? Fix it if you know that Windows
624 * doesn't do it that way. Maybe depends on tab control styles ?
626 static inline LRESULT
627 TAB_NCHitTest (const TAB_INFO *infoPtr, LPARAM lParam)
629 POINT pt;
630 UINT dummyflag;
632 pt.x = (short)LOWORD(lParam);
633 pt.y = (short)HIWORD(lParam);
634 ScreenToClient(infoPtr->hwnd, &pt);
636 if (TAB_InternalHitTest(infoPtr, pt, &dummyflag) == -1)
637 return HTTRANSPARENT;
638 else
639 return HTCLIENT;
642 static LRESULT
643 TAB_LButtonDown (TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
645 POINT pt;
646 INT newItem;
647 UINT dummy;
649 if (infoPtr->hwndToolTip)
650 TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
651 WM_LBUTTONDOWN, wParam, lParam);
653 if (!(infoPtr->dwStyle & TCS_FOCUSNEVER)) {
654 SetFocus (infoPtr->hwnd);
657 if (infoPtr->hwndToolTip)
658 TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
659 WM_LBUTTONDOWN, wParam, lParam);
661 pt.x = (short)LOWORD(lParam);
662 pt.y = (short)HIWORD(lParam);
664 newItem = TAB_InternalHitTest (infoPtr, pt, &dummy);
666 TRACE("On Tab, item %d\n", newItem);
668 if ((newItem != -1) && (infoPtr->iSelected != newItem))
670 if ((infoPtr->dwStyle & TCS_BUTTONS) && (infoPtr->dwStyle & TCS_MULTISELECT) &&
671 (wParam & MK_CONTROL))
673 RECT r;
675 /* toggle multiselection */
676 TAB_GetItem(infoPtr, newItem)->dwState ^= TCIS_BUTTONPRESSED;
677 if (TAB_InternalGetItemRect (infoPtr, newItem, &r, NULL))
678 InvalidateRect (infoPtr->hwnd, &r, TRUE);
680 else
682 INT i;
683 BOOL pressed = FALSE;
685 /* any button pressed ? */
686 for (i = 0; i < infoPtr->uNumItem; i++)
687 if ((TAB_GetItem (infoPtr, i)->dwState & TCIS_BUTTONPRESSED) &&
688 (infoPtr->iSelected != i))
690 pressed = TRUE;
691 break;
694 if (TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGING))
695 return 0;
697 if (pressed)
698 TAB_DeselectAll (infoPtr, FALSE);
699 else
700 TAB_SetCurSel(infoPtr, newItem);
702 TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGE);
706 return 0;
709 static inline LRESULT
710 TAB_LButtonUp (const TAB_INFO *infoPtr)
712 TAB_SendSimpleNotify(infoPtr, NM_CLICK);
714 return 0;
717 static inline void
718 TAB_RButtonUp (const TAB_INFO *infoPtr)
720 TAB_SendSimpleNotify(infoPtr, NM_RCLICK);
723 /******************************************************************************
724 * TAB_DrawLoneItemInterior
726 * This calls TAB_DrawItemInterior. However, TAB_DrawItemInterior is normally
727 * called by TAB_DrawItem which is normally called by TAB_Refresh which sets
728 * up the device context and font. This routine does the same setup but
729 * only calls TAB_DrawItemInterior for the single specified item.
731 static void
732 TAB_DrawLoneItemInterior(const TAB_INFO* infoPtr, int iItem)
734 HDC hdc = GetDC(infoPtr->hwnd);
735 RECT r, rC;
737 /* Clip UpDown control to not draw over it */
738 if (infoPtr->needsScrolling)
740 GetWindowRect(infoPtr->hwnd, &rC);
741 GetWindowRect(infoPtr->hwndUpDown, &r);
742 ExcludeClipRect(hdc, r.left - rC.left, r.top - rC.top, r.right - rC.left, r.bottom - rC.top);
744 TAB_DrawItemInterior(infoPtr, hdc, iItem, NULL);
745 ReleaseDC(infoPtr->hwnd, hdc);
748 /* update a tab after hottracking - invalidate it or just redraw the interior,
749 * based on whether theming is used or not */
750 static inline void hottrack_refresh(const TAB_INFO *infoPtr, int tabIndex)
752 if (tabIndex == -1) return;
754 if (GetWindowTheme (infoPtr->hwnd))
756 RECT rect;
757 TAB_InternalGetItemRect(infoPtr, tabIndex, &rect, NULL);
758 InvalidateRect (infoPtr->hwnd, &rect, FALSE);
760 else
761 TAB_DrawLoneItemInterior(infoPtr, tabIndex);
764 /******************************************************************************
765 * TAB_HotTrackTimerProc
767 * When a mouse-move event causes a tab to be highlighted (hot-tracking), a
768 * timer is setup so we can check if the mouse is moved out of our window.
769 * (We don't get an event when the mouse leaves, the mouse-move events just
770 * stop being delivered to our window and just start being delivered to
771 * another window.) This function is called when the timer triggers so
772 * we can check if the mouse has left our window. If so, we un-highlight
773 * the hot-tracked tab.
775 static void CALLBACK
776 TAB_HotTrackTimerProc
778 HWND hwnd, /* handle of window for timer messages */
779 UINT uMsg, /* WM_TIMER message */
780 UINT_PTR idEvent, /* timer identifier */
781 DWORD dwTime /* current system time */
784 TAB_INFO* infoPtr = TAB_GetInfoPtr(hwnd);
786 if (infoPtr != NULL && infoPtr->iHotTracked >= 0)
788 POINT pt;
791 ** If we can't get the cursor position, or if the cursor is outside our
792 ** window, we un-highlight the hot-tracked tab. Note that the cursor is
793 ** "outside" even if it is within our bounding rect if another window
794 ** overlaps. Note also that the case where the cursor stayed within our
795 ** window but has moved off the hot-tracked tab will be handled by the
796 ** WM_MOUSEMOVE event.
798 if (!GetCursorPos(&pt) || WindowFromPoint(pt) != hwnd)
800 /* Redraw iHotTracked to look normal */
801 INT iRedraw = infoPtr->iHotTracked;
802 infoPtr->iHotTracked = -1;
803 hottrack_refresh (infoPtr, iRedraw);
805 /* Kill this timer */
806 KillTimer(hwnd, TAB_HOTTRACK_TIMER);
811 /******************************************************************************
812 * TAB_RecalcHotTrack
814 * If a tab control has the TCS_HOTTRACK style, then the tab under the mouse
815 * should be highlighted. This function determines which tab in a tab control,
816 * if any, is under the mouse and records that information. The caller may
817 * supply output parameters to receive the item number of the tab item which
818 * was highlighted but isn't any longer and of the tab item which is now
819 * highlighted but wasn't previously. The caller can use this information to
820 * selectively redraw those tab items.
822 * If the caller has a mouse position, it can supply it through the pos
823 * parameter. For example, TAB_MouseMove does this. Otherwise, the caller
824 * supplies NULL and this function determines the current mouse position
825 * itself.
827 static void
828 TAB_RecalcHotTrack
830 TAB_INFO* infoPtr,
831 const LPARAM* pos,
832 int* out_redrawLeave,
833 int* out_redrawEnter
836 int item = -1;
839 if (out_redrawLeave != NULL)
840 *out_redrawLeave = -1;
841 if (out_redrawEnter != NULL)
842 *out_redrawEnter = -1;
844 if ((infoPtr->dwStyle & TCS_HOTTRACK) || GetWindowTheme(infoPtr->hwnd))
846 POINT pt;
847 UINT flags;
849 if (pos == NULL)
851 GetCursorPos(&pt);
852 ScreenToClient(infoPtr->hwnd, &pt);
854 else
856 pt.x = (short)LOWORD(*pos);
857 pt.y = (short)HIWORD(*pos);
860 item = TAB_InternalHitTest(infoPtr, pt, &flags);
863 if (item != infoPtr->iHotTracked)
865 if (infoPtr->iHotTracked >= 0)
867 /* Mark currently hot-tracked to be redrawn to look normal */
868 if (out_redrawLeave != NULL)
869 *out_redrawLeave = infoPtr->iHotTracked;
871 if (item < 0)
873 /* Kill timer which forces recheck of mouse pos */
874 KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
877 else
879 /* Start timer so we recheck mouse pos */
880 UINT timerID = SetTimer
882 infoPtr->hwnd,
883 TAB_HOTTRACK_TIMER,
884 TAB_HOTTRACK_TIMER_INTERVAL,
885 TAB_HotTrackTimerProc
888 if (timerID == 0)
889 return; /* Hot tracking not available */
892 infoPtr->iHotTracked = item;
894 if (item >= 0)
896 /* Mark new hot-tracked to be redrawn to look highlighted */
897 if (out_redrawEnter != NULL)
898 *out_redrawEnter = item;
903 /******************************************************************************
904 * TAB_MouseMove
906 * Handles the mouse-move event. Updates tooltips. Updates hot-tracking.
908 static LRESULT
909 TAB_MouseMove (TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
911 int redrawLeave;
912 int redrawEnter;
914 if (infoPtr->hwndToolTip)
915 TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
916 WM_LBUTTONDOWN, wParam, lParam);
918 /* Determine which tab to highlight. Redraw tabs which change highlight
919 ** status. */
920 TAB_RecalcHotTrack(infoPtr, &lParam, &redrawLeave, &redrawEnter);
922 hottrack_refresh (infoPtr, redrawLeave);
923 hottrack_refresh (infoPtr, redrawEnter);
925 return 0;
928 /******************************************************************************
929 * TAB_AdjustRect
931 * Calculates the tab control's display area given the window rectangle or
932 * the window rectangle given the requested display rectangle.
934 static LRESULT TAB_AdjustRect(const TAB_INFO *infoPtr, WPARAM fLarger, LPRECT prc)
936 LONG *iRightBottom, *iLeftTop;
938 TRACE ("hwnd=%p fLarger=%ld (%s)\n", infoPtr->hwnd, fLarger,
939 wine_dbgstr_rect(prc));
941 if (!prc) return -1;
943 if(infoPtr->dwStyle & TCS_VERTICAL)
945 iRightBottom = &(prc->right);
946 iLeftTop = &(prc->left);
948 else
950 iRightBottom = &(prc->bottom);
951 iLeftTop = &(prc->top);
954 if (fLarger) /* Go from display rectangle */
956 /* Add the height of the tabs. */
957 if (infoPtr->dwStyle & TCS_BOTTOM)
958 *iRightBottom += infoPtr->tabHeight * infoPtr->uNumRows;
959 else
960 *iLeftTop -= infoPtr->tabHeight * infoPtr->uNumRows +
961 ((infoPtr->dwStyle & TCS_BUTTONS)? 3 * (infoPtr->uNumRows - 1) : 0);
963 /* Inflate the rectangle for the padding */
964 InflateRect(prc, DISPLAY_AREA_PADDINGX, DISPLAY_AREA_PADDINGY);
966 /* Inflate for the border */
967 InflateRect(prc, CONTROL_BORDER_SIZEX, CONTROL_BORDER_SIZEY);
969 else /* Go from window rectangle. */
971 /* Deflate the rectangle for the border */
972 InflateRect(prc, -CONTROL_BORDER_SIZEX, -CONTROL_BORDER_SIZEY);
974 /* Deflate the rectangle for the padding */
975 InflateRect(prc, -DISPLAY_AREA_PADDINGX, -DISPLAY_AREA_PADDINGY);
977 /* Remove the height of the tabs. */
978 if (infoPtr->dwStyle & TCS_BOTTOM)
979 *iRightBottom -= infoPtr->tabHeight * infoPtr->uNumRows;
980 else
981 *iLeftTop += (infoPtr->tabHeight) * infoPtr->uNumRows +
982 ((infoPtr->dwStyle & TCS_BUTTONS)? 3 * (infoPtr->uNumRows - 1) : 0);
985 return 0;
988 /******************************************************************************
989 * TAB_OnHScroll
991 * This method will handle the notification from the scroll control and
992 * perform the scrolling operation on the tab control.
994 static LRESULT TAB_OnHScroll(TAB_INFO *infoPtr, int nScrollCode, int nPos)
996 if(nScrollCode == SB_THUMBPOSITION && nPos != infoPtr->leftmostVisible)
998 if(nPos < infoPtr->leftmostVisible)
999 infoPtr->leftmostVisible--;
1000 else
1001 infoPtr->leftmostVisible++;
1003 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
1004 TAB_InvalidateTabArea(infoPtr);
1005 SendMessageW(infoPtr->hwndUpDown, UDM_SETPOS, 0,
1006 MAKELONG(infoPtr->leftmostVisible, 0));
1009 return 0;
1012 /******************************************************************************
1013 * TAB_SetupScrolling
1015 * This method will check the current scrolling state and make sure the
1016 * scrolling control is displayed (or not).
1018 static void TAB_SetupScrolling(
1019 TAB_INFO* infoPtr,
1020 const RECT* clientRect)
1022 static const WCHAR emptyW[] = { 0 };
1023 INT maxRange = 0;
1025 if (infoPtr->needsScrolling)
1027 RECT controlPos;
1028 INT vsize, tabwidth;
1031 * Calculate the position of the scroll control.
1033 controlPos.right = clientRect->right;
1034 controlPos.left = controlPos.right - 2 * GetSystemMetrics(SM_CXHSCROLL);
1036 if (infoPtr->dwStyle & TCS_BOTTOM)
1038 controlPos.top = clientRect->bottom - infoPtr->tabHeight;
1039 controlPos.bottom = controlPos.top + GetSystemMetrics(SM_CYHSCROLL);
1041 else
1043 controlPos.bottom = clientRect->top + infoPtr->tabHeight;
1044 controlPos.top = controlPos.bottom - GetSystemMetrics(SM_CYHSCROLL);
1048 * If we don't have a scroll control yet, we want to create one.
1049 * If we have one, we want to make sure it's positioned properly.
1051 if (infoPtr->hwndUpDown==0)
1053 infoPtr->hwndUpDown = CreateWindowW(UPDOWN_CLASSW, emptyW,
1054 WS_VISIBLE | WS_CHILD | UDS_HORZ,
1055 controlPos.left, controlPos.top,
1056 controlPos.right - controlPos.left,
1057 controlPos.bottom - controlPos.top,
1058 infoPtr->hwnd, NULL, NULL, NULL);
1060 else
1062 SetWindowPos(infoPtr->hwndUpDown,
1063 NULL,
1064 controlPos.left, controlPos.top,
1065 controlPos.right - controlPos.left,
1066 controlPos.bottom - controlPos.top,
1067 SWP_SHOWWINDOW | SWP_NOZORDER);
1070 /* Now calculate upper limit of the updown control range.
1071 * We do this by calculating how many tabs will be offscreen when the
1072 * last tab is visible.
1074 if(infoPtr->uNumItem)
1076 vsize = clientRect->right - (controlPos.right - controlPos.left + 1);
1077 maxRange = infoPtr->uNumItem;
1078 tabwidth = TAB_GetItem(infoPtr, infoPtr->uNumItem - 1)->rect.right;
1080 for(; maxRange > 0; maxRange--)
1082 if(tabwidth - TAB_GetItem(infoPtr,maxRange - 1)->rect.left > vsize)
1083 break;
1086 if(maxRange == infoPtr->uNumItem)
1087 maxRange--;
1090 else
1092 /* If we once had a scroll control... hide it */
1093 if (infoPtr->hwndUpDown)
1094 ShowWindow(infoPtr->hwndUpDown, SW_HIDE);
1096 if (infoPtr->hwndUpDown)
1097 SendMessageW(infoPtr->hwndUpDown, UDM_SETRANGE32, 0, maxRange);
1100 /******************************************************************************
1101 * TAB_SetItemBounds
1103 * This method will calculate the position rectangles of all the items in the
1104 * control. The rectangle calculated starts at 0 for the first item in the
1105 * list and ignores scrolling and selection.
1106 * It also uses the current font to determine the height of the tab row and
1107 * it checks if all the tabs fit in the client area of the window. If they
1108 * don't, a scrolling control is added.
1110 static void TAB_SetItemBounds (TAB_INFO *infoPtr)
1112 TEXTMETRICW fontMetrics;
1113 UINT curItem;
1114 INT curItemLeftPos;
1115 INT curItemRowCount;
1116 HFONT hFont, hOldFont;
1117 HDC hdc;
1118 RECT clientRect;
1119 INT iTemp;
1120 RECT* rcItem;
1121 INT iIndex;
1122 INT icon_width = 0;
1125 * We need to get text information so we need a DC and we need to select
1126 * a font.
1128 hdc = GetDC(infoPtr->hwnd);
1130 hFont = infoPtr->hFont ? infoPtr->hFont : GetStockObject (SYSTEM_FONT);
1131 hOldFont = SelectObject (hdc, hFont);
1134 * We will base the rectangle calculations on the client rectangle
1135 * of the control.
1137 GetClientRect(infoPtr->hwnd, &clientRect);
1139 /* if TCS_VERTICAL then swap the height and width so this code places the
1140 tabs along the top of the rectangle and we can just rotate them after
1141 rather than duplicate all of the below code */
1142 if(infoPtr->dwStyle & TCS_VERTICAL)
1144 iTemp = clientRect.bottom;
1145 clientRect.bottom = clientRect.right;
1146 clientRect.right = iTemp;
1149 /* Now use hPadding and vPadding */
1150 infoPtr->uHItemPadding = infoPtr->uHItemPadding_s;
1151 infoPtr->uVItemPadding = infoPtr->uVItemPadding_s;
1153 /* The leftmost item will be "0" aligned */
1154 curItemLeftPos = 0;
1155 curItemRowCount = infoPtr->uNumItem ? 1 : 0;
1157 if (!(infoPtr->fHeightSet))
1159 int item_height;
1160 INT icon_height = 0, cx;
1162 /* Use the current font to determine the height of a tab. */
1163 GetTextMetricsW(hdc, &fontMetrics);
1165 /* Get the icon height */
1166 if (infoPtr->himl)
1167 ImageList_GetIconSize(infoPtr->himl, &cx, &icon_height);
1169 /* Take the highest between font or icon */
1170 if (fontMetrics.tmHeight > icon_height)
1171 item_height = fontMetrics.tmHeight + 2;
1172 else
1173 item_height = icon_height;
1176 * Make sure there is enough space for the letters + icon + growing the
1177 * selected item + extra space for the selected item.
1179 infoPtr->tabHeight = item_height +
1180 ((infoPtr->dwStyle & TCS_BUTTONS) ? 2 : 1) *
1181 infoPtr->uVItemPadding;
1183 TRACE("tabH=%d, tmH=%d, iconh=%d\n",
1184 infoPtr->tabHeight, fontMetrics.tmHeight, icon_height);
1187 TRACE("client right=%d\n", clientRect.right);
1189 /* Get the icon width */
1190 if (infoPtr->himl)
1192 INT cy;
1194 ImageList_GetIconSize(infoPtr->himl, &icon_width, &cy);
1196 if (infoPtr->dwStyle & TCS_FIXEDWIDTH)
1197 icon_width += 4;
1198 else
1199 /* Add padding if icon is present */
1200 icon_width += infoPtr->uHItemPadding;
1203 for (curItem = 0; curItem < infoPtr->uNumItem; curItem++)
1205 TAB_ITEM *curr = TAB_GetItem(infoPtr, curItem);
1207 /* Set the leftmost position of the tab. */
1208 curr->rect.left = curItemLeftPos;
1210 if (infoPtr->dwStyle & TCS_FIXEDWIDTH)
1212 curr->rect.right = curr->rect.left +
1213 max(infoPtr->tabWidth, icon_width);
1215 else if (!curr->pszText)
1217 /* If no text use minimum tab width including padding. */
1218 if (infoPtr->tabMinWidth < 0)
1219 curr->rect.right = curr->rect.left + GET_DEFAULT_MIN_TAB_WIDTH(infoPtr);
1220 else
1222 curr->rect.right = curr->rect.left + infoPtr->tabMinWidth;
1224 /* Add extra padding if icon is present */
1225 if (infoPtr->himl && infoPtr->tabMinWidth > 0 && infoPtr->tabMinWidth < DEFAULT_MIN_TAB_WIDTH
1226 && infoPtr->uHItemPadding > 1)
1227 curr->rect.right += EXTRA_ICON_PADDING * (infoPtr->uHItemPadding-1);
1230 else
1232 int tabwidth;
1233 SIZE size;
1234 /* Calculate how wide the tab is depending on the text it contains */
1235 GetTextExtentPoint32W(hdc, curr->pszText,
1236 lstrlenW(curr->pszText), &size);
1238 tabwidth = size.cx + icon_width + 2 * infoPtr->uHItemPadding;
1240 if (infoPtr->tabMinWidth < 0)
1241 tabwidth = max(tabwidth, GET_DEFAULT_MIN_TAB_WIDTH(infoPtr));
1242 else
1243 tabwidth = max(tabwidth, infoPtr->tabMinWidth);
1245 curr->rect.right = curr->rect.left + tabwidth;
1246 TRACE("for <%s>, l,r=%d,%d\n",
1247 debugstr_w(curr->pszText), curr->rect.left, curr->rect.right);
1251 * Check if this is a multiline tab control and if so
1252 * check to see if we should wrap the tabs
1254 * Wrap all these tabs. We will arrange them evenly later.
1258 if (((infoPtr->dwStyle & TCS_MULTILINE) || (infoPtr->dwStyle & TCS_VERTICAL)) &&
1259 (curr->rect.right >
1260 (clientRect.right - CONTROL_BORDER_SIZEX - DISPLAY_AREA_PADDINGX)))
1262 curr->rect.right -= curr->rect.left;
1264 curr->rect.left = 0;
1265 curItemRowCount++;
1266 TRACE("wrapping <%s>, l,r=%d,%d\n", debugstr_w(curr->pszText),
1267 curr->rect.left, curr->rect.right);
1270 curr->rect.bottom = 0;
1271 curr->rect.top = curItemRowCount - 1;
1273 TRACE("Rect: %s\n", wine_dbgstr_rect(&curr->rect));
1276 * The leftmost position of the next item is the rightmost position
1277 * of this one.
1279 if (infoPtr->dwStyle & TCS_BUTTONS)
1281 curItemLeftPos = curr->rect.right + BUTTON_SPACINGX;
1282 if (infoPtr->dwStyle & TCS_FLATBUTTONS)
1283 curItemLeftPos += FLAT_BTN_SPACINGX;
1285 else
1286 curItemLeftPos = curr->rect.right;
1289 if (!((infoPtr->dwStyle & TCS_MULTILINE) || (infoPtr->dwStyle & TCS_VERTICAL)))
1292 * Check if we need a scrolling control.
1294 infoPtr->needsScrolling = (curItemLeftPos + (2 * SELECTED_TAB_OFFSET) >
1295 clientRect.right);
1297 /* Don't need scrolling, then update infoPtr->leftmostVisible */
1298 if(!infoPtr->needsScrolling)
1299 infoPtr->leftmostVisible = 0;
1301 else
1304 * No scrolling in Multiline or Vertical styles.
1306 infoPtr->needsScrolling = FALSE;
1307 infoPtr->leftmostVisible = 0;
1309 TAB_SetupScrolling(infoPtr, &clientRect);
1311 /* Set the number of rows */
1312 infoPtr->uNumRows = curItemRowCount;
1314 /* Arrange all tabs evenly if style says so */
1315 if (!(infoPtr->dwStyle & TCS_RAGGEDRIGHT) &&
1316 ((infoPtr->dwStyle & TCS_MULTILINE) || (infoPtr->dwStyle & TCS_VERTICAL)) &&
1317 (infoPtr->uNumItem > 0) &&
1318 (infoPtr->uNumRows > 1))
1320 INT tabPerRow,remTab,iRow;
1321 UINT iItm;
1322 INT iCount=0;
1325 * Ok windows tries to even out the rows. place the same
1326 * number of tabs in each row. So lets give that a shot
1329 tabPerRow = infoPtr->uNumItem / (infoPtr->uNumRows);
1330 remTab = infoPtr->uNumItem % (infoPtr->uNumRows);
1332 for (iItm=0,iRow=0,iCount=0,curItemLeftPos=0;
1333 iItm<infoPtr->uNumItem;
1334 iItm++,iCount++)
1336 /* normalize the current rect */
1337 TAB_ITEM *curr = TAB_GetItem(infoPtr, iItm);
1339 /* shift the item to the left side of the clientRect */
1340 curr->rect.right -= curr->rect.left;
1341 curr->rect.left = 0;
1343 TRACE("r=%d, cl=%d, cl.r=%d, iCount=%d, iRow=%d, uNumRows=%d, remTab=%d, tabPerRow=%d\n",
1344 curr->rect.right, curItemLeftPos, clientRect.right,
1345 iCount, iRow, infoPtr->uNumRows, remTab, tabPerRow);
1347 /* if we have reached the maximum number of tabs on this row */
1348 /* move to the next row, reset our current item left position and */
1349 /* the count of items on this row */
1351 if (infoPtr->dwStyle & TCS_VERTICAL) {
1352 /* Vert: Add the remaining tabs in the *last* remainder rows */
1353 if (iCount >= ((iRow>=(INT)infoPtr->uNumRows - remTab)?tabPerRow + 1:tabPerRow)) {
1354 iRow++;
1355 curItemLeftPos = 0;
1356 iCount = 0;
1358 } else {
1359 /* Horz: Add the remaining tabs in the *first* remainder rows */
1360 if (iCount >= ((iRow<remTab)?tabPerRow + 1:tabPerRow)) {
1361 iRow++;
1362 curItemLeftPos = 0;
1363 iCount = 0;
1367 /* shift the item to the right to place it as the next item in this row */
1368 curr->rect.left += curItemLeftPos;
1369 curr->rect.right += curItemLeftPos;
1370 curr->rect.top = iRow;
1371 if (infoPtr->dwStyle & TCS_BUTTONS)
1373 curItemLeftPos = curr->rect.right + 1;
1374 if (infoPtr->dwStyle & TCS_FLATBUTTONS)
1375 curItemLeftPos += FLAT_BTN_SPACINGX;
1377 else
1378 curItemLeftPos = curr->rect.right;
1380 TRACE("arranging <%s>, l,r=%d,%d, row=%d\n",
1381 debugstr_w(curr->pszText), curr->rect.left,
1382 curr->rect.right, curr->rect.top);
1386 * Justify the rows
1389 INT widthDiff, iIndexStart=0, iIndexEnd=0;
1390 INT remainder;
1391 INT iCount=0;
1393 while(iIndexStart < infoPtr->uNumItem)
1395 TAB_ITEM *start = TAB_GetItem(infoPtr, iIndexStart);
1398 * find the index of the row
1400 /* find the first item on the next row */
1401 for (iIndexEnd=iIndexStart;
1402 (iIndexEnd < infoPtr->uNumItem) &&
1403 (TAB_GetItem(infoPtr, iIndexEnd)->rect.top ==
1404 start->rect.top) ;
1405 iIndexEnd++)
1406 /* intentionally blank */;
1409 * we need to justify these tabs so they fill the whole given
1410 * client area
1413 /* find the amount of space remaining on this row */
1414 widthDiff = clientRect.right - (2 * SELECTED_TAB_OFFSET) -
1415 TAB_GetItem(infoPtr, iIndexEnd - 1)->rect.right;
1417 /* iCount is the number of tab items on this row */
1418 iCount = iIndexEnd - iIndexStart;
1420 if (iCount > 1)
1422 remainder = widthDiff % iCount;
1423 widthDiff = widthDiff / iCount;
1424 /* add widthDiff/iCount, or extra space/items on row, to each item on this row */
1425 for (iIndex=iIndexStart, iCount=0; iIndex < iIndexEnd; iIndex++, iCount++)
1427 TAB_ITEM *item = TAB_GetItem(infoPtr, iIndex);
1429 item->rect.left += iCount * widthDiff;
1430 item->rect.right += (iCount + 1) * widthDiff;
1432 TRACE("adjusting 1 <%s>, l,r=%d,%d\n",
1433 debugstr_w(item->pszText),
1434 item->rect.left, item->rect.right);
1437 TAB_GetItem(infoPtr, iIndex - 1)->rect.right += remainder;
1439 else /* we have only one item on this row, make it take up the entire row */
1441 start->rect.left = clientRect.left;
1442 start->rect.right = clientRect.right - 4;
1444 TRACE("adjusting 2 <%s>, l,r=%d,%d\n",
1445 debugstr_w(start->pszText),
1446 start->rect.left, start->rect.right);
1451 iIndexStart = iIndexEnd;
1456 /* if TCS_VERTICAL rotate the tabs so they are along the side of the clientRect */
1457 if(infoPtr->dwStyle & TCS_VERTICAL)
1459 RECT rcOriginal;
1460 for(iIndex = 0; iIndex < infoPtr->uNumItem; iIndex++)
1462 rcItem = &TAB_GetItem(infoPtr, iIndex)->rect;
1464 rcOriginal = *rcItem;
1466 /* this is rotating the items by 90 degrees clockwise around the center of the control */
1467 rcItem->top = (rcOriginal.left - clientRect.left);
1468 rcItem->bottom = rcItem->top + (rcOriginal.right - rcOriginal.left);
1469 rcItem->left = rcOriginal.top;
1470 rcItem->right = rcOriginal.bottom;
1474 TAB_EnsureSelectionVisible(infoPtr);
1475 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
1477 /* Cleanup */
1478 SelectObject (hdc, hOldFont);
1479 ReleaseDC (infoPtr->hwnd, hdc);
1483 static void
1484 TAB_EraseTabInterior(const TAB_INFO *infoPtr, HDC hdc, INT iItem, const RECT *drawRect)
1486 HBRUSH hbr = CreateSolidBrush (comctl32_color.clrBtnFace);
1487 BOOL deleteBrush = TRUE;
1488 RECT rTemp = *drawRect;
1490 if (infoPtr->dwStyle & TCS_BUTTONS)
1492 if (iItem == infoPtr->iSelected)
1494 /* Background color */
1495 if (!(infoPtr->dwStyle & TCS_OWNERDRAWFIXED))
1497 DeleteObject(hbr);
1498 hbr = GetSysColorBrush(COLOR_SCROLLBAR);
1500 SetTextColor(hdc, comctl32_color.clr3dFace);
1501 SetBkColor(hdc, comctl32_color.clr3dHilight);
1503 /* if COLOR_WINDOW happens to be the same as COLOR_3DHILIGHT
1504 * we better use 0x55aa bitmap brush to make scrollbar's background
1505 * look different from the window background.
1507 if (comctl32_color.clr3dHilight == comctl32_color.clrWindow)
1508 hbr = COMCTL32_hPattern55AABrush;
1510 deleteBrush = FALSE;
1512 FillRect(hdc, &rTemp, hbr);
1514 else /* ! selected */
1516 if (infoPtr->dwStyle & TCS_FLATBUTTONS)
1518 InflateRect(&rTemp, 2, 2);
1519 FillRect(hdc, &rTemp, hbr);
1520 if (iItem == infoPtr->iHotTracked ||
1521 (iItem != infoPtr->iSelected && iItem == infoPtr->uFocus))
1522 DrawEdge(hdc, &rTemp, BDR_RAISEDINNER, BF_RECT);
1524 else
1525 FillRect(hdc, &rTemp, hbr);
1529 else /* !TCS_BUTTONS */
1531 InflateRect(&rTemp, -2, -2);
1532 if (!GetWindowTheme (infoPtr->hwnd))
1533 FillRect(hdc, &rTemp, hbr);
1536 /* highlighting is drawn on top of previous fills */
1537 if (TAB_GetItem(infoPtr, iItem)->dwState & TCIS_HIGHLIGHTED)
1539 if (deleteBrush)
1541 DeleteObject(hbr);
1542 deleteBrush = FALSE;
1544 hbr = GetSysColorBrush(COLOR_HIGHLIGHT);
1545 FillRect(hdc, &rTemp, hbr);
1548 /* Cleanup */
1549 if (deleteBrush) DeleteObject(hbr);
1552 /******************************************************************************
1553 * TAB_DrawItemInterior
1555 * This method is used to draw the interior (text and icon) of a single tab
1556 * into the tab control.
1558 static void
1559 TAB_DrawItemInterior(const TAB_INFO *infoPtr, HDC hdc, INT iItem, RECT *drawRect)
1561 RECT localRect;
1563 HPEN htextPen;
1564 HPEN holdPen;
1565 INT oldBkMode;
1566 HFONT hOldFont;
1568 /* if (drawRect == NULL) */
1570 BOOL isVisible;
1571 RECT itemRect;
1572 RECT selectedRect;
1575 * Get the rectangle for the item.
1577 isVisible = TAB_InternalGetItemRect(infoPtr, iItem, &itemRect, &selectedRect);
1578 if (!isVisible)
1579 return;
1582 * Make sure drawRect points to something valid; simplifies code.
1584 drawRect = &localRect;
1587 * This logic copied from the part of TAB_DrawItem which draws
1588 * the tab background. It's important to keep it in sync. I
1589 * would have liked to avoid code duplication, but couldn't figure
1590 * out how without making spaghetti of TAB_DrawItem.
1592 if (iItem == infoPtr->iSelected)
1593 *drawRect = selectedRect;
1594 else
1595 *drawRect = itemRect;
1597 if (infoPtr->dwStyle & TCS_BUTTONS)
1599 if (iItem == infoPtr->iSelected)
1601 drawRect->left += 4;
1602 drawRect->top += 4;
1603 drawRect->right -= 4;
1605 if (infoPtr->dwStyle & TCS_VERTICAL)
1607 if (!(infoPtr->dwStyle & TCS_BOTTOM)) drawRect->right += 1;
1608 drawRect->bottom -= 4;
1610 else
1612 if (infoPtr->dwStyle & TCS_BOTTOM)
1614 drawRect->top -= 2;
1615 drawRect->bottom -= 4;
1617 else
1618 drawRect->bottom -= 1;
1621 else
1623 drawRect->left += 2;
1624 drawRect->top += 2;
1625 drawRect->right -= 2;
1626 drawRect->bottom -= 2;
1629 else
1631 if ((infoPtr->dwStyle & TCS_VERTICAL) && (infoPtr->dwStyle & TCS_BOTTOM))
1633 if (iItem != infoPtr->iSelected)
1635 drawRect->left += 2;
1636 drawRect->top += 2;
1637 drawRect->bottom -= 2;
1640 else if (infoPtr->dwStyle & TCS_VERTICAL)
1642 if (iItem == infoPtr->iSelected)
1644 drawRect->right += 1;
1646 else
1648 drawRect->top += 2;
1649 drawRect->right -= 2;
1650 drawRect->bottom -= 2;
1653 else if (infoPtr->dwStyle & TCS_BOTTOM)
1655 if (iItem == infoPtr->iSelected)
1657 drawRect->top -= 2;
1659 else
1661 InflateRect(drawRect, -2, -2);
1662 drawRect->bottom += 2;
1665 else
1667 if (iItem == infoPtr->iSelected)
1669 drawRect->bottom += 3;
1671 else
1673 drawRect->bottom -= 2;
1674 InflateRect(drawRect, -2, 0);
1679 TRACE("drawRect=(%s)\n", wine_dbgstr_rect(drawRect));
1681 /* Clear interior */
1682 TAB_EraseTabInterior (infoPtr, hdc, iItem, drawRect);
1684 /* Draw the focus rectangle */
1685 if (!(infoPtr->dwStyle & TCS_FOCUSNEVER) &&
1686 (GetFocus() == infoPtr->hwnd) &&
1687 (iItem == infoPtr->uFocus) )
1689 RECT rFocus = *drawRect;
1691 if (!(infoPtr->dwStyle & TCS_BUTTONS)) InflateRect(&rFocus, -3, -3);
1692 if (infoPtr->dwStyle & TCS_BOTTOM && !(infoPtr->dwStyle & TCS_VERTICAL))
1693 rFocus.top -= 3;
1695 /* focus should stay on selected item for TCS_BUTTONS style */
1696 if (!((infoPtr->dwStyle & TCS_BUTTONS) && (infoPtr->iSelected != iItem)))
1697 DrawFocusRect(hdc, &rFocus);
1701 * Text pen
1703 htextPen = CreatePen( PS_SOLID, 1, comctl32_color.clrBtnText );
1704 holdPen = SelectObject(hdc, htextPen);
1705 hOldFont = SelectObject(hdc, infoPtr->hFont);
1708 * Setup for text output
1710 oldBkMode = SetBkMode(hdc, TRANSPARENT);
1711 if (!GetWindowTheme (infoPtr->hwnd) || (infoPtr->dwStyle & TCS_BUTTONS))
1713 if ((infoPtr->dwStyle & TCS_HOTTRACK) && (iItem == infoPtr->iHotTracked) &&
1714 !(infoPtr->dwStyle & TCS_FLATBUTTONS))
1715 SetTextColor(hdc, comctl32_color.clrHighlight);
1716 else if (TAB_GetItem(infoPtr, iItem)->dwState & TCIS_HIGHLIGHTED)
1717 SetTextColor(hdc, comctl32_color.clrHighlightText);
1718 else
1719 SetTextColor(hdc, comctl32_color.clrBtnText);
1723 * if owner draw, tell the owner to draw
1725 if ((infoPtr->dwStyle & TCS_OWNERDRAWFIXED) && IsWindow(infoPtr->hwndNotify))
1727 DRAWITEMSTRUCT dis;
1728 UINT id;
1730 drawRect->top += 2;
1731 drawRect->right -= 1;
1732 if ( iItem == infoPtr->iSelected )
1734 drawRect->right -= 1;
1735 drawRect->left += 1;
1738 id = (UINT)GetWindowLongPtrW( infoPtr->hwnd, GWLP_ID );
1740 /* fill DRAWITEMSTRUCT */
1741 dis.CtlType = ODT_TAB;
1742 dis.CtlID = id;
1743 dis.itemID = iItem;
1744 dis.itemAction = ODA_DRAWENTIRE;
1745 dis.itemState = 0;
1746 if ( iItem == infoPtr->iSelected )
1747 dis.itemState |= ODS_SELECTED;
1748 if (infoPtr->uFocus == iItem)
1749 dis.itemState |= ODS_FOCUS;
1750 dis.hwndItem = infoPtr->hwnd;
1751 dis.hDC = hdc;
1752 CopyRect(&dis.rcItem,drawRect);
1754 /* when extra data fits ULONG_PTR, store it directly */
1755 if (infoPtr->cbInfo > sizeof(LPARAM))
1756 dis.itemData = (ULONG_PTR) TAB_GetItem(infoPtr, iItem)->extra;
1757 else
1759 /* this could be considered broken on 64 bit, but that's how it works -
1760 only first 4 bytes are copied */
1761 dis.itemData = 0;
1762 memcpy(&dis.itemData, (ULONG_PTR*)TAB_GetItem(infoPtr, iItem)->extra, 4);
1765 /* draw notification */
1766 SendMessageW( infoPtr->hwndNotify, WM_DRAWITEM, id, (LPARAM)&dis );
1768 else
1770 TAB_ITEM *item = TAB_GetItem(infoPtr, iItem);
1771 RECT rcTemp;
1772 RECT rcImage;
1774 /* used to center the icon and text in the tab */
1775 RECT rcText;
1776 INT center_offset_h, center_offset_v;
1778 /* set rcImage to drawRect, we will use top & left in our ImageList_Draw call */
1779 rcImage = *drawRect;
1781 rcTemp = *drawRect;
1783 rcText.left = rcText.top = rcText.right = rcText.bottom = 0;
1785 /* get the rectangle that the text fits in */
1786 if (item->pszText)
1788 DrawTextW(hdc, item->pszText, -1, &rcText, DT_CALCRECT);
1791 * If not owner draw, then do the drawing ourselves.
1793 * Draw the icon.
1795 if (infoPtr->himl && item->iImage != -1)
1797 INT cx;
1798 INT cy;
1800 ImageList_GetIconSize(infoPtr->himl, &cx, &cy);
1802 if(infoPtr->dwStyle & TCS_VERTICAL)
1804 center_offset_h = ((drawRect->bottom - drawRect->top) - (cy + infoPtr->uHItemPadding + (rcText.right - rcText.left))) / 2;
1805 center_offset_v = ((drawRect->right - drawRect->left) - cx) / 2;
1807 else
1809 center_offset_h = ((drawRect->right - drawRect->left) - (cx + infoPtr->uHItemPadding + (rcText.right - rcText.left))) / 2;
1810 center_offset_v = ((drawRect->bottom - drawRect->top) - cy) / 2;
1813 /* if an item is selected, the icon is shifted up instead of down */
1814 if (iItem == infoPtr->iSelected)
1815 center_offset_v -= infoPtr->uVItemPadding / 2;
1816 else
1817 center_offset_v += infoPtr->uVItemPadding / 2;
1819 if (infoPtr->dwStyle & TCS_FIXEDWIDTH && infoPtr->dwStyle & (TCS_FORCELABELLEFT | TCS_FORCEICONLEFT))
1820 center_offset_h = infoPtr->uHItemPadding;
1822 if (center_offset_h < 2)
1823 center_offset_h = 2;
1825 if (center_offset_v < 0)
1826 center_offset_v = 0;
1828 TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%s), textlen=%d\n",
1829 debugstr_w(item->pszText), center_offset_h, center_offset_v,
1830 wine_dbgstr_rect(drawRect), (rcText.right-rcText.left));
1832 if((infoPtr->dwStyle & TCS_VERTICAL) && (infoPtr->dwStyle & TCS_BOTTOM))
1834 rcImage.top = drawRect->top + center_offset_h;
1835 /* if tab is TCS_VERTICAL and TCS_BOTTOM, the text is drawn from the */
1836 /* right side of the tab, but the image still uses the left as its x position */
1837 /* this keeps the image always drawn off of the same side of the tab */
1838 rcImage.left = drawRect->right - cx - center_offset_v;
1839 drawRect->top += cy + infoPtr->uHItemPadding;
1841 else if(infoPtr->dwStyle & TCS_VERTICAL)
1843 rcImage.top = drawRect->bottom - cy - center_offset_h;
1844 rcImage.left = drawRect->left + center_offset_v;
1845 drawRect->bottom -= cy + infoPtr->uHItemPadding;
1847 else /* normal style, whether TCS_BOTTOM or not */
1849 rcImage.left = drawRect->left + center_offset_h;
1850 rcImage.top = drawRect->top + center_offset_v;
1851 drawRect->left += cx + infoPtr->uHItemPadding;
1854 TRACE("drawing image=%d, left=%d, top=%d\n",
1855 item->iImage, rcImage.left, rcImage.top-1);
1856 ImageList_Draw
1858 infoPtr->himl,
1859 item->iImage,
1860 hdc,
1861 rcImage.left,
1862 rcImage.top,
1863 ILD_NORMAL
1867 /* Now position text */
1868 if (infoPtr->dwStyle & TCS_FIXEDWIDTH && infoPtr->dwStyle & TCS_FORCELABELLEFT)
1869 center_offset_h = infoPtr->uHItemPadding;
1870 else
1871 if(infoPtr->dwStyle & TCS_VERTICAL)
1872 center_offset_h = ((drawRect->bottom - drawRect->top) - (rcText.right - rcText.left)) / 2;
1873 else
1874 center_offset_h = ((drawRect->right - drawRect->left) - (rcText.right - rcText.left)) / 2;
1876 if(infoPtr->dwStyle & TCS_VERTICAL)
1878 if(infoPtr->dwStyle & TCS_BOTTOM)
1879 drawRect->top+=center_offset_h;
1880 else
1881 drawRect->bottom-=center_offset_h;
1883 center_offset_v = ((drawRect->right - drawRect->left) - (rcText.bottom - rcText.top)) / 2;
1885 else
1887 drawRect->left += center_offset_h;
1888 center_offset_v = ((drawRect->bottom - drawRect->top) - (rcText.bottom - rcText.top)) / 2;
1891 /* if an item is selected, the text is shifted up instead of down */
1892 if (iItem == infoPtr->iSelected)
1893 center_offset_v -= infoPtr->uVItemPadding / 2;
1894 else
1895 center_offset_v += infoPtr->uVItemPadding / 2;
1897 if (center_offset_v < 0)
1898 center_offset_v = 0;
1900 if(infoPtr->dwStyle & TCS_VERTICAL)
1901 drawRect->left += center_offset_v;
1902 else
1903 drawRect->top += center_offset_v;
1905 /* Draw the text */
1906 if(infoPtr->dwStyle & TCS_VERTICAL) /* if we are vertical rotate the text and each character */
1908 LOGFONTW logfont;
1909 HFONT hFont;
1910 INT nEscapement = 900;
1911 INT nOrientation = 900;
1913 if(infoPtr->dwStyle & TCS_BOTTOM)
1915 nEscapement = -900;
1916 nOrientation = -900;
1919 /* to get a font with the escapement and orientation we are looking for, we need to */
1920 /* call CreateFontIndirect, which requires us to set the values of the logfont we pass in */
1921 if (!GetObjectW(infoPtr->hFont, sizeof(logfont), &logfont))
1922 GetObjectW(GetStockObject(DEFAULT_GUI_FONT), sizeof(logfont), &logfont);
1924 logfont.lfEscapement = nEscapement;
1925 logfont.lfOrientation = nOrientation;
1926 hFont = CreateFontIndirectW(&logfont);
1927 SelectObject(hdc, hFont);
1929 if (item->pszText)
1931 ExtTextOutW(hdc,
1932 (infoPtr->dwStyle & TCS_BOTTOM) ? drawRect->right : drawRect->left,
1933 (!(infoPtr->dwStyle & TCS_BOTTOM)) ? drawRect->bottom : drawRect->top,
1934 ETO_CLIPPED,
1935 drawRect,
1936 item->pszText,
1937 lstrlenW(item->pszText),
1941 DeleteObject(hFont);
1943 else
1945 TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%s), textlen=%d\n",
1946 debugstr_w(item->pszText), center_offset_h, center_offset_v,
1947 wine_dbgstr_rect(drawRect), (rcText.right-rcText.left));
1948 if (item->pszText)
1950 DrawTextW
1952 hdc,
1953 item->pszText,
1954 lstrlenW(item->pszText),
1955 drawRect,
1956 DT_LEFT | DT_SINGLELINE
1961 *drawRect = rcTemp; /* restore drawRect */
1965 * Cleanup
1967 SelectObject(hdc, hOldFont);
1968 SetBkMode(hdc, oldBkMode);
1969 SelectObject(hdc, holdPen);
1970 DeleteObject( htextPen );
1973 /******************************************************************************
1974 * TAB_DrawItem
1976 * This method is used to draw a single tab into the tab control.
1978 static void TAB_DrawItem(const TAB_INFO *infoPtr, HDC hdc, INT iItem)
1980 RECT itemRect;
1981 RECT selectedRect;
1982 BOOL isVisible;
1983 RECT r, fillRect, r1;
1984 INT clRight = 0;
1985 INT clBottom = 0;
1986 COLORREF bkgnd, corner;
1987 HTHEME theme;
1990 * Get the rectangle for the item.
1992 isVisible = TAB_InternalGetItemRect(infoPtr,
1993 iItem,
1994 &itemRect,
1995 &selectedRect);
1997 if (isVisible)
1999 RECT rUD, rC;
2001 /* Clip UpDown control to not draw over it */
2002 if (infoPtr->needsScrolling)
2004 GetWindowRect(infoPtr->hwnd, &rC);
2005 GetWindowRect(infoPtr->hwndUpDown, &rUD);
2006 ExcludeClipRect(hdc, rUD.left - rC.left, rUD.top - rC.top, rUD.right - rC.left, rUD.bottom - rC.top);
2009 /* If you need to see what the control is doing,
2010 * then override these variables. They will change what
2011 * fill colors are used for filling the tabs, and the
2012 * corners when drawing the edge.
2014 bkgnd = comctl32_color.clrBtnFace;
2015 corner = comctl32_color.clrBtnFace;
2017 if (infoPtr->dwStyle & TCS_BUTTONS)
2019 /* Get item rectangle */
2020 r = itemRect;
2022 /* Separators between flat buttons */
2023 if ((infoPtr->dwStyle & TCS_FLATBUTTONS) && (infoPtr->exStyle & TCS_EX_FLATSEPARATORS))
2025 r1 = r;
2026 r1.right += (FLAT_BTN_SPACINGX -2);
2027 DrawEdge(hdc, &r1, EDGE_ETCHED, BF_RIGHT);
2030 if (iItem == infoPtr->iSelected)
2032 DrawEdge(hdc, &r, EDGE_SUNKEN, BF_SOFT|BF_RECT);
2034 OffsetRect(&r, 1, 1);
2036 else /* ! selected */
2038 DWORD state = TAB_GetItem(infoPtr, iItem)->dwState;
2040 if ((state & TCIS_BUTTONPRESSED) || (iItem == infoPtr->uFocus))
2041 DrawEdge(hdc, &r, EDGE_SUNKEN, BF_SOFT|BF_RECT);
2042 else
2043 if (!(infoPtr->dwStyle & TCS_FLATBUTTONS))
2044 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_RECT);
2047 else /* !TCS_BUTTONS */
2049 /* We draw a rectangle of different sizes depending on the selection
2050 * state. */
2051 if (iItem == infoPtr->iSelected) {
2052 RECT rect;
2053 GetClientRect (infoPtr->hwnd, &rect);
2054 clRight = rect.right;
2055 clBottom = rect.bottom;
2056 r = selectedRect;
2058 else
2059 r = itemRect;
2062 * Erase the background. (Delay it but setup rectangle.)
2063 * This is necessary when drawing the selected item since it is larger
2064 * than the others, it might overlap with stuff already drawn by the
2065 * other tabs
2067 fillRect = r;
2069 /* Draw themed tabs - but only if they are at the top.
2070 * Windows draws even side or bottom tabs themed, with wacky results.
2071 * However, since in Wine apps may get themed that did not opt in via
2072 * a manifest avoid theming when we know the result will be wrong */
2073 if ((theme = GetWindowTheme (infoPtr->hwnd))
2074 && ((infoPtr->dwStyle & (TCS_VERTICAL | TCS_BOTTOM)) == 0))
2076 static const int partIds[8] = {
2077 /* Normal item */
2078 TABP_TABITEM,
2079 TABP_TABITEMLEFTEDGE,
2080 TABP_TABITEMRIGHTEDGE,
2081 TABP_TABITEMBOTHEDGE,
2082 /* Selected tab */
2083 TABP_TOPTABITEM,
2084 TABP_TOPTABITEMLEFTEDGE,
2085 TABP_TOPTABITEMRIGHTEDGE,
2086 TABP_TOPTABITEMBOTHEDGE,
2088 int partIndex = 0;
2089 int stateId = TIS_NORMAL;
2091 /* selected and unselected tabs have different parts */
2092 if (iItem == infoPtr->iSelected)
2093 partIndex += 4;
2094 /* The part also differs on the position of a tab on a line.
2095 * "Visually" determining the position works well enough. */
2096 GetClientRect(infoPtr->hwnd, &r1);
2097 if(selectedRect.left == 0)
2098 partIndex += 1;
2099 if(selectedRect.right == r1.right)
2100 partIndex += 2;
2102 if (iItem == infoPtr->iSelected)
2103 stateId = TIS_SELECTED;
2104 else if (iItem == infoPtr->iHotTracked)
2105 stateId = TIS_HOT;
2106 else if (iItem == infoPtr->uFocus)
2107 stateId = TIS_FOCUSED;
2109 /* Adjust rectangle for bottommost row */
2110 if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2111 r.bottom += 3;
2113 DrawThemeBackground (theme, hdc, partIds[partIndex], stateId, &r, NULL);
2114 GetThemeBackgroundContentRect (theme, hdc, partIds[partIndex], stateId, &r, &r);
2116 else if(infoPtr->dwStyle & TCS_VERTICAL)
2118 /* These are for adjusting the drawing of a Selected tab */
2119 /* The initial values are for the normal case of non-Selected */
2120 int ZZ = 1; /* Do not stretch if selected */
2121 if (iItem == infoPtr->iSelected) {
2122 ZZ = 0;
2124 /* if leftmost draw the line longer */
2125 if(selectedRect.top == 0)
2126 fillRect.top += CONTROL_BORDER_SIZEY;
2127 /* if rightmost draw the line longer */
2128 if(selectedRect.bottom == clBottom)
2129 fillRect.bottom -= CONTROL_BORDER_SIZEY;
2132 if (infoPtr->dwStyle & TCS_BOTTOM)
2134 /* Adjust both rectangles to match native */
2135 r.left += (1-ZZ);
2137 TRACE("<right> item=%d, fill=(%s), edge=(%s)\n",
2138 iItem, wine_dbgstr_rect(&fillRect), wine_dbgstr_rect(&r));
2140 /* Clear interior */
2141 SetBkColor(hdc, bkgnd);
2142 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2144 /* Draw rectangular edge around tab */
2145 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_RIGHT|BF_TOP|BF_BOTTOM);
2147 /* Now erase the top corner and draw diagonal edge */
2148 SetBkColor(hdc, corner);
2149 r1.left = r.right - ROUND_CORNER_SIZE - 1;
2150 r1.top = r.top;
2151 r1.right = r.right;
2152 r1.bottom = r1.top + ROUND_CORNER_SIZE;
2153 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2154 r1.right--;
2155 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPLEFT);
2157 /* Now erase the bottom corner and draw diagonal edge */
2158 r1.left = r.right - ROUND_CORNER_SIZE - 1;
2159 r1.bottom = r.bottom;
2160 r1.right = r.right;
2161 r1.top = r1.bottom - ROUND_CORNER_SIZE;
2162 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2163 r1.right--;
2164 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMLEFT);
2166 if ((iItem == infoPtr->iSelected) && (selectedRect.top == 0)) {
2167 r1 = r;
2168 r1.right = r1.left;
2169 r1.left--;
2170 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_TOP);
2174 else
2176 TRACE("<left> item=%d, fill=(%s), edge=(%s)\n",
2177 iItem, wine_dbgstr_rect(&fillRect), wine_dbgstr_rect(&r));
2179 /* Clear interior */
2180 SetBkColor(hdc, bkgnd);
2181 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2183 /* Draw rectangular edge around tab */
2184 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_TOP|BF_BOTTOM);
2186 /* Now erase the top corner and draw diagonal edge */
2187 SetBkColor(hdc, corner);
2188 r1.left = r.left;
2189 r1.top = r.top;
2190 r1.right = r1.left + ROUND_CORNER_SIZE + 1;
2191 r1.bottom = r1.top + ROUND_CORNER_SIZE;
2192 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2193 r1.left++;
2194 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPRIGHT);
2196 /* Now erase the bottom corner and draw diagonal edge */
2197 r1.left = r.left;
2198 r1.bottom = r.bottom;
2199 r1.right = r1.left + ROUND_CORNER_SIZE + 1;
2200 r1.top = r1.bottom - ROUND_CORNER_SIZE;
2201 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2202 r1.left++;
2203 DrawEdge(hdc, &r1, EDGE_SUNKEN, BF_DIAGONAL_ENDTOPLEFT);
2206 else /* ! TCS_VERTICAL */
2208 /* These are for adjusting the drawing of a Selected tab */
2209 /* The initial values are for the normal case of non-Selected */
2210 if (iItem == infoPtr->iSelected) {
2211 /* if leftmost draw the line longer */
2212 if(selectedRect.left == 0)
2213 fillRect.left += CONTROL_BORDER_SIZEX;
2214 /* if rightmost draw the line longer */
2215 if(selectedRect.right == clRight)
2216 fillRect.right -= CONTROL_BORDER_SIZEX;
2219 if (infoPtr->dwStyle & TCS_BOTTOM)
2221 /* Adjust both rectangles for topmost row */
2222 if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2224 fillRect.top -= 2;
2225 r.top -= 1;
2228 TRACE("<bottom> item=%d, fill=(%s), edge=(%s)\n",
2229 iItem, wine_dbgstr_rect(&fillRect), wine_dbgstr_rect(&r));
2231 /* Clear interior */
2232 SetBkColor(hdc, bkgnd);
2233 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2235 /* Draw rectangular edge around tab */
2236 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_BOTTOM|BF_RIGHT);
2238 /* Now erase the righthand corner and draw diagonal edge */
2239 SetBkColor(hdc, corner);
2240 r1.left = r.right - ROUND_CORNER_SIZE;
2241 r1.bottom = r.bottom;
2242 r1.right = r.right;
2243 r1.top = r1.bottom - ROUND_CORNER_SIZE - 1;
2244 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2245 r1.bottom--;
2246 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMLEFT);
2248 /* Now erase the lefthand corner and draw diagonal edge */
2249 r1.left = r.left;
2250 r1.bottom = r.bottom;
2251 r1.right = r1.left + ROUND_CORNER_SIZE;
2252 r1.top = r1.bottom - ROUND_CORNER_SIZE - 1;
2253 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2254 r1.bottom--;
2255 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPLEFT);
2257 if (iItem == infoPtr->iSelected)
2259 r.top += 2;
2260 r.left += 1;
2261 if (selectedRect.left == 0)
2263 r1 = r;
2264 r1.bottom = r1.top;
2265 r1.top--;
2266 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_LEFT);
2271 else
2273 /* Adjust both rectangles for bottommost row */
2274 if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2276 fillRect.bottom += 3;
2277 r.bottom += 2;
2280 TRACE("<top> item=%d, fill=(%s), edge=(%s)\n",
2281 iItem, wine_dbgstr_rect(&fillRect), wine_dbgstr_rect(&r));
2283 /* Clear interior */
2284 SetBkColor(hdc, bkgnd);
2285 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2287 /* Draw rectangular edge around tab */
2288 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_TOP|BF_RIGHT);
2290 /* Now erase the righthand corner and draw diagonal edge */
2291 SetBkColor(hdc, corner);
2292 r1.left = r.right - ROUND_CORNER_SIZE;
2293 r1.top = r.top;
2294 r1.right = r.right;
2295 r1.bottom = r1.top + ROUND_CORNER_SIZE + 1;
2296 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2297 r1.top++;
2298 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMRIGHT);
2300 /* Now erase the lefthand corner and draw diagonal edge */
2301 r1.left = r.left;
2302 r1.top = r.top;
2303 r1.right = r1.left + ROUND_CORNER_SIZE;
2304 r1.bottom = r1.top + ROUND_CORNER_SIZE + 1;
2305 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2306 r1.top++;
2307 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPRIGHT);
2312 TAB_DumpItemInternal(infoPtr, iItem);
2314 /* This modifies r to be the text rectangle. */
2315 TAB_DrawItemInterior(infoPtr, hdc, iItem, &r);
2319 /******************************************************************************
2320 * TAB_DrawBorder
2322 * This method is used to draw the raised border around the tab control
2323 * "content" area.
2325 static void TAB_DrawBorder(const TAB_INFO *infoPtr, HDC hdc)
2327 RECT rect;
2328 HTHEME theme = GetWindowTheme (infoPtr->hwnd);
2330 GetClientRect (infoPtr->hwnd, &rect);
2333 * Adjust for the style
2336 if (infoPtr->uNumItem)
2338 if ((infoPtr->dwStyle & TCS_BOTTOM) && !(infoPtr->dwStyle & TCS_VERTICAL))
2339 rect.bottom -= infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2340 else if((infoPtr->dwStyle & TCS_BOTTOM) && (infoPtr->dwStyle & TCS_VERTICAL))
2341 rect.right -= infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2342 else if(infoPtr->dwStyle & TCS_VERTICAL)
2343 rect.left += infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2344 else /* not TCS_VERTICAL and not TCS_BOTTOM */
2345 rect.top += infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2348 TRACE("border=(%s)\n", wine_dbgstr_rect(&rect));
2350 if (theme)
2351 DrawThemeBackground (theme, hdc, TABP_PANE, 0, &rect, NULL);
2352 else
2353 DrawEdge(hdc, &rect, EDGE_RAISED, BF_SOFT|BF_RECT);
2356 /******************************************************************************
2357 * TAB_Refresh
2359 * This method repaints the tab control..
2361 static void TAB_Refresh (const TAB_INFO *infoPtr, HDC hdc)
2363 HFONT hOldFont;
2364 INT i;
2366 if (!infoPtr->DoRedraw)
2367 return;
2369 hOldFont = SelectObject (hdc, infoPtr->hFont);
2371 if (infoPtr->dwStyle & TCS_BUTTONS)
2373 for (i = 0; i < infoPtr->uNumItem; i++)
2374 TAB_DrawItem (infoPtr, hdc, i);
2376 else
2378 /* Draw all the non selected item first */
2379 for (i = 0; i < infoPtr->uNumItem; i++)
2381 if (i != infoPtr->iSelected)
2382 TAB_DrawItem (infoPtr, hdc, i);
2385 /* Now, draw the border, draw it before the selected item
2386 * since the selected item overwrites part of the border. */
2387 TAB_DrawBorder (infoPtr, hdc);
2389 /* Then, draw the selected item */
2390 TAB_DrawItem (infoPtr, hdc, infoPtr->iSelected);
2393 SelectObject (hdc, hOldFont);
2396 static inline DWORD TAB_GetRowCount (const TAB_INFO *infoPtr)
2398 TRACE("(%p)\n", infoPtr);
2399 return infoPtr->uNumRows;
2402 static inline LRESULT TAB_SetRedraw (TAB_INFO *infoPtr, BOOL doRedraw)
2404 infoPtr->DoRedraw = doRedraw;
2405 return 0;
2408 /******************************************************************************
2409 * TAB_EnsureSelectionVisible
2411 * This method will make sure that the current selection is completely
2412 * visible by scrolling until it is.
2414 static void TAB_EnsureSelectionVisible(
2415 TAB_INFO* infoPtr)
2417 INT iSelected = infoPtr->iSelected;
2418 INT iOrigLeftmostVisible = infoPtr->leftmostVisible;
2420 if (iSelected < 0)
2421 return;
2423 /* set the items row to the bottommost row or topmost row depending on
2424 * style */
2425 if ((infoPtr->uNumRows > 1) && !(infoPtr->dwStyle & TCS_BUTTONS))
2427 TAB_ITEM *selected = TAB_GetItem(infoPtr, iSelected);
2428 INT newselected;
2429 INT iTargetRow;
2431 if(infoPtr->dwStyle & TCS_VERTICAL)
2432 newselected = selected->rect.left;
2433 else
2434 newselected = selected->rect.top;
2436 /* the target row is always (number of rows - 1)
2437 as row 0 is furthest from the clientRect */
2438 iTargetRow = infoPtr->uNumRows - 1;
2440 if (newselected != iTargetRow)
2442 UINT i;
2443 if(infoPtr->dwStyle & TCS_VERTICAL)
2445 for (i=0; i < infoPtr->uNumItem; i++)
2447 /* move everything in the row of the selected item to the iTargetRow */
2448 TAB_ITEM *item = TAB_GetItem(infoPtr, i);
2450 if (item->rect.left == newselected )
2451 item->rect.left = iTargetRow;
2452 else
2454 if (item->rect.left > newselected)
2455 item->rect.left-=1;
2459 else
2461 for (i=0; i < infoPtr->uNumItem; i++)
2463 TAB_ITEM *item = TAB_GetItem(infoPtr, i);
2465 if (item->rect.top == newselected )
2466 item->rect.top = iTargetRow;
2467 else
2469 if (item->rect.top > newselected)
2470 item->rect.top-=1;
2474 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
2479 * Do the trivial cases first.
2481 if ( (!infoPtr->needsScrolling) ||
2482 (infoPtr->hwndUpDown==0) || (infoPtr->dwStyle & TCS_VERTICAL))
2483 return;
2485 if (infoPtr->leftmostVisible >= iSelected)
2487 infoPtr->leftmostVisible = iSelected;
2489 else
2491 TAB_ITEM *selected = TAB_GetItem(infoPtr, iSelected);
2492 RECT r;
2493 INT width;
2494 UINT i;
2496 /* Calculate the part of the client area that is visible */
2497 GetClientRect(infoPtr->hwnd, &r);
2498 width = r.right;
2500 GetClientRect(infoPtr->hwndUpDown, &r);
2501 width -= r.right;
2503 if ((selected->rect.right -
2504 selected->rect.left) >= width )
2506 /* Special case: width of selected item is greater than visible
2507 * part of control.
2509 infoPtr->leftmostVisible = iSelected;
2511 else
2513 for (i = infoPtr->leftmostVisible; i < infoPtr->uNumItem; i++)
2515 if ((selected->rect.right - TAB_GetItem(infoPtr, i)->rect.left) < width)
2516 break;
2518 infoPtr->leftmostVisible = i;
2522 if (infoPtr->leftmostVisible != iOrigLeftmostVisible)
2523 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
2525 SendMessageW(infoPtr->hwndUpDown, UDM_SETPOS, 0,
2526 MAKELONG(infoPtr->leftmostVisible, 0));
2529 /******************************************************************************
2530 * TAB_InvalidateTabArea
2532 * This method will invalidate the portion of the control that contains the
2533 * tabs. It is called when the state of the control changes and needs
2534 * to be redisplayed
2536 static void TAB_InvalidateTabArea(const TAB_INFO *infoPtr)
2538 RECT clientRect, rInvalidate, rAdjClient;
2539 INT lastRow = infoPtr->uNumRows - 1;
2540 RECT rect;
2542 if (lastRow < 0) return;
2544 GetClientRect(infoPtr->hwnd, &clientRect);
2545 rInvalidate = clientRect;
2546 rAdjClient = clientRect;
2548 TAB_AdjustRect(infoPtr, 0, &rAdjClient);
2550 TAB_InternalGetItemRect(infoPtr, infoPtr->uNumItem-1 , &rect, NULL);
2551 if ((infoPtr->dwStyle & TCS_BOTTOM) && (infoPtr->dwStyle & TCS_VERTICAL))
2553 rInvalidate.left = rAdjClient.right;
2554 if (infoPtr->uNumRows == 1)
2555 rInvalidate.bottom = clientRect.top + rect.bottom + 2 * SELECTED_TAB_OFFSET;
2557 else if(infoPtr->dwStyle & TCS_VERTICAL)
2559 rInvalidate.right = rAdjClient.left;
2560 if (infoPtr->uNumRows == 1)
2561 rInvalidate.bottom = clientRect.top + rect.bottom + 2 * SELECTED_TAB_OFFSET;
2563 else if (infoPtr->dwStyle & TCS_BOTTOM)
2565 rInvalidate.top = rAdjClient.bottom;
2566 if (infoPtr->uNumRows == 1)
2567 rInvalidate.right = clientRect.left + rect.right + 2 * SELECTED_TAB_OFFSET;
2569 else
2571 rInvalidate.bottom = rAdjClient.top;
2572 if (infoPtr->uNumRows == 1)
2573 rInvalidate.right = clientRect.left + rect.right + 2 * SELECTED_TAB_OFFSET;
2576 /* Punch out the updown control */
2577 if (infoPtr->needsScrolling && (rInvalidate.right > 0)) {
2578 RECT r;
2579 GetClientRect(infoPtr->hwndUpDown, &r);
2580 if (rInvalidate.right > clientRect.right - r.left)
2581 rInvalidate.right = rInvalidate.right - (r.right - r.left);
2582 else
2583 rInvalidate.right = clientRect.right - r.left;
2586 TRACE("invalidate (%s)\n", wine_dbgstr_rect(&rInvalidate));
2588 InvalidateRect(infoPtr->hwnd, &rInvalidate, TRUE);
2591 static inline LRESULT TAB_Paint (TAB_INFO *infoPtr, HDC hdcPaint)
2593 HDC hdc;
2594 PAINTSTRUCT ps;
2596 if (hdcPaint)
2597 hdc = hdcPaint;
2598 else
2600 hdc = BeginPaint (infoPtr->hwnd, &ps);
2601 TRACE("erase %d, rect=(%s)\n", ps.fErase, wine_dbgstr_rect(&ps.rcPaint));
2604 TAB_Refresh (infoPtr, hdc);
2606 if (!hdcPaint)
2607 EndPaint (infoPtr->hwnd, &ps);
2609 return 0;
2612 static LRESULT
2613 TAB_InsertItemT (TAB_INFO *infoPtr, INT iItem, const TCITEMW *pti, BOOL bUnicode)
2615 TAB_ITEM *item;
2616 RECT rect;
2618 GetClientRect (infoPtr->hwnd, &rect);
2619 TRACE("Rect: %p %s\n", infoPtr->hwnd, wine_dbgstr_rect(&rect));
2621 if (iItem < 0) return -1;
2622 if (iItem > infoPtr->uNumItem)
2623 iItem = infoPtr->uNumItem;
2625 TAB_DumpItemExternalT(pti, iItem, bUnicode);
2627 if (!(item = Alloc(TAB_ITEM_SIZE(infoPtr)))) return FALSE;
2628 if (DPA_InsertPtr(infoPtr->items, iItem, item) == -1)
2630 Free(item);
2631 return FALSE;
2634 if (infoPtr->uNumItem == 0)
2635 infoPtr->iSelected = 0;
2636 else if (iItem <= infoPtr->iSelected)
2637 infoPtr->iSelected++;
2639 infoPtr->uNumItem++;
2641 item->pszText = NULL;
2642 if (pti->mask & TCIF_TEXT)
2644 if (bUnicode)
2645 Str_SetPtrW (&item->pszText, pti->pszText);
2646 else
2647 Str_SetPtrAtoW (&item->pszText, (LPSTR)pti->pszText);
2650 if (pti->mask & TCIF_IMAGE)
2651 item->iImage = pti->iImage;
2652 else
2653 item->iImage = -1;
2655 if (pti->mask & TCIF_PARAM)
2656 memcpy(item->extra, &pti->lParam, EXTRA_ITEM_SIZE(infoPtr));
2657 else
2658 memset(item->extra, 0, EXTRA_ITEM_SIZE(infoPtr));
2660 TAB_SetItemBounds(infoPtr);
2661 if (infoPtr->uNumItem > 1)
2662 TAB_InvalidateTabArea(infoPtr);
2663 else
2664 InvalidateRect(infoPtr->hwnd, NULL, TRUE);
2666 TRACE("[%p]: added item %d %s\n",
2667 infoPtr->hwnd, iItem, debugstr_w(item->pszText));
2669 /* If we haven't set the current focus yet, set it now. */
2670 if (infoPtr->uFocus == -1)
2671 TAB_SetCurFocus(infoPtr, iItem);
2673 return iItem;
2676 static LRESULT
2677 TAB_SetItemSize (TAB_INFO *infoPtr, INT cx, INT cy)
2679 LONG lResult = 0;
2680 BOOL bNeedPaint = FALSE;
2682 lResult = MAKELONG(infoPtr->tabWidth, infoPtr->tabHeight);
2684 /* UNDOCUMENTED: If requested Width or Height is 0 this means that program wants to use auto size. */
2685 if (infoPtr->dwStyle & TCS_FIXEDWIDTH && (infoPtr->tabWidth != cx))
2687 infoPtr->tabWidth = cx;
2688 bNeedPaint = TRUE;
2691 if (infoPtr->tabHeight != cy)
2693 if ((infoPtr->fHeightSet = (cy != 0)))
2694 infoPtr->tabHeight = cy;
2696 bNeedPaint = TRUE;
2698 TRACE("was h=%d,w=%d, now h=%d,w=%d\n",
2699 HIWORD(lResult), LOWORD(lResult),
2700 infoPtr->tabHeight, infoPtr->tabWidth);
2702 if (bNeedPaint)
2704 TAB_SetItemBounds(infoPtr);
2705 RedrawWindow(infoPtr->hwnd, NULL, NULL, RDW_ERASE | RDW_INVALIDATE | RDW_UPDATENOW);
2708 return lResult;
2711 static inline LRESULT TAB_SetMinTabWidth (TAB_INFO *infoPtr, INT cx)
2713 INT oldcx = 0;
2715 TRACE("(%p,%d)\n", infoPtr, cx);
2717 if (infoPtr->tabMinWidth < 0)
2718 oldcx = DEFAULT_MIN_TAB_WIDTH;
2719 else
2720 oldcx = infoPtr->tabMinWidth;
2721 infoPtr->tabMinWidth = cx;
2722 TAB_SetItemBounds(infoPtr);
2723 return oldcx;
2726 static inline LRESULT
2727 TAB_HighlightItem (TAB_INFO *infoPtr, INT iItem, BOOL fHighlight)
2729 LPDWORD lpState;
2730 DWORD oldState;
2731 RECT r;
2733 TRACE("(%p,%d,%s)\n", infoPtr, iItem, fHighlight ? "true" : "false");
2735 if (iItem < 0 || iItem >= infoPtr->uNumItem)
2736 return FALSE;
2738 lpState = &TAB_GetItem(infoPtr, iItem)->dwState;
2739 oldState = *lpState;
2741 if (fHighlight)
2742 *lpState |= TCIS_HIGHLIGHTED;
2743 else
2744 *lpState &= ~TCIS_HIGHLIGHTED;
2746 if ((oldState != *lpState) && TAB_InternalGetItemRect (infoPtr, iItem, &r, NULL))
2747 InvalidateRect (infoPtr->hwnd, &r, TRUE);
2749 return TRUE;
2752 static LRESULT
2753 TAB_SetItemT (TAB_INFO *infoPtr, INT iItem, LPTCITEMW tabItem, BOOL bUnicode)
2755 TAB_ITEM *wineItem;
2757 TRACE("(%p,%d,%p,%s)\n", infoPtr, iItem, tabItem, bUnicode ? "true" : "false");
2759 if (iItem < 0 || iItem >= infoPtr->uNumItem)
2760 return FALSE;
2762 TAB_DumpItemExternalT(tabItem, iItem, bUnicode);
2764 wineItem = TAB_GetItem(infoPtr, iItem);
2766 if (tabItem->mask & TCIF_IMAGE)
2767 wineItem->iImage = tabItem->iImage;
2769 if (tabItem->mask & TCIF_PARAM)
2770 memcpy(wineItem->extra, &tabItem->lParam, infoPtr->cbInfo);
2772 if (tabItem->mask & TCIF_RTLREADING)
2773 FIXME("TCIF_RTLREADING\n");
2775 if (tabItem->mask & TCIF_STATE)
2776 wineItem->dwState = (wineItem->dwState & ~tabItem->dwStateMask) |
2777 ( tabItem->dwState & tabItem->dwStateMask);
2779 if (tabItem->mask & TCIF_TEXT)
2781 Free(wineItem->pszText);
2782 wineItem->pszText = NULL;
2783 if (bUnicode)
2784 Str_SetPtrW(&wineItem->pszText, tabItem->pszText);
2785 else
2786 Str_SetPtrAtoW(&wineItem->pszText, (LPSTR)tabItem->pszText);
2789 /* Update and repaint tabs */
2790 TAB_SetItemBounds(infoPtr);
2791 TAB_InvalidateTabArea(infoPtr);
2793 return TRUE;
2796 static inline LRESULT TAB_GetItemCount (const TAB_INFO *infoPtr)
2798 TRACE("\n");
2799 return infoPtr->uNumItem;
2803 static LRESULT
2804 TAB_GetItemT (TAB_INFO *infoPtr, INT iItem, LPTCITEMW tabItem, BOOL bUnicode)
2806 TAB_ITEM *wineItem;
2808 TRACE("(%p,%d,%p,%s)\n", infoPtr, iItem, tabItem, bUnicode ? "true" : "false");
2810 if (!tabItem) return FALSE;
2812 if (iItem < 0 || iItem >= infoPtr->uNumItem)
2814 /* init requested fields */
2815 if (tabItem->mask & TCIF_IMAGE) tabItem->iImage = 0;
2816 if (tabItem->mask & TCIF_PARAM) tabItem->lParam = 0;
2817 if (tabItem->mask & TCIF_STATE) tabItem->dwState = 0;
2818 return FALSE;
2821 wineItem = TAB_GetItem(infoPtr, iItem);
2823 if (tabItem->mask & TCIF_IMAGE)
2824 tabItem->iImage = wineItem->iImage;
2826 if (tabItem->mask & TCIF_PARAM)
2827 memcpy(&tabItem->lParam, wineItem->extra, infoPtr->cbInfo);
2829 if (tabItem->mask & TCIF_RTLREADING)
2830 FIXME("TCIF_RTLREADING\n");
2832 if (tabItem->mask & TCIF_STATE)
2833 tabItem->dwState = wineItem->dwState & tabItem->dwStateMask;
2835 if (tabItem->mask & TCIF_TEXT)
2837 if (bUnicode)
2838 Str_GetPtrW (wineItem->pszText, tabItem->pszText, tabItem->cchTextMax);
2839 else
2840 Str_GetPtrWtoA (wineItem->pszText, (LPSTR)tabItem->pszText, tabItem->cchTextMax);
2843 TAB_DumpItemExternalT(tabItem, iItem, bUnicode);
2845 return TRUE;
2849 static LRESULT TAB_DeleteItem (TAB_INFO *infoPtr, INT iItem)
2851 TAB_ITEM *item;
2853 TRACE("(%p, %d)\n", infoPtr, iItem);
2855 if (iItem < 0 || iItem >= infoPtr->uNumItem) return FALSE;
2857 TAB_InvalidateTabArea(infoPtr);
2858 item = TAB_GetItem(infoPtr, iItem);
2859 Free(item->pszText);
2860 Free(item);
2861 infoPtr->uNumItem--;
2862 DPA_DeletePtr(infoPtr->items, iItem);
2864 if (infoPtr->uNumItem == 0)
2866 if (infoPtr->iHotTracked >= 0)
2868 KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
2869 infoPtr->iHotTracked = -1;
2872 infoPtr->iSelected = -1;
2874 else
2876 if (iItem <= infoPtr->iHotTracked)
2878 /* When tabs move left/up, the hot track item may change */
2879 FIXME("Recalc hot track\n");
2883 /* adjust the selected index */
2884 if (iItem == infoPtr->iSelected)
2885 infoPtr->iSelected = -1;
2886 else if (iItem < infoPtr->iSelected)
2887 infoPtr->iSelected--;
2889 /* reposition and repaint tabs */
2890 TAB_SetItemBounds(infoPtr);
2892 return TRUE;
2895 static inline LRESULT TAB_DeleteAllItems (TAB_INFO *infoPtr)
2897 TRACE("(%p)\n", infoPtr);
2898 while (infoPtr->uNumItem)
2899 TAB_DeleteItem (infoPtr, 0);
2900 return TRUE;
2904 static inline LRESULT TAB_GetFont (const TAB_INFO *infoPtr)
2906 TRACE("(%p) returning %p\n", infoPtr, infoPtr->hFont);
2907 return (LRESULT)infoPtr->hFont;
2910 static inline LRESULT TAB_SetFont (TAB_INFO *infoPtr, HFONT hNewFont)
2912 TRACE("(%p,%p)\n", infoPtr, hNewFont);
2914 infoPtr->hFont = hNewFont;
2916 TAB_SetItemBounds(infoPtr);
2918 TAB_InvalidateTabArea(infoPtr);
2920 return 0;
2924 static inline LRESULT TAB_GetImageList (const TAB_INFO *infoPtr)
2926 TRACE("\n");
2927 return (LRESULT)infoPtr->himl;
2930 static inline LRESULT TAB_SetImageList (TAB_INFO *infoPtr, HIMAGELIST himlNew)
2932 HIMAGELIST himlPrev = infoPtr->himl;
2933 TRACE("himl=%p\n", himlNew);
2934 infoPtr->himl = himlNew;
2935 TAB_SetItemBounds(infoPtr);
2936 InvalidateRect(infoPtr->hwnd, NULL, TRUE);
2937 return (LRESULT)himlPrev;
2940 static inline LRESULT TAB_GetUnicodeFormat (const TAB_INFO *infoPtr)
2942 TRACE("(%p)\n", infoPtr);
2943 return infoPtr->bUnicode;
2946 static inline LRESULT TAB_SetUnicodeFormat (TAB_INFO *infoPtr, BOOL bUnicode)
2948 BOOL bTemp = infoPtr->bUnicode;
2950 TRACE("(%p %d)\n", infoPtr, bUnicode);
2951 infoPtr->bUnicode = bUnicode;
2953 return bTemp;
2956 static inline LRESULT TAB_Size (TAB_INFO *infoPtr)
2958 /* I'm not really sure what the following code was meant to do.
2959 This is what it is doing:
2960 When WM_SIZE is sent with SIZE_RESTORED, the control
2961 gets positioned in the top left corner.
2963 RECT parent_rect;
2964 HWND parent;
2965 UINT uPosFlags,cx,cy;
2967 uPosFlags=0;
2968 if (!wParam) {
2969 parent = GetParent (hwnd);
2970 GetClientRect(parent, &parent_rect);
2971 cx=LOWORD (lParam);
2972 cy=HIWORD (lParam);
2973 if (GetWindowLongW(hwnd, GWL_STYLE) & CCS_NORESIZE)
2974 uPosFlags |= (SWP_NOSIZE | SWP_NOMOVE);
2976 SetWindowPos (hwnd, 0, parent_rect.left, parent_rect.top,
2977 cx, cy, uPosFlags | SWP_NOZORDER);
2978 } else {
2979 FIXME("WM_SIZE flag %x %lx not handled\n", wParam, lParam);
2980 } */
2982 /* Recompute the size/position of the tabs. */
2983 TAB_SetItemBounds (infoPtr);
2985 /* Force a repaint of the control. */
2986 InvalidateRect(infoPtr->hwnd, NULL, TRUE);
2988 return 0;
2992 static LRESULT TAB_Create (HWND hwnd, LPARAM lParam)
2994 TAB_INFO *infoPtr;
2995 TEXTMETRICW fontMetrics;
2996 HDC hdc;
2997 HFONT hOldFont;
2998 DWORD style;
3000 infoPtr = Alloc (sizeof(TAB_INFO));
3002 SetWindowLongPtrW(hwnd, 0, (DWORD_PTR)infoPtr);
3004 infoPtr->hwnd = hwnd;
3005 infoPtr->hwndNotify = ((LPCREATESTRUCTW)lParam)->hwndParent;
3006 infoPtr->uNumItem = 0;
3007 infoPtr->uNumRows = 0;
3008 infoPtr->uHItemPadding = 6;
3009 infoPtr->uVItemPadding = 3;
3010 infoPtr->uHItemPadding_s = 6;
3011 infoPtr->uVItemPadding_s = 3;
3012 infoPtr->hFont = 0;
3013 infoPtr->items = DPA_Create(8);
3014 infoPtr->hcurArrow = LoadCursorW (0, (LPWSTR)IDC_ARROW);
3015 infoPtr->iSelected = -1;
3016 infoPtr->iHotTracked = -1;
3017 infoPtr->uFocus = -1;
3018 infoPtr->hwndToolTip = 0;
3019 infoPtr->DoRedraw = TRUE;
3020 infoPtr->needsScrolling = FALSE;
3021 infoPtr->hwndUpDown = 0;
3022 infoPtr->leftmostVisible = 0;
3023 infoPtr->fHeightSet = FALSE;
3024 infoPtr->bUnicode = IsWindowUnicode (hwnd);
3025 infoPtr->cbInfo = sizeof(LPARAM);
3027 TRACE("Created tab control, hwnd [%p]\n", hwnd);
3029 /* The tab control always has the WS_CLIPSIBLINGS style. Even
3030 if you don't specify it in CreateWindow. This is necessary in
3031 order for paint to work correctly. This follows windows behaviour. */
3032 style = GetWindowLongW(hwnd, GWL_STYLE);
3033 if (style & TCS_VERTICAL) style |= TCS_MULTILINE;
3034 style |= WS_CLIPSIBLINGS;
3035 SetWindowLongW(hwnd, GWL_STYLE, style);
3037 infoPtr->dwStyle = style;
3038 infoPtr->exStyle = (style & TCS_FLATBUTTONS) ? TCS_EX_FLATSEPARATORS : 0;
3040 if (infoPtr->dwStyle & TCS_TOOLTIPS) {
3041 /* Create tooltip control */
3042 infoPtr->hwndToolTip =
3043 CreateWindowExW (0, TOOLTIPS_CLASSW, NULL, WS_POPUP,
3044 CW_USEDEFAULT, CW_USEDEFAULT,
3045 CW_USEDEFAULT, CW_USEDEFAULT,
3046 hwnd, 0, 0, 0);
3048 /* Send NM_TOOLTIPSCREATED notification */
3049 if (infoPtr->hwndToolTip) {
3050 NMTOOLTIPSCREATED nmttc;
3052 nmttc.hdr.hwndFrom = hwnd;
3053 nmttc.hdr.idFrom = GetWindowLongPtrW(hwnd, GWLP_ID);
3054 nmttc.hdr.code = NM_TOOLTIPSCREATED;
3055 nmttc.hwndToolTips = infoPtr->hwndToolTip;
3057 SendMessageW (infoPtr->hwndNotify, WM_NOTIFY,
3058 GetWindowLongPtrW(hwnd, GWLP_ID), (LPARAM)&nmttc);
3062 OpenThemeData (infoPtr->hwnd, themeClass);
3065 * We need to get text information so we need a DC and we need to select
3066 * a font.
3068 hdc = GetDC(hwnd);
3069 hOldFont = SelectObject (hdc, GetStockObject (SYSTEM_FONT));
3071 /* Use the system font to determine the initial height of a tab. */
3072 GetTextMetricsW(hdc, &fontMetrics);
3075 * Make sure there is enough space for the letters + growing the
3076 * selected item + extra space for the selected item.
3078 infoPtr->tabHeight = fontMetrics.tmHeight + SELECTED_TAB_OFFSET +
3079 ((infoPtr->dwStyle & TCS_BUTTONS) ? 2 : 1) *
3080 infoPtr->uVItemPadding;
3082 /* Initialize the width of a tab. */
3083 if (infoPtr->dwStyle & TCS_FIXEDWIDTH)
3084 infoPtr->tabWidth = GetDeviceCaps(hdc, LOGPIXELSX);
3086 infoPtr->tabMinWidth = -1;
3088 TRACE("tabH=%d, tabW=%d\n", infoPtr->tabHeight, infoPtr->tabWidth);
3090 SelectObject (hdc, hOldFont);
3091 ReleaseDC(hwnd, hdc);
3093 return 0;
3096 static LRESULT
3097 TAB_Destroy (TAB_INFO *infoPtr)
3099 INT iItem;
3101 SetWindowLongPtrW(infoPtr->hwnd, 0, 0);
3103 for (iItem = infoPtr->uNumItem - 1; iItem >= 0; iItem--)
3105 TAB_ITEM *tab = TAB_GetItem(infoPtr, iItem);
3107 DPA_DeletePtr(infoPtr->items, iItem);
3108 infoPtr->uNumItem--;
3110 Free(tab->pszText);
3111 Free(tab);
3113 DPA_Destroy(infoPtr->items);
3114 infoPtr->items = NULL;
3116 if (infoPtr->hwndToolTip)
3117 DestroyWindow (infoPtr->hwndToolTip);
3119 if (infoPtr->hwndUpDown)
3120 DestroyWindow(infoPtr->hwndUpDown);
3122 if (infoPtr->iHotTracked >= 0)
3123 KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
3125 CloseThemeData (GetWindowTheme (infoPtr->hwnd));
3127 Free (infoPtr);
3128 return 0;
3131 /* update theme after a WM_THEMECHANGED message */
3132 static LRESULT theme_changed(const TAB_INFO *infoPtr)
3134 HTHEME theme = GetWindowTheme (infoPtr->hwnd);
3135 CloseThemeData (theme);
3136 OpenThemeData (infoPtr->hwnd, themeClass);
3137 return 0;
3140 static LRESULT TAB_NCCalcSize(WPARAM wParam)
3142 if (!wParam)
3143 return 0;
3144 return WVR_ALIGNTOP;
3147 static inline LRESULT
3148 TAB_SetItemExtra (TAB_INFO *infoPtr, INT cbInfo)
3150 TRACE("(%p %d)\n", infoPtr, cbInfo);
3152 if (cbInfo < 0 || infoPtr->uNumItem) return FALSE;
3154 infoPtr->cbInfo = cbInfo;
3155 return TRUE;
3158 static LRESULT TAB_RemoveImage (TAB_INFO *infoPtr, INT image)
3160 TRACE("%p %d\n", infoPtr, image);
3162 if (ImageList_Remove (infoPtr->himl, image))
3164 INT i, *idx;
3165 RECT r;
3167 /* shift indices, repaint items if needed */
3168 for (i = 0; i < infoPtr->uNumItem; i++)
3170 idx = &TAB_GetItem(infoPtr, i)->iImage;
3171 if (*idx >= image)
3173 if (*idx == image)
3174 *idx = -1;
3175 else
3176 (*idx)--;
3178 /* repaint item */
3179 if (TAB_InternalGetItemRect (infoPtr, i, &r, NULL))
3180 InvalidateRect (infoPtr->hwnd, &r, TRUE);
3185 return 0;
3188 static LRESULT
3189 TAB_SetExtendedStyle (TAB_INFO *infoPtr, DWORD exMask, DWORD exStyle)
3191 DWORD prevstyle = infoPtr->exStyle;
3193 /* zero mask means all styles */
3194 if (exMask == 0) exMask = ~0;
3196 if (exMask & TCS_EX_REGISTERDROP)
3198 FIXME("TCS_EX_REGISTERDROP style unimplemented\n");
3199 exMask &= ~TCS_EX_REGISTERDROP;
3200 exStyle &= ~TCS_EX_REGISTERDROP;
3203 if (exMask & TCS_EX_FLATSEPARATORS)
3205 if ((prevstyle ^ exStyle) & TCS_EX_FLATSEPARATORS)
3207 infoPtr->exStyle ^= TCS_EX_FLATSEPARATORS;
3208 TAB_InvalidateTabArea(infoPtr);
3212 return prevstyle;
3215 static inline LRESULT
3216 TAB_GetExtendedStyle (const TAB_INFO *infoPtr)
3218 return infoPtr->exStyle;
3221 static LRESULT
3222 TAB_DeselectAll (TAB_INFO *infoPtr, BOOL excludesel)
3224 BOOL paint = FALSE;
3225 INT i, selected = infoPtr->iSelected;
3227 TRACE("(%p, %d)\n", infoPtr, excludesel);
3229 if (!(infoPtr->dwStyle & TCS_BUTTONS))
3230 return 0;
3232 for (i = 0; i < infoPtr->uNumItem; i++)
3234 if ((TAB_GetItem(infoPtr, i)->dwState & TCIS_BUTTONPRESSED) &&
3235 (selected != i))
3237 TAB_GetItem(infoPtr, i)->dwState &= ~TCIS_BUTTONPRESSED;
3238 paint = TRUE;
3242 if (!excludesel && (selected != -1))
3244 TAB_GetItem(infoPtr, selected)->dwState &= ~TCIS_BUTTONPRESSED;
3245 infoPtr->iSelected = -1;
3246 paint = TRUE;
3249 if (paint)
3250 TAB_InvalidateTabArea (infoPtr);
3252 return 0;
3255 /***
3256 * DESCRIPTION:
3257 * Processes WM_STYLECHANGED messages.
3259 * PARAMETER(S):
3260 * [I] infoPtr : valid pointer to the tab data structure
3261 * [I] wStyleType : window style type (normal or extended)
3262 * [I] lpss : window style information
3264 * RETURN:
3265 * Zero
3267 static INT TAB_StyleChanged(TAB_INFO *infoPtr, WPARAM wStyleType,
3268 const STYLESTRUCT *lpss)
3270 TRACE("(styletype=%lx, styleOld=0x%08x, styleNew=0x%08x)\n",
3271 wStyleType, lpss->styleOld, lpss->styleNew);
3273 if (wStyleType != GWL_STYLE) return 0;
3275 infoPtr->dwStyle = lpss->styleNew;
3277 TAB_SetItemBounds (infoPtr);
3278 InvalidateRect(infoPtr->hwnd, NULL, TRUE);
3280 return 0;
3283 static LRESULT WINAPI
3284 TAB_WindowProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
3286 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
3288 TRACE("hwnd=%p msg=%x wParam=%lx lParam=%lx\n", hwnd, uMsg, wParam, lParam);
3289 if (!infoPtr && (uMsg != WM_CREATE))
3290 return DefWindowProcW (hwnd, uMsg, wParam, lParam);
3292 switch (uMsg)
3294 case TCM_GETIMAGELIST:
3295 return TAB_GetImageList (infoPtr);
3297 case TCM_SETIMAGELIST:
3298 return TAB_SetImageList (infoPtr, (HIMAGELIST)lParam);
3300 case TCM_GETITEMCOUNT:
3301 return TAB_GetItemCount (infoPtr);
3303 case TCM_GETITEMA:
3304 case TCM_GETITEMW:
3305 return TAB_GetItemT (infoPtr, (INT)wParam, (LPTCITEMW)lParam, uMsg == TCM_GETITEMW);
3307 case TCM_SETITEMA:
3308 case TCM_SETITEMW:
3309 return TAB_SetItemT (infoPtr, (INT)wParam, (LPTCITEMW)lParam, uMsg == TCM_SETITEMW);
3311 case TCM_DELETEITEM:
3312 return TAB_DeleteItem (infoPtr, (INT)wParam);
3314 case TCM_DELETEALLITEMS:
3315 return TAB_DeleteAllItems (infoPtr);
3317 case TCM_GETITEMRECT:
3318 return TAB_GetItemRect (infoPtr, (INT)wParam, (LPRECT)lParam);
3320 case TCM_GETCURSEL:
3321 return TAB_GetCurSel (infoPtr);
3323 case TCM_HITTEST:
3324 return TAB_HitTest (infoPtr, (LPTCHITTESTINFO)lParam);
3326 case TCM_SETCURSEL:
3327 return TAB_SetCurSel (infoPtr, (INT)wParam);
3329 case TCM_INSERTITEMA:
3330 case TCM_INSERTITEMW:
3331 return TAB_InsertItemT (infoPtr, (INT)wParam, (TCITEMW*)lParam, uMsg == TCM_INSERTITEMW);
3333 case TCM_SETITEMEXTRA:
3334 return TAB_SetItemExtra (infoPtr, (INT)wParam);
3336 case TCM_ADJUSTRECT:
3337 return TAB_AdjustRect (infoPtr, (BOOL)wParam, (LPRECT)lParam);
3339 case TCM_SETITEMSIZE:
3340 return TAB_SetItemSize (infoPtr, (INT)LOWORD(lParam), (INT)HIWORD(lParam));
3342 case TCM_REMOVEIMAGE:
3343 return TAB_RemoveImage (infoPtr, (INT)wParam);
3345 case TCM_SETPADDING:
3346 return TAB_SetPadding (infoPtr, lParam);
3348 case TCM_GETROWCOUNT:
3349 return TAB_GetRowCount(infoPtr);
3351 case TCM_GETUNICODEFORMAT:
3352 return TAB_GetUnicodeFormat (infoPtr);
3354 case TCM_SETUNICODEFORMAT:
3355 return TAB_SetUnicodeFormat (infoPtr, (BOOL)wParam);
3357 case TCM_HIGHLIGHTITEM:
3358 return TAB_HighlightItem (infoPtr, (INT)wParam, (BOOL)LOWORD(lParam));
3360 case TCM_GETTOOLTIPS:
3361 return TAB_GetToolTips (infoPtr);
3363 case TCM_SETTOOLTIPS:
3364 return TAB_SetToolTips (infoPtr, (HWND)wParam);
3366 case TCM_GETCURFOCUS:
3367 return TAB_GetCurFocus (infoPtr);
3369 case TCM_SETCURFOCUS:
3370 return TAB_SetCurFocus (infoPtr, (INT)wParam);
3372 case TCM_SETMINTABWIDTH:
3373 return TAB_SetMinTabWidth(infoPtr, (INT)lParam);
3375 case TCM_DESELECTALL:
3376 return TAB_DeselectAll (infoPtr, (BOOL)wParam);
3378 case TCM_GETEXTENDEDSTYLE:
3379 return TAB_GetExtendedStyle (infoPtr);
3381 case TCM_SETEXTENDEDSTYLE:
3382 return TAB_SetExtendedStyle (infoPtr, wParam, lParam);
3384 case WM_GETFONT:
3385 return TAB_GetFont (infoPtr);
3387 case WM_SETFONT:
3388 return TAB_SetFont (infoPtr, (HFONT)wParam);
3390 case WM_CREATE:
3391 return TAB_Create (hwnd, lParam);
3393 case WM_NCDESTROY:
3394 return TAB_Destroy (infoPtr);
3396 case WM_GETDLGCODE:
3397 return DLGC_WANTARROWS | DLGC_WANTCHARS;
3399 case WM_LBUTTONDOWN:
3400 return TAB_LButtonDown (infoPtr, wParam, lParam);
3402 case WM_LBUTTONUP:
3403 return TAB_LButtonUp (infoPtr);
3405 case WM_NOTIFY:
3406 return SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, wParam, lParam);
3408 case WM_RBUTTONUP:
3409 TAB_RButtonUp (infoPtr);
3410 return DefWindowProcW (hwnd, uMsg, wParam, lParam);
3412 case WM_MOUSEMOVE:
3413 return TAB_MouseMove (infoPtr, wParam, lParam);
3415 case WM_PRINTCLIENT:
3416 case WM_PAINT:
3417 return TAB_Paint (infoPtr, (HDC)wParam);
3419 case WM_SIZE:
3420 return TAB_Size (infoPtr);
3422 case WM_SETREDRAW:
3423 return TAB_SetRedraw (infoPtr, (BOOL)wParam);
3425 case WM_HSCROLL:
3426 return TAB_OnHScroll(infoPtr, (int)LOWORD(wParam), (int)HIWORD(wParam));
3428 case WM_STYLECHANGED:
3429 return TAB_StyleChanged(infoPtr, wParam, (LPSTYLESTRUCT)lParam);
3431 case WM_SYSCOLORCHANGE:
3432 COMCTL32_RefreshSysColors();
3433 return 0;
3435 case WM_THEMECHANGED:
3436 return theme_changed (infoPtr);
3438 case WM_KILLFOCUS:
3439 TAB_KillFocus(infoPtr);
3440 case WM_SETFOCUS:
3441 TAB_FocusChanging(infoPtr);
3442 break; /* Don't disturb normal focus behavior */
3444 case WM_KEYDOWN:
3445 return TAB_KeyDown(infoPtr, wParam, lParam);
3447 case WM_NCHITTEST:
3448 return TAB_NCHitTest(infoPtr, lParam);
3450 case WM_NCCALCSIZE:
3451 return TAB_NCCalcSize(wParam);
3453 default:
3454 if (uMsg >= WM_USER && uMsg < WM_APP && !COMCTL32_IsReflectedMessage(uMsg))
3455 WARN("unknown msg %04x wp=%08lx lp=%08lx\n",
3456 uMsg, wParam, lParam);
3457 break;
3459 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
3463 void
3464 TAB_Register (void)
3466 WNDCLASSW wndClass;
3468 ZeroMemory (&wndClass, sizeof(WNDCLASSW));
3469 wndClass.style = CS_GLOBALCLASS | CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
3470 wndClass.lpfnWndProc = TAB_WindowProc;
3471 wndClass.cbClsExtra = 0;
3472 wndClass.cbWndExtra = sizeof(TAB_INFO *);
3473 wndClass.hCursor = LoadCursorW (0, (LPWSTR)IDC_ARROW);
3474 wndClass.hbrBackground = (HBRUSH)(COLOR_BTNFACE+1);
3475 wndClass.lpszClassName = WC_TABCONTROLW;
3477 RegisterClassW (&wndClass);
3481 void
3482 TAB_Unregister (void)
3484 UnregisterClassW (WC_TABCONTROLW, NULL);