gdi32: Use the get_clipped_rects helper in the pen functions.
[wine/multimedia.git] / dlls / comctl32 / tab.c
blob374d4ee2776b8570c5dda1a1719faa434f252d46
1 /*
2 * Tab control
4 * Copyright 1998 Anders Carlsson
5 * Copyright 1999 Alex Priem <alexp@sci.kun.nl>
6 * Copyright 1999 Francis Beaudet
7 * Copyright 2003 Vitaliy Margolen
9 * This library is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Lesser General Public
11 * License as published by the Free Software Foundation; either
12 * version 2.1 of the License, or (at your option) any later version.
14 * This library is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Lesser General Public License for more details.
19 * You should have received a copy of the GNU Lesser General Public
20 * License along with this library; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
23 * NOTES
25 * This code was audited for completeness against the documented features
26 * of Comctl32.dll version 6.0 on May. 20, 2005, by James Hawkins.
28 * Unless otherwise noted, we believe this code to be complete, as per
29 * the specification mentioned above.
30 * If you discover missing features, or bugs, please note them below.
32 * TODO:
34 * Styles:
35 * TCS_MULTISELECT - implement for VK_SPACE selection
36 * TCS_RIGHT
37 * TCS_RIGHTJUSTIFY
38 * TCS_SCROLLOPPOSITE
39 * TCS_SINGLELINE
40 * TCIF_RTLREADING
42 * Extended Styles:
43 * TCS_EX_REGISTERDROP
45 * Notifications:
46 * NM_RELEASEDCAPTURE
47 * TCN_FOCUSCHANGE
48 * TCN_GETOBJECT
50 * Macros:
51 * TabCtrl_AdjustRect
55 #include <assert.h>
56 #include <stdarg.h>
57 #include <string.h>
59 #include "windef.h"
60 #include "winbase.h"
61 #include "wingdi.h"
62 #include "winuser.h"
63 #include "winnls.h"
64 #include "commctrl.h"
65 #include "comctl32.h"
66 #include "uxtheme.h"
67 #include "vssym32.h"
68 #include "wine/debug.h"
69 #include <math.h>
71 WINE_DEFAULT_DEBUG_CHANNEL(tab);
73 typedef struct
75 DWORD dwState;
76 LPWSTR pszText;
77 INT iImage;
78 RECT rect; /* bounding rectangle of the item relative to the
79 * leftmost item (the leftmost item, 0, would have a
80 * "left" member of 0 in this rectangle)
82 * additionally the top member holds the row number
83 * and bottom is unused and should be 0 */
84 BYTE extra[1]; /* Space for caller supplied info, variable size */
85 } TAB_ITEM;
87 /* The size of a tab item depends on how much extra data is requested.
88 TCM_INSERTITEM always stores at least LPARAM sized data. */
89 #define EXTRA_ITEM_SIZE(infoPtr) (max((infoPtr)->cbInfo, sizeof(LPARAM)))
90 #define TAB_ITEM_SIZE(infoPtr) FIELD_OFFSET(TAB_ITEM, extra[EXTRA_ITEM_SIZE(infoPtr)])
92 typedef struct
94 HWND hwnd; /* Tab control window */
95 HWND hwndNotify; /* notification window (parent) */
96 UINT uNumItem; /* number of tab items */
97 UINT uNumRows; /* number of tab rows */
98 INT tabHeight; /* height of the tab row */
99 INT tabWidth; /* width of tabs */
100 INT tabMinWidth; /* minimum width of items */
101 USHORT uHItemPadding; /* amount of horizontal padding, in pixels */
102 USHORT uVItemPadding; /* amount of vertical padding, in pixels */
103 USHORT uHItemPadding_s; /* Set amount of horizontal padding, in pixels */
104 USHORT uVItemPadding_s; /* Set amount of vertical padding, in pixels */
105 HFONT hFont; /* handle to the current font */
106 HCURSOR hcurArrow; /* handle to the current cursor */
107 HIMAGELIST himl; /* handle to an image list (may be 0) */
108 HWND hwndToolTip; /* handle to tab's tooltip */
109 INT leftmostVisible; /* Used for scrolling, this member contains
110 * the index of the first visible item */
111 INT iSelected; /* the currently selected item */
112 INT iHotTracked; /* the highlighted item under the mouse */
113 INT uFocus; /* item which has the focus */
114 BOOL DoRedraw; /* flag for redrawing when tab contents is changed*/
115 BOOL needsScrolling; /* TRUE if the size of the tabs is greater than
116 * the size of the control */
117 BOOL fHeightSet; /* was the height of the tabs explicitly set? */
118 BOOL bUnicode; /* Unicode control? */
119 HWND hwndUpDown; /* Updown control used for scrolling */
120 INT cbInfo; /* Number of bytes of caller supplied info per tab */
122 DWORD exStyle; /* Extended style used, currently:
123 TCS_EX_FLATSEPARATORS, TCS_EX_REGISTERDROP */
124 DWORD dwStyle; /* the cached window GWL_STYLE */
126 HDPA items; /* dynamic array of TAB_ITEM* pointers */
127 } TAB_INFO;
129 /******************************************************************************
130 * Positioning constants
132 #define SELECTED_TAB_OFFSET 2
133 #define ROUND_CORNER_SIZE 2
134 #define DISPLAY_AREA_PADDINGX 2
135 #define DISPLAY_AREA_PADDINGY 2
136 #define CONTROL_BORDER_SIZEX 2
137 #define CONTROL_BORDER_SIZEY 2
138 #define BUTTON_SPACINGX 3
139 #define BUTTON_SPACINGY 3
140 #define FLAT_BTN_SPACINGX 8
141 #define DEFAULT_MIN_TAB_WIDTH 54
142 #define DEFAULT_PADDING_X 6
143 #define EXTRA_ICON_PADDING 3
145 #define TAB_GetInfoPtr(hwnd) ((TAB_INFO *)GetWindowLongPtrW(hwnd,0))
147 #define GET_DEFAULT_MIN_TAB_WIDTH(infoPtr) (DEFAULT_MIN_TAB_WIDTH - (DEFAULT_PADDING_X - (infoPtr)->uHItemPadding) * 2)
149 /******************************************************************************
150 * Hot-tracking timer constants
152 #define TAB_HOTTRACK_TIMER 1
153 #define TAB_HOTTRACK_TIMER_INTERVAL 100 /* milliseconds */
155 static const WCHAR themeClass[] = { 'T','a','b',0 };
157 static inline TAB_ITEM* TAB_GetItem(const TAB_INFO *infoPtr, INT i)
159 assert(i >= 0 && i < infoPtr->uNumItem);
160 return DPA_GetPtr(infoPtr->items, i);
163 /******************************************************************************
164 * Prototypes
166 static void TAB_InvalidateTabArea(const TAB_INFO *);
167 static void TAB_EnsureSelectionVisible(TAB_INFO *);
168 static void TAB_DrawItemInterior(const TAB_INFO *, HDC, INT, RECT*);
169 static LRESULT TAB_DeselectAll(TAB_INFO *, BOOL);
170 static BOOL TAB_InternalGetItemRect(const TAB_INFO *, INT, RECT*, RECT*);
172 static BOOL
173 TAB_SendSimpleNotify (const TAB_INFO *infoPtr, UINT code)
175 NMHDR nmhdr;
177 nmhdr.hwndFrom = infoPtr->hwnd;
178 nmhdr.idFrom = GetWindowLongPtrW(infoPtr->hwnd, GWLP_ID);
179 nmhdr.code = code;
181 return (BOOL) SendMessageW (infoPtr->hwndNotify, WM_NOTIFY,
182 nmhdr.idFrom, (LPARAM) &nmhdr);
185 static void
186 TAB_RelayEvent (HWND hwndTip, HWND hwndMsg, UINT uMsg,
187 WPARAM wParam, LPARAM lParam)
189 MSG msg;
191 msg.hwnd = hwndMsg;
192 msg.message = uMsg;
193 msg.wParam = wParam;
194 msg.lParam = lParam;
195 msg.time = GetMessageTime ();
196 msg.pt.x = (short)LOWORD(GetMessagePos ());
197 msg.pt.y = (short)HIWORD(GetMessagePos ());
199 SendMessageW (hwndTip, TTM_RELAYEVENT, 0, (LPARAM)&msg);
202 static void
203 TAB_DumpItemExternalT(const TCITEMW *pti, UINT iItem, BOOL isW)
205 if (TRACE_ON(tab)) {
206 TRACE("external tab %d, mask=0x%08x, dwState=0x%08x, dwStateMask=0x%08x, cchTextMax=0x%08x\n",
207 iItem, pti->mask, pti->dwState, pti->dwStateMask, pti->cchTextMax);
208 TRACE("external tab %d, iImage=%d, lParam=0x%08lx, pszTextW=%s\n",
209 iItem, pti->iImage, pti->lParam, isW ? debugstr_w(pti->pszText) : debugstr_a((LPSTR)pti->pszText));
213 static void
214 TAB_DumpItemInternal(const TAB_INFO *infoPtr, UINT iItem)
216 if (TRACE_ON(tab)) {
217 TAB_ITEM *ti = TAB_GetItem(infoPtr, iItem);
219 TRACE("tab %d, dwState=0x%08x, pszText=%s, iImage=%d\n",
220 iItem, ti->dwState, debugstr_w(ti->pszText), ti->iImage);
221 TRACE("tab %d, rect.left=%d, rect.top(row)=%d\n",
222 iItem, ti->rect.left, ti->rect.top);
226 /* RETURNS
227 * the index of the selected tab, or -1 if no tab is selected. */
228 static inline LRESULT TAB_GetCurSel (const TAB_INFO *infoPtr)
230 TRACE("(%p)\n", infoPtr);
231 return infoPtr->iSelected;
234 /* RETURNS
235 * the index of the tab item that has the focus. */
236 static inline LRESULT
237 TAB_GetCurFocus (const TAB_INFO *infoPtr)
239 TRACE("(%p)\n", infoPtr);
240 return infoPtr->uFocus;
243 static inline LRESULT TAB_GetToolTips (const TAB_INFO *infoPtr)
245 TRACE("(%p)\n", infoPtr);
246 return (LRESULT)infoPtr->hwndToolTip;
249 static inline LRESULT TAB_SetCurSel (TAB_INFO *infoPtr, INT iItem)
251 INT prevItem = infoPtr->iSelected;
253 TRACE("(%p %d)\n", infoPtr, iItem);
255 if (iItem < 0)
256 infoPtr->iSelected = -1;
257 else if (iItem >= infoPtr->uNumItem)
258 return -1;
259 else {
260 if (prevItem != iItem) {
261 if (prevItem != -1)
262 TAB_GetItem(infoPtr, prevItem)->dwState &= ~TCIS_BUTTONPRESSED;
263 TAB_GetItem(infoPtr, iItem)->dwState |= TCIS_BUTTONPRESSED;
265 infoPtr->iSelected = iItem;
266 infoPtr->uFocus = iItem;
267 TAB_EnsureSelectionVisible(infoPtr);
268 TAB_InvalidateTabArea(infoPtr);
271 return prevItem;
274 static LRESULT TAB_SetCurFocus (TAB_INFO *infoPtr, INT iItem)
276 TRACE("(%p %d)\n", infoPtr, iItem);
278 if (iItem < 0) {
279 infoPtr->uFocus = -1;
280 if (infoPtr->iSelected != -1) {
281 infoPtr->iSelected = -1;
282 TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGE);
283 TAB_InvalidateTabArea(infoPtr);
286 else if (iItem < infoPtr->uNumItem) {
287 if (infoPtr->dwStyle & TCS_BUTTONS) {
288 /* set focus to new item, leave selection as is */
289 if (infoPtr->uFocus != iItem) {
290 INT prev_focus = infoPtr->uFocus;
291 RECT r;
293 infoPtr->uFocus = iItem;
295 if (prev_focus != infoPtr->iSelected) {
296 if (TAB_InternalGetItemRect(infoPtr, prev_focus, &r, NULL))
297 InvalidateRect(infoPtr->hwnd, &r, FALSE);
300 if (TAB_InternalGetItemRect(infoPtr, iItem, &r, NULL))
301 InvalidateRect(infoPtr->hwnd, &r, FALSE);
303 TAB_SendSimpleNotify(infoPtr, TCN_FOCUSCHANGE);
305 } else {
306 INT oldFocus = infoPtr->uFocus;
307 if (infoPtr->iSelected != iItem || oldFocus == -1 ) {
308 infoPtr->uFocus = iItem;
309 if (oldFocus != -1) {
310 if (!TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGING)) {
311 infoPtr->iSelected = iItem;
312 TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGE);
314 else
315 infoPtr->iSelected = iItem;
316 TAB_EnsureSelectionVisible(infoPtr);
317 TAB_InvalidateTabArea(infoPtr);
322 return 0;
325 static inline LRESULT
326 TAB_SetToolTips (TAB_INFO *infoPtr, HWND hwndToolTip)
328 TRACE("%p %p\n", infoPtr, hwndToolTip);
329 infoPtr->hwndToolTip = hwndToolTip;
330 return 0;
333 static inline LRESULT
334 TAB_SetPadding (TAB_INFO *infoPtr, LPARAM lParam)
336 TRACE("(%p %d %d)\n", infoPtr, LOWORD(lParam), HIWORD(lParam));
337 infoPtr->uHItemPadding_s = LOWORD(lParam);
338 infoPtr->uVItemPadding_s = HIWORD(lParam);
340 return 0;
343 /******************************************************************************
344 * TAB_InternalGetItemRect
346 * This method will calculate the rectangle representing a given tab item in
347 * client coordinates. This method takes scrolling into account.
349 * This method returns TRUE if the item is visible in the window and FALSE
350 * if it is completely outside the client area.
352 static BOOL TAB_InternalGetItemRect(
353 const TAB_INFO* infoPtr,
354 INT itemIndex,
355 RECT* itemRect,
356 RECT* selectedRect)
358 RECT tmpItemRect,clientRect;
360 /* Perform a sanity check and a trivial visibility check. */
361 if ( (infoPtr->uNumItem <= 0) ||
362 (itemIndex >= infoPtr->uNumItem) ||
363 (!(((infoPtr->dwStyle & TCS_MULTILINE) || (infoPtr->dwStyle & TCS_VERTICAL))) &&
364 (itemIndex < infoPtr->leftmostVisible)))
366 TRACE("Not Visible\n");
367 /* need to initialize these to empty rects */
368 if (itemRect)
370 memset(itemRect,0,sizeof(RECT));
371 itemRect->bottom = infoPtr->tabHeight;
373 if (selectedRect)
374 memset(selectedRect,0,sizeof(RECT));
375 return FALSE;
379 * Avoid special cases in this procedure by assigning the "out"
380 * parameters if the caller didn't supply them
382 if (itemRect == NULL)
383 itemRect = &tmpItemRect;
385 /* Retrieve the unmodified item rect. */
386 *itemRect = TAB_GetItem(infoPtr,itemIndex)->rect;
388 /* calculate the times bottom and top based on the row */
389 GetClientRect(infoPtr->hwnd, &clientRect);
391 if ((infoPtr->dwStyle & TCS_BOTTOM) && (infoPtr->dwStyle & TCS_VERTICAL))
393 itemRect->right = clientRect.right - SELECTED_TAB_OFFSET - itemRect->left * infoPtr->tabHeight -
394 ((infoPtr->dwStyle & TCS_BUTTONS) ? itemRect->left * BUTTON_SPACINGX : 0);
395 itemRect->left = itemRect->right - infoPtr->tabHeight;
397 else if (infoPtr->dwStyle & TCS_VERTICAL)
399 itemRect->left = clientRect.left + SELECTED_TAB_OFFSET + itemRect->left * infoPtr->tabHeight +
400 ((infoPtr->dwStyle & TCS_BUTTONS) ? itemRect->left * BUTTON_SPACINGX : 0);
401 itemRect->right = itemRect->left + infoPtr->tabHeight;
403 else if (infoPtr->dwStyle & TCS_BOTTOM)
405 itemRect->bottom = clientRect.bottom - itemRect->top * infoPtr->tabHeight -
406 ((infoPtr->dwStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : SELECTED_TAB_OFFSET);
407 itemRect->top = itemRect->bottom - infoPtr->tabHeight;
409 else /* not TCS_BOTTOM and not TCS_VERTICAL */
411 itemRect->top = clientRect.top + itemRect->top * infoPtr->tabHeight +
412 ((infoPtr->dwStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : SELECTED_TAB_OFFSET);
413 itemRect->bottom = itemRect->top + infoPtr->tabHeight;
417 * "scroll" it to make sure the item at the very left of the
418 * tab control is the leftmost visible tab.
420 if(infoPtr->dwStyle & TCS_VERTICAL)
422 OffsetRect(itemRect,
424 -TAB_GetItem(infoPtr, infoPtr->leftmostVisible)->rect.top);
427 * Move the rectangle so the first item is slightly offset from
428 * the bottom of the tab control.
430 OffsetRect(itemRect,
432 SELECTED_TAB_OFFSET);
434 } else
436 OffsetRect(itemRect,
437 -TAB_GetItem(infoPtr, infoPtr->leftmostVisible)->rect.left,
441 * Move the rectangle so the first item is slightly offset from
442 * the left of the tab control.
444 OffsetRect(itemRect,
445 SELECTED_TAB_OFFSET,
448 TRACE("item %d tab h=%d, rect=(%s)\n",
449 itemIndex, infoPtr->tabHeight, wine_dbgstr_rect(itemRect));
451 /* Now, calculate the position of the item as if it were selected. */
452 if (selectedRect!=NULL)
454 CopyRect(selectedRect, itemRect);
456 /* The rectangle of a selected item is a bit wider. */
457 if(infoPtr->dwStyle & TCS_VERTICAL)
458 InflateRect(selectedRect, 0, SELECTED_TAB_OFFSET);
459 else
460 InflateRect(selectedRect, SELECTED_TAB_OFFSET, 0);
462 /* If it also a bit higher. */
463 if ((infoPtr->dwStyle & TCS_BOTTOM) && (infoPtr->dwStyle & TCS_VERTICAL))
465 selectedRect->left -= 2; /* the border is thicker on the right */
466 selectedRect->right += SELECTED_TAB_OFFSET;
468 else if (infoPtr->dwStyle & TCS_VERTICAL)
470 selectedRect->left -= SELECTED_TAB_OFFSET;
471 selectedRect->right += 1;
473 else if (infoPtr->dwStyle & TCS_BOTTOM)
475 selectedRect->bottom += SELECTED_TAB_OFFSET;
477 else /* not TCS_BOTTOM and not TCS_VERTICAL */
479 selectedRect->top -= SELECTED_TAB_OFFSET;
480 selectedRect->bottom -= 1;
484 /* Check for visibility */
485 if (infoPtr->dwStyle & TCS_VERTICAL)
486 return (itemRect->top < clientRect.bottom) && (itemRect->bottom > clientRect.top);
487 else
488 return (itemRect->left < clientRect.right) && (itemRect->right > clientRect.left);
491 static inline BOOL
492 TAB_GetItemRect(const TAB_INFO *infoPtr, INT item, RECT *rect)
494 TRACE("(%p, %d, %p)\n", infoPtr, item, rect);
495 return TAB_InternalGetItemRect(infoPtr, item, rect, NULL);
498 /******************************************************************************
499 * TAB_KeyDown
501 * This method is called to handle keyboard input
503 static LRESULT TAB_KeyDown(TAB_INFO* infoPtr, WPARAM keyCode, LPARAM lParam)
505 INT newItem = -1;
506 NMTCKEYDOWN nm;
508 /* TCN_KEYDOWN notification sent always */
509 nm.hdr.hwndFrom = infoPtr->hwnd;
510 nm.hdr.idFrom = GetWindowLongPtrW(infoPtr->hwnd, GWLP_ID);
511 nm.hdr.code = TCN_KEYDOWN;
512 nm.wVKey = keyCode;
513 nm.flags = lParam;
514 SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, nm.hdr.idFrom, (LPARAM)&nm);
516 switch (keyCode)
518 case VK_LEFT:
519 newItem = infoPtr->uFocus - 1;
520 break;
521 case VK_RIGHT:
522 newItem = infoPtr->uFocus + 1;
523 break;
526 /* If we changed to a valid item, change focused item */
527 if (newItem >= 0 && newItem < infoPtr->uNumItem && infoPtr->uFocus != newItem)
528 TAB_SetCurFocus(infoPtr, newItem);
530 return 0;
534 * WM_KILLFOCUS handler
536 static void TAB_KillFocus(TAB_INFO *infoPtr)
538 /* clear current focused item back to selected for TCS_BUTTONS */
539 if ((infoPtr->dwStyle & TCS_BUTTONS) && (infoPtr->uFocus != infoPtr->iSelected))
541 RECT r;
543 if (TAB_InternalGetItemRect(infoPtr, infoPtr->uFocus, &r, NULL))
544 InvalidateRect(infoPtr->hwnd, &r, FALSE);
546 infoPtr->uFocus = infoPtr->iSelected;
550 /******************************************************************************
551 * TAB_FocusChanging
553 * This method is called whenever the focus goes in or out of this control
554 * it is used to update the visual state of the control.
556 static void TAB_FocusChanging(const TAB_INFO *infoPtr)
558 RECT selectedRect;
559 BOOL isVisible;
562 * Get the rectangle for the item.
564 isVisible = TAB_InternalGetItemRect(infoPtr,
565 infoPtr->uFocus,
566 NULL,
567 &selectedRect);
570 * If the rectangle is not completely invisible, invalidate that
571 * portion of the window.
573 if (isVisible)
575 TRACE("invalidate (%s)\n", wine_dbgstr_rect(&selectedRect));
576 InvalidateRect(infoPtr->hwnd, &selectedRect, TRUE);
580 static INT TAB_InternalHitTest (const TAB_INFO *infoPtr, POINT pt, UINT *flags)
582 RECT rect;
583 INT iCount;
585 for (iCount = 0; iCount < infoPtr->uNumItem; iCount++)
587 TAB_InternalGetItemRect(infoPtr, iCount, &rect, NULL);
589 if (PtInRect(&rect, pt))
591 *flags = TCHT_ONITEM;
592 return iCount;
596 *flags = TCHT_NOWHERE;
597 return -1;
600 static inline LRESULT
601 TAB_HitTest (const TAB_INFO *infoPtr, LPTCHITTESTINFO lptest)
603 TRACE("(%p, %p)\n", infoPtr, lptest);
604 return TAB_InternalHitTest (infoPtr, lptest->pt, &lptest->flags);
607 /******************************************************************************
608 * TAB_NCHitTest
610 * Napster v2b5 has a tab control for its main navigation which has a client
611 * area that covers the whole area of the dialog pages.
612 * That's why it receives all msgs for that area and the underlying dialog ctrls
613 * are dead.
614 * So I decided that we should handle WM_NCHITTEST here and return
615 * HTTRANSPARENT if we don't hit the tab control buttons.
616 * FIXME: WM_NCHITTEST handling correct ? Fix it if you know that Windows
617 * doesn't do it that way. Maybe depends on tab control styles ?
619 static inline LRESULT
620 TAB_NCHitTest (const TAB_INFO *infoPtr, LPARAM lParam)
622 POINT pt;
623 UINT dummyflag;
625 pt.x = (short)LOWORD(lParam);
626 pt.y = (short)HIWORD(lParam);
627 ScreenToClient(infoPtr->hwnd, &pt);
629 if (TAB_InternalHitTest(infoPtr, pt, &dummyflag) == -1)
630 return HTTRANSPARENT;
631 else
632 return HTCLIENT;
635 static LRESULT
636 TAB_LButtonDown (TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
638 POINT pt;
639 INT newItem;
640 UINT dummy;
642 if (infoPtr->hwndToolTip)
643 TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
644 WM_LBUTTONDOWN, wParam, lParam);
646 if (!(infoPtr->dwStyle & TCS_FOCUSNEVER)) {
647 SetFocus (infoPtr->hwnd);
650 if (infoPtr->hwndToolTip)
651 TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
652 WM_LBUTTONDOWN, wParam, lParam);
654 pt.x = (short)LOWORD(lParam);
655 pt.y = (short)HIWORD(lParam);
657 newItem = TAB_InternalHitTest (infoPtr, pt, &dummy);
659 TRACE("On Tab, item %d\n", newItem);
661 if ((newItem != -1) && (infoPtr->iSelected != newItem))
663 if ((infoPtr->dwStyle & TCS_BUTTONS) && (infoPtr->dwStyle & TCS_MULTISELECT) &&
664 (wParam & MK_CONTROL))
666 RECT r;
668 /* toggle multiselection */
669 TAB_GetItem(infoPtr, newItem)->dwState ^= TCIS_BUTTONPRESSED;
670 if (TAB_InternalGetItemRect (infoPtr, newItem, &r, NULL))
671 InvalidateRect (infoPtr->hwnd, &r, TRUE);
673 else
675 INT i;
676 BOOL pressed = FALSE;
678 /* any button pressed ? */
679 for (i = 0; i < infoPtr->uNumItem; i++)
680 if ((TAB_GetItem (infoPtr, i)->dwState & TCIS_BUTTONPRESSED) &&
681 (infoPtr->iSelected != i))
683 pressed = TRUE;
684 break;
687 TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGING);
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=%ld (%s)\n", infoPtr->hwnd, fLarger,
931 wine_dbgstr_rect(prc));
933 if (!prc) return -1;
935 if(infoPtr->dwStyle & TCS_VERTICAL)
937 iRightBottom = &(prc->right);
938 iLeftTop = &(prc->left);
940 else
942 iRightBottom = &(prc->bottom);
943 iLeftTop = &(prc->top);
946 if (fLarger) /* Go from display rectangle */
948 /* Add the height of the tabs. */
949 if (infoPtr->dwStyle & TCS_BOTTOM)
950 *iRightBottom += infoPtr->tabHeight * infoPtr->uNumRows;
951 else
952 *iLeftTop -= infoPtr->tabHeight * infoPtr->uNumRows +
953 ((infoPtr->dwStyle & TCS_BUTTONS)? 3 * (infoPtr->uNumRows - 1) : 0);
955 /* Inflate the rectangle for the padding */
956 InflateRect(prc, DISPLAY_AREA_PADDINGX, DISPLAY_AREA_PADDINGY);
958 /* Inflate for the border */
959 InflateRect(prc, CONTROL_BORDER_SIZEX, CONTROL_BORDER_SIZEY);
961 else /* Go from window rectangle. */
963 /* Deflate the rectangle for the border */
964 InflateRect(prc, -CONTROL_BORDER_SIZEX, -CONTROL_BORDER_SIZEY);
966 /* Deflate the rectangle for the padding */
967 InflateRect(prc, -DISPLAY_AREA_PADDINGX, -DISPLAY_AREA_PADDINGY);
969 /* Remove the height of the tabs. */
970 if (infoPtr->dwStyle & TCS_BOTTOM)
971 *iRightBottom -= infoPtr->tabHeight * infoPtr->uNumRows;
972 else
973 *iLeftTop += (infoPtr->tabHeight) * infoPtr->uNumRows +
974 ((infoPtr->dwStyle & TCS_BUTTONS)? 3 * (infoPtr->uNumRows - 1) : 0);
977 return 0;
980 /******************************************************************************
981 * TAB_OnHScroll
983 * This method will handle the notification from the scroll control and
984 * perform the scrolling operation on the tab control.
986 static LRESULT TAB_OnHScroll(TAB_INFO *infoPtr, int nScrollCode, int nPos)
988 if(nScrollCode == SB_THUMBPOSITION && nPos != infoPtr->leftmostVisible)
990 if(nPos < infoPtr->leftmostVisible)
991 infoPtr->leftmostVisible--;
992 else
993 infoPtr->leftmostVisible++;
995 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
996 TAB_InvalidateTabArea(infoPtr);
997 SendMessageW(infoPtr->hwndUpDown, UDM_SETPOS, 0,
998 MAKELONG(infoPtr->leftmostVisible, 0));
1001 return 0;
1004 /******************************************************************************
1005 * TAB_SetupScrolling
1007 * This method will check the current scrolling state and make sure the
1008 * scrolling control is displayed (or not).
1010 static void TAB_SetupScrolling(
1011 TAB_INFO* infoPtr,
1012 const RECT* clientRect)
1014 static const WCHAR emptyW[] = { 0 };
1015 INT maxRange = 0;
1017 if (infoPtr->needsScrolling)
1019 RECT controlPos;
1020 INT vsize, tabwidth;
1023 * Calculate the position of the scroll control.
1025 if(infoPtr->dwStyle & TCS_VERTICAL)
1027 controlPos.right = clientRect->right;
1028 controlPos.left = controlPos.right - 2 * GetSystemMetrics(SM_CXHSCROLL);
1030 if (infoPtr->dwStyle & TCS_BOTTOM)
1032 controlPos.top = clientRect->bottom - infoPtr->tabHeight;
1033 controlPos.bottom = controlPos.top + GetSystemMetrics(SM_CYHSCROLL);
1035 else
1037 controlPos.bottom = clientRect->top + infoPtr->tabHeight;
1038 controlPos.top = controlPos.bottom - GetSystemMetrics(SM_CYHSCROLL);
1041 else
1043 controlPos.right = clientRect->right;
1044 controlPos.left = controlPos.right - 2 * GetSystemMetrics(SM_CXHSCROLL);
1046 if (infoPtr->dwStyle & TCS_BOTTOM)
1048 controlPos.top = clientRect->bottom - infoPtr->tabHeight;
1049 controlPos.bottom = controlPos.top + GetSystemMetrics(SM_CYHSCROLL);
1051 else
1053 controlPos.bottom = clientRect->top + infoPtr->tabHeight;
1054 controlPos.top = controlPos.bottom - GetSystemMetrics(SM_CYHSCROLL);
1059 * If we don't have a scroll control yet, we want to create one.
1060 * If we have one, we want to make sure it's positioned properly.
1062 if (infoPtr->hwndUpDown==0)
1064 infoPtr->hwndUpDown = CreateWindowW(UPDOWN_CLASSW, emptyW,
1065 WS_VISIBLE | WS_CHILD | UDS_HORZ,
1066 controlPos.left, controlPos.top,
1067 controlPos.right - controlPos.left,
1068 controlPos.bottom - controlPos.top,
1069 infoPtr->hwnd, NULL, NULL, NULL);
1071 else
1073 SetWindowPos(infoPtr->hwndUpDown,
1074 NULL,
1075 controlPos.left, controlPos.top,
1076 controlPos.right - controlPos.left,
1077 controlPos.bottom - controlPos.top,
1078 SWP_SHOWWINDOW | SWP_NOZORDER);
1081 /* Now calculate upper limit of the updown control range.
1082 * We do this by calculating how many tabs will be offscreen when the
1083 * last tab is visible.
1085 if(infoPtr->uNumItem)
1087 vsize = clientRect->right - (controlPos.right - controlPos.left + 1);
1088 maxRange = infoPtr->uNumItem;
1089 tabwidth = TAB_GetItem(infoPtr, infoPtr->uNumItem - 1)->rect.right;
1091 for(; maxRange > 0; maxRange--)
1093 if(tabwidth - TAB_GetItem(infoPtr,maxRange - 1)->rect.left > vsize)
1094 break;
1097 if(maxRange == infoPtr->uNumItem)
1098 maxRange--;
1101 else
1103 /* If we once had a scroll control... hide it */
1104 if (infoPtr->hwndUpDown)
1105 ShowWindow(infoPtr->hwndUpDown, SW_HIDE);
1107 if (infoPtr->hwndUpDown)
1108 SendMessageW(infoPtr->hwndUpDown, UDM_SETRANGE32, 0, maxRange);
1111 /******************************************************************************
1112 * TAB_SetItemBounds
1114 * This method will calculate the position rectangles of all the items in the
1115 * control. The rectangle calculated starts at 0 for the first item in the
1116 * list and ignores scrolling and selection.
1117 * It also uses the current font to determine the height of the tab row and
1118 * it checks if all the tabs fit in the client area of the window. If they
1119 * don't, a scrolling control is added.
1121 static void TAB_SetItemBounds (TAB_INFO *infoPtr)
1123 TEXTMETRICW fontMetrics;
1124 UINT curItem;
1125 INT curItemLeftPos;
1126 INT curItemRowCount;
1127 HFONT hFont, hOldFont;
1128 HDC hdc;
1129 RECT clientRect;
1130 INT iTemp;
1131 RECT* rcItem;
1132 INT iIndex;
1133 INT icon_width = 0;
1136 * We need to get text information so we need a DC and we need to select
1137 * a font.
1139 hdc = GetDC(infoPtr->hwnd);
1141 hFont = infoPtr->hFont ? infoPtr->hFont : GetStockObject (SYSTEM_FONT);
1142 hOldFont = SelectObject (hdc, hFont);
1145 * We will base the rectangle calculations on the client rectangle
1146 * of the control.
1148 GetClientRect(infoPtr->hwnd, &clientRect);
1150 /* if TCS_VERTICAL then swap the height and width so this code places the
1151 tabs along the top of the rectangle and we can just rotate them after
1152 rather than duplicate all of the below code */
1153 if(infoPtr->dwStyle & TCS_VERTICAL)
1155 iTemp = clientRect.bottom;
1156 clientRect.bottom = clientRect.right;
1157 clientRect.right = iTemp;
1160 /* Now use hPadding and vPadding */
1161 infoPtr->uHItemPadding = infoPtr->uHItemPadding_s;
1162 infoPtr->uVItemPadding = infoPtr->uVItemPadding_s;
1164 /* The leftmost item will be "0" aligned */
1165 curItemLeftPos = 0;
1166 curItemRowCount = infoPtr->uNumItem ? 1 : 0;
1168 if (!(infoPtr->fHeightSet))
1170 int item_height;
1171 INT icon_height = 0, cx;
1173 /* Use the current font to determine the height of a tab. */
1174 GetTextMetricsW(hdc, &fontMetrics);
1176 /* Get the icon height */
1177 if (infoPtr->himl)
1178 ImageList_GetIconSize(infoPtr->himl, &cx, &icon_height);
1180 /* Take the highest between font or icon */
1181 if (fontMetrics.tmHeight > icon_height)
1182 item_height = fontMetrics.tmHeight + 2;
1183 else
1184 item_height = icon_height;
1187 * Make sure there is enough space for the letters + icon + growing the
1188 * selected item + extra space for the selected item.
1190 infoPtr->tabHeight = item_height +
1191 ((infoPtr->dwStyle & TCS_BUTTONS) ? 2 : 1) *
1192 infoPtr->uVItemPadding;
1194 TRACE("tabH=%d, tmH=%d, iconh=%d\n",
1195 infoPtr->tabHeight, fontMetrics.tmHeight, icon_height);
1198 TRACE("client right=%d\n", clientRect.right);
1200 /* Get the icon width */
1201 if (infoPtr->himl)
1203 INT cy;
1205 ImageList_GetIconSize(infoPtr->himl, &icon_width, &cy);
1207 if (infoPtr->dwStyle & TCS_FIXEDWIDTH)
1208 icon_width += 4;
1209 else
1210 /* Add padding if icon is present */
1211 icon_width += infoPtr->uHItemPadding;
1214 for (curItem = 0; curItem < infoPtr->uNumItem; curItem++)
1216 TAB_ITEM *curr = TAB_GetItem(infoPtr, curItem);
1218 /* Set the leftmost position of the tab. */
1219 curr->rect.left = curItemLeftPos;
1221 if (infoPtr->dwStyle & TCS_FIXEDWIDTH)
1223 curr->rect.right = curr->rect.left +
1224 max(infoPtr->tabWidth, icon_width);
1226 else if (!curr->pszText)
1228 /* If no text use minimum tab width including padding. */
1229 if (infoPtr->tabMinWidth < 0)
1230 curr->rect.right = curr->rect.left + GET_DEFAULT_MIN_TAB_WIDTH(infoPtr);
1231 else
1233 curr->rect.right = curr->rect.left + infoPtr->tabMinWidth;
1235 /* Add extra padding if icon is present */
1236 if (infoPtr->himl && infoPtr->tabMinWidth > 0 && infoPtr->tabMinWidth < DEFAULT_MIN_TAB_WIDTH
1237 && infoPtr->uHItemPadding > 1)
1238 curr->rect.right += EXTRA_ICON_PADDING * (infoPtr->uHItemPadding-1);
1241 else
1243 int tabwidth;
1244 SIZE size;
1245 /* Calculate how wide the tab is depending on the text it contains */
1246 GetTextExtentPoint32W(hdc, curr->pszText,
1247 lstrlenW(curr->pszText), &size);
1249 tabwidth = size.cx + icon_width + 2 * infoPtr->uHItemPadding;
1251 if (infoPtr->tabMinWidth < 0)
1252 tabwidth = max(tabwidth, GET_DEFAULT_MIN_TAB_WIDTH(infoPtr));
1253 else
1254 tabwidth = max(tabwidth, infoPtr->tabMinWidth);
1256 curr->rect.right = curr->rect.left + tabwidth;
1257 TRACE("for <%s>, l,r=%d,%d\n",
1258 debugstr_w(curr->pszText), curr->rect.left, curr->rect.right);
1262 * Check if this is a multiline tab control and if so
1263 * check to see if we should wrap the tabs
1265 * Wrap all these tabs. We will arrange them evenly later.
1269 if (((infoPtr->dwStyle & TCS_MULTILINE) || (infoPtr->dwStyle & TCS_VERTICAL)) &&
1270 (curr->rect.right >
1271 (clientRect.right - CONTROL_BORDER_SIZEX - DISPLAY_AREA_PADDINGX)))
1273 curr->rect.right -= curr->rect.left;
1275 curr->rect.left = 0;
1276 curItemRowCount++;
1277 TRACE("wrapping <%s>, l,r=%d,%d\n", debugstr_w(curr->pszText),
1278 curr->rect.left, curr->rect.right);
1281 curr->rect.bottom = 0;
1282 curr->rect.top = curItemRowCount - 1;
1284 TRACE("Rect: %s\n", wine_dbgstr_rect(&curr->rect));
1287 * The leftmost position of the next item is the rightmost position
1288 * of this one.
1290 if (infoPtr->dwStyle & TCS_BUTTONS)
1292 curItemLeftPos = curr->rect.right + BUTTON_SPACINGX;
1293 if (infoPtr->dwStyle & TCS_FLATBUTTONS)
1294 curItemLeftPos += FLAT_BTN_SPACINGX;
1296 else
1297 curItemLeftPos = curr->rect.right;
1300 if (!((infoPtr->dwStyle & TCS_MULTILINE) || (infoPtr->dwStyle & TCS_VERTICAL)))
1303 * Check if we need a scrolling control.
1305 infoPtr->needsScrolling = (curItemLeftPos + (2 * SELECTED_TAB_OFFSET) >
1306 clientRect.right);
1308 /* Don't need scrolling, then update infoPtr->leftmostVisible */
1309 if(!infoPtr->needsScrolling)
1310 infoPtr->leftmostVisible = 0;
1312 else
1315 * No scrolling in Multiline or Vertical styles.
1317 infoPtr->needsScrolling = FALSE;
1318 infoPtr->leftmostVisible = 0;
1320 TAB_SetupScrolling(infoPtr, &clientRect);
1322 /* Set the number of rows */
1323 infoPtr->uNumRows = curItemRowCount;
1325 /* Arrange all tabs evenly if style says so */
1326 if (!(infoPtr->dwStyle & TCS_RAGGEDRIGHT) &&
1327 ((infoPtr->dwStyle & TCS_MULTILINE) || (infoPtr->dwStyle & TCS_VERTICAL)) &&
1328 (infoPtr->uNumItem > 0) &&
1329 (infoPtr->uNumRows > 1))
1331 INT tabPerRow,remTab,iRow;
1332 UINT iItm;
1333 INT iCount=0;
1336 * Ok windows tries to even out the rows. place the same
1337 * number of tabs in each row. So lets give that a shot
1340 tabPerRow = infoPtr->uNumItem / (infoPtr->uNumRows);
1341 remTab = infoPtr->uNumItem % (infoPtr->uNumRows);
1343 for (iItm=0,iRow=0,iCount=0,curItemLeftPos=0;
1344 iItm<infoPtr->uNumItem;
1345 iItm++,iCount++)
1347 /* normalize the current rect */
1348 TAB_ITEM *curr = TAB_GetItem(infoPtr, iItm);
1350 /* shift the item to the left side of the clientRect */
1351 curr->rect.right -= curr->rect.left;
1352 curr->rect.left = 0;
1354 TRACE("r=%d, cl=%d, cl.r=%d, iCount=%d, iRow=%d, uNumRows=%d, remTab=%d, tabPerRow=%d\n",
1355 curr->rect.right, curItemLeftPos, clientRect.right,
1356 iCount, iRow, infoPtr->uNumRows, remTab, tabPerRow);
1358 /* if we have reached the maximum number of tabs on this row */
1359 /* move to the next row, reset our current item left position and */
1360 /* the count of items on this row */
1362 if (infoPtr->dwStyle & TCS_VERTICAL) {
1363 /* Vert: Add the remaining tabs in the *last* remainder rows */
1364 if (iCount >= ((iRow>=(INT)infoPtr->uNumRows - remTab)?tabPerRow + 1:tabPerRow)) {
1365 iRow++;
1366 curItemLeftPos = 0;
1367 iCount = 0;
1369 } else {
1370 /* Horz: Add the remaining tabs in the *first* remainder rows */
1371 if (iCount >= ((iRow<remTab)?tabPerRow + 1:tabPerRow)) {
1372 iRow++;
1373 curItemLeftPos = 0;
1374 iCount = 0;
1378 /* shift the item to the right to place it as the next item in this row */
1379 curr->rect.left += curItemLeftPos;
1380 curr->rect.right += curItemLeftPos;
1381 curr->rect.top = iRow;
1382 if (infoPtr->dwStyle & TCS_BUTTONS)
1384 curItemLeftPos = curr->rect.right + 1;
1385 if (infoPtr->dwStyle & TCS_FLATBUTTONS)
1386 curItemLeftPos += FLAT_BTN_SPACINGX;
1388 else
1389 curItemLeftPos = curr->rect.right;
1391 TRACE("arranging <%s>, l,r=%d,%d, row=%d\n",
1392 debugstr_w(curr->pszText), curr->rect.left,
1393 curr->rect.right, curr->rect.top);
1397 * Justify the rows
1400 INT widthDiff, iIndexStart=0, iIndexEnd=0;
1401 INT remainder;
1402 INT iCount=0;
1404 while(iIndexStart < infoPtr->uNumItem)
1406 TAB_ITEM *start = TAB_GetItem(infoPtr, iIndexStart);
1409 * find the index of the row
1411 /* find the first item on the next row */
1412 for (iIndexEnd=iIndexStart;
1413 (iIndexEnd < infoPtr->uNumItem) &&
1414 (TAB_GetItem(infoPtr, iIndexEnd)->rect.top ==
1415 start->rect.top) ;
1416 iIndexEnd++)
1417 /* intentionally blank */;
1420 * we need to justify these tabs so they fill the whole given
1421 * client area
1424 /* find the amount of space remaining on this row */
1425 widthDiff = clientRect.right - (2 * SELECTED_TAB_OFFSET) -
1426 TAB_GetItem(infoPtr, iIndexEnd - 1)->rect.right;
1428 /* iCount is the number of tab items on this row */
1429 iCount = iIndexEnd - iIndexStart;
1431 if (iCount > 1)
1433 remainder = widthDiff % iCount;
1434 widthDiff = widthDiff / iCount;
1435 /* add widthDiff/iCount, or extra space/items on row, to each item on this row */
1436 for (iIndex=iIndexStart, iCount=0; iIndex < iIndexEnd; iIndex++, iCount++)
1438 TAB_ITEM *item = TAB_GetItem(infoPtr, iIndex);
1440 item->rect.left += iCount * widthDiff;
1441 item->rect.right += (iCount + 1) * widthDiff;
1443 TRACE("adjusting 1 <%s>, l,r=%d,%d\n",
1444 debugstr_w(item->pszText),
1445 item->rect.left, item->rect.right);
1448 TAB_GetItem(infoPtr, iIndex - 1)->rect.right += remainder;
1450 else /* we have only one item on this row, make it take up the entire row */
1452 start->rect.left = clientRect.left;
1453 start->rect.right = clientRect.right - 4;
1455 TRACE("adjusting 2 <%s>, l,r=%d,%d\n",
1456 debugstr_w(start->pszText),
1457 start->rect.left, start->rect.right);
1462 iIndexStart = iIndexEnd;
1467 /* if TCS_VERTICAL rotate the tabs so they are along the side of the clientRect */
1468 if(infoPtr->dwStyle & TCS_VERTICAL)
1470 RECT rcOriginal;
1471 for(iIndex = 0; iIndex < infoPtr->uNumItem; iIndex++)
1473 rcItem = &TAB_GetItem(infoPtr, iIndex)->rect;
1475 rcOriginal = *rcItem;
1477 /* this is rotating the items by 90 degrees clockwise around the center of the control */
1478 rcItem->top = (rcOriginal.left - clientRect.left);
1479 rcItem->bottom = rcItem->top + (rcOriginal.right - rcOriginal.left);
1480 rcItem->left = rcOriginal.top;
1481 rcItem->right = rcOriginal.bottom;
1485 TAB_EnsureSelectionVisible(infoPtr);
1486 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
1488 /* Cleanup */
1489 SelectObject (hdc, hOldFont);
1490 ReleaseDC (infoPtr->hwnd, hdc);
1494 static void
1495 TAB_EraseTabInterior(const TAB_INFO *infoPtr, HDC hdc, INT iItem, const RECT *drawRect)
1497 HBRUSH hbr = CreateSolidBrush (comctl32_color.clrBtnFace);
1498 BOOL deleteBrush = TRUE;
1499 RECT rTemp = *drawRect;
1501 if (infoPtr->dwStyle & TCS_BUTTONS)
1503 if (iItem == infoPtr->iSelected)
1505 /* Background color */
1506 if (!(infoPtr->dwStyle & TCS_OWNERDRAWFIXED))
1508 DeleteObject(hbr);
1509 hbr = GetSysColorBrush(COLOR_SCROLLBAR);
1511 SetTextColor(hdc, comctl32_color.clr3dFace);
1512 SetBkColor(hdc, comctl32_color.clr3dHilight);
1514 /* if COLOR_WINDOW happens to be the same as COLOR_3DHILIGHT
1515 * we better use 0x55aa bitmap brush to make scrollbar's background
1516 * look different from the window background.
1518 if (comctl32_color.clr3dHilight == comctl32_color.clrWindow)
1519 hbr = COMCTL32_hPattern55AABrush;
1521 deleteBrush = FALSE;
1523 FillRect(hdc, &rTemp, hbr);
1525 else /* ! selected */
1527 if (infoPtr->dwStyle & TCS_FLATBUTTONS)
1529 InflateRect(&rTemp, 2, 2);
1530 FillRect(hdc, &rTemp, hbr);
1531 if (iItem == infoPtr->iHotTracked ||
1532 (iItem != infoPtr->iSelected && iItem == infoPtr->uFocus))
1533 DrawEdge(hdc, &rTemp, BDR_RAISEDINNER, BF_RECT);
1535 else
1536 FillRect(hdc, &rTemp, hbr);
1540 else /* !TCS_BUTTONS */
1542 InflateRect(&rTemp, -2, -2);
1543 if (!GetWindowTheme (infoPtr->hwnd))
1544 FillRect(hdc, &rTemp, hbr);
1547 /* highlighting is drawn on top of previous fills */
1548 if (TAB_GetItem(infoPtr, iItem)->dwState & TCIS_HIGHLIGHTED)
1550 if (deleteBrush)
1552 DeleteObject(hbr);
1553 deleteBrush = FALSE;
1555 hbr = GetSysColorBrush(COLOR_HIGHLIGHT);
1556 FillRect(hdc, &rTemp, hbr);
1559 /* Cleanup */
1560 if (deleteBrush) DeleteObject(hbr);
1563 /******************************************************************************
1564 * TAB_DrawItemInterior
1566 * This method is used to draw the interior (text and icon) of a single tab
1567 * into the tab control.
1569 static void
1570 TAB_DrawItemInterior(const TAB_INFO *infoPtr, HDC hdc, INT iItem, RECT *drawRect)
1572 RECT localRect;
1574 HPEN htextPen;
1575 HPEN holdPen;
1576 INT oldBkMode;
1577 HFONT hOldFont;
1579 /* if (drawRect == NULL) */
1581 BOOL isVisible;
1582 RECT itemRect;
1583 RECT selectedRect;
1586 * Get the rectangle for the item.
1588 isVisible = TAB_InternalGetItemRect(infoPtr, iItem, &itemRect, &selectedRect);
1589 if (!isVisible)
1590 return;
1593 * Make sure drawRect points to something valid; simplifies code.
1595 drawRect = &localRect;
1598 * This logic copied from the part of TAB_DrawItem which draws
1599 * the tab background. It's important to keep it in sync. I
1600 * would have liked to avoid code duplication, but couldn't figure
1601 * out how without making spaghetti of TAB_DrawItem.
1603 if (iItem == infoPtr->iSelected)
1604 *drawRect = selectedRect;
1605 else
1606 *drawRect = itemRect;
1608 if (infoPtr->dwStyle & TCS_BUTTONS)
1610 if (iItem == infoPtr->iSelected)
1612 drawRect->left += 4;
1613 drawRect->top += 4;
1614 drawRect->right -= 4;
1616 if (infoPtr->dwStyle & TCS_VERTICAL)
1618 if (!(infoPtr->dwStyle & TCS_BOTTOM)) drawRect->right += 1;
1619 drawRect->bottom -= 4;
1621 else
1623 if (infoPtr->dwStyle & TCS_BOTTOM)
1625 drawRect->top -= 2;
1626 drawRect->bottom -= 4;
1628 else
1629 drawRect->bottom -= 1;
1632 else
1634 drawRect->left += 2;
1635 drawRect->top += 2;
1636 drawRect->right -= 2;
1637 drawRect->bottom -= 2;
1640 else
1642 if ((infoPtr->dwStyle & TCS_VERTICAL) && (infoPtr->dwStyle & TCS_BOTTOM))
1644 if (iItem != infoPtr->iSelected)
1646 drawRect->left += 2;
1647 drawRect->top += 2;
1648 drawRect->bottom -= 2;
1651 else if (infoPtr->dwStyle & TCS_VERTICAL)
1653 if (iItem == infoPtr->iSelected)
1655 drawRect->right += 1;
1657 else
1659 drawRect->top += 2;
1660 drawRect->right -= 2;
1661 drawRect->bottom -= 2;
1664 else if (infoPtr->dwStyle & TCS_BOTTOM)
1666 if (iItem == infoPtr->iSelected)
1668 drawRect->top -= 2;
1670 else
1672 InflateRect(drawRect, -2, -2);
1673 drawRect->bottom += 2;
1676 else
1678 if (iItem == infoPtr->iSelected)
1680 drawRect->bottom += 3;
1682 else
1684 drawRect->bottom -= 2;
1685 InflateRect(drawRect, -2, 0);
1690 TRACE("drawRect=(%s)\n", wine_dbgstr_rect(drawRect));
1692 /* Clear interior */
1693 TAB_EraseTabInterior (infoPtr, hdc, iItem, drawRect);
1695 /* Draw the focus rectangle */
1696 if (!(infoPtr->dwStyle & TCS_FOCUSNEVER) &&
1697 (GetFocus() == infoPtr->hwnd) &&
1698 (iItem == infoPtr->uFocus) )
1700 RECT rFocus = *drawRect;
1702 if (!(infoPtr->dwStyle & TCS_BUTTONS)) InflateRect(&rFocus, -3, -3);
1703 if (infoPtr->dwStyle & TCS_BOTTOM && !(infoPtr->dwStyle & TCS_VERTICAL))
1704 rFocus.top -= 3;
1706 /* focus should stay on selected item for TCS_BUTTONS style */
1707 if (!((infoPtr->dwStyle & TCS_BUTTONS) && (infoPtr->iSelected != iItem)))
1708 DrawFocusRect(hdc, &rFocus);
1712 * Text pen
1714 htextPen = CreatePen( PS_SOLID, 1, comctl32_color.clrBtnText );
1715 holdPen = SelectObject(hdc, htextPen);
1716 hOldFont = SelectObject(hdc, infoPtr->hFont);
1719 * Setup for text output
1721 oldBkMode = SetBkMode(hdc, TRANSPARENT);
1722 if (!GetWindowTheme (infoPtr->hwnd) || (infoPtr->dwStyle & TCS_BUTTONS))
1724 if ((infoPtr->dwStyle & TCS_HOTTRACK) && (iItem == infoPtr->iHotTracked) &&
1725 !(infoPtr->dwStyle & TCS_FLATBUTTONS))
1726 SetTextColor(hdc, comctl32_color.clrHighlight);
1727 else if (TAB_GetItem(infoPtr, iItem)->dwState & TCIS_HIGHLIGHTED)
1728 SetTextColor(hdc, comctl32_color.clrHighlightText);
1729 else
1730 SetTextColor(hdc, comctl32_color.clrBtnText);
1734 * if owner draw, tell the owner to draw
1736 if ((infoPtr->dwStyle & TCS_OWNERDRAWFIXED) && IsWindow(infoPtr->hwndNotify))
1738 DRAWITEMSTRUCT dis;
1739 UINT id;
1741 drawRect->top += 2;
1742 drawRect->right -= 1;
1743 if ( iItem == infoPtr->iSelected )
1745 drawRect->right -= 1;
1746 drawRect->left += 1;
1749 id = (UINT)GetWindowLongPtrW( infoPtr->hwnd, GWLP_ID );
1751 /* fill DRAWITEMSTRUCT */
1752 dis.CtlType = ODT_TAB;
1753 dis.CtlID = id;
1754 dis.itemID = iItem;
1755 dis.itemAction = ODA_DRAWENTIRE;
1756 dis.itemState = 0;
1757 if ( iItem == infoPtr->iSelected )
1758 dis.itemState |= ODS_SELECTED;
1759 if (infoPtr->uFocus == iItem)
1760 dis.itemState |= ODS_FOCUS;
1761 dis.hwndItem = infoPtr->hwnd;
1762 dis.hDC = hdc;
1763 CopyRect(&dis.rcItem,drawRect);
1765 /* when extra data fits ULONG_PTR, store it directly */
1766 if (infoPtr->cbInfo > sizeof(LPARAM))
1767 dis.itemData = (ULONG_PTR) TAB_GetItem(infoPtr, iItem)->extra;
1768 else
1770 /* this could be considered broken on 64 bit, but that's how it works -
1771 only first 4 bytes are copied */
1772 dis.itemData = 0;
1773 memcpy(&dis.itemData, (ULONG_PTR*)TAB_GetItem(infoPtr, iItem)->extra, 4);
1776 /* draw notification */
1777 SendMessageW( infoPtr->hwndNotify, WM_DRAWITEM, id, (LPARAM)&dis );
1779 else
1781 TAB_ITEM *item = TAB_GetItem(infoPtr, iItem);
1782 RECT rcTemp;
1783 RECT rcImage;
1785 /* used to center the icon and text in the tab */
1786 RECT rcText;
1787 INT center_offset_h, center_offset_v;
1789 /* set rcImage to drawRect, we will use top & left in our ImageList_Draw call */
1790 rcImage = *drawRect;
1792 rcTemp = *drawRect;
1794 rcText.left = rcText.top = rcText.right = rcText.bottom = 0;
1796 /* get the rectangle that the text fits in */
1797 if (item->pszText)
1799 DrawTextW(hdc, item->pszText, -1, &rcText, DT_CALCRECT);
1802 * If not owner draw, then do the drawing ourselves.
1804 * Draw the icon.
1806 if (infoPtr->himl && item->iImage != -1)
1808 INT cx;
1809 INT cy;
1811 ImageList_GetIconSize(infoPtr->himl, &cx, &cy);
1813 if(infoPtr->dwStyle & TCS_VERTICAL)
1815 center_offset_h = ((drawRect->bottom - drawRect->top) - (cy + infoPtr->uHItemPadding + (rcText.right - rcText.left))) / 2;
1816 center_offset_v = ((drawRect->right - drawRect->left) - cx) / 2;
1818 else
1820 center_offset_h = ((drawRect->right - drawRect->left) - (cx + infoPtr->uHItemPadding + (rcText.right - rcText.left))) / 2;
1821 center_offset_v = ((drawRect->bottom - drawRect->top) - cy) / 2;
1824 /* if an item is selected, the icon is shifted up instead of down */
1825 if (iItem == infoPtr->iSelected)
1826 center_offset_v -= infoPtr->uVItemPadding / 2;
1827 else
1828 center_offset_v += infoPtr->uVItemPadding / 2;
1830 if (infoPtr->dwStyle & TCS_FIXEDWIDTH && infoPtr->dwStyle & (TCS_FORCELABELLEFT | TCS_FORCEICONLEFT))
1831 center_offset_h = infoPtr->uHItemPadding;
1833 if (center_offset_h < 2)
1834 center_offset_h = 2;
1836 if (center_offset_v < 0)
1837 center_offset_v = 0;
1839 TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%s), textlen=%d\n",
1840 debugstr_w(item->pszText), center_offset_h, center_offset_v,
1841 wine_dbgstr_rect(drawRect), (rcText.right-rcText.left));
1843 if((infoPtr->dwStyle & TCS_VERTICAL) && (infoPtr->dwStyle & TCS_BOTTOM))
1845 rcImage.top = drawRect->top + center_offset_h;
1846 /* if tab is TCS_VERTICAL and TCS_BOTTOM, the text is drawn from the */
1847 /* right side of the tab, but the image still uses the left as its x position */
1848 /* this keeps the image always drawn off of the same side of the tab */
1849 rcImage.left = drawRect->right - cx - center_offset_v;
1850 drawRect->top += cy + infoPtr->uHItemPadding;
1852 else if(infoPtr->dwStyle & TCS_VERTICAL)
1854 rcImage.top = drawRect->bottom - cy - center_offset_h;
1855 rcImage.left = drawRect->left + center_offset_v;
1856 drawRect->bottom -= cy + infoPtr->uHItemPadding;
1858 else /* normal style, whether TCS_BOTTOM or not */
1860 rcImage.left = drawRect->left + center_offset_h;
1861 rcImage.top = drawRect->top + center_offset_v;
1862 drawRect->left += cx + infoPtr->uHItemPadding;
1865 TRACE("drawing image=%d, left=%d, top=%d\n",
1866 item->iImage, rcImage.left, rcImage.top-1);
1867 ImageList_Draw
1869 infoPtr->himl,
1870 item->iImage,
1871 hdc,
1872 rcImage.left,
1873 rcImage.top,
1874 ILD_NORMAL
1878 /* Now position text */
1879 if (infoPtr->dwStyle & TCS_FIXEDWIDTH && infoPtr->dwStyle & TCS_FORCELABELLEFT)
1880 center_offset_h = infoPtr->uHItemPadding;
1881 else
1882 if(infoPtr->dwStyle & TCS_VERTICAL)
1883 center_offset_h = ((drawRect->bottom - drawRect->top) - (rcText.right - rcText.left)) / 2;
1884 else
1885 center_offset_h = ((drawRect->right - drawRect->left) - (rcText.right - rcText.left)) / 2;
1887 if(infoPtr->dwStyle & TCS_VERTICAL)
1889 if(infoPtr->dwStyle & TCS_BOTTOM)
1890 drawRect->top+=center_offset_h;
1891 else
1892 drawRect->bottom-=center_offset_h;
1894 center_offset_v = ((drawRect->right - drawRect->left) - (rcText.bottom - rcText.top)) / 2;
1896 else
1898 drawRect->left += center_offset_h;
1899 center_offset_v = ((drawRect->bottom - drawRect->top) - (rcText.bottom - rcText.top)) / 2;
1902 /* if an item is selected, the text is shifted up instead of down */
1903 if (iItem == infoPtr->iSelected)
1904 center_offset_v -= infoPtr->uVItemPadding / 2;
1905 else
1906 center_offset_v += infoPtr->uVItemPadding / 2;
1908 if (center_offset_v < 0)
1909 center_offset_v = 0;
1911 if(infoPtr->dwStyle & TCS_VERTICAL)
1912 drawRect->left += center_offset_v;
1913 else
1914 drawRect->top += center_offset_v;
1916 /* Draw the text */
1917 if(infoPtr->dwStyle & TCS_VERTICAL) /* if we are vertical rotate the text and each character */
1919 static const WCHAR ArialW[] = { 'A','r','i','a','l',0 };
1920 LOGFONTW logfont;
1921 HFONT hFont = 0;
1922 INT nEscapement = 900;
1923 INT nOrientation = 900;
1925 if(infoPtr->dwStyle & TCS_BOTTOM)
1927 nEscapement = -900;
1928 nOrientation = -900;
1931 /* to get a font with the escapement and orientation we are looking for, we need to */
1932 /* call CreateFontIndirectA, which requires us to set the values of the logfont we pass in */
1933 if (!GetObjectW((infoPtr->hFont) ?
1934 infoPtr->hFont : GetStockObject(SYSTEM_FONT),
1935 sizeof(LOGFONTW),&logfont))
1937 INT iPointSize = 9;
1939 lstrcpyW(logfont.lfFaceName, ArialW);
1940 logfont.lfHeight = -MulDiv(iPointSize, GetDeviceCaps(hdc, LOGPIXELSY),
1941 72);
1942 logfont.lfWeight = FW_NORMAL;
1943 logfont.lfItalic = 0;
1944 logfont.lfUnderline = 0;
1945 logfont.lfStrikeOut = 0;
1948 logfont.lfEscapement = nEscapement;
1949 logfont.lfOrientation = nOrientation;
1950 hFont = CreateFontIndirectW(&logfont);
1951 SelectObject(hdc, hFont);
1953 if (item->pszText)
1955 ExtTextOutW(hdc,
1956 (infoPtr->dwStyle & TCS_BOTTOM) ? drawRect->right : drawRect->left,
1957 (!(infoPtr->dwStyle & TCS_BOTTOM)) ? drawRect->bottom : drawRect->top,
1958 ETO_CLIPPED,
1959 drawRect,
1960 item->pszText,
1961 lstrlenW(item->pszText),
1965 DeleteObject(hFont);
1967 else
1969 TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%s), textlen=%d\n",
1970 debugstr_w(item->pszText), center_offset_h, center_offset_v,
1971 wine_dbgstr_rect(drawRect), (rcText.right-rcText.left));
1972 if (item->pszText)
1974 DrawTextW
1976 hdc,
1977 item->pszText,
1978 lstrlenW(item->pszText),
1979 drawRect,
1980 DT_LEFT | DT_SINGLELINE
1985 *drawRect = rcTemp; /* restore drawRect */
1989 * Cleanup
1991 SelectObject(hdc, hOldFont);
1992 SetBkMode(hdc, oldBkMode);
1993 SelectObject(hdc, holdPen);
1994 DeleteObject( htextPen );
1997 /******************************************************************************
1998 * TAB_DrawItem
2000 * This method is used to draw a single tab into the tab control.
2002 static void TAB_DrawItem(const TAB_INFO *infoPtr, HDC hdc, INT iItem)
2004 RECT itemRect;
2005 RECT selectedRect;
2006 BOOL isVisible;
2007 RECT r, fillRect, r1;
2008 INT clRight = 0;
2009 INT clBottom = 0;
2010 COLORREF bkgnd, corner;
2011 HTHEME theme;
2014 * Get the rectangle for the item.
2016 isVisible = TAB_InternalGetItemRect(infoPtr,
2017 iItem,
2018 &itemRect,
2019 &selectedRect);
2021 if (isVisible)
2023 RECT rUD, rC;
2025 /* Clip UpDown control to not draw over it */
2026 if (infoPtr->needsScrolling)
2028 GetWindowRect(infoPtr->hwnd, &rC);
2029 GetWindowRect(infoPtr->hwndUpDown, &rUD);
2030 ExcludeClipRect(hdc, rUD.left - rC.left, rUD.top - rC.top, rUD.right - rC.left, rUD.bottom - rC.top);
2033 /* If you need to see what the control is doing,
2034 * then override these variables. They will change what
2035 * fill colors are used for filling the tabs, and the
2036 * corners when drawing the edge.
2038 bkgnd = comctl32_color.clrBtnFace;
2039 corner = comctl32_color.clrBtnFace;
2041 if (infoPtr->dwStyle & TCS_BUTTONS)
2043 /* Get item rectangle */
2044 r = itemRect;
2046 /* Separators between flat buttons */
2047 if ((infoPtr->dwStyle & TCS_FLATBUTTONS) && (infoPtr->exStyle & TCS_EX_FLATSEPARATORS))
2049 r1 = r;
2050 r1.right += (FLAT_BTN_SPACINGX -2);
2051 DrawEdge(hdc, &r1, EDGE_ETCHED, BF_RIGHT);
2054 if (iItem == infoPtr->iSelected)
2056 DrawEdge(hdc, &r, EDGE_SUNKEN, BF_SOFT|BF_RECT);
2058 OffsetRect(&r, 1, 1);
2060 else /* ! selected */
2062 DWORD state = TAB_GetItem(infoPtr, iItem)->dwState;
2064 if ((state & TCIS_BUTTONPRESSED) || (iItem == infoPtr->uFocus))
2065 DrawEdge(hdc, &r, EDGE_SUNKEN, BF_SOFT|BF_RECT);
2066 else
2067 if (!(infoPtr->dwStyle & TCS_FLATBUTTONS))
2068 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_RECT);
2071 else /* !TCS_BUTTONS */
2073 /* We draw a rectangle of different sizes depending on the selection
2074 * state. */
2075 if (iItem == infoPtr->iSelected) {
2076 RECT rect;
2077 GetClientRect (infoPtr->hwnd, &rect);
2078 clRight = rect.right;
2079 clBottom = rect.bottom;
2080 r = selectedRect;
2082 else
2083 r = itemRect;
2086 * Erase the background. (Delay it but setup rectangle.)
2087 * This is necessary when drawing the selected item since it is larger
2088 * than the others, it might overlap with stuff already drawn by the
2089 * other tabs
2091 fillRect = r;
2093 /* Draw themed tabs - but only if they are at the top.
2094 * Windows draws even side or bottom tabs themed, with wacky results.
2095 * However, since in Wine apps may get themed that did not opt in via
2096 * a manifest avoid theming when we know the result will be wrong */
2097 if ((theme = GetWindowTheme (infoPtr->hwnd))
2098 && ((infoPtr->dwStyle & (TCS_VERTICAL | TCS_BOTTOM)) == 0))
2100 static const int partIds[8] = {
2101 /* Normal item */
2102 TABP_TABITEM,
2103 TABP_TABITEMLEFTEDGE,
2104 TABP_TABITEMRIGHTEDGE,
2105 TABP_TABITEMBOTHEDGE,
2106 /* Selected tab */
2107 TABP_TOPTABITEM,
2108 TABP_TOPTABITEMLEFTEDGE,
2109 TABP_TOPTABITEMRIGHTEDGE,
2110 TABP_TOPTABITEMBOTHEDGE,
2112 int partIndex = 0;
2113 int stateId = TIS_NORMAL;
2115 /* selected and unselected tabs have different parts */
2116 if (iItem == infoPtr->iSelected)
2117 partIndex += 4;
2118 /* The part also differs on the position of a tab on a line.
2119 * "Visually" determining the position works well enough. */
2120 GetClientRect(infoPtr->hwnd, &r1);
2121 if(selectedRect.left == 0)
2122 partIndex += 1;
2123 if(selectedRect.right == r1.right)
2124 partIndex += 2;
2126 if (iItem == infoPtr->iSelected)
2127 stateId = TIS_SELECTED;
2128 else if (iItem == infoPtr->iHotTracked)
2129 stateId = TIS_HOT;
2130 else if (iItem == infoPtr->uFocus)
2131 stateId = TIS_FOCUSED;
2133 /* Adjust rectangle for bottommost row */
2134 if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2135 r.bottom += 3;
2137 DrawThemeBackground (theme, hdc, partIds[partIndex], stateId, &r, NULL);
2138 GetThemeBackgroundContentRect (theme, hdc, partIds[partIndex], stateId, &r, &r);
2140 else if(infoPtr->dwStyle & TCS_VERTICAL)
2142 /* These are for adjusting the drawing of a Selected tab */
2143 /* The initial values are for the normal case of non-Selected */
2144 int ZZ = 1; /* Do not stretch if selected */
2145 if (iItem == infoPtr->iSelected) {
2146 ZZ = 0;
2148 /* if leftmost draw the line longer */
2149 if(selectedRect.top == 0)
2150 fillRect.top += CONTROL_BORDER_SIZEY;
2151 /* if rightmost draw the line longer */
2152 if(selectedRect.bottom == clBottom)
2153 fillRect.bottom -= CONTROL_BORDER_SIZEY;
2156 if (infoPtr->dwStyle & TCS_BOTTOM)
2158 /* Adjust both rectangles to match native */
2159 r.left += (1-ZZ);
2161 TRACE("<right> item=%d, fill=(%s), edge=(%s)\n",
2162 iItem, wine_dbgstr_rect(&fillRect), wine_dbgstr_rect(&r));
2164 /* Clear interior */
2165 SetBkColor(hdc, bkgnd);
2166 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2168 /* Draw rectangular edge around tab */
2169 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_RIGHT|BF_TOP|BF_BOTTOM);
2171 /* Now erase the top corner and draw diagonal edge */
2172 SetBkColor(hdc, corner);
2173 r1.left = r.right - ROUND_CORNER_SIZE - 1;
2174 r1.top = r.top;
2175 r1.right = r.right;
2176 r1.bottom = r1.top + ROUND_CORNER_SIZE;
2177 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2178 r1.right--;
2179 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPLEFT);
2181 /* Now erase the bottom corner and draw diagonal edge */
2182 r1.left = r.right - ROUND_CORNER_SIZE - 1;
2183 r1.bottom = r.bottom;
2184 r1.right = r.right;
2185 r1.top = r1.bottom - ROUND_CORNER_SIZE;
2186 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2187 r1.right--;
2188 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMLEFT);
2190 if ((iItem == infoPtr->iSelected) && (selectedRect.top == 0)) {
2191 r1 = r;
2192 r1.right = r1.left;
2193 r1.left--;
2194 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_TOP);
2198 else
2200 TRACE("<left> item=%d, fill=(%s), edge=(%s)\n",
2201 iItem, wine_dbgstr_rect(&fillRect), wine_dbgstr_rect(&r));
2203 /* Clear interior */
2204 SetBkColor(hdc, bkgnd);
2205 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2207 /* Draw rectangular edge around tab */
2208 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_TOP|BF_BOTTOM);
2210 /* Now erase the top corner and draw diagonal edge */
2211 SetBkColor(hdc, corner);
2212 r1.left = r.left;
2213 r1.top = r.top;
2214 r1.right = r1.left + ROUND_CORNER_SIZE + 1;
2215 r1.bottom = r1.top + ROUND_CORNER_SIZE;
2216 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2217 r1.left++;
2218 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPRIGHT);
2220 /* Now erase the bottom corner and draw diagonal edge */
2221 r1.left = r.left;
2222 r1.bottom = r.bottom;
2223 r1.right = r1.left + ROUND_CORNER_SIZE + 1;
2224 r1.top = r1.bottom - ROUND_CORNER_SIZE;
2225 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2226 r1.left++;
2227 DrawEdge(hdc, &r1, EDGE_SUNKEN, BF_DIAGONAL_ENDTOPLEFT);
2230 else /* ! TCS_VERTICAL */
2232 /* These are for adjusting the drawing of a Selected tab */
2233 /* The initial values are for the normal case of non-Selected */
2234 if (iItem == infoPtr->iSelected) {
2235 /* if leftmost draw the line longer */
2236 if(selectedRect.left == 0)
2237 fillRect.left += CONTROL_BORDER_SIZEX;
2238 /* if rightmost draw the line longer */
2239 if(selectedRect.right == clRight)
2240 fillRect.right -= CONTROL_BORDER_SIZEX;
2243 if (infoPtr->dwStyle & TCS_BOTTOM)
2245 /* Adjust both rectangles for topmost row */
2246 if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2248 fillRect.top -= 2;
2249 r.top -= 1;
2252 TRACE("<bottom> item=%d, fill=(%s), edge=(%s)\n",
2253 iItem, wine_dbgstr_rect(&fillRect), wine_dbgstr_rect(&r));
2255 /* Clear interior */
2256 SetBkColor(hdc, bkgnd);
2257 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2259 /* Draw rectangular edge around tab */
2260 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_BOTTOM|BF_RIGHT);
2262 /* Now erase the righthand corner and draw diagonal edge */
2263 SetBkColor(hdc, corner);
2264 r1.left = r.right - ROUND_CORNER_SIZE;
2265 r1.bottom = r.bottom;
2266 r1.right = r.right;
2267 r1.top = r1.bottom - ROUND_CORNER_SIZE - 1;
2268 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2269 r1.bottom--;
2270 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMLEFT);
2272 /* Now erase the lefthand corner and draw diagonal edge */
2273 r1.left = r.left;
2274 r1.bottom = r.bottom;
2275 r1.right = r1.left + ROUND_CORNER_SIZE;
2276 r1.top = r1.bottom - ROUND_CORNER_SIZE - 1;
2277 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2278 r1.bottom--;
2279 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPLEFT);
2281 if (iItem == infoPtr->iSelected)
2283 r.top += 2;
2284 r.left += 1;
2285 if (selectedRect.left == 0)
2287 r1 = r;
2288 r1.bottom = r1.top;
2289 r1.top--;
2290 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_LEFT);
2295 else
2297 /* Adjust both rectangles for bottommost row */
2298 if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2300 fillRect.bottom += 3;
2301 r.bottom += 2;
2304 TRACE("<top> item=%d, fill=(%s), edge=(%s)\n",
2305 iItem, wine_dbgstr_rect(&fillRect), wine_dbgstr_rect(&r));
2307 /* Clear interior */
2308 SetBkColor(hdc, bkgnd);
2309 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2311 /* Draw rectangular edge around tab */
2312 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_TOP|BF_RIGHT);
2314 /* Now erase the righthand corner and draw diagonal edge */
2315 SetBkColor(hdc, corner);
2316 r1.left = r.right - ROUND_CORNER_SIZE;
2317 r1.top = r.top;
2318 r1.right = r.right;
2319 r1.bottom = r1.top + ROUND_CORNER_SIZE + 1;
2320 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2321 r1.top++;
2322 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMRIGHT);
2324 /* Now erase the lefthand corner and draw diagonal edge */
2325 r1.left = r.left;
2326 r1.top = r.top;
2327 r1.right = r1.left + ROUND_CORNER_SIZE;
2328 r1.bottom = r1.top + ROUND_CORNER_SIZE + 1;
2329 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2330 r1.top++;
2331 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPRIGHT);
2336 TAB_DumpItemInternal(infoPtr, iItem);
2338 /* This modifies r to be the text rectangle. */
2339 TAB_DrawItemInterior(infoPtr, hdc, iItem, &r);
2343 /******************************************************************************
2344 * TAB_DrawBorder
2346 * This method is used to draw the raised border around the tab control
2347 * "content" area.
2349 static void TAB_DrawBorder(const TAB_INFO *infoPtr, HDC hdc)
2351 RECT rect;
2352 HTHEME theme = GetWindowTheme (infoPtr->hwnd);
2354 GetClientRect (infoPtr->hwnd, &rect);
2357 * Adjust for the style
2360 if (infoPtr->uNumItem)
2362 if ((infoPtr->dwStyle & TCS_BOTTOM) && !(infoPtr->dwStyle & TCS_VERTICAL))
2363 rect.bottom -= infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2364 else if((infoPtr->dwStyle & TCS_BOTTOM) && (infoPtr->dwStyle & TCS_VERTICAL))
2365 rect.right -= infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2366 else if(infoPtr->dwStyle & TCS_VERTICAL)
2367 rect.left += infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2368 else /* not TCS_VERTICAL and not TCS_BOTTOM */
2369 rect.top += infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2372 TRACE("border=(%s)\n", wine_dbgstr_rect(&rect));
2374 if (theme)
2375 DrawThemeBackground (theme, hdc, TABP_PANE, 0, &rect, NULL);
2376 else
2377 DrawEdge(hdc, &rect, EDGE_RAISED, BF_SOFT|BF_RECT);
2380 /******************************************************************************
2381 * TAB_Refresh
2383 * This method repaints the tab control..
2385 static void TAB_Refresh (const TAB_INFO *infoPtr, HDC hdc)
2387 HFONT hOldFont;
2388 INT i;
2390 if (!infoPtr->DoRedraw)
2391 return;
2393 hOldFont = SelectObject (hdc, infoPtr->hFont);
2395 if (infoPtr->dwStyle & TCS_BUTTONS)
2397 for (i = 0; i < infoPtr->uNumItem; i++)
2398 TAB_DrawItem (infoPtr, hdc, i);
2400 else
2402 /* Draw all the non selected item first */
2403 for (i = 0; i < infoPtr->uNumItem; i++)
2405 if (i != infoPtr->iSelected)
2406 TAB_DrawItem (infoPtr, hdc, i);
2409 /* Now, draw the border, draw it before the selected item
2410 * since the selected item overwrites part of the border. */
2411 TAB_DrawBorder (infoPtr, hdc);
2413 /* Then, draw the selected item */
2414 TAB_DrawItem (infoPtr, hdc, infoPtr->iSelected);
2417 SelectObject (hdc, hOldFont);
2420 static inline DWORD TAB_GetRowCount (const TAB_INFO *infoPtr)
2422 TRACE("(%p)\n", infoPtr);
2423 return infoPtr->uNumRows;
2426 static inline LRESULT TAB_SetRedraw (TAB_INFO *infoPtr, BOOL doRedraw)
2428 infoPtr->DoRedraw = doRedraw;
2429 return 0;
2432 /******************************************************************************
2433 * TAB_EnsureSelectionVisible
2435 * This method will make sure that the current selection is completely
2436 * visible by scrolling until it is.
2438 static void TAB_EnsureSelectionVisible(
2439 TAB_INFO* infoPtr)
2441 INT iSelected = infoPtr->iSelected;
2442 INT iOrigLeftmostVisible = infoPtr->leftmostVisible;
2444 if (iSelected < 0)
2445 return;
2447 /* set the items row to the bottommost row or topmost row depending on
2448 * style */
2449 if ((infoPtr->uNumRows > 1) && !(infoPtr->dwStyle & TCS_BUTTONS))
2451 TAB_ITEM *selected = TAB_GetItem(infoPtr, iSelected);
2452 INT newselected;
2453 INT iTargetRow;
2455 if(infoPtr->dwStyle & TCS_VERTICAL)
2456 newselected = selected->rect.left;
2457 else
2458 newselected = selected->rect.top;
2460 /* the target row is always (number of rows - 1)
2461 as row 0 is furthest from the clientRect */
2462 iTargetRow = infoPtr->uNumRows - 1;
2464 if (newselected != iTargetRow)
2466 UINT i;
2467 if(infoPtr->dwStyle & TCS_VERTICAL)
2469 for (i=0; i < infoPtr->uNumItem; i++)
2471 /* move everything in the row of the selected item to the iTargetRow */
2472 TAB_ITEM *item = TAB_GetItem(infoPtr, i);
2474 if (item->rect.left == newselected )
2475 item->rect.left = iTargetRow;
2476 else
2478 if (item->rect.left > newselected)
2479 item->rect.left-=1;
2483 else
2485 for (i=0; i < infoPtr->uNumItem; i++)
2487 TAB_ITEM *item = TAB_GetItem(infoPtr, i);
2489 if (item->rect.top == newselected )
2490 item->rect.top = iTargetRow;
2491 else
2493 if (item->rect.top > newselected)
2494 item->rect.top-=1;
2498 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
2503 * Do the trivial cases first.
2505 if ( (!infoPtr->needsScrolling) ||
2506 (infoPtr->hwndUpDown==0) || (infoPtr->dwStyle & TCS_VERTICAL))
2507 return;
2509 if (infoPtr->leftmostVisible >= iSelected)
2511 infoPtr->leftmostVisible = iSelected;
2513 else
2515 TAB_ITEM *selected = TAB_GetItem(infoPtr, iSelected);
2516 RECT r;
2517 INT width;
2518 UINT i;
2520 /* Calculate the part of the client area that is visible */
2521 GetClientRect(infoPtr->hwnd, &r);
2522 width = r.right;
2524 GetClientRect(infoPtr->hwndUpDown, &r);
2525 width -= r.right;
2527 if ((selected->rect.right -
2528 selected->rect.left) >= width )
2530 /* Special case: width of selected item is greater than visible
2531 * part of control.
2533 infoPtr->leftmostVisible = iSelected;
2535 else
2537 for (i = infoPtr->leftmostVisible; i < infoPtr->uNumItem; i++)
2539 if ((selected->rect.right - TAB_GetItem(infoPtr, i)->rect.left) < width)
2540 break;
2542 infoPtr->leftmostVisible = i;
2546 if (infoPtr->leftmostVisible != iOrigLeftmostVisible)
2547 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
2549 SendMessageW(infoPtr->hwndUpDown, UDM_SETPOS, 0,
2550 MAKELONG(infoPtr->leftmostVisible, 0));
2553 /******************************************************************************
2554 * TAB_InvalidateTabArea
2556 * This method will invalidate the portion of the control that contains the
2557 * tabs. It is called when the state of the control changes and needs
2558 * to be redisplayed
2560 static void TAB_InvalidateTabArea(const TAB_INFO *infoPtr)
2562 RECT clientRect, rInvalidate, rAdjClient;
2563 INT lastRow = infoPtr->uNumRows - 1;
2564 RECT rect;
2566 if (lastRow < 0) return;
2568 GetClientRect(infoPtr->hwnd, &clientRect);
2569 rInvalidate = clientRect;
2570 rAdjClient = clientRect;
2572 TAB_AdjustRect(infoPtr, 0, &rAdjClient);
2574 TAB_InternalGetItemRect(infoPtr, infoPtr->uNumItem-1 , &rect, NULL);
2575 if ((infoPtr->dwStyle & TCS_BOTTOM) && (infoPtr->dwStyle & TCS_VERTICAL))
2577 rInvalidate.left = rAdjClient.right;
2578 if (infoPtr->uNumRows == 1)
2579 rInvalidate.bottom = clientRect.top + rect.bottom + 2 * SELECTED_TAB_OFFSET;
2581 else if(infoPtr->dwStyle & TCS_VERTICAL)
2583 rInvalidate.right = rAdjClient.left;
2584 if (infoPtr->uNumRows == 1)
2585 rInvalidate.bottom = clientRect.top + rect.bottom + 2 * SELECTED_TAB_OFFSET;
2587 else if (infoPtr->dwStyle & TCS_BOTTOM)
2589 rInvalidate.top = rAdjClient.bottom;
2590 if (infoPtr->uNumRows == 1)
2591 rInvalidate.right = clientRect.left + rect.right + 2 * SELECTED_TAB_OFFSET;
2593 else
2595 rInvalidate.bottom = rAdjClient.top;
2596 if (infoPtr->uNumRows == 1)
2597 rInvalidate.right = clientRect.left + rect.right + 2 * SELECTED_TAB_OFFSET;
2600 /* Punch out the updown control */
2601 if (infoPtr->needsScrolling && (rInvalidate.right > 0)) {
2602 RECT r;
2603 GetClientRect(infoPtr->hwndUpDown, &r);
2604 if (rInvalidate.right > clientRect.right - r.left)
2605 rInvalidate.right = rInvalidate.right - (r.right - r.left);
2606 else
2607 rInvalidate.right = clientRect.right - r.left;
2610 TRACE("invalidate (%s)\n", wine_dbgstr_rect(&rInvalidate));
2612 InvalidateRect(infoPtr->hwnd, &rInvalidate, TRUE);
2615 static inline LRESULT TAB_Paint (TAB_INFO *infoPtr, HDC hdcPaint)
2617 HDC hdc;
2618 PAINTSTRUCT ps;
2620 if (hdcPaint)
2621 hdc = hdcPaint;
2622 else
2624 hdc = BeginPaint (infoPtr->hwnd, &ps);
2625 TRACE("erase %d, rect=(%s)\n", ps.fErase, wine_dbgstr_rect(&ps.rcPaint));
2628 TAB_Refresh (infoPtr, hdc);
2630 if (!hdcPaint)
2631 EndPaint (infoPtr->hwnd, &ps);
2633 return 0;
2636 static LRESULT
2637 TAB_InsertItemT (TAB_INFO *infoPtr, INT iItem, const TCITEMW *pti, BOOL bUnicode)
2639 TAB_ITEM *item;
2640 RECT rect;
2642 GetClientRect (infoPtr->hwnd, &rect);
2643 TRACE("Rect: %p %s\n", infoPtr->hwnd, wine_dbgstr_rect(&rect));
2645 if (iItem < 0) return -1;
2646 if (iItem > infoPtr->uNumItem)
2647 iItem = infoPtr->uNumItem;
2649 TAB_DumpItemExternalT(pti, iItem, bUnicode);
2651 if (!(item = Alloc(TAB_ITEM_SIZE(infoPtr)))) return FALSE;
2652 if (DPA_InsertPtr(infoPtr->items, iItem, item) == -1)
2654 Free(item);
2655 return FALSE;
2658 if (infoPtr->uNumItem == 0)
2659 infoPtr->iSelected = 0;
2660 else if (iItem <= infoPtr->iSelected)
2661 infoPtr->iSelected++;
2663 infoPtr->uNumItem++;
2665 item->pszText = NULL;
2666 if (pti->mask & TCIF_TEXT)
2668 if (bUnicode)
2669 Str_SetPtrW (&item->pszText, pti->pszText);
2670 else
2671 Str_SetPtrAtoW (&item->pszText, (LPSTR)pti->pszText);
2674 if (pti->mask & TCIF_IMAGE)
2675 item->iImage = pti->iImage;
2676 else
2677 item->iImage = -1;
2679 if (pti->mask & TCIF_PARAM)
2680 memcpy(item->extra, &pti->lParam, EXTRA_ITEM_SIZE(infoPtr));
2681 else
2682 memset(item->extra, 0, EXTRA_ITEM_SIZE(infoPtr));
2684 TAB_SetItemBounds(infoPtr);
2685 if (infoPtr->uNumItem > 1)
2686 TAB_InvalidateTabArea(infoPtr);
2687 else
2688 InvalidateRect(infoPtr->hwnd, NULL, TRUE);
2690 TRACE("[%p]: added item %d %s\n",
2691 infoPtr->hwnd, iItem, debugstr_w(item->pszText));
2693 /* If we haven't set the current focus yet, set it now. */
2694 if (infoPtr->uFocus == -1)
2695 TAB_SetCurFocus(infoPtr, iItem);
2697 return iItem;
2700 static LRESULT
2701 TAB_SetItemSize (TAB_INFO *infoPtr, INT cx, INT cy)
2703 LONG lResult = 0;
2704 BOOL bNeedPaint = FALSE;
2706 lResult = MAKELONG(infoPtr->tabWidth, infoPtr->tabHeight);
2708 /* UNDOCUMENTED: If requested Width or Height is 0 this means that program wants to use auto size. */
2709 if (infoPtr->dwStyle & TCS_FIXEDWIDTH && (infoPtr->tabWidth != cx))
2711 infoPtr->tabWidth = cx;
2712 bNeedPaint = TRUE;
2715 if (infoPtr->tabHeight != cy)
2717 if ((infoPtr->fHeightSet = (cy != 0)))
2718 infoPtr->tabHeight = cy;
2720 bNeedPaint = TRUE;
2722 TRACE("was h=%d,w=%d, now h=%d,w=%d\n",
2723 HIWORD(lResult), LOWORD(lResult),
2724 infoPtr->tabHeight, infoPtr->tabWidth);
2726 if (bNeedPaint)
2728 TAB_SetItemBounds(infoPtr);
2729 RedrawWindow(infoPtr->hwnd, NULL, NULL, RDW_ERASE | RDW_INVALIDATE | RDW_UPDATENOW);
2732 return lResult;
2735 static inline LRESULT TAB_SetMinTabWidth (TAB_INFO *infoPtr, INT cx)
2737 INT oldcx = 0;
2739 TRACE("(%p,%d)\n", infoPtr, cx);
2741 if (infoPtr->tabMinWidth < 0)
2742 oldcx = DEFAULT_MIN_TAB_WIDTH;
2743 else
2744 oldcx = infoPtr->tabMinWidth;
2745 infoPtr->tabMinWidth = cx;
2746 TAB_SetItemBounds(infoPtr);
2747 return oldcx;
2750 static inline LRESULT
2751 TAB_HighlightItem (TAB_INFO *infoPtr, INT iItem, BOOL fHighlight)
2753 LPDWORD lpState;
2754 DWORD oldState;
2755 RECT r;
2757 TRACE("(%p,%d,%s)\n", infoPtr, iItem, fHighlight ? "true" : "false");
2759 if (iItem < 0 || iItem >= infoPtr->uNumItem)
2760 return FALSE;
2762 lpState = &TAB_GetItem(infoPtr, iItem)->dwState;
2763 oldState = *lpState;
2765 if (fHighlight)
2766 *lpState |= TCIS_HIGHLIGHTED;
2767 else
2768 *lpState &= ~TCIS_HIGHLIGHTED;
2770 if ((oldState != *lpState) && TAB_InternalGetItemRect (infoPtr, iItem, &r, NULL))
2771 InvalidateRect (infoPtr->hwnd, &r, TRUE);
2773 return TRUE;
2776 static LRESULT
2777 TAB_SetItemT (TAB_INFO *infoPtr, INT iItem, LPTCITEMW tabItem, BOOL bUnicode)
2779 TAB_ITEM *wineItem;
2781 TRACE("(%p,%d,%p,%s)\n", infoPtr, iItem, tabItem, bUnicode ? "true" : "false");
2783 if (iItem < 0 || iItem >= infoPtr->uNumItem)
2784 return FALSE;
2786 TAB_DumpItemExternalT(tabItem, iItem, bUnicode);
2788 wineItem = TAB_GetItem(infoPtr, iItem);
2790 if (tabItem->mask & TCIF_IMAGE)
2791 wineItem->iImage = tabItem->iImage;
2793 if (tabItem->mask & TCIF_PARAM)
2794 memcpy(wineItem->extra, &tabItem->lParam, infoPtr->cbInfo);
2796 if (tabItem->mask & TCIF_RTLREADING)
2797 FIXME("TCIF_RTLREADING\n");
2799 if (tabItem->mask & TCIF_STATE)
2800 wineItem->dwState = (wineItem->dwState & ~tabItem->dwStateMask) |
2801 ( tabItem->dwState & tabItem->dwStateMask);
2803 if (tabItem->mask & TCIF_TEXT)
2805 Free(wineItem->pszText);
2806 wineItem->pszText = NULL;
2807 if (bUnicode)
2808 Str_SetPtrW(&wineItem->pszText, tabItem->pszText);
2809 else
2810 Str_SetPtrAtoW(&wineItem->pszText, (LPSTR)tabItem->pszText);
2813 /* Update and repaint tabs */
2814 TAB_SetItemBounds(infoPtr);
2815 TAB_InvalidateTabArea(infoPtr);
2817 return TRUE;
2820 static inline LRESULT TAB_GetItemCount (const TAB_INFO *infoPtr)
2822 TRACE("\n");
2823 return infoPtr->uNumItem;
2827 static LRESULT
2828 TAB_GetItemT (TAB_INFO *infoPtr, INT iItem, LPTCITEMW tabItem, BOOL bUnicode)
2830 TAB_ITEM *wineItem;
2832 TRACE("(%p,%d,%p,%s)\n", infoPtr, iItem, tabItem, bUnicode ? "true" : "false");
2834 if (!tabItem) return FALSE;
2836 if (iItem < 0 || iItem >= infoPtr->uNumItem)
2838 /* init requested fields */
2839 if (tabItem->mask & TCIF_IMAGE) tabItem->iImage = 0;
2840 if (tabItem->mask & TCIF_PARAM) tabItem->lParam = 0;
2841 if (tabItem->mask & TCIF_STATE) tabItem->dwState = 0;
2842 return FALSE;
2845 wineItem = TAB_GetItem(infoPtr, iItem);
2847 if (tabItem->mask & TCIF_IMAGE)
2848 tabItem->iImage = wineItem->iImage;
2850 if (tabItem->mask & TCIF_PARAM)
2851 memcpy(&tabItem->lParam, wineItem->extra, infoPtr->cbInfo);
2853 if (tabItem->mask & TCIF_RTLREADING)
2854 FIXME("TCIF_RTLREADING\n");
2856 if (tabItem->mask & TCIF_STATE)
2857 tabItem->dwState = wineItem->dwState & tabItem->dwStateMask;
2859 if (tabItem->mask & TCIF_TEXT)
2861 if (bUnicode)
2862 Str_GetPtrW (wineItem->pszText, tabItem->pszText, tabItem->cchTextMax);
2863 else
2864 Str_GetPtrWtoA (wineItem->pszText, (LPSTR)tabItem->pszText, tabItem->cchTextMax);
2867 TAB_DumpItemExternalT(tabItem, iItem, bUnicode);
2869 return TRUE;
2873 static LRESULT TAB_DeleteItem (TAB_INFO *infoPtr, INT iItem)
2875 TAB_ITEM *item;
2877 TRACE("(%p, %d)\n", infoPtr, iItem);
2879 if (iItem < 0 || iItem >= infoPtr->uNumItem) return FALSE;
2881 item = TAB_GetItem(infoPtr, iItem);
2882 Free(item->pszText);
2883 Free(item);
2884 infoPtr->uNumItem--;
2885 DPA_DeletePtr(infoPtr->items, iItem);
2887 TAB_InvalidateTabArea(infoPtr);
2889 if (infoPtr->uNumItem == 0)
2891 if (infoPtr->iHotTracked >= 0)
2893 KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
2894 infoPtr->iHotTracked = -1;
2897 infoPtr->iSelected = -1;
2899 else
2901 if (iItem <= infoPtr->iHotTracked)
2903 /* When tabs move left/up, the hot track item may change */
2904 FIXME("Recalc hot track\n");
2908 /* adjust the selected index */
2909 if (iItem == infoPtr->iSelected)
2910 infoPtr->iSelected = -1;
2911 else if (iItem < infoPtr->iSelected)
2912 infoPtr->iSelected--;
2914 /* reposition and repaint tabs */
2915 TAB_SetItemBounds(infoPtr);
2917 return TRUE;
2920 static inline LRESULT TAB_DeleteAllItems (TAB_INFO *infoPtr)
2922 TRACE("(%p)\n", infoPtr);
2923 while (infoPtr->uNumItem)
2924 TAB_DeleteItem (infoPtr, 0);
2925 return TRUE;
2929 static inline LRESULT TAB_GetFont (const TAB_INFO *infoPtr)
2931 TRACE("(%p) returning %p\n", infoPtr, infoPtr->hFont);
2932 return (LRESULT)infoPtr->hFont;
2935 static inline LRESULT TAB_SetFont (TAB_INFO *infoPtr, HFONT hNewFont)
2937 TRACE("(%p,%p)\n", infoPtr, hNewFont);
2939 infoPtr->hFont = hNewFont;
2941 TAB_SetItemBounds(infoPtr);
2943 TAB_InvalidateTabArea(infoPtr);
2945 return 0;
2949 static inline LRESULT TAB_GetImageList (const TAB_INFO *infoPtr)
2951 TRACE("\n");
2952 return (LRESULT)infoPtr->himl;
2955 static inline LRESULT TAB_SetImageList (TAB_INFO *infoPtr, HIMAGELIST himlNew)
2957 HIMAGELIST himlPrev = infoPtr->himl;
2958 TRACE("himl=%p\n", himlNew);
2959 infoPtr->himl = himlNew;
2960 TAB_SetItemBounds(infoPtr);
2961 InvalidateRect(infoPtr->hwnd, NULL, TRUE);
2962 return (LRESULT)himlPrev;
2965 static inline LRESULT TAB_GetUnicodeFormat (const TAB_INFO *infoPtr)
2967 TRACE("(%p)\n", infoPtr);
2968 return infoPtr->bUnicode;
2971 static inline LRESULT TAB_SetUnicodeFormat (TAB_INFO *infoPtr, BOOL bUnicode)
2973 BOOL bTemp = infoPtr->bUnicode;
2975 TRACE("(%p %d)\n", infoPtr, bUnicode);
2976 infoPtr->bUnicode = bUnicode;
2978 return bTemp;
2981 static inline LRESULT TAB_Size (TAB_INFO *infoPtr)
2983 /* I'm not really sure what the following code was meant to do.
2984 This is what it is doing:
2985 When WM_SIZE is sent with SIZE_RESTORED, the control
2986 gets positioned in the top left corner.
2988 RECT parent_rect;
2989 HWND parent;
2990 UINT uPosFlags,cx,cy;
2992 uPosFlags=0;
2993 if (!wParam) {
2994 parent = GetParent (hwnd);
2995 GetClientRect(parent, &parent_rect);
2996 cx=LOWORD (lParam);
2997 cy=HIWORD (lParam);
2998 if (GetWindowLongW(hwnd, GWL_STYLE) & CCS_NORESIZE)
2999 uPosFlags |= (SWP_NOSIZE | SWP_NOMOVE);
3001 SetWindowPos (hwnd, 0, parent_rect.left, parent_rect.top,
3002 cx, cy, uPosFlags | SWP_NOZORDER);
3003 } else {
3004 FIXME("WM_SIZE flag %x %lx not handled\n", wParam, lParam);
3005 } */
3007 /* Recompute the size/position of the tabs. */
3008 TAB_SetItemBounds (infoPtr);
3010 /* Force a repaint of the control. */
3011 InvalidateRect(infoPtr->hwnd, NULL, TRUE);
3013 return 0;
3017 static LRESULT TAB_Create (HWND hwnd, LPARAM lParam)
3019 TAB_INFO *infoPtr;
3020 TEXTMETRICW fontMetrics;
3021 HDC hdc;
3022 HFONT hOldFont;
3023 DWORD dwStyle;
3025 infoPtr = Alloc (sizeof(TAB_INFO));
3027 SetWindowLongPtrW(hwnd, 0, (DWORD_PTR)infoPtr);
3029 infoPtr->hwnd = hwnd;
3030 infoPtr->hwndNotify = ((LPCREATESTRUCTW)lParam)->hwndParent;
3031 infoPtr->uNumItem = 0;
3032 infoPtr->uNumRows = 0;
3033 infoPtr->uHItemPadding = 6;
3034 infoPtr->uVItemPadding = 3;
3035 infoPtr->uHItemPadding_s = 6;
3036 infoPtr->uVItemPadding_s = 3;
3037 infoPtr->hFont = 0;
3038 infoPtr->items = DPA_Create(8);
3039 infoPtr->hcurArrow = LoadCursorW (0, (LPWSTR)IDC_ARROW);
3040 infoPtr->iSelected = -1;
3041 infoPtr->iHotTracked = -1;
3042 infoPtr->uFocus = -1;
3043 infoPtr->hwndToolTip = 0;
3044 infoPtr->DoRedraw = TRUE;
3045 infoPtr->needsScrolling = FALSE;
3046 infoPtr->hwndUpDown = 0;
3047 infoPtr->leftmostVisible = 0;
3048 infoPtr->fHeightSet = FALSE;
3049 infoPtr->bUnicode = IsWindowUnicode (hwnd);
3050 infoPtr->cbInfo = sizeof(LPARAM);
3052 TRACE("Created tab control, hwnd [%p]\n", hwnd);
3054 /* The tab control always has the WS_CLIPSIBLINGS style. Even
3055 if you don't specify it in CreateWindow. This is necessary in
3056 order for paint to work correctly. This follows windows behaviour. */
3057 dwStyle = GetWindowLongW(hwnd, GWL_STYLE);
3058 SetWindowLongW(hwnd, GWL_STYLE, dwStyle|WS_CLIPSIBLINGS);
3060 infoPtr->dwStyle = dwStyle | WS_CLIPSIBLINGS;
3061 infoPtr->exStyle = (dwStyle & TCS_FLATBUTTONS) ? TCS_EX_FLATSEPARATORS : 0;
3063 if (infoPtr->dwStyle & TCS_TOOLTIPS) {
3064 /* Create tooltip control */
3065 infoPtr->hwndToolTip =
3066 CreateWindowExW (0, TOOLTIPS_CLASSW, NULL, WS_POPUP,
3067 CW_USEDEFAULT, CW_USEDEFAULT,
3068 CW_USEDEFAULT, CW_USEDEFAULT,
3069 hwnd, 0, 0, 0);
3071 /* Send NM_TOOLTIPSCREATED notification */
3072 if (infoPtr->hwndToolTip) {
3073 NMTOOLTIPSCREATED nmttc;
3075 nmttc.hdr.hwndFrom = hwnd;
3076 nmttc.hdr.idFrom = GetWindowLongPtrW(hwnd, GWLP_ID);
3077 nmttc.hdr.code = NM_TOOLTIPSCREATED;
3078 nmttc.hwndToolTips = infoPtr->hwndToolTip;
3080 SendMessageW (infoPtr->hwndNotify, WM_NOTIFY,
3081 GetWindowLongPtrW(hwnd, GWLP_ID), (LPARAM)&nmttc);
3085 OpenThemeData (infoPtr->hwnd, themeClass);
3088 * We need to get text information so we need a DC and we need to select
3089 * a font.
3091 hdc = GetDC(hwnd);
3092 hOldFont = SelectObject (hdc, GetStockObject (SYSTEM_FONT));
3094 /* Use the system font to determine the initial height of a tab. */
3095 GetTextMetricsW(hdc, &fontMetrics);
3098 * Make sure there is enough space for the letters + growing the
3099 * selected item + extra space for the selected item.
3101 infoPtr->tabHeight = fontMetrics.tmHeight + SELECTED_TAB_OFFSET +
3102 ((infoPtr->dwStyle & TCS_BUTTONS) ? 2 : 1) *
3103 infoPtr->uVItemPadding;
3105 /* Initialize the width of a tab. */
3106 if (infoPtr->dwStyle & TCS_FIXEDWIDTH)
3107 infoPtr->tabWidth = GetDeviceCaps(hdc, LOGPIXELSX);
3109 infoPtr->tabMinWidth = -1;
3111 TRACE("tabH=%d, tabW=%d\n", infoPtr->tabHeight, infoPtr->tabWidth);
3113 SelectObject (hdc, hOldFont);
3114 ReleaseDC(hwnd, hdc);
3116 return 0;
3119 static LRESULT
3120 TAB_Destroy (TAB_INFO *infoPtr)
3122 INT iItem;
3124 SetWindowLongPtrW(infoPtr->hwnd, 0, 0);
3126 for (iItem = infoPtr->uNumItem - 1; iItem >= 0; iItem--)
3128 TAB_ITEM *tab = TAB_GetItem(infoPtr, iItem);
3130 DPA_DeletePtr(infoPtr->items, iItem);
3131 infoPtr->uNumItem--;
3133 Free(tab->pszText);
3134 Free(tab);
3136 DPA_Destroy(infoPtr->items);
3137 infoPtr->items = NULL;
3139 if (infoPtr->hwndToolTip)
3140 DestroyWindow (infoPtr->hwndToolTip);
3142 if (infoPtr->hwndUpDown)
3143 DestroyWindow(infoPtr->hwndUpDown);
3145 if (infoPtr->iHotTracked >= 0)
3146 KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
3148 CloseThemeData (GetWindowTheme (infoPtr->hwnd));
3150 Free (infoPtr);
3151 return 0;
3154 /* update theme after a WM_THEMECHANGED message */
3155 static LRESULT theme_changed(const TAB_INFO *infoPtr)
3157 HTHEME theme = GetWindowTheme (infoPtr->hwnd);
3158 CloseThemeData (theme);
3159 OpenThemeData (infoPtr->hwnd, themeClass);
3160 return 0;
3163 static LRESULT TAB_NCCalcSize(WPARAM wParam)
3165 if (!wParam)
3166 return 0;
3167 return WVR_ALIGNTOP;
3170 static inline LRESULT
3171 TAB_SetItemExtra (TAB_INFO *infoPtr, INT cbInfo)
3173 TRACE("(%p %d)\n", infoPtr, cbInfo);
3175 if (cbInfo < 0 || infoPtr->uNumItem) return FALSE;
3177 infoPtr->cbInfo = cbInfo;
3178 return TRUE;
3181 static LRESULT TAB_RemoveImage (TAB_INFO *infoPtr, INT image)
3183 TRACE("%p %d\n", infoPtr, image);
3185 if (ImageList_Remove (infoPtr->himl, image))
3187 INT i, *idx;
3188 RECT r;
3190 /* shift indices, repaint items if needed */
3191 for (i = 0; i < infoPtr->uNumItem; i++)
3193 idx = &TAB_GetItem(infoPtr, i)->iImage;
3194 if (*idx >= image)
3196 if (*idx == image)
3197 *idx = -1;
3198 else
3199 (*idx)--;
3201 /* repaint item */
3202 if (TAB_InternalGetItemRect (infoPtr, i, &r, NULL))
3203 InvalidateRect (infoPtr->hwnd, &r, TRUE);
3208 return 0;
3211 static LRESULT
3212 TAB_SetExtendedStyle (TAB_INFO *infoPtr, DWORD exMask, DWORD exStyle)
3214 DWORD prevstyle = infoPtr->exStyle;
3216 /* zero mask means all styles */
3217 if (exMask == 0) exMask = ~0;
3219 if (exMask & TCS_EX_REGISTERDROP)
3221 FIXME("TCS_EX_REGISTERDROP style unimplemented\n");
3222 exMask &= ~TCS_EX_REGISTERDROP;
3223 exStyle &= ~TCS_EX_REGISTERDROP;
3226 if (exMask & TCS_EX_FLATSEPARATORS)
3228 if ((prevstyle ^ exStyle) & TCS_EX_FLATSEPARATORS)
3230 infoPtr->exStyle ^= TCS_EX_FLATSEPARATORS;
3231 TAB_InvalidateTabArea(infoPtr);
3235 return prevstyle;
3238 static inline LRESULT
3239 TAB_GetExtendedStyle (const TAB_INFO *infoPtr)
3241 return infoPtr->exStyle;
3244 static LRESULT
3245 TAB_DeselectAll (TAB_INFO *infoPtr, BOOL excludesel)
3247 BOOL paint = FALSE;
3248 INT i, selected = infoPtr->iSelected;
3250 TRACE("(%p, %d)\n", infoPtr, excludesel);
3252 if (!(infoPtr->dwStyle & TCS_BUTTONS))
3253 return 0;
3255 for (i = 0; i < infoPtr->uNumItem; i++)
3257 if ((TAB_GetItem(infoPtr, i)->dwState & TCIS_BUTTONPRESSED) &&
3258 (selected != i))
3260 TAB_GetItem(infoPtr, i)->dwState &= ~TCIS_BUTTONPRESSED;
3261 paint = TRUE;
3265 if (!excludesel && (selected != -1))
3267 TAB_GetItem(infoPtr, selected)->dwState &= ~TCIS_BUTTONPRESSED;
3268 infoPtr->iSelected = -1;
3269 paint = TRUE;
3272 if (paint)
3273 TAB_InvalidateTabArea (infoPtr);
3275 return 0;
3278 /***
3279 * DESCRIPTION:
3280 * Processes WM_STYLECHANGED messages.
3282 * PARAMETER(S):
3283 * [I] infoPtr : valid pointer to the tab data structure
3284 * [I] wStyleType : window style type (normal or extended)
3285 * [I] lpss : window style information
3287 * RETURN:
3288 * Zero
3290 static INT TAB_StyleChanged(TAB_INFO *infoPtr, WPARAM wStyleType,
3291 const STYLESTRUCT *lpss)
3293 TRACE("(styletype=%lx, styleOld=0x%08x, styleNew=0x%08x)\n",
3294 wStyleType, lpss->styleOld, lpss->styleNew);
3296 if (wStyleType != GWL_STYLE) return 0;
3298 infoPtr->dwStyle = lpss->styleNew;
3300 TAB_SetItemBounds (infoPtr);
3301 InvalidateRect(infoPtr->hwnd, NULL, TRUE);
3303 return 0;
3306 static LRESULT WINAPI
3307 TAB_WindowProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
3309 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
3311 TRACE("hwnd=%p msg=%x wParam=%lx lParam=%lx\n", hwnd, uMsg, wParam, lParam);
3312 if (!infoPtr && (uMsg != WM_CREATE))
3313 return DefWindowProcW (hwnd, uMsg, wParam, lParam);
3315 switch (uMsg)
3317 case TCM_GETIMAGELIST:
3318 return TAB_GetImageList (infoPtr);
3320 case TCM_SETIMAGELIST:
3321 return TAB_SetImageList (infoPtr, (HIMAGELIST)lParam);
3323 case TCM_GETITEMCOUNT:
3324 return TAB_GetItemCount (infoPtr);
3326 case TCM_GETITEMA:
3327 case TCM_GETITEMW:
3328 return TAB_GetItemT (infoPtr, (INT)wParam, (LPTCITEMW)lParam, uMsg == TCM_GETITEMW);
3330 case TCM_SETITEMA:
3331 case TCM_SETITEMW:
3332 return TAB_SetItemT (infoPtr, (INT)wParam, (LPTCITEMW)lParam, uMsg == TCM_SETITEMW);
3334 case TCM_DELETEITEM:
3335 return TAB_DeleteItem (infoPtr, (INT)wParam);
3337 case TCM_DELETEALLITEMS:
3338 return TAB_DeleteAllItems (infoPtr);
3340 case TCM_GETITEMRECT:
3341 return TAB_GetItemRect (infoPtr, (INT)wParam, (LPRECT)lParam);
3343 case TCM_GETCURSEL:
3344 return TAB_GetCurSel (infoPtr);
3346 case TCM_HITTEST:
3347 return TAB_HitTest (infoPtr, (LPTCHITTESTINFO)lParam);
3349 case TCM_SETCURSEL:
3350 return TAB_SetCurSel (infoPtr, (INT)wParam);
3352 case TCM_INSERTITEMA:
3353 case TCM_INSERTITEMW:
3354 return TAB_InsertItemT (infoPtr, (INT)wParam, (TCITEMW*)lParam, uMsg == TCM_INSERTITEMW);
3356 case TCM_SETITEMEXTRA:
3357 return TAB_SetItemExtra (infoPtr, (INT)wParam);
3359 case TCM_ADJUSTRECT:
3360 return TAB_AdjustRect (infoPtr, (BOOL)wParam, (LPRECT)lParam);
3362 case TCM_SETITEMSIZE:
3363 return TAB_SetItemSize (infoPtr, (INT)LOWORD(lParam), (INT)HIWORD(lParam));
3365 case TCM_REMOVEIMAGE:
3366 return TAB_RemoveImage (infoPtr, (INT)wParam);
3368 case TCM_SETPADDING:
3369 return TAB_SetPadding (infoPtr, lParam);
3371 case TCM_GETROWCOUNT:
3372 return TAB_GetRowCount(infoPtr);
3374 case TCM_GETUNICODEFORMAT:
3375 return TAB_GetUnicodeFormat (infoPtr);
3377 case TCM_SETUNICODEFORMAT:
3378 return TAB_SetUnicodeFormat (infoPtr, (BOOL)wParam);
3380 case TCM_HIGHLIGHTITEM:
3381 return TAB_HighlightItem (infoPtr, (INT)wParam, (BOOL)LOWORD(lParam));
3383 case TCM_GETTOOLTIPS:
3384 return TAB_GetToolTips (infoPtr);
3386 case TCM_SETTOOLTIPS:
3387 return TAB_SetToolTips (infoPtr, (HWND)wParam);
3389 case TCM_GETCURFOCUS:
3390 return TAB_GetCurFocus (infoPtr);
3392 case TCM_SETCURFOCUS:
3393 return TAB_SetCurFocus (infoPtr, (INT)wParam);
3395 case TCM_SETMINTABWIDTH:
3396 return TAB_SetMinTabWidth(infoPtr, (INT)lParam);
3398 case TCM_DESELECTALL:
3399 return TAB_DeselectAll (infoPtr, (BOOL)wParam);
3401 case TCM_GETEXTENDEDSTYLE:
3402 return TAB_GetExtendedStyle (infoPtr);
3404 case TCM_SETEXTENDEDSTYLE:
3405 return TAB_SetExtendedStyle (infoPtr, wParam, lParam);
3407 case WM_GETFONT:
3408 return TAB_GetFont (infoPtr);
3410 case WM_SETFONT:
3411 return TAB_SetFont (infoPtr, (HFONT)wParam);
3413 case WM_CREATE:
3414 return TAB_Create (hwnd, lParam);
3416 case WM_NCDESTROY:
3417 return TAB_Destroy (infoPtr);
3419 case WM_GETDLGCODE:
3420 return DLGC_WANTARROWS | DLGC_WANTCHARS;
3422 case WM_LBUTTONDOWN:
3423 return TAB_LButtonDown (infoPtr, wParam, lParam);
3425 case WM_LBUTTONUP:
3426 return TAB_LButtonUp (infoPtr);
3428 case WM_NOTIFY:
3429 return SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, wParam, lParam);
3431 case WM_RBUTTONUP:
3432 TAB_RButtonUp (infoPtr);
3433 return DefWindowProcW (hwnd, uMsg, wParam, lParam);
3435 case WM_MOUSEMOVE:
3436 return TAB_MouseMove (infoPtr, wParam, lParam);
3438 case WM_PRINTCLIENT:
3439 case WM_PAINT:
3440 return TAB_Paint (infoPtr, (HDC)wParam);
3442 case WM_SIZE:
3443 return TAB_Size (infoPtr);
3445 case WM_SETREDRAW:
3446 return TAB_SetRedraw (infoPtr, (BOOL)wParam);
3448 case WM_HSCROLL:
3449 return TAB_OnHScroll(infoPtr, (int)LOWORD(wParam), (int)HIWORD(wParam));
3451 case WM_STYLECHANGED:
3452 return TAB_StyleChanged(infoPtr, wParam, (LPSTYLESTRUCT)lParam);
3454 case WM_SYSCOLORCHANGE:
3455 COMCTL32_RefreshSysColors();
3456 return 0;
3458 case WM_THEMECHANGED:
3459 return theme_changed (infoPtr);
3461 case WM_KILLFOCUS:
3462 TAB_KillFocus(infoPtr);
3463 case WM_SETFOCUS:
3464 TAB_FocusChanging(infoPtr);
3465 break; /* Don't disturb normal focus behavior */
3467 case WM_KEYDOWN:
3468 return TAB_KeyDown(infoPtr, wParam, lParam);
3470 case WM_NCHITTEST:
3471 return TAB_NCHitTest(infoPtr, lParam);
3473 case WM_NCCALCSIZE:
3474 return TAB_NCCalcSize(wParam);
3476 default:
3477 if (uMsg >= WM_USER && uMsg < WM_APP && !COMCTL32_IsReflectedMessage(uMsg))
3478 WARN("unknown msg %04x wp=%08lx lp=%08lx\n",
3479 uMsg, wParam, lParam);
3480 break;
3482 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
3486 void
3487 TAB_Register (void)
3489 WNDCLASSW wndClass;
3491 ZeroMemory (&wndClass, sizeof(WNDCLASSW));
3492 wndClass.style = CS_GLOBALCLASS | CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
3493 wndClass.lpfnWndProc = TAB_WindowProc;
3494 wndClass.cbClsExtra = 0;
3495 wndClass.cbWndExtra = sizeof(TAB_INFO *);
3496 wndClass.hCursor = LoadCursorW (0, (LPWSTR)IDC_ARROW);
3497 wndClass.hbrBackground = (HBRUSH)(COLOR_BTNFACE+1);
3498 wndClass.lpszClassName = WC_TABCONTROLW;
3500 RegisterClassW (&wndClass);
3504 void
3505 TAB_Unregister (void)
3507 UnregisterClassW (WC_TABCONTROLW, NULL);