win32u: Move NtUserTranslateMessage implementation from user32.
[wine.git] / dlls / comctl32 / tab.c
blob1275381489a8f8c7c43c5396d3a4f67a28ceeee8
1 /*
2 * Tab control
4 * Copyright 1998 Anders Carlsson
5 * Copyright 1999 Alex Priem <alexp@sci.kun.nl>
6 * Copyright 1999 Francis Beaudet
7 * Copyright 2003 Vitaliy Margolen
9 * This library is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Lesser General Public
11 * License as published by the Free Software Foundation; either
12 * version 2.1 of the License, or (at your option) any later version.
14 * This library is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Lesser General Public License for more details.
19 * You should have received a copy of the GNU Lesser General Public
20 * License along with this library; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
23 * NOTES
25 * This code was audited for completeness against the documented features
26 * of Comctl32.dll version 6.0 on May. 20, 2005, by James Hawkins.
28 * Unless otherwise noted, we believe this code to be complete, as per
29 * the specification mentioned above.
30 * If you discover missing features, or bugs, please note them below.
32 * TODO:
34 * Styles:
35 * TCS_MULTISELECT - implement for VK_SPACE selection
36 * TCS_RIGHT
37 * TCS_RIGHTJUSTIFY
38 * TCS_SCROLLOPPOSITE
39 * TCS_SINGLELINE
40 * TCIF_RTLREADING
42 * Extended Styles:
43 * TCS_EX_REGISTERDROP
45 * Notifications:
46 * NM_RELEASEDCAPTURE
47 * TCN_FOCUSCHANGE
48 * TCN_GETOBJECT
50 * Macros:
51 * TabCtrl_AdjustRect
55 #include <assert.h>
56 #include <stdarg.h>
57 #include <string.h>
59 #include "windef.h"
60 #include "winbase.h"
61 #include "wingdi.h"
62 #include "winuser.h"
63 #include "winnls.h"
64 #include "commctrl.h"
65 #include "comctl32.h"
66 #include "uxtheme.h"
67 #include "vssym32.h"
68 #include "wine/debug.h"
69 #include <math.h>
71 WINE_DEFAULT_DEBUG_CHANNEL(tab);
73 typedef struct
75 DWORD dwState;
76 LPWSTR pszText;
77 INT iImage;
78 RECT rect; /* bounding rectangle of the item relative to the
79 * leftmost item (the leftmost item, 0, would have a
80 * "left" member of 0 in this rectangle)
82 * additionally the top member holds the row number
83 * and bottom is unused and should be 0 */
84 BYTE extra[1]; /* Space for caller supplied info, variable size */
85 } TAB_ITEM;
87 /* The size of a tab item depends on how much extra data is requested.
88 TCM_INSERTITEM always stores at least LPARAM sized data. */
89 #define EXTRA_ITEM_SIZE(infoPtr) (max((infoPtr)->cbInfo, sizeof(LPARAM)))
90 #define TAB_ITEM_SIZE(infoPtr) FIELD_OFFSET(TAB_ITEM, extra[EXTRA_ITEM_SIZE(infoPtr)])
92 typedef struct
94 HWND hwnd; /* Tab control window */
95 HWND hwndNotify; /* notification window (parent) */
96 UINT uNumItem; /* number of tab items */
97 UINT uNumRows; /* number of tab rows */
98 INT tabHeight; /* height of the tab row */
99 INT tabWidth; /* width of tabs */
100 INT tabMinWidth; /* minimum width of items */
101 USHORT uHItemPadding; /* amount of horizontal padding, in pixels */
102 USHORT uVItemPadding; /* amount of vertical padding, in pixels */
103 USHORT uHItemPadding_s; /* Set amount of horizontal padding, in pixels */
104 USHORT uVItemPadding_s; /* Set amount of vertical padding, in pixels */
105 HFONT hFont; /* handle to the current font */
106 HCURSOR hcurArrow; /* handle to the current cursor */
107 HIMAGELIST himl; /* handle to an image list (may be 0) */
108 HWND hwndToolTip; /* handle to tab's tooltip */
109 INT leftmostVisible; /* Used for scrolling, this member contains
110 * the index of the first visible item */
111 INT iSelected; /* the currently selected item */
112 INT iHotTracked; /* the highlighted item under the mouse */
113 INT uFocus; /* item which has the focus */
114 BOOL DoRedraw; /* flag for redrawing when tab contents is changed*/
115 BOOL needsScrolling; /* TRUE if the size of the tabs is greater than
116 * the size of the control */
117 BOOL fHeightSet; /* was the height of the tabs explicitly set? */
118 BOOL bUnicode; /* Unicode control? */
119 HWND hwndUpDown; /* Updown control used for scrolling */
120 INT cbInfo; /* Number of bytes of caller supplied info per tab */
122 DWORD exStyle; /* Extended style used, currently:
123 TCS_EX_FLATSEPARATORS, TCS_EX_REGISTERDROP */
124 DWORD dwStyle; /* the cached window GWL_STYLE */
126 HDPA items; /* dynamic array of TAB_ITEM* pointers */
127 } TAB_INFO;
129 /******************************************************************************
130 * Positioning constants
132 #define SELECTED_TAB_OFFSET 2
133 #define ROUND_CORNER_SIZE 2
134 #define DISPLAY_AREA_PADDINGX 2
135 #define DISPLAY_AREA_PADDINGY 2
136 #define CONTROL_BORDER_SIZEX 2
137 #define CONTROL_BORDER_SIZEY 2
138 #define BUTTON_SPACINGX 3
139 #define BUTTON_SPACINGY 3
140 #define FLAT_BTN_SPACINGX 8
141 #define DEFAULT_MIN_TAB_WIDTH 54
142 #define DEFAULT_PADDING_X 6
143 #define EXTRA_ICON_PADDING 3
145 #define TAB_GetInfoPtr(hwnd) ((TAB_INFO *)GetWindowLongPtrW(hwnd,0))
147 #define GET_DEFAULT_MIN_TAB_WIDTH(infoPtr) (DEFAULT_MIN_TAB_WIDTH - (DEFAULT_PADDING_X - (infoPtr)->uHItemPadding) * 2)
149 /******************************************************************************
150 * Hot-tracking timer constants
152 #define TAB_HOTTRACK_TIMER 1
153 #define TAB_HOTTRACK_TIMER_INTERVAL 100 /* milliseconds */
155 static const WCHAR themeClass[] = L"Tab";
157 static inline TAB_ITEM* TAB_GetItem(const TAB_INFO *infoPtr, INT i)
159 assert(i >= 0 && i < infoPtr->uNumItem);
160 return DPA_GetPtr(infoPtr->items, i);
163 /******************************************************************************
164 * Prototypes
166 static void TAB_InvalidateTabArea(const TAB_INFO *);
167 static void TAB_EnsureSelectionVisible(TAB_INFO *);
168 static void TAB_DrawItemInterior(const TAB_INFO *, HDC, INT, RECT*);
169 static LRESULT TAB_DeselectAll(TAB_INFO *, BOOL);
170 static BOOL TAB_InternalGetItemRect(const TAB_INFO *, INT, RECT*, RECT*);
172 static BOOL
173 TAB_SendSimpleNotify (const TAB_INFO *infoPtr, UINT code)
175 NMHDR nmhdr;
177 nmhdr.hwndFrom = infoPtr->hwnd;
178 nmhdr.idFrom = GetWindowLongPtrW(infoPtr->hwnd, GWLP_ID);
179 nmhdr.code = code;
181 return (BOOL) SendMessageW (infoPtr->hwndNotify, WM_NOTIFY,
182 nmhdr.idFrom, (LPARAM) &nmhdr);
185 static void
186 TAB_RelayEvent (HWND hwndTip, HWND hwndMsg, UINT uMsg,
187 WPARAM wParam, LPARAM lParam)
189 MSG msg;
191 msg.hwnd = hwndMsg;
192 msg.message = uMsg;
193 msg.wParam = wParam;
194 msg.lParam = lParam;
195 msg.time = GetMessageTime ();
196 msg.pt.x = (short)LOWORD(GetMessagePos ());
197 msg.pt.y = (short)HIWORD(GetMessagePos ());
199 SendMessageW (hwndTip, TTM_RELAYEVENT, 0, (LPARAM)&msg);
202 static void
203 TAB_DumpItemExternalT(const TCITEMW *pti, UINT iItem, BOOL isW)
205 if (TRACE_ON(tab)) {
206 TRACE("external tab %d, mask=0x%08x, dwState %#lx, dwStateMask %#lx, cchTextMax=0x%08x\n",
207 iItem, pti->mask, pti->dwState, pti->dwStateMask, pti->cchTextMax);
208 TRACE("external tab %d, iImage=%d, lParam %Ix, pszTextW=%s\n",
209 iItem, pti->iImage, pti->lParam, isW ? debugstr_w(pti->pszText) : debugstr_a((LPSTR)pti->pszText));
213 static void
214 TAB_DumpItemInternal(const TAB_INFO *infoPtr, UINT iItem)
216 if (TRACE_ON(tab)) {
217 TAB_ITEM *ti = TAB_GetItem(infoPtr, iItem);
219 TRACE("tab %d, dwState %#lx, pszText %s, iImage %d\n", iItem, ti->dwState, debugstr_w(ti->pszText), ti->iImage);
220 TRACE("tab %d, rect.left=%ld, rect.top(row)=%ld\n", iItem, ti->rect.left, ti->rect.top);
224 /* RETURNS
225 * the index of the selected tab, or -1 if no tab is selected. */
226 static inline LRESULT TAB_GetCurSel (const TAB_INFO *infoPtr)
228 TRACE("(%p)\n", infoPtr);
229 return infoPtr->iSelected;
232 /* RETURNS
233 * the index of the tab item that has the focus. */
234 static inline LRESULT
235 TAB_GetCurFocus (const TAB_INFO *infoPtr)
237 TRACE("(%p)\n", infoPtr);
238 return infoPtr->uFocus;
241 static inline LRESULT TAB_GetToolTips (const TAB_INFO *infoPtr)
243 TRACE("(%p)\n", infoPtr);
244 return (LRESULT)infoPtr->hwndToolTip;
247 static inline LRESULT TAB_SetCurSel (TAB_INFO *infoPtr, INT iItem)
249 INT prevItem = infoPtr->iSelected;
251 TRACE("(%p %d)\n", infoPtr, iItem);
253 if (iItem >= (INT)infoPtr->uNumItem)
254 return -1;
256 if (prevItem != iItem) {
257 if (prevItem != -1)
258 TAB_GetItem(infoPtr, prevItem)->dwState &= ~TCIS_BUTTONPRESSED;
260 if (iItem >= 0)
262 TAB_GetItem(infoPtr, iItem)->dwState |= TCIS_BUTTONPRESSED;
263 infoPtr->iSelected = iItem;
264 infoPtr->uFocus = iItem;
266 else
268 infoPtr->iSelected = -1;
269 infoPtr->uFocus = -1;
272 TAB_EnsureSelectionVisible(infoPtr);
273 TAB_InvalidateTabArea(infoPtr);
276 return prevItem;
279 static LRESULT TAB_SetCurFocus (TAB_INFO *infoPtr, INT iItem)
281 TRACE("(%p %d)\n", infoPtr, iItem);
283 if (iItem < 0) {
284 infoPtr->uFocus = -1;
285 if (infoPtr->iSelected != -1) {
286 infoPtr->iSelected = -1;
287 TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGE);
288 TAB_InvalidateTabArea(infoPtr);
291 else if (iItem < infoPtr->uNumItem) {
292 if (infoPtr->dwStyle & TCS_BUTTONS) {
293 /* set focus to new item, leave selection as is */
294 if (infoPtr->uFocus != iItem) {
295 INT prev_focus = infoPtr->uFocus;
296 RECT r;
298 infoPtr->uFocus = iItem;
300 if (prev_focus != infoPtr->iSelected) {
301 if (TAB_InternalGetItemRect(infoPtr, prev_focus, &r, NULL))
302 InvalidateRect(infoPtr->hwnd, &r, FALSE);
305 if (TAB_InternalGetItemRect(infoPtr, iItem, &r, NULL))
306 InvalidateRect(infoPtr->hwnd, &r, FALSE);
308 TAB_SendSimpleNotify(infoPtr, TCN_FOCUSCHANGE);
310 } else {
311 INT oldFocus = infoPtr->uFocus;
312 if (infoPtr->iSelected != iItem || oldFocus == -1 ) {
313 infoPtr->uFocus = iItem;
314 if (oldFocus != -1) {
315 if (!TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGING)) {
316 infoPtr->iSelected = iItem;
317 TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGE);
319 else
320 infoPtr->iSelected = iItem;
321 TAB_EnsureSelectionVisible(infoPtr);
322 TAB_InvalidateTabArea(infoPtr);
327 return 0;
330 static inline LRESULT
331 TAB_SetToolTips (TAB_INFO *infoPtr, HWND hwndToolTip)
333 TRACE("%p %p\n", infoPtr, hwndToolTip);
334 infoPtr->hwndToolTip = hwndToolTip;
335 return 0;
338 static inline LRESULT
339 TAB_SetPadding (TAB_INFO *infoPtr, LPARAM lParam)
341 TRACE("(%p %d %d)\n", infoPtr, LOWORD(lParam), HIWORD(lParam));
342 infoPtr->uHItemPadding_s = LOWORD(lParam);
343 infoPtr->uVItemPadding_s = HIWORD(lParam);
345 return 0;
348 /******************************************************************************
349 * TAB_InternalGetItemRect
351 * This method will calculate the rectangle representing a given tab item in
352 * client coordinates. This method takes scrolling into account.
354 * This method returns TRUE if the item is visible in the window and FALSE
355 * if it is completely outside the client area.
357 static BOOL TAB_InternalGetItemRect(
358 const TAB_INFO* infoPtr,
359 INT itemIndex,
360 RECT* itemRect,
361 RECT* selectedRect)
363 RECT tmpItemRect,clientRect;
365 /* Perform a sanity check and a trivial visibility check. */
366 if ( (infoPtr->uNumItem <= 0) ||
367 (itemIndex >= infoPtr->uNumItem) ||
368 (!(((infoPtr->dwStyle & TCS_MULTILINE) || (infoPtr->dwStyle & TCS_VERTICAL))) &&
369 (itemIndex < infoPtr->leftmostVisible)))
371 TRACE("Not Visible\n");
372 SetRect(itemRect, 0, 0, 0, infoPtr->tabHeight);
373 SetRectEmpty(selectedRect);
374 return FALSE;
378 * Avoid special cases in this procedure by assigning the "out"
379 * parameters if the caller didn't supply them
381 if (itemRect == NULL)
382 itemRect = &tmpItemRect;
384 /* Retrieve the unmodified item rect. */
385 *itemRect = TAB_GetItem(infoPtr,itemIndex)->rect;
387 /* calculate the times bottom and top based on the row */
388 GetClientRect(infoPtr->hwnd, &clientRect);
390 if ((infoPtr->dwStyle & TCS_BOTTOM) && (infoPtr->dwStyle & TCS_VERTICAL))
392 itemRect->right = clientRect.right - SELECTED_TAB_OFFSET - itemRect->left * infoPtr->tabHeight -
393 ((infoPtr->dwStyle & TCS_BUTTONS) ? itemRect->left * BUTTON_SPACINGX : 0);
394 itemRect->left = itemRect->right - infoPtr->tabHeight;
396 else if (infoPtr->dwStyle & TCS_VERTICAL)
398 itemRect->left = clientRect.left + SELECTED_TAB_OFFSET + itemRect->left * infoPtr->tabHeight +
399 ((infoPtr->dwStyle & TCS_BUTTONS) ? itemRect->left * BUTTON_SPACINGX : 0);
400 itemRect->right = itemRect->left + infoPtr->tabHeight;
402 else if (infoPtr->dwStyle & TCS_BOTTOM)
404 itemRect->bottom = clientRect.bottom - itemRect->top * infoPtr->tabHeight -
405 ((infoPtr->dwStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : SELECTED_TAB_OFFSET);
406 itemRect->top = itemRect->bottom - infoPtr->tabHeight;
408 else /* not TCS_BOTTOM and not TCS_VERTICAL */
410 itemRect->top = clientRect.top + itemRect->top * infoPtr->tabHeight +
411 ((infoPtr->dwStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : SELECTED_TAB_OFFSET);
412 itemRect->bottom = itemRect->top + infoPtr->tabHeight;
416 * "scroll" it to make sure the item at the very left of the
417 * tab control is the leftmost visible tab.
419 if(infoPtr->dwStyle & TCS_VERTICAL)
421 OffsetRect(itemRect,
423 -TAB_GetItem(infoPtr, infoPtr->leftmostVisible)->rect.top);
426 * Move the rectangle so the first item is slightly offset from
427 * the bottom of the tab control.
429 OffsetRect(itemRect,
431 SELECTED_TAB_OFFSET);
433 } else
435 OffsetRect(itemRect,
436 -TAB_GetItem(infoPtr, infoPtr->leftmostVisible)->rect.left,
440 * Move the rectangle so the first item is slightly offset from
441 * the left of the tab control.
443 OffsetRect(itemRect,
444 SELECTED_TAB_OFFSET,
447 TRACE("item %d tab h=%d, rect=(%s)\n",
448 itemIndex, infoPtr->tabHeight, wine_dbgstr_rect(itemRect));
450 /* Now, calculate the position of the item as if it were selected. */
451 if (selectedRect!=NULL)
453 *selectedRect = *itemRect;
455 /* The rectangle of a selected item is a bit wider. */
456 if(infoPtr->dwStyle & TCS_VERTICAL)
457 InflateRect(selectedRect, 0, SELECTED_TAB_OFFSET);
458 else
459 InflateRect(selectedRect, SELECTED_TAB_OFFSET, 0);
461 /* If it also a bit higher. */
462 if ((infoPtr->dwStyle & TCS_BOTTOM) && (infoPtr->dwStyle & TCS_VERTICAL))
464 selectedRect->left -= 2; /* the border is thicker on the right */
465 selectedRect->right += SELECTED_TAB_OFFSET;
467 else if (infoPtr->dwStyle & TCS_VERTICAL)
469 selectedRect->left -= SELECTED_TAB_OFFSET;
470 selectedRect->right += 1;
472 else if (infoPtr->dwStyle & TCS_BOTTOM)
474 selectedRect->bottom += SELECTED_TAB_OFFSET;
476 else /* not TCS_BOTTOM and not TCS_VERTICAL */
478 selectedRect->top -= SELECTED_TAB_OFFSET;
479 selectedRect->bottom -= 1;
483 /* Check for visibility */
484 if (infoPtr->dwStyle & TCS_VERTICAL)
485 return (itemRect->top < clientRect.bottom) && (itemRect->bottom > clientRect.top);
486 else
487 return (itemRect->left < clientRect.right) && (itemRect->right > clientRect.left);
490 static inline BOOL
491 TAB_GetItemRect(const TAB_INFO *infoPtr, INT item, RECT *rect)
493 TRACE("(%p, %d, %p)\n", infoPtr, item, rect);
494 return TAB_InternalGetItemRect(infoPtr, item, rect, NULL);
497 /******************************************************************************
498 * TAB_KeyDown
500 * This method is called to handle keyboard input
502 static LRESULT TAB_KeyDown(TAB_INFO* infoPtr, WPARAM keyCode, LPARAM lParam)
504 INT newItem = -1;
505 NMTCKEYDOWN nm;
507 /* TCN_KEYDOWN notification sent always */
508 nm.hdr.hwndFrom = infoPtr->hwnd;
509 nm.hdr.idFrom = GetWindowLongPtrW(infoPtr->hwnd, GWLP_ID);
510 nm.hdr.code = TCN_KEYDOWN;
511 nm.wVKey = keyCode;
512 nm.flags = lParam;
513 SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, nm.hdr.idFrom, (LPARAM)&nm);
515 switch (keyCode)
517 case VK_LEFT:
518 newItem = infoPtr->uFocus - 1;
519 break;
520 case VK_RIGHT:
521 newItem = infoPtr->uFocus + 1;
522 break;
525 /* If we changed to a valid item, change focused item */
526 if (newItem >= 0 && newItem < infoPtr->uNumItem && infoPtr->uFocus != newItem)
527 TAB_SetCurFocus(infoPtr, newItem);
529 return 0;
533 * WM_KILLFOCUS handler
535 static void TAB_KillFocus(TAB_INFO *infoPtr)
537 /* clear current focused item back to selected for TCS_BUTTONS */
538 if ((infoPtr->dwStyle & TCS_BUTTONS) && (infoPtr->uFocus != infoPtr->iSelected))
540 RECT r;
542 if (TAB_InternalGetItemRect(infoPtr, infoPtr->uFocus, &r, NULL))
543 InvalidateRect(infoPtr->hwnd, &r, FALSE);
545 infoPtr->uFocus = infoPtr->iSelected;
549 /******************************************************************************
550 * TAB_FocusChanging
552 * This method is called whenever the focus goes in or out of this control
553 * it is used to update the visual state of the control.
555 static void TAB_FocusChanging(const TAB_INFO *infoPtr)
557 RECT selectedRect;
558 BOOL isVisible;
561 * Get the rectangle for the item.
563 isVisible = TAB_InternalGetItemRect(infoPtr,
564 infoPtr->uFocus,
565 NULL,
566 &selectedRect);
569 * If the rectangle is not completely invisible, invalidate that
570 * portion of the window.
572 if (isVisible)
574 TRACE("invalidate (%s)\n", wine_dbgstr_rect(&selectedRect));
575 InvalidateRect(infoPtr->hwnd, &selectedRect, TRUE);
579 static INT TAB_InternalHitTest (const TAB_INFO *infoPtr, POINT pt, UINT *flags)
581 RECT rect;
582 INT iCount;
584 for (iCount = 0; iCount < infoPtr->uNumItem; iCount++)
586 TAB_InternalGetItemRect(infoPtr, iCount, &rect, NULL);
588 if (PtInRect(&rect, pt))
590 *flags = TCHT_ONITEM;
591 return iCount;
595 *flags = TCHT_NOWHERE;
596 return -1;
599 static inline LRESULT
600 TAB_HitTest (const TAB_INFO *infoPtr, LPTCHITTESTINFO lptest)
602 TRACE("(%p, %p)\n", infoPtr, lptest);
603 return TAB_InternalHitTest (infoPtr, lptest->pt, &lptest->flags);
606 /******************************************************************************
607 * TAB_NCHitTest
609 * Napster v2b5 has a tab control for its main navigation which has a client
610 * area that covers the whole area of the dialog pages.
611 * That's why it receives all msgs for that area and the underlying dialog ctrls
612 * are dead.
613 * So I decided that we should handle WM_NCHITTEST here and return
614 * HTTRANSPARENT if we don't hit the tab control buttons.
615 * FIXME: WM_NCHITTEST handling correct ? Fix it if you know that Windows
616 * doesn't do it that way. Maybe depends on tab control styles ?
618 static inline LRESULT
619 TAB_NCHitTest (const TAB_INFO *infoPtr, LPARAM lParam)
621 POINT pt;
622 UINT dummyflag;
624 pt.x = (short)LOWORD(lParam);
625 pt.y = (short)HIWORD(lParam);
626 ScreenToClient(infoPtr->hwnd, &pt);
628 if (TAB_InternalHitTest(infoPtr, pt, &dummyflag) == -1)
629 return HTTRANSPARENT;
630 else
631 return HTCLIENT;
634 static LRESULT
635 TAB_LButtonDown (TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
637 POINT pt;
638 INT newItem;
639 UINT dummy;
641 if (infoPtr->hwndToolTip)
642 TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
643 WM_LBUTTONDOWN, wParam, lParam);
645 if (!(infoPtr->dwStyle & TCS_FOCUSNEVER)) {
646 SetFocus (infoPtr->hwnd);
649 if (infoPtr->hwndToolTip)
650 TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
651 WM_LBUTTONDOWN, wParam, lParam);
653 pt.x = (short)LOWORD(lParam);
654 pt.y = (short)HIWORD(lParam);
656 newItem = TAB_InternalHitTest (infoPtr, pt, &dummy);
658 TRACE("On Tab, item %d\n", newItem);
660 if ((newItem != -1) && (infoPtr->iSelected != newItem))
662 if ((infoPtr->dwStyle & TCS_BUTTONS) && (infoPtr->dwStyle & TCS_MULTISELECT) &&
663 (wParam & MK_CONTROL))
665 RECT r;
667 /* toggle multiselection */
668 TAB_GetItem(infoPtr, newItem)->dwState ^= TCIS_BUTTONPRESSED;
669 if (TAB_InternalGetItemRect (infoPtr, newItem, &r, NULL))
670 InvalidateRect (infoPtr->hwnd, &r, TRUE);
672 else
674 INT i;
675 BOOL pressed = FALSE;
677 /* any button pressed ? */
678 for (i = 0; i < infoPtr->uNumItem; i++)
679 if ((TAB_GetItem (infoPtr, i)->dwState & TCIS_BUTTONPRESSED) &&
680 (infoPtr->iSelected != i))
682 pressed = TRUE;
683 break;
686 if (TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGING))
687 return 0;
689 if (pressed)
690 TAB_DeselectAll (infoPtr, FALSE);
691 else
692 TAB_SetCurSel(infoPtr, newItem);
694 TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGE);
698 return 0;
701 static inline LRESULT
702 TAB_LButtonUp (const TAB_INFO *infoPtr)
704 TAB_SendSimpleNotify(infoPtr, NM_CLICK);
706 return 0;
709 static inline void
710 TAB_RButtonUp (const TAB_INFO *infoPtr)
712 TAB_SendSimpleNotify(infoPtr, NM_RCLICK);
715 /******************************************************************************
716 * TAB_DrawLoneItemInterior
718 * This calls TAB_DrawItemInterior. However, TAB_DrawItemInterior is normally
719 * called by TAB_DrawItem which is normally called by TAB_Refresh which sets
720 * up the device context and font. This routine does the same setup but
721 * only calls TAB_DrawItemInterior for the single specified item.
723 static void
724 TAB_DrawLoneItemInterior(const TAB_INFO* infoPtr, int iItem)
726 HDC hdc = GetDC(infoPtr->hwnd);
727 RECT r, rC;
729 /* Clip UpDown control to not draw over it */
730 if (infoPtr->needsScrolling)
732 GetWindowRect(infoPtr->hwnd, &rC);
733 GetWindowRect(infoPtr->hwndUpDown, &r);
734 ExcludeClipRect(hdc, r.left - rC.left, r.top - rC.top, r.right - rC.left, r.bottom - rC.top);
736 TAB_DrawItemInterior(infoPtr, hdc, iItem, NULL);
737 ReleaseDC(infoPtr->hwnd, hdc);
740 /* update a tab after hottracking - invalidate it or just redraw the interior,
741 * based on whether theming is used or not */
742 static inline void hottrack_refresh(const TAB_INFO *infoPtr, int tabIndex)
744 if (tabIndex == -1) return;
746 if (GetWindowTheme (infoPtr->hwnd))
748 RECT rect;
749 TAB_InternalGetItemRect(infoPtr, tabIndex, &rect, NULL);
750 InvalidateRect (infoPtr->hwnd, &rect, FALSE);
752 else
753 TAB_DrawLoneItemInterior(infoPtr, tabIndex);
756 /******************************************************************************
757 * TAB_HotTrackTimerProc
759 * When a mouse-move event causes a tab to be highlighted (hot-tracking), a
760 * timer is setup so we can check if the mouse is moved out of our window.
761 * (We don't get an event when the mouse leaves, the mouse-move events just
762 * stop being delivered to our window and just start being delivered to
763 * another window.) This function is called when the timer triggers so
764 * we can check if the mouse has left our window. If so, we un-highlight
765 * the hot-tracked tab.
767 static void CALLBACK
768 TAB_HotTrackTimerProc
770 HWND hwnd, /* handle of window for timer messages */
771 UINT uMsg, /* WM_TIMER message */
772 UINT_PTR idEvent, /* timer identifier */
773 DWORD dwTime /* current system time */
776 TAB_INFO* infoPtr = TAB_GetInfoPtr(hwnd);
778 if (infoPtr != NULL && infoPtr->iHotTracked >= 0)
780 POINT pt;
783 ** If we can't get the cursor position, or if the cursor is outside our
784 ** window, we un-highlight the hot-tracked tab. Note that the cursor is
785 ** "outside" even if it is within our bounding rect if another window
786 ** overlaps. Note also that the case where the cursor stayed within our
787 ** window but has moved off the hot-tracked tab will be handled by the
788 ** WM_MOUSEMOVE event.
790 if (!GetCursorPos(&pt) || WindowFromPoint(pt) != hwnd)
792 /* Redraw iHotTracked to look normal */
793 INT iRedraw = infoPtr->iHotTracked;
794 infoPtr->iHotTracked = -1;
795 hottrack_refresh (infoPtr, iRedraw);
797 /* Kill this timer */
798 KillTimer(hwnd, TAB_HOTTRACK_TIMER);
803 /******************************************************************************
804 * TAB_RecalcHotTrack
806 * If a tab control has the TCS_HOTTRACK style, then the tab under the mouse
807 * should be highlighted. This function determines which tab in a tab control,
808 * if any, is under the mouse and records that information. The caller may
809 * supply output parameters to receive the item number of the tab item which
810 * was highlighted but isn't any longer and of the tab item which is now
811 * highlighted but wasn't previously. The caller can use this information to
812 * selectively redraw those tab items.
814 * If the caller has a mouse position, it can supply it through the pos
815 * parameter. For example, TAB_MouseMove does this. Otherwise, the caller
816 * supplies NULL and this function determines the current mouse position
817 * itself.
819 static void
820 TAB_RecalcHotTrack
822 TAB_INFO* infoPtr,
823 const LPARAM* pos,
824 int* out_redrawLeave,
825 int* out_redrawEnter
828 int item = -1;
831 if (out_redrawLeave != NULL)
832 *out_redrawLeave = -1;
833 if (out_redrawEnter != NULL)
834 *out_redrawEnter = -1;
836 if ((infoPtr->dwStyle & TCS_HOTTRACK) || GetWindowTheme(infoPtr->hwnd))
838 POINT pt;
839 UINT flags;
841 if (pos == NULL)
843 GetCursorPos(&pt);
844 ScreenToClient(infoPtr->hwnd, &pt);
846 else
848 pt.x = (short)LOWORD(*pos);
849 pt.y = (short)HIWORD(*pos);
852 item = TAB_InternalHitTest(infoPtr, pt, &flags);
855 if (item != infoPtr->iHotTracked)
857 if (infoPtr->iHotTracked >= 0)
859 /* Mark currently hot-tracked to be redrawn to look normal */
860 if (out_redrawLeave != NULL)
861 *out_redrawLeave = infoPtr->iHotTracked;
863 if (item < 0)
865 /* Kill timer which forces recheck of mouse pos */
866 KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
869 else
871 /* Start timer so we recheck mouse pos */
872 UINT timerID = SetTimer
874 infoPtr->hwnd,
875 TAB_HOTTRACK_TIMER,
876 TAB_HOTTRACK_TIMER_INTERVAL,
877 TAB_HotTrackTimerProc
880 if (timerID == 0)
881 return; /* Hot tracking not available */
884 infoPtr->iHotTracked = item;
886 if (item >= 0)
888 /* Mark new hot-tracked to be redrawn to look highlighted */
889 if (out_redrawEnter != NULL)
890 *out_redrawEnter = item;
895 /******************************************************************************
896 * TAB_MouseMove
898 * Handles the mouse-move event. Updates tooltips. Updates hot-tracking.
900 static LRESULT
901 TAB_MouseMove (TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
903 int redrawLeave;
904 int redrawEnter;
906 if (infoPtr->hwndToolTip)
907 TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
908 WM_LBUTTONDOWN, wParam, lParam);
910 /* Determine which tab to highlight. Redraw tabs which change highlight
911 ** status. */
912 TAB_RecalcHotTrack(infoPtr, &lParam, &redrawLeave, &redrawEnter);
914 hottrack_refresh (infoPtr, redrawLeave);
915 hottrack_refresh (infoPtr, redrawEnter);
917 return 0;
920 /******************************************************************************
921 * TAB_AdjustRect
923 * Calculates the tab control's display area given the window rectangle or
924 * the window rectangle given the requested display rectangle.
926 static LRESULT TAB_AdjustRect(const TAB_INFO *infoPtr, WPARAM fLarger, LPRECT prc)
928 LONG *iRightBottom, *iLeftTop;
930 TRACE("hwnd %p, fLarger %Id, (%s)\n", infoPtr->hwnd, fLarger, wine_dbgstr_rect(prc));
932 if (!prc) return -1;
934 if(infoPtr->dwStyle & TCS_VERTICAL)
936 iRightBottom = &(prc->right);
937 iLeftTop = &(prc->left);
939 else
941 iRightBottom = &(prc->bottom);
942 iLeftTop = &(prc->top);
945 if (fLarger) /* Go from display rectangle */
947 /* Add the height of the tabs. */
948 if (infoPtr->dwStyle & TCS_BOTTOM)
949 *iRightBottom += infoPtr->tabHeight * infoPtr->uNumRows;
950 else
951 *iLeftTop -= infoPtr->tabHeight * infoPtr->uNumRows +
952 ((infoPtr->dwStyle & TCS_BUTTONS)? 3 * (infoPtr->uNumRows - 1) : 0);
954 /* Inflate the rectangle for the padding */
955 InflateRect(prc, DISPLAY_AREA_PADDINGX, DISPLAY_AREA_PADDINGY);
957 /* Inflate for the border */
958 InflateRect(prc, CONTROL_BORDER_SIZEX, CONTROL_BORDER_SIZEY);
960 else /* Go from window rectangle. */
962 /* Deflate the rectangle for the border */
963 InflateRect(prc, -CONTROL_BORDER_SIZEX, -CONTROL_BORDER_SIZEY);
965 /* Deflate the rectangle for the padding */
966 InflateRect(prc, -DISPLAY_AREA_PADDINGX, -DISPLAY_AREA_PADDINGY);
968 /* Remove the height of the tabs. */
969 if (infoPtr->dwStyle & TCS_BOTTOM)
970 *iRightBottom -= infoPtr->tabHeight * infoPtr->uNumRows;
971 else
972 *iLeftTop += (infoPtr->tabHeight) * infoPtr->uNumRows +
973 ((infoPtr->dwStyle & TCS_BUTTONS)? 3 * (infoPtr->uNumRows - 1) : 0);
976 return 0;
979 /******************************************************************************
980 * TAB_OnHScroll
982 * This method will handle the notification from the scroll control and
983 * perform the scrolling operation on the tab control.
985 static LRESULT TAB_OnHScroll(TAB_INFO *infoPtr, int nScrollCode, int nPos)
987 if(nScrollCode == SB_THUMBPOSITION && nPos != infoPtr->leftmostVisible)
989 if(nPos < infoPtr->leftmostVisible)
990 infoPtr->leftmostVisible--;
991 else
992 infoPtr->leftmostVisible++;
994 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
995 TAB_InvalidateTabArea(infoPtr);
996 SendMessageW(infoPtr->hwndUpDown, UDM_SETPOS, 0,
997 MAKELONG(infoPtr->leftmostVisible, 0));
1000 return 0;
1003 /******************************************************************************
1004 * TAB_SetupScrolling
1006 * This method will check the current scrolling state and make sure the
1007 * scrolling control is displayed (or not).
1009 static void TAB_SetupScrolling(
1010 TAB_INFO* infoPtr,
1011 const RECT* clientRect)
1013 INT maxRange = 0;
1015 if (infoPtr->needsScrolling)
1017 RECT controlPos;
1018 INT vsize, tabwidth;
1021 * Calculate the position of the scroll control.
1023 controlPos.right = clientRect->right;
1024 controlPos.left = controlPos.right - 2 * GetSystemMetrics(SM_CXHSCROLL);
1026 if (infoPtr->dwStyle & TCS_BOTTOM)
1028 controlPos.top = clientRect->bottom - infoPtr->tabHeight;
1029 controlPos.bottom = controlPos.top + GetSystemMetrics(SM_CYHSCROLL);
1031 else
1033 controlPos.bottom = clientRect->top + infoPtr->tabHeight;
1034 controlPos.top = controlPos.bottom - GetSystemMetrics(SM_CYHSCROLL);
1038 * If we don't have a scroll control yet, we want to create one.
1039 * If we have one, we want to make sure it's positioned properly.
1041 if (infoPtr->hwndUpDown==0)
1043 infoPtr->hwndUpDown = CreateWindowW(UPDOWN_CLASSW, L"",
1044 WS_VISIBLE | WS_CHILD | UDS_HORZ,
1045 controlPos.left, controlPos.top,
1046 controlPos.right - controlPos.left,
1047 controlPos.bottom - controlPos.top,
1048 infoPtr->hwnd, NULL, NULL, NULL);
1050 else
1052 SetWindowPos(infoPtr->hwndUpDown,
1053 NULL,
1054 controlPos.left, controlPos.top,
1055 controlPos.right - controlPos.left,
1056 controlPos.bottom - controlPos.top,
1057 SWP_SHOWWINDOW | SWP_NOZORDER);
1060 /* Now calculate upper limit of the updown control range.
1061 * We do this by calculating how many tabs will be offscreen when the
1062 * last tab is visible.
1064 if(infoPtr->uNumItem)
1066 vsize = clientRect->right - (controlPos.right - controlPos.left + 1);
1067 maxRange = infoPtr->uNumItem;
1068 tabwidth = TAB_GetItem(infoPtr, infoPtr->uNumItem - 1)->rect.right;
1070 for(; maxRange > 0; maxRange--)
1072 if(tabwidth - TAB_GetItem(infoPtr,maxRange - 1)->rect.left > vsize)
1073 break;
1076 if(maxRange == infoPtr->uNumItem)
1077 maxRange--;
1080 else
1082 /* If we once had a scroll control... hide it */
1083 if (infoPtr->hwndUpDown)
1084 ShowWindow(infoPtr->hwndUpDown, SW_HIDE);
1086 if (infoPtr->hwndUpDown)
1087 SendMessageW(infoPtr->hwndUpDown, UDM_SETRANGE32, 0, maxRange);
1090 /******************************************************************************
1091 * TAB_SetItemBounds
1093 * This method will calculate the position rectangles of all the items in the
1094 * control. The rectangle calculated starts at 0 for the first item in the
1095 * list and ignores scrolling and selection.
1096 * It also uses the current font to determine the height of the tab row and
1097 * it checks if all the tabs fit in the client area of the window. If they
1098 * don't, a scrolling control is added.
1100 static void TAB_SetItemBounds (TAB_INFO *infoPtr)
1102 TEXTMETRICW fontMetrics;
1103 UINT curItem;
1104 INT curItemLeftPos;
1105 INT curItemRowCount;
1106 HFONT hFont, hOldFont;
1107 HDC hdc;
1108 RECT clientRect;
1109 INT iTemp;
1110 RECT* rcItem;
1111 INT iIndex;
1112 INT icon_width = 0;
1115 * We need to get text information so we need a DC and we need to select
1116 * a font.
1118 hdc = GetDC(infoPtr->hwnd);
1120 hFont = infoPtr->hFont ? infoPtr->hFont : GetStockObject (SYSTEM_FONT);
1121 hOldFont = SelectObject (hdc, hFont);
1124 * We will base the rectangle calculations on the client rectangle
1125 * of the control.
1127 GetClientRect(infoPtr->hwnd, &clientRect);
1129 /* if TCS_VERTICAL then swap the height and width so this code places the
1130 tabs along the top of the rectangle and we can just rotate them after
1131 rather than duplicate all of the below code */
1132 if(infoPtr->dwStyle & TCS_VERTICAL)
1134 iTemp = clientRect.bottom;
1135 clientRect.bottom = clientRect.right;
1136 clientRect.right = iTemp;
1139 /* Now use hPadding and vPadding */
1140 infoPtr->uHItemPadding = infoPtr->uHItemPadding_s;
1141 infoPtr->uVItemPadding = infoPtr->uVItemPadding_s;
1143 /* The leftmost item will be "0" aligned */
1144 curItemLeftPos = 0;
1145 curItemRowCount = infoPtr->uNumItem ? 1 : 0;
1147 if (!(infoPtr->fHeightSet))
1149 int item_height;
1150 INT icon_height = 0, cx;
1152 /* Use the current font to determine the height of a tab. */
1153 GetTextMetricsW(hdc, &fontMetrics);
1155 /* Get the icon height */
1156 if (infoPtr->himl)
1157 ImageList_GetIconSize(infoPtr->himl, &cx, &icon_height);
1159 /* Take the highest between font or icon */
1160 if (fontMetrics.tmHeight > icon_height)
1161 item_height = fontMetrics.tmHeight + 2;
1162 else
1163 item_height = icon_height;
1166 * Make sure there is enough space for the letters + icon + growing the
1167 * selected item + extra space for the selected item.
1169 infoPtr->tabHeight = item_height +
1170 ((infoPtr->dwStyle & TCS_BUTTONS) ? 2 : 1) *
1171 infoPtr->uVItemPadding;
1173 TRACE("tabH=%d, tmH %ld, iconh %d\n", infoPtr->tabHeight, fontMetrics.tmHeight, icon_height);
1176 TRACE("client right %ld\n", clientRect.right);
1178 /* Get the icon width */
1179 if (infoPtr->himl)
1181 INT cy;
1183 ImageList_GetIconSize(infoPtr->himl, &icon_width, &cy);
1185 if (infoPtr->dwStyle & TCS_FIXEDWIDTH)
1186 icon_width += 4;
1187 else
1188 /* Add padding if icon is present */
1189 icon_width += infoPtr->uHItemPadding;
1192 for (curItem = 0; curItem < infoPtr->uNumItem; curItem++)
1194 TAB_ITEM *curr = TAB_GetItem(infoPtr, curItem);
1196 /* Set the leftmost position of the tab. */
1197 curr->rect.left = curItemLeftPos;
1199 if (infoPtr->dwStyle & TCS_FIXEDWIDTH)
1201 curr->rect.right = curr->rect.left +
1202 max(infoPtr->tabWidth, icon_width);
1204 else if (!curr->pszText)
1206 /* If no text use minimum tab width including padding. */
1207 if (infoPtr->tabMinWidth < 0)
1208 curr->rect.right = curr->rect.left + GET_DEFAULT_MIN_TAB_WIDTH(infoPtr);
1209 else
1211 curr->rect.right = curr->rect.left + infoPtr->tabMinWidth;
1213 /* Add extra padding if icon is present */
1214 if (infoPtr->himl && infoPtr->tabMinWidth > 0 && infoPtr->tabMinWidth < DEFAULT_MIN_TAB_WIDTH
1215 && infoPtr->uHItemPadding > 1)
1216 curr->rect.right += EXTRA_ICON_PADDING * (infoPtr->uHItemPadding-1);
1219 else
1221 int tabwidth;
1222 SIZE size;
1223 /* Calculate how wide the tab is depending on the text it contains */
1224 GetTextExtentPoint32W(hdc, curr->pszText,
1225 lstrlenW(curr->pszText), &size);
1227 tabwidth = size.cx + icon_width + 2 * infoPtr->uHItemPadding;
1229 if (infoPtr->tabMinWidth < 0)
1230 tabwidth = max(tabwidth, GET_DEFAULT_MIN_TAB_WIDTH(infoPtr));
1231 else
1232 tabwidth = max(tabwidth, infoPtr->tabMinWidth);
1234 curr->rect.right = curr->rect.left + tabwidth;
1235 TRACE("for <%s>, rect %s\n", debugstr_w(curr->pszText), wine_dbgstr_rect(&curr->rect));
1239 * Check if this is a multiline tab control and if so
1240 * check to see if we should wrap the tabs
1242 * Wrap all these tabs. We will arrange them evenly later.
1246 if (((infoPtr->dwStyle & TCS_MULTILINE) || (infoPtr->dwStyle & TCS_VERTICAL)) &&
1247 (curr->rect.right >
1248 (clientRect.right - CONTROL_BORDER_SIZEX - DISPLAY_AREA_PADDINGX)))
1250 curr->rect.right -= curr->rect.left;
1252 curr->rect.left = 0;
1253 curItemRowCount++;
1254 TRACE("wrapping <%s>, rect %s\n", debugstr_w(curr->pszText), wine_dbgstr_rect(&curr->rect));
1257 curr->rect.bottom = 0;
1258 curr->rect.top = curItemRowCount - 1;
1260 TRACE("Rect: %s\n", wine_dbgstr_rect(&curr->rect));
1263 * The leftmost position of the next item is the rightmost position
1264 * of this one.
1266 if (infoPtr->dwStyle & TCS_BUTTONS)
1268 curItemLeftPos = curr->rect.right + BUTTON_SPACINGX;
1269 if (infoPtr->dwStyle & TCS_FLATBUTTONS)
1270 curItemLeftPos += FLAT_BTN_SPACINGX;
1272 else
1273 curItemLeftPos = curr->rect.right;
1276 if (!((infoPtr->dwStyle & TCS_MULTILINE) || (infoPtr->dwStyle & TCS_VERTICAL)))
1279 * Check if we need a scrolling control.
1281 infoPtr->needsScrolling = (curItemLeftPos + (2 * SELECTED_TAB_OFFSET) >
1282 clientRect.right);
1284 /* Don't need scrolling, then update infoPtr->leftmostVisible */
1285 if(!infoPtr->needsScrolling)
1286 infoPtr->leftmostVisible = 0;
1288 else
1291 * No scrolling in Multiline or Vertical styles.
1293 infoPtr->needsScrolling = FALSE;
1294 infoPtr->leftmostVisible = 0;
1296 TAB_SetupScrolling(infoPtr, &clientRect);
1298 /* Set the number of rows */
1299 infoPtr->uNumRows = curItemRowCount;
1301 /* Arrange all tabs evenly if style says so */
1302 if (!(infoPtr->dwStyle & TCS_RAGGEDRIGHT) &&
1303 ((infoPtr->dwStyle & TCS_MULTILINE) || (infoPtr->dwStyle & TCS_VERTICAL)) &&
1304 (infoPtr->uNumItem > 0) &&
1305 (infoPtr->uNumRows > 1))
1307 INT tabPerRow,remTab,iRow;
1308 UINT iItm;
1309 INT iCount=0;
1312 * Ok windows tries to even out the rows. place the same
1313 * number of tabs in each row. So lets give that a shot
1316 tabPerRow = infoPtr->uNumItem / (infoPtr->uNumRows);
1317 remTab = infoPtr->uNumItem % (infoPtr->uNumRows);
1319 for (iItm=0,iRow=0,iCount=0,curItemLeftPos=0;
1320 iItm<infoPtr->uNumItem;
1321 iItm++,iCount++)
1323 /* normalize the current rect */
1324 TAB_ITEM *curr = TAB_GetItem(infoPtr, iItm);
1326 /* shift the item to the left side of the clientRect */
1327 curr->rect.right -= curr->rect.left;
1328 curr->rect.left = 0;
1330 TRACE("r=%ld, cl=%d, cl.r=%ld, iCount=%d, iRow=%d, uNumRows=%d, remTab=%d, tabPerRow=%d\n",
1331 curr->rect.right, curItemLeftPos, clientRect.right,
1332 iCount, iRow, infoPtr->uNumRows, remTab, tabPerRow);
1334 /* if we have reached the maximum number of tabs on this row */
1335 /* move to the next row, reset our current item left position and */
1336 /* the count of items on this row */
1338 if (infoPtr->dwStyle & TCS_VERTICAL) {
1339 /* Vert: Add the remaining tabs in the *last* remainder rows */
1340 if (iCount >= ((iRow>=(INT)infoPtr->uNumRows - remTab)?tabPerRow + 1:tabPerRow)) {
1341 iRow++;
1342 curItemLeftPos = 0;
1343 iCount = 0;
1345 } else {
1346 /* Horz: Add the remaining tabs in the *first* remainder rows */
1347 if (iCount >= ((iRow<remTab)?tabPerRow + 1:tabPerRow)) {
1348 iRow++;
1349 curItemLeftPos = 0;
1350 iCount = 0;
1354 /* shift the item to the right to place it as the next item in this row */
1355 curr->rect.left += curItemLeftPos;
1356 curr->rect.right += curItemLeftPos;
1357 curr->rect.top = iRow;
1358 if (infoPtr->dwStyle & TCS_BUTTONS)
1360 curItemLeftPos = curr->rect.right + 1;
1361 if (infoPtr->dwStyle & TCS_FLATBUTTONS)
1362 curItemLeftPos += FLAT_BTN_SPACINGX;
1364 else
1365 curItemLeftPos = curr->rect.right;
1367 TRACE("arranging <%s>, rect %s\n", debugstr_w(curr->pszText), wine_dbgstr_rect(&curr->rect));
1371 * Justify the rows
1374 INT widthDiff, iIndexStart=0, iIndexEnd=0;
1375 INT remainder;
1376 INT iCount=0;
1378 while(iIndexStart < infoPtr->uNumItem)
1380 TAB_ITEM *start = TAB_GetItem(infoPtr, iIndexStart);
1383 * find the index of the row
1385 /* find the first item on the next row */
1386 for (iIndexEnd=iIndexStart;
1387 (iIndexEnd < infoPtr->uNumItem) &&
1388 (TAB_GetItem(infoPtr, iIndexEnd)->rect.top ==
1389 start->rect.top) ;
1390 iIndexEnd++)
1391 /* intentionally blank */;
1394 * we need to justify these tabs so they fill the whole given
1395 * client area
1398 /* find the amount of space remaining on this row */
1399 widthDiff = clientRect.right - (2 * SELECTED_TAB_OFFSET) -
1400 TAB_GetItem(infoPtr, iIndexEnd - 1)->rect.right;
1402 /* iCount is the number of tab items on this row */
1403 iCount = iIndexEnd - iIndexStart;
1405 if (iCount > 1)
1407 remainder = widthDiff % iCount;
1408 widthDiff = widthDiff / iCount;
1409 /* add widthDiff/iCount, or extra space/items on row, to each item on this row */
1410 for (iIndex=iIndexStart, iCount=0; iIndex < iIndexEnd; iIndex++, iCount++)
1412 TAB_ITEM *item = TAB_GetItem(infoPtr, iIndex);
1414 item->rect.left += iCount * widthDiff;
1415 item->rect.right += (iCount + 1) * widthDiff;
1417 TRACE("adjusting 1 <%s>, rect %s\n", debugstr_w(item->pszText), wine_dbgstr_rect(&item->rect));
1420 TAB_GetItem(infoPtr, iIndex - 1)->rect.right += remainder;
1422 else /* we have only one item on this row, make it take up the entire row */
1424 start->rect.left = clientRect.left;
1425 start->rect.right = clientRect.right - 4;
1427 TRACE("adjusting 2 <%s>, rect %s\n", debugstr_w(start->pszText), wine_dbgstr_rect(&start->rect));
1430 iIndexStart = iIndexEnd;
1435 /* if TCS_VERTICAL rotate the tabs so they are along the side of the clientRect */
1436 if(infoPtr->dwStyle & TCS_VERTICAL)
1438 RECT rcOriginal;
1439 for(iIndex = 0; iIndex < infoPtr->uNumItem; iIndex++)
1441 rcItem = &TAB_GetItem(infoPtr, iIndex)->rect;
1443 rcOriginal = *rcItem;
1445 /* this is rotating the items by 90 degrees clockwise around the center of the control */
1446 rcItem->top = (rcOriginal.left - clientRect.left);
1447 rcItem->bottom = rcItem->top + (rcOriginal.right - rcOriginal.left);
1448 rcItem->left = rcOriginal.top;
1449 rcItem->right = rcOriginal.bottom;
1453 TAB_EnsureSelectionVisible(infoPtr);
1454 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
1456 /* Cleanup */
1457 SelectObject (hdc, hOldFont);
1458 ReleaseDC (infoPtr->hwnd, hdc);
1462 static void
1463 TAB_EraseTabInterior(const TAB_INFO *infoPtr, HDC hdc, INT iItem, const RECT *drawRect)
1465 HBRUSH hbr = CreateSolidBrush (comctl32_color.clrBtnFace);
1466 BOOL deleteBrush = TRUE;
1467 RECT rTemp = *drawRect;
1469 if (infoPtr->dwStyle & TCS_BUTTONS)
1471 if (iItem == infoPtr->iSelected)
1473 /* Background color */
1474 if (!(infoPtr->dwStyle & TCS_OWNERDRAWFIXED))
1476 DeleteObject(hbr);
1477 hbr = GetSysColorBrush(COLOR_SCROLLBAR);
1479 SetTextColor(hdc, comctl32_color.clr3dFace);
1480 SetBkColor(hdc, comctl32_color.clr3dHilight);
1482 /* if COLOR_WINDOW happens to be the same as COLOR_3DHILIGHT
1483 * we better use 0x55aa bitmap brush to make scrollbar's background
1484 * look different from the window background.
1486 if (comctl32_color.clr3dHilight == comctl32_color.clrWindow)
1487 hbr = COMCTL32_hPattern55AABrush;
1489 deleteBrush = FALSE;
1491 FillRect(hdc, &rTemp, hbr);
1493 else /* ! selected */
1495 if (infoPtr->dwStyle & TCS_FLATBUTTONS)
1497 InflateRect(&rTemp, 2, 2);
1498 FillRect(hdc, &rTemp, hbr);
1499 if (iItem == infoPtr->iHotTracked ||
1500 (iItem != infoPtr->iSelected && iItem == infoPtr->uFocus))
1501 DrawEdge(hdc, &rTemp, BDR_RAISEDINNER, BF_RECT);
1503 else
1504 FillRect(hdc, &rTemp, hbr);
1508 else /* !TCS_BUTTONS */
1510 InflateRect(&rTemp, -2, -2);
1511 if (!GetWindowTheme (infoPtr->hwnd))
1512 FillRect(hdc, &rTemp, hbr);
1515 /* highlighting is drawn on top of previous fills */
1516 if (TAB_GetItem(infoPtr, iItem)->dwState & TCIS_HIGHLIGHTED)
1518 if (deleteBrush)
1520 DeleteObject(hbr);
1521 deleteBrush = FALSE;
1523 hbr = GetSysColorBrush(COLOR_HIGHLIGHT);
1524 FillRect(hdc, &rTemp, hbr);
1527 /* Cleanup */
1528 if (deleteBrush) DeleteObject(hbr);
1531 /******************************************************************************
1532 * TAB_DrawItemInterior
1534 * This method is used to draw the interior (text and icon) of a single tab
1535 * into the tab control.
1537 static void
1538 TAB_DrawItemInterior(const TAB_INFO *infoPtr, HDC hdc, INT iItem, RECT *drawRect)
1540 RECT localRect;
1542 HPEN htextPen;
1543 HPEN holdPen;
1544 INT oldBkMode;
1545 HFONT hOldFont;
1547 /* if (drawRect == NULL) */
1549 BOOL isVisible;
1550 RECT itemRect;
1551 RECT selectedRect;
1554 * Get the rectangle for the item.
1556 isVisible = TAB_InternalGetItemRect(infoPtr, iItem, &itemRect, &selectedRect);
1557 if (!isVisible)
1558 return;
1561 * Make sure drawRect points to something valid; simplifies code.
1563 drawRect = &localRect;
1566 * This logic copied from the part of TAB_DrawItem which draws
1567 * the tab background. It's important to keep it in sync. I
1568 * would have liked to avoid code duplication, but couldn't figure
1569 * out how without making spaghetti of TAB_DrawItem.
1571 if (iItem == infoPtr->iSelected)
1572 *drawRect = selectedRect;
1573 else
1574 *drawRect = itemRect;
1576 if (infoPtr->dwStyle & TCS_BUTTONS)
1578 if (iItem == infoPtr->iSelected)
1580 drawRect->left += 4;
1581 drawRect->top += 4;
1582 drawRect->right -= 4;
1584 if (infoPtr->dwStyle & TCS_VERTICAL)
1586 if (!(infoPtr->dwStyle & TCS_BOTTOM)) drawRect->right += 1;
1587 drawRect->bottom -= 4;
1589 else
1591 if (infoPtr->dwStyle & TCS_BOTTOM)
1593 drawRect->top -= 2;
1594 drawRect->bottom -= 4;
1596 else
1597 drawRect->bottom -= 1;
1600 else
1601 InflateRect(drawRect, -2, -2);
1603 else
1605 if ((infoPtr->dwStyle & TCS_VERTICAL) && (infoPtr->dwStyle & TCS_BOTTOM))
1607 if (iItem != infoPtr->iSelected)
1609 drawRect->left += 2;
1610 InflateRect(drawRect, 0, -2);
1613 else if (infoPtr->dwStyle & TCS_VERTICAL)
1615 if (iItem == infoPtr->iSelected)
1617 drawRect->right += 1;
1619 else
1621 drawRect->right -= 2;
1622 InflateRect(drawRect, 0, -2);
1625 else if (infoPtr->dwStyle & TCS_BOTTOM)
1627 if (iItem == infoPtr->iSelected)
1629 drawRect->top -= 2;
1631 else
1633 InflateRect(drawRect, -2, -2);
1634 drawRect->bottom += 2;
1637 else
1639 if (iItem == infoPtr->iSelected)
1641 drawRect->bottom += 3;
1643 else
1645 drawRect->bottom -= 2;
1646 InflateRect(drawRect, -2, 0);
1651 TRACE("drawRect=(%s)\n", wine_dbgstr_rect(drawRect));
1653 /* Clear interior */
1654 TAB_EraseTabInterior (infoPtr, hdc, iItem, drawRect);
1656 /* Draw the focus rectangle */
1657 if (!(infoPtr->dwStyle & TCS_FOCUSNEVER) &&
1658 (GetFocus() == infoPtr->hwnd) &&
1659 (iItem == infoPtr->uFocus) )
1661 RECT rFocus = *drawRect;
1663 if (!(infoPtr->dwStyle & TCS_BUTTONS)) InflateRect(&rFocus, -3, -3);
1664 if (infoPtr->dwStyle & TCS_BOTTOM && !(infoPtr->dwStyle & TCS_VERTICAL))
1665 rFocus.top -= 3;
1667 /* focus should stay on selected item for TCS_BUTTONS style */
1668 if (!((infoPtr->dwStyle & TCS_BUTTONS) && (infoPtr->iSelected != iItem)))
1669 DrawFocusRect(hdc, &rFocus);
1673 * Text pen
1675 htextPen = CreatePen( PS_SOLID, 1, comctl32_color.clrBtnText );
1676 holdPen = SelectObject(hdc, htextPen);
1677 hOldFont = SelectObject(hdc, infoPtr->hFont);
1680 * Setup for text output
1682 oldBkMode = SetBkMode(hdc, TRANSPARENT);
1683 if (!GetWindowTheme (infoPtr->hwnd) || (infoPtr->dwStyle & TCS_BUTTONS))
1685 if ((infoPtr->dwStyle & TCS_HOTTRACK) && (iItem == infoPtr->iHotTracked) &&
1686 !(infoPtr->dwStyle & TCS_FLATBUTTONS))
1687 SetTextColor(hdc, comctl32_color.clrHighlight);
1688 else if (TAB_GetItem(infoPtr, iItem)->dwState & TCIS_HIGHLIGHTED)
1689 SetTextColor(hdc, comctl32_color.clrHighlightText);
1690 else
1691 SetTextColor(hdc, comctl32_color.clrBtnText);
1695 * if owner draw, tell the owner to draw
1697 if ((infoPtr->dwStyle & TCS_OWNERDRAWFIXED) && IsWindow(infoPtr->hwndNotify))
1699 DRAWITEMSTRUCT dis;
1700 UINT id;
1702 drawRect->top += 2;
1703 drawRect->right -= 1;
1704 if ( iItem == infoPtr->iSelected )
1705 InflateRect(drawRect, -1, 0);
1707 id = (UINT)GetWindowLongPtrW( infoPtr->hwnd, GWLP_ID );
1709 /* fill DRAWITEMSTRUCT */
1710 dis.CtlType = ODT_TAB;
1711 dis.CtlID = id;
1712 dis.itemID = iItem;
1713 dis.itemAction = ODA_DRAWENTIRE;
1714 dis.itemState = 0;
1715 if ( iItem == infoPtr->iSelected )
1716 dis.itemState |= ODS_SELECTED;
1717 if (infoPtr->uFocus == iItem)
1718 dis.itemState |= ODS_FOCUS;
1719 dis.hwndItem = infoPtr->hwnd;
1720 dis.hDC = hdc;
1721 dis.rcItem = *drawRect;
1723 /* when extra data fits ULONG_PTR, store it directly */
1724 if (infoPtr->cbInfo > sizeof(LPARAM))
1725 dis.itemData = (ULONG_PTR) TAB_GetItem(infoPtr, iItem)->extra;
1726 else
1728 /* this could be considered broken on 64 bit, but that's how it works -
1729 only first 4 bytes are copied */
1730 dis.itemData = 0;
1731 memcpy(&dis.itemData, (ULONG_PTR*)TAB_GetItem(infoPtr, iItem)->extra, 4);
1734 /* draw notification */
1735 SendMessageW( infoPtr->hwndNotify, WM_DRAWITEM, id, (LPARAM)&dis );
1737 else
1739 TAB_ITEM *item = TAB_GetItem(infoPtr, iItem);
1740 RECT rcTemp;
1741 RECT rcImage;
1743 /* used to center the icon and text in the tab */
1744 RECT rcText;
1745 INT center_offset_h, center_offset_v;
1747 /* set rcImage to drawRect, we will use top & left in our ImageList_Draw call */
1748 rcImage = *drawRect;
1750 rcTemp = *drawRect;
1751 SetRectEmpty(&rcText);
1753 /* get the rectangle that the text fits in */
1754 if (item->pszText)
1756 DrawTextW(hdc, item->pszText, -1, &rcText, DT_CALCRECT);
1759 * If not owner draw, then do the drawing ourselves.
1761 * Draw the icon.
1763 if (infoPtr->himl && item->iImage != -1)
1765 INT cx;
1766 INT cy;
1768 ImageList_GetIconSize(infoPtr->himl, &cx, &cy);
1770 if(infoPtr->dwStyle & TCS_VERTICAL)
1772 center_offset_h = ((drawRect->bottom - drawRect->top) - (cy + infoPtr->uHItemPadding + (rcText.right - rcText.left))) / 2;
1773 center_offset_v = ((drawRect->right - drawRect->left) - cx) / 2;
1775 else
1777 center_offset_h = ((drawRect->right - drawRect->left) - (cx + infoPtr->uHItemPadding + (rcText.right - rcText.left))) / 2;
1778 center_offset_v = ((drawRect->bottom - drawRect->top) - cy) / 2;
1781 /* if an item is selected, the icon is shifted up instead of down */
1782 if (iItem == infoPtr->iSelected)
1783 center_offset_v -= infoPtr->uVItemPadding / 2;
1784 else
1785 center_offset_v += infoPtr->uVItemPadding / 2;
1787 if (infoPtr->dwStyle & TCS_FIXEDWIDTH && infoPtr->dwStyle & (TCS_FORCELABELLEFT | TCS_FORCEICONLEFT))
1788 center_offset_h = infoPtr->uHItemPadding;
1790 if (center_offset_h < 2)
1791 center_offset_h = 2;
1793 if (center_offset_v < 0)
1794 center_offset_v = 0;
1796 TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%s), textlen=%ld\n",
1797 debugstr_w(item->pszText), center_offset_h, center_offset_v,
1798 wine_dbgstr_rect(drawRect), (rcText.right-rcText.left));
1800 if((infoPtr->dwStyle & TCS_VERTICAL) && (infoPtr->dwStyle & TCS_BOTTOM))
1802 rcImage.top = drawRect->top + center_offset_h;
1803 /* if tab is TCS_VERTICAL and TCS_BOTTOM, the text is drawn from the */
1804 /* right side of the tab, but the image still uses the left as its x position */
1805 /* this keeps the image always drawn off of the same side of the tab */
1806 rcImage.left = drawRect->right - cx - center_offset_v;
1807 drawRect->top += cy + infoPtr->uHItemPadding;
1809 else if(infoPtr->dwStyle & TCS_VERTICAL)
1811 rcImage.top = drawRect->bottom - cy - center_offset_h;
1812 rcImage.left = drawRect->left + center_offset_v;
1813 drawRect->bottom -= cy + infoPtr->uHItemPadding;
1815 else /* normal style, whether TCS_BOTTOM or not */
1817 rcImage.left = drawRect->left + center_offset_h;
1818 rcImage.top = drawRect->top + center_offset_v;
1819 drawRect->left += cx + infoPtr->uHItemPadding;
1822 TRACE("drawing image %d, left %ld, top %ld\n", item->iImage, rcImage.left, rcImage.top-1);
1823 ImageList_Draw
1825 infoPtr->himl,
1826 item->iImage,
1827 hdc,
1828 rcImage.left,
1829 rcImage.top,
1830 ILD_NORMAL
1834 /* Now position text */
1835 if (infoPtr->dwStyle & TCS_FIXEDWIDTH && infoPtr->dwStyle & TCS_FORCELABELLEFT)
1836 center_offset_h = infoPtr->uHItemPadding;
1837 else
1838 if(infoPtr->dwStyle & TCS_VERTICAL)
1839 center_offset_h = ((drawRect->bottom - drawRect->top) - (rcText.right - rcText.left)) / 2;
1840 else
1841 center_offset_h = ((drawRect->right - drawRect->left) - (rcText.right - rcText.left)) / 2;
1843 if(infoPtr->dwStyle & TCS_VERTICAL)
1845 if(infoPtr->dwStyle & TCS_BOTTOM)
1846 drawRect->top+=center_offset_h;
1847 else
1848 drawRect->bottom-=center_offset_h;
1850 center_offset_v = ((drawRect->right - drawRect->left) - (rcText.bottom - rcText.top)) / 2;
1852 else
1854 drawRect->left += center_offset_h;
1855 center_offset_v = ((drawRect->bottom - drawRect->top) - (rcText.bottom - rcText.top)) / 2;
1858 /* if an item is selected, the text is shifted up instead of down */
1859 if (iItem == infoPtr->iSelected)
1860 center_offset_v -= infoPtr->uVItemPadding / 2;
1861 else
1862 center_offset_v += infoPtr->uVItemPadding / 2;
1864 if (center_offset_v < 0)
1865 center_offset_v = 0;
1867 if(infoPtr->dwStyle & TCS_VERTICAL)
1868 drawRect->left += center_offset_v;
1869 else
1870 drawRect->top += center_offset_v;
1872 /* Draw the text */
1873 if(infoPtr->dwStyle & TCS_VERTICAL) /* if we are vertical rotate the text and each character */
1875 LOGFONTW logfont;
1876 HFONT hFont;
1877 INT nEscapement = 900;
1878 INT nOrientation = 900;
1880 if(infoPtr->dwStyle & TCS_BOTTOM)
1882 nEscapement = -900;
1883 nOrientation = -900;
1886 /* to get a font with the escapement and orientation we are looking for, we need to */
1887 /* call CreateFontIndirect, which requires us to set the values of the logfont we pass in */
1888 if (!GetObjectW(infoPtr->hFont, sizeof(logfont), &logfont))
1889 GetObjectW(GetStockObject(DEFAULT_GUI_FONT), sizeof(logfont), &logfont);
1891 logfont.lfEscapement = nEscapement;
1892 logfont.lfOrientation = nOrientation;
1893 hFont = CreateFontIndirectW(&logfont);
1894 SelectObject(hdc, hFont);
1896 if (item->pszText)
1898 ExtTextOutW(hdc,
1899 (infoPtr->dwStyle & TCS_BOTTOM) ? drawRect->right : drawRect->left,
1900 (!(infoPtr->dwStyle & TCS_BOTTOM)) ? drawRect->bottom : drawRect->top,
1901 ETO_CLIPPED,
1902 drawRect,
1903 item->pszText,
1904 lstrlenW(item->pszText),
1908 DeleteObject(hFont);
1910 else
1912 TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%s), textlen=%ld\n",
1913 debugstr_w(item->pszText), center_offset_h, center_offset_v,
1914 wine_dbgstr_rect(drawRect), (rcText.right-rcText.left));
1915 if (item->pszText)
1917 DrawTextW
1919 hdc,
1920 item->pszText,
1921 lstrlenW(item->pszText),
1922 drawRect,
1923 DT_LEFT | DT_SINGLELINE
1928 *drawRect = rcTemp; /* restore drawRect */
1932 * Cleanup
1934 SelectObject(hdc, hOldFont);
1935 SetBkMode(hdc, oldBkMode);
1936 SelectObject(hdc, holdPen);
1937 DeleteObject( htextPen );
1940 /******************************************************************************
1941 * TAB_DrawItem
1943 * This method is used to draw a single tab into the tab control.
1945 static void TAB_DrawItem(const TAB_INFO *infoPtr, HDC hdc, INT iItem)
1947 RECT itemRect;
1948 RECT selectedRect;
1949 BOOL isVisible;
1950 RECT r, fillRect, r1;
1951 INT clRight = 0;
1952 INT clBottom = 0;
1953 COLORREF bkgnd, corner;
1954 HTHEME theme;
1957 * Get the rectangle for the item.
1959 isVisible = TAB_InternalGetItemRect(infoPtr,
1960 iItem,
1961 &itemRect,
1962 &selectedRect);
1964 if (isVisible)
1966 RECT rUD, rC;
1968 /* Clip UpDown control to not draw over it */
1969 if (infoPtr->needsScrolling)
1971 GetWindowRect(infoPtr->hwnd, &rC);
1972 GetWindowRect(infoPtr->hwndUpDown, &rUD);
1973 ExcludeClipRect(hdc, rUD.left - rC.left, rUD.top - rC.top, rUD.right - rC.left, rUD.bottom - rC.top);
1976 /* If you need to see what the control is doing,
1977 * then override these variables. They will change what
1978 * fill colors are used for filling the tabs, and the
1979 * corners when drawing the edge.
1981 bkgnd = comctl32_color.clrBtnFace;
1982 corner = comctl32_color.clrBtnFace;
1984 if (infoPtr->dwStyle & TCS_BUTTONS)
1986 /* Get item rectangle */
1987 r = itemRect;
1989 /* Separators between flat buttons */
1990 if ((infoPtr->dwStyle & TCS_FLATBUTTONS) && (infoPtr->exStyle & TCS_EX_FLATSEPARATORS))
1992 r1 = r;
1993 r1.right += (FLAT_BTN_SPACINGX -2);
1994 DrawEdge(hdc, &r1, EDGE_ETCHED, BF_RIGHT);
1997 if (iItem == infoPtr->iSelected)
1999 DrawEdge(hdc, &r, EDGE_SUNKEN, BF_SOFT|BF_RECT);
2001 OffsetRect(&r, 1, 1);
2003 else /* ! selected */
2005 DWORD state = TAB_GetItem(infoPtr, iItem)->dwState;
2007 if ((state & TCIS_BUTTONPRESSED) || (iItem == infoPtr->uFocus))
2008 DrawEdge(hdc, &r, EDGE_SUNKEN, BF_SOFT|BF_RECT);
2009 else
2010 if (!(infoPtr->dwStyle & TCS_FLATBUTTONS))
2011 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_RECT);
2014 else /* !TCS_BUTTONS */
2016 /* We draw a rectangle of different sizes depending on the selection
2017 * state. */
2018 if (iItem == infoPtr->iSelected) {
2019 RECT rect;
2020 GetClientRect (infoPtr->hwnd, &rect);
2021 clRight = rect.right;
2022 clBottom = rect.bottom;
2023 r = selectedRect;
2025 else
2026 r = itemRect;
2029 * Erase the background. (Delay it but setup rectangle.)
2030 * This is necessary when drawing the selected item since it is larger
2031 * than the others, it might overlap with stuff already drawn by the
2032 * other tabs
2034 fillRect = r;
2036 /* Draw themed tabs - but only if they are at the top.
2037 * Windows draws even side or bottom tabs themed, with wacky results.
2038 * However, since in Wine apps may get themed that did not opt in via
2039 * a manifest avoid theming when we know the result will be wrong */
2040 if ((theme = GetWindowTheme (infoPtr->hwnd))
2041 && ((infoPtr->dwStyle & (TCS_VERTICAL | TCS_BOTTOM)) == 0))
2043 static const int partIds[8] = {
2044 /* Normal item */
2045 TABP_TABITEM,
2046 TABP_TABITEMLEFTEDGE,
2047 TABP_TABITEMRIGHTEDGE,
2048 TABP_TABITEMBOTHEDGE,
2049 /* Selected tab */
2050 TABP_TOPTABITEM,
2051 TABP_TOPTABITEMLEFTEDGE,
2052 TABP_TOPTABITEMRIGHTEDGE,
2053 TABP_TOPTABITEMBOTHEDGE,
2055 int partIndex = 0;
2056 int stateId = TIS_NORMAL;
2058 /* selected and unselected tabs have different parts */
2059 if (iItem == infoPtr->iSelected)
2060 partIndex += 4;
2061 /* The part also differs on the position of a tab on a line.
2062 * "Visually" determining the position works well enough. */
2063 GetClientRect(infoPtr->hwnd, &r1);
2064 if(selectedRect.left == 0)
2065 partIndex += 1;
2066 if(selectedRect.right == r1.right)
2067 partIndex += 2;
2069 if (iItem == infoPtr->iSelected)
2070 stateId = TIS_SELECTED;
2071 else if (iItem == infoPtr->iHotTracked)
2072 stateId = TIS_HOT;
2073 else if (iItem == infoPtr->uFocus)
2074 stateId = TIS_FOCUSED;
2076 /* Adjust rectangle for bottommost row */
2077 if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2078 r.bottom += 3;
2080 DrawThemeBackground (theme, hdc, partIds[partIndex], stateId, &r, NULL);
2081 GetThemeBackgroundContentRect (theme, hdc, partIds[partIndex], stateId, &r, &r);
2083 else if(infoPtr->dwStyle & TCS_VERTICAL)
2085 /* These are for adjusting the drawing of a Selected tab */
2086 /* The initial values are for the normal case of non-Selected */
2087 int ZZ = 1; /* Do not stretch if selected */
2088 if (iItem == infoPtr->iSelected) {
2089 ZZ = 0;
2091 /* if leftmost draw the line longer */
2092 if(selectedRect.top == 0)
2093 fillRect.top += CONTROL_BORDER_SIZEY;
2094 /* if rightmost draw the line longer */
2095 if(selectedRect.bottom == clBottom)
2096 fillRect.bottom -= CONTROL_BORDER_SIZEY;
2099 if (infoPtr->dwStyle & TCS_BOTTOM)
2101 /* Adjust both rectangles to match native */
2102 r.left += (1-ZZ);
2104 TRACE("<right> item=%d, fill=(%s), edge=(%s)\n",
2105 iItem, wine_dbgstr_rect(&fillRect), wine_dbgstr_rect(&r));
2107 /* Clear interior */
2108 SetBkColor(hdc, bkgnd);
2109 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2111 /* Draw rectangular edge around tab */
2112 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_RIGHT|BF_TOP|BF_BOTTOM);
2114 /* Now erase the top corner and draw diagonal edge */
2115 SetBkColor(hdc, corner);
2116 r1.left = r.right - ROUND_CORNER_SIZE - 1;
2117 r1.top = r.top;
2118 r1.right = r.right;
2119 r1.bottom = r1.top + ROUND_CORNER_SIZE;
2120 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2121 r1.right--;
2122 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPLEFT);
2124 /* Now erase the bottom corner and draw diagonal edge */
2125 r1.left = r.right - ROUND_CORNER_SIZE - 1;
2126 r1.bottom = r.bottom;
2127 r1.right = r.right;
2128 r1.top = r1.bottom - ROUND_CORNER_SIZE;
2129 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2130 r1.right--;
2131 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMLEFT);
2133 if ((iItem == infoPtr->iSelected) && (selectedRect.top == 0)) {
2134 r1 = r;
2135 r1.right = r1.left;
2136 r1.left--;
2137 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_TOP);
2141 else
2143 TRACE("<left> item=%d, fill=(%s), edge=(%s)\n",
2144 iItem, wine_dbgstr_rect(&fillRect), wine_dbgstr_rect(&r));
2146 /* Clear interior */
2147 SetBkColor(hdc, bkgnd);
2148 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2150 /* Draw rectangular edge around tab */
2151 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_TOP|BF_BOTTOM);
2153 /* Now erase the top corner and draw diagonal edge */
2154 SetBkColor(hdc, corner);
2155 r1.left = r.left;
2156 r1.top = r.top;
2157 r1.right = r1.left + ROUND_CORNER_SIZE + 1;
2158 r1.bottom = r1.top + ROUND_CORNER_SIZE;
2159 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2160 r1.left++;
2161 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPRIGHT);
2163 /* Now erase the bottom corner and draw diagonal edge */
2164 r1.left = r.left;
2165 r1.bottom = r.bottom;
2166 r1.right = r1.left + ROUND_CORNER_SIZE + 1;
2167 r1.top = r1.bottom - ROUND_CORNER_SIZE;
2168 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2169 r1.left++;
2170 DrawEdge(hdc, &r1, EDGE_SUNKEN, BF_DIAGONAL_ENDTOPLEFT);
2173 else /* ! TCS_VERTICAL */
2175 /* These are for adjusting the drawing of a Selected tab */
2176 /* The initial values are for the normal case of non-Selected */
2177 if (iItem == infoPtr->iSelected) {
2178 /* if leftmost draw the line longer */
2179 if(selectedRect.left == 0)
2180 fillRect.left += CONTROL_BORDER_SIZEX;
2181 /* if rightmost draw the line longer */
2182 if(selectedRect.right == clRight)
2183 fillRect.right -= CONTROL_BORDER_SIZEX;
2186 if (infoPtr->dwStyle & TCS_BOTTOM)
2188 /* Adjust both rectangles for topmost row */
2189 if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2191 fillRect.top -= 2;
2192 r.top -= 1;
2195 TRACE("<bottom> 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_BOTTOM|BF_RIGHT);
2205 /* Now erase the righthand corner and draw diagonal edge */
2206 SetBkColor(hdc, corner);
2207 r1.left = r.right - ROUND_CORNER_SIZE;
2208 r1.bottom = r.bottom;
2209 r1.right = r.right;
2210 r1.top = r1.bottom - ROUND_CORNER_SIZE - 1;
2211 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2212 r1.bottom--;
2213 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMLEFT);
2215 /* Now erase the lefthand corner and draw diagonal edge */
2216 r1.left = r.left;
2217 r1.bottom = r.bottom;
2218 r1.right = r1.left + ROUND_CORNER_SIZE;
2219 r1.top = r1.bottom - ROUND_CORNER_SIZE - 1;
2220 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2221 r1.bottom--;
2222 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPLEFT);
2224 if (iItem == infoPtr->iSelected)
2226 r.top += 2;
2227 r.left += 1;
2228 if (selectedRect.left == 0)
2230 r1 = r;
2231 r1.bottom = r1.top;
2232 r1.top--;
2233 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_LEFT);
2238 else
2240 /* Adjust both rectangles for bottommost row */
2241 if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2243 fillRect.bottom += 3;
2244 r.bottom += 2;
2247 TRACE("<top> 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_TOP|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.top = r.top;
2261 r1.right = r.right;
2262 r1.bottom = r1.top + ROUND_CORNER_SIZE + 1;
2263 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2264 r1.top++;
2265 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMRIGHT);
2267 /* Now erase the lefthand corner and draw diagonal edge */
2268 r1.left = r.left;
2269 r1.top = r.top;
2270 r1.right = r1.left + ROUND_CORNER_SIZE;
2271 r1.bottom = r1.top + ROUND_CORNER_SIZE + 1;
2272 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2273 r1.top++;
2274 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPRIGHT);
2279 TAB_DumpItemInternal(infoPtr, iItem);
2281 /* This modifies r to be the text rectangle. */
2282 TAB_DrawItemInterior(infoPtr, hdc, iItem, &r);
2286 /******************************************************************************
2287 * TAB_DrawBorder
2289 * This method is used to draw the raised border around the tab control
2290 * "content" area.
2292 static void TAB_DrawBorder(const TAB_INFO *infoPtr, HDC hdc)
2294 RECT rect;
2295 HTHEME theme = GetWindowTheme (infoPtr->hwnd);
2297 GetClientRect (infoPtr->hwnd, &rect);
2300 * Adjust for the style
2303 if (infoPtr->uNumItem)
2305 if ((infoPtr->dwStyle & TCS_BOTTOM) && !(infoPtr->dwStyle & TCS_VERTICAL))
2306 rect.bottom -= infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2307 else if((infoPtr->dwStyle & TCS_BOTTOM) && (infoPtr->dwStyle & TCS_VERTICAL))
2308 rect.right -= infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2309 else if(infoPtr->dwStyle & TCS_VERTICAL)
2310 rect.left += infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2311 else /* not TCS_VERTICAL and not TCS_BOTTOM */
2312 rect.top += infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2315 TRACE("border=(%s)\n", wine_dbgstr_rect(&rect));
2317 if (theme)
2319 DrawThemeParentBackground(infoPtr->hwnd, hdc, &rect);
2320 DrawThemeBackground (theme, hdc, TABP_PANE, 0, &rect, NULL);
2322 else
2324 DrawEdge(hdc, &rect, EDGE_RAISED, BF_SOFT|BF_RECT);
2328 /******************************************************************************
2329 * TAB_Refresh
2331 * This method repaints the tab control..
2333 static void TAB_Refresh (const TAB_INFO *infoPtr, HDC hdc)
2335 HFONT hOldFont;
2336 INT i;
2338 if (!infoPtr->DoRedraw)
2339 return;
2341 hOldFont = SelectObject (hdc, infoPtr->hFont);
2343 if (infoPtr->dwStyle & TCS_BUTTONS)
2345 for (i = 0; i < infoPtr->uNumItem; i++)
2346 TAB_DrawItem (infoPtr, hdc, i);
2348 else
2350 /* Draw all the non selected item first */
2351 for (i = 0; i < infoPtr->uNumItem; i++)
2353 if (i != infoPtr->iSelected)
2354 TAB_DrawItem (infoPtr, hdc, i);
2357 /* Now, draw the border, draw it before the selected item
2358 * since the selected item overwrites part of the border. */
2359 TAB_DrawBorder (infoPtr, hdc);
2361 /* Then, draw the selected item */
2362 TAB_DrawItem (infoPtr, hdc, infoPtr->iSelected);
2365 SelectObject (hdc, hOldFont);
2368 static inline DWORD TAB_GetRowCount (const TAB_INFO *infoPtr)
2370 TRACE("(%p)\n", infoPtr);
2371 return infoPtr->uNumRows;
2374 static inline LRESULT TAB_SetRedraw (TAB_INFO *infoPtr, BOOL doRedraw)
2376 infoPtr->DoRedraw = doRedraw;
2377 return 0;
2380 /******************************************************************************
2381 * TAB_EnsureSelectionVisible
2383 * This method will make sure that the current selection is completely
2384 * visible by scrolling until it is.
2386 static void TAB_EnsureSelectionVisible(
2387 TAB_INFO* infoPtr)
2389 INT iSelected = infoPtr->iSelected;
2390 INT iOrigLeftmostVisible = infoPtr->leftmostVisible;
2392 if (iSelected < 0)
2393 return;
2395 /* set the items row to the bottommost row or topmost row depending on
2396 * style */
2397 if ((infoPtr->uNumRows > 1) && !(infoPtr->dwStyle & TCS_BUTTONS))
2399 TAB_ITEM *selected = TAB_GetItem(infoPtr, iSelected);
2400 INT newselected;
2401 INT iTargetRow;
2403 if(infoPtr->dwStyle & TCS_VERTICAL)
2404 newselected = selected->rect.left;
2405 else
2406 newselected = selected->rect.top;
2408 /* the target row is always (number of rows - 1)
2409 as row 0 is furthest from the clientRect */
2410 iTargetRow = infoPtr->uNumRows - 1;
2412 if (newselected != iTargetRow)
2414 UINT i;
2415 if(infoPtr->dwStyle & TCS_VERTICAL)
2417 for (i=0; i < infoPtr->uNumItem; i++)
2419 /* move everything in the row of the selected item to the iTargetRow */
2420 TAB_ITEM *item = TAB_GetItem(infoPtr, i);
2422 if (item->rect.left == newselected )
2423 item->rect.left = iTargetRow;
2424 else
2426 if (item->rect.left > newselected)
2427 item->rect.left-=1;
2431 else
2433 for (i=0; i < infoPtr->uNumItem; i++)
2435 TAB_ITEM *item = TAB_GetItem(infoPtr, i);
2437 if (item->rect.top == newselected )
2438 item->rect.top = iTargetRow;
2439 else
2441 if (item->rect.top > newselected)
2442 item->rect.top-=1;
2446 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
2451 * Do the trivial cases first.
2453 if ( (!infoPtr->needsScrolling) ||
2454 (infoPtr->hwndUpDown==0) || (infoPtr->dwStyle & TCS_VERTICAL))
2455 return;
2457 if (infoPtr->leftmostVisible >= iSelected)
2459 infoPtr->leftmostVisible = iSelected;
2461 else
2463 TAB_ITEM *selected = TAB_GetItem(infoPtr, iSelected);
2464 RECT r;
2465 INT width;
2466 UINT i;
2468 /* Calculate the part of the client area that is visible */
2469 GetClientRect(infoPtr->hwnd, &r);
2470 width = r.right;
2472 GetClientRect(infoPtr->hwndUpDown, &r);
2473 width -= r.right;
2475 if ((selected->rect.right -
2476 selected->rect.left) >= width )
2478 /* Special case: width of selected item is greater than visible
2479 * part of control.
2481 infoPtr->leftmostVisible = iSelected;
2483 else
2485 for (i = infoPtr->leftmostVisible; i < infoPtr->uNumItem; i++)
2487 if ((selected->rect.right - TAB_GetItem(infoPtr, i)->rect.left) < width)
2488 break;
2490 infoPtr->leftmostVisible = i;
2494 if (infoPtr->leftmostVisible != iOrigLeftmostVisible)
2495 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
2497 SendMessageW(infoPtr->hwndUpDown, UDM_SETPOS, 0,
2498 MAKELONG(infoPtr->leftmostVisible, 0));
2501 /******************************************************************************
2502 * TAB_InvalidateTabArea
2504 * This method will invalidate the portion of the control that contains the
2505 * tabs. It is called when the state of the control changes and needs
2506 * to be redisplayed
2508 static void TAB_InvalidateTabArea(const TAB_INFO *infoPtr)
2510 RECT clientRect, rInvalidate, rAdjClient;
2511 INT lastRow = infoPtr->uNumRows - 1;
2512 RECT rect;
2514 if (lastRow < 0) return;
2516 GetClientRect(infoPtr->hwnd, &clientRect);
2517 rInvalidate = clientRect;
2518 rAdjClient = clientRect;
2520 TAB_AdjustRect(infoPtr, 0, &rAdjClient);
2522 TAB_InternalGetItemRect(infoPtr, infoPtr->uNumItem-1 , &rect, NULL);
2523 if ((infoPtr->dwStyle & TCS_BOTTOM) && (infoPtr->dwStyle & TCS_VERTICAL))
2525 rInvalidate.left = rAdjClient.right;
2526 if (infoPtr->uNumRows == 1)
2527 rInvalidate.bottom = clientRect.top + rect.bottom + 2 * SELECTED_TAB_OFFSET;
2529 else if(infoPtr->dwStyle & TCS_VERTICAL)
2531 rInvalidate.right = rAdjClient.left;
2532 if (infoPtr->uNumRows == 1)
2533 rInvalidate.bottom = clientRect.top + rect.bottom + 2 * SELECTED_TAB_OFFSET;
2535 else if (infoPtr->dwStyle & TCS_BOTTOM)
2537 rInvalidate.top = rAdjClient.bottom;
2538 if (infoPtr->uNumRows == 1)
2539 rInvalidate.right = clientRect.left + rect.right + 2 * SELECTED_TAB_OFFSET;
2541 else
2543 rInvalidate.bottom = rAdjClient.top;
2544 if (infoPtr->uNumRows == 1)
2545 rInvalidate.right = clientRect.left + rect.right + 2 * SELECTED_TAB_OFFSET;
2548 /* Punch out the updown control */
2549 if (infoPtr->needsScrolling && (rInvalidate.right > 0)) {
2550 RECT r;
2551 GetClientRect(infoPtr->hwndUpDown, &r);
2552 if (rInvalidate.right > clientRect.right - r.left)
2553 rInvalidate.right = rInvalidate.right - (r.right - r.left);
2554 else
2555 rInvalidate.right = clientRect.right - r.left;
2558 TRACE("invalidate (%s)\n", wine_dbgstr_rect(&rInvalidate));
2560 InvalidateRect(infoPtr->hwnd, &rInvalidate, TRUE);
2563 static inline LRESULT TAB_Paint (TAB_INFO *infoPtr, HDC hdcPaint)
2565 HDC hdc;
2566 PAINTSTRUCT ps;
2568 if (hdcPaint)
2569 hdc = hdcPaint;
2570 else
2572 hdc = BeginPaint (infoPtr->hwnd, &ps);
2573 TRACE("erase %d, rect=(%s)\n", ps.fErase, wine_dbgstr_rect(&ps.rcPaint));
2576 TAB_Refresh (infoPtr, hdc);
2578 if (!hdcPaint)
2579 EndPaint (infoPtr->hwnd, &ps);
2581 return 0;
2584 static LRESULT
2585 TAB_InsertItemT (TAB_INFO *infoPtr, INT iItem, const TCITEMW *pti, BOOL bUnicode)
2587 TAB_ITEM *item;
2588 RECT rect;
2590 GetClientRect (infoPtr->hwnd, &rect);
2591 TRACE("Rect: %p %s\n", infoPtr->hwnd, wine_dbgstr_rect(&rect));
2593 if (iItem < 0) return -1;
2594 if (iItem > infoPtr->uNumItem)
2595 iItem = infoPtr->uNumItem;
2597 TAB_DumpItemExternalT(pti, iItem, bUnicode);
2599 if (!(item = Alloc(TAB_ITEM_SIZE(infoPtr)))) return FALSE;
2600 if (DPA_InsertPtr(infoPtr->items, iItem, item) == -1)
2602 Free(item);
2603 return FALSE;
2606 if (infoPtr->uNumItem == 0)
2607 infoPtr->iSelected = 0;
2608 else if (iItem <= infoPtr->iSelected)
2609 infoPtr->iSelected++;
2611 infoPtr->uNumItem++;
2613 item->pszText = NULL;
2614 if (pti->mask & TCIF_TEXT)
2616 if (bUnicode)
2617 Str_SetPtrW (&item->pszText, pti->pszText);
2618 else
2619 Str_SetPtrAtoW (&item->pszText, (LPSTR)pti->pszText);
2622 if (pti->mask & TCIF_IMAGE)
2623 item->iImage = pti->iImage;
2624 else
2625 item->iImage = -1;
2627 if (pti->mask & TCIF_PARAM)
2628 memcpy(item->extra, &pti->lParam, EXTRA_ITEM_SIZE(infoPtr));
2629 else
2630 memset(item->extra, 0, EXTRA_ITEM_SIZE(infoPtr));
2632 TAB_SetItemBounds(infoPtr);
2633 if (infoPtr->uNumItem > 1)
2634 TAB_InvalidateTabArea(infoPtr);
2635 else
2636 InvalidateRect(infoPtr->hwnd, NULL, TRUE);
2638 TRACE("[%p]: added item %d %s\n",
2639 infoPtr->hwnd, iItem, debugstr_w(item->pszText));
2641 /* If we haven't set the current focus yet, set it now. */
2642 if (infoPtr->uFocus == -1)
2643 TAB_SetCurFocus(infoPtr, iItem);
2645 return iItem;
2648 static LRESULT
2649 TAB_SetItemSize (TAB_INFO *infoPtr, INT cx, INT cy)
2651 LONG lResult = 0;
2652 BOOL bNeedPaint = FALSE;
2654 lResult = MAKELONG(infoPtr->tabWidth, infoPtr->tabHeight);
2656 /* UNDOCUMENTED: If requested Width or Height is 0 this means that program wants to use auto size. */
2657 if (infoPtr->dwStyle & TCS_FIXEDWIDTH && (infoPtr->tabWidth != cx))
2659 infoPtr->tabWidth = cx;
2660 bNeedPaint = TRUE;
2663 if (infoPtr->tabHeight != cy)
2665 if ((infoPtr->fHeightSet = (cy != 0)))
2666 infoPtr->tabHeight = cy;
2668 bNeedPaint = TRUE;
2670 TRACE("was h=%d,w=%d, now h=%d,w=%d\n",
2671 HIWORD(lResult), LOWORD(lResult),
2672 infoPtr->tabHeight, infoPtr->tabWidth);
2674 if (bNeedPaint)
2676 TAB_SetItemBounds(infoPtr);
2677 RedrawWindow(infoPtr->hwnd, NULL, NULL, RDW_ERASE | RDW_INVALIDATE | RDW_UPDATENOW);
2680 return lResult;
2683 static inline LRESULT TAB_SetMinTabWidth (TAB_INFO *infoPtr, INT cx)
2685 INT oldcx = 0;
2687 TRACE("(%p,%d)\n", infoPtr, cx);
2689 if (infoPtr->tabMinWidth < 0)
2690 oldcx = DEFAULT_MIN_TAB_WIDTH;
2691 else
2692 oldcx = infoPtr->tabMinWidth;
2693 infoPtr->tabMinWidth = cx;
2694 TAB_SetItemBounds(infoPtr);
2695 return oldcx;
2698 static inline LRESULT
2699 TAB_HighlightItem (TAB_INFO *infoPtr, INT iItem, BOOL fHighlight)
2701 LPDWORD lpState;
2702 DWORD oldState;
2703 RECT r;
2705 TRACE("(%p,%d,%s)\n", infoPtr, iItem, fHighlight ? "true" : "false");
2707 if (iItem < 0 || iItem >= infoPtr->uNumItem)
2708 return FALSE;
2710 lpState = &TAB_GetItem(infoPtr, iItem)->dwState;
2711 oldState = *lpState;
2713 if (fHighlight)
2714 *lpState |= TCIS_HIGHLIGHTED;
2715 else
2716 *lpState &= ~TCIS_HIGHLIGHTED;
2718 if ((oldState != *lpState) && TAB_InternalGetItemRect (infoPtr, iItem, &r, NULL))
2719 InvalidateRect (infoPtr->hwnd, &r, TRUE);
2721 return TRUE;
2724 static LRESULT
2725 TAB_SetItemT (TAB_INFO *infoPtr, INT iItem, LPTCITEMW tabItem, BOOL bUnicode)
2727 TAB_ITEM *wineItem;
2729 TRACE("(%p,%d,%p,%s)\n", infoPtr, iItem, tabItem, bUnicode ? "true" : "false");
2731 if (iItem < 0 || iItem >= infoPtr->uNumItem)
2732 return FALSE;
2734 TAB_DumpItemExternalT(tabItem, iItem, bUnicode);
2736 wineItem = TAB_GetItem(infoPtr, iItem);
2738 if (tabItem->mask & TCIF_IMAGE)
2739 wineItem->iImage = tabItem->iImage;
2741 if (tabItem->mask & TCIF_PARAM)
2742 memcpy(wineItem->extra, &tabItem->lParam, infoPtr->cbInfo);
2744 if (tabItem->mask & TCIF_RTLREADING)
2745 FIXME("TCIF_RTLREADING\n");
2747 if (tabItem->mask & TCIF_STATE)
2748 wineItem->dwState = (wineItem->dwState & ~tabItem->dwStateMask) |
2749 ( tabItem->dwState & tabItem->dwStateMask);
2751 if (tabItem->mask & TCIF_TEXT)
2753 Free(wineItem->pszText);
2754 wineItem->pszText = NULL;
2755 if (bUnicode)
2756 Str_SetPtrW(&wineItem->pszText, tabItem->pszText);
2757 else
2758 Str_SetPtrAtoW(&wineItem->pszText, (LPSTR)tabItem->pszText);
2761 /* Update and repaint tabs */
2762 TAB_SetItemBounds(infoPtr);
2763 TAB_InvalidateTabArea(infoPtr);
2765 return TRUE;
2768 static inline LRESULT TAB_GetItemCount (const TAB_INFO *infoPtr)
2770 TRACE("\n");
2771 return infoPtr->uNumItem;
2775 static LRESULT
2776 TAB_GetItemT (TAB_INFO *infoPtr, INT iItem, LPTCITEMW tabItem, BOOL bUnicode)
2778 TAB_ITEM *wineItem;
2780 TRACE("(%p,%d,%p,%s)\n", infoPtr, iItem, tabItem, bUnicode ? "true" : "false");
2782 if (!tabItem) return FALSE;
2784 if (iItem < 0 || iItem >= infoPtr->uNumItem)
2786 /* init requested fields */
2787 if (tabItem->mask & TCIF_IMAGE) tabItem->iImage = 0;
2788 if (tabItem->mask & TCIF_PARAM) tabItem->lParam = 0;
2789 if (tabItem->mask & TCIF_STATE) tabItem->dwState = 0;
2790 return FALSE;
2793 wineItem = TAB_GetItem(infoPtr, iItem);
2795 if (tabItem->mask & TCIF_IMAGE)
2796 tabItem->iImage = wineItem->iImage;
2798 if (tabItem->mask & TCIF_PARAM)
2799 memcpy(&tabItem->lParam, wineItem->extra, infoPtr->cbInfo);
2801 if (tabItem->mask & TCIF_RTLREADING)
2802 FIXME("TCIF_RTLREADING\n");
2804 if (tabItem->mask & TCIF_STATE)
2805 tabItem->dwState = wineItem->dwState & tabItem->dwStateMask;
2807 if (tabItem->mask & TCIF_TEXT)
2809 if (bUnicode)
2810 Str_GetPtrW (wineItem->pszText, tabItem->pszText, tabItem->cchTextMax);
2811 else
2812 Str_GetPtrWtoA (wineItem->pszText, (LPSTR)tabItem->pszText, tabItem->cchTextMax);
2815 TAB_DumpItemExternalT(tabItem, iItem, bUnicode);
2817 return TRUE;
2821 static LRESULT TAB_DeleteItem (TAB_INFO *infoPtr, INT iItem)
2823 TAB_ITEM *item;
2825 TRACE("(%p, %d)\n", infoPtr, iItem);
2827 if (iItem < 0 || iItem >= infoPtr->uNumItem) return FALSE;
2829 TAB_InvalidateTabArea(infoPtr);
2830 item = TAB_GetItem(infoPtr, iItem);
2831 Free(item->pszText);
2832 Free(item);
2833 infoPtr->uNumItem--;
2834 DPA_DeletePtr(infoPtr->items, iItem);
2836 if (infoPtr->uNumItem == 0)
2838 if (infoPtr->iHotTracked >= 0)
2840 KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
2841 infoPtr->iHotTracked = -1;
2844 infoPtr->iSelected = -1;
2846 else
2848 if (iItem <= infoPtr->iHotTracked)
2850 /* When tabs move left/up, the hot track item may change */
2851 FIXME("Recalc hot track\n");
2855 /* adjust the selected index */
2856 if (iItem == infoPtr->iSelected)
2857 infoPtr->iSelected = -1;
2858 else if (iItem < infoPtr->iSelected)
2859 infoPtr->iSelected--;
2861 /* reposition and repaint tabs */
2862 TAB_SetItemBounds(infoPtr);
2864 return TRUE;
2867 static inline LRESULT TAB_DeleteAllItems (TAB_INFO *infoPtr)
2869 TRACE("(%p)\n", infoPtr);
2870 while (infoPtr->uNumItem)
2871 TAB_DeleteItem (infoPtr, 0);
2872 return TRUE;
2876 static inline LRESULT TAB_GetFont (const TAB_INFO *infoPtr)
2878 TRACE("(%p) returning %p\n", infoPtr, infoPtr->hFont);
2879 return (LRESULT)infoPtr->hFont;
2882 static inline LRESULT TAB_SetFont (TAB_INFO *infoPtr, HFONT hNewFont)
2884 TRACE("(%p,%p)\n", infoPtr, hNewFont);
2886 infoPtr->hFont = hNewFont;
2888 TAB_SetItemBounds(infoPtr);
2890 TAB_InvalidateTabArea(infoPtr);
2892 return 0;
2896 static inline LRESULT TAB_GetImageList (const TAB_INFO *infoPtr)
2898 TRACE("\n");
2899 return (LRESULT)infoPtr->himl;
2902 static inline LRESULT TAB_SetImageList (TAB_INFO *infoPtr, HIMAGELIST himlNew)
2904 HIMAGELIST himlPrev = infoPtr->himl;
2905 TRACE("himl=%p\n", himlNew);
2906 infoPtr->himl = himlNew;
2907 TAB_SetItemBounds(infoPtr);
2908 InvalidateRect(infoPtr->hwnd, NULL, TRUE);
2909 return (LRESULT)himlPrev;
2912 static inline LRESULT TAB_GetUnicodeFormat (const TAB_INFO *infoPtr)
2914 TRACE("(%p)\n", infoPtr);
2915 return infoPtr->bUnicode;
2918 static inline LRESULT TAB_SetUnicodeFormat (TAB_INFO *infoPtr, BOOL bUnicode)
2920 BOOL bTemp = infoPtr->bUnicode;
2922 TRACE("(%p %d)\n", infoPtr, bUnicode);
2923 infoPtr->bUnicode = bUnicode;
2925 return bTemp;
2928 static inline LRESULT TAB_Size (TAB_INFO *infoPtr)
2930 /* I'm not really sure what the following code was meant to do.
2931 This is what it is doing:
2932 When WM_SIZE is sent with SIZE_RESTORED, the control
2933 gets positioned in the top left corner.
2935 RECT parent_rect;
2936 HWND parent;
2937 UINT uPosFlags,cx,cy;
2939 uPosFlags=0;
2940 if (!wParam) {
2941 parent = GetParent (hwnd);
2942 GetClientRect(parent, &parent_rect);
2943 cx=LOWORD (lParam);
2944 cy=HIWORD (lParam);
2945 if (GetWindowLongW(hwnd, GWL_STYLE) & CCS_NORESIZE)
2946 uPosFlags |= (SWP_NOSIZE | SWP_NOMOVE);
2948 SetWindowPos (hwnd, 0, parent_rect.left, parent_rect.top,
2949 cx, cy, uPosFlags | SWP_NOZORDER);
2950 } else {
2951 FIXME("WM_SIZE flag %x %lx not handled\n", wParam, lParam);
2952 } */
2954 /* Recompute the size/position of the tabs. */
2955 TAB_SetItemBounds (infoPtr);
2957 /* Force a repaint of the control. */
2958 InvalidateRect(infoPtr->hwnd, NULL, TRUE);
2960 return 0;
2964 static LRESULT TAB_Create (HWND hwnd, LPARAM lParam)
2966 TAB_INFO *infoPtr;
2967 TEXTMETRICW fontMetrics;
2968 HDC hdc;
2969 HFONT hOldFont;
2970 DWORD style;
2972 infoPtr = Alloc (sizeof(TAB_INFO));
2974 SetWindowLongPtrW(hwnd, 0, (DWORD_PTR)infoPtr);
2976 infoPtr->hwnd = hwnd;
2977 infoPtr->hwndNotify = ((LPCREATESTRUCTW)lParam)->hwndParent;
2978 infoPtr->uNumItem = 0;
2979 infoPtr->uNumRows = 0;
2980 infoPtr->uHItemPadding = 6;
2981 infoPtr->uVItemPadding = 3;
2982 infoPtr->uHItemPadding_s = 6;
2983 infoPtr->uVItemPadding_s = 3;
2984 infoPtr->hFont = 0;
2985 infoPtr->items = DPA_Create(8);
2986 infoPtr->hcurArrow = LoadCursorW (0, (LPWSTR)IDC_ARROW);
2987 infoPtr->iSelected = -1;
2988 infoPtr->iHotTracked = -1;
2989 infoPtr->uFocus = -1;
2990 infoPtr->hwndToolTip = 0;
2991 infoPtr->DoRedraw = TRUE;
2992 infoPtr->needsScrolling = FALSE;
2993 infoPtr->hwndUpDown = 0;
2994 infoPtr->leftmostVisible = 0;
2995 infoPtr->fHeightSet = FALSE;
2996 infoPtr->bUnicode = IsWindowUnicode (hwnd);
2997 infoPtr->cbInfo = sizeof(LPARAM);
2999 TRACE("Created tab control, hwnd [%p]\n", hwnd);
3001 /* The tab control always has the WS_CLIPSIBLINGS style. Even
3002 if you don't specify it in CreateWindow. This is necessary in
3003 order for paint to work correctly. This follows windows behaviour. */
3004 style = GetWindowLongW(hwnd, GWL_STYLE);
3005 if (style & TCS_VERTICAL) style |= TCS_MULTILINE;
3006 style |= WS_CLIPSIBLINGS;
3007 SetWindowLongW(hwnd, GWL_STYLE, style);
3009 infoPtr->dwStyle = style;
3010 infoPtr->exStyle = (style & TCS_FLATBUTTONS) ? TCS_EX_FLATSEPARATORS : 0;
3012 if (infoPtr->dwStyle & TCS_TOOLTIPS) {
3013 /* Create tooltip control */
3014 infoPtr->hwndToolTip =
3015 CreateWindowExW (0, TOOLTIPS_CLASSW, NULL, WS_POPUP,
3016 CW_USEDEFAULT, CW_USEDEFAULT,
3017 CW_USEDEFAULT, CW_USEDEFAULT,
3018 hwnd, 0, 0, 0);
3020 /* Send NM_TOOLTIPSCREATED notification */
3021 if (infoPtr->hwndToolTip) {
3022 NMTOOLTIPSCREATED nmttc;
3024 nmttc.hdr.hwndFrom = hwnd;
3025 nmttc.hdr.idFrom = GetWindowLongPtrW(hwnd, GWLP_ID);
3026 nmttc.hdr.code = NM_TOOLTIPSCREATED;
3027 nmttc.hwndToolTips = infoPtr->hwndToolTip;
3029 SendMessageW (infoPtr->hwndNotify, WM_NOTIFY,
3030 GetWindowLongPtrW(hwnd, GWLP_ID), (LPARAM)&nmttc);
3034 OpenThemeData (infoPtr->hwnd, themeClass);
3037 * We need to get text information so we need a DC and we need to select
3038 * a font.
3040 hdc = GetDC(hwnd);
3041 hOldFont = SelectObject (hdc, GetStockObject (SYSTEM_FONT));
3043 /* Use the system font to determine the initial height of a tab. */
3044 GetTextMetricsW(hdc, &fontMetrics);
3047 * Make sure there is enough space for the letters + growing the
3048 * selected item + extra space for the selected item.
3050 infoPtr->tabHeight = fontMetrics.tmHeight + SELECTED_TAB_OFFSET +
3051 ((infoPtr->dwStyle & TCS_BUTTONS) ? 2 : 1) *
3052 infoPtr->uVItemPadding;
3054 /* Initialize the width of a tab. */
3055 if (infoPtr->dwStyle & TCS_FIXEDWIDTH)
3056 infoPtr->tabWidth = GetDeviceCaps(hdc, LOGPIXELSX);
3058 infoPtr->tabMinWidth = -1;
3060 TRACE("tabH=%d, tabW=%d\n", infoPtr->tabHeight, infoPtr->tabWidth);
3062 SelectObject (hdc, hOldFont);
3063 ReleaseDC(hwnd, hdc);
3065 return 0;
3068 static LRESULT
3069 TAB_Destroy (TAB_INFO *infoPtr)
3071 INT iItem;
3073 SetWindowLongPtrW(infoPtr->hwnd, 0, 0);
3075 for (iItem = infoPtr->uNumItem - 1; iItem >= 0; iItem--)
3077 TAB_ITEM *tab = TAB_GetItem(infoPtr, iItem);
3079 DPA_DeletePtr(infoPtr->items, iItem);
3080 infoPtr->uNumItem--;
3082 Free(tab->pszText);
3083 Free(tab);
3085 DPA_Destroy(infoPtr->items);
3086 infoPtr->items = NULL;
3088 if (infoPtr->hwndToolTip)
3089 DestroyWindow (infoPtr->hwndToolTip);
3091 if (infoPtr->hwndUpDown)
3092 DestroyWindow(infoPtr->hwndUpDown);
3094 if (infoPtr->iHotTracked >= 0)
3095 KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
3097 CloseThemeData (GetWindowTheme (infoPtr->hwnd));
3099 Free (infoPtr);
3100 return 0;
3103 /* update theme after a WM_THEMECHANGED message */
3104 static LRESULT theme_changed(const TAB_INFO *infoPtr)
3106 HTHEME theme = GetWindowTheme (infoPtr->hwnd);
3107 CloseThemeData (theme);
3108 OpenThemeData (infoPtr->hwnd, themeClass);
3109 InvalidateRect (infoPtr->hwnd, NULL, TRUE);
3110 return 0;
3113 static LRESULT TAB_NCCalcSize(WPARAM wParam)
3115 if (!wParam)
3116 return 0;
3117 return WVR_ALIGNTOP;
3120 static inline LRESULT
3121 TAB_SetItemExtra (TAB_INFO *infoPtr, INT cbInfo)
3123 TRACE("(%p %d)\n", infoPtr, cbInfo);
3125 if (cbInfo < 0 || infoPtr->uNumItem) return FALSE;
3127 infoPtr->cbInfo = cbInfo;
3128 return TRUE;
3131 static LRESULT TAB_RemoveImage (TAB_INFO *infoPtr, INT image)
3133 TRACE("%p %d\n", infoPtr, image);
3135 if (ImageList_Remove (infoPtr->himl, image))
3137 INT i, *idx;
3138 RECT r;
3140 /* shift indices, repaint items if needed */
3141 for (i = 0; i < infoPtr->uNumItem; i++)
3143 idx = &TAB_GetItem(infoPtr, i)->iImage;
3144 if (*idx >= image)
3146 if (*idx == image)
3147 *idx = -1;
3148 else
3149 (*idx)--;
3151 /* repaint item */
3152 if (TAB_InternalGetItemRect (infoPtr, i, &r, NULL))
3153 InvalidateRect (infoPtr->hwnd, &r, TRUE);
3158 return 0;
3161 static LRESULT
3162 TAB_SetExtendedStyle (TAB_INFO *infoPtr, DWORD exMask, DWORD exStyle)
3164 DWORD prevstyle = infoPtr->exStyle;
3166 /* zero mask means all styles */
3167 if (exMask == 0) exMask = ~0;
3169 if (exMask & TCS_EX_REGISTERDROP)
3171 FIXME("TCS_EX_REGISTERDROP style unimplemented\n");
3172 exMask &= ~TCS_EX_REGISTERDROP;
3173 exStyle &= ~TCS_EX_REGISTERDROP;
3176 if (exMask & TCS_EX_FLATSEPARATORS)
3178 if ((prevstyle ^ exStyle) & TCS_EX_FLATSEPARATORS)
3180 infoPtr->exStyle ^= TCS_EX_FLATSEPARATORS;
3181 TAB_InvalidateTabArea(infoPtr);
3185 return prevstyle;
3188 static inline LRESULT
3189 TAB_GetExtendedStyle (const TAB_INFO *infoPtr)
3191 return infoPtr->exStyle;
3194 static LRESULT
3195 TAB_DeselectAll (TAB_INFO *infoPtr, BOOL excludesel)
3197 BOOL paint = FALSE;
3198 INT i, selected = infoPtr->iSelected;
3200 TRACE("(%p, %d)\n", infoPtr, excludesel);
3202 if (!(infoPtr->dwStyle & TCS_BUTTONS))
3203 return 0;
3205 for (i = 0; i < infoPtr->uNumItem; i++)
3207 if ((TAB_GetItem(infoPtr, i)->dwState & TCIS_BUTTONPRESSED) &&
3208 (selected != i))
3210 TAB_GetItem(infoPtr, i)->dwState &= ~TCIS_BUTTONPRESSED;
3211 paint = TRUE;
3215 if (!excludesel && (selected != -1))
3217 TAB_GetItem(infoPtr, selected)->dwState &= ~TCIS_BUTTONPRESSED;
3218 infoPtr->iSelected = -1;
3219 paint = TRUE;
3222 if (paint)
3223 TAB_InvalidateTabArea (infoPtr);
3225 return 0;
3228 /***
3229 * DESCRIPTION:
3230 * Processes WM_STYLECHANGED messages.
3232 * PARAMETER(S):
3233 * [I] infoPtr : valid pointer to the tab data structure
3234 * [I] wStyleType : window style type (normal or extended)
3235 * [I] lpss : window style information
3237 * RETURN:
3238 * Zero
3240 static INT TAB_StyleChanged(TAB_INFO *infoPtr, WPARAM wStyleType,
3241 const STYLESTRUCT *lpss)
3243 TRACE("style type %Ix, styleOld %#lx, styleNew %#lx\n", wStyleType, lpss->styleOld, lpss->styleNew);
3245 if (wStyleType != GWL_STYLE) return 0;
3247 infoPtr->dwStyle = lpss->styleNew;
3249 TAB_SetItemBounds (infoPtr);
3250 InvalidateRect(infoPtr->hwnd, NULL, TRUE);
3252 return 0;
3255 static LRESULT WINAPI
3256 TAB_WindowProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
3258 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
3260 TRACE("hwnd %p, msg %x, wParam %Ix, lParam %Ix\n", hwnd, uMsg, wParam, lParam);
3262 if (!infoPtr && (uMsg != WM_CREATE))
3263 return DefWindowProcW (hwnd, uMsg, wParam, lParam);
3265 switch (uMsg)
3267 case TCM_GETIMAGELIST:
3268 return TAB_GetImageList (infoPtr);
3270 case TCM_SETIMAGELIST:
3271 return TAB_SetImageList (infoPtr, (HIMAGELIST)lParam);
3273 case TCM_GETITEMCOUNT:
3274 return TAB_GetItemCount (infoPtr);
3276 case TCM_GETITEMA:
3277 case TCM_GETITEMW:
3278 return TAB_GetItemT (infoPtr, (INT)wParam, (LPTCITEMW)lParam, uMsg == TCM_GETITEMW);
3280 case TCM_SETITEMA:
3281 case TCM_SETITEMW:
3282 return TAB_SetItemT (infoPtr, (INT)wParam, (LPTCITEMW)lParam, uMsg == TCM_SETITEMW);
3284 case TCM_DELETEITEM:
3285 return TAB_DeleteItem (infoPtr, (INT)wParam);
3287 case TCM_DELETEALLITEMS:
3288 return TAB_DeleteAllItems (infoPtr);
3290 case TCM_GETITEMRECT:
3291 return TAB_GetItemRect (infoPtr, (INT)wParam, (LPRECT)lParam);
3293 case TCM_GETCURSEL:
3294 return TAB_GetCurSel (infoPtr);
3296 case TCM_HITTEST:
3297 return TAB_HitTest (infoPtr, (LPTCHITTESTINFO)lParam);
3299 case TCM_SETCURSEL:
3300 return TAB_SetCurSel (infoPtr, (INT)wParam);
3302 case TCM_INSERTITEMA:
3303 case TCM_INSERTITEMW:
3304 return TAB_InsertItemT (infoPtr, (INT)wParam, (TCITEMW*)lParam, uMsg == TCM_INSERTITEMW);
3306 case TCM_SETITEMEXTRA:
3307 return TAB_SetItemExtra (infoPtr, (INT)wParam);
3309 case TCM_ADJUSTRECT:
3310 return TAB_AdjustRect (infoPtr, (BOOL)wParam, (LPRECT)lParam);
3312 case TCM_SETITEMSIZE:
3313 return TAB_SetItemSize (infoPtr, (INT)LOWORD(lParam), (INT)HIWORD(lParam));
3315 case TCM_REMOVEIMAGE:
3316 return TAB_RemoveImage (infoPtr, (INT)wParam);
3318 case TCM_SETPADDING:
3319 return TAB_SetPadding (infoPtr, lParam);
3321 case TCM_GETROWCOUNT:
3322 return TAB_GetRowCount(infoPtr);
3324 case TCM_GETUNICODEFORMAT:
3325 return TAB_GetUnicodeFormat (infoPtr);
3327 case TCM_SETUNICODEFORMAT:
3328 return TAB_SetUnicodeFormat (infoPtr, (BOOL)wParam);
3330 case TCM_HIGHLIGHTITEM:
3331 return TAB_HighlightItem (infoPtr, (INT)wParam, (BOOL)LOWORD(lParam));
3333 case TCM_GETTOOLTIPS:
3334 return TAB_GetToolTips (infoPtr);
3336 case TCM_SETTOOLTIPS:
3337 return TAB_SetToolTips (infoPtr, (HWND)wParam);
3339 case TCM_GETCURFOCUS:
3340 return TAB_GetCurFocus (infoPtr);
3342 case TCM_SETCURFOCUS:
3343 return TAB_SetCurFocus (infoPtr, (INT)wParam);
3345 case TCM_SETMINTABWIDTH:
3346 return TAB_SetMinTabWidth(infoPtr, (INT)lParam);
3348 case TCM_DESELECTALL:
3349 return TAB_DeselectAll (infoPtr, (BOOL)wParam);
3351 case TCM_GETEXTENDEDSTYLE:
3352 return TAB_GetExtendedStyle (infoPtr);
3354 case TCM_SETEXTENDEDSTYLE:
3355 return TAB_SetExtendedStyle (infoPtr, wParam, lParam);
3357 case WM_GETFONT:
3358 return TAB_GetFont (infoPtr);
3360 case WM_SETFONT:
3361 return TAB_SetFont (infoPtr, (HFONT)wParam);
3363 case WM_CREATE:
3364 return TAB_Create (hwnd, lParam);
3366 case WM_NCDESTROY:
3367 return TAB_Destroy (infoPtr);
3369 case WM_GETDLGCODE:
3370 return DLGC_WANTARROWS | DLGC_WANTCHARS;
3372 case WM_LBUTTONDOWN:
3373 return TAB_LButtonDown (infoPtr, wParam, lParam);
3375 case WM_LBUTTONUP:
3376 return TAB_LButtonUp (infoPtr);
3378 case WM_NOTIFY:
3379 return SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, wParam, lParam);
3381 case WM_RBUTTONUP:
3382 TAB_RButtonUp (infoPtr);
3383 return DefWindowProcW (hwnd, uMsg, wParam, lParam);
3385 case WM_MOUSEMOVE:
3386 return TAB_MouseMove (infoPtr, wParam, lParam);
3388 case WM_PRINTCLIENT:
3389 case WM_PAINT:
3390 return TAB_Paint (infoPtr, (HDC)wParam);
3392 case WM_SIZE:
3393 return TAB_Size (infoPtr);
3395 case WM_SETREDRAW:
3396 return TAB_SetRedraw (infoPtr, (BOOL)wParam);
3398 case WM_HSCROLL:
3399 return TAB_OnHScroll(infoPtr, (int)LOWORD(wParam), (int)HIWORD(wParam));
3401 case WM_STYLECHANGED:
3402 return TAB_StyleChanged(infoPtr, wParam, (LPSTYLESTRUCT)lParam);
3404 case WM_SYSCOLORCHANGE:
3405 COMCTL32_RefreshSysColors();
3406 return 0;
3408 case WM_THEMECHANGED:
3409 return theme_changed (infoPtr);
3411 case WM_KILLFOCUS:
3412 TAB_KillFocus(infoPtr);
3413 case WM_SETFOCUS:
3414 TAB_FocusChanging(infoPtr);
3415 break; /* Don't disturb normal focus behavior */
3417 case WM_KEYDOWN:
3418 return TAB_KeyDown(infoPtr, wParam, lParam);
3420 case WM_NCHITTEST:
3421 return TAB_NCHitTest(infoPtr, lParam);
3423 case WM_NCCALCSIZE:
3424 return TAB_NCCalcSize(wParam);
3426 default:
3427 if (uMsg >= WM_USER && uMsg < WM_APP && !COMCTL32_IsReflectedMessage(uMsg))
3428 WARN("unknown msg %04x wp %Ix, lp %Ix\n", uMsg, wParam, lParam);
3429 break;
3431 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
3435 void
3436 TAB_Register (void)
3438 WNDCLASSW wndClass;
3440 ZeroMemory (&wndClass, sizeof(WNDCLASSW));
3441 wndClass.style = CS_GLOBALCLASS | CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
3442 wndClass.lpfnWndProc = TAB_WindowProc;
3443 wndClass.cbClsExtra = 0;
3444 wndClass.cbWndExtra = sizeof(TAB_INFO *);
3445 wndClass.hCursor = LoadCursorW (0, (LPWSTR)IDC_ARROW);
3446 wndClass.hbrBackground = (HBRUSH)(COLOR_BTNFACE+1);
3447 wndClass.lpszClassName = WC_TABCONTROLW;
3449 RegisterClassW (&wndClass);
3453 void
3454 TAB_Unregister (void)
3456 UnregisterClassW (WC_TABCONTROLW, NULL);