push d49b3885cdb0e58b792f6bf912143b7a9a4ea546
[wine/hacks.git] / dlls / comctl32 / tab.c
blob1830b2da0fbe703b56b50c18c8b6027712737dbb
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
49 * TCN_KEYDOWN
51 * Macros:
52 * TabCtrl_AdjustRect
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 "tmschema.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 #define TAB_ITEM_SIZE(infoPtr) (FIELD_OFFSET(TAB_ITEM, extra[(infoPtr)->cbInfo]))
90 typedef struct
92 HWND hwnd; /* Tab control window */
93 HWND hwndNotify; /* notification window (parent) */
94 UINT uNumItem; /* number of tab items */
95 UINT uNumRows; /* number of tab rows */
96 INT tabHeight; /* height of the tab row */
97 INT tabWidth; /* width of tabs */
98 INT tabMinWidth; /* minimum width of items */
99 USHORT uHItemPadding; /* amount of horizontal padding, in pixels */
100 USHORT uVItemPadding; /* amount of vertical padding, in pixels */
101 USHORT uHItemPadding_s; /* Set amount of horizontal padding, in pixels */
102 USHORT uVItemPadding_s; /* Set amount of vertical padding, in pixels */
103 HFONT hFont; /* handle to the current font */
104 HCURSOR hcurArrow; /* handle to the current cursor */
105 HIMAGELIST himl; /* handle to an image list (may be 0) */
106 HWND hwndToolTip; /* handle to tab's tooltip */
107 INT leftmostVisible; /* Used for scrolling, this member contains
108 * the index of the first visible item */
109 INT iSelected; /* the currently selected item */
110 INT iHotTracked; /* the highlighted item under the mouse */
111 INT uFocus; /* item which has the focus */
112 TAB_ITEM* items; /* pointer to an array of TAB_ITEM's */
113 BOOL DoRedraw; /* flag for redrawing when tab contents is changed*/
114 BOOL needsScrolling; /* TRUE if the size of the tabs is greater than
115 * the size of the control */
116 BOOL fHeightSet; /* was the height of the tabs explicitly set? */
117 BOOL bUnicode; /* Unicode control? */
118 HWND hwndUpDown; /* Updown control used for scrolling */
119 INT cbInfo; /* Number of bytes of caller supplied info per tab */
121 DWORD exStyle; /* Extended style used, currently:
122 TCS_EX_FLATSEPARATORS, TCS_EX_REGISTERDROP */
123 } TAB_INFO;
125 /******************************************************************************
126 * Positioning constants
128 #define SELECTED_TAB_OFFSET 2
129 #define ROUND_CORNER_SIZE 2
130 #define DISPLAY_AREA_PADDINGX 2
131 #define DISPLAY_AREA_PADDINGY 2
132 #define CONTROL_BORDER_SIZEX 2
133 #define CONTROL_BORDER_SIZEY 2
134 #define BUTTON_SPACINGX 3
135 #define BUTTON_SPACINGY 3
136 #define FLAT_BTN_SPACINGX 8
137 #define DEFAULT_MIN_TAB_WIDTH 54
138 #define DEFAULT_PADDING_X 6
139 #define EXTRA_ICON_PADDING 3
141 #define TAB_GetInfoPtr(hwnd) ((TAB_INFO *)GetWindowLongPtrW(hwnd,0))
142 /* Since items are variable sized, cannot directly access them */
143 #define TAB_GetItem(info,i) \
144 ((TAB_ITEM*)((LPBYTE)info->items + (i) * TAB_ITEM_SIZE(info)))
146 #define GET_DEFAULT_MIN_TAB_WIDTH(infoPtr) (DEFAULT_MIN_TAB_WIDTH - (DEFAULT_PADDING_X - (infoPtr)->uHItemPadding) * 2)
148 /******************************************************************************
149 * Hot-tracking timer constants
151 #define TAB_HOTTRACK_TIMER 1
152 #define TAB_HOTTRACK_TIMER_INTERVAL 100 /* milliseconds */
154 static const WCHAR themeClass[] = { 'T','a','b',0 };
156 /******************************************************************************
157 * Prototypes
159 static void TAB_InvalidateTabArea(const TAB_INFO *);
160 static void TAB_EnsureSelectionVisible(TAB_INFO *);
161 static void TAB_DrawItemInterior(const TAB_INFO *, HDC, INT, RECT*);
162 static LRESULT TAB_DeselectAll(TAB_INFO *, BOOL);
164 static BOOL
165 TAB_SendSimpleNotify (const TAB_INFO *infoPtr, UINT code)
167 NMHDR nmhdr;
169 nmhdr.hwndFrom = infoPtr->hwnd;
170 nmhdr.idFrom = GetWindowLongPtrW(infoPtr->hwnd, GWLP_ID);
171 nmhdr.code = code;
173 return (BOOL) SendMessageW (infoPtr->hwndNotify, WM_NOTIFY,
174 nmhdr.idFrom, (LPARAM) &nmhdr);
177 static void
178 TAB_RelayEvent (HWND hwndTip, HWND hwndMsg, UINT uMsg,
179 WPARAM wParam, LPARAM lParam)
181 MSG msg;
183 msg.hwnd = hwndMsg;
184 msg.message = uMsg;
185 msg.wParam = wParam;
186 msg.lParam = lParam;
187 msg.time = GetMessageTime ();
188 msg.pt.x = (short)LOWORD(GetMessagePos ());
189 msg.pt.y = (short)HIWORD(GetMessagePos ());
191 SendMessageW (hwndTip, TTM_RELAYEVENT, 0, (LPARAM)&msg);
194 static void
195 TAB_DumpItemExternalT(const TCITEMW *pti, UINT iItem, BOOL isW)
197 if (TRACE_ON(tab)) {
198 TRACE("external tab %d, mask=0x%08x, dwState=0x%08x, dwStateMask=0x%08x, cchTextMax=0x%08x\n",
199 iItem, pti->mask, pti->dwState, pti->dwStateMask, pti->cchTextMax);
200 TRACE("external tab %d, iImage=%d, lParam=0x%08lx, pszTextW=%s\n",
201 iItem, pti->iImage, pti->lParam, isW ? debugstr_w(pti->pszText) : debugstr_a((LPSTR)pti->pszText));
205 static void
206 TAB_DumpItemInternal(const TAB_INFO *infoPtr, UINT iItem)
208 if (TRACE_ON(tab)) {
209 TAB_ITEM *ti;
211 ti = TAB_GetItem(infoPtr, iItem);
212 TRACE("tab %d, dwState=0x%08x, pszText=%s, iImage=%d\n",
213 iItem, ti->dwState, debugstr_w(ti->pszText), ti->iImage);
214 TRACE("tab %d, rect.left=%d, rect.top(row)=%d\n",
215 iItem, ti->rect.left, ti->rect.top);
219 /* RETURNS
220 * the index of the selected tab, or -1 if no tab is selected. */
221 static inline LRESULT TAB_GetCurSel (const TAB_INFO *infoPtr)
223 return infoPtr->iSelected;
226 /* RETURNS
227 * the index of the tab item that has the focus. */
228 static inline LRESULT
229 TAB_GetCurFocus (const TAB_INFO *infoPtr)
231 return infoPtr->uFocus;
234 static inline LRESULT TAB_GetToolTips (const TAB_INFO *infoPtr)
236 if (infoPtr == NULL) return 0;
237 return (LRESULT)infoPtr->hwndToolTip;
240 static inline LRESULT TAB_SetCurSel (TAB_INFO *infoPtr, INT iItem)
242 INT prevItem = infoPtr->iSelected;
244 if (iItem < 0)
245 infoPtr->iSelected=-1;
246 else if (iItem >= infoPtr->uNumItem)
247 return -1;
248 else {
249 if (infoPtr->iSelected != iItem) {
250 TAB_GetItem(infoPtr, prevItem)->dwState &= ~TCIS_BUTTONPRESSED;
251 TAB_GetItem(infoPtr, iItem)->dwState |= TCIS_BUTTONPRESSED;
253 infoPtr->iSelected=iItem;
254 infoPtr->uFocus=iItem;
255 TAB_EnsureSelectionVisible(infoPtr);
256 TAB_InvalidateTabArea(infoPtr);
259 return prevItem;
262 static LRESULT TAB_SetCurFocus (TAB_INFO *infoPtr, INT iItem)
264 if (iItem < 0)
265 infoPtr->uFocus = -1;
266 else if (iItem < infoPtr->uNumItem) {
267 if (GetWindowLongW(infoPtr->hwnd, GWL_STYLE) & TCS_BUTTONS) {
268 FIXME("Should set input focus\n");
269 } else {
270 int oldFocus = infoPtr->uFocus;
271 if (infoPtr->iSelected != iItem || oldFocus == -1 ) {
272 infoPtr->uFocus = iItem;
273 if (oldFocus != -1) {
274 if (!TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGING)) {
275 infoPtr->iSelected = iItem;
276 TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGE);
278 else
279 infoPtr->iSelected = iItem;
280 TAB_EnsureSelectionVisible(infoPtr);
281 TAB_InvalidateTabArea(infoPtr);
286 return 0;
289 static inline LRESULT
290 TAB_SetToolTips (TAB_INFO *infoPtr, HWND hwndToolTip)
292 if (infoPtr)
293 infoPtr->hwndToolTip = hwndToolTip;
294 return 0;
297 static inline LRESULT
298 TAB_SetPadding (TAB_INFO *infoPtr, LPARAM lParam)
300 if (infoPtr)
302 infoPtr->uHItemPadding_s=LOWORD(lParam);
303 infoPtr->uVItemPadding_s=HIWORD(lParam);
305 return 0;
308 /******************************************************************************
309 * TAB_InternalGetItemRect
311 * This method will calculate the rectangle representing a given tab item in
312 * client coordinates. This method takes scrolling into account.
314 * This method returns TRUE if the item is visible in the window and FALSE
315 * if it is completely outside the client area.
317 static BOOL TAB_InternalGetItemRect(
318 const TAB_INFO* infoPtr,
319 INT itemIndex,
320 RECT* itemRect,
321 RECT* selectedRect)
323 RECT tmpItemRect,clientRect;
324 LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
326 /* Perform a sanity check and a trivial visibility check. */
327 if ( (infoPtr->uNumItem <= 0) ||
328 (itemIndex >= infoPtr->uNumItem) ||
329 (!((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)) && (itemIndex < infoPtr->leftmostVisible)) )
331 TRACE("Not Visible\n");
332 /* need to initialize these to empty rects */
333 if (itemRect)
335 memset(itemRect,0,sizeof(RECT));
336 itemRect->bottom = infoPtr->tabHeight;
338 if (selectedRect)
339 memset(selectedRect,0,sizeof(RECT));
340 return FALSE;
344 * Avoid special cases in this procedure by assigning the "out"
345 * parameters if the caller didn't supply them
347 if (itemRect == NULL)
348 itemRect = &tmpItemRect;
350 /* Retrieve the unmodified item rect. */
351 *itemRect = TAB_GetItem(infoPtr,itemIndex)->rect;
353 /* calculate the times bottom and top based on the row */
354 GetClientRect(infoPtr->hwnd, &clientRect);
356 if ((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
358 itemRect->right = clientRect.right - SELECTED_TAB_OFFSET - itemRect->left * infoPtr->tabHeight -
359 ((lStyle & TCS_BUTTONS) ? itemRect->left * BUTTON_SPACINGX : 0);
360 itemRect->left = itemRect->right - infoPtr->tabHeight;
362 else if (lStyle & TCS_VERTICAL)
364 itemRect->left = clientRect.left + SELECTED_TAB_OFFSET + itemRect->left * infoPtr->tabHeight +
365 ((lStyle & TCS_BUTTONS) ? itemRect->left * BUTTON_SPACINGX : 0);
366 itemRect->right = itemRect->left + infoPtr->tabHeight;
368 else if (lStyle & TCS_BOTTOM)
370 itemRect->bottom = clientRect.bottom - itemRect->top * infoPtr->tabHeight -
371 ((lStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : SELECTED_TAB_OFFSET);
372 itemRect->top = itemRect->bottom - infoPtr->tabHeight;
374 else /* not TCS_BOTTOM and not TCS_VERTICAL */
376 itemRect->top = clientRect.top + itemRect->top * infoPtr->tabHeight +
377 ((lStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : SELECTED_TAB_OFFSET);
378 itemRect->bottom = itemRect->top + infoPtr->tabHeight;
382 * "scroll" it to make sure the item at the very left of the
383 * tab control is the leftmost visible tab.
385 if(lStyle & TCS_VERTICAL)
387 OffsetRect(itemRect,
389 -TAB_GetItem(infoPtr, infoPtr->leftmostVisible)->rect.top);
392 * Move the rectangle so the first item is slightly offset from
393 * the bottom of the tab control.
395 OffsetRect(itemRect,
397 SELECTED_TAB_OFFSET);
399 } else
401 OffsetRect(itemRect,
402 -TAB_GetItem(infoPtr, infoPtr->leftmostVisible)->rect.left,
406 * Move the rectangle so the first item is slightly offset from
407 * the left of the tab control.
409 OffsetRect(itemRect,
410 SELECTED_TAB_OFFSET,
413 TRACE("item %d tab h=%d, rect=(%s)\n",
414 itemIndex, infoPtr->tabHeight, wine_dbgstr_rect(itemRect));
416 /* Now, calculate the position of the item as if it were selected. */
417 if (selectedRect!=NULL)
419 CopyRect(selectedRect, itemRect);
421 /* The rectangle of a selected item is a bit wider. */
422 if(lStyle & TCS_VERTICAL)
423 InflateRect(selectedRect, 0, SELECTED_TAB_OFFSET);
424 else
425 InflateRect(selectedRect, SELECTED_TAB_OFFSET, 0);
427 /* If it also a bit higher. */
428 if ((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
430 selectedRect->left -= 2; /* the border is thicker on the right */
431 selectedRect->right += SELECTED_TAB_OFFSET;
433 else if (lStyle & TCS_VERTICAL)
435 selectedRect->left -= SELECTED_TAB_OFFSET;
436 selectedRect->right += 1;
438 else if (lStyle & TCS_BOTTOM)
440 selectedRect->bottom += SELECTED_TAB_OFFSET;
442 else /* not TCS_BOTTOM and not TCS_VERTICAL */
444 selectedRect->top -= SELECTED_TAB_OFFSET;
445 selectedRect->bottom -= 1;
449 /* Check for visibility */
450 if (lStyle & TCS_VERTICAL)
451 return (itemRect->top < clientRect.bottom) && (itemRect->bottom > clientRect.top);
452 else
453 return (itemRect->left < clientRect.right) && (itemRect->right > clientRect.left);
456 static inline BOOL
457 TAB_GetItemRect(const TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
459 return TAB_InternalGetItemRect(infoPtr, wParam, (LPRECT)lParam, NULL);
462 /******************************************************************************
463 * TAB_KeyUp
465 * This method is called to handle keyboard input
467 static LRESULT TAB_KeyUp(TAB_INFO* infoPtr, WPARAM keyCode)
469 int newItem = -1;
471 switch (keyCode)
473 case VK_LEFT:
474 newItem = infoPtr->uFocus - 1;
475 break;
476 case VK_RIGHT:
477 newItem = infoPtr->uFocus + 1;
478 break;
482 * If we changed to a valid item, change the selection
484 if (newItem >= 0 &&
485 newItem < infoPtr->uNumItem &&
486 infoPtr->uFocus != newItem)
488 if (!TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGING))
490 TAB_SetCurSel(infoPtr, newItem);
491 TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGE);
495 return 0;
498 /******************************************************************************
499 * TAB_FocusChanging
501 * This method is called whenever the focus goes in or out of this control
502 * it is used to update the visual state of the control.
504 static void TAB_FocusChanging(const TAB_INFO *infoPtr)
506 RECT selectedRect;
507 BOOL isVisible;
510 * Get the rectangle for the item.
512 isVisible = TAB_InternalGetItemRect(infoPtr,
513 infoPtr->uFocus,
514 NULL,
515 &selectedRect);
518 * If the rectangle is not completely invisible, invalidate that
519 * portion of the window.
521 if (isVisible)
523 TRACE("invalidate (%s)\n", wine_dbgstr_rect(&selectedRect));
524 InvalidateRect(infoPtr->hwnd, &selectedRect, TRUE);
528 static INT TAB_InternalHitTest (const TAB_INFO *infoPtr, POINT pt, UINT *flags)
530 RECT rect;
531 INT iCount;
533 for (iCount = 0; iCount < infoPtr->uNumItem; iCount++)
535 TAB_InternalGetItemRect(infoPtr, iCount, &rect, NULL);
537 if (PtInRect(&rect, pt))
539 *flags = TCHT_ONITEM;
540 return iCount;
544 *flags = TCHT_NOWHERE;
545 return -1;
548 static inline LRESULT
549 TAB_HitTest (const TAB_INFO *infoPtr, LPTCHITTESTINFO lptest)
551 return TAB_InternalHitTest (infoPtr, lptest->pt, &lptest->flags);
554 /******************************************************************************
555 * TAB_NCHitTest
557 * Napster v2b5 has a tab control for its main navigation which has a client
558 * area that covers the whole area of the dialog pages.
559 * That's why it receives all msgs for that area and the underlying dialog ctrls
560 * are dead.
561 * So I decided that we should handle WM_NCHITTEST here and return
562 * HTTRANSPARENT if we don't hit the tab control buttons.
563 * FIXME: WM_NCHITTEST handling correct ? Fix it if you know that Windows
564 * doesn't do it that way. Maybe depends on tab control styles ?
566 static inline LRESULT
567 TAB_NCHitTest (const TAB_INFO *infoPtr, LPARAM lParam)
569 POINT pt;
570 UINT dummyflag;
572 pt.x = (short)LOWORD(lParam);
573 pt.y = (short)HIWORD(lParam);
574 ScreenToClient(infoPtr->hwnd, &pt);
576 if (TAB_InternalHitTest(infoPtr, pt, &dummyflag) == -1)
577 return HTTRANSPARENT;
578 else
579 return HTCLIENT;
582 static LRESULT
583 TAB_LButtonDown (TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
585 POINT pt;
586 INT newItem;
587 UINT dummy;
588 LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
590 if (infoPtr->hwndToolTip)
591 TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
592 WM_LBUTTONDOWN, wParam, lParam);
594 if (GetWindowLongW(infoPtr->hwnd, GWL_STYLE) & TCS_FOCUSONBUTTONDOWN ) {
595 SetFocus (infoPtr->hwnd);
598 if (infoPtr->hwndToolTip)
599 TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
600 WM_LBUTTONDOWN, wParam, lParam);
602 pt.x = (short)LOWORD(lParam);
603 pt.y = (short)HIWORD(lParam);
605 newItem = TAB_InternalHitTest (infoPtr, pt, &dummy);
607 TRACE("On Tab, item %d\n", newItem);
609 if ((newItem != -1) && (infoPtr->iSelected != newItem))
611 if ((lStyle & TCS_BUTTONS) && (lStyle & TCS_MULTISELECT) &&
612 (wParam & MK_CONTROL))
614 RECT r;
616 /* toggle multiselection */
617 TAB_GetItem(infoPtr, newItem)->dwState ^= TCIS_BUTTONPRESSED;
618 if (TAB_InternalGetItemRect (infoPtr, newItem, &r, NULL))
619 InvalidateRect (infoPtr->hwnd, &r, TRUE);
621 else
623 INT i;
624 BOOL pressed = FALSE;
626 /* any button pressed ? */
627 for (i = 0; i < infoPtr->uNumItem; i++)
628 if ((TAB_GetItem (infoPtr, i)->dwState & TCIS_BUTTONPRESSED) &&
629 (infoPtr->iSelected != i))
631 pressed = TRUE;
632 break;
635 TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGING);
637 if (pressed)
638 TAB_DeselectAll (infoPtr, FALSE);
639 else
640 TAB_SetCurSel(infoPtr, newItem);
642 TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGE);
646 return 0;
649 static inline LRESULT
650 TAB_LButtonUp (const TAB_INFO *infoPtr)
652 TAB_SendSimpleNotify(infoPtr, NM_CLICK);
654 return 0;
657 static inline LRESULT
658 TAB_RButtonDown (const TAB_INFO *infoPtr)
660 TAB_SendSimpleNotify(infoPtr, NM_RCLICK);
661 return 0;
664 /******************************************************************************
665 * TAB_DrawLoneItemInterior
667 * This calls TAB_DrawItemInterior. However, TAB_DrawItemInterior is normally
668 * called by TAB_DrawItem which is normally called by TAB_Refresh which sets
669 * up the device context and font. This routine does the same setup but
670 * only calls TAB_DrawItemInterior for the single specified item.
672 static void
673 TAB_DrawLoneItemInterior(const TAB_INFO* infoPtr, int iItem)
675 HDC hdc = GetDC(infoPtr->hwnd);
676 RECT r, rC;
678 /* Clip UpDown control to not draw over it */
679 if (infoPtr->needsScrolling)
681 GetWindowRect(infoPtr->hwnd, &rC);
682 GetWindowRect(infoPtr->hwndUpDown, &r);
683 ExcludeClipRect(hdc, r.left - rC.left, r.top - rC.top, r.right - rC.left, r.bottom - rC.top);
685 TAB_DrawItemInterior(infoPtr, hdc, iItem, NULL);
686 ReleaseDC(infoPtr->hwnd, hdc);
689 /* update a tab after hottracking - invalidate it or just redraw the interior,
690 * based on whether theming is used or not */
691 static inline void hottrack_refresh(const TAB_INFO *infoPtr, int tabIndex)
693 if (tabIndex == -1) return;
695 if (GetWindowTheme (infoPtr->hwnd))
697 RECT rect;
698 TAB_InternalGetItemRect(infoPtr, tabIndex, &rect, NULL);
699 InvalidateRect (infoPtr->hwnd, &rect, FALSE);
701 else
702 TAB_DrawLoneItemInterior(infoPtr, tabIndex);
705 /******************************************************************************
706 * TAB_HotTrackTimerProc
708 * When a mouse-move event causes a tab to be highlighted (hot-tracking), a
709 * timer is setup so we can check if the mouse is moved out of our window.
710 * (We don't get an event when the mouse leaves, the mouse-move events just
711 * stop being delivered to our window and just start being delivered to
712 * another window.) This function is called when the timer triggers so
713 * we can check if the mouse has left our window. If so, we un-highlight
714 * the hot-tracked tab.
716 static void CALLBACK
717 TAB_HotTrackTimerProc
719 HWND hwnd, /* handle of window for timer messages */
720 UINT uMsg, /* WM_TIMER message */
721 UINT_PTR idEvent, /* timer identifier */
722 DWORD dwTime /* current system time */
725 TAB_INFO* infoPtr = TAB_GetInfoPtr(hwnd);
727 if (infoPtr != NULL && infoPtr->iHotTracked >= 0)
729 POINT pt;
732 ** If we can't get the cursor position, or if the cursor is outside our
733 ** window, we un-highlight the hot-tracked tab. Note that the cursor is
734 ** "outside" even if it is within our bounding rect if another window
735 ** overlaps. Note also that the case where the cursor stayed within our
736 ** window but has moved off the hot-tracked tab will be handled by the
737 ** WM_MOUSEMOVE event.
739 if (!GetCursorPos(&pt) || WindowFromPoint(pt) != hwnd)
741 /* Redraw iHotTracked to look normal */
742 INT iRedraw = infoPtr->iHotTracked;
743 infoPtr->iHotTracked = -1;
744 hottrack_refresh (infoPtr, iRedraw);
746 /* Kill this timer */
747 KillTimer(hwnd, TAB_HOTTRACK_TIMER);
752 /******************************************************************************
753 * TAB_RecalcHotTrack
755 * If a tab control has the TCS_HOTTRACK style, then the tab under the mouse
756 * should be highlighted. This function determines which tab in a tab control,
757 * if any, is under the mouse and records that information. The caller may
758 * supply output parameters to receive the item number of the tab item which
759 * was highlighted but isn't any longer and of the tab item which is now
760 * highlighted but wasn't previously. The caller can use this information to
761 * selectively redraw those tab items.
763 * If the caller has a mouse position, it can supply it through the pos
764 * parameter. For example, TAB_MouseMove does this. Otherwise, the caller
765 * supplies NULL and this function determines the current mouse position
766 * itself.
768 static void
769 TAB_RecalcHotTrack
771 TAB_INFO* infoPtr,
772 const LPARAM* pos,
773 int* out_redrawLeave,
774 int* out_redrawEnter
777 int item = -1;
780 if (out_redrawLeave != NULL)
781 *out_redrawLeave = -1;
782 if (out_redrawEnter != NULL)
783 *out_redrawEnter = -1;
785 if ((GetWindowLongW(infoPtr->hwnd, GWL_STYLE) & TCS_HOTTRACK)
786 || GetWindowTheme (infoPtr->hwnd))
788 POINT pt;
789 UINT flags;
791 if (pos == NULL)
793 GetCursorPos(&pt);
794 ScreenToClient(infoPtr->hwnd, &pt);
796 else
798 pt.x = (short)LOWORD(*pos);
799 pt.y = (short)HIWORD(*pos);
802 item = TAB_InternalHitTest(infoPtr, pt, &flags);
805 if (item != infoPtr->iHotTracked)
807 if (infoPtr->iHotTracked >= 0)
809 /* Mark currently hot-tracked to be redrawn to look normal */
810 if (out_redrawLeave != NULL)
811 *out_redrawLeave = infoPtr->iHotTracked;
813 if (item < 0)
815 /* Kill timer which forces recheck of mouse pos */
816 KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
819 else
821 /* Start timer so we recheck mouse pos */
822 UINT timerID = SetTimer
824 infoPtr->hwnd,
825 TAB_HOTTRACK_TIMER,
826 TAB_HOTTRACK_TIMER_INTERVAL,
827 TAB_HotTrackTimerProc
830 if (timerID == 0)
831 return; /* Hot tracking not available */
834 infoPtr->iHotTracked = item;
836 if (item >= 0)
838 /* Mark new hot-tracked to be redrawn to look highlighted */
839 if (out_redrawEnter != NULL)
840 *out_redrawEnter = item;
845 /******************************************************************************
846 * TAB_MouseMove
848 * Handles the mouse-move event. Updates tooltips. Updates hot-tracking.
850 static LRESULT
851 TAB_MouseMove (TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
853 int redrawLeave;
854 int redrawEnter;
856 if (infoPtr->hwndToolTip)
857 TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
858 WM_LBUTTONDOWN, wParam, lParam);
860 /* Determine which tab to highlight. Redraw tabs which change highlight
861 ** status. */
862 TAB_RecalcHotTrack(infoPtr, &lParam, &redrawLeave, &redrawEnter);
864 hottrack_refresh (infoPtr, redrawLeave);
865 hottrack_refresh (infoPtr, redrawEnter);
867 return 0;
870 /******************************************************************************
871 * TAB_AdjustRect
873 * Calculates the tab control's display area given the window rectangle or
874 * the window rectangle given the requested display rectangle.
876 static LRESULT TAB_AdjustRect(const TAB_INFO *infoPtr, WPARAM fLarger, LPRECT prc)
878 DWORD lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
879 LONG *iRightBottom, *iLeftTop;
881 TRACE ("hwnd=%p fLarger=%ld (%s)\n", infoPtr->hwnd, fLarger,
882 wine_dbgstr_rect(prc));
884 if (!prc) return -1;
886 if(lStyle & TCS_VERTICAL)
888 iRightBottom = &(prc->right);
889 iLeftTop = &(prc->left);
891 else
893 iRightBottom = &(prc->bottom);
894 iLeftTop = &(prc->top);
897 if (fLarger) /* Go from display rectangle */
899 /* Add the height of the tabs. */
900 if (lStyle & TCS_BOTTOM)
901 *iRightBottom += infoPtr->tabHeight * infoPtr->uNumRows;
902 else
903 *iLeftTop -= infoPtr->tabHeight * infoPtr->uNumRows +
904 ((lStyle & TCS_BUTTONS)? 3 * (infoPtr->uNumRows - 1) : 0);
906 /* Inflate the rectangle for the padding */
907 InflateRect(prc, DISPLAY_AREA_PADDINGX, DISPLAY_AREA_PADDINGY);
909 /* Inflate for the border */
910 InflateRect(prc, CONTROL_BORDER_SIZEX, CONTROL_BORDER_SIZEY);
912 else /* Go from window rectangle. */
914 /* Deflate the rectangle for the border */
915 InflateRect(prc, -CONTROL_BORDER_SIZEX, -CONTROL_BORDER_SIZEY);
917 /* Deflate the rectangle for the padding */
918 InflateRect(prc, -DISPLAY_AREA_PADDINGX, -DISPLAY_AREA_PADDINGY);
920 /* Remove the height of the tabs. */
921 if (lStyle & TCS_BOTTOM)
922 *iRightBottom -= infoPtr->tabHeight * infoPtr->uNumRows;
923 else
924 *iLeftTop += (infoPtr->tabHeight) * infoPtr->uNumRows +
925 ((lStyle & TCS_BUTTONS)? 3 * (infoPtr->uNumRows - 1) : 0);
928 return 0;
931 /******************************************************************************
932 * TAB_OnHScroll
934 * This method will handle the notification from the scroll control and
935 * perform the scrolling operation on the tab control.
937 static LRESULT TAB_OnHScroll(TAB_INFO *infoPtr, int nScrollCode, int nPos)
939 if(nScrollCode == SB_THUMBPOSITION && nPos != infoPtr->leftmostVisible)
941 if(nPos < infoPtr->leftmostVisible)
942 infoPtr->leftmostVisible--;
943 else
944 infoPtr->leftmostVisible++;
946 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
947 TAB_InvalidateTabArea(infoPtr);
948 SendMessageW(infoPtr->hwndUpDown, UDM_SETPOS, 0,
949 MAKELONG(infoPtr->leftmostVisible, 0));
952 return 0;
955 /******************************************************************************
956 * TAB_SetupScrolling
958 * This method will check the current scrolling state and make sure the
959 * scrolling control is displayed (or not).
961 static void TAB_SetupScrolling(
962 HWND hwnd,
963 TAB_INFO* infoPtr,
964 const RECT* clientRect)
966 static const WCHAR msctls_updown32W[] = { 'm','s','c','t','l','s','_','u','p','d','o','w','n','3','2',0 };
967 static const WCHAR emptyW[] = { 0 };
968 INT maxRange = 0;
969 DWORD lStyle = GetWindowLongW(hwnd, GWL_STYLE);
971 if (infoPtr->needsScrolling)
973 RECT controlPos;
974 INT vsize, tabwidth;
977 * Calculate the position of the scroll control.
979 if(lStyle & TCS_VERTICAL)
981 controlPos.right = clientRect->right;
982 controlPos.left = controlPos.right - 2 * GetSystemMetrics(SM_CXHSCROLL);
984 if (lStyle & TCS_BOTTOM)
986 controlPos.top = clientRect->bottom - infoPtr->tabHeight;
987 controlPos.bottom = controlPos.top + GetSystemMetrics(SM_CYHSCROLL);
989 else
991 controlPos.bottom = clientRect->top + infoPtr->tabHeight;
992 controlPos.top = controlPos.bottom - GetSystemMetrics(SM_CYHSCROLL);
995 else
997 controlPos.right = clientRect->right;
998 controlPos.left = controlPos.right - 2 * GetSystemMetrics(SM_CXHSCROLL);
1000 if (lStyle & TCS_BOTTOM)
1002 controlPos.top = clientRect->bottom - infoPtr->tabHeight;
1003 controlPos.bottom = controlPos.top + GetSystemMetrics(SM_CYHSCROLL);
1005 else
1007 controlPos.bottom = clientRect->top + infoPtr->tabHeight;
1008 controlPos.top = controlPos.bottom - GetSystemMetrics(SM_CYHSCROLL);
1013 * If we don't have a scroll control yet, we want to create one.
1014 * If we have one, we want to make sure it's positioned properly.
1016 if (infoPtr->hwndUpDown==0)
1018 infoPtr->hwndUpDown = CreateWindowW(msctls_updown32W, emptyW,
1019 WS_VISIBLE | WS_CHILD | UDS_HORZ,
1020 controlPos.left, controlPos.top,
1021 controlPos.right - controlPos.left,
1022 controlPos.bottom - controlPos.top,
1023 hwnd, NULL, NULL, NULL);
1025 else
1027 SetWindowPos(infoPtr->hwndUpDown,
1028 NULL,
1029 controlPos.left, controlPos.top,
1030 controlPos.right - controlPos.left,
1031 controlPos.bottom - controlPos.top,
1032 SWP_SHOWWINDOW | SWP_NOZORDER);
1035 /* Now calculate upper limit of the updown control range.
1036 * We do this by calculating how many tabs will be offscreen when the
1037 * last tab is visible.
1039 if(infoPtr->uNumItem)
1041 vsize = clientRect->right - (controlPos.right - controlPos.left + 1);
1042 maxRange = infoPtr->uNumItem;
1043 tabwidth = TAB_GetItem(infoPtr, infoPtr->uNumItem - 1)->rect.right;
1045 for(; maxRange > 0; maxRange--)
1047 if(tabwidth - TAB_GetItem(infoPtr,maxRange - 1)->rect.left > vsize)
1048 break;
1051 if(maxRange == infoPtr->uNumItem)
1052 maxRange--;
1055 else
1057 /* If we once had a scroll control... hide it */
1058 if (infoPtr->hwndUpDown!=0)
1059 ShowWindow(infoPtr->hwndUpDown, SW_HIDE);
1061 if (infoPtr->hwndUpDown)
1062 SendMessageW(infoPtr->hwndUpDown, UDM_SETRANGE32, 0, maxRange);
1065 /******************************************************************************
1066 * TAB_SetItemBounds
1068 * This method will calculate the position rectangles of all the items in the
1069 * control. The rectangle calculated starts at 0 for the first item in the
1070 * list and ignores scrolling and selection.
1071 * It also uses the current font to determine the height of the tab row and
1072 * it checks if all the tabs fit in the client area of the window. If they
1073 * don't, a scrolling control is added.
1075 static void TAB_SetItemBounds (TAB_INFO *infoPtr)
1077 LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1078 TEXTMETRICW fontMetrics;
1079 UINT curItem;
1080 INT curItemLeftPos;
1081 INT curItemRowCount;
1082 HFONT hFont, hOldFont;
1083 HDC hdc;
1084 RECT clientRect;
1085 INT iTemp;
1086 RECT* rcItem;
1087 INT iIndex;
1088 INT icon_width = 0;
1091 * We need to get text information so we need a DC and we need to select
1092 * a font.
1094 hdc = GetDC(infoPtr->hwnd);
1096 hFont = infoPtr->hFont ? infoPtr->hFont : GetStockObject (SYSTEM_FONT);
1097 hOldFont = SelectObject (hdc, hFont);
1100 * We will base the rectangle calculations on the client rectangle
1101 * of the control.
1103 GetClientRect(infoPtr->hwnd, &clientRect);
1105 /* if TCS_VERTICAL then swap the height and width so this code places the
1106 tabs along the top of the rectangle and we can just rotate them after
1107 rather than duplicate all of the below code */
1108 if(lStyle & TCS_VERTICAL)
1110 iTemp = clientRect.bottom;
1111 clientRect.bottom = clientRect.right;
1112 clientRect.right = iTemp;
1115 /* Now use hPadding and vPadding */
1116 infoPtr->uHItemPadding = infoPtr->uHItemPadding_s;
1117 infoPtr->uVItemPadding = infoPtr->uVItemPadding_s;
1119 /* The leftmost item will be "0" aligned */
1120 curItemLeftPos = 0;
1121 curItemRowCount = infoPtr->uNumItem ? 1 : 0;
1123 if (!(infoPtr->fHeightSet))
1125 int item_height;
1126 int icon_height = 0;
1128 /* Use the current font to determine the height of a tab. */
1129 GetTextMetricsW(hdc, &fontMetrics);
1131 /* Get the icon height */
1132 if (infoPtr->himl)
1133 ImageList_GetIconSize(infoPtr->himl, 0, &icon_height);
1135 /* Take the highest between font or icon */
1136 if (fontMetrics.tmHeight > icon_height)
1137 item_height = fontMetrics.tmHeight + 2;
1138 else
1139 item_height = icon_height;
1142 * Make sure there is enough space for the letters + icon + growing the
1143 * selected item + extra space for the selected item.
1145 infoPtr->tabHeight = item_height +
1146 ((lStyle & TCS_BUTTONS) ? 2 : 1) *
1147 infoPtr->uVItemPadding;
1149 TRACE("tabH=%d, tmH=%d, iconh=%d\n",
1150 infoPtr->tabHeight, fontMetrics.tmHeight, icon_height);
1153 TRACE("client right=%d\n", clientRect.right);
1155 /* Get the icon width */
1156 if (infoPtr->himl)
1158 ImageList_GetIconSize(infoPtr->himl, &icon_width, 0);
1160 if (lStyle & TCS_FIXEDWIDTH)
1161 icon_width += 4;
1162 else
1163 /* Add padding if icon is present */
1164 icon_width += infoPtr->uHItemPadding;
1167 for (curItem = 0; curItem < infoPtr->uNumItem; curItem++)
1169 TAB_ITEM *curr = TAB_GetItem(infoPtr, curItem);
1171 /* Set the leftmost position of the tab. */
1172 curr->rect.left = curItemLeftPos;
1174 if (lStyle & TCS_FIXEDWIDTH)
1176 curr->rect.right = curr->rect.left +
1177 max(infoPtr->tabWidth, icon_width);
1179 else if (!curr->pszText)
1181 /* If no text use minimum tab width including padding. */
1182 if (infoPtr->tabMinWidth < 0)
1183 curr->rect.right = curr->rect.left + GET_DEFAULT_MIN_TAB_WIDTH(infoPtr);
1184 else
1186 curr->rect.right = curr->rect.left + infoPtr->tabMinWidth;
1188 /* Add extra padding if icon is present */
1189 if (infoPtr->himl && infoPtr->tabMinWidth > 0 && infoPtr->tabMinWidth < DEFAULT_MIN_TAB_WIDTH
1190 && infoPtr->uHItemPadding > 1)
1191 curr->rect.right += EXTRA_ICON_PADDING * (infoPtr->uHItemPadding-1);
1194 else
1196 int tabwidth;
1197 SIZE size;
1198 /* Calculate how wide the tab is depending on the text it contains */
1199 GetTextExtentPoint32W(hdc, curr->pszText,
1200 lstrlenW(curr->pszText), &size);
1202 tabwidth = size.cx + icon_width + 2 * infoPtr->uHItemPadding;
1204 if (infoPtr->tabMinWidth < 0)
1205 tabwidth = max(tabwidth, GET_DEFAULT_MIN_TAB_WIDTH(infoPtr));
1206 else
1207 tabwidth = max(tabwidth, infoPtr->tabMinWidth);
1209 curr->rect.right = curr->rect.left + tabwidth;
1210 TRACE("for <%s>, l,r=%d,%d\n",
1211 debugstr_w(curr->pszText), curr->rect.left, curr->rect.right);
1215 * Check if this is a multiline tab control and if so
1216 * check to see if we should wrap the tabs
1218 * Wrap all these tabs. We will arrange them evenly later.
1222 if (((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)) &&
1223 (curr->rect.right >
1224 (clientRect.right - CONTROL_BORDER_SIZEX - DISPLAY_AREA_PADDINGX)))
1226 curr->rect.right -= curr->rect.left;
1228 curr->rect.left = 0;
1229 curItemRowCount++;
1230 TRACE("wrapping <%s>, l,r=%d,%d\n", debugstr_w(curr->pszText),
1231 curr->rect.left, curr->rect.right);
1234 curr->rect.bottom = 0;
1235 curr->rect.top = curItemRowCount - 1;
1237 TRACE("Rect: %s\n", wine_dbgstr_rect(&curr->rect));
1240 * The leftmost position of the next item is the rightmost position
1241 * of this one.
1243 if (lStyle & TCS_BUTTONS)
1245 curItemLeftPos = curr->rect.right + BUTTON_SPACINGX;
1246 if (lStyle & TCS_FLATBUTTONS)
1247 curItemLeftPos += FLAT_BTN_SPACINGX;
1249 else
1250 curItemLeftPos = curr->rect.right;
1253 if (!((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)))
1256 * Check if we need a scrolling control.
1258 infoPtr->needsScrolling = (curItemLeftPos + (2 * SELECTED_TAB_OFFSET) >
1259 clientRect.right);
1261 /* Don't need scrolling, then update infoPtr->leftmostVisible */
1262 if(!infoPtr->needsScrolling)
1263 infoPtr->leftmostVisible = 0;
1265 else
1268 * No scrolling in Multiline or Vertical styles.
1270 infoPtr->needsScrolling = FALSE;
1271 infoPtr->leftmostVisible = 0;
1273 TAB_SetupScrolling(infoPtr->hwnd, infoPtr, &clientRect);
1275 /* Set the number of rows */
1276 infoPtr->uNumRows = curItemRowCount;
1278 /* Arrange all tabs evenly if style says so */
1279 if (!(lStyle & TCS_RAGGEDRIGHT) &&
1280 ((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)) &&
1281 (infoPtr->uNumItem > 0) &&
1282 (infoPtr->uNumRows > 1))
1284 INT tabPerRow,remTab,iRow;
1285 UINT iItm;
1286 INT iCount=0;
1289 * Ok windows tries to even out the rows. place the same
1290 * number of tabs in each row. So lets give that a shot
1293 tabPerRow = infoPtr->uNumItem / (infoPtr->uNumRows);
1294 remTab = infoPtr->uNumItem % (infoPtr->uNumRows);
1296 for (iItm=0,iRow=0,iCount=0,curItemLeftPos=0;
1297 iItm<infoPtr->uNumItem;
1298 iItm++,iCount++)
1300 /* normalize the current rect */
1301 TAB_ITEM *curr = TAB_GetItem(infoPtr, iItm);
1303 /* shift the item to the left side of the clientRect */
1304 curr->rect.right -= curr->rect.left;
1305 curr->rect.left = 0;
1307 TRACE("r=%d, cl=%d, cl.r=%d, iCount=%d, iRow=%d, uNumRows=%d, remTab=%d, tabPerRow=%d\n",
1308 curr->rect.right, curItemLeftPos, clientRect.right,
1309 iCount, iRow, infoPtr->uNumRows, remTab, tabPerRow);
1311 /* if we have reached the maximum number of tabs on this row */
1312 /* move to the next row, reset our current item left position and */
1313 /* the count of items on this row */
1315 if (lStyle & TCS_VERTICAL) {
1316 /* Vert: Add the remaining tabs in the *last* remainder rows */
1317 if (iCount >= ((iRow>=(INT)infoPtr->uNumRows - remTab)?tabPerRow + 1:tabPerRow)) {
1318 iRow++;
1319 curItemLeftPos = 0;
1320 iCount = 0;
1322 } else {
1323 /* Horz: Add the remaining tabs in the *first* remainder rows */
1324 if (iCount >= ((iRow<remTab)?tabPerRow + 1:tabPerRow)) {
1325 iRow++;
1326 curItemLeftPos = 0;
1327 iCount = 0;
1331 /* shift the item to the right to place it as the next item in this row */
1332 curr->rect.left += curItemLeftPos;
1333 curr->rect.right += curItemLeftPos;
1334 curr->rect.top = iRow;
1335 if (lStyle & TCS_BUTTONS)
1337 curItemLeftPos = curr->rect.right + 1;
1338 if (lStyle & TCS_FLATBUTTONS)
1339 curItemLeftPos += FLAT_BTN_SPACINGX;
1341 else
1342 curItemLeftPos = curr->rect.right;
1344 TRACE("arranging <%s>, l,r=%d,%d, row=%d\n",
1345 debugstr_w(curr->pszText), curr->rect.left,
1346 curr->rect.right, curr->rect.top);
1350 * Justify the rows
1353 INT widthDiff, iIndexStart=0, iIndexEnd=0;
1354 INT remainder;
1355 INT iCount=0;
1357 while(iIndexStart < infoPtr->uNumItem)
1359 TAB_ITEM *start = TAB_GetItem(infoPtr, iIndexStart);
1362 * find the index of the row
1364 /* find the first item on the next row */
1365 for (iIndexEnd=iIndexStart;
1366 (iIndexEnd < infoPtr->uNumItem) &&
1367 (TAB_GetItem(infoPtr, iIndexEnd)->rect.top ==
1368 start->rect.top) ;
1369 iIndexEnd++)
1370 /* intentionally blank */;
1373 * we need to justify these tabs so they fill the whole given
1374 * client area
1377 /* find the amount of space remaining on this row */
1378 widthDiff = clientRect.right - (2 * SELECTED_TAB_OFFSET) -
1379 TAB_GetItem(infoPtr, iIndexEnd - 1)->rect.right;
1381 /* iCount is the number of tab items on this row */
1382 iCount = iIndexEnd - iIndexStart;
1384 if (iCount > 1)
1386 remainder = widthDiff % iCount;
1387 widthDiff = widthDiff / iCount;
1388 /* add widthDiff/iCount, or extra space/items on row, to each item on this row */
1389 for (iIndex=iIndexStart, iCount=0; iIndex < iIndexEnd; iIndex++, iCount++)
1391 TAB_ITEM *item = TAB_GetItem(infoPtr, iIndex);
1393 item->rect.left += iCount * widthDiff;
1394 item->rect.right += (iCount + 1) * widthDiff;
1396 TRACE("adjusting 1 <%s>, l,r=%d,%d\n",
1397 debugstr_w(item->pszText),
1398 item->rect.left, item->rect.right);
1401 TAB_GetItem(infoPtr, iIndex - 1)->rect.right += remainder;
1403 else /* we have only one item on this row, make it take up the entire row */
1405 start->rect.left = clientRect.left;
1406 start->rect.right = clientRect.right - 4;
1408 TRACE("adjusting 2 <%s>, l,r=%d,%d\n",
1409 debugstr_w(start->pszText),
1410 start->rect.left, start->rect.right);
1415 iIndexStart = iIndexEnd;
1420 /* if TCS_VERTICAL rotate the tabs so they are along the side of the clientRect */
1421 if(lStyle & TCS_VERTICAL)
1423 RECT rcOriginal;
1424 for(iIndex = 0; iIndex < infoPtr->uNumItem; iIndex++)
1426 rcItem = &TAB_GetItem(infoPtr, iIndex)->rect;
1428 rcOriginal = *rcItem;
1430 /* this is rotating the items by 90 degrees clockwise around the center of the control */
1431 rcItem->top = (rcOriginal.left - clientRect.left);
1432 rcItem->bottom = rcItem->top + (rcOriginal.right - rcOriginal.left);
1433 rcItem->left = rcOriginal.top;
1434 rcItem->right = rcOriginal.bottom;
1438 TAB_EnsureSelectionVisible(infoPtr);
1439 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
1441 /* Cleanup */
1442 SelectObject (hdc, hOldFont);
1443 ReleaseDC (infoPtr->hwnd, hdc);
1447 static void
1448 TAB_EraseTabInterior(const TAB_INFO *infoPtr, HDC hdc, INT iItem, RECT *drawRect)
1450 LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1451 HBRUSH hbr = CreateSolidBrush (comctl32_color.clrBtnFace);
1452 BOOL deleteBrush = TRUE;
1453 RECT rTemp = *drawRect;
1455 if (lStyle & TCS_BUTTONS)
1457 if (iItem == infoPtr->iSelected)
1459 /* Background color */
1460 if (!(lStyle & TCS_OWNERDRAWFIXED))
1462 DeleteObject(hbr);
1463 hbr = GetSysColorBrush(COLOR_SCROLLBAR);
1465 SetTextColor(hdc, comctl32_color.clr3dFace);
1466 SetBkColor(hdc, comctl32_color.clr3dHilight);
1468 /* if COLOR_WINDOW happens to be the same as COLOR_3DHILIGHT
1469 * we better use 0x55aa bitmap brush to make scrollbar's background
1470 * look different from the window background.
1472 if (comctl32_color.clr3dHilight == comctl32_color.clrWindow)
1473 hbr = COMCTL32_hPattern55AABrush;
1475 deleteBrush = FALSE;
1477 FillRect(hdc, &rTemp, hbr);
1479 else /* ! selected */
1481 if (lStyle & TCS_FLATBUTTONS)
1483 FillRect(hdc, drawRect, hbr);
1484 if (iItem == infoPtr->iHotTracked)
1485 DrawEdge(hdc, drawRect, EDGE_RAISED, BF_SOFT|BF_RECT);
1487 else
1488 FillRect(hdc, &rTemp, hbr);
1492 else /* !TCS_BUTTONS */
1494 InflateRect(&rTemp, -2, -2);
1495 if (!GetWindowTheme (infoPtr->hwnd))
1496 FillRect(hdc, &rTemp, hbr);
1499 /* highlighting is drawn on top of previous fills */
1500 if (TAB_GetItem(infoPtr, iItem)->dwState & TCIS_HIGHLIGHTED)
1502 if (deleteBrush)
1504 DeleteObject(hbr);
1505 deleteBrush = FALSE;
1507 hbr = GetSysColorBrush(COLOR_HIGHLIGHT);
1508 FillRect(hdc, &rTemp, hbr);
1511 /* Cleanup */
1512 if (deleteBrush) DeleteObject(hbr);
1515 /******************************************************************************
1516 * TAB_DrawItemInterior
1518 * This method is used to draw the interior (text and icon) of a single tab
1519 * into the tab control.
1521 static void
1522 TAB_DrawItemInterior(const TAB_INFO *infoPtr, HDC hdc, INT iItem, RECT *drawRect)
1524 LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1526 RECT localRect;
1528 HPEN htextPen;
1529 HPEN holdPen;
1530 INT oldBkMode;
1531 HFONT hOldFont;
1533 /* if (drawRect == NULL) */
1535 BOOL isVisible;
1536 RECT itemRect;
1537 RECT selectedRect;
1540 * Get the rectangle for the item.
1542 isVisible = TAB_InternalGetItemRect(infoPtr, iItem, &itemRect, &selectedRect);
1543 if (!isVisible)
1544 return;
1547 * Make sure drawRect points to something valid; simplifies code.
1549 drawRect = &localRect;
1552 * This logic copied from the part of TAB_DrawItem which draws
1553 * the tab background. It's important to keep it in sync. I
1554 * would have liked to avoid code duplication, but couldn't figure
1555 * out how without making spaghetti of TAB_DrawItem.
1557 if (iItem == infoPtr->iSelected)
1558 *drawRect = selectedRect;
1559 else
1560 *drawRect = itemRect;
1562 if (lStyle & TCS_BUTTONS)
1564 if (iItem == infoPtr->iSelected)
1566 drawRect->left += 4;
1567 drawRect->top += 4;
1568 drawRect->right -= 4;
1569 drawRect->bottom -= 1;
1571 else
1573 drawRect->left += 2;
1574 drawRect->top += 2;
1575 drawRect->right -= 2;
1576 drawRect->bottom -= 2;
1579 else
1581 if ((lStyle & TCS_VERTICAL) && (lStyle & TCS_BOTTOM))
1583 if (iItem != infoPtr->iSelected)
1585 drawRect->left += 2;
1586 drawRect->top += 2;
1587 drawRect->bottom -= 2;
1590 else if (lStyle & TCS_VERTICAL)
1592 if (iItem == infoPtr->iSelected)
1594 drawRect->right += 1;
1596 else
1598 drawRect->top += 2;
1599 drawRect->right -= 2;
1600 drawRect->bottom -= 2;
1603 else if (lStyle & TCS_BOTTOM)
1605 if (iItem == infoPtr->iSelected)
1607 drawRect->top -= 2;
1609 else
1611 InflateRect(drawRect, -2, -2);
1612 drawRect->bottom += 2;
1615 else
1617 if (iItem == infoPtr->iSelected)
1619 drawRect->bottom += 3;
1621 else
1623 drawRect->bottom -= 2;
1624 InflateRect(drawRect, -2, 0);
1629 TRACE("drawRect=(%s)\n", wine_dbgstr_rect(drawRect));
1631 /* Clear interior */
1632 TAB_EraseTabInterior (infoPtr, hdc, iItem, drawRect);
1634 /* Draw the focus rectangle */
1635 if (!(lStyle & TCS_FOCUSNEVER) &&
1636 (GetFocus() == infoPtr->hwnd) &&
1637 (iItem == infoPtr->uFocus) )
1639 RECT rFocus = *drawRect;
1640 InflateRect(&rFocus, -3, -3);
1641 if (lStyle & TCS_BOTTOM && !(lStyle & TCS_VERTICAL))
1642 rFocus.top -= 3;
1643 if (lStyle & TCS_BUTTONS)
1645 rFocus.left -= 3;
1646 rFocus.top -= 3;
1649 DrawFocusRect(hdc, &rFocus);
1653 * Text pen
1655 htextPen = CreatePen( PS_SOLID, 1, GetSysColor(COLOR_BTNTEXT) );
1656 holdPen = SelectObject(hdc, htextPen);
1657 hOldFont = SelectObject(hdc, infoPtr->hFont);
1660 * Setup for text output
1662 oldBkMode = SetBkMode(hdc, TRANSPARENT);
1663 if (!GetWindowTheme (infoPtr->hwnd) || (lStyle & TCS_BUTTONS))
1665 if ((lStyle & TCS_HOTTRACK) && (iItem == infoPtr->iHotTracked) &&
1666 !(lStyle & TCS_FLATBUTTONS))
1667 SetTextColor(hdc, comctl32_color.clrHighlight);
1668 else if (TAB_GetItem(infoPtr, iItem)->dwState & TCIS_HIGHLIGHTED)
1669 SetTextColor(hdc, comctl32_color.clrHighlightText);
1670 else
1671 SetTextColor(hdc, comctl32_color.clrBtnText);
1675 * if owner draw, tell the owner to draw
1677 if ((lStyle & TCS_OWNERDRAWFIXED) && GetParent(infoPtr->hwnd))
1679 DRAWITEMSTRUCT dis;
1680 UINT id;
1682 drawRect->top += 2;
1683 drawRect->right -= 1;
1684 if ( iItem == infoPtr->iSelected )
1686 drawRect->right -= 1;
1687 drawRect->left += 1;
1691 * get the control id
1693 id = (UINT)GetWindowLongPtrW( infoPtr->hwnd, GWLP_ID );
1696 * put together the DRAWITEMSTRUCT
1698 dis.CtlType = ODT_TAB;
1699 dis.CtlID = id;
1700 dis.itemID = iItem;
1701 dis.itemAction = ODA_DRAWENTIRE;
1702 dis.itemState = 0;
1703 if ( iItem == infoPtr->iSelected )
1704 dis.itemState |= ODS_SELECTED;
1705 if (infoPtr->uFocus == iItem)
1706 dis.itemState |= ODS_FOCUS;
1707 dis.hwndItem = infoPtr->hwnd;
1708 dis.hDC = hdc;
1709 CopyRect(&dis.rcItem,drawRect);
1710 dis.itemData = (ULONG_PTR)TAB_GetItem(infoPtr, iItem)->extra;
1713 * send the draw message
1715 SendMessageW( infoPtr->hwndNotify, WM_DRAWITEM, (WPARAM)id, (LPARAM)&dis );
1717 else
1719 TAB_ITEM *item = TAB_GetItem(infoPtr, iItem);
1720 RECT rcTemp;
1721 RECT rcImage;
1723 /* used to center the icon and text in the tab */
1724 RECT rcText;
1725 INT center_offset_h, center_offset_v;
1727 /* set rcImage to drawRect, we will use top & left in our ImageList_Draw call */
1728 rcImage = *drawRect;
1730 rcTemp = *drawRect;
1732 rcText.left = rcText.top = rcText.right = rcText.bottom = 0;
1734 /* get the rectangle that the text fits in */
1735 if (item->pszText)
1737 DrawTextW(hdc, item->pszText, -1, &rcText, DT_CALCRECT);
1740 * If not owner draw, then do the drawing ourselves.
1742 * Draw the icon.
1744 if (infoPtr->himl && item->iImage != -1)
1746 INT cx;
1747 INT cy;
1749 ImageList_GetIconSize(infoPtr->himl, &cx, &cy);
1751 if(lStyle & TCS_VERTICAL)
1753 center_offset_h = ((drawRect->bottom - drawRect->top) - (cy + infoPtr->uHItemPadding + (rcText.right - rcText.left))) / 2;
1754 center_offset_v = ((drawRect->right - drawRect->left) - cx) / 2;
1756 else
1758 center_offset_h = ((drawRect->right - drawRect->left) - (cx + infoPtr->uHItemPadding + (rcText.right - rcText.left))) / 2;
1759 center_offset_v = ((drawRect->bottom - drawRect->top) - cy) / 2;
1762 /* if an item is selected, the icon is shifted up instead of down */
1763 if (iItem == infoPtr->iSelected)
1764 center_offset_v -= infoPtr->uVItemPadding / 2;
1765 else
1766 center_offset_v += infoPtr->uVItemPadding / 2;
1768 if (lStyle & TCS_FIXEDWIDTH && lStyle & (TCS_FORCELABELLEFT | TCS_FORCEICONLEFT))
1769 center_offset_h = infoPtr->uHItemPadding;
1771 if (center_offset_h < 2)
1772 center_offset_h = 2;
1774 if (center_offset_v < 0)
1775 center_offset_v = 0;
1777 TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%s), textlen=%d\n",
1778 debugstr_w(item->pszText), center_offset_h, center_offset_v,
1779 wine_dbgstr_rect(drawRect), (rcText.right-rcText.left));
1781 if((lStyle & TCS_VERTICAL) && (lStyle & TCS_BOTTOM))
1783 rcImage.top = drawRect->top + center_offset_h;
1784 /* if tab is TCS_VERTICAL and TCS_BOTTOM, the text is drawn from the */
1785 /* right side of the tab, but the image still uses the left as its x position */
1786 /* this keeps the image always drawn off of the same side of the tab */
1787 rcImage.left = drawRect->right - cx - center_offset_v;
1788 drawRect->top += cy + infoPtr->uHItemPadding;
1790 else if(lStyle & TCS_VERTICAL)
1792 rcImage.top = drawRect->bottom - cy - center_offset_h;
1793 rcImage.left = drawRect->left + center_offset_v;
1794 drawRect->bottom -= cy + infoPtr->uHItemPadding;
1796 else /* normal style, whether TCS_BOTTOM or not */
1798 rcImage.left = drawRect->left + center_offset_h;
1799 rcImage.top = drawRect->top + center_offset_v;
1800 drawRect->left += cx + infoPtr->uHItemPadding;
1803 TRACE("drawing image=%d, left=%d, top=%d\n",
1804 item->iImage, rcImage.left, rcImage.top-1);
1805 ImageList_Draw
1807 infoPtr->himl,
1808 item->iImage,
1809 hdc,
1810 rcImage.left,
1811 rcImage.top,
1812 ILD_NORMAL
1816 /* Now position text */
1817 if (lStyle & TCS_FIXEDWIDTH && lStyle & TCS_FORCELABELLEFT)
1818 center_offset_h = infoPtr->uHItemPadding;
1819 else
1820 if(lStyle & TCS_VERTICAL)
1821 center_offset_h = ((drawRect->bottom - drawRect->top) - (rcText.right - rcText.left)) / 2;
1822 else
1823 center_offset_h = ((drawRect->right - drawRect->left) - (rcText.right - rcText.left)) / 2;
1825 if(lStyle & TCS_VERTICAL)
1827 if(lStyle & TCS_BOTTOM)
1828 drawRect->top+=center_offset_h;
1829 else
1830 drawRect->bottom-=center_offset_h;
1832 center_offset_v = ((drawRect->right - drawRect->left) - (rcText.bottom - rcText.top)) / 2;
1834 else
1836 drawRect->left += center_offset_h;
1837 center_offset_v = ((drawRect->bottom - drawRect->top) - (rcText.bottom - rcText.top)) / 2;
1840 /* if an item is selected, the text is shifted up instead of down */
1841 if (iItem == infoPtr->iSelected)
1842 center_offset_v -= infoPtr->uVItemPadding / 2;
1843 else
1844 center_offset_v += infoPtr->uVItemPadding / 2;
1846 if (center_offset_v < 0)
1847 center_offset_v = 0;
1849 if(lStyle & TCS_VERTICAL)
1850 drawRect->left += center_offset_v;
1851 else
1852 drawRect->top += center_offset_v;
1854 /* Draw the text */
1855 if(lStyle & TCS_VERTICAL) /* if we are vertical rotate the text and each character */
1857 static const WCHAR ArialW[] = { 'A','r','i','a','l',0 };
1858 LOGFONTW logfont;
1859 HFONT hFont = 0;
1860 INT nEscapement = 900;
1861 INT nOrientation = 900;
1863 if(lStyle & TCS_BOTTOM)
1865 nEscapement = -900;
1866 nOrientation = -900;
1869 /* to get a font with the escapement and orientation we are looking for, we need to */
1870 /* call CreateFontIndirectA, which requires us to set the values of the logfont we pass in */
1871 if (!GetObjectW((infoPtr->hFont) ?
1872 infoPtr->hFont : GetStockObject(SYSTEM_FONT),
1873 sizeof(LOGFONTW),&logfont))
1875 INT iPointSize = 9;
1877 lstrcpyW(logfont.lfFaceName, ArialW);
1878 logfont.lfHeight = -MulDiv(iPointSize, GetDeviceCaps(hdc, LOGPIXELSY),
1879 72);
1880 logfont.lfWeight = FW_NORMAL;
1881 logfont.lfItalic = 0;
1882 logfont.lfUnderline = 0;
1883 logfont.lfStrikeOut = 0;
1886 logfont.lfEscapement = nEscapement;
1887 logfont.lfOrientation = nOrientation;
1888 hFont = CreateFontIndirectW(&logfont);
1889 SelectObject(hdc, hFont);
1891 if (item->pszText)
1893 ExtTextOutW(hdc,
1894 (lStyle & TCS_BOTTOM) ? drawRect->right : drawRect->left,
1895 (!(lStyle & TCS_BOTTOM)) ? drawRect->bottom : drawRect->top,
1896 ETO_CLIPPED,
1897 drawRect,
1898 item->pszText,
1899 lstrlenW(item->pszText),
1903 DeleteObject(hFont);
1905 else
1907 TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%s), textlen=%d\n",
1908 debugstr_w(item->pszText), center_offset_h, center_offset_v,
1909 wine_dbgstr_rect(drawRect), (rcText.right-rcText.left));
1910 if (item->pszText)
1912 DrawTextW
1914 hdc,
1915 item->pszText,
1916 lstrlenW(item->pszText),
1917 drawRect,
1918 DT_LEFT | DT_SINGLELINE
1923 *drawRect = rcTemp; /* restore drawRect */
1927 * Cleanup
1929 SelectObject(hdc, hOldFont);
1930 SetBkMode(hdc, oldBkMode);
1931 SelectObject(hdc, holdPen);
1932 DeleteObject( htextPen );
1935 /******************************************************************************
1936 * TAB_DrawItem
1938 * This method is used to draw a single tab into the tab control.
1940 static void TAB_DrawItem(const TAB_INFO *infoPtr, HDC hdc, INT iItem)
1942 LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1943 RECT itemRect;
1944 RECT selectedRect;
1945 BOOL isVisible;
1946 RECT r, fillRect, r1;
1947 INT clRight = 0;
1948 INT clBottom = 0;
1949 COLORREF bkgnd, corner;
1950 HTHEME theme;
1953 * Get the rectangle for the item.
1955 isVisible = TAB_InternalGetItemRect(infoPtr,
1956 iItem,
1957 &itemRect,
1958 &selectedRect);
1960 if (isVisible)
1962 RECT rUD, rC;
1964 /* Clip UpDown control to not draw over it */
1965 if (infoPtr->needsScrolling)
1967 GetWindowRect(infoPtr->hwnd, &rC);
1968 GetWindowRect(infoPtr->hwndUpDown, &rUD);
1969 ExcludeClipRect(hdc, rUD.left - rC.left, rUD.top - rC.top, rUD.right - rC.left, rUD.bottom - rC.top);
1972 /* If you need to see what the control is doing,
1973 * then override these variables. They will change what
1974 * fill colors are used for filling the tabs, and the
1975 * corners when drawing the edge.
1977 bkgnd = comctl32_color.clrBtnFace;
1978 corner = comctl32_color.clrBtnFace;
1980 if (lStyle & TCS_BUTTONS)
1982 /* Get item rectangle */
1983 r = itemRect;
1985 /* Separators between flat buttons */
1986 if ((lStyle & TCS_FLATBUTTONS) && (infoPtr->exStyle & TCS_EX_FLATSEPARATORS))
1988 r1 = r;
1989 r1.right += (FLAT_BTN_SPACINGX -2);
1990 DrawEdge(hdc, &r1, EDGE_ETCHED, BF_RIGHT);
1993 if (iItem == infoPtr->iSelected)
1995 DrawEdge(hdc, &r, EDGE_SUNKEN, BF_SOFT|BF_RECT);
1997 OffsetRect(&r, 1, 1);
1999 else /* ! selected */
2001 DWORD state = TAB_GetItem(infoPtr, iItem)->dwState;
2003 if (state & TCIS_BUTTONPRESSED)
2004 DrawEdge(hdc, &r, EDGE_SUNKEN, BF_SOFT|BF_RECT);
2005 else
2006 if (!(lStyle & TCS_FLATBUTTONS))
2007 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_RECT);
2010 else /* !TCS_BUTTONS */
2012 /* We draw a rectangle of different sizes depending on the selection
2013 * state. */
2014 if (iItem == infoPtr->iSelected) {
2015 RECT rect;
2016 GetClientRect (infoPtr->hwnd, &rect);
2017 clRight = rect.right;
2018 clBottom = rect.bottom;
2019 r = selectedRect;
2021 else
2022 r = itemRect;
2025 * Erase the background. (Delay it but setup rectangle.)
2026 * This is necessary when drawing the selected item since it is larger
2027 * than the others, it might overlap with stuff already drawn by the
2028 * other tabs
2030 fillRect = r;
2032 /* Draw themed tabs - but only if they are at the top.
2033 * Windows draws even side or bottom tabs themed, with wacky results.
2034 * However, since in Wine apps may get themed that did not opt in via
2035 * a manifest avoid theming when we know the result will be wrong */
2036 if ((theme = GetWindowTheme (infoPtr->hwnd))
2037 && ((lStyle & (TCS_VERTICAL | TCS_BOTTOM)) == 0))
2039 static const int partIds[8] = {
2040 /* Normal item */
2041 TABP_TABITEM,
2042 TABP_TABITEMLEFTEDGE,
2043 TABP_TABITEMRIGHTEDGE,
2044 TABP_TABITEMBOTHEDGE,
2045 /* Selected tab */
2046 TABP_TOPTABITEM,
2047 TABP_TOPTABITEMLEFTEDGE,
2048 TABP_TOPTABITEMRIGHTEDGE,
2049 TABP_TOPTABITEMBOTHEDGE,
2051 int partIndex = 0;
2052 int stateId = TIS_NORMAL;
2054 /* selected and unselected tabs have different parts */
2055 if (iItem == infoPtr->iSelected)
2056 partIndex += 4;
2057 /* The part also differs on the position of a tab on a line.
2058 * "Visually" determining the position works well enough. */
2059 if(selectedRect.left == 0)
2060 partIndex += 1;
2061 if(selectedRect.right == clRight)
2062 partIndex += 2;
2064 if (iItem == infoPtr->iSelected)
2065 stateId = TIS_SELECTED;
2066 else if (iItem == infoPtr->iHotTracked)
2067 stateId = TIS_HOT;
2068 else if (iItem == infoPtr->uFocus)
2069 stateId = TIS_FOCUSED;
2071 /* Adjust rectangle for bottommost row */
2072 if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2073 r.bottom += 3;
2075 DrawThemeBackground (theme, hdc, partIds[partIndex], stateId, &r, NULL);
2076 GetThemeBackgroundContentRect (theme, hdc, partIds[partIndex], stateId, &r, &r);
2078 else if(lStyle & TCS_VERTICAL)
2080 /* These are for adjusting the drawing of a Selected tab */
2081 /* The initial values are for the normal case of non-Selected */
2082 int ZZ = 1; /* Do not stretch if selected */
2083 if (iItem == infoPtr->iSelected) {
2084 ZZ = 0;
2086 /* if leftmost draw the line longer */
2087 if(selectedRect.top == 0)
2088 fillRect.top += CONTROL_BORDER_SIZEY;
2089 /* if rightmost draw the line longer */
2090 if(selectedRect.bottom == clBottom)
2091 fillRect.bottom -= CONTROL_BORDER_SIZEY;
2094 if (lStyle & TCS_BOTTOM)
2096 /* Adjust both rectangles to match native */
2097 r.left += (1-ZZ);
2099 TRACE("<right> item=%d, fill=(%s), edge=(%s)\n",
2100 iItem, wine_dbgstr_rect(&fillRect), wine_dbgstr_rect(&r));
2102 /* Clear interior */
2103 SetBkColor(hdc, bkgnd);
2104 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2106 /* Draw rectangular edge around tab */
2107 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_RIGHT|BF_TOP|BF_BOTTOM);
2109 /* Now erase the top corner and draw diagonal edge */
2110 SetBkColor(hdc, corner);
2111 r1.left = r.right - ROUND_CORNER_SIZE - 1;
2112 r1.top = r.top;
2113 r1.right = r.right;
2114 r1.bottom = r1.top + ROUND_CORNER_SIZE;
2115 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2116 r1.right--;
2117 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPLEFT);
2119 /* Now erase the bottom corner and draw diagonal edge */
2120 r1.left = r.right - ROUND_CORNER_SIZE - 1;
2121 r1.bottom = r.bottom;
2122 r1.right = r.right;
2123 r1.top = r1.bottom - ROUND_CORNER_SIZE;
2124 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2125 r1.right--;
2126 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMLEFT);
2128 if ((iItem == infoPtr->iSelected) && (selectedRect.top == 0)) {
2129 r1 = r;
2130 r1.right = r1.left;
2131 r1.left--;
2132 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_TOP);
2136 else
2138 TRACE("<left> item=%d, fill=(%s), edge=(%s)\n",
2139 iItem, wine_dbgstr_rect(&fillRect), wine_dbgstr_rect(&r));
2141 /* Clear interior */
2142 SetBkColor(hdc, bkgnd);
2143 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2145 /* Draw rectangular edge around tab */
2146 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_TOP|BF_BOTTOM);
2148 /* Now erase the top corner and draw diagonal edge */
2149 SetBkColor(hdc, corner);
2150 r1.left = r.left;
2151 r1.top = r.top;
2152 r1.right = r1.left + ROUND_CORNER_SIZE + 1;
2153 r1.bottom = r1.top + ROUND_CORNER_SIZE;
2154 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2155 r1.left++;
2156 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPRIGHT);
2158 /* Now erase the bottom corner and draw diagonal edge */
2159 r1.left = r.left;
2160 r1.bottom = r.bottom;
2161 r1.right = r1.left + ROUND_CORNER_SIZE + 1;
2162 r1.top = r1.bottom - ROUND_CORNER_SIZE;
2163 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2164 r1.left++;
2165 DrawEdge(hdc, &r1, EDGE_SUNKEN, BF_DIAGONAL_ENDTOPLEFT);
2168 else /* ! TCS_VERTICAL */
2170 /* These are for adjusting the drawing of a Selected tab */
2171 /* The initial values are for the normal case of non-Selected */
2172 if (iItem == infoPtr->iSelected) {
2173 /* if leftmost draw the line longer */
2174 if(selectedRect.left == 0)
2175 fillRect.left += CONTROL_BORDER_SIZEX;
2176 /* if rightmost draw the line longer */
2177 if(selectedRect.right == clRight)
2178 fillRect.right -= CONTROL_BORDER_SIZEX;
2181 if (lStyle & TCS_BOTTOM)
2183 /* Adjust both rectangles for topmost row */
2184 if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2186 fillRect.top -= 2;
2187 r.top -= 1;
2190 TRACE("<bottom> item=%d, fill=(%s), edge=(%s)\n",
2191 iItem, wine_dbgstr_rect(&fillRect), wine_dbgstr_rect(&r));
2193 /* Clear interior */
2194 SetBkColor(hdc, bkgnd);
2195 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2197 /* Draw rectangular edge around tab */
2198 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_BOTTOM|BF_RIGHT);
2200 /* Now erase the righthand corner and draw diagonal edge */
2201 SetBkColor(hdc, corner);
2202 r1.left = r.right - ROUND_CORNER_SIZE;
2203 r1.bottom = r.bottom;
2204 r1.right = r.right;
2205 r1.top = r1.bottom - ROUND_CORNER_SIZE - 1;
2206 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2207 r1.bottom--;
2208 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMLEFT);
2210 /* Now erase the lefthand corner and draw diagonal edge */
2211 r1.left = r.left;
2212 r1.bottom = r.bottom;
2213 r1.right = r1.left + ROUND_CORNER_SIZE;
2214 r1.top = r1.bottom - ROUND_CORNER_SIZE - 1;
2215 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2216 r1.bottom--;
2217 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPLEFT);
2219 if (iItem == infoPtr->iSelected)
2221 r.top += 2;
2222 r.left += 1;
2223 if (selectedRect.left == 0)
2225 r1 = r;
2226 r1.bottom = r1.top;
2227 r1.top--;
2228 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_LEFT);
2233 else
2235 /* Adjust both rectangles for bottommost row */
2236 if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2238 fillRect.bottom += 3;
2239 r.bottom += 2;
2242 TRACE("<top> item=%d, fill=(%s), edge=(%s)\n",
2243 iItem, wine_dbgstr_rect(&fillRect), wine_dbgstr_rect(&r));
2245 /* Clear interior */
2246 SetBkColor(hdc, bkgnd);
2247 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2249 /* Draw rectangular edge around tab */
2250 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_TOP|BF_RIGHT);
2252 /* Now erase the righthand corner and draw diagonal edge */
2253 SetBkColor(hdc, corner);
2254 r1.left = r.right - ROUND_CORNER_SIZE;
2255 r1.top = r.top;
2256 r1.right = r.right;
2257 r1.bottom = r1.top + ROUND_CORNER_SIZE + 1;
2258 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2259 r1.top++;
2260 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMRIGHT);
2262 /* Now erase the lefthand corner and draw diagonal edge */
2263 r1.left = r.left;
2264 r1.top = r.top;
2265 r1.right = r1.left + ROUND_CORNER_SIZE;
2266 r1.bottom = r1.top + ROUND_CORNER_SIZE + 1;
2267 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2268 r1.top++;
2269 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPRIGHT);
2274 TAB_DumpItemInternal(infoPtr, iItem);
2276 /* This modifies r to be the text rectangle. */
2277 TAB_DrawItemInterior(infoPtr, hdc, iItem, &r);
2281 /******************************************************************************
2282 * TAB_DrawBorder
2284 * This method is used to draw the raised border around the tab control
2285 * "content" area.
2287 static void TAB_DrawBorder(const TAB_INFO *infoPtr, HDC hdc)
2289 RECT rect;
2290 DWORD lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
2291 HTHEME theme = GetWindowTheme (infoPtr->hwnd);
2293 GetClientRect (infoPtr->hwnd, &rect);
2296 * Adjust for the style
2299 if (infoPtr->uNumItem)
2301 if ((lStyle & TCS_BOTTOM) && !(lStyle & TCS_VERTICAL))
2302 rect.bottom -= infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2303 else if((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
2304 rect.right -= infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2305 else if(lStyle & TCS_VERTICAL)
2306 rect.left += infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2307 else /* not TCS_VERTICAL and not TCS_BOTTOM */
2308 rect.top += infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2311 TRACE("border=(%s)\n", wine_dbgstr_rect(&rect));
2313 if (theme)
2314 DrawThemeBackground (theme, hdc, TABP_PANE, 0, &rect, NULL);
2315 else
2316 DrawEdge(hdc, &rect, EDGE_RAISED, BF_SOFT|BF_RECT);
2319 /******************************************************************************
2320 * TAB_Refresh
2322 * This method repaints the tab control..
2324 static void TAB_Refresh (TAB_INFO *infoPtr, HDC hdc)
2326 HFONT hOldFont;
2327 INT i;
2329 if (!infoPtr->DoRedraw)
2330 return;
2332 hOldFont = SelectObject (hdc, infoPtr->hFont);
2334 if (GetWindowLongW(infoPtr->hwnd, GWL_STYLE) & TCS_BUTTONS)
2336 for (i = 0; i < infoPtr->uNumItem; i++)
2337 TAB_DrawItem (infoPtr, hdc, i);
2339 else
2341 /* Draw all the non selected item first */
2342 for (i = 0; i < infoPtr->uNumItem; i++)
2344 if (i != infoPtr->iSelected)
2345 TAB_DrawItem (infoPtr, hdc, i);
2348 /* Now, draw the border, draw it before the selected item
2349 * since the selected item overwrites part of the border. */
2350 TAB_DrawBorder (infoPtr, hdc);
2352 /* Then, draw the selected item */
2353 TAB_DrawItem (infoPtr, hdc, infoPtr->iSelected);
2356 SelectObject (hdc, hOldFont);
2359 static inline DWORD TAB_GetRowCount (const TAB_INFO *infoPtr)
2361 return infoPtr->uNumRows;
2364 static inline LRESULT TAB_SetRedraw (TAB_INFO *infoPtr, BOOL doRedraw)
2366 infoPtr->DoRedraw = doRedraw;
2367 return 0;
2370 /******************************************************************************
2371 * TAB_EnsureSelectionVisible
2373 * This method will make sure that the current selection is completely
2374 * visible by scrolling until it is.
2376 static void TAB_EnsureSelectionVisible(
2377 TAB_INFO* infoPtr)
2379 INT iSelected = infoPtr->iSelected;
2380 LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
2381 INT iOrigLeftmostVisible = infoPtr->leftmostVisible;
2383 /* set the items row to the bottommost row or topmost row depending on
2384 * style */
2385 if ((infoPtr->uNumRows > 1) && !(lStyle & TCS_BUTTONS))
2387 TAB_ITEM *selected = TAB_GetItem(infoPtr, iSelected);
2388 INT newselected;
2389 INT iTargetRow;
2391 if(lStyle & TCS_VERTICAL)
2392 newselected = selected->rect.left;
2393 else
2394 newselected = selected->rect.top;
2396 /* the target row is always (number of rows - 1)
2397 as row 0 is furthest from the clientRect */
2398 iTargetRow = infoPtr->uNumRows - 1;
2400 if (newselected != iTargetRow)
2402 UINT i;
2403 if(lStyle & TCS_VERTICAL)
2405 for (i=0; i < infoPtr->uNumItem; i++)
2407 /* move everything in the row of the selected item to the iTargetRow */
2408 TAB_ITEM *item = TAB_GetItem(infoPtr, i);
2410 if (item->rect.left == newselected )
2411 item->rect.left = iTargetRow;
2412 else
2414 if (item->rect.left > newselected)
2415 item->rect.left-=1;
2419 else
2421 for (i=0; i < infoPtr->uNumItem; i++)
2423 TAB_ITEM *item = TAB_GetItem(infoPtr, i);
2425 if (item->rect.top == newselected )
2426 item->rect.top = iTargetRow;
2427 else
2429 if (item->rect.top > newselected)
2430 item->rect.top-=1;
2434 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
2439 * Do the trivial cases first.
2441 if ( (!infoPtr->needsScrolling) ||
2442 (infoPtr->hwndUpDown==0) || (lStyle & TCS_VERTICAL))
2443 return;
2445 if (infoPtr->leftmostVisible >= iSelected)
2447 infoPtr->leftmostVisible = iSelected;
2449 else
2451 TAB_ITEM *selected = TAB_GetItem(infoPtr, iSelected);
2452 RECT r;
2453 INT width;
2454 UINT i;
2456 /* Calculate the part of the client area that is visible */
2457 GetClientRect(infoPtr->hwnd, &r);
2458 width = r.right;
2460 GetClientRect(infoPtr->hwndUpDown, &r);
2461 width -= r.right;
2463 if ((selected->rect.right -
2464 selected->rect.left) >= width )
2466 /* Special case: width of selected item is greater than visible
2467 * part of control.
2469 infoPtr->leftmostVisible = iSelected;
2471 else
2473 for (i = infoPtr->leftmostVisible; i < infoPtr->uNumItem; i++)
2475 if ((selected->rect.right - TAB_GetItem(infoPtr, i)->rect.left) < width)
2476 break;
2478 infoPtr->leftmostVisible = i;
2482 if (infoPtr->leftmostVisible != iOrigLeftmostVisible)
2483 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
2485 SendMessageW(infoPtr->hwndUpDown, UDM_SETPOS, 0,
2486 MAKELONG(infoPtr->leftmostVisible, 0));
2489 /******************************************************************************
2490 * TAB_InvalidateTabArea
2492 * This method will invalidate the portion of the control that contains the
2493 * tabs. It is called when the state of the control changes and needs
2494 * to be redisplayed
2496 static void TAB_InvalidateTabArea(const TAB_INFO *infoPtr)
2498 RECT clientRect, rInvalidate, rAdjClient;
2499 DWORD lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
2500 INT lastRow = infoPtr->uNumRows - 1;
2501 RECT rect;
2503 if (lastRow < 0) return;
2505 GetClientRect(infoPtr->hwnd, &clientRect);
2506 rInvalidate = clientRect;
2507 rAdjClient = clientRect;
2509 TAB_AdjustRect(infoPtr, 0, &rAdjClient);
2511 TAB_InternalGetItemRect(infoPtr, infoPtr->uNumItem-1 , &rect, NULL);
2512 if ((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
2514 rInvalidate.left = rAdjClient.right;
2515 if (infoPtr->uNumRows == 1)
2516 rInvalidate.bottom = clientRect.top + rect.bottom + 2 * SELECTED_TAB_OFFSET;
2518 else if(lStyle & TCS_VERTICAL)
2520 rInvalidate.right = rAdjClient.left;
2521 if (infoPtr->uNumRows == 1)
2522 rInvalidate.bottom = clientRect.top + rect.bottom + 2 * SELECTED_TAB_OFFSET;
2524 else if (lStyle & TCS_BOTTOM)
2526 rInvalidate.top = rAdjClient.bottom;
2527 if (infoPtr->uNumRows == 1)
2528 rInvalidate.right = clientRect.left + rect.right + 2 * SELECTED_TAB_OFFSET;
2530 else
2532 rInvalidate.bottom = rAdjClient.top;
2533 if (infoPtr->uNumRows == 1)
2534 rInvalidate.right = clientRect.left + rect.right + 2 * SELECTED_TAB_OFFSET;
2537 /* Punch out the updown control */
2538 if (infoPtr->needsScrolling && (rInvalidate.right > 0)) {
2539 RECT r;
2540 GetClientRect(infoPtr->hwndUpDown, &r);
2541 if (rInvalidate.right > clientRect.right - r.left)
2542 rInvalidate.right = rInvalidate.right - (r.right - r.left);
2543 else
2544 rInvalidate.right = clientRect.right - r.left;
2547 TRACE("invalidate (%s)\n", wine_dbgstr_rect(&rInvalidate));
2549 InvalidateRect(infoPtr->hwnd, &rInvalidate, TRUE);
2552 static inline LRESULT TAB_Paint (TAB_INFO *infoPtr, HDC hdcPaint)
2554 HDC hdc;
2555 PAINTSTRUCT ps;
2557 if (hdcPaint)
2558 hdc = hdcPaint;
2559 else
2561 hdc = BeginPaint (infoPtr->hwnd, &ps);
2562 TRACE("erase %d, rect=(%s)\n", ps.fErase, wine_dbgstr_rect(&ps.rcPaint));
2565 TAB_Refresh (infoPtr, hdc);
2567 if (!hdcPaint)
2568 EndPaint (infoPtr->hwnd, &ps);
2570 return 0;
2573 static LRESULT
2574 TAB_InsertItemT (TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam, BOOL bUnicode)
2576 TAB_ITEM *item;
2577 TCITEMW *pti;
2578 INT iItem;
2579 RECT rect;
2581 GetClientRect (infoPtr->hwnd, &rect);
2582 TRACE("Rect: %p %s\n", infoPtr->hwnd, wine_dbgstr_rect(&rect));
2584 pti = (TCITEMW *)lParam;
2585 iItem = (INT)wParam;
2587 if (iItem < 0) return -1;
2588 if (iItem > infoPtr->uNumItem)
2589 iItem = infoPtr->uNumItem;
2591 TAB_DumpItemExternalT(pti, iItem, bUnicode);
2594 if (infoPtr->uNumItem == 0) {
2595 infoPtr->items = Alloc (TAB_ITEM_SIZE(infoPtr));
2596 infoPtr->uNumItem++;
2597 infoPtr->iSelected = 0;
2599 else {
2600 LPBYTE oldItems = (LPBYTE)infoPtr->items;
2602 infoPtr->uNumItem++;
2603 infoPtr->items = Alloc (TAB_ITEM_SIZE(infoPtr) * infoPtr->uNumItem);
2605 /* pre insert copy */
2606 if (iItem > 0) {
2607 memcpy (infoPtr->items, oldItems,
2608 iItem * TAB_ITEM_SIZE(infoPtr));
2611 /* post insert copy */
2612 if (iItem < infoPtr->uNumItem - 1) {
2613 memcpy (TAB_GetItem(infoPtr, iItem + 1),
2614 oldItems + iItem * TAB_ITEM_SIZE(infoPtr),
2615 (infoPtr->uNumItem - iItem - 1) * TAB_ITEM_SIZE(infoPtr));
2619 if (iItem <= infoPtr->iSelected)
2620 infoPtr->iSelected++;
2622 Free (oldItems);
2625 item = TAB_GetItem(infoPtr, iItem);
2627 item->pszText = NULL;
2629 if (pti->mask & TCIF_TEXT)
2631 if (bUnicode)
2632 Str_SetPtrW (&item->pszText, pti->pszText);
2633 else
2634 Str_SetPtrAtoW (&item->pszText, (LPSTR)pti->pszText);
2637 if (pti->mask & TCIF_IMAGE)
2638 item->iImage = pti->iImage;
2639 else
2640 item->iImage = -1;
2642 if (pti->mask & TCIF_PARAM)
2643 memcpy(item->extra, &pti->lParam, infoPtr->cbInfo);
2644 else
2645 memset(item->extra, 0, infoPtr->cbInfo);
2647 TAB_SetItemBounds(infoPtr);
2648 if (infoPtr->uNumItem > 1)
2649 TAB_InvalidateTabArea(infoPtr);
2650 else
2651 InvalidateRect(infoPtr->hwnd, NULL, TRUE);
2653 TRACE("[%p]: added item %d %s\n",
2654 infoPtr->hwnd, iItem, debugstr_w(item->pszText));
2656 /* If we haven't set the current focus yet, set it now. */
2657 if (infoPtr->uFocus == -1)
2658 TAB_SetCurFocus(infoPtr, iItem);
2660 return iItem;
2663 static LRESULT
2664 TAB_SetItemSize (TAB_INFO *infoPtr, LPARAM lParam)
2666 LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
2667 LONG lResult = 0;
2668 BOOL bNeedPaint = FALSE;
2670 lResult = MAKELONG(infoPtr->tabWidth, infoPtr->tabHeight);
2672 /* UNDOCUMENTED: If requested Width or Height is 0 this means that program wants to use auto size. */
2673 if (lStyle & TCS_FIXEDWIDTH && (infoPtr->tabWidth != (INT)LOWORD(lParam)))
2675 infoPtr->tabWidth = (INT)LOWORD(lParam);
2676 bNeedPaint = TRUE;
2679 if (infoPtr->tabHeight != (INT)HIWORD(lParam))
2681 if ((infoPtr->fHeightSet = ((INT)HIWORD(lParam) != 0)))
2682 infoPtr->tabHeight = (INT)HIWORD(lParam);
2684 bNeedPaint = TRUE;
2686 TRACE("was h=%d,w=%d, now h=%d,w=%d\n",
2687 HIWORD(lResult), LOWORD(lResult),
2688 infoPtr->tabHeight, infoPtr->tabWidth);
2690 if (bNeedPaint)
2692 TAB_SetItemBounds(infoPtr);
2693 RedrawWindow(infoPtr->hwnd, NULL, NULL, RDW_ERASE | RDW_INVALIDATE | RDW_UPDATENOW);
2696 return lResult;
2699 static inline LRESULT TAB_SetMinTabWidth (TAB_INFO *infoPtr, INT cx)
2701 INT oldcx = 0;
2703 TRACE("(%p,%d)\n", infoPtr, cx);
2705 if (infoPtr->tabMinWidth < 0)
2706 oldcx = DEFAULT_MIN_TAB_WIDTH;
2707 else
2708 oldcx = infoPtr->tabMinWidth;
2709 infoPtr->tabMinWidth = cx;
2710 TAB_SetItemBounds(infoPtr);
2711 return oldcx;
2714 static inline LRESULT
2715 TAB_HighlightItem (TAB_INFO *infoPtr, INT iItem, BOOL fHighlight)
2717 LPDWORD lpState;
2718 DWORD oldState;
2719 RECT r;
2721 TRACE("(%p,%d,%s)\n", infoPtr, iItem, fHighlight ? "true" : "false");
2723 if (!infoPtr || iItem < 0 || iItem >= infoPtr->uNumItem)
2724 return FALSE;
2726 lpState = &TAB_GetItem(infoPtr, iItem)->dwState;
2727 oldState = *lpState;
2729 if (fHighlight)
2730 *lpState |= TCIS_HIGHLIGHTED;
2731 else
2732 *lpState &= ~TCIS_HIGHLIGHTED;
2734 if ((oldState != *lpState) && TAB_InternalGetItemRect (infoPtr, iItem, &r, NULL))
2735 InvalidateRect (infoPtr->hwnd, &r, TRUE);
2737 return TRUE;
2740 static LRESULT
2741 TAB_SetItemT (TAB_INFO *infoPtr, INT iItem, LPTCITEMW tabItem, BOOL bUnicode)
2743 TAB_ITEM *wineItem;
2745 TRACE("(%p,%d,%p,%s)\n", infoPtr, iItem, tabItem, bUnicode ? "true" : "false");
2747 if (iItem < 0 || iItem >= infoPtr->uNumItem)
2748 return FALSE;
2750 TAB_DumpItemExternalT(tabItem, iItem, bUnicode);
2752 wineItem = TAB_GetItem(infoPtr, iItem);
2754 if (tabItem->mask & TCIF_IMAGE)
2755 wineItem->iImage = tabItem->iImage;
2757 if (tabItem->mask & TCIF_PARAM)
2758 memcpy(wineItem->extra, &tabItem->lParam, infoPtr->cbInfo);
2760 if (tabItem->mask & TCIF_RTLREADING)
2761 FIXME("TCIF_RTLREADING\n");
2763 if (tabItem->mask & TCIF_STATE)
2764 wineItem->dwState = (wineItem->dwState & ~tabItem->dwStateMask) |
2765 ( tabItem->dwState & tabItem->dwStateMask);
2767 if (tabItem->mask & TCIF_TEXT)
2769 Free(wineItem->pszText);
2770 wineItem->pszText = NULL;
2771 if (bUnicode)
2772 Str_SetPtrW(&wineItem->pszText, tabItem->pszText);
2773 else
2774 Str_SetPtrAtoW(&wineItem->pszText, (LPSTR)tabItem->pszText);
2777 /* Update and repaint tabs */
2778 TAB_SetItemBounds(infoPtr);
2779 TAB_InvalidateTabArea(infoPtr);
2781 return TRUE;
2784 static inline LRESULT TAB_GetItemCount (const TAB_INFO *infoPtr)
2786 return infoPtr->uNumItem;
2790 static LRESULT
2791 TAB_GetItemT (TAB_INFO *infoPtr, INT iItem, LPTCITEMW tabItem, BOOL bUnicode)
2793 TAB_ITEM *wineItem;
2795 TRACE("(%p,%d,%p,%s)\n", infoPtr, iItem, tabItem, bUnicode ? "true" : "false");
2797 if (iItem < 0 || iItem >= infoPtr->uNumItem)
2798 return FALSE;
2800 wineItem = TAB_GetItem(infoPtr, iItem);
2802 if (tabItem->mask & TCIF_IMAGE)
2803 tabItem->iImage = wineItem->iImage;
2805 if (tabItem->mask & TCIF_PARAM)
2806 memcpy(&tabItem->lParam, wineItem->extra, infoPtr->cbInfo);
2808 if (tabItem->mask & TCIF_RTLREADING)
2809 FIXME("TCIF_RTLREADING\n");
2811 if (tabItem->mask & TCIF_STATE)
2812 tabItem->dwState = wineItem->dwState & tabItem->dwStateMask;
2814 if (tabItem->mask & TCIF_TEXT)
2816 if (bUnicode)
2817 Str_GetPtrW (wineItem->pszText, tabItem->pszText, tabItem->cchTextMax);
2818 else
2819 Str_GetPtrWtoA (wineItem->pszText, (LPSTR)tabItem->pszText, tabItem->cchTextMax);
2822 TAB_DumpItemExternalT(tabItem, iItem, bUnicode);
2824 return TRUE;
2828 static LRESULT TAB_DeleteItem (TAB_INFO *infoPtr, INT iItem)
2830 BOOL bResult = FALSE;
2832 TRACE("(%p, %d)\n", infoPtr, iItem);
2834 if ((iItem >= 0) && (iItem < infoPtr->uNumItem))
2836 TAB_ITEM *item = TAB_GetItem(infoPtr, iItem);
2837 LPBYTE oldItems = (LPBYTE)infoPtr->items;
2839 TAB_InvalidateTabArea(infoPtr);
2840 Free(item->pszText);
2841 infoPtr->uNumItem--;
2843 if (!infoPtr->uNumItem)
2845 infoPtr->items = NULL;
2846 if (infoPtr->iHotTracked >= 0)
2848 KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
2849 infoPtr->iHotTracked = -1;
2852 else
2854 infoPtr->items = Alloc(TAB_ITEM_SIZE(infoPtr) * infoPtr->uNumItem);
2856 if (iItem > 0)
2857 memcpy(infoPtr->items, oldItems, iItem * TAB_ITEM_SIZE(infoPtr));
2859 if (iItem < infoPtr->uNumItem)
2860 memcpy(TAB_GetItem(infoPtr, iItem),
2861 oldItems + (iItem + 1) * TAB_ITEM_SIZE(infoPtr),
2862 (infoPtr->uNumItem - iItem) * TAB_ITEM_SIZE(infoPtr));
2864 if (iItem <= infoPtr->iHotTracked)
2866 /* When tabs move left/up, the hot track item may change */
2867 FIXME("Recalc hot track\n");
2870 Free(oldItems);
2872 /* Readjust the selected index */
2873 if ((iItem == infoPtr->iSelected) && (iItem > 0))
2874 infoPtr->iSelected--;
2876 if (iItem < infoPtr->iSelected)
2877 infoPtr->iSelected--;
2879 if (infoPtr->uNumItem == 0)
2880 infoPtr->iSelected = -1;
2882 /* Reposition and repaint tabs */
2883 TAB_SetItemBounds(infoPtr);
2885 bResult = TRUE;
2888 return bResult;
2891 static inline LRESULT TAB_DeleteAllItems (TAB_INFO *infoPtr)
2893 TRACE("(%p)\n", infoPtr);
2894 while (infoPtr->uNumItem)
2895 TAB_DeleteItem (infoPtr, 0);
2896 return TRUE;
2900 static inline LRESULT TAB_GetFont (const TAB_INFO *infoPtr)
2902 TRACE("(%p) returning %p\n", infoPtr, infoPtr->hFont);
2903 return (LRESULT)infoPtr->hFont;
2906 static inline LRESULT TAB_SetFont (TAB_INFO *infoPtr, HFONT hNewFont)
2908 TRACE("(%p,%p)\n", infoPtr, hNewFont);
2910 infoPtr->hFont = hNewFont;
2912 TAB_SetItemBounds(infoPtr);
2914 TAB_InvalidateTabArea(infoPtr);
2916 return 0;
2920 static inline LRESULT TAB_GetImageList (const TAB_INFO *infoPtr)
2922 TRACE("\n");
2923 return (LRESULT)infoPtr->himl;
2926 static inline LRESULT TAB_SetImageList (TAB_INFO *infoPtr, HIMAGELIST himlNew)
2928 HIMAGELIST himlPrev = infoPtr->himl;
2929 TRACE("\n");
2930 infoPtr->himl = himlNew;
2931 TAB_SetItemBounds(infoPtr);
2932 InvalidateRect(infoPtr->hwnd, NULL, TRUE);
2933 return (LRESULT)himlPrev;
2936 static inline LRESULT TAB_GetUnicodeFormat (const TAB_INFO *infoPtr)
2938 return infoPtr->bUnicode;
2941 static inline LRESULT TAB_SetUnicodeFormat (TAB_INFO *infoPtr, BOOL bUnicode)
2943 BOOL bTemp = infoPtr->bUnicode;
2945 infoPtr->bUnicode = bUnicode;
2947 return bTemp;
2950 static inline LRESULT TAB_Size (TAB_INFO *infoPtr)
2952 /* I'm not really sure what the following code was meant to do.
2953 This is what it is doing:
2954 When WM_SIZE is sent with SIZE_RESTORED, the control
2955 gets positioned in the top left corner.
2957 RECT parent_rect;
2958 HWND parent;
2959 UINT uPosFlags,cx,cy;
2961 uPosFlags=0;
2962 if (!wParam) {
2963 parent = GetParent (hwnd);
2964 GetClientRect(parent, &parent_rect);
2965 cx=LOWORD (lParam);
2966 cy=HIWORD (lParam);
2967 if (GetWindowLongW(hwnd, GWL_STYLE) & CCS_NORESIZE)
2968 uPosFlags |= (SWP_NOSIZE | SWP_NOMOVE);
2970 SetWindowPos (hwnd, 0, parent_rect.left, parent_rect.top,
2971 cx, cy, uPosFlags | SWP_NOZORDER);
2972 } else {
2973 FIXME("WM_SIZE flag %x %lx not handled\n", wParam, lParam);
2974 } */
2976 /* Recompute the size/position of the tabs. */
2977 TAB_SetItemBounds (infoPtr);
2979 /* Force a repaint of the control. */
2980 InvalidateRect(infoPtr->hwnd, NULL, TRUE);
2982 return 0;
2986 static LRESULT TAB_Create (HWND hwnd, LPARAM lParam)
2988 TAB_INFO *infoPtr;
2989 TEXTMETRICW fontMetrics;
2990 HDC hdc;
2991 HFONT hOldFont;
2992 DWORD dwStyle;
2994 infoPtr = Alloc (sizeof(TAB_INFO));
2996 SetWindowLongPtrW(hwnd, 0, (DWORD_PTR)infoPtr);
2998 infoPtr->hwnd = hwnd;
2999 infoPtr->hwndNotify = ((LPCREATESTRUCTW)lParam)->hwndParent;
3000 infoPtr->uNumItem = 0;
3001 infoPtr->uNumRows = 0;
3002 infoPtr->uHItemPadding = 6;
3003 infoPtr->uVItemPadding = 3;
3004 infoPtr->uHItemPadding_s = 6;
3005 infoPtr->uVItemPadding_s = 3;
3006 infoPtr->hFont = 0;
3007 infoPtr->items = 0;
3008 infoPtr->hcurArrow = LoadCursorW (0, (LPWSTR)IDC_ARROW);
3009 infoPtr->iSelected = -1;
3010 infoPtr->iHotTracked = -1;
3011 infoPtr->uFocus = -1;
3012 infoPtr->hwndToolTip = 0;
3013 infoPtr->DoRedraw = TRUE;
3014 infoPtr->needsScrolling = FALSE;
3015 infoPtr->hwndUpDown = 0;
3016 infoPtr->leftmostVisible = 0;
3017 infoPtr->fHeightSet = FALSE;
3018 infoPtr->bUnicode = IsWindowUnicode (hwnd);
3019 infoPtr->cbInfo = sizeof(LPARAM);
3021 TRACE("Created tab control, hwnd [%p]\n", hwnd);
3023 /* The tab control always has the WS_CLIPSIBLINGS style. Even
3024 if you don't specify it in CreateWindow. This is necessary in
3025 order for paint to work correctly. This follows windows behaviour. */
3026 dwStyle = GetWindowLongW(hwnd, GWL_STYLE);
3027 SetWindowLongW(hwnd, GWL_STYLE, dwStyle|WS_CLIPSIBLINGS);
3029 infoPtr->exStyle = (dwStyle & TCS_FLATBUTTONS) ? TCS_EX_FLATSEPARATORS : 0;
3031 if (dwStyle & TCS_TOOLTIPS) {
3032 /* Create tooltip control */
3033 infoPtr->hwndToolTip =
3034 CreateWindowExW (0, TOOLTIPS_CLASSW, NULL, WS_POPUP,
3035 CW_USEDEFAULT, CW_USEDEFAULT,
3036 CW_USEDEFAULT, CW_USEDEFAULT,
3037 hwnd, 0, 0, 0);
3039 /* Send NM_TOOLTIPSCREATED notification */
3040 if (infoPtr->hwndToolTip) {
3041 NMTOOLTIPSCREATED nmttc;
3043 nmttc.hdr.hwndFrom = hwnd;
3044 nmttc.hdr.idFrom = GetWindowLongPtrW(hwnd, GWLP_ID);
3045 nmttc.hdr.code = NM_TOOLTIPSCREATED;
3046 nmttc.hwndToolTips = infoPtr->hwndToolTip;
3048 SendMessageW (infoPtr->hwndNotify, WM_NOTIFY,
3049 (WPARAM)GetWindowLongPtrW(hwnd, GWLP_ID), (LPARAM)&nmttc);
3053 OpenThemeData (infoPtr->hwnd, themeClass);
3056 * We need to get text information so we need a DC and we need to select
3057 * a font.
3059 hdc = GetDC(hwnd);
3060 hOldFont = SelectObject (hdc, GetStockObject (SYSTEM_FONT));
3062 /* Use the system font to determine the initial height of a tab. */
3063 GetTextMetricsW(hdc, &fontMetrics);
3066 * Make sure there is enough space for the letters + growing the
3067 * selected item + extra space for the selected item.
3069 infoPtr->tabHeight = fontMetrics.tmHeight + SELECTED_TAB_OFFSET +
3070 ((dwStyle & TCS_BUTTONS) ? 2 : 1) *
3071 infoPtr->uVItemPadding;
3073 /* Initialize the width of a tab. */
3074 if (dwStyle & TCS_FIXEDWIDTH)
3075 infoPtr->tabWidth = GetDeviceCaps(hdc, LOGPIXELSX);
3077 infoPtr->tabMinWidth = -1;
3079 TRACE("tabH=%d, tabW=%d\n", infoPtr->tabHeight, infoPtr->tabWidth);
3081 SelectObject (hdc, hOldFont);
3082 ReleaseDC(hwnd, hdc);
3084 return 0;
3087 static LRESULT
3088 TAB_Destroy (TAB_INFO *infoPtr)
3090 UINT iItem;
3092 if (!infoPtr)
3093 return 0;
3095 SetWindowLongPtrW(infoPtr->hwnd, 0, 0);
3097 if (infoPtr->items) {
3098 for (iItem = 0; iItem < infoPtr->uNumItem; iItem++) {
3099 Free (TAB_GetItem(infoPtr, iItem)->pszText);
3101 Free (infoPtr->items);
3104 if (infoPtr->hwndToolTip)
3105 DestroyWindow (infoPtr->hwndToolTip);
3107 if (infoPtr->hwndUpDown)
3108 DestroyWindow(infoPtr->hwndUpDown);
3110 if (infoPtr->iHotTracked >= 0)
3111 KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
3113 CloseThemeData (GetWindowTheme (infoPtr->hwnd));
3115 Free (infoPtr);
3116 return 0;
3119 /* update theme after a WM_THEMECHANGED message */
3120 static LRESULT theme_changed(const TAB_INFO *infoPtr)
3122 HTHEME theme = GetWindowTheme (infoPtr->hwnd);
3123 CloseThemeData (theme);
3124 OpenThemeData (infoPtr->hwnd, themeClass);
3125 return 0;
3128 static LRESULT TAB_NCCalcSize(WPARAM wParam)
3130 if (!wParam)
3131 return 0;
3132 return WVR_ALIGNTOP;
3135 static inline LRESULT
3136 TAB_SetItemExtra (TAB_INFO *infoPtr, INT cbInfo)
3138 if (!infoPtr || cbInfo <= 0)
3139 return FALSE;
3141 if (infoPtr->uNumItem)
3143 /* FIXME: MSDN says this is not allowed, but this hasn't been verified */
3144 return FALSE;
3147 infoPtr->cbInfo = cbInfo;
3148 return TRUE;
3151 static LRESULT TAB_RemoveImage (TAB_INFO *infoPtr, INT image)
3153 if (!infoPtr)
3154 return 0;
3156 if (ImageList_Remove (infoPtr->himl, image))
3158 INT i, *idx;
3159 RECT r;
3161 /* shift indices, repaint items if needed */
3162 for (i = 0; i < infoPtr->uNumItem; i++)
3164 idx = &TAB_GetItem(infoPtr, i)->iImage;
3165 if (*idx >= image)
3167 if (*idx == image)
3168 *idx = -1;
3169 else
3170 (*idx)--;
3172 /* repaint item */
3173 if (TAB_InternalGetItemRect (infoPtr, i, &r, NULL))
3174 InvalidateRect (infoPtr->hwnd, &r, TRUE);
3179 return 0;
3182 static LRESULT
3183 TAB_SetExtendedStyle (TAB_INFO *infoPtr, DWORD exMask, DWORD exStyle)
3185 DWORD prevstyle = infoPtr->exStyle;
3187 /* zero mask means all styles */
3188 if (exMask == 0) exMask = ~0;
3190 if (exMask & TCS_EX_REGISTERDROP)
3192 FIXME("TCS_EX_REGISTERDROP style unimplemented\n");
3193 exMask &= ~TCS_EX_REGISTERDROP;
3194 exStyle &= ~TCS_EX_REGISTERDROP;
3197 if (exMask & TCS_EX_FLATSEPARATORS)
3199 if ((prevstyle ^ exStyle) & TCS_EX_FLATSEPARATORS)
3201 infoPtr->exStyle ^= TCS_EX_FLATSEPARATORS;
3202 TAB_InvalidateTabArea(infoPtr);
3206 return prevstyle;
3209 static inline LRESULT
3210 TAB_GetExtendedStyle (TAB_INFO *infoPtr)
3212 return infoPtr->exStyle;
3215 static LRESULT
3216 TAB_DeselectAll (TAB_INFO *infoPtr, BOOL excludesel)
3218 LONG style = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
3219 BOOL paint = FALSE;
3220 INT i, selected = infoPtr->iSelected;
3222 if (!(style & 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 static LRESULT WINAPI
3249 TAB_WindowProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
3251 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
3253 TRACE("hwnd=%p msg=%x wParam=%lx lParam=%lx\n", hwnd, uMsg, wParam, lParam);
3254 if (!infoPtr && (uMsg != WM_CREATE))
3255 return DefWindowProcW (hwnd, uMsg, wParam, lParam);
3257 switch (uMsg)
3259 case TCM_GETIMAGELIST:
3260 return TAB_GetImageList (infoPtr);
3262 case TCM_SETIMAGELIST:
3263 return TAB_SetImageList (infoPtr, (HIMAGELIST)lParam);
3265 case TCM_GETITEMCOUNT:
3266 return TAB_GetItemCount (infoPtr);
3268 case TCM_GETITEMA:
3269 case TCM_GETITEMW:
3270 return TAB_GetItemT (infoPtr, (INT)wParam, (LPTCITEMW)lParam, uMsg == TCM_GETITEMW);
3272 case TCM_SETITEMA:
3273 case TCM_SETITEMW:
3274 return TAB_SetItemT (infoPtr, (INT)wParam, (LPTCITEMW)lParam, uMsg == TCM_SETITEMW);
3276 case TCM_DELETEITEM:
3277 return TAB_DeleteItem (infoPtr, (INT)wParam);
3279 case TCM_DELETEALLITEMS:
3280 return TAB_DeleteAllItems (infoPtr);
3282 case TCM_GETITEMRECT:
3283 return TAB_GetItemRect (infoPtr, wParam, lParam);
3285 case TCM_GETCURSEL:
3286 return TAB_GetCurSel (infoPtr);
3288 case TCM_HITTEST:
3289 return TAB_HitTest (infoPtr, (LPTCHITTESTINFO)lParam);
3291 case TCM_SETCURSEL:
3292 return TAB_SetCurSel (infoPtr, (INT)wParam);
3294 case TCM_INSERTITEMA:
3295 case TCM_INSERTITEMW:
3296 return TAB_InsertItemT (infoPtr, wParam, lParam, uMsg == TCM_INSERTITEMW);
3298 case TCM_SETITEMEXTRA:
3299 return TAB_SetItemExtra (infoPtr, (int)wParam);
3301 case TCM_ADJUSTRECT:
3302 return TAB_AdjustRect (infoPtr, (BOOL)wParam, (LPRECT)lParam);
3304 case TCM_SETITEMSIZE:
3305 return TAB_SetItemSize (infoPtr, lParam);
3307 case TCM_REMOVEIMAGE:
3308 return TAB_RemoveImage (infoPtr, wParam);
3310 case TCM_SETPADDING:
3311 return TAB_SetPadding (infoPtr, lParam);
3313 case TCM_GETROWCOUNT:
3314 return TAB_GetRowCount(infoPtr);
3316 case TCM_GETUNICODEFORMAT:
3317 return TAB_GetUnicodeFormat (infoPtr);
3319 case TCM_SETUNICODEFORMAT:
3320 return TAB_SetUnicodeFormat (infoPtr, (BOOL)wParam);
3322 case TCM_HIGHLIGHTITEM:
3323 return TAB_HighlightItem (infoPtr, (INT)wParam, (BOOL)LOWORD(lParam));
3325 case TCM_GETTOOLTIPS:
3326 return TAB_GetToolTips (infoPtr);
3328 case TCM_SETTOOLTIPS:
3329 return TAB_SetToolTips (infoPtr, (HWND)wParam);
3331 case TCM_GETCURFOCUS:
3332 return TAB_GetCurFocus (infoPtr);
3334 case TCM_SETCURFOCUS:
3335 return TAB_SetCurFocus (infoPtr, (INT)wParam);
3337 case TCM_SETMINTABWIDTH:
3338 return TAB_SetMinTabWidth(infoPtr, (INT)lParam);
3340 case TCM_DESELECTALL:
3341 return TAB_DeselectAll (infoPtr, (BOOL)wParam);
3343 case TCM_GETEXTENDEDSTYLE:
3344 return TAB_GetExtendedStyle (infoPtr);
3346 case TCM_SETEXTENDEDSTYLE:
3347 return TAB_SetExtendedStyle (infoPtr, wParam, lParam);
3349 case WM_GETFONT:
3350 return TAB_GetFont (infoPtr);
3352 case WM_SETFONT:
3353 return TAB_SetFont (infoPtr, (HFONT)wParam);
3355 case WM_CREATE:
3356 return TAB_Create (hwnd, lParam);
3358 case WM_NCDESTROY:
3359 return TAB_Destroy (infoPtr);
3361 case WM_GETDLGCODE:
3362 return DLGC_WANTARROWS | DLGC_WANTCHARS;
3364 case WM_LBUTTONDOWN:
3365 return TAB_LButtonDown (infoPtr, wParam, lParam);
3367 case WM_LBUTTONUP:
3368 return TAB_LButtonUp (infoPtr);
3370 case WM_NOTIFY:
3371 return SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, wParam, lParam);
3373 case WM_RBUTTONDOWN:
3374 return TAB_RButtonDown (infoPtr);
3376 case WM_MOUSEMOVE:
3377 return TAB_MouseMove (infoPtr, wParam, lParam);
3379 case WM_PRINTCLIENT:
3380 case WM_PAINT:
3381 return TAB_Paint (infoPtr, (HDC)wParam);
3383 case WM_SIZE:
3384 return TAB_Size (infoPtr);
3386 case WM_SETREDRAW:
3387 return TAB_SetRedraw (infoPtr, (BOOL)wParam);
3389 case WM_HSCROLL:
3390 return TAB_OnHScroll(infoPtr, (int)LOWORD(wParam), (int)HIWORD(wParam));
3392 case WM_STYLECHANGED:
3393 TAB_SetItemBounds (infoPtr);
3394 InvalidateRect(hwnd, NULL, TRUE);
3395 return 0;
3397 case WM_SYSCOLORCHANGE:
3398 COMCTL32_RefreshSysColors();
3399 return 0;
3401 case WM_THEMECHANGED:
3402 return theme_changed (infoPtr);
3404 case WM_KILLFOCUS:
3405 case WM_SETFOCUS:
3406 TAB_FocusChanging(infoPtr);
3407 break; /* Don't disturb normal focus behavior */
3409 case WM_KEYUP:
3410 return TAB_KeyUp(infoPtr, wParam);
3411 case WM_NCHITTEST:
3412 return TAB_NCHitTest(infoPtr, lParam);
3414 case WM_NCCALCSIZE:
3415 return TAB_NCCalcSize(wParam);
3417 default:
3418 if (uMsg >= WM_USER && uMsg < WM_APP && !COMCTL32_IsReflectedMessage(uMsg))
3419 WARN("unknown msg %04x wp=%08lx lp=%08lx\n",
3420 uMsg, wParam, lParam);
3421 break;
3423 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
3427 void
3428 TAB_Register (void)
3430 WNDCLASSW wndClass;
3432 ZeroMemory (&wndClass, sizeof(WNDCLASSW));
3433 wndClass.style = CS_GLOBALCLASS | CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
3434 wndClass.lpfnWndProc = TAB_WindowProc;
3435 wndClass.cbClsExtra = 0;
3436 wndClass.cbWndExtra = sizeof(TAB_INFO *);
3437 wndClass.hCursor = LoadCursorW (0, (LPWSTR)IDC_ARROW);
3438 wndClass.hbrBackground = (HBRUSH)(COLOR_BTNFACE+1);
3439 wndClass.lpszClassName = WC_TABCONTROLW;
3441 RegisterClassW (&wndClass);
3445 void
3446 TAB_Unregister (void)
3448 UnregisterClassW (WC_TABCONTROLW, NULL);