ws2_32/tests: Add some WSALookupService[Begin|Next|End] tests.
[wine.git] / dlls / comctl32 / tab.c
blob85467ba8130f32f4e99ca55b7cdfba06e93ed4ac
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 if (TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGING))
688 return 0;
690 if (pressed)
691 TAB_DeselectAll (infoPtr, FALSE);
692 else
693 TAB_SetCurSel(infoPtr, newItem);
695 TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGE);
699 return 0;
702 static inline LRESULT
703 TAB_LButtonUp (const TAB_INFO *infoPtr)
705 TAB_SendSimpleNotify(infoPtr, NM_CLICK);
707 return 0;
710 static inline void
711 TAB_RButtonUp (const TAB_INFO *infoPtr)
713 TAB_SendSimpleNotify(infoPtr, NM_RCLICK);
716 /******************************************************************************
717 * TAB_DrawLoneItemInterior
719 * This calls TAB_DrawItemInterior. However, TAB_DrawItemInterior is normally
720 * called by TAB_DrawItem which is normally called by TAB_Refresh which sets
721 * up the device context and font. This routine does the same setup but
722 * only calls TAB_DrawItemInterior for the single specified item.
724 static void
725 TAB_DrawLoneItemInterior(const TAB_INFO* infoPtr, int iItem)
727 HDC hdc = GetDC(infoPtr->hwnd);
728 RECT r, rC;
730 /* Clip UpDown control to not draw over it */
731 if (infoPtr->needsScrolling)
733 GetWindowRect(infoPtr->hwnd, &rC);
734 GetWindowRect(infoPtr->hwndUpDown, &r);
735 ExcludeClipRect(hdc, r.left - rC.left, r.top - rC.top, r.right - rC.left, r.bottom - rC.top);
737 TAB_DrawItemInterior(infoPtr, hdc, iItem, NULL);
738 ReleaseDC(infoPtr->hwnd, hdc);
741 /* update a tab after hottracking - invalidate it or just redraw the interior,
742 * based on whether theming is used or not */
743 static inline void hottrack_refresh(const TAB_INFO *infoPtr, int tabIndex)
745 if (tabIndex == -1) return;
747 if (GetWindowTheme (infoPtr->hwnd))
749 RECT rect;
750 TAB_InternalGetItemRect(infoPtr, tabIndex, &rect, NULL);
751 InvalidateRect (infoPtr->hwnd, &rect, FALSE);
753 else
754 TAB_DrawLoneItemInterior(infoPtr, tabIndex);
757 /******************************************************************************
758 * TAB_HotTrackTimerProc
760 * When a mouse-move event causes a tab to be highlighted (hot-tracking), a
761 * timer is setup so we can check if the mouse is moved out of our window.
762 * (We don't get an event when the mouse leaves, the mouse-move events just
763 * stop being delivered to our window and just start being delivered to
764 * another window.) This function is called when the timer triggers so
765 * we can check if the mouse has left our window. If so, we un-highlight
766 * the hot-tracked tab.
768 static void CALLBACK
769 TAB_HotTrackTimerProc
771 HWND hwnd, /* handle of window for timer messages */
772 UINT uMsg, /* WM_TIMER message */
773 UINT_PTR idEvent, /* timer identifier */
774 DWORD dwTime /* current system time */
777 TAB_INFO* infoPtr = TAB_GetInfoPtr(hwnd);
779 if (infoPtr != NULL && infoPtr->iHotTracked >= 0)
781 POINT pt;
784 ** If we can't get the cursor position, or if the cursor is outside our
785 ** window, we un-highlight the hot-tracked tab. Note that the cursor is
786 ** "outside" even if it is within our bounding rect if another window
787 ** overlaps. Note also that the case where the cursor stayed within our
788 ** window but has moved off the hot-tracked tab will be handled by the
789 ** WM_MOUSEMOVE event.
791 if (!GetCursorPos(&pt) || WindowFromPoint(pt) != hwnd)
793 /* Redraw iHotTracked to look normal */
794 INT iRedraw = infoPtr->iHotTracked;
795 infoPtr->iHotTracked = -1;
796 hottrack_refresh (infoPtr, iRedraw);
798 /* Kill this timer */
799 KillTimer(hwnd, TAB_HOTTRACK_TIMER);
804 /******************************************************************************
805 * TAB_RecalcHotTrack
807 * If a tab control has the TCS_HOTTRACK style, then the tab under the mouse
808 * should be highlighted. This function determines which tab in a tab control,
809 * if any, is under the mouse and records that information. The caller may
810 * supply output parameters to receive the item number of the tab item which
811 * was highlighted but isn't any longer and of the tab item which is now
812 * highlighted but wasn't previously. The caller can use this information to
813 * selectively redraw those tab items.
815 * If the caller has a mouse position, it can supply it through the pos
816 * parameter. For example, TAB_MouseMove does this. Otherwise, the caller
817 * supplies NULL and this function determines the current mouse position
818 * itself.
820 static void
821 TAB_RecalcHotTrack
823 TAB_INFO* infoPtr,
824 const LPARAM* pos,
825 int* out_redrawLeave,
826 int* out_redrawEnter
829 int item = -1;
832 if (out_redrawLeave != NULL)
833 *out_redrawLeave = -1;
834 if (out_redrawEnter != NULL)
835 *out_redrawEnter = -1;
837 if ((infoPtr->dwStyle & TCS_HOTTRACK) || GetWindowTheme(infoPtr->hwnd))
839 POINT pt;
840 UINT flags;
842 if (pos == NULL)
844 GetCursorPos(&pt);
845 ScreenToClient(infoPtr->hwnd, &pt);
847 else
849 pt.x = (short)LOWORD(*pos);
850 pt.y = (short)HIWORD(*pos);
853 item = TAB_InternalHitTest(infoPtr, pt, &flags);
856 if (item != infoPtr->iHotTracked)
858 if (infoPtr->iHotTracked >= 0)
860 /* Mark currently hot-tracked to be redrawn to look normal */
861 if (out_redrawLeave != NULL)
862 *out_redrawLeave = infoPtr->iHotTracked;
864 if (item < 0)
866 /* Kill timer which forces recheck of mouse pos */
867 KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
870 else
872 /* Start timer so we recheck mouse pos */
873 UINT timerID = SetTimer
875 infoPtr->hwnd,
876 TAB_HOTTRACK_TIMER,
877 TAB_HOTTRACK_TIMER_INTERVAL,
878 TAB_HotTrackTimerProc
881 if (timerID == 0)
882 return; /* Hot tracking not available */
885 infoPtr->iHotTracked = item;
887 if (item >= 0)
889 /* Mark new hot-tracked to be redrawn to look highlighted */
890 if (out_redrawEnter != NULL)
891 *out_redrawEnter = item;
896 /******************************************************************************
897 * TAB_MouseMove
899 * Handles the mouse-move event. Updates tooltips. Updates hot-tracking.
901 static LRESULT
902 TAB_MouseMove (TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
904 int redrawLeave;
905 int redrawEnter;
907 if (infoPtr->hwndToolTip)
908 TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
909 WM_LBUTTONDOWN, wParam, lParam);
911 /* Determine which tab to highlight. Redraw tabs which change highlight
912 ** status. */
913 TAB_RecalcHotTrack(infoPtr, &lParam, &redrawLeave, &redrawEnter);
915 hottrack_refresh (infoPtr, redrawLeave);
916 hottrack_refresh (infoPtr, redrawEnter);
918 return 0;
921 /******************************************************************************
922 * TAB_AdjustRect
924 * Calculates the tab control's display area given the window rectangle or
925 * the window rectangle given the requested display rectangle.
927 static LRESULT TAB_AdjustRect(const TAB_INFO *infoPtr, WPARAM fLarger, LPRECT prc)
929 LONG *iRightBottom, *iLeftTop;
931 TRACE ("hwnd=%p fLarger=%ld (%s)\n", infoPtr->hwnd, fLarger,
932 wine_dbgstr_rect(prc));
934 if (!prc) return -1;
936 if(infoPtr->dwStyle & TCS_VERTICAL)
938 iRightBottom = &(prc->right);
939 iLeftTop = &(prc->left);
941 else
943 iRightBottom = &(prc->bottom);
944 iLeftTop = &(prc->top);
947 if (fLarger) /* Go from display rectangle */
949 /* Add the height of the tabs. */
950 if (infoPtr->dwStyle & TCS_BOTTOM)
951 *iRightBottom += infoPtr->tabHeight * infoPtr->uNumRows;
952 else
953 *iLeftTop -= infoPtr->tabHeight * infoPtr->uNumRows +
954 ((infoPtr->dwStyle & TCS_BUTTONS)? 3 * (infoPtr->uNumRows - 1) : 0);
956 /* Inflate the rectangle for the padding */
957 InflateRect(prc, DISPLAY_AREA_PADDINGX, DISPLAY_AREA_PADDINGY);
959 /* Inflate for the border */
960 InflateRect(prc, CONTROL_BORDER_SIZEX, CONTROL_BORDER_SIZEY);
962 else /* Go from window rectangle. */
964 /* Deflate the rectangle for the border */
965 InflateRect(prc, -CONTROL_BORDER_SIZEX, -CONTROL_BORDER_SIZEY);
967 /* Deflate the rectangle for the padding */
968 InflateRect(prc, -DISPLAY_AREA_PADDINGX, -DISPLAY_AREA_PADDINGY);
970 /* Remove the height of the tabs. */
971 if (infoPtr->dwStyle & TCS_BOTTOM)
972 *iRightBottom -= infoPtr->tabHeight * infoPtr->uNumRows;
973 else
974 *iLeftTop += (infoPtr->tabHeight) * infoPtr->uNumRows +
975 ((infoPtr->dwStyle & TCS_BUTTONS)? 3 * (infoPtr->uNumRows - 1) : 0);
978 return 0;
981 /******************************************************************************
982 * TAB_OnHScroll
984 * This method will handle the notification from the scroll control and
985 * perform the scrolling operation on the tab control.
987 static LRESULT TAB_OnHScroll(TAB_INFO *infoPtr, int nScrollCode, int nPos)
989 if(nScrollCode == SB_THUMBPOSITION && nPos != infoPtr->leftmostVisible)
991 if(nPos < infoPtr->leftmostVisible)
992 infoPtr->leftmostVisible--;
993 else
994 infoPtr->leftmostVisible++;
996 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
997 TAB_InvalidateTabArea(infoPtr);
998 SendMessageW(infoPtr->hwndUpDown, UDM_SETPOS, 0,
999 MAKELONG(infoPtr->leftmostVisible, 0));
1002 return 0;
1005 /******************************************************************************
1006 * TAB_SetupScrolling
1008 * This method will check the current scrolling state and make sure the
1009 * scrolling control is displayed (or not).
1011 static void TAB_SetupScrolling(
1012 TAB_INFO* infoPtr,
1013 const RECT* clientRect)
1015 static const WCHAR emptyW[] = { 0 };
1016 INT maxRange = 0;
1018 if (infoPtr->needsScrolling)
1020 RECT controlPos;
1021 INT vsize, tabwidth;
1024 * Calculate the position of the scroll control.
1026 controlPos.right = clientRect->right;
1027 controlPos.left = controlPos.right - 2 * GetSystemMetrics(SM_CXHSCROLL);
1029 if (infoPtr->dwStyle & TCS_BOTTOM)
1031 controlPos.top = clientRect->bottom - infoPtr->tabHeight;
1032 controlPos.bottom = controlPos.top + GetSystemMetrics(SM_CYHSCROLL);
1034 else
1036 controlPos.bottom = clientRect->top + infoPtr->tabHeight;
1037 controlPos.top = controlPos.bottom - GetSystemMetrics(SM_CYHSCROLL);
1041 * If we don't have a scroll control yet, we want to create one.
1042 * If we have one, we want to make sure it's positioned properly.
1044 if (infoPtr->hwndUpDown==0)
1046 infoPtr->hwndUpDown = CreateWindowW(UPDOWN_CLASSW, emptyW,
1047 WS_VISIBLE | WS_CHILD | UDS_HORZ,
1048 controlPos.left, controlPos.top,
1049 controlPos.right - controlPos.left,
1050 controlPos.bottom - controlPos.top,
1051 infoPtr->hwnd, NULL, NULL, NULL);
1053 else
1055 SetWindowPos(infoPtr->hwndUpDown,
1056 NULL,
1057 controlPos.left, controlPos.top,
1058 controlPos.right - controlPos.left,
1059 controlPos.bottom - controlPos.top,
1060 SWP_SHOWWINDOW | SWP_NOZORDER);
1063 /* Now calculate upper limit of the updown control range.
1064 * We do this by calculating how many tabs will be offscreen when the
1065 * last tab is visible.
1067 if(infoPtr->uNumItem)
1069 vsize = clientRect->right - (controlPos.right - controlPos.left + 1);
1070 maxRange = infoPtr->uNumItem;
1071 tabwidth = TAB_GetItem(infoPtr, infoPtr->uNumItem - 1)->rect.right;
1073 for(; maxRange > 0; maxRange--)
1075 if(tabwidth - TAB_GetItem(infoPtr,maxRange - 1)->rect.left > vsize)
1076 break;
1079 if(maxRange == infoPtr->uNumItem)
1080 maxRange--;
1083 else
1085 /* If we once had a scroll control... hide it */
1086 if (infoPtr->hwndUpDown)
1087 ShowWindow(infoPtr->hwndUpDown, SW_HIDE);
1089 if (infoPtr->hwndUpDown)
1090 SendMessageW(infoPtr->hwndUpDown, UDM_SETRANGE32, 0, maxRange);
1093 /******************************************************************************
1094 * TAB_SetItemBounds
1096 * This method will calculate the position rectangles of all the items in the
1097 * control. The rectangle calculated starts at 0 for the first item in the
1098 * list and ignores scrolling and selection.
1099 * It also uses the current font to determine the height of the tab row and
1100 * it checks if all the tabs fit in the client area of the window. If they
1101 * don't, a scrolling control is added.
1103 static void TAB_SetItemBounds (TAB_INFO *infoPtr)
1105 TEXTMETRICW fontMetrics;
1106 UINT curItem;
1107 INT curItemLeftPos;
1108 INT curItemRowCount;
1109 HFONT hFont, hOldFont;
1110 HDC hdc;
1111 RECT clientRect;
1112 INT iTemp;
1113 RECT* rcItem;
1114 INT iIndex;
1115 INT icon_width = 0;
1118 * We need to get text information so we need a DC and we need to select
1119 * a font.
1121 hdc = GetDC(infoPtr->hwnd);
1123 hFont = infoPtr->hFont ? infoPtr->hFont : GetStockObject (SYSTEM_FONT);
1124 hOldFont = SelectObject (hdc, hFont);
1127 * We will base the rectangle calculations on the client rectangle
1128 * of the control.
1130 GetClientRect(infoPtr->hwnd, &clientRect);
1132 /* if TCS_VERTICAL then swap the height and width so this code places the
1133 tabs along the top of the rectangle and we can just rotate them after
1134 rather than duplicate all of the below code */
1135 if(infoPtr->dwStyle & TCS_VERTICAL)
1137 iTemp = clientRect.bottom;
1138 clientRect.bottom = clientRect.right;
1139 clientRect.right = iTemp;
1142 /* Now use hPadding and vPadding */
1143 infoPtr->uHItemPadding = infoPtr->uHItemPadding_s;
1144 infoPtr->uVItemPadding = infoPtr->uVItemPadding_s;
1146 /* The leftmost item will be "0" aligned */
1147 curItemLeftPos = 0;
1148 curItemRowCount = infoPtr->uNumItem ? 1 : 0;
1150 if (!(infoPtr->fHeightSet))
1152 int item_height;
1153 INT icon_height = 0, cx;
1155 /* Use the current font to determine the height of a tab. */
1156 GetTextMetricsW(hdc, &fontMetrics);
1158 /* Get the icon height */
1159 if (infoPtr->himl)
1160 ImageList_GetIconSize(infoPtr->himl, &cx, &icon_height);
1162 /* Take the highest between font or icon */
1163 if (fontMetrics.tmHeight > icon_height)
1164 item_height = fontMetrics.tmHeight + 2;
1165 else
1166 item_height = icon_height;
1169 * Make sure there is enough space for the letters + icon + growing the
1170 * selected item + extra space for the selected item.
1172 infoPtr->tabHeight = item_height +
1173 ((infoPtr->dwStyle & TCS_BUTTONS) ? 2 : 1) *
1174 infoPtr->uVItemPadding;
1176 TRACE("tabH=%d, tmH=%d, iconh=%d\n",
1177 infoPtr->tabHeight, fontMetrics.tmHeight, icon_height);
1180 TRACE("client right=%d\n", clientRect.right);
1182 /* Get the icon width */
1183 if (infoPtr->himl)
1185 INT cy;
1187 ImageList_GetIconSize(infoPtr->himl, &icon_width, &cy);
1189 if (infoPtr->dwStyle & TCS_FIXEDWIDTH)
1190 icon_width += 4;
1191 else
1192 /* Add padding if icon is present */
1193 icon_width += infoPtr->uHItemPadding;
1196 for (curItem = 0; curItem < infoPtr->uNumItem; curItem++)
1198 TAB_ITEM *curr = TAB_GetItem(infoPtr, curItem);
1200 /* Set the leftmost position of the tab. */
1201 curr->rect.left = curItemLeftPos;
1203 if (infoPtr->dwStyle & TCS_FIXEDWIDTH)
1205 curr->rect.right = curr->rect.left +
1206 max(infoPtr->tabWidth, icon_width);
1208 else if (!curr->pszText)
1210 /* If no text use minimum tab width including padding. */
1211 if (infoPtr->tabMinWidth < 0)
1212 curr->rect.right = curr->rect.left + GET_DEFAULT_MIN_TAB_WIDTH(infoPtr);
1213 else
1215 curr->rect.right = curr->rect.left + infoPtr->tabMinWidth;
1217 /* Add extra padding if icon is present */
1218 if (infoPtr->himl && infoPtr->tabMinWidth > 0 && infoPtr->tabMinWidth < DEFAULT_MIN_TAB_WIDTH
1219 && infoPtr->uHItemPadding > 1)
1220 curr->rect.right += EXTRA_ICON_PADDING * (infoPtr->uHItemPadding-1);
1223 else
1225 int tabwidth;
1226 SIZE size;
1227 /* Calculate how wide the tab is depending on the text it contains */
1228 GetTextExtentPoint32W(hdc, curr->pszText,
1229 lstrlenW(curr->pszText), &size);
1231 tabwidth = size.cx + icon_width + 2 * infoPtr->uHItemPadding;
1233 if (infoPtr->tabMinWidth < 0)
1234 tabwidth = max(tabwidth, GET_DEFAULT_MIN_TAB_WIDTH(infoPtr));
1235 else
1236 tabwidth = max(tabwidth, infoPtr->tabMinWidth);
1238 curr->rect.right = curr->rect.left + tabwidth;
1239 TRACE("for <%s>, l,r=%d,%d\n",
1240 debugstr_w(curr->pszText), curr->rect.left, curr->rect.right);
1244 * Check if this is a multiline tab control and if so
1245 * check to see if we should wrap the tabs
1247 * Wrap all these tabs. We will arrange them evenly later.
1251 if (((infoPtr->dwStyle & TCS_MULTILINE) || (infoPtr->dwStyle & TCS_VERTICAL)) &&
1252 (curr->rect.right >
1253 (clientRect.right - CONTROL_BORDER_SIZEX - DISPLAY_AREA_PADDINGX)))
1255 curr->rect.right -= curr->rect.left;
1257 curr->rect.left = 0;
1258 curItemRowCount++;
1259 TRACE("wrapping <%s>, l,r=%d,%d\n", debugstr_w(curr->pszText),
1260 curr->rect.left, curr->rect.right);
1263 curr->rect.bottom = 0;
1264 curr->rect.top = curItemRowCount - 1;
1266 TRACE("Rect: %s\n", wine_dbgstr_rect(&curr->rect));
1269 * The leftmost position of the next item is the rightmost position
1270 * of this one.
1272 if (infoPtr->dwStyle & TCS_BUTTONS)
1274 curItemLeftPos = curr->rect.right + BUTTON_SPACINGX;
1275 if (infoPtr->dwStyle & TCS_FLATBUTTONS)
1276 curItemLeftPos += FLAT_BTN_SPACINGX;
1278 else
1279 curItemLeftPos = curr->rect.right;
1282 if (!((infoPtr->dwStyle & TCS_MULTILINE) || (infoPtr->dwStyle & TCS_VERTICAL)))
1285 * Check if we need a scrolling control.
1287 infoPtr->needsScrolling = (curItemLeftPos + (2 * SELECTED_TAB_OFFSET) >
1288 clientRect.right);
1290 /* Don't need scrolling, then update infoPtr->leftmostVisible */
1291 if(!infoPtr->needsScrolling)
1292 infoPtr->leftmostVisible = 0;
1294 else
1297 * No scrolling in Multiline or Vertical styles.
1299 infoPtr->needsScrolling = FALSE;
1300 infoPtr->leftmostVisible = 0;
1302 TAB_SetupScrolling(infoPtr, &clientRect);
1304 /* Set the number of rows */
1305 infoPtr->uNumRows = curItemRowCount;
1307 /* Arrange all tabs evenly if style says so */
1308 if (!(infoPtr->dwStyle & TCS_RAGGEDRIGHT) &&
1309 ((infoPtr->dwStyle & TCS_MULTILINE) || (infoPtr->dwStyle & TCS_VERTICAL)) &&
1310 (infoPtr->uNumItem > 0) &&
1311 (infoPtr->uNumRows > 1))
1313 INT tabPerRow,remTab,iRow;
1314 UINT iItm;
1315 INT iCount=0;
1318 * Ok windows tries to even out the rows. place the same
1319 * number of tabs in each row. So lets give that a shot
1322 tabPerRow = infoPtr->uNumItem / (infoPtr->uNumRows);
1323 remTab = infoPtr->uNumItem % (infoPtr->uNumRows);
1325 for (iItm=0,iRow=0,iCount=0,curItemLeftPos=0;
1326 iItm<infoPtr->uNumItem;
1327 iItm++,iCount++)
1329 /* normalize the current rect */
1330 TAB_ITEM *curr = TAB_GetItem(infoPtr, iItm);
1332 /* shift the item to the left side of the clientRect */
1333 curr->rect.right -= curr->rect.left;
1334 curr->rect.left = 0;
1336 TRACE("r=%d, cl=%d, cl.r=%d, iCount=%d, iRow=%d, uNumRows=%d, remTab=%d, tabPerRow=%d\n",
1337 curr->rect.right, curItemLeftPos, clientRect.right,
1338 iCount, iRow, infoPtr->uNumRows, remTab, tabPerRow);
1340 /* if we have reached the maximum number of tabs on this row */
1341 /* move to the next row, reset our current item left position and */
1342 /* the count of items on this row */
1344 if (infoPtr->dwStyle & TCS_VERTICAL) {
1345 /* Vert: Add the remaining tabs in the *last* remainder rows */
1346 if (iCount >= ((iRow>=(INT)infoPtr->uNumRows - remTab)?tabPerRow + 1:tabPerRow)) {
1347 iRow++;
1348 curItemLeftPos = 0;
1349 iCount = 0;
1351 } else {
1352 /* Horz: Add the remaining tabs in the *first* remainder rows */
1353 if (iCount >= ((iRow<remTab)?tabPerRow + 1:tabPerRow)) {
1354 iRow++;
1355 curItemLeftPos = 0;
1356 iCount = 0;
1360 /* shift the item to the right to place it as the next item in this row */
1361 curr->rect.left += curItemLeftPos;
1362 curr->rect.right += curItemLeftPos;
1363 curr->rect.top = iRow;
1364 if (infoPtr->dwStyle & TCS_BUTTONS)
1366 curItemLeftPos = curr->rect.right + 1;
1367 if (infoPtr->dwStyle & TCS_FLATBUTTONS)
1368 curItemLeftPos += FLAT_BTN_SPACINGX;
1370 else
1371 curItemLeftPos = curr->rect.right;
1373 TRACE("arranging <%s>, l,r=%d,%d, row=%d\n",
1374 debugstr_w(curr->pszText), curr->rect.left,
1375 curr->rect.right, curr->rect.top);
1379 * Justify the rows
1382 INT widthDiff, iIndexStart=0, iIndexEnd=0;
1383 INT remainder;
1384 INT iCount=0;
1386 while(iIndexStart < infoPtr->uNumItem)
1388 TAB_ITEM *start = TAB_GetItem(infoPtr, iIndexStart);
1391 * find the index of the row
1393 /* find the first item on the next row */
1394 for (iIndexEnd=iIndexStart;
1395 (iIndexEnd < infoPtr->uNumItem) &&
1396 (TAB_GetItem(infoPtr, iIndexEnd)->rect.top ==
1397 start->rect.top) ;
1398 iIndexEnd++)
1399 /* intentionally blank */;
1402 * we need to justify these tabs so they fill the whole given
1403 * client area
1406 /* find the amount of space remaining on this row */
1407 widthDiff = clientRect.right - (2 * SELECTED_TAB_OFFSET) -
1408 TAB_GetItem(infoPtr, iIndexEnd - 1)->rect.right;
1410 /* iCount is the number of tab items on this row */
1411 iCount = iIndexEnd - iIndexStart;
1413 if (iCount > 1)
1415 remainder = widthDiff % iCount;
1416 widthDiff = widthDiff / iCount;
1417 /* add widthDiff/iCount, or extra space/items on row, to each item on this row */
1418 for (iIndex=iIndexStart, iCount=0; iIndex < iIndexEnd; iIndex++, iCount++)
1420 TAB_ITEM *item = TAB_GetItem(infoPtr, iIndex);
1422 item->rect.left += iCount * widthDiff;
1423 item->rect.right += (iCount + 1) * widthDiff;
1425 TRACE("adjusting 1 <%s>, l,r=%d,%d\n",
1426 debugstr_w(item->pszText),
1427 item->rect.left, item->rect.right);
1430 TAB_GetItem(infoPtr, iIndex - 1)->rect.right += remainder;
1432 else /* we have only one item on this row, make it take up the entire row */
1434 start->rect.left = clientRect.left;
1435 start->rect.right = clientRect.right - 4;
1437 TRACE("adjusting 2 <%s>, l,r=%d,%d\n",
1438 debugstr_w(start->pszText),
1439 start->rect.left, start->rect.right);
1444 iIndexStart = iIndexEnd;
1449 /* if TCS_VERTICAL rotate the tabs so they are along the side of the clientRect */
1450 if(infoPtr->dwStyle & TCS_VERTICAL)
1452 RECT rcOriginal;
1453 for(iIndex = 0; iIndex < infoPtr->uNumItem; iIndex++)
1455 rcItem = &TAB_GetItem(infoPtr, iIndex)->rect;
1457 rcOriginal = *rcItem;
1459 /* this is rotating the items by 90 degrees clockwise around the center of the control */
1460 rcItem->top = (rcOriginal.left - clientRect.left);
1461 rcItem->bottom = rcItem->top + (rcOriginal.right - rcOriginal.left);
1462 rcItem->left = rcOriginal.top;
1463 rcItem->right = rcOriginal.bottom;
1467 TAB_EnsureSelectionVisible(infoPtr);
1468 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
1470 /* Cleanup */
1471 SelectObject (hdc, hOldFont);
1472 ReleaseDC (infoPtr->hwnd, hdc);
1476 static void
1477 TAB_EraseTabInterior(const TAB_INFO *infoPtr, HDC hdc, INT iItem, const RECT *drawRect)
1479 HBRUSH hbr = CreateSolidBrush (comctl32_color.clrBtnFace);
1480 BOOL deleteBrush = TRUE;
1481 RECT rTemp = *drawRect;
1483 if (infoPtr->dwStyle & TCS_BUTTONS)
1485 if (iItem == infoPtr->iSelected)
1487 /* Background color */
1488 if (!(infoPtr->dwStyle & TCS_OWNERDRAWFIXED))
1490 DeleteObject(hbr);
1491 hbr = GetSysColorBrush(COLOR_SCROLLBAR);
1493 SetTextColor(hdc, comctl32_color.clr3dFace);
1494 SetBkColor(hdc, comctl32_color.clr3dHilight);
1496 /* if COLOR_WINDOW happens to be the same as COLOR_3DHILIGHT
1497 * we better use 0x55aa bitmap brush to make scrollbar's background
1498 * look different from the window background.
1500 if (comctl32_color.clr3dHilight == comctl32_color.clrWindow)
1501 hbr = COMCTL32_hPattern55AABrush;
1503 deleteBrush = FALSE;
1505 FillRect(hdc, &rTemp, hbr);
1507 else /* ! selected */
1509 if (infoPtr->dwStyle & TCS_FLATBUTTONS)
1511 InflateRect(&rTemp, 2, 2);
1512 FillRect(hdc, &rTemp, hbr);
1513 if (iItem == infoPtr->iHotTracked ||
1514 (iItem != infoPtr->iSelected && iItem == infoPtr->uFocus))
1515 DrawEdge(hdc, &rTemp, BDR_RAISEDINNER, BF_RECT);
1517 else
1518 FillRect(hdc, &rTemp, hbr);
1522 else /* !TCS_BUTTONS */
1524 InflateRect(&rTemp, -2, -2);
1525 if (!GetWindowTheme (infoPtr->hwnd))
1526 FillRect(hdc, &rTemp, hbr);
1529 /* highlighting is drawn on top of previous fills */
1530 if (TAB_GetItem(infoPtr, iItem)->dwState & TCIS_HIGHLIGHTED)
1532 if (deleteBrush)
1534 DeleteObject(hbr);
1535 deleteBrush = FALSE;
1537 hbr = GetSysColorBrush(COLOR_HIGHLIGHT);
1538 FillRect(hdc, &rTemp, hbr);
1541 /* Cleanup */
1542 if (deleteBrush) DeleteObject(hbr);
1545 /******************************************************************************
1546 * TAB_DrawItemInterior
1548 * This method is used to draw the interior (text and icon) of a single tab
1549 * into the tab control.
1551 static void
1552 TAB_DrawItemInterior(const TAB_INFO *infoPtr, HDC hdc, INT iItem, RECT *drawRect)
1554 RECT localRect;
1556 HPEN htextPen;
1557 HPEN holdPen;
1558 INT oldBkMode;
1559 HFONT hOldFont;
1561 /* if (drawRect == NULL) */
1563 BOOL isVisible;
1564 RECT itemRect;
1565 RECT selectedRect;
1568 * Get the rectangle for the item.
1570 isVisible = TAB_InternalGetItemRect(infoPtr, iItem, &itemRect, &selectedRect);
1571 if (!isVisible)
1572 return;
1575 * Make sure drawRect points to something valid; simplifies code.
1577 drawRect = &localRect;
1580 * This logic copied from the part of TAB_DrawItem which draws
1581 * the tab background. It's important to keep it in sync. I
1582 * would have liked to avoid code duplication, but couldn't figure
1583 * out how without making spaghetti of TAB_DrawItem.
1585 if (iItem == infoPtr->iSelected)
1586 *drawRect = selectedRect;
1587 else
1588 *drawRect = itemRect;
1590 if (infoPtr->dwStyle & TCS_BUTTONS)
1592 if (iItem == infoPtr->iSelected)
1594 drawRect->left += 4;
1595 drawRect->top += 4;
1596 drawRect->right -= 4;
1598 if (infoPtr->dwStyle & TCS_VERTICAL)
1600 if (!(infoPtr->dwStyle & TCS_BOTTOM)) drawRect->right += 1;
1601 drawRect->bottom -= 4;
1603 else
1605 if (infoPtr->dwStyle & TCS_BOTTOM)
1607 drawRect->top -= 2;
1608 drawRect->bottom -= 4;
1610 else
1611 drawRect->bottom -= 1;
1614 else
1616 drawRect->left += 2;
1617 drawRect->top += 2;
1618 drawRect->right -= 2;
1619 drawRect->bottom -= 2;
1622 else
1624 if ((infoPtr->dwStyle & TCS_VERTICAL) && (infoPtr->dwStyle & TCS_BOTTOM))
1626 if (iItem != infoPtr->iSelected)
1628 drawRect->left += 2;
1629 drawRect->top += 2;
1630 drawRect->bottom -= 2;
1633 else if (infoPtr->dwStyle & TCS_VERTICAL)
1635 if (iItem == infoPtr->iSelected)
1637 drawRect->right += 1;
1639 else
1641 drawRect->top += 2;
1642 drawRect->right -= 2;
1643 drawRect->bottom -= 2;
1646 else if (infoPtr->dwStyle & TCS_BOTTOM)
1648 if (iItem == infoPtr->iSelected)
1650 drawRect->top -= 2;
1652 else
1654 InflateRect(drawRect, -2, -2);
1655 drawRect->bottom += 2;
1658 else
1660 if (iItem == infoPtr->iSelected)
1662 drawRect->bottom += 3;
1664 else
1666 drawRect->bottom -= 2;
1667 InflateRect(drawRect, -2, 0);
1672 TRACE("drawRect=(%s)\n", wine_dbgstr_rect(drawRect));
1674 /* Clear interior */
1675 TAB_EraseTabInterior (infoPtr, hdc, iItem, drawRect);
1677 /* Draw the focus rectangle */
1678 if (!(infoPtr->dwStyle & TCS_FOCUSNEVER) &&
1679 (GetFocus() == infoPtr->hwnd) &&
1680 (iItem == infoPtr->uFocus) )
1682 RECT rFocus = *drawRect;
1684 if (!(infoPtr->dwStyle & TCS_BUTTONS)) InflateRect(&rFocus, -3, -3);
1685 if (infoPtr->dwStyle & TCS_BOTTOM && !(infoPtr->dwStyle & TCS_VERTICAL))
1686 rFocus.top -= 3;
1688 /* focus should stay on selected item for TCS_BUTTONS style */
1689 if (!((infoPtr->dwStyle & TCS_BUTTONS) && (infoPtr->iSelected != iItem)))
1690 DrawFocusRect(hdc, &rFocus);
1694 * Text pen
1696 htextPen = CreatePen( PS_SOLID, 1, comctl32_color.clrBtnText );
1697 holdPen = SelectObject(hdc, htextPen);
1698 hOldFont = SelectObject(hdc, infoPtr->hFont);
1701 * Setup for text output
1703 oldBkMode = SetBkMode(hdc, TRANSPARENT);
1704 if (!GetWindowTheme (infoPtr->hwnd) || (infoPtr->dwStyle & TCS_BUTTONS))
1706 if ((infoPtr->dwStyle & TCS_HOTTRACK) && (iItem == infoPtr->iHotTracked) &&
1707 !(infoPtr->dwStyle & TCS_FLATBUTTONS))
1708 SetTextColor(hdc, comctl32_color.clrHighlight);
1709 else if (TAB_GetItem(infoPtr, iItem)->dwState & TCIS_HIGHLIGHTED)
1710 SetTextColor(hdc, comctl32_color.clrHighlightText);
1711 else
1712 SetTextColor(hdc, comctl32_color.clrBtnText);
1716 * if owner draw, tell the owner to draw
1718 if ((infoPtr->dwStyle & TCS_OWNERDRAWFIXED) && IsWindow(infoPtr->hwndNotify))
1720 DRAWITEMSTRUCT dis;
1721 UINT id;
1723 drawRect->top += 2;
1724 drawRect->right -= 1;
1725 if ( iItem == infoPtr->iSelected )
1727 drawRect->right -= 1;
1728 drawRect->left += 1;
1731 id = (UINT)GetWindowLongPtrW( infoPtr->hwnd, GWLP_ID );
1733 /* fill DRAWITEMSTRUCT */
1734 dis.CtlType = ODT_TAB;
1735 dis.CtlID = id;
1736 dis.itemID = iItem;
1737 dis.itemAction = ODA_DRAWENTIRE;
1738 dis.itemState = 0;
1739 if ( iItem == infoPtr->iSelected )
1740 dis.itemState |= ODS_SELECTED;
1741 if (infoPtr->uFocus == iItem)
1742 dis.itemState |= ODS_FOCUS;
1743 dis.hwndItem = infoPtr->hwnd;
1744 dis.hDC = hdc;
1745 CopyRect(&dis.rcItem,drawRect);
1747 /* when extra data fits ULONG_PTR, store it directly */
1748 if (infoPtr->cbInfo > sizeof(LPARAM))
1749 dis.itemData = (ULONG_PTR) TAB_GetItem(infoPtr, iItem)->extra;
1750 else
1752 /* this could be considered broken on 64 bit, but that's how it works -
1753 only first 4 bytes are copied */
1754 dis.itemData = 0;
1755 memcpy(&dis.itemData, (ULONG_PTR*)TAB_GetItem(infoPtr, iItem)->extra, 4);
1758 /* draw notification */
1759 SendMessageW( infoPtr->hwndNotify, WM_DRAWITEM, id, (LPARAM)&dis );
1761 else
1763 TAB_ITEM *item = TAB_GetItem(infoPtr, iItem);
1764 RECT rcTemp;
1765 RECT rcImage;
1767 /* used to center the icon and text in the tab */
1768 RECT rcText;
1769 INT center_offset_h, center_offset_v;
1771 /* set rcImage to drawRect, we will use top & left in our ImageList_Draw call */
1772 rcImage = *drawRect;
1774 rcTemp = *drawRect;
1776 rcText.left = rcText.top = rcText.right = rcText.bottom = 0;
1778 /* get the rectangle that the text fits in */
1779 if (item->pszText)
1781 DrawTextW(hdc, item->pszText, -1, &rcText, DT_CALCRECT);
1784 * If not owner draw, then do the drawing ourselves.
1786 * Draw the icon.
1788 if (infoPtr->himl && item->iImage != -1)
1790 INT cx;
1791 INT cy;
1793 ImageList_GetIconSize(infoPtr->himl, &cx, &cy);
1795 if(infoPtr->dwStyle & TCS_VERTICAL)
1797 center_offset_h = ((drawRect->bottom - drawRect->top) - (cy + infoPtr->uHItemPadding + (rcText.right - rcText.left))) / 2;
1798 center_offset_v = ((drawRect->right - drawRect->left) - cx) / 2;
1800 else
1802 center_offset_h = ((drawRect->right - drawRect->left) - (cx + infoPtr->uHItemPadding + (rcText.right - rcText.left))) / 2;
1803 center_offset_v = ((drawRect->bottom - drawRect->top) - cy) / 2;
1806 /* if an item is selected, the icon is shifted up instead of down */
1807 if (iItem == infoPtr->iSelected)
1808 center_offset_v -= infoPtr->uVItemPadding / 2;
1809 else
1810 center_offset_v += infoPtr->uVItemPadding / 2;
1812 if (infoPtr->dwStyle & TCS_FIXEDWIDTH && infoPtr->dwStyle & (TCS_FORCELABELLEFT | TCS_FORCEICONLEFT))
1813 center_offset_h = infoPtr->uHItemPadding;
1815 if (center_offset_h < 2)
1816 center_offset_h = 2;
1818 if (center_offset_v < 0)
1819 center_offset_v = 0;
1821 TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%s), textlen=%d\n",
1822 debugstr_w(item->pszText), center_offset_h, center_offset_v,
1823 wine_dbgstr_rect(drawRect), (rcText.right-rcText.left));
1825 if((infoPtr->dwStyle & TCS_VERTICAL) && (infoPtr->dwStyle & TCS_BOTTOM))
1827 rcImage.top = drawRect->top + center_offset_h;
1828 /* if tab is TCS_VERTICAL and TCS_BOTTOM, the text is drawn from the */
1829 /* right side of the tab, but the image still uses the left as its x position */
1830 /* this keeps the image always drawn off of the same side of the tab */
1831 rcImage.left = drawRect->right - cx - center_offset_v;
1832 drawRect->top += cy + infoPtr->uHItemPadding;
1834 else if(infoPtr->dwStyle & TCS_VERTICAL)
1836 rcImage.top = drawRect->bottom - cy - center_offset_h;
1837 rcImage.left = drawRect->left + center_offset_v;
1838 drawRect->bottom -= cy + infoPtr->uHItemPadding;
1840 else /* normal style, whether TCS_BOTTOM or not */
1842 rcImage.left = drawRect->left + center_offset_h;
1843 rcImage.top = drawRect->top + center_offset_v;
1844 drawRect->left += cx + infoPtr->uHItemPadding;
1847 TRACE("drawing image=%d, left=%d, top=%d\n",
1848 item->iImage, rcImage.left, rcImage.top-1);
1849 ImageList_Draw
1851 infoPtr->himl,
1852 item->iImage,
1853 hdc,
1854 rcImage.left,
1855 rcImage.top,
1856 ILD_NORMAL
1860 /* Now position text */
1861 if (infoPtr->dwStyle & TCS_FIXEDWIDTH && infoPtr->dwStyle & TCS_FORCELABELLEFT)
1862 center_offset_h = infoPtr->uHItemPadding;
1863 else
1864 if(infoPtr->dwStyle & TCS_VERTICAL)
1865 center_offset_h = ((drawRect->bottom - drawRect->top) - (rcText.right - rcText.left)) / 2;
1866 else
1867 center_offset_h = ((drawRect->right - drawRect->left) - (rcText.right - rcText.left)) / 2;
1869 if(infoPtr->dwStyle & TCS_VERTICAL)
1871 if(infoPtr->dwStyle & TCS_BOTTOM)
1872 drawRect->top+=center_offset_h;
1873 else
1874 drawRect->bottom-=center_offset_h;
1876 center_offset_v = ((drawRect->right - drawRect->left) - (rcText.bottom - rcText.top)) / 2;
1878 else
1880 drawRect->left += center_offset_h;
1881 center_offset_v = ((drawRect->bottom - drawRect->top) - (rcText.bottom - rcText.top)) / 2;
1884 /* if an item is selected, the text is shifted up instead of down */
1885 if (iItem == infoPtr->iSelected)
1886 center_offset_v -= infoPtr->uVItemPadding / 2;
1887 else
1888 center_offset_v += infoPtr->uVItemPadding / 2;
1890 if (center_offset_v < 0)
1891 center_offset_v = 0;
1893 if(infoPtr->dwStyle & TCS_VERTICAL)
1894 drawRect->left += center_offset_v;
1895 else
1896 drawRect->top += center_offset_v;
1898 /* Draw the text */
1899 if(infoPtr->dwStyle & TCS_VERTICAL) /* if we are vertical rotate the text and each character */
1901 LOGFONTW logfont;
1902 HFONT hFont;
1903 INT nEscapement = 900;
1904 INT nOrientation = 900;
1906 if(infoPtr->dwStyle & TCS_BOTTOM)
1908 nEscapement = -900;
1909 nOrientation = -900;
1912 /* to get a font with the escapement and orientation we are looking for, we need to */
1913 /* call CreateFontIndirect, which requires us to set the values of the logfont we pass in */
1914 if (!GetObjectW(infoPtr->hFont, sizeof(logfont), &logfont))
1915 GetObjectW(GetStockObject(DEFAULT_GUI_FONT), sizeof(logfont), &logfont);
1917 logfont.lfEscapement = nEscapement;
1918 logfont.lfOrientation = nOrientation;
1919 hFont = CreateFontIndirectW(&logfont);
1920 SelectObject(hdc, hFont);
1922 if (item->pszText)
1924 ExtTextOutW(hdc,
1925 (infoPtr->dwStyle & TCS_BOTTOM) ? drawRect->right : drawRect->left,
1926 (!(infoPtr->dwStyle & TCS_BOTTOM)) ? drawRect->bottom : drawRect->top,
1927 ETO_CLIPPED,
1928 drawRect,
1929 item->pszText,
1930 lstrlenW(item->pszText),
1934 DeleteObject(hFont);
1936 else
1938 TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%s), textlen=%d\n",
1939 debugstr_w(item->pszText), center_offset_h, center_offset_v,
1940 wine_dbgstr_rect(drawRect), (rcText.right-rcText.left));
1941 if (item->pszText)
1943 DrawTextW
1945 hdc,
1946 item->pszText,
1947 lstrlenW(item->pszText),
1948 drawRect,
1949 DT_LEFT | DT_SINGLELINE
1954 *drawRect = rcTemp; /* restore drawRect */
1958 * Cleanup
1960 SelectObject(hdc, hOldFont);
1961 SetBkMode(hdc, oldBkMode);
1962 SelectObject(hdc, holdPen);
1963 DeleteObject( htextPen );
1966 /******************************************************************************
1967 * TAB_DrawItem
1969 * This method is used to draw a single tab into the tab control.
1971 static void TAB_DrawItem(const TAB_INFO *infoPtr, HDC hdc, INT iItem)
1973 RECT itemRect;
1974 RECT selectedRect;
1975 BOOL isVisible;
1976 RECT r, fillRect, r1;
1977 INT clRight = 0;
1978 INT clBottom = 0;
1979 COLORREF bkgnd, corner;
1980 HTHEME theme;
1983 * Get the rectangle for the item.
1985 isVisible = TAB_InternalGetItemRect(infoPtr,
1986 iItem,
1987 &itemRect,
1988 &selectedRect);
1990 if (isVisible)
1992 RECT rUD, rC;
1994 /* Clip UpDown control to not draw over it */
1995 if (infoPtr->needsScrolling)
1997 GetWindowRect(infoPtr->hwnd, &rC);
1998 GetWindowRect(infoPtr->hwndUpDown, &rUD);
1999 ExcludeClipRect(hdc, rUD.left - rC.left, rUD.top - rC.top, rUD.right - rC.left, rUD.bottom - rC.top);
2002 /* If you need to see what the control is doing,
2003 * then override these variables. They will change what
2004 * fill colors are used for filling the tabs, and the
2005 * corners when drawing the edge.
2007 bkgnd = comctl32_color.clrBtnFace;
2008 corner = comctl32_color.clrBtnFace;
2010 if (infoPtr->dwStyle & TCS_BUTTONS)
2012 /* Get item rectangle */
2013 r = itemRect;
2015 /* Separators between flat buttons */
2016 if ((infoPtr->dwStyle & TCS_FLATBUTTONS) && (infoPtr->exStyle & TCS_EX_FLATSEPARATORS))
2018 r1 = r;
2019 r1.right += (FLAT_BTN_SPACINGX -2);
2020 DrawEdge(hdc, &r1, EDGE_ETCHED, BF_RIGHT);
2023 if (iItem == infoPtr->iSelected)
2025 DrawEdge(hdc, &r, EDGE_SUNKEN, BF_SOFT|BF_RECT);
2027 OffsetRect(&r, 1, 1);
2029 else /* ! selected */
2031 DWORD state = TAB_GetItem(infoPtr, iItem)->dwState;
2033 if ((state & TCIS_BUTTONPRESSED) || (iItem == infoPtr->uFocus))
2034 DrawEdge(hdc, &r, EDGE_SUNKEN, BF_SOFT|BF_RECT);
2035 else
2036 if (!(infoPtr->dwStyle & TCS_FLATBUTTONS))
2037 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_RECT);
2040 else /* !TCS_BUTTONS */
2042 /* We draw a rectangle of different sizes depending on the selection
2043 * state. */
2044 if (iItem == infoPtr->iSelected) {
2045 RECT rect;
2046 GetClientRect (infoPtr->hwnd, &rect);
2047 clRight = rect.right;
2048 clBottom = rect.bottom;
2049 r = selectedRect;
2051 else
2052 r = itemRect;
2055 * Erase the background. (Delay it but setup rectangle.)
2056 * This is necessary when drawing the selected item since it is larger
2057 * than the others, it might overlap with stuff already drawn by the
2058 * other tabs
2060 fillRect = r;
2062 /* Draw themed tabs - but only if they are at the top.
2063 * Windows draws even side or bottom tabs themed, with wacky results.
2064 * However, since in Wine apps may get themed that did not opt in via
2065 * a manifest avoid theming when we know the result will be wrong */
2066 if ((theme = GetWindowTheme (infoPtr->hwnd))
2067 && ((infoPtr->dwStyle & (TCS_VERTICAL | TCS_BOTTOM)) == 0))
2069 static const int partIds[8] = {
2070 /* Normal item */
2071 TABP_TABITEM,
2072 TABP_TABITEMLEFTEDGE,
2073 TABP_TABITEMRIGHTEDGE,
2074 TABP_TABITEMBOTHEDGE,
2075 /* Selected tab */
2076 TABP_TOPTABITEM,
2077 TABP_TOPTABITEMLEFTEDGE,
2078 TABP_TOPTABITEMRIGHTEDGE,
2079 TABP_TOPTABITEMBOTHEDGE,
2081 int partIndex = 0;
2082 int stateId = TIS_NORMAL;
2084 /* selected and unselected tabs have different parts */
2085 if (iItem == infoPtr->iSelected)
2086 partIndex += 4;
2087 /* The part also differs on the position of a tab on a line.
2088 * "Visually" determining the position works well enough. */
2089 GetClientRect(infoPtr->hwnd, &r1);
2090 if(selectedRect.left == 0)
2091 partIndex += 1;
2092 if(selectedRect.right == r1.right)
2093 partIndex += 2;
2095 if (iItem == infoPtr->iSelected)
2096 stateId = TIS_SELECTED;
2097 else if (iItem == infoPtr->iHotTracked)
2098 stateId = TIS_HOT;
2099 else if (iItem == infoPtr->uFocus)
2100 stateId = TIS_FOCUSED;
2102 /* Adjust rectangle for bottommost row */
2103 if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2104 r.bottom += 3;
2106 DrawThemeBackground (theme, hdc, partIds[partIndex], stateId, &r, NULL);
2107 GetThemeBackgroundContentRect (theme, hdc, partIds[partIndex], stateId, &r, &r);
2109 else if(infoPtr->dwStyle & TCS_VERTICAL)
2111 /* These are for adjusting the drawing of a Selected tab */
2112 /* The initial values are for the normal case of non-Selected */
2113 int ZZ = 1; /* Do not stretch if selected */
2114 if (iItem == infoPtr->iSelected) {
2115 ZZ = 0;
2117 /* if leftmost draw the line longer */
2118 if(selectedRect.top == 0)
2119 fillRect.top += CONTROL_BORDER_SIZEY;
2120 /* if rightmost draw the line longer */
2121 if(selectedRect.bottom == clBottom)
2122 fillRect.bottom -= CONTROL_BORDER_SIZEY;
2125 if (infoPtr->dwStyle & TCS_BOTTOM)
2127 /* Adjust both rectangles to match native */
2128 r.left += (1-ZZ);
2130 TRACE("<right> item=%d, fill=(%s), edge=(%s)\n",
2131 iItem, wine_dbgstr_rect(&fillRect), wine_dbgstr_rect(&r));
2133 /* Clear interior */
2134 SetBkColor(hdc, bkgnd);
2135 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2137 /* Draw rectangular edge around tab */
2138 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_RIGHT|BF_TOP|BF_BOTTOM);
2140 /* Now erase the top corner and draw diagonal edge */
2141 SetBkColor(hdc, corner);
2142 r1.left = r.right - ROUND_CORNER_SIZE - 1;
2143 r1.top = r.top;
2144 r1.right = r.right;
2145 r1.bottom = r1.top + ROUND_CORNER_SIZE;
2146 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2147 r1.right--;
2148 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPLEFT);
2150 /* Now erase the bottom corner and draw diagonal edge */
2151 r1.left = r.right - ROUND_CORNER_SIZE - 1;
2152 r1.bottom = r.bottom;
2153 r1.right = r.right;
2154 r1.top = r1.bottom - ROUND_CORNER_SIZE;
2155 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2156 r1.right--;
2157 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMLEFT);
2159 if ((iItem == infoPtr->iSelected) && (selectedRect.top == 0)) {
2160 r1 = r;
2161 r1.right = r1.left;
2162 r1.left--;
2163 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_TOP);
2167 else
2169 TRACE("<left> item=%d, fill=(%s), edge=(%s)\n",
2170 iItem, wine_dbgstr_rect(&fillRect), wine_dbgstr_rect(&r));
2172 /* Clear interior */
2173 SetBkColor(hdc, bkgnd);
2174 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2176 /* Draw rectangular edge around tab */
2177 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_TOP|BF_BOTTOM);
2179 /* Now erase the top corner and draw diagonal edge */
2180 SetBkColor(hdc, corner);
2181 r1.left = r.left;
2182 r1.top = r.top;
2183 r1.right = r1.left + ROUND_CORNER_SIZE + 1;
2184 r1.bottom = r1.top + ROUND_CORNER_SIZE;
2185 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2186 r1.left++;
2187 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPRIGHT);
2189 /* Now erase the bottom corner and draw diagonal edge */
2190 r1.left = r.left;
2191 r1.bottom = r.bottom;
2192 r1.right = r1.left + ROUND_CORNER_SIZE + 1;
2193 r1.top = r1.bottom - ROUND_CORNER_SIZE;
2194 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2195 r1.left++;
2196 DrawEdge(hdc, &r1, EDGE_SUNKEN, BF_DIAGONAL_ENDTOPLEFT);
2199 else /* ! TCS_VERTICAL */
2201 /* These are for adjusting the drawing of a Selected tab */
2202 /* The initial values are for the normal case of non-Selected */
2203 if (iItem == infoPtr->iSelected) {
2204 /* if leftmost draw the line longer */
2205 if(selectedRect.left == 0)
2206 fillRect.left += CONTROL_BORDER_SIZEX;
2207 /* if rightmost draw the line longer */
2208 if(selectedRect.right == clRight)
2209 fillRect.right -= CONTROL_BORDER_SIZEX;
2212 if (infoPtr->dwStyle & TCS_BOTTOM)
2214 /* Adjust both rectangles for topmost row */
2215 if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2217 fillRect.top -= 2;
2218 r.top -= 1;
2221 TRACE("<bottom> item=%d, fill=(%s), edge=(%s)\n",
2222 iItem, wine_dbgstr_rect(&fillRect), wine_dbgstr_rect(&r));
2224 /* Clear interior */
2225 SetBkColor(hdc, bkgnd);
2226 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2228 /* Draw rectangular edge around tab */
2229 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_BOTTOM|BF_RIGHT);
2231 /* Now erase the righthand corner and draw diagonal edge */
2232 SetBkColor(hdc, corner);
2233 r1.left = r.right - ROUND_CORNER_SIZE;
2234 r1.bottom = r.bottom;
2235 r1.right = r.right;
2236 r1.top = r1.bottom - ROUND_CORNER_SIZE - 1;
2237 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2238 r1.bottom--;
2239 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMLEFT);
2241 /* Now erase the lefthand corner and draw diagonal edge */
2242 r1.left = r.left;
2243 r1.bottom = r.bottom;
2244 r1.right = r1.left + ROUND_CORNER_SIZE;
2245 r1.top = r1.bottom - ROUND_CORNER_SIZE - 1;
2246 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2247 r1.bottom--;
2248 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPLEFT);
2250 if (iItem == infoPtr->iSelected)
2252 r.top += 2;
2253 r.left += 1;
2254 if (selectedRect.left == 0)
2256 r1 = r;
2257 r1.bottom = r1.top;
2258 r1.top--;
2259 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_LEFT);
2264 else
2266 /* Adjust both rectangles for bottommost row */
2267 if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2269 fillRect.bottom += 3;
2270 r.bottom += 2;
2273 TRACE("<top> item=%d, fill=(%s), edge=(%s)\n",
2274 iItem, wine_dbgstr_rect(&fillRect), wine_dbgstr_rect(&r));
2276 /* Clear interior */
2277 SetBkColor(hdc, bkgnd);
2278 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2280 /* Draw rectangular edge around tab */
2281 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_TOP|BF_RIGHT);
2283 /* Now erase the righthand corner and draw diagonal edge */
2284 SetBkColor(hdc, corner);
2285 r1.left = r.right - ROUND_CORNER_SIZE;
2286 r1.top = r.top;
2287 r1.right = r.right;
2288 r1.bottom = r1.top + ROUND_CORNER_SIZE + 1;
2289 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2290 r1.top++;
2291 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMRIGHT);
2293 /* Now erase the lefthand corner and draw diagonal edge */
2294 r1.left = r.left;
2295 r1.top = r.top;
2296 r1.right = r1.left + ROUND_CORNER_SIZE;
2297 r1.bottom = r1.top + ROUND_CORNER_SIZE + 1;
2298 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2299 r1.top++;
2300 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPRIGHT);
2305 TAB_DumpItemInternal(infoPtr, iItem);
2307 /* This modifies r to be the text rectangle. */
2308 TAB_DrawItemInterior(infoPtr, hdc, iItem, &r);
2312 /******************************************************************************
2313 * TAB_DrawBorder
2315 * This method is used to draw the raised border around the tab control
2316 * "content" area.
2318 static void TAB_DrawBorder(const TAB_INFO *infoPtr, HDC hdc)
2320 RECT rect;
2321 HTHEME theme = GetWindowTheme (infoPtr->hwnd);
2323 GetClientRect (infoPtr->hwnd, &rect);
2326 * Adjust for the style
2329 if (infoPtr->uNumItem)
2331 if ((infoPtr->dwStyle & TCS_BOTTOM) && !(infoPtr->dwStyle & TCS_VERTICAL))
2332 rect.bottom -= infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2333 else if((infoPtr->dwStyle & TCS_BOTTOM) && (infoPtr->dwStyle & TCS_VERTICAL))
2334 rect.right -= infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2335 else if(infoPtr->dwStyle & TCS_VERTICAL)
2336 rect.left += infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2337 else /* not TCS_VERTICAL and not TCS_BOTTOM */
2338 rect.top += infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2341 TRACE("border=(%s)\n", wine_dbgstr_rect(&rect));
2343 if (theme)
2344 DrawThemeBackground (theme, hdc, TABP_PANE, 0, &rect, NULL);
2345 else
2346 DrawEdge(hdc, &rect, EDGE_RAISED, BF_SOFT|BF_RECT);
2349 /******************************************************************************
2350 * TAB_Refresh
2352 * This method repaints the tab control..
2354 static void TAB_Refresh (const TAB_INFO *infoPtr, HDC hdc)
2356 HFONT hOldFont;
2357 INT i;
2359 if (!infoPtr->DoRedraw)
2360 return;
2362 hOldFont = SelectObject (hdc, infoPtr->hFont);
2364 if (infoPtr->dwStyle & TCS_BUTTONS)
2366 for (i = 0; i < infoPtr->uNumItem; i++)
2367 TAB_DrawItem (infoPtr, hdc, i);
2369 else
2371 /* Draw all the non selected item first */
2372 for (i = 0; i < infoPtr->uNumItem; i++)
2374 if (i != infoPtr->iSelected)
2375 TAB_DrawItem (infoPtr, hdc, i);
2378 /* Now, draw the border, draw it before the selected item
2379 * since the selected item overwrites part of the border. */
2380 TAB_DrawBorder (infoPtr, hdc);
2382 /* Then, draw the selected item */
2383 TAB_DrawItem (infoPtr, hdc, infoPtr->iSelected);
2386 SelectObject (hdc, hOldFont);
2389 static inline DWORD TAB_GetRowCount (const TAB_INFO *infoPtr)
2391 TRACE("(%p)\n", infoPtr);
2392 return infoPtr->uNumRows;
2395 static inline LRESULT TAB_SetRedraw (TAB_INFO *infoPtr, BOOL doRedraw)
2397 infoPtr->DoRedraw = doRedraw;
2398 return 0;
2401 /******************************************************************************
2402 * TAB_EnsureSelectionVisible
2404 * This method will make sure that the current selection is completely
2405 * visible by scrolling until it is.
2407 static void TAB_EnsureSelectionVisible(
2408 TAB_INFO* infoPtr)
2410 INT iSelected = infoPtr->iSelected;
2411 INT iOrigLeftmostVisible = infoPtr->leftmostVisible;
2413 if (iSelected < 0)
2414 return;
2416 /* set the items row to the bottommost row or topmost row depending on
2417 * style */
2418 if ((infoPtr->uNumRows > 1) && !(infoPtr->dwStyle & TCS_BUTTONS))
2420 TAB_ITEM *selected = TAB_GetItem(infoPtr, iSelected);
2421 INT newselected;
2422 INT iTargetRow;
2424 if(infoPtr->dwStyle & TCS_VERTICAL)
2425 newselected = selected->rect.left;
2426 else
2427 newselected = selected->rect.top;
2429 /* the target row is always (number of rows - 1)
2430 as row 0 is furthest from the clientRect */
2431 iTargetRow = infoPtr->uNumRows - 1;
2433 if (newselected != iTargetRow)
2435 UINT i;
2436 if(infoPtr->dwStyle & TCS_VERTICAL)
2438 for (i=0; i < infoPtr->uNumItem; i++)
2440 /* move everything in the row of the selected item to the iTargetRow */
2441 TAB_ITEM *item = TAB_GetItem(infoPtr, i);
2443 if (item->rect.left == newselected )
2444 item->rect.left = iTargetRow;
2445 else
2447 if (item->rect.left > newselected)
2448 item->rect.left-=1;
2452 else
2454 for (i=0; i < infoPtr->uNumItem; i++)
2456 TAB_ITEM *item = TAB_GetItem(infoPtr, i);
2458 if (item->rect.top == newselected )
2459 item->rect.top = iTargetRow;
2460 else
2462 if (item->rect.top > newselected)
2463 item->rect.top-=1;
2467 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
2472 * Do the trivial cases first.
2474 if ( (!infoPtr->needsScrolling) ||
2475 (infoPtr->hwndUpDown==0) || (infoPtr->dwStyle & TCS_VERTICAL))
2476 return;
2478 if (infoPtr->leftmostVisible >= iSelected)
2480 infoPtr->leftmostVisible = iSelected;
2482 else
2484 TAB_ITEM *selected = TAB_GetItem(infoPtr, iSelected);
2485 RECT r;
2486 INT width;
2487 UINT i;
2489 /* Calculate the part of the client area that is visible */
2490 GetClientRect(infoPtr->hwnd, &r);
2491 width = r.right;
2493 GetClientRect(infoPtr->hwndUpDown, &r);
2494 width -= r.right;
2496 if ((selected->rect.right -
2497 selected->rect.left) >= width )
2499 /* Special case: width of selected item is greater than visible
2500 * part of control.
2502 infoPtr->leftmostVisible = iSelected;
2504 else
2506 for (i = infoPtr->leftmostVisible; i < infoPtr->uNumItem; i++)
2508 if ((selected->rect.right - TAB_GetItem(infoPtr, i)->rect.left) < width)
2509 break;
2511 infoPtr->leftmostVisible = i;
2515 if (infoPtr->leftmostVisible != iOrigLeftmostVisible)
2516 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
2518 SendMessageW(infoPtr->hwndUpDown, UDM_SETPOS, 0,
2519 MAKELONG(infoPtr->leftmostVisible, 0));
2522 /******************************************************************************
2523 * TAB_InvalidateTabArea
2525 * This method will invalidate the portion of the control that contains the
2526 * tabs. It is called when the state of the control changes and needs
2527 * to be redisplayed
2529 static void TAB_InvalidateTabArea(const TAB_INFO *infoPtr)
2531 RECT clientRect, rInvalidate, rAdjClient;
2532 INT lastRow = infoPtr->uNumRows - 1;
2533 RECT rect;
2535 if (lastRow < 0) return;
2537 GetClientRect(infoPtr->hwnd, &clientRect);
2538 rInvalidate = clientRect;
2539 rAdjClient = clientRect;
2541 TAB_AdjustRect(infoPtr, 0, &rAdjClient);
2543 TAB_InternalGetItemRect(infoPtr, infoPtr->uNumItem-1 , &rect, NULL);
2544 if ((infoPtr->dwStyle & TCS_BOTTOM) && (infoPtr->dwStyle & TCS_VERTICAL))
2546 rInvalidate.left = rAdjClient.right;
2547 if (infoPtr->uNumRows == 1)
2548 rInvalidate.bottom = clientRect.top + rect.bottom + 2 * SELECTED_TAB_OFFSET;
2550 else if(infoPtr->dwStyle & TCS_VERTICAL)
2552 rInvalidate.right = rAdjClient.left;
2553 if (infoPtr->uNumRows == 1)
2554 rInvalidate.bottom = clientRect.top + rect.bottom + 2 * SELECTED_TAB_OFFSET;
2556 else if (infoPtr->dwStyle & TCS_BOTTOM)
2558 rInvalidate.top = rAdjClient.bottom;
2559 if (infoPtr->uNumRows == 1)
2560 rInvalidate.right = clientRect.left + rect.right + 2 * SELECTED_TAB_OFFSET;
2562 else
2564 rInvalidate.bottom = rAdjClient.top;
2565 if (infoPtr->uNumRows == 1)
2566 rInvalidate.right = clientRect.left + rect.right + 2 * SELECTED_TAB_OFFSET;
2569 /* Punch out the updown control */
2570 if (infoPtr->needsScrolling && (rInvalidate.right > 0)) {
2571 RECT r;
2572 GetClientRect(infoPtr->hwndUpDown, &r);
2573 if (rInvalidate.right > clientRect.right - r.left)
2574 rInvalidate.right = rInvalidate.right - (r.right - r.left);
2575 else
2576 rInvalidate.right = clientRect.right - r.left;
2579 TRACE("invalidate (%s)\n", wine_dbgstr_rect(&rInvalidate));
2581 InvalidateRect(infoPtr->hwnd, &rInvalidate, TRUE);
2584 static inline LRESULT TAB_Paint (TAB_INFO *infoPtr, HDC hdcPaint)
2586 HDC hdc;
2587 PAINTSTRUCT ps;
2589 if (hdcPaint)
2590 hdc = hdcPaint;
2591 else
2593 hdc = BeginPaint (infoPtr->hwnd, &ps);
2594 TRACE("erase %d, rect=(%s)\n", ps.fErase, wine_dbgstr_rect(&ps.rcPaint));
2597 TAB_Refresh (infoPtr, hdc);
2599 if (!hdcPaint)
2600 EndPaint (infoPtr->hwnd, &ps);
2602 return 0;
2605 static LRESULT
2606 TAB_InsertItemT (TAB_INFO *infoPtr, INT iItem, const TCITEMW *pti, BOOL bUnicode)
2608 TAB_ITEM *item;
2609 RECT rect;
2611 GetClientRect (infoPtr->hwnd, &rect);
2612 TRACE("Rect: %p %s\n", infoPtr->hwnd, wine_dbgstr_rect(&rect));
2614 if (iItem < 0) return -1;
2615 if (iItem > infoPtr->uNumItem)
2616 iItem = infoPtr->uNumItem;
2618 TAB_DumpItemExternalT(pti, iItem, bUnicode);
2620 if (!(item = Alloc(TAB_ITEM_SIZE(infoPtr)))) return FALSE;
2621 if (DPA_InsertPtr(infoPtr->items, iItem, item) == -1)
2623 Free(item);
2624 return FALSE;
2627 if (infoPtr->uNumItem == 0)
2628 infoPtr->iSelected = 0;
2629 else if (iItem <= infoPtr->iSelected)
2630 infoPtr->iSelected++;
2632 infoPtr->uNumItem++;
2634 item->pszText = NULL;
2635 if (pti->mask & TCIF_TEXT)
2637 if (bUnicode)
2638 Str_SetPtrW (&item->pszText, pti->pszText);
2639 else
2640 Str_SetPtrAtoW (&item->pszText, (LPSTR)pti->pszText);
2643 if (pti->mask & TCIF_IMAGE)
2644 item->iImage = pti->iImage;
2645 else
2646 item->iImage = -1;
2648 if (pti->mask & TCIF_PARAM)
2649 memcpy(item->extra, &pti->lParam, EXTRA_ITEM_SIZE(infoPtr));
2650 else
2651 memset(item->extra, 0, EXTRA_ITEM_SIZE(infoPtr));
2653 TAB_SetItemBounds(infoPtr);
2654 if (infoPtr->uNumItem > 1)
2655 TAB_InvalidateTabArea(infoPtr);
2656 else
2657 InvalidateRect(infoPtr->hwnd, NULL, TRUE);
2659 TRACE("[%p]: added item %d %s\n",
2660 infoPtr->hwnd, iItem, debugstr_w(item->pszText));
2662 /* If we haven't set the current focus yet, set it now. */
2663 if (infoPtr->uFocus == -1)
2664 TAB_SetCurFocus(infoPtr, iItem);
2666 return iItem;
2669 static LRESULT
2670 TAB_SetItemSize (TAB_INFO *infoPtr, INT cx, INT cy)
2672 LONG lResult = 0;
2673 BOOL bNeedPaint = FALSE;
2675 lResult = MAKELONG(infoPtr->tabWidth, infoPtr->tabHeight);
2677 /* UNDOCUMENTED: If requested Width or Height is 0 this means that program wants to use auto size. */
2678 if (infoPtr->dwStyle & TCS_FIXEDWIDTH && (infoPtr->tabWidth != cx))
2680 infoPtr->tabWidth = cx;
2681 bNeedPaint = TRUE;
2684 if (infoPtr->tabHeight != cy)
2686 if ((infoPtr->fHeightSet = (cy != 0)))
2687 infoPtr->tabHeight = cy;
2689 bNeedPaint = TRUE;
2691 TRACE("was h=%d,w=%d, now h=%d,w=%d\n",
2692 HIWORD(lResult), LOWORD(lResult),
2693 infoPtr->tabHeight, infoPtr->tabWidth);
2695 if (bNeedPaint)
2697 TAB_SetItemBounds(infoPtr);
2698 RedrawWindow(infoPtr->hwnd, NULL, NULL, RDW_ERASE | RDW_INVALIDATE | RDW_UPDATENOW);
2701 return lResult;
2704 static inline LRESULT TAB_SetMinTabWidth (TAB_INFO *infoPtr, INT cx)
2706 INT oldcx = 0;
2708 TRACE("(%p,%d)\n", infoPtr, cx);
2710 if (infoPtr->tabMinWidth < 0)
2711 oldcx = DEFAULT_MIN_TAB_WIDTH;
2712 else
2713 oldcx = infoPtr->tabMinWidth;
2714 infoPtr->tabMinWidth = cx;
2715 TAB_SetItemBounds(infoPtr);
2716 return oldcx;
2719 static inline LRESULT
2720 TAB_HighlightItem (TAB_INFO *infoPtr, INT iItem, BOOL fHighlight)
2722 LPDWORD lpState;
2723 DWORD oldState;
2724 RECT r;
2726 TRACE("(%p,%d,%s)\n", infoPtr, iItem, fHighlight ? "true" : "false");
2728 if (iItem < 0 || iItem >= infoPtr->uNumItem)
2729 return FALSE;
2731 lpState = &TAB_GetItem(infoPtr, iItem)->dwState;
2732 oldState = *lpState;
2734 if (fHighlight)
2735 *lpState |= TCIS_HIGHLIGHTED;
2736 else
2737 *lpState &= ~TCIS_HIGHLIGHTED;
2739 if ((oldState != *lpState) && TAB_InternalGetItemRect (infoPtr, iItem, &r, NULL))
2740 InvalidateRect (infoPtr->hwnd, &r, TRUE);
2742 return TRUE;
2745 static LRESULT
2746 TAB_SetItemT (TAB_INFO *infoPtr, INT iItem, LPTCITEMW tabItem, BOOL bUnicode)
2748 TAB_ITEM *wineItem;
2750 TRACE("(%p,%d,%p,%s)\n", infoPtr, iItem, tabItem, bUnicode ? "true" : "false");
2752 if (iItem < 0 || iItem >= infoPtr->uNumItem)
2753 return FALSE;
2755 TAB_DumpItemExternalT(tabItem, iItem, bUnicode);
2757 wineItem = TAB_GetItem(infoPtr, iItem);
2759 if (tabItem->mask & TCIF_IMAGE)
2760 wineItem->iImage = tabItem->iImage;
2762 if (tabItem->mask & TCIF_PARAM)
2763 memcpy(wineItem->extra, &tabItem->lParam, infoPtr->cbInfo);
2765 if (tabItem->mask & TCIF_RTLREADING)
2766 FIXME("TCIF_RTLREADING\n");
2768 if (tabItem->mask & TCIF_STATE)
2769 wineItem->dwState = (wineItem->dwState & ~tabItem->dwStateMask) |
2770 ( tabItem->dwState & tabItem->dwStateMask);
2772 if (tabItem->mask & TCIF_TEXT)
2774 Free(wineItem->pszText);
2775 wineItem->pszText = NULL;
2776 if (bUnicode)
2777 Str_SetPtrW(&wineItem->pszText, tabItem->pszText);
2778 else
2779 Str_SetPtrAtoW(&wineItem->pszText, (LPSTR)tabItem->pszText);
2782 /* Update and repaint tabs */
2783 TAB_SetItemBounds(infoPtr);
2784 TAB_InvalidateTabArea(infoPtr);
2786 return TRUE;
2789 static inline LRESULT TAB_GetItemCount (const TAB_INFO *infoPtr)
2791 TRACE("\n");
2792 return infoPtr->uNumItem;
2796 static LRESULT
2797 TAB_GetItemT (TAB_INFO *infoPtr, INT iItem, LPTCITEMW tabItem, BOOL bUnicode)
2799 TAB_ITEM *wineItem;
2801 TRACE("(%p,%d,%p,%s)\n", infoPtr, iItem, tabItem, bUnicode ? "true" : "false");
2803 if (!tabItem) return FALSE;
2805 if (iItem < 0 || iItem >= infoPtr->uNumItem)
2807 /* init requested fields */
2808 if (tabItem->mask & TCIF_IMAGE) tabItem->iImage = 0;
2809 if (tabItem->mask & TCIF_PARAM) tabItem->lParam = 0;
2810 if (tabItem->mask & TCIF_STATE) tabItem->dwState = 0;
2811 return FALSE;
2814 wineItem = TAB_GetItem(infoPtr, iItem);
2816 if (tabItem->mask & TCIF_IMAGE)
2817 tabItem->iImage = wineItem->iImage;
2819 if (tabItem->mask & TCIF_PARAM)
2820 memcpy(&tabItem->lParam, wineItem->extra, infoPtr->cbInfo);
2822 if (tabItem->mask & TCIF_RTLREADING)
2823 FIXME("TCIF_RTLREADING\n");
2825 if (tabItem->mask & TCIF_STATE)
2826 tabItem->dwState = wineItem->dwState & tabItem->dwStateMask;
2828 if (tabItem->mask & TCIF_TEXT)
2830 if (bUnicode)
2831 Str_GetPtrW (wineItem->pszText, tabItem->pszText, tabItem->cchTextMax);
2832 else
2833 Str_GetPtrWtoA (wineItem->pszText, (LPSTR)tabItem->pszText, tabItem->cchTextMax);
2836 TAB_DumpItemExternalT(tabItem, iItem, bUnicode);
2838 return TRUE;
2842 static LRESULT TAB_DeleteItem (TAB_INFO *infoPtr, INT iItem)
2844 TAB_ITEM *item;
2846 TRACE("(%p, %d)\n", infoPtr, iItem);
2848 if (iItem < 0 || iItem >= infoPtr->uNumItem) return FALSE;
2850 TAB_InvalidateTabArea(infoPtr);
2851 item = TAB_GetItem(infoPtr, iItem);
2852 Free(item->pszText);
2853 Free(item);
2854 infoPtr->uNumItem--;
2855 DPA_DeletePtr(infoPtr->items, iItem);
2857 if (infoPtr->uNumItem == 0)
2859 if (infoPtr->iHotTracked >= 0)
2861 KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
2862 infoPtr->iHotTracked = -1;
2865 infoPtr->iSelected = -1;
2867 else
2869 if (iItem <= infoPtr->iHotTracked)
2871 /* When tabs move left/up, the hot track item may change */
2872 FIXME("Recalc hot track\n");
2876 /* adjust the selected index */
2877 if (iItem == infoPtr->iSelected)
2878 infoPtr->iSelected = -1;
2879 else if (iItem < infoPtr->iSelected)
2880 infoPtr->iSelected--;
2882 /* reposition and repaint tabs */
2883 TAB_SetItemBounds(infoPtr);
2885 return TRUE;
2888 static inline LRESULT TAB_DeleteAllItems (TAB_INFO *infoPtr)
2890 TRACE("(%p)\n", infoPtr);
2891 while (infoPtr->uNumItem)
2892 TAB_DeleteItem (infoPtr, 0);
2893 return TRUE;
2897 static inline LRESULT TAB_GetFont (const TAB_INFO *infoPtr)
2899 TRACE("(%p) returning %p\n", infoPtr, infoPtr->hFont);
2900 return (LRESULT)infoPtr->hFont;
2903 static inline LRESULT TAB_SetFont (TAB_INFO *infoPtr, HFONT hNewFont)
2905 TRACE("(%p,%p)\n", infoPtr, hNewFont);
2907 infoPtr->hFont = hNewFont;
2909 TAB_SetItemBounds(infoPtr);
2911 TAB_InvalidateTabArea(infoPtr);
2913 return 0;
2917 static inline LRESULT TAB_GetImageList (const TAB_INFO *infoPtr)
2919 TRACE("\n");
2920 return (LRESULT)infoPtr->himl;
2923 static inline LRESULT TAB_SetImageList (TAB_INFO *infoPtr, HIMAGELIST himlNew)
2925 HIMAGELIST himlPrev = infoPtr->himl;
2926 TRACE("himl=%p\n", himlNew);
2927 infoPtr->himl = himlNew;
2928 TAB_SetItemBounds(infoPtr);
2929 InvalidateRect(infoPtr->hwnd, NULL, TRUE);
2930 return (LRESULT)himlPrev;
2933 static inline LRESULT TAB_GetUnicodeFormat (const TAB_INFO *infoPtr)
2935 TRACE("(%p)\n", infoPtr);
2936 return infoPtr->bUnicode;
2939 static inline LRESULT TAB_SetUnicodeFormat (TAB_INFO *infoPtr, BOOL bUnicode)
2941 BOOL bTemp = infoPtr->bUnicode;
2943 TRACE("(%p %d)\n", infoPtr, bUnicode);
2944 infoPtr->bUnicode = bUnicode;
2946 return bTemp;
2949 static inline LRESULT TAB_Size (TAB_INFO *infoPtr)
2951 /* I'm not really sure what the following code was meant to do.
2952 This is what it is doing:
2953 When WM_SIZE is sent with SIZE_RESTORED, the control
2954 gets positioned in the top left corner.
2956 RECT parent_rect;
2957 HWND parent;
2958 UINT uPosFlags,cx,cy;
2960 uPosFlags=0;
2961 if (!wParam) {
2962 parent = GetParent (hwnd);
2963 GetClientRect(parent, &parent_rect);
2964 cx=LOWORD (lParam);
2965 cy=HIWORD (lParam);
2966 if (GetWindowLongW(hwnd, GWL_STYLE) & CCS_NORESIZE)
2967 uPosFlags |= (SWP_NOSIZE | SWP_NOMOVE);
2969 SetWindowPos (hwnd, 0, parent_rect.left, parent_rect.top,
2970 cx, cy, uPosFlags | SWP_NOZORDER);
2971 } else {
2972 FIXME("WM_SIZE flag %x %lx not handled\n", wParam, lParam);
2973 } */
2975 /* Recompute the size/position of the tabs. */
2976 TAB_SetItemBounds (infoPtr);
2978 /* Force a repaint of the control. */
2979 InvalidateRect(infoPtr->hwnd, NULL, TRUE);
2981 return 0;
2985 static LRESULT TAB_Create (HWND hwnd, LPARAM lParam)
2987 TAB_INFO *infoPtr;
2988 TEXTMETRICW fontMetrics;
2989 HDC hdc;
2990 HFONT hOldFont;
2991 DWORD style;
2993 infoPtr = Alloc (sizeof(TAB_INFO));
2995 SetWindowLongPtrW(hwnd, 0, (DWORD_PTR)infoPtr);
2997 infoPtr->hwnd = hwnd;
2998 infoPtr->hwndNotify = ((LPCREATESTRUCTW)lParam)->hwndParent;
2999 infoPtr->uNumItem = 0;
3000 infoPtr->uNumRows = 0;
3001 infoPtr->uHItemPadding = 6;
3002 infoPtr->uVItemPadding = 3;
3003 infoPtr->uHItemPadding_s = 6;
3004 infoPtr->uVItemPadding_s = 3;
3005 infoPtr->hFont = 0;
3006 infoPtr->items = DPA_Create(8);
3007 infoPtr->hcurArrow = LoadCursorW (0, (LPWSTR)IDC_ARROW);
3008 infoPtr->iSelected = -1;
3009 infoPtr->iHotTracked = -1;
3010 infoPtr->uFocus = -1;
3011 infoPtr->hwndToolTip = 0;
3012 infoPtr->DoRedraw = TRUE;
3013 infoPtr->needsScrolling = FALSE;
3014 infoPtr->hwndUpDown = 0;
3015 infoPtr->leftmostVisible = 0;
3016 infoPtr->fHeightSet = FALSE;
3017 infoPtr->bUnicode = IsWindowUnicode (hwnd);
3018 infoPtr->cbInfo = sizeof(LPARAM);
3020 TRACE("Created tab control, hwnd [%p]\n", hwnd);
3022 /* The tab control always has the WS_CLIPSIBLINGS style. Even
3023 if you don't specify it in CreateWindow. This is necessary in
3024 order for paint to work correctly. This follows windows behaviour. */
3025 style = GetWindowLongW(hwnd, GWL_STYLE);
3026 if (style & TCS_VERTICAL) style |= TCS_MULTILINE;
3027 style |= WS_CLIPSIBLINGS;
3028 SetWindowLongW(hwnd, GWL_STYLE, style);
3030 infoPtr->dwStyle = style;
3031 infoPtr->exStyle = (style & TCS_FLATBUTTONS) ? TCS_EX_FLATSEPARATORS : 0;
3033 if (infoPtr->dwStyle & TCS_TOOLTIPS) {
3034 /* Create tooltip control */
3035 infoPtr->hwndToolTip =
3036 CreateWindowExW (0, TOOLTIPS_CLASSW, NULL, WS_POPUP,
3037 CW_USEDEFAULT, CW_USEDEFAULT,
3038 CW_USEDEFAULT, CW_USEDEFAULT,
3039 hwnd, 0, 0, 0);
3041 /* Send NM_TOOLTIPSCREATED notification */
3042 if (infoPtr->hwndToolTip) {
3043 NMTOOLTIPSCREATED nmttc;
3045 nmttc.hdr.hwndFrom = hwnd;
3046 nmttc.hdr.idFrom = GetWindowLongPtrW(hwnd, GWLP_ID);
3047 nmttc.hdr.code = NM_TOOLTIPSCREATED;
3048 nmttc.hwndToolTips = infoPtr->hwndToolTip;
3050 SendMessageW (infoPtr->hwndNotify, WM_NOTIFY,
3051 GetWindowLongPtrW(hwnd, GWLP_ID), (LPARAM)&nmttc);
3055 OpenThemeData (infoPtr->hwnd, themeClass);
3058 * We need to get text information so we need a DC and we need to select
3059 * a font.
3061 hdc = GetDC(hwnd);
3062 hOldFont = SelectObject (hdc, GetStockObject (SYSTEM_FONT));
3064 /* Use the system font to determine the initial height of a tab. */
3065 GetTextMetricsW(hdc, &fontMetrics);
3068 * Make sure there is enough space for the letters + growing the
3069 * selected item + extra space for the selected item.
3071 infoPtr->tabHeight = fontMetrics.tmHeight + SELECTED_TAB_OFFSET +
3072 ((infoPtr->dwStyle & TCS_BUTTONS) ? 2 : 1) *
3073 infoPtr->uVItemPadding;
3075 /* Initialize the width of a tab. */
3076 if (infoPtr->dwStyle & TCS_FIXEDWIDTH)
3077 infoPtr->tabWidth = GetDeviceCaps(hdc, LOGPIXELSX);
3079 infoPtr->tabMinWidth = -1;
3081 TRACE("tabH=%d, tabW=%d\n", infoPtr->tabHeight, infoPtr->tabWidth);
3083 SelectObject (hdc, hOldFont);
3084 ReleaseDC(hwnd, hdc);
3086 return 0;
3089 static LRESULT
3090 TAB_Destroy (TAB_INFO *infoPtr)
3092 INT iItem;
3094 SetWindowLongPtrW(infoPtr->hwnd, 0, 0);
3096 for (iItem = infoPtr->uNumItem - 1; iItem >= 0; iItem--)
3098 TAB_ITEM *tab = TAB_GetItem(infoPtr, iItem);
3100 DPA_DeletePtr(infoPtr->items, iItem);
3101 infoPtr->uNumItem--;
3103 Free(tab->pszText);
3104 Free(tab);
3106 DPA_Destroy(infoPtr->items);
3107 infoPtr->items = NULL;
3109 if (infoPtr->hwndToolTip)
3110 DestroyWindow (infoPtr->hwndToolTip);
3112 if (infoPtr->hwndUpDown)
3113 DestroyWindow(infoPtr->hwndUpDown);
3115 if (infoPtr->iHotTracked >= 0)
3116 KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
3118 CloseThemeData (GetWindowTheme (infoPtr->hwnd));
3120 Free (infoPtr);
3121 return 0;
3124 /* update theme after a WM_THEMECHANGED message */
3125 static LRESULT theme_changed(const TAB_INFO *infoPtr)
3127 HTHEME theme = GetWindowTheme (infoPtr->hwnd);
3128 CloseThemeData (theme);
3129 OpenThemeData (infoPtr->hwnd, themeClass);
3130 return 0;
3133 static LRESULT TAB_NCCalcSize(WPARAM wParam)
3135 if (!wParam)
3136 return 0;
3137 return WVR_ALIGNTOP;
3140 static inline LRESULT
3141 TAB_SetItemExtra (TAB_INFO *infoPtr, INT cbInfo)
3143 TRACE("(%p %d)\n", infoPtr, cbInfo);
3145 if (cbInfo < 0 || infoPtr->uNumItem) return FALSE;
3147 infoPtr->cbInfo = cbInfo;
3148 return TRUE;
3151 static LRESULT TAB_RemoveImage (TAB_INFO *infoPtr, INT image)
3153 TRACE("%p %d\n", infoPtr, image);
3155 if (ImageList_Remove (infoPtr->himl, image))
3157 INT i, *idx;
3158 RECT r;
3160 /* shift indices, repaint items if needed */
3161 for (i = 0; i < infoPtr->uNumItem; i++)
3163 idx = &TAB_GetItem(infoPtr, i)->iImage;
3164 if (*idx >= image)
3166 if (*idx == image)
3167 *idx = -1;
3168 else
3169 (*idx)--;
3171 /* repaint item */
3172 if (TAB_InternalGetItemRect (infoPtr, i, &r, NULL))
3173 InvalidateRect (infoPtr->hwnd, &r, TRUE);
3178 return 0;
3181 static LRESULT
3182 TAB_SetExtendedStyle (TAB_INFO *infoPtr, DWORD exMask, DWORD exStyle)
3184 DWORD prevstyle = infoPtr->exStyle;
3186 /* zero mask means all styles */
3187 if (exMask == 0) exMask = ~0;
3189 if (exMask & TCS_EX_REGISTERDROP)
3191 FIXME("TCS_EX_REGISTERDROP style unimplemented\n");
3192 exMask &= ~TCS_EX_REGISTERDROP;
3193 exStyle &= ~TCS_EX_REGISTERDROP;
3196 if (exMask & TCS_EX_FLATSEPARATORS)
3198 if ((prevstyle ^ exStyle) & TCS_EX_FLATSEPARATORS)
3200 infoPtr->exStyle ^= TCS_EX_FLATSEPARATORS;
3201 TAB_InvalidateTabArea(infoPtr);
3205 return prevstyle;
3208 static inline LRESULT
3209 TAB_GetExtendedStyle (const TAB_INFO *infoPtr)
3211 return infoPtr->exStyle;
3214 static LRESULT
3215 TAB_DeselectAll (TAB_INFO *infoPtr, BOOL excludesel)
3217 BOOL paint = FALSE;
3218 INT i, selected = infoPtr->iSelected;
3220 TRACE("(%p, %d)\n", infoPtr, excludesel);
3222 if (!(infoPtr->dwStyle & TCS_BUTTONS))
3223 return 0;
3225 for (i = 0; i < infoPtr->uNumItem; i++)
3227 if ((TAB_GetItem(infoPtr, i)->dwState & TCIS_BUTTONPRESSED) &&
3228 (selected != i))
3230 TAB_GetItem(infoPtr, i)->dwState &= ~TCIS_BUTTONPRESSED;
3231 paint = TRUE;
3235 if (!excludesel && (selected != -1))
3237 TAB_GetItem(infoPtr, selected)->dwState &= ~TCIS_BUTTONPRESSED;
3238 infoPtr->iSelected = -1;
3239 paint = TRUE;
3242 if (paint)
3243 TAB_InvalidateTabArea (infoPtr);
3245 return 0;
3248 /***
3249 * DESCRIPTION:
3250 * Processes WM_STYLECHANGED messages.
3252 * PARAMETER(S):
3253 * [I] infoPtr : valid pointer to the tab data structure
3254 * [I] wStyleType : window style type (normal or extended)
3255 * [I] lpss : window style information
3257 * RETURN:
3258 * Zero
3260 static INT TAB_StyleChanged(TAB_INFO *infoPtr, WPARAM wStyleType,
3261 const STYLESTRUCT *lpss)
3263 TRACE("(styletype=%lx, styleOld=0x%08x, styleNew=0x%08x)\n",
3264 wStyleType, lpss->styleOld, lpss->styleNew);
3266 if (wStyleType != GWL_STYLE) return 0;
3268 infoPtr->dwStyle = lpss->styleNew;
3270 TAB_SetItemBounds (infoPtr);
3271 InvalidateRect(infoPtr->hwnd, NULL, TRUE);
3273 return 0;
3276 static LRESULT WINAPI
3277 TAB_WindowProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
3279 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
3281 TRACE("hwnd=%p msg=%x wParam=%lx lParam=%lx\n", hwnd, uMsg, wParam, lParam);
3282 if (!infoPtr && (uMsg != WM_CREATE))
3283 return DefWindowProcW (hwnd, uMsg, wParam, lParam);
3285 switch (uMsg)
3287 case TCM_GETIMAGELIST:
3288 return TAB_GetImageList (infoPtr);
3290 case TCM_SETIMAGELIST:
3291 return TAB_SetImageList (infoPtr, (HIMAGELIST)lParam);
3293 case TCM_GETITEMCOUNT:
3294 return TAB_GetItemCount (infoPtr);
3296 case TCM_GETITEMA:
3297 case TCM_GETITEMW:
3298 return TAB_GetItemT (infoPtr, (INT)wParam, (LPTCITEMW)lParam, uMsg == TCM_GETITEMW);
3300 case TCM_SETITEMA:
3301 case TCM_SETITEMW:
3302 return TAB_SetItemT (infoPtr, (INT)wParam, (LPTCITEMW)lParam, uMsg == TCM_SETITEMW);
3304 case TCM_DELETEITEM:
3305 return TAB_DeleteItem (infoPtr, (INT)wParam);
3307 case TCM_DELETEALLITEMS:
3308 return TAB_DeleteAllItems (infoPtr);
3310 case TCM_GETITEMRECT:
3311 return TAB_GetItemRect (infoPtr, (INT)wParam, (LPRECT)lParam);
3313 case TCM_GETCURSEL:
3314 return TAB_GetCurSel (infoPtr);
3316 case TCM_HITTEST:
3317 return TAB_HitTest (infoPtr, (LPTCHITTESTINFO)lParam);
3319 case TCM_SETCURSEL:
3320 return TAB_SetCurSel (infoPtr, (INT)wParam);
3322 case TCM_INSERTITEMA:
3323 case TCM_INSERTITEMW:
3324 return TAB_InsertItemT (infoPtr, (INT)wParam, (TCITEMW*)lParam, uMsg == TCM_INSERTITEMW);
3326 case TCM_SETITEMEXTRA:
3327 return TAB_SetItemExtra (infoPtr, (INT)wParam);
3329 case TCM_ADJUSTRECT:
3330 return TAB_AdjustRect (infoPtr, (BOOL)wParam, (LPRECT)lParam);
3332 case TCM_SETITEMSIZE:
3333 return TAB_SetItemSize (infoPtr, (INT)LOWORD(lParam), (INT)HIWORD(lParam));
3335 case TCM_REMOVEIMAGE:
3336 return TAB_RemoveImage (infoPtr, (INT)wParam);
3338 case TCM_SETPADDING:
3339 return TAB_SetPadding (infoPtr, lParam);
3341 case TCM_GETROWCOUNT:
3342 return TAB_GetRowCount(infoPtr);
3344 case TCM_GETUNICODEFORMAT:
3345 return TAB_GetUnicodeFormat (infoPtr);
3347 case TCM_SETUNICODEFORMAT:
3348 return TAB_SetUnicodeFormat (infoPtr, (BOOL)wParam);
3350 case TCM_HIGHLIGHTITEM:
3351 return TAB_HighlightItem (infoPtr, (INT)wParam, (BOOL)LOWORD(lParam));
3353 case TCM_GETTOOLTIPS:
3354 return TAB_GetToolTips (infoPtr);
3356 case TCM_SETTOOLTIPS:
3357 return TAB_SetToolTips (infoPtr, (HWND)wParam);
3359 case TCM_GETCURFOCUS:
3360 return TAB_GetCurFocus (infoPtr);
3362 case TCM_SETCURFOCUS:
3363 return TAB_SetCurFocus (infoPtr, (INT)wParam);
3365 case TCM_SETMINTABWIDTH:
3366 return TAB_SetMinTabWidth(infoPtr, (INT)lParam);
3368 case TCM_DESELECTALL:
3369 return TAB_DeselectAll (infoPtr, (BOOL)wParam);
3371 case TCM_GETEXTENDEDSTYLE:
3372 return TAB_GetExtendedStyle (infoPtr);
3374 case TCM_SETEXTENDEDSTYLE:
3375 return TAB_SetExtendedStyle (infoPtr, wParam, lParam);
3377 case WM_GETFONT:
3378 return TAB_GetFont (infoPtr);
3380 case WM_SETFONT:
3381 return TAB_SetFont (infoPtr, (HFONT)wParam);
3383 case WM_CREATE:
3384 return TAB_Create (hwnd, lParam);
3386 case WM_NCDESTROY:
3387 return TAB_Destroy (infoPtr);
3389 case WM_GETDLGCODE:
3390 return DLGC_WANTARROWS | DLGC_WANTCHARS;
3392 case WM_LBUTTONDOWN:
3393 return TAB_LButtonDown (infoPtr, wParam, lParam);
3395 case WM_LBUTTONUP:
3396 return TAB_LButtonUp (infoPtr);
3398 case WM_NOTIFY:
3399 return SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, wParam, lParam);
3401 case WM_RBUTTONUP:
3402 TAB_RButtonUp (infoPtr);
3403 return DefWindowProcW (hwnd, uMsg, wParam, lParam);
3405 case WM_MOUSEMOVE:
3406 return TAB_MouseMove (infoPtr, wParam, lParam);
3408 case WM_PRINTCLIENT:
3409 case WM_PAINT:
3410 return TAB_Paint (infoPtr, (HDC)wParam);
3412 case WM_SIZE:
3413 return TAB_Size (infoPtr);
3415 case WM_SETREDRAW:
3416 return TAB_SetRedraw (infoPtr, (BOOL)wParam);
3418 case WM_HSCROLL:
3419 return TAB_OnHScroll(infoPtr, (int)LOWORD(wParam), (int)HIWORD(wParam));
3421 case WM_STYLECHANGED:
3422 return TAB_StyleChanged(infoPtr, wParam, (LPSTYLESTRUCT)lParam);
3424 case WM_SYSCOLORCHANGE:
3425 COMCTL32_RefreshSysColors();
3426 return 0;
3428 case WM_THEMECHANGED:
3429 return theme_changed (infoPtr);
3431 case WM_KILLFOCUS:
3432 TAB_KillFocus(infoPtr);
3433 case WM_SETFOCUS:
3434 TAB_FocusChanging(infoPtr);
3435 break; /* Don't disturb normal focus behavior */
3437 case WM_KEYDOWN:
3438 return TAB_KeyDown(infoPtr, wParam, lParam);
3440 case WM_NCHITTEST:
3441 return TAB_NCHitTest(infoPtr, lParam);
3443 case WM_NCCALCSIZE:
3444 return TAB_NCCalcSize(wParam);
3446 default:
3447 if (uMsg >= WM_USER && uMsg < WM_APP && !COMCTL32_IsReflectedMessage(uMsg))
3448 WARN("unknown msg %04x wp=%08lx lp=%08lx\n",
3449 uMsg, wParam, lParam);
3450 break;
3452 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
3456 void
3457 TAB_Register (void)
3459 WNDCLASSW wndClass;
3461 ZeroMemory (&wndClass, sizeof(WNDCLASSW));
3462 wndClass.style = CS_GLOBALCLASS | CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
3463 wndClass.lpfnWndProc = TAB_WindowProc;
3464 wndClass.cbClsExtra = 0;
3465 wndClass.cbWndExtra = sizeof(TAB_INFO *);
3466 wndClass.hCursor = LoadCursorW (0, (LPWSTR)IDC_ARROW);
3467 wndClass.hbrBackground = (HBRUSH)(COLOR_BTNFACE+1);
3468 wndClass.lpszClassName = WC_TABCONTROLW;
3470 RegisterClassW (&wndClass);
3474 void
3475 TAB_Unregister (void)
3477 UnregisterClassW (WC_TABCONTROLW, NULL);