comctl32/tab: Fix invalid read of item data.
[wine/multimedia.git] / dlls / comctl32 / tab.c
blobf86fd0f292a7feddf63dbd5c649e505c45edbe94
1 /*
2 * Tab control
4 * Copyright 1998 Anders Carlsson
5 * Copyright 1999 Alex Priem <alexp@sci.kun.nl>
6 * Copyright 1999 Francis Beaudet
7 * Copyright 2003 Vitaliy Margolen
9 * This library is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Lesser General Public
11 * License as published by the Free Software Foundation; either
12 * version 2.1 of the License, or (at your option) any later version.
14 * This library is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Lesser General Public License for more details.
19 * You should have received a copy of the GNU Lesser General Public
20 * License along with this library; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
23 * NOTES
25 * This code was audited for completeness against the documented features
26 * of Comctl32.dll version 6.0 on May. 20, 2005, by James Hawkins.
28 * Unless otherwise noted, we believe this code to be complete, as per
29 * the specification mentioned above.
30 * If you discover missing features, or bugs, please note them below.
32 * TODO:
34 * Styles:
35 * TCS_MULTISELECT - implement for VK_SPACE selection
36 * TCS_RIGHT
37 * TCS_RIGHTJUSTIFY
38 * TCS_SCROLLOPPOSITE
39 * TCS_SINGLELINE
40 * TCIF_RTLREADING
42 * Extended Styles:
43 * TCS_EX_REGISTERDROP
45 * Notifications:
46 * NM_RELEASEDCAPTURE
47 * TCN_FOCUSCHANGE
48 * TCN_GETOBJECT
50 * Macros:
51 * TabCtrl_AdjustRect
55 #include <stdarg.h>
56 #include <string.h>
58 #include "windef.h"
59 #include "winbase.h"
60 #include "wingdi.h"
61 #include "winuser.h"
62 #include "winnls.h"
63 #include "commctrl.h"
64 #include "comctl32.h"
65 #include "uxtheme.h"
66 #include "tmschema.h"
67 #include "wine/debug.h"
68 #include <math.h>
70 WINE_DEFAULT_DEBUG_CHANNEL(tab);
72 typedef struct
74 DWORD dwState;
75 LPWSTR pszText;
76 INT iImage;
77 RECT rect; /* bounding rectangle of the item relative to the
78 * leftmost item (the leftmost item, 0, would have a
79 * "left" member of 0 in this rectangle)
81 * additionally the top member holds the row number
82 * and bottom is unused and should be 0 */
83 BYTE extra[1]; /* Space for caller supplied info, variable size */
84 } TAB_ITEM;
86 /* The size of a tab item depends on how much extra data is requested.
87 TCM_INSERTITEM always stores at least LPARAM sized data. */
88 #define EXTRA_ITEM_SIZE(infoPtr) (max((infoPtr)->cbInfo, sizeof(LPARAM)))
89 #define TAB_ITEM_SIZE(infoPtr) FIELD_OFFSET(TAB_ITEM, extra[EXTRA_ITEM_SIZE(infoPtr)])
91 typedef struct
93 HWND hwnd; /* Tab control window */
94 HWND hwndNotify; /* notification window (parent) */
95 UINT uNumItem; /* number of tab items */
96 UINT uNumRows; /* number of tab rows */
97 INT tabHeight; /* height of the tab row */
98 INT tabWidth; /* width of tabs */
99 INT tabMinWidth; /* minimum width of items */
100 USHORT uHItemPadding; /* amount of horizontal padding, in pixels */
101 USHORT uVItemPadding; /* amount of vertical padding, in pixels */
102 USHORT uHItemPadding_s; /* Set amount of horizontal padding, in pixels */
103 USHORT uVItemPadding_s; /* Set amount of vertical padding, in pixels */
104 HFONT hFont; /* handle to the current font */
105 HCURSOR hcurArrow; /* handle to the current cursor */
106 HIMAGELIST himl; /* handle to an image list (may be 0) */
107 HWND hwndToolTip; /* handle to tab's tooltip */
108 INT leftmostVisible; /* Used for scrolling, this member contains
109 * the index of the first visible item */
110 INT iSelected; /* the currently selected item */
111 INT iHotTracked; /* the highlighted item under the mouse */
112 INT uFocus; /* item which has the focus */
113 TAB_ITEM* items; /* pointer to an array of TAB_ITEM's */
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 */
125 } TAB_INFO;
127 /******************************************************************************
128 * Positioning constants
130 #define SELECTED_TAB_OFFSET 2
131 #define ROUND_CORNER_SIZE 2
132 #define DISPLAY_AREA_PADDINGX 2
133 #define DISPLAY_AREA_PADDINGY 2
134 #define CONTROL_BORDER_SIZEX 2
135 #define CONTROL_BORDER_SIZEY 2
136 #define BUTTON_SPACINGX 3
137 #define BUTTON_SPACINGY 3
138 #define FLAT_BTN_SPACINGX 8
139 #define DEFAULT_MIN_TAB_WIDTH 54
140 #define DEFAULT_PADDING_X 6
141 #define EXTRA_ICON_PADDING 3
143 #define TAB_GetInfoPtr(hwnd) ((TAB_INFO *)GetWindowLongPtrW(hwnd,0))
144 /* Since items are variable sized, cannot directly access them */
145 #define TAB_GetItem(info,i) \
146 ((TAB_ITEM*)((LPBYTE)info->items + (i) * TAB_ITEM_SIZE(info)))
148 #define GET_DEFAULT_MIN_TAB_WIDTH(infoPtr) (DEFAULT_MIN_TAB_WIDTH - (DEFAULT_PADDING_X - (infoPtr)->uHItemPadding) * 2)
150 /******************************************************************************
151 * Hot-tracking timer constants
153 #define TAB_HOTTRACK_TIMER 1
154 #define TAB_HOTTRACK_TIMER_INTERVAL 100 /* milliseconds */
156 static const WCHAR themeClass[] = { 'T','a','b',0 };
158 /******************************************************************************
159 * Prototypes
161 static void TAB_InvalidateTabArea(const TAB_INFO *);
162 static void TAB_EnsureSelectionVisible(TAB_INFO *);
163 static void TAB_DrawItemInterior(const TAB_INFO *, HDC, INT, RECT*);
164 static LRESULT TAB_DeselectAll(TAB_INFO *, BOOL);
165 static BOOL TAB_InternalGetItemRect(const TAB_INFO *, INT, RECT*, RECT*);
167 static BOOL
168 TAB_SendSimpleNotify (const TAB_INFO *infoPtr, UINT code)
170 NMHDR nmhdr;
172 nmhdr.hwndFrom = infoPtr->hwnd;
173 nmhdr.idFrom = GetWindowLongPtrW(infoPtr->hwnd, GWLP_ID);
174 nmhdr.code = code;
176 return (BOOL) SendMessageW (infoPtr->hwndNotify, WM_NOTIFY,
177 nmhdr.idFrom, (LPARAM) &nmhdr);
180 static void
181 TAB_RelayEvent (HWND hwndTip, HWND hwndMsg, UINT uMsg,
182 WPARAM wParam, LPARAM lParam)
184 MSG msg;
186 msg.hwnd = hwndMsg;
187 msg.message = uMsg;
188 msg.wParam = wParam;
189 msg.lParam = lParam;
190 msg.time = GetMessageTime ();
191 msg.pt.x = (short)LOWORD(GetMessagePos ());
192 msg.pt.y = (short)HIWORD(GetMessagePos ());
194 SendMessageW (hwndTip, TTM_RELAYEVENT, 0, (LPARAM)&msg);
197 static void
198 TAB_DumpItemExternalT(const TCITEMW *pti, UINT iItem, BOOL isW)
200 if (TRACE_ON(tab)) {
201 TRACE("external tab %d, mask=0x%08x, dwState=0x%08x, dwStateMask=0x%08x, cchTextMax=0x%08x\n",
202 iItem, pti->mask, pti->dwState, pti->dwStateMask, pti->cchTextMax);
203 TRACE("external tab %d, iImage=%d, lParam=0x%08lx, pszTextW=%s\n",
204 iItem, pti->iImage, pti->lParam, isW ? debugstr_w(pti->pszText) : debugstr_a((LPSTR)pti->pszText));
208 static void
209 TAB_DumpItemInternal(const TAB_INFO *infoPtr, UINT iItem)
211 if (TRACE_ON(tab)) {
212 TAB_ITEM *ti;
214 ti = TAB_GetItem(infoPtr, iItem);
215 TRACE("tab %d, dwState=0x%08x, pszText=%s, iImage=%d\n",
216 iItem, ti->dwState, debugstr_w(ti->pszText), ti->iImage);
217 TRACE("tab %d, rect.left=%d, rect.top(row)=%d\n",
218 iItem, ti->rect.left, ti->rect.top);
222 /* RETURNS
223 * the index of the selected tab, or -1 if no tab is selected. */
224 static inline LRESULT TAB_GetCurSel (const TAB_INFO *infoPtr)
226 TRACE("(%p)\n", infoPtr);
227 return infoPtr->iSelected;
230 /* RETURNS
231 * the index of the tab item that has the focus. */
232 static inline LRESULT
233 TAB_GetCurFocus (const TAB_INFO *infoPtr)
235 TRACE("(%p)\n", infoPtr);
236 return infoPtr->uFocus;
239 static inline LRESULT TAB_GetToolTips (const TAB_INFO *infoPtr)
241 TRACE("(%p)\n", infoPtr);
242 return (LRESULT)infoPtr->hwndToolTip;
245 static inline LRESULT TAB_SetCurSel (TAB_INFO *infoPtr, INT iItem)
247 INT prevItem = infoPtr->iSelected;
249 TRACE("(%p %d)\n", infoPtr, iItem);
251 if (iItem < 0)
252 infoPtr->iSelected = -1;
253 else if (iItem >= infoPtr->uNumItem)
254 return -1;
255 else {
256 if (prevItem != iItem) {
257 if (prevItem != -1)
258 TAB_GetItem(infoPtr, prevItem)->dwState &= ~TCIS_BUTTONPRESSED;
259 TAB_GetItem(infoPtr, iItem)->dwState |= TCIS_BUTTONPRESSED;
261 infoPtr->iSelected = iItem;
262 infoPtr->uFocus = iItem;
263 TAB_EnsureSelectionVisible(infoPtr);
264 TAB_InvalidateTabArea(infoPtr);
267 return prevItem;
270 static LRESULT TAB_SetCurFocus (TAB_INFO *infoPtr, INT iItem)
272 TRACE("(%p %d)\n", infoPtr, iItem);
274 if (iItem < 0) {
275 infoPtr->uFocus = -1;
276 if (infoPtr->iSelected != -1) {
277 infoPtr->iSelected = -1;
278 TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGE);
279 TAB_InvalidateTabArea(infoPtr);
282 else if (iItem < infoPtr->uNumItem) {
283 if (infoPtr->dwStyle & TCS_BUTTONS) {
284 /* set focus to new item, leave selection as is */
285 if (infoPtr->uFocus != iItem) {
286 INT prev_focus = infoPtr->uFocus;
287 RECT r;
289 infoPtr->uFocus = iItem;
291 if (prev_focus != infoPtr->iSelected) {
292 if (TAB_InternalGetItemRect(infoPtr, prev_focus, &r, NULL))
293 InvalidateRect(infoPtr->hwnd, &r, FALSE);
296 if (TAB_InternalGetItemRect(infoPtr, iItem, &r, NULL))
297 InvalidateRect(infoPtr->hwnd, &r, FALSE);
299 TAB_SendSimpleNotify(infoPtr, TCN_FOCUSCHANGE);
301 } else {
302 INT oldFocus = infoPtr->uFocus;
303 if (infoPtr->iSelected != iItem || oldFocus == -1 ) {
304 infoPtr->uFocus = iItem;
305 if (oldFocus != -1) {
306 if (!TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGING)) {
307 infoPtr->iSelected = iItem;
308 TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGE);
310 else
311 infoPtr->iSelected = iItem;
312 TAB_EnsureSelectionVisible(infoPtr);
313 TAB_InvalidateTabArea(infoPtr);
318 return 0;
321 static inline LRESULT
322 TAB_SetToolTips (TAB_INFO *infoPtr, HWND hwndToolTip)
324 TRACE("%p %p\n", infoPtr, hwndToolTip);
325 infoPtr->hwndToolTip = hwndToolTip;
326 return 0;
329 static inline LRESULT
330 TAB_SetPadding (TAB_INFO *infoPtr, LPARAM lParam)
332 TRACE("(%p %d %d)\n", infoPtr, LOWORD(lParam), HIWORD(lParam));
333 infoPtr->uHItemPadding_s = LOWORD(lParam);
334 infoPtr->uVItemPadding_s = HIWORD(lParam);
336 return 0;
339 /******************************************************************************
340 * TAB_InternalGetItemRect
342 * This method will calculate the rectangle representing a given tab item in
343 * client coordinates. This method takes scrolling into account.
345 * This method returns TRUE if the item is visible in the window and FALSE
346 * if it is completely outside the client area.
348 static BOOL TAB_InternalGetItemRect(
349 const TAB_INFO* infoPtr,
350 INT itemIndex,
351 RECT* itemRect,
352 RECT* selectedRect)
354 RECT tmpItemRect,clientRect;
356 /* Perform a sanity check and a trivial visibility check. */
357 if ( (infoPtr->uNumItem <= 0) ||
358 (itemIndex >= infoPtr->uNumItem) ||
359 (!(((infoPtr->dwStyle & TCS_MULTILINE) || (infoPtr->dwStyle & TCS_VERTICAL))) &&
360 (itemIndex < infoPtr->leftmostVisible)))
362 TRACE("Not Visible\n");
363 /* need to initialize these to empty rects */
364 if (itemRect)
366 memset(itemRect,0,sizeof(RECT));
367 itemRect->bottom = infoPtr->tabHeight;
369 if (selectedRect)
370 memset(selectedRect,0,sizeof(RECT));
371 return FALSE;
375 * Avoid special cases in this procedure by assigning the "out"
376 * parameters if the caller didn't supply them
378 if (itemRect == NULL)
379 itemRect = &tmpItemRect;
381 /* Retrieve the unmodified item rect. */
382 *itemRect = TAB_GetItem(infoPtr,itemIndex)->rect;
384 /* calculate the times bottom and top based on the row */
385 GetClientRect(infoPtr->hwnd, &clientRect);
387 if ((infoPtr->dwStyle & TCS_BOTTOM) && (infoPtr->dwStyle & TCS_VERTICAL))
389 itemRect->right = clientRect.right - SELECTED_TAB_OFFSET - itemRect->left * infoPtr->tabHeight -
390 ((infoPtr->dwStyle & TCS_BUTTONS) ? itemRect->left * BUTTON_SPACINGX : 0);
391 itemRect->left = itemRect->right - infoPtr->tabHeight;
393 else if (infoPtr->dwStyle & TCS_VERTICAL)
395 itemRect->left = clientRect.left + SELECTED_TAB_OFFSET + itemRect->left * infoPtr->tabHeight +
396 ((infoPtr->dwStyle & TCS_BUTTONS) ? itemRect->left * BUTTON_SPACINGX : 0);
397 itemRect->right = itemRect->left + infoPtr->tabHeight;
399 else if (infoPtr->dwStyle & TCS_BOTTOM)
401 itemRect->bottom = clientRect.bottom - itemRect->top * infoPtr->tabHeight -
402 ((infoPtr->dwStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : SELECTED_TAB_OFFSET);
403 itemRect->top = itemRect->bottom - infoPtr->tabHeight;
405 else /* not TCS_BOTTOM and not TCS_VERTICAL */
407 itemRect->top = clientRect.top + itemRect->top * infoPtr->tabHeight +
408 ((infoPtr->dwStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : SELECTED_TAB_OFFSET);
409 itemRect->bottom = itemRect->top + infoPtr->tabHeight;
413 * "scroll" it to make sure the item at the very left of the
414 * tab control is the leftmost visible tab.
416 if(infoPtr->dwStyle & TCS_VERTICAL)
418 OffsetRect(itemRect,
420 -TAB_GetItem(infoPtr, infoPtr->leftmostVisible)->rect.top);
423 * Move the rectangle so the first item is slightly offset from
424 * the bottom of the tab control.
426 OffsetRect(itemRect,
428 SELECTED_TAB_OFFSET);
430 } else
432 OffsetRect(itemRect,
433 -TAB_GetItem(infoPtr, infoPtr->leftmostVisible)->rect.left,
437 * Move the rectangle so the first item is slightly offset from
438 * the left of the tab control.
440 OffsetRect(itemRect,
441 SELECTED_TAB_OFFSET,
444 TRACE("item %d tab h=%d, rect=(%s)\n",
445 itemIndex, infoPtr->tabHeight, wine_dbgstr_rect(itemRect));
447 /* Now, calculate the position of the item as if it were selected. */
448 if (selectedRect!=NULL)
450 CopyRect(selectedRect, itemRect);
452 /* The rectangle of a selected item is a bit wider. */
453 if(infoPtr->dwStyle & TCS_VERTICAL)
454 InflateRect(selectedRect, 0, SELECTED_TAB_OFFSET);
455 else
456 InflateRect(selectedRect, SELECTED_TAB_OFFSET, 0);
458 /* If it also a bit higher. */
459 if ((infoPtr->dwStyle & TCS_BOTTOM) && (infoPtr->dwStyle & TCS_VERTICAL))
461 selectedRect->left -= 2; /* the border is thicker on the right */
462 selectedRect->right += SELECTED_TAB_OFFSET;
464 else if (infoPtr->dwStyle & TCS_VERTICAL)
466 selectedRect->left -= SELECTED_TAB_OFFSET;
467 selectedRect->right += 1;
469 else if (infoPtr->dwStyle & TCS_BOTTOM)
471 selectedRect->bottom += SELECTED_TAB_OFFSET;
473 else /* not TCS_BOTTOM and not TCS_VERTICAL */
475 selectedRect->top -= SELECTED_TAB_OFFSET;
476 selectedRect->bottom -= 1;
480 /* Check for visibility */
481 if (infoPtr->dwStyle & TCS_VERTICAL)
482 return (itemRect->top < clientRect.bottom) && (itemRect->bottom > clientRect.top);
483 else
484 return (itemRect->left < clientRect.right) && (itemRect->right > clientRect.left);
487 static inline BOOL
488 TAB_GetItemRect(const TAB_INFO *infoPtr, INT item, RECT *rect)
490 TRACE("(%p, %d, %p)\n", infoPtr, item, rect);
491 return TAB_InternalGetItemRect(infoPtr, item, rect, NULL);
494 /******************************************************************************
495 * TAB_KeyDown
497 * This method is called to handle keyboard input
499 static LRESULT TAB_KeyDown(TAB_INFO* infoPtr, WPARAM keyCode, LPARAM lParam)
501 INT newItem = -1;
502 NMTCKEYDOWN nm;
504 /* TCN_KEYDOWN notification sent always */
505 nm.hdr.hwndFrom = infoPtr->hwnd;
506 nm.hdr.idFrom = GetWindowLongPtrW(infoPtr->hwnd, GWLP_ID);
507 nm.hdr.code = TCN_KEYDOWN;
508 nm.wVKey = keyCode;
509 nm.flags = lParam;
510 SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, nm.hdr.idFrom, (LPARAM)&nm);
512 switch (keyCode)
514 case VK_LEFT:
515 newItem = infoPtr->uFocus - 1;
516 break;
517 case VK_RIGHT:
518 newItem = infoPtr->uFocus + 1;
519 break;
522 /* If we changed to a valid item, change focused item */
523 if (newItem >= 0 && newItem < infoPtr->uNumItem && infoPtr->uFocus != newItem)
524 TAB_SetCurFocus(infoPtr, newItem);
526 return 0;
530 * WM_KILLFOCUS handler
532 static void TAB_KillFocus(TAB_INFO *infoPtr)
534 /* clear current focused item back to selected for TCS_BUTTONS */
535 if ((infoPtr->dwStyle & TCS_BUTTONS) && (infoPtr->uFocus != infoPtr->iSelected))
537 RECT r;
539 if (TAB_InternalGetItemRect(infoPtr, infoPtr->uFocus, &r, NULL))
540 InvalidateRect(infoPtr->hwnd, &r, FALSE);
542 infoPtr->uFocus = infoPtr->iSelected;
546 /******************************************************************************
547 * TAB_FocusChanging
549 * This method is called whenever the focus goes in or out of this control
550 * it is used to update the visual state of the control.
552 static void TAB_FocusChanging(const TAB_INFO *infoPtr)
554 RECT selectedRect;
555 BOOL isVisible;
558 * Get the rectangle for the item.
560 isVisible = TAB_InternalGetItemRect(infoPtr,
561 infoPtr->uFocus,
562 NULL,
563 &selectedRect);
566 * If the rectangle is not completely invisible, invalidate that
567 * portion of the window.
569 if (isVisible)
571 TRACE("invalidate (%s)\n", wine_dbgstr_rect(&selectedRect));
572 InvalidateRect(infoPtr->hwnd, &selectedRect, TRUE);
576 static INT TAB_InternalHitTest (const TAB_INFO *infoPtr, POINT pt, UINT *flags)
578 RECT rect;
579 INT iCount;
581 for (iCount = 0; iCount < infoPtr->uNumItem; iCount++)
583 TAB_InternalGetItemRect(infoPtr, iCount, &rect, NULL);
585 if (PtInRect(&rect, pt))
587 *flags = TCHT_ONITEM;
588 return iCount;
592 *flags = TCHT_NOWHERE;
593 return -1;
596 static inline LRESULT
597 TAB_HitTest (const TAB_INFO *infoPtr, LPTCHITTESTINFO lptest)
599 TRACE("(%p, %p)\n", infoPtr, lptest);
600 return TAB_InternalHitTest (infoPtr, lptest->pt, &lptest->flags);
603 /******************************************************************************
604 * TAB_NCHitTest
606 * Napster v2b5 has a tab control for its main navigation which has a client
607 * area that covers the whole area of the dialog pages.
608 * That's why it receives all msgs for that area and the underlying dialog ctrls
609 * are dead.
610 * So I decided that we should handle WM_NCHITTEST here and return
611 * HTTRANSPARENT if we don't hit the tab control buttons.
612 * FIXME: WM_NCHITTEST handling correct ? Fix it if you know that Windows
613 * doesn't do it that way. Maybe depends on tab control styles ?
615 static inline LRESULT
616 TAB_NCHitTest (const TAB_INFO *infoPtr, LPARAM lParam)
618 POINT pt;
619 UINT dummyflag;
621 pt.x = (short)LOWORD(lParam);
622 pt.y = (short)HIWORD(lParam);
623 ScreenToClient(infoPtr->hwnd, &pt);
625 if (TAB_InternalHitTest(infoPtr, pt, &dummyflag) == -1)
626 return HTTRANSPARENT;
627 else
628 return HTCLIENT;
631 static LRESULT
632 TAB_LButtonDown (TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
634 POINT pt;
635 INT newItem;
636 UINT dummy;
638 if (infoPtr->hwndToolTip)
639 TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
640 WM_LBUTTONDOWN, wParam, lParam);
642 if (!(infoPtr->dwStyle & TCS_FOCUSNEVER)) {
643 SetFocus (infoPtr->hwnd);
646 if (infoPtr->hwndToolTip)
647 TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
648 WM_LBUTTONDOWN, wParam, lParam);
650 pt.x = (short)LOWORD(lParam);
651 pt.y = (short)HIWORD(lParam);
653 newItem = TAB_InternalHitTest (infoPtr, pt, &dummy);
655 TRACE("On Tab, item %d\n", newItem);
657 if ((newItem != -1) && (infoPtr->iSelected != newItem))
659 if ((infoPtr->dwStyle & TCS_BUTTONS) && (infoPtr->dwStyle & TCS_MULTISELECT) &&
660 (wParam & MK_CONTROL))
662 RECT r;
664 /* toggle multiselection */
665 TAB_GetItem(infoPtr, newItem)->dwState ^= TCIS_BUTTONPRESSED;
666 if (TAB_InternalGetItemRect (infoPtr, newItem, &r, NULL))
667 InvalidateRect (infoPtr->hwnd, &r, TRUE);
669 else
671 INT i;
672 BOOL pressed = FALSE;
674 /* any button pressed ? */
675 for (i = 0; i < infoPtr->uNumItem; i++)
676 if ((TAB_GetItem (infoPtr, i)->dwState & TCIS_BUTTONPRESSED) &&
677 (infoPtr->iSelected != i))
679 pressed = TRUE;
680 break;
683 TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGING);
685 if (pressed)
686 TAB_DeselectAll (infoPtr, FALSE);
687 else
688 TAB_SetCurSel(infoPtr, newItem);
690 TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGE);
694 return 0;
697 static inline LRESULT
698 TAB_LButtonUp (const TAB_INFO *infoPtr)
700 TAB_SendSimpleNotify(infoPtr, NM_CLICK);
702 return 0;
705 static inline LRESULT
706 TAB_RButtonDown (const TAB_INFO *infoPtr)
708 TAB_SendSimpleNotify(infoPtr, NM_RCLICK);
709 return 0;
712 /******************************************************************************
713 * TAB_DrawLoneItemInterior
715 * This calls TAB_DrawItemInterior. However, TAB_DrawItemInterior is normally
716 * called by TAB_DrawItem which is normally called by TAB_Refresh which sets
717 * up the device context and font. This routine does the same setup but
718 * only calls TAB_DrawItemInterior for the single specified item.
720 static void
721 TAB_DrawLoneItemInterior(const TAB_INFO* infoPtr, int iItem)
723 HDC hdc = GetDC(infoPtr->hwnd);
724 RECT r, rC;
726 /* Clip UpDown control to not draw over it */
727 if (infoPtr->needsScrolling)
729 GetWindowRect(infoPtr->hwnd, &rC);
730 GetWindowRect(infoPtr->hwndUpDown, &r);
731 ExcludeClipRect(hdc, r.left - rC.left, r.top - rC.top, r.right - rC.left, r.bottom - rC.top);
733 TAB_DrawItemInterior(infoPtr, hdc, iItem, NULL);
734 ReleaseDC(infoPtr->hwnd, hdc);
737 /* update a tab after hottracking - invalidate it or just redraw the interior,
738 * based on whether theming is used or not */
739 static inline void hottrack_refresh(const TAB_INFO *infoPtr, int tabIndex)
741 if (tabIndex == -1) return;
743 if (GetWindowTheme (infoPtr->hwnd))
745 RECT rect;
746 TAB_InternalGetItemRect(infoPtr, tabIndex, &rect, NULL);
747 InvalidateRect (infoPtr->hwnd, &rect, FALSE);
749 else
750 TAB_DrawLoneItemInterior(infoPtr, tabIndex);
753 /******************************************************************************
754 * TAB_HotTrackTimerProc
756 * When a mouse-move event causes a tab to be highlighted (hot-tracking), a
757 * timer is setup so we can check if the mouse is moved out of our window.
758 * (We don't get an event when the mouse leaves, the mouse-move events just
759 * stop being delivered to our window and just start being delivered to
760 * another window.) This function is called when the timer triggers so
761 * we can check if the mouse has left our window. If so, we un-highlight
762 * the hot-tracked tab.
764 static void CALLBACK
765 TAB_HotTrackTimerProc
767 HWND hwnd, /* handle of window for timer messages */
768 UINT uMsg, /* WM_TIMER message */
769 UINT_PTR idEvent, /* timer identifier */
770 DWORD dwTime /* current system time */
773 TAB_INFO* infoPtr = TAB_GetInfoPtr(hwnd);
775 if (infoPtr != NULL && infoPtr->iHotTracked >= 0)
777 POINT pt;
780 ** If we can't get the cursor position, or if the cursor is outside our
781 ** window, we un-highlight the hot-tracked tab. Note that the cursor is
782 ** "outside" even if it is within our bounding rect if another window
783 ** overlaps. Note also that the case where the cursor stayed within our
784 ** window but has moved off the hot-tracked tab will be handled by the
785 ** WM_MOUSEMOVE event.
787 if (!GetCursorPos(&pt) || WindowFromPoint(pt) != hwnd)
789 /* Redraw iHotTracked to look normal */
790 INT iRedraw = infoPtr->iHotTracked;
791 infoPtr->iHotTracked = -1;
792 hottrack_refresh (infoPtr, iRedraw);
794 /* Kill this timer */
795 KillTimer(hwnd, TAB_HOTTRACK_TIMER);
800 /******************************************************************************
801 * TAB_RecalcHotTrack
803 * If a tab control has the TCS_HOTTRACK style, then the tab under the mouse
804 * should be highlighted. This function determines which tab in a tab control,
805 * if any, is under the mouse and records that information. The caller may
806 * supply output parameters to receive the item number of the tab item which
807 * was highlighted but isn't any longer and of the tab item which is now
808 * highlighted but wasn't previously. The caller can use this information to
809 * selectively redraw those tab items.
811 * If the caller has a mouse position, it can supply it through the pos
812 * parameter. For example, TAB_MouseMove does this. Otherwise, the caller
813 * supplies NULL and this function determines the current mouse position
814 * itself.
816 static void
817 TAB_RecalcHotTrack
819 TAB_INFO* infoPtr,
820 const LPARAM* pos,
821 int* out_redrawLeave,
822 int* out_redrawEnter
825 int item = -1;
828 if (out_redrawLeave != NULL)
829 *out_redrawLeave = -1;
830 if (out_redrawEnter != NULL)
831 *out_redrawEnter = -1;
833 if ((infoPtr->dwStyle & TCS_HOTTRACK) || GetWindowTheme(infoPtr->hwnd))
835 POINT pt;
836 UINT flags;
838 if (pos == NULL)
840 GetCursorPos(&pt);
841 ScreenToClient(infoPtr->hwnd, &pt);
843 else
845 pt.x = (short)LOWORD(*pos);
846 pt.y = (short)HIWORD(*pos);
849 item = TAB_InternalHitTest(infoPtr, pt, &flags);
852 if (item != infoPtr->iHotTracked)
854 if (infoPtr->iHotTracked >= 0)
856 /* Mark currently hot-tracked to be redrawn to look normal */
857 if (out_redrawLeave != NULL)
858 *out_redrawLeave = infoPtr->iHotTracked;
860 if (item < 0)
862 /* Kill timer which forces recheck of mouse pos */
863 KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
866 else
868 /* Start timer so we recheck mouse pos */
869 UINT timerID = SetTimer
871 infoPtr->hwnd,
872 TAB_HOTTRACK_TIMER,
873 TAB_HOTTRACK_TIMER_INTERVAL,
874 TAB_HotTrackTimerProc
877 if (timerID == 0)
878 return; /* Hot tracking not available */
881 infoPtr->iHotTracked = item;
883 if (item >= 0)
885 /* Mark new hot-tracked to be redrawn to look highlighted */
886 if (out_redrawEnter != NULL)
887 *out_redrawEnter = item;
892 /******************************************************************************
893 * TAB_MouseMove
895 * Handles the mouse-move event. Updates tooltips. Updates hot-tracking.
897 static LRESULT
898 TAB_MouseMove (TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
900 int redrawLeave;
901 int redrawEnter;
903 if (infoPtr->hwndToolTip)
904 TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
905 WM_LBUTTONDOWN, wParam, lParam);
907 /* Determine which tab to highlight. Redraw tabs which change highlight
908 ** status. */
909 TAB_RecalcHotTrack(infoPtr, &lParam, &redrawLeave, &redrawEnter);
911 hottrack_refresh (infoPtr, redrawLeave);
912 hottrack_refresh (infoPtr, redrawEnter);
914 return 0;
917 /******************************************************************************
918 * TAB_AdjustRect
920 * Calculates the tab control's display area given the window rectangle or
921 * the window rectangle given the requested display rectangle.
923 static LRESULT TAB_AdjustRect(const TAB_INFO *infoPtr, WPARAM fLarger, LPRECT prc)
925 LONG *iRightBottom, *iLeftTop;
927 TRACE ("hwnd=%p fLarger=%ld (%s)\n", infoPtr->hwnd, fLarger,
928 wine_dbgstr_rect(prc));
930 if (!prc) return -1;
932 if(infoPtr->dwStyle & TCS_VERTICAL)
934 iRightBottom = &(prc->right);
935 iLeftTop = &(prc->left);
937 else
939 iRightBottom = &(prc->bottom);
940 iLeftTop = &(prc->top);
943 if (fLarger) /* Go from display rectangle */
945 /* Add the height of the tabs. */
946 if (infoPtr->dwStyle & TCS_BOTTOM)
947 *iRightBottom += infoPtr->tabHeight * infoPtr->uNumRows;
948 else
949 *iLeftTop -= infoPtr->tabHeight * infoPtr->uNumRows +
950 ((infoPtr->dwStyle & TCS_BUTTONS)? 3 * (infoPtr->uNumRows - 1) : 0);
952 /* Inflate the rectangle for the padding */
953 InflateRect(prc, DISPLAY_AREA_PADDINGX, DISPLAY_AREA_PADDINGY);
955 /* Inflate for the border */
956 InflateRect(prc, CONTROL_BORDER_SIZEX, CONTROL_BORDER_SIZEY);
958 else /* Go from window rectangle. */
960 /* Deflate the rectangle for the border */
961 InflateRect(prc, -CONTROL_BORDER_SIZEX, -CONTROL_BORDER_SIZEY);
963 /* Deflate the rectangle for the padding */
964 InflateRect(prc, -DISPLAY_AREA_PADDINGX, -DISPLAY_AREA_PADDINGY);
966 /* Remove the height of the tabs. */
967 if (infoPtr->dwStyle & TCS_BOTTOM)
968 *iRightBottom -= infoPtr->tabHeight * infoPtr->uNumRows;
969 else
970 *iLeftTop += (infoPtr->tabHeight) * infoPtr->uNumRows +
971 ((infoPtr->dwStyle & TCS_BUTTONS)? 3 * (infoPtr->uNumRows - 1) : 0);
974 return 0;
977 /******************************************************************************
978 * TAB_OnHScroll
980 * This method will handle the notification from the scroll control and
981 * perform the scrolling operation on the tab control.
983 static LRESULT TAB_OnHScroll(TAB_INFO *infoPtr, int nScrollCode, int nPos)
985 if(nScrollCode == SB_THUMBPOSITION && nPos != infoPtr->leftmostVisible)
987 if(nPos < infoPtr->leftmostVisible)
988 infoPtr->leftmostVisible--;
989 else
990 infoPtr->leftmostVisible++;
992 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
993 TAB_InvalidateTabArea(infoPtr);
994 SendMessageW(infoPtr->hwndUpDown, UDM_SETPOS, 0,
995 MAKELONG(infoPtr->leftmostVisible, 0));
998 return 0;
1001 /******************************************************************************
1002 * TAB_SetupScrolling
1004 * This method will check the current scrolling state and make sure the
1005 * scrolling control is displayed (or not).
1007 static void TAB_SetupScrolling(
1008 TAB_INFO* infoPtr,
1009 const RECT* clientRect)
1011 static const WCHAR emptyW[] = { 0 };
1012 INT maxRange = 0;
1014 if (infoPtr->needsScrolling)
1016 RECT controlPos;
1017 INT vsize, tabwidth;
1020 * Calculate the position of the scroll control.
1022 if(infoPtr->dwStyle & TCS_VERTICAL)
1024 controlPos.right = clientRect->right;
1025 controlPos.left = controlPos.right - 2 * GetSystemMetrics(SM_CXHSCROLL);
1027 if (infoPtr->dwStyle & TCS_BOTTOM)
1029 controlPos.top = clientRect->bottom - infoPtr->tabHeight;
1030 controlPos.bottom = controlPos.top + GetSystemMetrics(SM_CYHSCROLL);
1032 else
1034 controlPos.bottom = clientRect->top + infoPtr->tabHeight;
1035 controlPos.top = controlPos.bottom - GetSystemMetrics(SM_CYHSCROLL);
1038 else
1040 controlPos.right = clientRect->right;
1041 controlPos.left = controlPos.right - 2 * GetSystemMetrics(SM_CXHSCROLL);
1043 if (infoPtr->dwStyle & TCS_BOTTOM)
1045 controlPos.top = clientRect->bottom - infoPtr->tabHeight;
1046 controlPos.bottom = controlPos.top + GetSystemMetrics(SM_CYHSCROLL);
1048 else
1050 controlPos.bottom = clientRect->top + infoPtr->tabHeight;
1051 controlPos.top = controlPos.bottom - GetSystemMetrics(SM_CYHSCROLL);
1056 * If we don't have a scroll control yet, we want to create one.
1057 * If we have one, we want to make sure it's positioned properly.
1059 if (infoPtr->hwndUpDown==0)
1061 infoPtr->hwndUpDown = CreateWindowW(UPDOWN_CLASSW, emptyW,
1062 WS_VISIBLE | WS_CHILD | UDS_HORZ,
1063 controlPos.left, controlPos.top,
1064 controlPos.right - controlPos.left,
1065 controlPos.bottom - controlPos.top,
1066 infoPtr->hwnd, NULL, NULL, NULL);
1068 else
1070 SetWindowPos(infoPtr->hwndUpDown,
1071 NULL,
1072 controlPos.left, controlPos.top,
1073 controlPos.right - controlPos.left,
1074 controlPos.bottom - controlPos.top,
1075 SWP_SHOWWINDOW | SWP_NOZORDER);
1078 /* Now calculate upper limit of the updown control range.
1079 * We do this by calculating how many tabs will be offscreen when the
1080 * last tab is visible.
1082 if(infoPtr->uNumItem)
1084 vsize = clientRect->right - (controlPos.right - controlPos.left + 1);
1085 maxRange = infoPtr->uNumItem;
1086 tabwidth = TAB_GetItem(infoPtr, infoPtr->uNumItem - 1)->rect.right;
1088 for(; maxRange > 0; maxRange--)
1090 if(tabwidth - TAB_GetItem(infoPtr,maxRange - 1)->rect.left > vsize)
1091 break;
1094 if(maxRange == infoPtr->uNumItem)
1095 maxRange--;
1098 else
1100 /* If we once had a scroll control... hide it */
1101 if (infoPtr->hwndUpDown)
1102 ShowWindow(infoPtr->hwndUpDown, SW_HIDE);
1104 if (infoPtr->hwndUpDown)
1105 SendMessageW(infoPtr->hwndUpDown, UDM_SETRANGE32, 0, maxRange);
1108 /******************************************************************************
1109 * TAB_SetItemBounds
1111 * This method will calculate the position rectangles of all the items in the
1112 * control. The rectangle calculated starts at 0 for the first item in the
1113 * list and ignores scrolling and selection.
1114 * It also uses the current font to determine the height of the tab row and
1115 * it checks if all the tabs fit in the client area of the window. If they
1116 * don't, a scrolling control is added.
1118 static void TAB_SetItemBounds (TAB_INFO *infoPtr)
1120 TEXTMETRICW fontMetrics;
1121 UINT curItem;
1122 INT curItemLeftPos;
1123 INT curItemRowCount;
1124 HFONT hFont, hOldFont;
1125 HDC hdc;
1126 RECT clientRect;
1127 INT iTemp;
1128 RECT* rcItem;
1129 INT iIndex;
1130 INT icon_width = 0;
1133 * We need to get text information so we need a DC and we need to select
1134 * a font.
1136 hdc = GetDC(infoPtr->hwnd);
1138 hFont = infoPtr->hFont ? infoPtr->hFont : GetStockObject (SYSTEM_FONT);
1139 hOldFont = SelectObject (hdc, hFont);
1142 * We will base the rectangle calculations on the client rectangle
1143 * of the control.
1145 GetClientRect(infoPtr->hwnd, &clientRect);
1147 /* if TCS_VERTICAL then swap the height and width so this code places the
1148 tabs along the top of the rectangle and we can just rotate them after
1149 rather than duplicate all of the below code */
1150 if(infoPtr->dwStyle & TCS_VERTICAL)
1152 iTemp = clientRect.bottom;
1153 clientRect.bottom = clientRect.right;
1154 clientRect.right = iTemp;
1157 /* Now use hPadding and vPadding */
1158 infoPtr->uHItemPadding = infoPtr->uHItemPadding_s;
1159 infoPtr->uVItemPadding = infoPtr->uVItemPadding_s;
1161 /* The leftmost item will be "0" aligned */
1162 curItemLeftPos = 0;
1163 curItemRowCount = infoPtr->uNumItem ? 1 : 0;
1165 if (!(infoPtr->fHeightSet))
1167 int item_height;
1168 INT icon_height = 0, cx;
1170 /* Use the current font to determine the height of a tab. */
1171 GetTextMetricsW(hdc, &fontMetrics);
1173 /* Get the icon height */
1174 if (infoPtr->himl)
1175 ImageList_GetIconSize(infoPtr->himl, &cx, &icon_height);
1177 /* Take the highest between font or icon */
1178 if (fontMetrics.tmHeight > icon_height)
1179 item_height = fontMetrics.tmHeight + 2;
1180 else
1181 item_height = icon_height;
1184 * Make sure there is enough space for the letters + icon + growing the
1185 * selected item + extra space for the selected item.
1187 infoPtr->tabHeight = item_height +
1188 ((infoPtr->dwStyle & TCS_BUTTONS) ? 2 : 1) *
1189 infoPtr->uVItemPadding;
1191 TRACE("tabH=%d, tmH=%d, iconh=%d\n",
1192 infoPtr->tabHeight, fontMetrics.tmHeight, icon_height);
1195 TRACE("client right=%d\n", clientRect.right);
1197 /* Get the icon width */
1198 if (infoPtr->himl)
1200 INT cy;
1202 ImageList_GetIconSize(infoPtr->himl, &icon_width, &cy);
1204 if (infoPtr->dwStyle & TCS_FIXEDWIDTH)
1205 icon_width += 4;
1206 else
1207 /* Add padding if icon is present */
1208 icon_width += infoPtr->uHItemPadding;
1211 for (curItem = 0; curItem < infoPtr->uNumItem; curItem++)
1213 TAB_ITEM *curr = TAB_GetItem(infoPtr, curItem);
1215 /* Set the leftmost position of the tab. */
1216 curr->rect.left = curItemLeftPos;
1218 if (infoPtr->dwStyle & TCS_FIXEDWIDTH)
1220 curr->rect.right = curr->rect.left +
1221 max(infoPtr->tabWidth, icon_width);
1223 else if (!curr->pszText)
1225 /* If no text use minimum tab width including padding. */
1226 if (infoPtr->tabMinWidth < 0)
1227 curr->rect.right = curr->rect.left + GET_DEFAULT_MIN_TAB_WIDTH(infoPtr);
1228 else
1230 curr->rect.right = curr->rect.left + infoPtr->tabMinWidth;
1232 /* Add extra padding if icon is present */
1233 if (infoPtr->himl && infoPtr->tabMinWidth > 0 && infoPtr->tabMinWidth < DEFAULT_MIN_TAB_WIDTH
1234 && infoPtr->uHItemPadding > 1)
1235 curr->rect.right += EXTRA_ICON_PADDING * (infoPtr->uHItemPadding-1);
1238 else
1240 int tabwidth;
1241 SIZE size;
1242 /* Calculate how wide the tab is depending on the text it contains */
1243 GetTextExtentPoint32W(hdc, curr->pszText,
1244 lstrlenW(curr->pszText), &size);
1246 tabwidth = size.cx + icon_width + 2 * infoPtr->uHItemPadding;
1248 if (infoPtr->tabMinWidth < 0)
1249 tabwidth = max(tabwidth, GET_DEFAULT_MIN_TAB_WIDTH(infoPtr));
1250 else
1251 tabwidth = max(tabwidth, infoPtr->tabMinWidth);
1253 curr->rect.right = curr->rect.left + tabwidth;
1254 TRACE("for <%s>, l,r=%d,%d\n",
1255 debugstr_w(curr->pszText), curr->rect.left, curr->rect.right);
1259 * Check if this is a multiline tab control and if so
1260 * check to see if we should wrap the tabs
1262 * Wrap all these tabs. We will arrange them evenly later.
1266 if (((infoPtr->dwStyle & TCS_MULTILINE) || (infoPtr->dwStyle & TCS_VERTICAL)) &&
1267 (curr->rect.right >
1268 (clientRect.right - CONTROL_BORDER_SIZEX - DISPLAY_AREA_PADDINGX)))
1270 curr->rect.right -= curr->rect.left;
1272 curr->rect.left = 0;
1273 curItemRowCount++;
1274 TRACE("wrapping <%s>, l,r=%d,%d\n", debugstr_w(curr->pszText),
1275 curr->rect.left, curr->rect.right);
1278 curr->rect.bottom = 0;
1279 curr->rect.top = curItemRowCount - 1;
1281 TRACE("Rect: %s\n", wine_dbgstr_rect(&curr->rect));
1284 * The leftmost position of the next item is the rightmost position
1285 * of this one.
1287 if (infoPtr->dwStyle & TCS_BUTTONS)
1289 curItemLeftPos = curr->rect.right + BUTTON_SPACINGX;
1290 if (infoPtr->dwStyle & TCS_FLATBUTTONS)
1291 curItemLeftPos += FLAT_BTN_SPACINGX;
1293 else
1294 curItemLeftPos = curr->rect.right;
1297 if (!((infoPtr->dwStyle & TCS_MULTILINE) || (infoPtr->dwStyle & TCS_VERTICAL)))
1300 * Check if we need a scrolling control.
1302 infoPtr->needsScrolling = (curItemLeftPos + (2 * SELECTED_TAB_OFFSET) >
1303 clientRect.right);
1305 /* Don't need scrolling, then update infoPtr->leftmostVisible */
1306 if(!infoPtr->needsScrolling)
1307 infoPtr->leftmostVisible = 0;
1309 else
1312 * No scrolling in Multiline or Vertical styles.
1314 infoPtr->needsScrolling = FALSE;
1315 infoPtr->leftmostVisible = 0;
1317 TAB_SetupScrolling(infoPtr, &clientRect);
1319 /* Set the number of rows */
1320 infoPtr->uNumRows = curItemRowCount;
1322 /* Arrange all tabs evenly if style says so */
1323 if (!(infoPtr->dwStyle & TCS_RAGGEDRIGHT) &&
1324 ((infoPtr->dwStyle & TCS_MULTILINE) || (infoPtr->dwStyle & TCS_VERTICAL)) &&
1325 (infoPtr->uNumItem > 0) &&
1326 (infoPtr->uNumRows > 1))
1328 INT tabPerRow,remTab,iRow;
1329 UINT iItm;
1330 INT iCount=0;
1333 * Ok windows tries to even out the rows. place the same
1334 * number of tabs in each row. So lets give that a shot
1337 tabPerRow = infoPtr->uNumItem / (infoPtr->uNumRows);
1338 remTab = infoPtr->uNumItem % (infoPtr->uNumRows);
1340 for (iItm=0,iRow=0,iCount=0,curItemLeftPos=0;
1341 iItm<infoPtr->uNumItem;
1342 iItm++,iCount++)
1344 /* normalize the current rect */
1345 TAB_ITEM *curr = TAB_GetItem(infoPtr, iItm);
1347 /* shift the item to the left side of the clientRect */
1348 curr->rect.right -= curr->rect.left;
1349 curr->rect.left = 0;
1351 TRACE("r=%d, cl=%d, cl.r=%d, iCount=%d, iRow=%d, uNumRows=%d, remTab=%d, tabPerRow=%d\n",
1352 curr->rect.right, curItemLeftPos, clientRect.right,
1353 iCount, iRow, infoPtr->uNumRows, remTab, tabPerRow);
1355 /* if we have reached the maximum number of tabs on this row */
1356 /* move to the next row, reset our current item left position and */
1357 /* the count of items on this row */
1359 if (infoPtr->dwStyle & TCS_VERTICAL) {
1360 /* Vert: Add the remaining tabs in the *last* remainder rows */
1361 if (iCount >= ((iRow>=(INT)infoPtr->uNumRows - remTab)?tabPerRow + 1:tabPerRow)) {
1362 iRow++;
1363 curItemLeftPos = 0;
1364 iCount = 0;
1366 } else {
1367 /* Horz: Add the remaining tabs in the *first* remainder rows */
1368 if (iCount >= ((iRow<remTab)?tabPerRow + 1:tabPerRow)) {
1369 iRow++;
1370 curItemLeftPos = 0;
1371 iCount = 0;
1375 /* shift the item to the right to place it as the next item in this row */
1376 curr->rect.left += curItemLeftPos;
1377 curr->rect.right += curItemLeftPos;
1378 curr->rect.top = iRow;
1379 if (infoPtr->dwStyle & TCS_BUTTONS)
1381 curItemLeftPos = curr->rect.right + 1;
1382 if (infoPtr->dwStyle & TCS_FLATBUTTONS)
1383 curItemLeftPos += FLAT_BTN_SPACINGX;
1385 else
1386 curItemLeftPos = curr->rect.right;
1388 TRACE("arranging <%s>, l,r=%d,%d, row=%d\n",
1389 debugstr_w(curr->pszText), curr->rect.left,
1390 curr->rect.right, curr->rect.top);
1394 * Justify the rows
1397 INT widthDiff, iIndexStart=0, iIndexEnd=0;
1398 INT remainder;
1399 INT iCount=0;
1401 while(iIndexStart < infoPtr->uNumItem)
1403 TAB_ITEM *start = TAB_GetItem(infoPtr, iIndexStart);
1406 * find the index of the row
1408 /* find the first item on the next row */
1409 for (iIndexEnd=iIndexStart;
1410 (iIndexEnd < infoPtr->uNumItem) &&
1411 (TAB_GetItem(infoPtr, iIndexEnd)->rect.top ==
1412 start->rect.top) ;
1413 iIndexEnd++)
1414 /* intentionally blank */;
1417 * we need to justify these tabs so they fill the whole given
1418 * client area
1421 /* find the amount of space remaining on this row */
1422 widthDiff = clientRect.right - (2 * SELECTED_TAB_OFFSET) -
1423 TAB_GetItem(infoPtr, iIndexEnd - 1)->rect.right;
1425 /* iCount is the number of tab items on this row */
1426 iCount = iIndexEnd - iIndexStart;
1428 if (iCount > 1)
1430 remainder = widthDiff % iCount;
1431 widthDiff = widthDiff / iCount;
1432 /* add widthDiff/iCount, or extra space/items on row, to each item on this row */
1433 for (iIndex=iIndexStart, iCount=0; iIndex < iIndexEnd; iIndex++, iCount++)
1435 TAB_ITEM *item = TAB_GetItem(infoPtr, iIndex);
1437 item->rect.left += iCount * widthDiff;
1438 item->rect.right += (iCount + 1) * widthDiff;
1440 TRACE("adjusting 1 <%s>, l,r=%d,%d\n",
1441 debugstr_w(item->pszText),
1442 item->rect.left, item->rect.right);
1445 TAB_GetItem(infoPtr, iIndex - 1)->rect.right += remainder;
1447 else /* we have only one item on this row, make it take up the entire row */
1449 start->rect.left = clientRect.left;
1450 start->rect.right = clientRect.right - 4;
1452 TRACE("adjusting 2 <%s>, l,r=%d,%d\n",
1453 debugstr_w(start->pszText),
1454 start->rect.left, start->rect.right);
1459 iIndexStart = iIndexEnd;
1464 /* if TCS_VERTICAL rotate the tabs so they are along the side of the clientRect */
1465 if(infoPtr->dwStyle & TCS_VERTICAL)
1467 RECT rcOriginal;
1468 for(iIndex = 0; iIndex < infoPtr->uNumItem; iIndex++)
1470 rcItem = &TAB_GetItem(infoPtr, iIndex)->rect;
1472 rcOriginal = *rcItem;
1474 /* this is rotating the items by 90 degrees clockwise around the center of the control */
1475 rcItem->top = (rcOriginal.left - clientRect.left);
1476 rcItem->bottom = rcItem->top + (rcOriginal.right - rcOriginal.left);
1477 rcItem->left = rcOriginal.top;
1478 rcItem->right = rcOriginal.bottom;
1482 TAB_EnsureSelectionVisible(infoPtr);
1483 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
1485 /* Cleanup */
1486 SelectObject (hdc, hOldFont);
1487 ReleaseDC (infoPtr->hwnd, hdc);
1491 static void
1492 TAB_EraseTabInterior(const TAB_INFO *infoPtr, HDC hdc, INT iItem, const RECT *drawRect)
1494 HBRUSH hbr = CreateSolidBrush (comctl32_color.clrBtnFace);
1495 BOOL deleteBrush = TRUE;
1496 RECT rTemp = *drawRect;
1498 if (infoPtr->dwStyle & TCS_BUTTONS)
1500 if (iItem == infoPtr->iSelected)
1502 /* Background color */
1503 if (!(infoPtr->dwStyle & TCS_OWNERDRAWFIXED))
1505 DeleteObject(hbr);
1506 hbr = GetSysColorBrush(COLOR_SCROLLBAR);
1508 SetTextColor(hdc, comctl32_color.clr3dFace);
1509 SetBkColor(hdc, comctl32_color.clr3dHilight);
1511 /* if COLOR_WINDOW happens to be the same as COLOR_3DHILIGHT
1512 * we better use 0x55aa bitmap brush to make scrollbar's background
1513 * look different from the window background.
1515 if (comctl32_color.clr3dHilight == comctl32_color.clrWindow)
1516 hbr = COMCTL32_hPattern55AABrush;
1518 deleteBrush = FALSE;
1520 FillRect(hdc, &rTemp, hbr);
1522 else /* ! selected */
1524 if (infoPtr->dwStyle & TCS_FLATBUTTONS)
1526 InflateRect(&rTemp, 2, 2);
1527 FillRect(hdc, &rTemp, hbr);
1528 if (iItem == infoPtr->iHotTracked ||
1529 (iItem != infoPtr->iSelected && iItem == infoPtr->uFocus))
1530 DrawEdge(hdc, &rTemp, BDR_RAISEDINNER, BF_RECT);
1532 else
1533 FillRect(hdc, &rTemp, hbr);
1537 else /* !TCS_BUTTONS */
1539 InflateRect(&rTemp, -2, -2);
1540 if (!GetWindowTheme (infoPtr->hwnd))
1541 FillRect(hdc, &rTemp, hbr);
1544 /* highlighting is drawn on top of previous fills */
1545 if (TAB_GetItem(infoPtr, iItem)->dwState & TCIS_HIGHLIGHTED)
1547 if (deleteBrush)
1549 DeleteObject(hbr);
1550 deleteBrush = FALSE;
1552 hbr = GetSysColorBrush(COLOR_HIGHLIGHT);
1553 FillRect(hdc, &rTemp, hbr);
1556 /* Cleanup */
1557 if (deleteBrush) DeleteObject(hbr);
1560 /******************************************************************************
1561 * TAB_DrawItemInterior
1563 * This method is used to draw the interior (text and icon) of a single tab
1564 * into the tab control.
1566 static void
1567 TAB_DrawItemInterior(const TAB_INFO *infoPtr, HDC hdc, INT iItem, RECT *drawRect)
1569 RECT localRect;
1571 HPEN htextPen;
1572 HPEN holdPen;
1573 INT oldBkMode;
1574 HFONT hOldFont;
1576 /* if (drawRect == NULL) */
1578 BOOL isVisible;
1579 RECT itemRect;
1580 RECT selectedRect;
1583 * Get the rectangle for the item.
1585 isVisible = TAB_InternalGetItemRect(infoPtr, iItem, &itemRect, &selectedRect);
1586 if (!isVisible)
1587 return;
1590 * Make sure drawRect points to something valid; simplifies code.
1592 drawRect = &localRect;
1595 * This logic copied from the part of TAB_DrawItem which draws
1596 * the tab background. It's important to keep it in sync. I
1597 * would have liked to avoid code duplication, but couldn't figure
1598 * out how without making spaghetti of TAB_DrawItem.
1600 if (iItem == infoPtr->iSelected)
1601 *drawRect = selectedRect;
1602 else
1603 *drawRect = itemRect;
1605 if (infoPtr->dwStyle & TCS_BUTTONS)
1607 if (iItem == infoPtr->iSelected)
1609 drawRect->left += 4;
1610 drawRect->top += 4;
1611 drawRect->right -= 4;
1613 if (infoPtr->dwStyle & TCS_VERTICAL)
1615 if (!(infoPtr->dwStyle & TCS_BOTTOM)) drawRect->right += 1;
1616 drawRect->bottom -= 4;
1618 else
1620 if (infoPtr->dwStyle & TCS_BOTTOM)
1622 drawRect->top -= 2;
1623 drawRect->bottom -= 4;
1625 else
1626 drawRect->bottom -= 1;
1629 else
1631 drawRect->left += 2;
1632 drawRect->top += 2;
1633 drawRect->right -= 2;
1634 drawRect->bottom -= 2;
1637 else
1639 if ((infoPtr->dwStyle & TCS_VERTICAL) && (infoPtr->dwStyle & TCS_BOTTOM))
1641 if (iItem != infoPtr->iSelected)
1643 drawRect->left += 2;
1644 drawRect->top += 2;
1645 drawRect->bottom -= 2;
1648 else if (infoPtr->dwStyle & TCS_VERTICAL)
1650 if (iItem == infoPtr->iSelected)
1652 drawRect->right += 1;
1654 else
1656 drawRect->top += 2;
1657 drawRect->right -= 2;
1658 drawRect->bottom -= 2;
1661 else if (infoPtr->dwStyle & TCS_BOTTOM)
1663 if (iItem == infoPtr->iSelected)
1665 drawRect->top -= 2;
1667 else
1669 InflateRect(drawRect, -2, -2);
1670 drawRect->bottom += 2;
1673 else
1675 if (iItem == infoPtr->iSelected)
1677 drawRect->bottom += 3;
1679 else
1681 drawRect->bottom -= 2;
1682 InflateRect(drawRect, -2, 0);
1687 TRACE("drawRect=(%s)\n", wine_dbgstr_rect(drawRect));
1689 /* Clear interior */
1690 TAB_EraseTabInterior (infoPtr, hdc, iItem, drawRect);
1692 /* Draw the focus rectangle */
1693 if (!(infoPtr->dwStyle & TCS_FOCUSNEVER) &&
1694 (GetFocus() == infoPtr->hwnd) &&
1695 (iItem == infoPtr->uFocus) )
1697 RECT rFocus = *drawRect;
1699 if (!(infoPtr->dwStyle & TCS_BUTTONS)) InflateRect(&rFocus, -3, -3);
1700 if (infoPtr->dwStyle & TCS_BOTTOM && !(infoPtr->dwStyle & TCS_VERTICAL))
1701 rFocus.top -= 3;
1703 /* focus should stay on selected item for TCS_BUTTONS style */
1704 if (!((infoPtr->dwStyle & TCS_BUTTONS) && (infoPtr->iSelected != iItem)))
1705 DrawFocusRect(hdc, &rFocus);
1709 * Text pen
1711 htextPen = CreatePen( PS_SOLID, 1, comctl32_color.clrBtnText );
1712 holdPen = SelectObject(hdc, htextPen);
1713 hOldFont = SelectObject(hdc, infoPtr->hFont);
1716 * Setup for text output
1718 oldBkMode = SetBkMode(hdc, TRANSPARENT);
1719 if (!GetWindowTheme (infoPtr->hwnd) || (infoPtr->dwStyle & TCS_BUTTONS))
1721 if ((infoPtr->dwStyle & TCS_HOTTRACK) && (iItem == infoPtr->iHotTracked) &&
1722 !(infoPtr->dwStyle & TCS_FLATBUTTONS))
1723 SetTextColor(hdc, comctl32_color.clrHighlight);
1724 else if (TAB_GetItem(infoPtr, iItem)->dwState & TCIS_HIGHLIGHTED)
1725 SetTextColor(hdc, comctl32_color.clrHighlightText);
1726 else
1727 SetTextColor(hdc, comctl32_color.clrBtnText);
1731 * if owner draw, tell the owner to draw
1733 if ((infoPtr->dwStyle & TCS_OWNERDRAWFIXED) && IsWindow(infoPtr->hwndNotify))
1735 DRAWITEMSTRUCT dis;
1736 UINT id;
1738 drawRect->top += 2;
1739 drawRect->right -= 1;
1740 if ( iItem == infoPtr->iSelected )
1742 drawRect->right -= 1;
1743 drawRect->left += 1;
1746 id = (UINT)GetWindowLongPtrW( infoPtr->hwnd, GWLP_ID );
1748 /* fill DRAWITEMSTRUCT */
1749 dis.CtlType = ODT_TAB;
1750 dis.CtlID = id;
1751 dis.itemID = iItem;
1752 dis.itemAction = ODA_DRAWENTIRE;
1753 dis.itemState = 0;
1754 if ( iItem == infoPtr->iSelected )
1755 dis.itemState |= ODS_SELECTED;
1756 if (infoPtr->uFocus == iItem)
1757 dis.itemState |= ODS_FOCUS;
1758 dis.hwndItem = infoPtr->hwnd;
1759 dis.hDC = hdc;
1760 CopyRect(&dis.rcItem,drawRect);
1762 /* when extra data fits ULONG_PTR, store it directly */
1763 if (infoPtr->cbInfo > sizeof(LPARAM))
1764 dis.itemData = (ULONG_PTR) TAB_GetItem(infoPtr, iItem)->extra;
1765 else
1767 /* this could be considered broken on 64 bit, but that's how it works -
1768 only first 4 bytes are copied */
1769 memcpy(&dis.itemData, (ULONG_PTR*)TAB_GetItem(infoPtr, iItem)->extra, 4);
1772 /* draw notification */
1773 SendMessageW( infoPtr->hwndNotify, WM_DRAWITEM, id, (LPARAM)&dis );
1775 else
1777 TAB_ITEM *item = TAB_GetItem(infoPtr, iItem);
1778 RECT rcTemp;
1779 RECT rcImage;
1781 /* used to center the icon and text in the tab */
1782 RECT rcText;
1783 INT center_offset_h, center_offset_v;
1785 /* set rcImage to drawRect, we will use top & left in our ImageList_Draw call */
1786 rcImage = *drawRect;
1788 rcTemp = *drawRect;
1790 rcText.left = rcText.top = rcText.right = rcText.bottom = 0;
1792 /* get the rectangle that the text fits in */
1793 if (item->pszText)
1795 DrawTextW(hdc, item->pszText, -1, &rcText, DT_CALCRECT);
1798 * If not owner draw, then do the drawing ourselves.
1800 * Draw the icon.
1802 if (infoPtr->himl && item->iImage != -1)
1804 INT cx;
1805 INT cy;
1807 ImageList_GetIconSize(infoPtr->himl, &cx, &cy);
1809 if(infoPtr->dwStyle & TCS_VERTICAL)
1811 center_offset_h = ((drawRect->bottom - drawRect->top) - (cy + infoPtr->uHItemPadding + (rcText.right - rcText.left))) / 2;
1812 center_offset_v = ((drawRect->right - drawRect->left) - cx) / 2;
1814 else
1816 center_offset_h = ((drawRect->right - drawRect->left) - (cx + infoPtr->uHItemPadding + (rcText.right - rcText.left))) / 2;
1817 center_offset_v = ((drawRect->bottom - drawRect->top) - cy) / 2;
1820 /* if an item is selected, the icon is shifted up instead of down */
1821 if (iItem == infoPtr->iSelected)
1822 center_offset_v -= infoPtr->uVItemPadding / 2;
1823 else
1824 center_offset_v += infoPtr->uVItemPadding / 2;
1826 if (infoPtr->dwStyle & TCS_FIXEDWIDTH && infoPtr->dwStyle & (TCS_FORCELABELLEFT | TCS_FORCEICONLEFT))
1827 center_offset_h = infoPtr->uHItemPadding;
1829 if (center_offset_h < 2)
1830 center_offset_h = 2;
1832 if (center_offset_v < 0)
1833 center_offset_v = 0;
1835 TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%s), textlen=%d\n",
1836 debugstr_w(item->pszText), center_offset_h, center_offset_v,
1837 wine_dbgstr_rect(drawRect), (rcText.right-rcText.left));
1839 if((infoPtr->dwStyle & TCS_VERTICAL) && (infoPtr->dwStyle & TCS_BOTTOM))
1841 rcImage.top = drawRect->top + center_offset_h;
1842 /* if tab is TCS_VERTICAL and TCS_BOTTOM, the text is drawn from the */
1843 /* right side of the tab, but the image still uses the left as its x position */
1844 /* this keeps the image always drawn off of the same side of the tab */
1845 rcImage.left = drawRect->right - cx - center_offset_v;
1846 drawRect->top += cy + infoPtr->uHItemPadding;
1848 else if(infoPtr->dwStyle & TCS_VERTICAL)
1850 rcImage.top = drawRect->bottom - cy - center_offset_h;
1851 rcImage.left = drawRect->left + center_offset_v;
1852 drawRect->bottom -= cy + infoPtr->uHItemPadding;
1854 else /* normal style, whether TCS_BOTTOM or not */
1856 rcImage.left = drawRect->left + center_offset_h;
1857 rcImage.top = drawRect->top + center_offset_v;
1858 drawRect->left += cx + infoPtr->uHItemPadding;
1861 TRACE("drawing image=%d, left=%d, top=%d\n",
1862 item->iImage, rcImage.left, rcImage.top-1);
1863 ImageList_Draw
1865 infoPtr->himl,
1866 item->iImage,
1867 hdc,
1868 rcImage.left,
1869 rcImage.top,
1870 ILD_NORMAL
1874 /* Now position text */
1875 if (infoPtr->dwStyle & TCS_FIXEDWIDTH && infoPtr->dwStyle & TCS_FORCELABELLEFT)
1876 center_offset_h = infoPtr->uHItemPadding;
1877 else
1878 if(infoPtr->dwStyle & TCS_VERTICAL)
1879 center_offset_h = ((drawRect->bottom - drawRect->top) - (rcText.right - rcText.left)) / 2;
1880 else
1881 center_offset_h = ((drawRect->right - drawRect->left) - (rcText.right - rcText.left)) / 2;
1883 if(infoPtr->dwStyle & TCS_VERTICAL)
1885 if(infoPtr->dwStyle & TCS_BOTTOM)
1886 drawRect->top+=center_offset_h;
1887 else
1888 drawRect->bottom-=center_offset_h;
1890 center_offset_v = ((drawRect->right - drawRect->left) - (rcText.bottom - rcText.top)) / 2;
1892 else
1894 drawRect->left += center_offset_h;
1895 center_offset_v = ((drawRect->bottom - drawRect->top) - (rcText.bottom - rcText.top)) / 2;
1898 /* if an item is selected, the text is shifted up instead of down */
1899 if (iItem == infoPtr->iSelected)
1900 center_offset_v -= infoPtr->uVItemPadding / 2;
1901 else
1902 center_offset_v += infoPtr->uVItemPadding / 2;
1904 if (center_offset_v < 0)
1905 center_offset_v = 0;
1907 if(infoPtr->dwStyle & TCS_VERTICAL)
1908 drawRect->left += center_offset_v;
1909 else
1910 drawRect->top += center_offset_v;
1912 /* Draw the text */
1913 if(infoPtr->dwStyle & TCS_VERTICAL) /* if we are vertical rotate the text and each character */
1915 static const WCHAR ArialW[] = { 'A','r','i','a','l',0 };
1916 LOGFONTW logfont;
1917 HFONT hFont = 0;
1918 INT nEscapement = 900;
1919 INT nOrientation = 900;
1921 if(infoPtr->dwStyle & TCS_BOTTOM)
1923 nEscapement = -900;
1924 nOrientation = -900;
1927 /* to get a font with the escapement and orientation we are looking for, we need to */
1928 /* call CreateFontIndirectA, which requires us to set the values of the logfont we pass in */
1929 if (!GetObjectW((infoPtr->hFont) ?
1930 infoPtr->hFont : GetStockObject(SYSTEM_FONT),
1931 sizeof(LOGFONTW),&logfont))
1933 INT iPointSize = 9;
1935 lstrcpyW(logfont.lfFaceName, ArialW);
1936 logfont.lfHeight = -MulDiv(iPointSize, GetDeviceCaps(hdc, LOGPIXELSY),
1937 72);
1938 logfont.lfWeight = FW_NORMAL;
1939 logfont.lfItalic = 0;
1940 logfont.lfUnderline = 0;
1941 logfont.lfStrikeOut = 0;
1944 logfont.lfEscapement = nEscapement;
1945 logfont.lfOrientation = nOrientation;
1946 hFont = CreateFontIndirectW(&logfont);
1947 SelectObject(hdc, hFont);
1949 if (item->pszText)
1951 ExtTextOutW(hdc,
1952 (infoPtr->dwStyle & TCS_BOTTOM) ? drawRect->right : drawRect->left,
1953 (!(infoPtr->dwStyle & TCS_BOTTOM)) ? drawRect->bottom : drawRect->top,
1954 ETO_CLIPPED,
1955 drawRect,
1956 item->pszText,
1957 lstrlenW(item->pszText),
1961 DeleteObject(hFont);
1963 else
1965 TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%s), textlen=%d\n",
1966 debugstr_w(item->pszText), center_offset_h, center_offset_v,
1967 wine_dbgstr_rect(drawRect), (rcText.right-rcText.left));
1968 if (item->pszText)
1970 DrawTextW
1972 hdc,
1973 item->pszText,
1974 lstrlenW(item->pszText),
1975 drawRect,
1976 DT_LEFT | DT_SINGLELINE
1981 *drawRect = rcTemp; /* restore drawRect */
1985 * Cleanup
1987 SelectObject(hdc, hOldFont);
1988 SetBkMode(hdc, oldBkMode);
1989 SelectObject(hdc, holdPen);
1990 DeleteObject( htextPen );
1993 /******************************************************************************
1994 * TAB_DrawItem
1996 * This method is used to draw a single tab into the tab control.
1998 static void TAB_DrawItem(const TAB_INFO *infoPtr, HDC hdc, INT iItem)
2000 RECT itemRect;
2001 RECT selectedRect;
2002 BOOL isVisible;
2003 RECT r, fillRect, r1;
2004 INT clRight = 0;
2005 INT clBottom = 0;
2006 COLORREF bkgnd, corner;
2007 HTHEME theme;
2010 * Get the rectangle for the item.
2012 isVisible = TAB_InternalGetItemRect(infoPtr,
2013 iItem,
2014 &itemRect,
2015 &selectedRect);
2017 if (isVisible)
2019 RECT rUD, rC;
2021 /* Clip UpDown control to not draw over it */
2022 if (infoPtr->needsScrolling)
2024 GetWindowRect(infoPtr->hwnd, &rC);
2025 GetWindowRect(infoPtr->hwndUpDown, &rUD);
2026 ExcludeClipRect(hdc, rUD.left - rC.left, rUD.top - rC.top, rUD.right - rC.left, rUD.bottom - rC.top);
2029 /* If you need to see what the control is doing,
2030 * then override these variables. They will change what
2031 * fill colors are used for filling the tabs, and the
2032 * corners when drawing the edge.
2034 bkgnd = comctl32_color.clrBtnFace;
2035 corner = comctl32_color.clrBtnFace;
2037 if (infoPtr->dwStyle & TCS_BUTTONS)
2039 /* Get item rectangle */
2040 r = itemRect;
2042 /* Separators between flat buttons */
2043 if ((infoPtr->dwStyle & TCS_FLATBUTTONS) && (infoPtr->exStyle & TCS_EX_FLATSEPARATORS))
2045 r1 = r;
2046 r1.right += (FLAT_BTN_SPACINGX -2);
2047 DrawEdge(hdc, &r1, EDGE_ETCHED, BF_RIGHT);
2050 if (iItem == infoPtr->iSelected)
2052 DrawEdge(hdc, &r, EDGE_SUNKEN, BF_SOFT|BF_RECT);
2054 OffsetRect(&r, 1, 1);
2056 else /* ! selected */
2058 DWORD state = TAB_GetItem(infoPtr, iItem)->dwState;
2060 if ((state & TCIS_BUTTONPRESSED) || (iItem == infoPtr->uFocus))
2061 DrawEdge(hdc, &r, EDGE_SUNKEN, BF_SOFT|BF_RECT);
2062 else
2063 if (!(infoPtr->dwStyle & TCS_FLATBUTTONS))
2064 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_RECT);
2067 else /* !TCS_BUTTONS */
2069 /* We draw a rectangle of different sizes depending on the selection
2070 * state. */
2071 if (iItem == infoPtr->iSelected) {
2072 RECT rect;
2073 GetClientRect (infoPtr->hwnd, &rect);
2074 clRight = rect.right;
2075 clBottom = rect.bottom;
2076 r = selectedRect;
2078 else
2079 r = itemRect;
2082 * Erase the background. (Delay it but setup rectangle.)
2083 * This is necessary when drawing the selected item since it is larger
2084 * than the others, it might overlap with stuff already drawn by the
2085 * other tabs
2087 fillRect = r;
2089 /* Draw themed tabs - but only if they are at the top.
2090 * Windows draws even side or bottom tabs themed, with wacky results.
2091 * However, since in Wine apps may get themed that did not opt in via
2092 * a manifest avoid theming when we know the result will be wrong */
2093 if ((theme = GetWindowTheme (infoPtr->hwnd))
2094 && ((infoPtr->dwStyle & (TCS_VERTICAL | TCS_BOTTOM)) == 0))
2096 static const int partIds[8] = {
2097 /* Normal item */
2098 TABP_TABITEM,
2099 TABP_TABITEMLEFTEDGE,
2100 TABP_TABITEMRIGHTEDGE,
2101 TABP_TABITEMBOTHEDGE,
2102 /* Selected tab */
2103 TABP_TOPTABITEM,
2104 TABP_TOPTABITEMLEFTEDGE,
2105 TABP_TOPTABITEMRIGHTEDGE,
2106 TABP_TOPTABITEMBOTHEDGE,
2108 int partIndex = 0;
2109 int stateId = TIS_NORMAL;
2111 /* selected and unselected tabs have different parts */
2112 if (iItem == infoPtr->iSelected)
2113 partIndex += 4;
2114 /* The part also differs on the position of a tab on a line.
2115 * "Visually" determining the position works well enough. */
2116 if(selectedRect.left == 0)
2117 partIndex += 1;
2118 if(selectedRect.right == clRight)
2119 partIndex += 2;
2121 if (iItem == infoPtr->iSelected)
2122 stateId = TIS_SELECTED;
2123 else if (iItem == infoPtr->iHotTracked)
2124 stateId = TIS_HOT;
2125 else if (iItem == infoPtr->uFocus)
2126 stateId = TIS_FOCUSED;
2128 /* Adjust rectangle for bottommost row */
2129 if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2130 r.bottom += 3;
2132 DrawThemeBackground (theme, hdc, partIds[partIndex], stateId, &r, NULL);
2133 GetThemeBackgroundContentRect (theme, hdc, partIds[partIndex], stateId, &r, &r);
2135 else if(infoPtr->dwStyle & TCS_VERTICAL)
2137 /* These are for adjusting the drawing of a Selected tab */
2138 /* The initial values are for the normal case of non-Selected */
2139 int ZZ = 1; /* Do not stretch if selected */
2140 if (iItem == infoPtr->iSelected) {
2141 ZZ = 0;
2143 /* if leftmost draw the line longer */
2144 if(selectedRect.top == 0)
2145 fillRect.top += CONTROL_BORDER_SIZEY;
2146 /* if rightmost draw the line longer */
2147 if(selectedRect.bottom == clBottom)
2148 fillRect.bottom -= CONTROL_BORDER_SIZEY;
2151 if (infoPtr->dwStyle & TCS_BOTTOM)
2153 /* Adjust both rectangles to match native */
2154 r.left += (1-ZZ);
2156 TRACE("<right> item=%d, fill=(%s), edge=(%s)\n",
2157 iItem, wine_dbgstr_rect(&fillRect), wine_dbgstr_rect(&r));
2159 /* Clear interior */
2160 SetBkColor(hdc, bkgnd);
2161 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2163 /* Draw rectangular edge around tab */
2164 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_RIGHT|BF_TOP|BF_BOTTOM);
2166 /* Now erase the top corner and draw diagonal edge */
2167 SetBkColor(hdc, corner);
2168 r1.left = r.right - ROUND_CORNER_SIZE - 1;
2169 r1.top = r.top;
2170 r1.right = r.right;
2171 r1.bottom = r1.top + ROUND_CORNER_SIZE;
2172 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2173 r1.right--;
2174 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPLEFT);
2176 /* Now erase the bottom corner and draw diagonal edge */
2177 r1.left = r.right - ROUND_CORNER_SIZE - 1;
2178 r1.bottom = r.bottom;
2179 r1.right = r.right;
2180 r1.top = r1.bottom - ROUND_CORNER_SIZE;
2181 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2182 r1.right--;
2183 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMLEFT);
2185 if ((iItem == infoPtr->iSelected) && (selectedRect.top == 0)) {
2186 r1 = r;
2187 r1.right = r1.left;
2188 r1.left--;
2189 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_TOP);
2193 else
2195 TRACE("<left> item=%d, fill=(%s), edge=(%s)\n",
2196 iItem, wine_dbgstr_rect(&fillRect), wine_dbgstr_rect(&r));
2198 /* Clear interior */
2199 SetBkColor(hdc, bkgnd);
2200 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2202 /* Draw rectangular edge around tab */
2203 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_TOP|BF_BOTTOM);
2205 /* Now erase the top corner and draw diagonal edge */
2206 SetBkColor(hdc, corner);
2207 r1.left = r.left;
2208 r1.top = r.top;
2209 r1.right = r1.left + ROUND_CORNER_SIZE + 1;
2210 r1.bottom = r1.top + ROUND_CORNER_SIZE;
2211 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2212 r1.left++;
2213 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPRIGHT);
2215 /* Now erase the bottom corner and draw diagonal edge */
2216 r1.left = r.left;
2217 r1.bottom = r.bottom;
2218 r1.right = r1.left + ROUND_CORNER_SIZE + 1;
2219 r1.top = r1.bottom - ROUND_CORNER_SIZE;
2220 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2221 r1.left++;
2222 DrawEdge(hdc, &r1, EDGE_SUNKEN, BF_DIAGONAL_ENDTOPLEFT);
2225 else /* ! TCS_VERTICAL */
2227 /* These are for adjusting the drawing of a Selected tab */
2228 /* The initial values are for the normal case of non-Selected */
2229 if (iItem == infoPtr->iSelected) {
2230 /* if leftmost draw the line longer */
2231 if(selectedRect.left == 0)
2232 fillRect.left += CONTROL_BORDER_SIZEX;
2233 /* if rightmost draw the line longer */
2234 if(selectedRect.right == clRight)
2235 fillRect.right -= CONTROL_BORDER_SIZEX;
2238 if (infoPtr->dwStyle & TCS_BOTTOM)
2240 /* Adjust both rectangles for topmost row */
2241 if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2243 fillRect.top -= 2;
2244 r.top -= 1;
2247 TRACE("<bottom> item=%d, fill=(%s), edge=(%s)\n",
2248 iItem, wine_dbgstr_rect(&fillRect), wine_dbgstr_rect(&r));
2250 /* Clear interior */
2251 SetBkColor(hdc, bkgnd);
2252 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2254 /* Draw rectangular edge around tab */
2255 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_BOTTOM|BF_RIGHT);
2257 /* Now erase the righthand corner and draw diagonal edge */
2258 SetBkColor(hdc, corner);
2259 r1.left = r.right - ROUND_CORNER_SIZE;
2260 r1.bottom = r.bottom;
2261 r1.right = r.right;
2262 r1.top = r1.bottom - ROUND_CORNER_SIZE - 1;
2263 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2264 r1.bottom--;
2265 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMLEFT);
2267 /* Now erase the lefthand corner and draw diagonal edge */
2268 r1.left = r.left;
2269 r1.bottom = r.bottom;
2270 r1.right = r1.left + ROUND_CORNER_SIZE;
2271 r1.top = r1.bottom - ROUND_CORNER_SIZE - 1;
2272 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2273 r1.bottom--;
2274 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPLEFT);
2276 if (iItem == infoPtr->iSelected)
2278 r.top += 2;
2279 r.left += 1;
2280 if (selectedRect.left == 0)
2282 r1 = r;
2283 r1.bottom = r1.top;
2284 r1.top--;
2285 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_LEFT);
2290 else
2292 /* Adjust both rectangles for bottommost row */
2293 if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2295 fillRect.bottom += 3;
2296 r.bottom += 2;
2299 TRACE("<top> item=%d, fill=(%s), edge=(%s)\n",
2300 iItem, wine_dbgstr_rect(&fillRect), wine_dbgstr_rect(&r));
2302 /* Clear interior */
2303 SetBkColor(hdc, bkgnd);
2304 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2306 /* Draw rectangular edge around tab */
2307 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_TOP|BF_RIGHT);
2309 /* Now erase the righthand corner and draw diagonal edge */
2310 SetBkColor(hdc, corner);
2311 r1.left = r.right - ROUND_CORNER_SIZE;
2312 r1.top = r.top;
2313 r1.right = r.right;
2314 r1.bottom = r1.top + ROUND_CORNER_SIZE + 1;
2315 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2316 r1.top++;
2317 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMRIGHT);
2319 /* Now erase the lefthand corner and draw diagonal edge */
2320 r1.left = r.left;
2321 r1.top = r.top;
2322 r1.right = r1.left + ROUND_CORNER_SIZE;
2323 r1.bottom = r1.top + ROUND_CORNER_SIZE + 1;
2324 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2325 r1.top++;
2326 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPRIGHT);
2331 TAB_DumpItemInternal(infoPtr, iItem);
2333 /* This modifies r to be the text rectangle. */
2334 TAB_DrawItemInterior(infoPtr, hdc, iItem, &r);
2338 /******************************************************************************
2339 * TAB_DrawBorder
2341 * This method is used to draw the raised border around the tab control
2342 * "content" area.
2344 static void TAB_DrawBorder(const TAB_INFO *infoPtr, HDC hdc)
2346 RECT rect;
2347 HTHEME theme = GetWindowTheme (infoPtr->hwnd);
2349 GetClientRect (infoPtr->hwnd, &rect);
2352 * Adjust for the style
2355 if (infoPtr->uNumItem)
2357 if ((infoPtr->dwStyle & TCS_BOTTOM) && !(infoPtr->dwStyle & TCS_VERTICAL))
2358 rect.bottom -= infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2359 else if((infoPtr->dwStyle & TCS_BOTTOM) && (infoPtr->dwStyle & TCS_VERTICAL))
2360 rect.right -= infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2361 else if(infoPtr->dwStyle & TCS_VERTICAL)
2362 rect.left += infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2363 else /* not TCS_VERTICAL and not TCS_BOTTOM */
2364 rect.top += infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2367 TRACE("border=(%s)\n", wine_dbgstr_rect(&rect));
2369 if (theme)
2370 DrawThemeBackground (theme, hdc, TABP_PANE, 0, &rect, NULL);
2371 else
2372 DrawEdge(hdc, &rect, EDGE_RAISED, BF_SOFT|BF_RECT);
2375 /******************************************************************************
2376 * TAB_Refresh
2378 * This method repaints the tab control..
2380 static void TAB_Refresh (const TAB_INFO *infoPtr, HDC hdc)
2382 HFONT hOldFont;
2383 INT i;
2385 if (!infoPtr->DoRedraw)
2386 return;
2388 hOldFont = SelectObject (hdc, infoPtr->hFont);
2390 if (infoPtr->dwStyle & TCS_BUTTONS)
2392 for (i = 0; i < infoPtr->uNumItem; i++)
2393 TAB_DrawItem (infoPtr, hdc, i);
2395 else
2397 /* Draw all the non selected item first */
2398 for (i = 0; i < infoPtr->uNumItem; i++)
2400 if (i != infoPtr->iSelected)
2401 TAB_DrawItem (infoPtr, hdc, i);
2404 /* Now, draw the border, draw it before the selected item
2405 * since the selected item overwrites part of the border. */
2406 TAB_DrawBorder (infoPtr, hdc);
2408 /* Then, draw the selected item */
2409 TAB_DrawItem (infoPtr, hdc, infoPtr->iSelected);
2412 SelectObject (hdc, hOldFont);
2415 static inline DWORD TAB_GetRowCount (const TAB_INFO *infoPtr)
2417 TRACE("(%p)\n", infoPtr);
2418 return infoPtr->uNumRows;
2421 static inline LRESULT TAB_SetRedraw (TAB_INFO *infoPtr, BOOL doRedraw)
2423 infoPtr->DoRedraw = doRedraw;
2424 return 0;
2427 /******************************************************************************
2428 * TAB_EnsureSelectionVisible
2430 * This method will make sure that the current selection is completely
2431 * visible by scrolling until it is.
2433 static void TAB_EnsureSelectionVisible(
2434 TAB_INFO* infoPtr)
2436 INT iSelected = infoPtr->iSelected;
2437 INT iOrigLeftmostVisible = infoPtr->leftmostVisible;
2439 /* set the items row to the bottommost row or topmost row depending on
2440 * style */
2441 if ((infoPtr->uNumRows > 1) && !(infoPtr->dwStyle & TCS_BUTTONS))
2443 TAB_ITEM *selected = TAB_GetItem(infoPtr, iSelected);
2444 INT newselected;
2445 INT iTargetRow;
2447 if(infoPtr->dwStyle & TCS_VERTICAL)
2448 newselected = selected->rect.left;
2449 else
2450 newselected = selected->rect.top;
2452 /* the target row is always (number of rows - 1)
2453 as row 0 is furthest from the clientRect */
2454 iTargetRow = infoPtr->uNumRows - 1;
2456 if (newselected != iTargetRow)
2458 UINT i;
2459 if(infoPtr->dwStyle & TCS_VERTICAL)
2461 for (i=0; i < infoPtr->uNumItem; i++)
2463 /* move everything in the row of the selected item to the iTargetRow */
2464 TAB_ITEM *item = TAB_GetItem(infoPtr, i);
2466 if (item->rect.left == newselected )
2467 item->rect.left = iTargetRow;
2468 else
2470 if (item->rect.left > newselected)
2471 item->rect.left-=1;
2475 else
2477 for (i=0; i < infoPtr->uNumItem; i++)
2479 TAB_ITEM *item = TAB_GetItem(infoPtr, i);
2481 if (item->rect.top == newselected )
2482 item->rect.top = iTargetRow;
2483 else
2485 if (item->rect.top > newselected)
2486 item->rect.top-=1;
2490 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
2495 * Do the trivial cases first.
2497 if ( (!infoPtr->needsScrolling) ||
2498 (infoPtr->hwndUpDown==0) || (infoPtr->dwStyle & TCS_VERTICAL))
2499 return;
2501 if (infoPtr->leftmostVisible >= iSelected)
2503 if (iSelected >= 0) infoPtr->leftmostVisible = iSelected;
2505 else
2507 TAB_ITEM *selected = TAB_GetItem(infoPtr, iSelected);
2508 RECT r;
2509 INT width;
2510 UINT i;
2512 /* Calculate the part of the client area that is visible */
2513 GetClientRect(infoPtr->hwnd, &r);
2514 width = r.right;
2516 GetClientRect(infoPtr->hwndUpDown, &r);
2517 width -= r.right;
2519 if ((selected->rect.right -
2520 selected->rect.left) >= width )
2522 /* Special case: width of selected item is greater than visible
2523 * part of control.
2525 infoPtr->leftmostVisible = iSelected;
2527 else
2529 for (i = infoPtr->leftmostVisible; i < infoPtr->uNumItem; i++)
2531 if ((selected->rect.right - TAB_GetItem(infoPtr, i)->rect.left) < width)
2532 break;
2534 infoPtr->leftmostVisible = i;
2538 if (infoPtr->leftmostVisible != iOrigLeftmostVisible)
2539 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
2541 SendMessageW(infoPtr->hwndUpDown, UDM_SETPOS, 0,
2542 MAKELONG(infoPtr->leftmostVisible, 0));
2545 /******************************************************************************
2546 * TAB_InvalidateTabArea
2548 * This method will invalidate the portion of the control that contains the
2549 * tabs. It is called when the state of the control changes and needs
2550 * to be redisplayed
2552 static void TAB_InvalidateTabArea(const TAB_INFO *infoPtr)
2554 RECT clientRect, rInvalidate, rAdjClient;
2555 INT lastRow = infoPtr->uNumRows - 1;
2556 RECT rect;
2558 if (lastRow < 0) return;
2560 GetClientRect(infoPtr->hwnd, &clientRect);
2561 rInvalidate = clientRect;
2562 rAdjClient = clientRect;
2564 TAB_AdjustRect(infoPtr, 0, &rAdjClient);
2566 TAB_InternalGetItemRect(infoPtr, infoPtr->uNumItem-1 , &rect, NULL);
2567 if ((infoPtr->dwStyle & TCS_BOTTOM) && (infoPtr->dwStyle & TCS_VERTICAL))
2569 rInvalidate.left = rAdjClient.right;
2570 if (infoPtr->uNumRows == 1)
2571 rInvalidate.bottom = clientRect.top + rect.bottom + 2 * SELECTED_TAB_OFFSET;
2573 else if(infoPtr->dwStyle & TCS_VERTICAL)
2575 rInvalidate.right = rAdjClient.left;
2576 if (infoPtr->uNumRows == 1)
2577 rInvalidate.bottom = clientRect.top + rect.bottom + 2 * SELECTED_TAB_OFFSET;
2579 else if (infoPtr->dwStyle & TCS_BOTTOM)
2581 rInvalidate.top = rAdjClient.bottom;
2582 if (infoPtr->uNumRows == 1)
2583 rInvalidate.right = clientRect.left + rect.right + 2 * SELECTED_TAB_OFFSET;
2585 else
2587 rInvalidate.bottom = rAdjClient.top;
2588 if (infoPtr->uNumRows == 1)
2589 rInvalidate.right = clientRect.left + rect.right + 2 * SELECTED_TAB_OFFSET;
2592 /* Punch out the updown control */
2593 if (infoPtr->needsScrolling && (rInvalidate.right > 0)) {
2594 RECT r;
2595 GetClientRect(infoPtr->hwndUpDown, &r);
2596 if (rInvalidate.right > clientRect.right - r.left)
2597 rInvalidate.right = rInvalidate.right - (r.right - r.left);
2598 else
2599 rInvalidate.right = clientRect.right - r.left;
2602 TRACE("invalidate (%s)\n", wine_dbgstr_rect(&rInvalidate));
2604 InvalidateRect(infoPtr->hwnd, &rInvalidate, TRUE);
2607 static inline LRESULT TAB_Paint (TAB_INFO *infoPtr, HDC hdcPaint)
2609 HDC hdc;
2610 PAINTSTRUCT ps;
2612 if (hdcPaint)
2613 hdc = hdcPaint;
2614 else
2616 hdc = BeginPaint (infoPtr->hwnd, &ps);
2617 TRACE("erase %d, rect=(%s)\n", ps.fErase, wine_dbgstr_rect(&ps.rcPaint));
2620 TAB_Refresh (infoPtr, hdc);
2622 if (!hdcPaint)
2623 EndPaint (infoPtr->hwnd, &ps);
2625 return 0;
2628 static LRESULT
2629 TAB_InsertItemT (TAB_INFO *infoPtr, INT iItem, const TCITEMW *pti, BOOL bUnicode)
2631 TAB_ITEM *item;
2632 RECT rect;
2634 GetClientRect (infoPtr->hwnd, &rect);
2635 TRACE("Rect: %p %s\n", infoPtr->hwnd, wine_dbgstr_rect(&rect));
2637 if (iItem < 0) return -1;
2638 if (iItem > infoPtr->uNumItem)
2639 iItem = infoPtr->uNumItem;
2641 TAB_DumpItemExternalT(pti, iItem, bUnicode);
2644 if (infoPtr->uNumItem == 0) {
2645 infoPtr->items = Alloc (TAB_ITEM_SIZE(infoPtr));
2646 infoPtr->uNumItem++;
2647 infoPtr->iSelected = 0;
2649 else {
2650 LPBYTE oldItems = (LPBYTE)infoPtr->items;
2652 infoPtr->uNumItem++;
2653 infoPtr->items = Alloc (TAB_ITEM_SIZE(infoPtr) * infoPtr->uNumItem);
2655 /* pre insert copy */
2656 if (iItem > 0) {
2657 memcpy (infoPtr->items, oldItems,
2658 iItem * TAB_ITEM_SIZE(infoPtr));
2661 /* post insert copy */
2662 if (iItem < infoPtr->uNumItem - 1) {
2663 memcpy (TAB_GetItem(infoPtr, iItem + 1),
2664 oldItems + iItem * TAB_ITEM_SIZE(infoPtr),
2665 (infoPtr->uNumItem - iItem - 1) * TAB_ITEM_SIZE(infoPtr));
2669 if (iItem <= infoPtr->iSelected)
2670 infoPtr->iSelected++;
2672 Free (oldItems);
2675 item = TAB_GetItem(infoPtr, iItem);
2677 item->pszText = NULL;
2679 if (pti->mask & TCIF_TEXT)
2681 if (bUnicode)
2682 Str_SetPtrW (&item->pszText, pti->pszText);
2683 else
2684 Str_SetPtrAtoW (&item->pszText, (LPSTR)pti->pszText);
2687 if (pti->mask & TCIF_IMAGE)
2688 item->iImage = pti->iImage;
2689 else
2690 item->iImage = -1;
2692 if (pti->mask & TCIF_PARAM)
2693 memcpy(item->extra, &pti->lParam, EXTRA_ITEM_SIZE(infoPtr));
2694 else
2695 memset(item->extra, 0, EXTRA_ITEM_SIZE(infoPtr));
2697 TAB_SetItemBounds(infoPtr);
2698 if (infoPtr->uNumItem > 1)
2699 TAB_InvalidateTabArea(infoPtr);
2700 else
2701 InvalidateRect(infoPtr->hwnd, NULL, TRUE);
2703 TRACE("[%p]: added item %d %s\n",
2704 infoPtr->hwnd, iItem, debugstr_w(item->pszText));
2706 /* If we haven't set the current focus yet, set it now. */
2707 if (infoPtr->uFocus == -1)
2708 TAB_SetCurFocus(infoPtr, iItem);
2710 return iItem;
2713 static LRESULT
2714 TAB_SetItemSize (TAB_INFO *infoPtr, INT cx, INT cy)
2716 LONG lResult = 0;
2717 BOOL bNeedPaint = FALSE;
2719 lResult = MAKELONG(infoPtr->tabWidth, infoPtr->tabHeight);
2721 /* UNDOCUMENTED: If requested Width or Height is 0 this means that program wants to use auto size. */
2722 if (infoPtr->dwStyle & TCS_FIXEDWIDTH && (infoPtr->tabWidth != cx))
2724 infoPtr->tabWidth = cx;
2725 bNeedPaint = TRUE;
2728 if (infoPtr->tabHeight != cy)
2730 if ((infoPtr->fHeightSet = (cy != 0)))
2731 infoPtr->tabHeight = cy;
2733 bNeedPaint = TRUE;
2735 TRACE("was h=%d,w=%d, now h=%d,w=%d\n",
2736 HIWORD(lResult), LOWORD(lResult),
2737 infoPtr->tabHeight, infoPtr->tabWidth);
2739 if (bNeedPaint)
2741 TAB_SetItemBounds(infoPtr);
2742 RedrawWindow(infoPtr->hwnd, NULL, NULL, RDW_ERASE | RDW_INVALIDATE | RDW_UPDATENOW);
2745 return lResult;
2748 static inline LRESULT TAB_SetMinTabWidth (TAB_INFO *infoPtr, INT cx)
2750 INT oldcx = 0;
2752 TRACE("(%p,%d)\n", infoPtr, cx);
2754 if (infoPtr->tabMinWidth < 0)
2755 oldcx = DEFAULT_MIN_TAB_WIDTH;
2756 else
2757 oldcx = infoPtr->tabMinWidth;
2758 infoPtr->tabMinWidth = cx;
2759 TAB_SetItemBounds(infoPtr);
2760 return oldcx;
2763 static inline LRESULT
2764 TAB_HighlightItem (TAB_INFO *infoPtr, INT iItem, BOOL fHighlight)
2766 LPDWORD lpState;
2767 DWORD oldState;
2768 RECT r;
2770 TRACE("(%p,%d,%s)\n", infoPtr, iItem, fHighlight ? "true" : "false");
2772 if (iItem < 0 || iItem >= infoPtr->uNumItem)
2773 return FALSE;
2775 lpState = &TAB_GetItem(infoPtr, iItem)->dwState;
2776 oldState = *lpState;
2778 if (fHighlight)
2779 *lpState |= TCIS_HIGHLIGHTED;
2780 else
2781 *lpState &= ~TCIS_HIGHLIGHTED;
2783 if ((oldState != *lpState) && TAB_InternalGetItemRect (infoPtr, iItem, &r, NULL))
2784 InvalidateRect (infoPtr->hwnd, &r, TRUE);
2786 return TRUE;
2789 static LRESULT
2790 TAB_SetItemT (TAB_INFO *infoPtr, INT iItem, LPTCITEMW tabItem, BOOL bUnicode)
2792 TAB_ITEM *wineItem;
2794 TRACE("(%p,%d,%p,%s)\n", infoPtr, iItem, tabItem, bUnicode ? "true" : "false");
2796 if (iItem < 0 || iItem >= infoPtr->uNumItem)
2797 return FALSE;
2799 TAB_DumpItemExternalT(tabItem, iItem, bUnicode);
2801 wineItem = TAB_GetItem(infoPtr, iItem);
2803 if (tabItem->mask & TCIF_IMAGE)
2804 wineItem->iImage = tabItem->iImage;
2806 if (tabItem->mask & TCIF_PARAM)
2807 memcpy(wineItem->extra, &tabItem->lParam, infoPtr->cbInfo);
2809 if (tabItem->mask & TCIF_RTLREADING)
2810 FIXME("TCIF_RTLREADING\n");
2812 if (tabItem->mask & TCIF_STATE)
2813 wineItem->dwState = (wineItem->dwState & ~tabItem->dwStateMask) |
2814 ( tabItem->dwState & tabItem->dwStateMask);
2816 if (tabItem->mask & TCIF_TEXT)
2818 Free(wineItem->pszText);
2819 wineItem->pszText = NULL;
2820 if (bUnicode)
2821 Str_SetPtrW(&wineItem->pszText, tabItem->pszText);
2822 else
2823 Str_SetPtrAtoW(&wineItem->pszText, (LPSTR)tabItem->pszText);
2826 /* Update and repaint tabs */
2827 TAB_SetItemBounds(infoPtr);
2828 TAB_InvalidateTabArea(infoPtr);
2830 return TRUE;
2833 static inline LRESULT TAB_GetItemCount (const TAB_INFO *infoPtr)
2835 TRACE("\n");
2836 return infoPtr->uNumItem;
2840 static LRESULT
2841 TAB_GetItemT (TAB_INFO *infoPtr, INT iItem, LPTCITEMW tabItem, BOOL bUnicode)
2843 TAB_ITEM *wineItem;
2845 TRACE("(%p,%d,%p,%s)\n", infoPtr, iItem, tabItem, bUnicode ? "true" : "false");
2847 if (!tabItem) return FALSE;
2849 if (iItem < 0 || iItem >= infoPtr->uNumItem)
2851 /* init requested fields */
2852 if (tabItem->mask & TCIF_IMAGE) tabItem->iImage = 0;
2853 if (tabItem->mask & TCIF_PARAM) tabItem->lParam = 0;
2854 if (tabItem->mask & TCIF_STATE) tabItem->dwState = 0;
2855 return FALSE;
2858 wineItem = TAB_GetItem(infoPtr, iItem);
2860 if (tabItem->mask & TCIF_IMAGE)
2861 tabItem->iImage = wineItem->iImage;
2863 if (tabItem->mask & TCIF_PARAM)
2864 memcpy(&tabItem->lParam, wineItem->extra, infoPtr->cbInfo);
2866 if (tabItem->mask & TCIF_RTLREADING)
2867 FIXME("TCIF_RTLREADING\n");
2869 if (tabItem->mask & TCIF_STATE)
2870 tabItem->dwState = wineItem->dwState & tabItem->dwStateMask;
2872 if (tabItem->mask & TCIF_TEXT)
2874 if (bUnicode)
2875 Str_GetPtrW (wineItem->pszText, tabItem->pszText, tabItem->cchTextMax);
2876 else
2877 Str_GetPtrWtoA (wineItem->pszText, (LPSTR)tabItem->pszText, tabItem->cchTextMax);
2880 TAB_DumpItemExternalT(tabItem, iItem, bUnicode);
2882 return TRUE;
2886 static LRESULT TAB_DeleteItem (TAB_INFO *infoPtr, INT iItem)
2888 BOOL bResult = FALSE;
2890 TRACE("(%p, %d)\n", infoPtr, iItem);
2892 if ((iItem >= 0) && (iItem < infoPtr->uNumItem))
2894 TAB_ITEM *item = TAB_GetItem(infoPtr, iItem);
2895 LPBYTE oldItems = (LPBYTE)infoPtr->items;
2897 TAB_InvalidateTabArea(infoPtr);
2898 Free(item->pszText);
2899 infoPtr->uNumItem--;
2901 if (!infoPtr->uNumItem)
2903 infoPtr->items = NULL;
2904 if (infoPtr->iHotTracked >= 0)
2906 KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
2907 infoPtr->iHotTracked = -1;
2910 else
2912 infoPtr->items = Alloc(TAB_ITEM_SIZE(infoPtr) * infoPtr->uNumItem);
2914 if (iItem > 0)
2915 memcpy(infoPtr->items, oldItems, iItem * TAB_ITEM_SIZE(infoPtr));
2917 if (iItem < infoPtr->uNumItem)
2918 memcpy(TAB_GetItem(infoPtr, iItem),
2919 oldItems + (iItem + 1) * TAB_ITEM_SIZE(infoPtr),
2920 (infoPtr->uNumItem - iItem) * TAB_ITEM_SIZE(infoPtr));
2922 if (iItem <= infoPtr->iHotTracked)
2924 /* When tabs move left/up, the hot track item may change */
2925 FIXME("Recalc hot track\n");
2928 Free(oldItems);
2930 /* Readjust the selected index */
2931 if (iItem == infoPtr->iSelected)
2932 infoPtr->iSelected = -1;
2933 else if (iItem < infoPtr->iSelected)
2934 infoPtr->iSelected--;
2936 if (infoPtr->uNumItem == 0)
2937 infoPtr->iSelected = -1;
2939 /* Reposition and repaint tabs */
2940 TAB_SetItemBounds(infoPtr);
2942 bResult = TRUE;
2945 return bResult;
2948 static inline LRESULT TAB_DeleteAllItems (TAB_INFO *infoPtr)
2950 TRACE("(%p)\n", infoPtr);
2951 while (infoPtr->uNumItem)
2952 TAB_DeleteItem (infoPtr, 0);
2953 return TRUE;
2957 static inline LRESULT TAB_GetFont (const TAB_INFO *infoPtr)
2959 TRACE("(%p) returning %p\n", infoPtr, infoPtr->hFont);
2960 return (LRESULT)infoPtr->hFont;
2963 static inline LRESULT TAB_SetFont (TAB_INFO *infoPtr, HFONT hNewFont)
2965 TRACE("(%p,%p)\n", infoPtr, hNewFont);
2967 infoPtr->hFont = hNewFont;
2969 TAB_SetItemBounds(infoPtr);
2971 TAB_InvalidateTabArea(infoPtr);
2973 return 0;
2977 static inline LRESULT TAB_GetImageList (const TAB_INFO *infoPtr)
2979 TRACE("\n");
2980 return (LRESULT)infoPtr->himl;
2983 static inline LRESULT TAB_SetImageList (TAB_INFO *infoPtr, HIMAGELIST himlNew)
2985 HIMAGELIST himlPrev = infoPtr->himl;
2986 TRACE("himl=%p\n", himlNew);
2987 infoPtr->himl = himlNew;
2988 TAB_SetItemBounds(infoPtr);
2989 InvalidateRect(infoPtr->hwnd, NULL, TRUE);
2990 return (LRESULT)himlPrev;
2993 static inline LRESULT TAB_GetUnicodeFormat (const TAB_INFO *infoPtr)
2995 TRACE("(%p)\n", infoPtr);
2996 return infoPtr->bUnicode;
2999 static inline LRESULT TAB_SetUnicodeFormat (TAB_INFO *infoPtr, BOOL bUnicode)
3001 BOOL bTemp = infoPtr->bUnicode;
3003 TRACE("(%p %d)\n", infoPtr, bUnicode);
3004 infoPtr->bUnicode = bUnicode;
3006 return bTemp;
3009 static inline LRESULT TAB_Size (TAB_INFO *infoPtr)
3011 /* I'm not really sure what the following code was meant to do.
3012 This is what it is doing:
3013 When WM_SIZE is sent with SIZE_RESTORED, the control
3014 gets positioned in the top left corner.
3016 RECT parent_rect;
3017 HWND parent;
3018 UINT uPosFlags,cx,cy;
3020 uPosFlags=0;
3021 if (!wParam) {
3022 parent = GetParent (hwnd);
3023 GetClientRect(parent, &parent_rect);
3024 cx=LOWORD (lParam);
3025 cy=HIWORD (lParam);
3026 if (GetWindowLongW(hwnd, GWL_STYLE) & CCS_NORESIZE)
3027 uPosFlags |= (SWP_NOSIZE | SWP_NOMOVE);
3029 SetWindowPos (hwnd, 0, parent_rect.left, parent_rect.top,
3030 cx, cy, uPosFlags | SWP_NOZORDER);
3031 } else {
3032 FIXME("WM_SIZE flag %x %lx not handled\n", wParam, lParam);
3033 } */
3035 /* Recompute the size/position of the tabs. */
3036 TAB_SetItemBounds (infoPtr);
3038 /* Force a repaint of the control. */
3039 InvalidateRect(infoPtr->hwnd, NULL, TRUE);
3041 return 0;
3045 static LRESULT TAB_Create (HWND hwnd, LPARAM lParam)
3047 TAB_INFO *infoPtr;
3048 TEXTMETRICW fontMetrics;
3049 HDC hdc;
3050 HFONT hOldFont;
3051 DWORD dwStyle;
3053 infoPtr = Alloc (sizeof(TAB_INFO));
3055 SetWindowLongPtrW(hwnd, 0, (DWORD_PTR)infoPtr);
3057 infoPtr->hwnd = hwnd;
3058 infoPtr->hwndNotify = ((LPCREATESTRUCTW)lParam)->hwndParent;
3059 infoPtr->uNumItem = 0;
3060 infoPtr->uNumRows = 0;
3061 infoPtr->uHItemPadding = 6;
3062 infoPtr->uVItemPadding = 3;
3063 infoPtr->uHItemPadding_s = 6;
3064 infoPtr->uVItemPadding_s = 3;
3065 infoPtr->hFont = 0;
3066 infoPtr->items = 0;
3067 infoPtr->hcurArrow = LoadCursorW (0, (LPWSTR)IDC_ARROW);
3068 infoPtr->iSelected = -1;
3069 infoPtr->iHotTracked = -1;
3070 infoPtr->uFocus = -1;
3071 infoPtr->hwndToolTip = 0;
3072 infoPtr->DoRedraw = TRUE;
3073 infoPtr->needsScrolling = FALSE;
3074 infoPtr->hwndUpDown = 0;
3075 infoPtr->leftmostVisible = 0;
3076 infoPtr->fHeightSet = FALSE;
3077 infoPtr->bUnicode = IsWindowUnicode (hwnd);
3078 infoPtr->cbInfo = sizeof(LPARAM);
3080 TRACE("Created tab control, hwnd [%p]\n", hwnd);
3082 /* The tab control always has the WS_CLIPSIBLINGS style. Even
3083 if you don't specify it in CreateWindow. This is necessary in
3084 order for paint to work correctly. This follows windows behaviour. */
3085 dwStyle = GetWindowLongW(hwnd, GWL_STYLE);
3086 SetWindowLongW(hwnd, GWL_STYLE, dwStyle|WS_CLIPSIBLINGS);
3088 infoPtr->dwStyle = dwStyle | WS_CLIPSIBLINGS;
3089 infoPtr->exStyle = (dwStyle & TCS_FLATBUTTONS) ? TCS_EX_FLATSEPARATORS : 0;
3091 if (infoPtr->dwStyle & TCS_TOOLTIPS) {
3092 /* Create tooltip control */
3093 infoPtr->hwndToolTip =
3094 CreateWindowExW (0, TOOLTIPS_CLASSW, NULL, WS_POPUP,
3095 CW_USEDEFAULT, CW_USEDEFAULT,
3096 CW_USEDEFAULT, CW_USEDEFAULT,
3097 hwnd, 0, 0, 0);
3099 /* Send NM_TOOLTIPSCREATED notification */
3100 if (infoPtr->hwndToolTip) {
3101 NMTOOLTIPSCREATED nmttc;
3103 nmttc.hdr.hwndFrom = hwnd;
3104 nmttc.hdr.idFrom = GetWindowLongPtrW(hwnd, GWLP_ID);
3105 nmttc.hdr.code = NM_TOOLTIPSCREATED;
3106 nmttc.hwndToolTips = infoPtr->hwndToolTip;
3108 SendMessageW (infoPtr->hwndNotify, WM_NOTIFY,
3109 GetWindowLongPtrW(hwnd, GWLP_ID), (LPARAM)&nmttc);
3113 OpenThemeData (infoPtr->hwnd, themeClass);
3116 * We need to get text information so we need a DC and we need to select
3117 * a font.
3119 hdc = GetDC(hwnd);
3120 hOldFont = SelectObject (hdc, GetStockObject (SYSTEM_FONT));
3122 /* Use the system font to determine the initial height of a tab. */
3123 GetTextMetricsW(hdc, &fontMetrics);
3126 * Make sure there is enough space for the letters + growing the
3127 * selected item + extra space for the selected item.
3129 infoPtr->tabHeight = fontMetrics.tmHeight + SELECTED_TAB_OFFSET +
3130 ((infoPtr->dwStyle & TCS_BUTTONS) ? 2 : 1) *
3131 infoPtr->uVItemPadding;
3133 /* Initialize the width of a tab. */
3134 if (infoPtr->dwStyle & TCS_FIXEDWIDTH)
3135 infoPtr->tabWidth = GetDeviceCaps(hdc, LOGPIXELSX);
3137 infoPtr->tabMinWidth = -1;
3139 TRACE("tabH=%d, tabW=%d\n", infoPtr->tabHeight, infoPtr->tabWidth);
3141 SelectObject (hdc, hOldFont);
3142 ReleaseDC(hwnd, hdc);
3144 return 0;
3147 static LRESULT
3148 TAB_Destroy (TAB_INFO *infoPtr)
3150 UINT iItem;
3152 SetWindowLongPtrW(infoPtr->hwnd, 0, 0);
3154 if (infoPtr->items) {
3155 for (iItem = 0; iItem < infoPtr->uNumItem; iItem++) {
3156 Free (TAB_GetItem(infoPtr, iItem)->pszText);
3158 Free (infoPtr->items);
3161 if (infoPtr->hwndToolTip)
3162 DestroyWindow (infoPtr->hwndToolTip);
3164 if (infoPtr->hwndUpDown)
3165 DestroyWindow(infoPtr->hwndUpDown);
3167 if (infoPtr->iHotTracked >= 0)
3168 KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
3170 CloseThemeData (GetWindowTheme (infoPtr->hwnd));
3172 Free (infoPtr);
3173 return 0;
3176 /* update theme after a WM_THEMECHANGED message */
3177 static LRESULT theme_changed(const TAB_INFO *infoPtr)
3179 HTHEME theme = GetWindowTheme (infoPtr->hwnd);
3180 CloseThemeData (theme);
3181 OpenThemeData (infoPtr->hwnd, themeClass);
3182 return 0;
3185 static LRESULT TAB_NCCalcSize(WPARAM wParam)
3187 if (!wParam)
3188 return 0;
3189 return WVR_ALIGNTOP;
3192 static inline LRESULT
3193 TAB_SetItemExtra (TAB_INFO *infoPtr, INT cbInfo)
3195 TRACE("(%p %d)\n", infoPtr, cbInfo);
3197 if (cbInfo < 0 || infoPtr->uNumItem) return FALSE;
3199 infoPtr->cbInfo = cbInfo;
3200 return TRUE;
3203 static LRESULT TAB_RemoveImage (TAB_INFO *infoPtr, INT image)
3205 TRACE("%p %d\n", infoPtr, image);
3207 if (ImageList_Remove (infoPtr->himl, image))
3209 INT i, *idx;
3210 RECT r;
3212 /* shift indices, repaint items if needed */
3213 for (i = 0; i < infoPtr->uNumItem; i++)
3215 idx = &TAB_GetItem(infoPtr, i)->iImage;
3216 if (*idx >= image)
3218 if (*idx == image)
3219 *idx = -1;
3220 else
3221 (*idx)--;
3223 /* repaint item */
3224 if (TAB_InternalGetItemRect (infoPtr, i, &r, NULL))
3225 InvalidateRect (infoPtr->hwnd, &r, TRUE);
3230 return 0;
3233 static LRESULT
3234 TAB_SetExtendedStyle (TAB_INFO *infoPtr, DWORD exMask, DWORD exStyle)
3236 DWORD prevstyle = infoPtr->exStyle;
3238 /* zero mask means all styles */
3239 if (exMask == 0) exMask = ~0;
3241 if (exMask & TCS_EX_REGISTERDROP)
3243 FIXME("TCS_EX_REGISTERDROP style unimplemented\n");
3244 exMask &= ~TCS_EX_REGISTERDROP;
3245 exStyle &= ~TCS_EX_REGISTERDROP;
3248 if (exMask & TCS_EX_FLATSEPARATORS)
3250 if ((prevstyle ^ exStyle) & TCS_EX_FLATSEPARATORS)
3252 infoPtr->exStyle ^= TCS_EX_FLATSEPARATORS;
3253 TAB_InvalidateTabArea(infoPtr);
3257 return prevstyle;
3260 static inline LRESULT
3261 TAB_GetExtendedStyle (const TAB_INFO *infoPtr)
3263 return infoPtr->exStyle;
3266 static LRESULT
3267 TAB_DeselectAll (TAB_INFO *infoPtr, BOOL excludesel)
3269 BOOL paint = FALSE;
3270 INT i, selected = infoPtr->iSelected;
3272 TRACE("(%p, %d)\n", infoPtr, excludesel);
3274 if (!(infoPtr->dwStyle & TCS_BUTTONS))
3275 return 0;
3277 for (i = 0; i < infoPtr->uNumItem; i++)
3279 if ((TAB_GetItem(infoPtr, i)->dwState & TCIS_BUTTONPRESSED) &&
3280 (selected != i))
3282 TAB_GetItem(infoPtr, i)->dwState &= ~TCIS_BUTTONPRESSED;
3283 paint = TRUE;
3287 if (!excludesel && (selected != -1))
3289 TAB_GetItem(infoPtr, selected)->dwState &= ~TCIS_BUTTONPRESSED;
3290 infoPtr->iSelected = -1;
3291 paint = TRUE;
3294 if (paint)
3295 TAB_InvalidateTabArea (infoPtr);
3297 return 0;
3300 /***
3301 * DESCRIPTION:
3302 * Processes WM_STYLECHANGED messages.
3304 * PARAMETER(S):
3305 * [I] infoPtr : valid pointer to the tab data structure
3306 * [I] wStyleType : window style type (normal or extended)
3307 * [I] lpss : window style information
3309 * RETURN:
3310 * Zero
3312 static INT TAB_StyleChanged(TAB_INFO *infoPtr, WPARAM wStyleType,
3313 const STYLESTRUCT *lpss)
3315 TRACE("(styletype=%lx, styleOld=0x%08x, styleNew=0x%08x)\n",
3316 wStyleType, lpss->styleOld, lpss->styleNew);
3318 if (wStyleType != GWL_STYLE) return 0;
3320 infoPtr->dwStyle = lpss->styleNew;
3322 TAB_SetItemBounds (infoPtr);
3323 InvalidateRect(infoPtr->hwnd, NULL, TRUE);
3325 return 0;
3328 static LRESULT WINAPI
3329 TAB_WindowProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
3331 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
3333 TRACE("hwnd=%p msg=%x wParam=%lx lParam=%lx\n", hwnd, uMsg, wParam, lParam);
3334 if (!infoPtr && (uMsg != WM_CREATE))
3335 return DefWindowProcW (hwnd, uMsg, wParam, lParam);
3337 switch (uMsg)
3339 case TCM_GETIMAGELIST:
3340 return TAB_GetImageList (infoPtr);
3342 case TCM_SETIMAGELIST:
3343 return TAB_SetImageList (infoPtr, (HIMAGELIST)lParam);
3345 case TCM_GETITEMCOUNT:
3346 return TAB_GetItemCount (infoPtr);
3348 case TCM_GETITEMA:
3349 case TCM_GETITEMW:
3350 return TAB_GetItemT (infoPtr, (INT)wParam, (LPTCITEMW)lParam, uMsg == TCM_GETITEMW);
3352 case TCM_SETITEMA:
3353 case TCM_SETITEMW:
3354 return TAB_SetItemT (infoPtr, (INT)wParam, (LPTCITEMW)lParam, uMsg == TCM_SETITEMW);
3356 case TCM_DELETEITEM:
3357 return TAB_DeleteItem (infoPtr, (INT)wParam);
3359 case TCM_DELETEALLITEMS:
3360 return TAB_DeleteAllItems (infoPtr);
3362 case TCM_GETITEMRECT:
3363 return TAB_GetItemRect (infoPtr, (INT)wParam, (LPRECT)lParam);
3365 case TCM_GETCURSEL:
3366 return TAB_GetCurSel (infoPtr);
3368 case TCM_HITTEST:
3369 return TAB_HitTest (infoPtr, (LPTCHITTESTINFO)lParam);
3371 case TCM_SETCURSEL:
3372 return TAB_SetCurSel (infoPtr, (INT)wParam);
3374 case TCM_INSERTITEMA:
3375 case TCM_INSERTITEMW:
3376 return TAB_InsertItemT (infoPtr, (INT)wParam, (TCITEMW*)lParam, uMsg == TCM_INSERTITEMW);
3378 case TCM_SETITEMEXTRA:
3379 return TAB_SetItemExtra (infoPtr, (INT)wParam);
3381 case TCM_ADJUSTRECT:
3382 return TAB_AdjustRect (infoPtr, (BOOL)wParam, (LPRECT)lParam);
3384 case TCM_SETITEMSIZE:
3385 return TAB_SetItemSize (infoPtr, (INT)LOWORD(lParam), (INT)HIWORD(lParam));
3387 case TCM_REMOVEIMAGE:
3388 return TAB_RemoveImage (infoPtr, (INT)wParam);
3390 case TCM_SETPADDING:
3391 return TAB_SetPadding (infoPtr, lParam);
3393 case TCM_GETROWCOUNT:
3394 return TAB_GetRowCount(infoPtr);
3396 case TCM_GETUNICODEFORMAT:
3397 return TAB_GetUnicodeFormat (infoPtr);
3399 case TCM_SETUNICODEFORMAT:
3400 return TAB_SetUnicodeFormat (infoPtr, (BOOL)wParam);
3402 case TCM_HIGHLIGHTITEM:
3403 return TAB_HighlightItem (infoPtr, (INT)wParam, (BOOL)LOWORD(lParam));
3405 case TCM_GETTOOLTIPS:
3406 return TAB_GetToolTips (infoPtr);
3408 case TCM_SETTOOLTIPS:
3409 return TAB_SetToolTips (infoPtr, (HWND)wParam);
3411 case TCM_GETCURFOCUS:
3412 return TAB_GetCurFocus (infoPtr);
3414 case TCM_SETCURFOCUS:
3415 return TAB_SetCurFocus (infoPtr, (INT)wParam);
3417 case TCM_SETMINTABWIDTH:
3418 return TAB_SetMinTabWidth(infoPtr, (INT)lParam);
3420 case TCM_DESELECTALL:
3421 return TAB_DeselectAll (infoPtr, (BOOL)wParam);
3423 case TCM_GETEXTENDEDSTYLE:
3424 return TAB_GetExtendedStyle (infoPtr);
3426 case TCM_SETEXTENDEDSTYLE:
3427 return TAB_SetExtendedStyle (infoPtr, wParam, lParam);
3429 case WM_GETFONT:
3430 return TAB_GetFont (infoPtr);
3432 case WM_SETFONT:
3433 return TAB_SetFont (infoPtr, (HFONT)wParam);
3435 case WM_CREATE:
3436 return TAB_Create (hwnd, lParam);
3438 case WM_NCDESTROY:
3439 return TAB_Destroy (infoPtr);
3441 case WM_GETDLGCODE:
3442 return DLGC_WANTARROWS | DLGC_WANTCHARS;
3444 case WM_LBUTTONDOWN:
3445 return TAB_LButtonDown (infoPtr, wParam, lParam);
3447 case WM_LBUTTONUP:
3448 return TAB_LButtonUp (infoPtr);
3450 case WM_NOTIFY:
3451 return SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, wParam, lParam);
3453 case WM_RBUTTONDOWN:
3454 return TAB_RButtonDown (infoPtr);
3456 case WM_MOUSEMOVE:
3457 return TAB_MouseMove (infoPtr, wParam, lParam);
3459 case WM_PRINTCLIENT:
3460 case WM_PAINT:
3461 return TAB_Paint (infoPtr, (HDC)wParam);
3463 case WM_SIZE:
3464 return TAB_Size (infoPtr);
3466 case WM_SETREDRAW:
3467 return TAB_SetRedraw (infoPtr, (BOOL)wParam);
3469 case WM_HSCROLL:
3470 return TAB_OnHScroll(infoPtr, (int)LOWORD(wParam), (int)HIWORD(wParam));
3472 case WM_STYLECHANGED:
3473 return TAB_StyleChanged(infoPtr, wParam, (LPSTYLESTRUCT)lParam);
3475 case WM_SYSCOLORCHANGE:
3476 COMCTL32_RefreshSysColors();
3477 return 0;
3479 case WM_THEMECHANGED:
3480 return theme_changed (infoPtr);
3482 case WM_KILLFOCUS:
3483 TAB_KillFocus(infoPtr);
3484 case WM_SETFOCUS:
3485 TAB_FocusChanging(infoPtr);
3486 break; /* Don't disturb normal focus behavior */
3488 case WM_KEYDOWN:
3489 return TAB_KeyDown(infoPtr, wParam, lParam);
3491 case WM_NCHITTEST:
3492 return TAB_NCHitTest(infoPtr, lParam);
3494 case WM_NCCALCSIZE:
3495 return TAB_NCCalcSize(wParam);
3497 default:
3498 if (uMsg >= WM_USER && uMsg < WM_APP && !COMCTL32_IsReflectedMessage(uMsg))
3499 WARN("unknown msg %04x wp=%08lx lp=%08lx\n",
3500 uMsg, wParam, lParam);
3501 break;
3503 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
3507 void
3508 TAB_Register (void)
3510 WNDCLASSW wndClass;
3512 ZeroMemory (&wndClass, sizeof(WNDCLASSW));
3513 wndClass.style = CS_GLOBALCLASS | CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
3514 wndClass.lpfnWndProc = TAB_WindowProc;
3515 wndClass.cbClsExtra = 0;
3516 wndClass.cbWndExtra = sizeof(TAB_INFO *);
3517 wndClass.hCursor = LoadCursorW (0, (LPWSTR)IDC_ARROW);
3518 wndClass.hbrBackground = (HBRUSH)(COLOR_BTNFACE+1);
3519 wndClass.lpszClassName = WC_TABCONTROLW;
3521 RegisterClassW (&wndClass);
3525 void
3526 TAB_Unregister (void)
3528 UnregisterClassW (WC_TABCONTROLW, NULL);