comctl32: TCM_SETCURSEL also updates the focus item.
[wine/wine64.git] / dlls / comctl32 / tab.c
blobd3cf0fd0c262415b34eb94d5bf1814e2da164b48
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
36 * TCS_RIGHT
37 * TCS_RIGHTJUSTIFY
38 * TCS_SCROLLOPPOSITE
39 * TCS_SINGLELINE
40 * TCIF_RTLREADING
42 * Extended Styles:
43 * TCS_EX_FLATSEPARATORS
44 * TCS_EX_REGISTERDROP
46 * States:
47 * TCIS_BUTTONPRESSED
49 * Notifications:
50 * NM_RELEASEDCAPTURE
51 * TCN_FOCUSCHANGE
52 * TCN_GETOBJECT
53 * TCN_KEYDOWN
55 * Messages:
56 * TCM_REMOVEIMAGE
57 * TCM_DESELECTALL
58 * TCM_GETEXTENDEDSTYLE
59 * TCM_SETEXTENDEDSTYLE
61 * Macros:
62 * TabCtrl_AdjustRect
66 #include <stdarg.h>
67 #include <string.h>
69 #include "windef.h"
70 #include "winbase.h"
71 #include "wingdi.h"
72 #include "winuser.h"
73 #include "winnls.h"
74 #include "commctrl.h"
75 #include "comctl32.h"
76 #include "uxtheme.h"
77 #include "tmschema.h"
78 #include "wine/debug.h"
79 #include <math.h>
81 WINE_DEFAULT_DEBUG_CHANNEL(tab);
83 typedef struct
85 DWORD dwState;
86 LPWSTR pszText;
87 INT iImage;
88 RECT rect; /* bounding rectangle of the item relative to the
89 * leftmost item (the leftmost item, 0, would have a
90 * "left" member of 0 in this rectangle)
92 * additionally the top member holds the row number
93 * and bottom is unused and should be 0 */
94 BYTE extra[1]; /* Space for caller supplied info, variable size */
95 } TAB_ITEM;
97 /* The size of a tab item depends on how much extra data is requested */
98 #define TAB_ITEM_SIZE(infoPtr) (FIELD_OFFSET(TAB_ITEM, extra[(infoPtr)->cbInfo]))
100 typedef struct
102 HWND hwnd; /* Tab control window */
103 HWND hwndNotify; /* notification window (parent) */
104 UINT uNumItem; /* number of tab items */
105 UINT uNumRows; /* number of tab rows */
106 INT tabHeight; /* height of the tab row */
107 INT tabWidth; /* width of tabs */
108 INT tabMinWidth; /* minimum width of items */
109 USHORT uHItemPadding; /* amount of horizontal padding, in pixels */
110 USHORT uVItemPadding; /* amount of vertical padding, in pixels */
111 USHORT uHItemPadding_s; /* Set amount of horizontal padding, in pixels */
112 USHORT uVItemPadding_s; /* Set amount of vertical padding, in pixels */
113 HFONT hFont; /* handle to the current font */
114 HCURSOR hcurArrow; /* handle to the current cursor */
115 HIMAGELIST himl; /* handle to an image list (may be 0) */
116 HWND hwndToolTip; /* handle to tab's tooltip */
117 INT leftmostVisible; /* Used for scrolling, this member contains
118 * the index of the first visible item */
119 INT iSelected; /* the currently selected item */
120 INT iHotTracked; /* the highlighted item under the mouse */
121 INT uFocus; /* item which has the focus */
122 TAB_ITEM* items; /* pointer to an array of TAB_ITEM's */
123 BOOL DoRedraw; /* flag for redrawing when tab contents is changed*/
124 BOOL needsScrolling; /* TRUE if the size of the tabs is greater than
125 * the size of the control */
126 BOOL fHeightSet; /* was the height of the tabs explicitly set? */
127 BOOL bUnicode; /* Unicode control? */
128 HWND hwndUpDown; /* Updown control used for scrolling */
129 INT cbInfo; /* Number of bytes of caller supplied info per tab */
130 } TAB_INFO;
132 /******************************************************************************
133 * Positioning constants
135 #define SELECTED_TAB_OFFSET 2
136 #define ROUND_CORNER_SIZE 2
137 #define DISPLAY_AREA_PADDINGX 2
138 #define DISPLAY_AREA_PADDINGY 2
139 #define CONTROL_BORDER_SIZEX 2
140 #define CONTROL_BORDER_SIZEY 2
141 #define BUTTON_SPACINGX 3
142 #define BUTTON_SPACINGY 3
143 #define FLAT_BTN_SPACINGX 8
144 #define DEFAULT_MIN_TAB_WIDTH 54
145 #define DEFAULT_TAB_WIDTH_FIXED 96
146 #define DEFAULT_PADDING_X 6
147 #define EXTRA_ICON_PADDING 3
149 #define TAB_GetInfoPtr(hwnd) ((TAB_INFO *)GetWindowLongPtrW(hwnd,0))
150 /* Since items are variable sized, cannot directly access them */
151 #define TAB_GetItem(info,i) \
152 ((TAB_ITEM*)((LPBYTE)info->items + (i) * TAB_ITEM_SIZE(info)))
154 #define GET_DEFAULT_MIN_TAB_WIDTH(infoPtr) (DEFAULT_MIN_TAB_WIDTH - (DEFAULT_PADDING_X - (infoPtr)->uHItemPadding) * 2)
156 /******************************************************************************
157 * Hot-tracking timer constants
159 #define TAB_HOTTRACK_TIMER 1
160 #define TAB_HOTTRACK_TIMER_INTERVAL 100 /* milliseconds */
162 static const WCHAR themeClass[] = { 'T','a','b',0 };
164 /******************************************************************************
165 * Prototypes
167 static void TAB_InvalidateTabArea(const TAB_INFO *);
168 static void TAB_EnsureSelectionVisible(TAB_INFO *);
169 static void TAB_DrawItemInterior(const TAB_INFO *, HDC, INT, RECT*);
171 static BOOL
172 TAB_SendSimpleNotify (const TAB_INFO *infoPtr, UINT code)
174 NMHDR nmhdr;
176 nmhdr.hwndFrom = infoPtr->hwnd;
177 nmhdr.idFrom = GetWindowLongPtrW(infoPtr->hwnd, GWLP_ID);
178 nmhdr.code = code;
180 return (BOOL) SendMessageW (infoPtr->hwndNotify, WM_NOTIFY,
181 (WPARAM) nmhdr.idFrom, (LPARAM) &nmhdr);
184 static void
185 TAB_RelayEvent (HWND hwndTip, HWND hwndMsg, UINT uMsg,
186 WPARAM wParam, LPARAM lParam)
188 MSG msg;
190 msg.hwnd = hwndMsg;
191 msg.message = uMsg;
192 msg.wParam = wParam;
193 msg.lParam = lParam;
194 msg.time = GetMessageTime ();
195 msg.pt.x = (short)LOWORD(GetMessagePos ());
196 msg.pt.y = (short)HIWORD(GetMessagePos ());
198 SendMessageW (hwndTip, TTM_RELAYEVENT, 0, (LPARAM)&msg);
201 static void
202 TAB_DumpItemExternalT(const TCITEMW *pti, UINT iItem, BOOL isW)
204 if (TRACE_ON(tab)) {
205 TRACE("external tab %d, mask=0x%08x, dwState=0x%08x, dwStateMask=0x%08x, cchTextMax=0x%08x\n",
206 iItem, pti->mask, pti->dwState, pti->dwStateMask, pti->cchTextMax);
207 TRACE("external tab %d, iImage=%d, lParam=0x%08lx, pszTextW=%s\n",
208 iItem, pti->iImage, pti->lParam, isW ? debugstr_w(pti->pszText) : debugstr_a((LPSTR)pti->pszText));
212 static void
213 TAB_DumpItemInternal(const TAB_INFO *infoPtr, UINT iItem)
215 if (TRACE_ON(tab)) {
216 TAB_ITEM *ti;
218 ti = TAB_GetItem(infoPtr, iItem);
219 TRACE("tab %d, dwState=0x%08x, pszText=%s, iImage=%d\n",
220 iItem, ti->dwState, debugstr_w(ti->pszText), ti->iImage);
221 TRACE("tab %d, rect.left=%d, rect.top(row)=%d\n",
222 iItem, ti->rect.left, ti->rect.top);
226 /* RETURNS
227 * the index of the selected tab, or -1 if no tab is selected. */
228 static inline LRESULT TAB_GetCurSel (const TAB_INFO *infoPtr)
230 return infoPtr->iSelected;
233 /* RETURNS
234 * the index of the tab item that has the focus. */
235 static inline LRESULT
236 TAB_GetCurFocus (const TAB_INFO *infoPtr)
238 return infoPtr->uFocus;
241 static inline LRESULT TAB_GetToolTips (const TAB_INFO *infoPtr)
243 if (infoPtr == NULL) return 0;
244 return (LRESULT)infoPtr->hwndToolTip;
247 static inline LRESULT TAB_SetCurSel (TAB_INFO *infoPtr, INT iItem)
249 INT prevItem = infoPtr->iSelected;
251 if (iItem < 0)
252 infoPtr->iSelected=-1;
253 else if (iItem >= infoPtr->uNumItem)
254 return -1;
255 else {
256 if (infoPtr->iSelected != iItem) {
257 infoPtr->iSelected=iItem;
258 infoPtr->uFocus=iItem;
259 TAB_EnsureSelectionVisible(infoPtr);
260 TAB_InvalidateTabArea(infoPtr);
263 return prevItem;
266 static LRESULT TAB_SetCurFocus (TAB_INFO *infoPtr, INT iItem)
268 if (iItem < 0)
269 infoPtr->uFocus = -1;
270 else if (iItem < infoPtr->uNumItem) {
271 if (GetWindowLongW(infoPtr->hwnd, GWL_STYLE) & TCS_BUTTONS) {
272 FIXME("Should set input focus\n");
273 } else {
274 int oldFocus = infoPtr->uFocus;
275 if (infoPtr->iSelected != iItem || oldFocus == -1 ) {
276 infoPtr->uFocus = iItem;
277 if (oldFocus != -1) {
278 if (!TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGING)) {
279 infoPtr->iSelected = iItem;
280 TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGE);
282 else
283 infoPtr->iSelected = iItem;
284 TAB_EnsureSelectionVisible(infoPtr);
285 TAB_InvalidateTabArea(infoPtr);
290 return 0;
293 static inline LRESULT
294 TAB_SetToolTips (TAB_INFO *infoPtr, HWND hwndToolTip)
296 if (infoPtr)
297 infoPtr->hwndToolTip = hwndToolTip;
298 return 0;
301 static inline LRESULT
302 TAB_SetPadding (TAB_INFO *infoPtr, LPARAM lParam)
304 if (infoPtr)
306 infoPtr->uHItemPadding_s=LOWORD(lParam);
307 infoPtr->uVItemPadding_s=HIWORD(lParam);
309 return 0;
312 /******************************************************************************
313 * TAB_InternalGetItemRect
315 * This method will calculate the rectangle representing a given tab item in
316 * client coordinates. This method takes scrolling into account.
318 * This method returns TRUE if the item is visible in the window and FALSE
319 * if it is completely outside the client area.
321 static BOOL TAB_InternalGetItemRect(
322 const TAB_INFO* infoPtr,
323 INT itemIndex,
324 RECT* itemRect,
325 RECT* selectedRect)
327 RECT tmpItemRect,clientRect;
328 LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
330 /* Perform a sanity check and a trivial visibility check. */
331 if ( (infoPtr->uNumItem <= 0) ||
332 (itemIndex >= infoPtr->uNumItem) ||
333 (!((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)) && (itemIndex < infoPtr->leftmostVisible)) )
335 TRACE("Not Visible\n");
336 /* need to initialize these to empty rects */
337 if (itemRect)
339 memset(itemRect,0,sizeof(RECT));
340 itemRect->bottom = infoPtr->tabHeight;
342 if (selectedRect)
343 memset(selectedRect,0,sizeof(RECT));
344 return FALSE;
348 * Avoid special cases in this procedure by assigning the "out"
349 * parameters if the caller didn't supply them
351 if (itemRect == NULL)
352 itemRect = &tmpItemRect;
354 /* Retrieve the unmodified item rect. */
355 *itemRect = TAB_GetItem(infoPtr,itemIndex)->rect;
357 /* calculate the times bottom and top based on the row */
358 GetClientRect(infoPtr->hwnd, &clientRect);
360 if ((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
362 itemRect->right = clientRect.right - SELECTED_TAB_OFFSET - itemRect->left * infoPtr->tabHeight -
363 ((lStyle & TCS_BUTTONS) ? itemRect->left * BUTTON_SPACINGX : 0);
364 itemRect->left = itemRect->right - infoPtr->tabHeight;
366 else if (lStyle & TCS_VERTICAL)
368 itemRect->left = clientRect.left + SELECTED_TAB_OFFSET + itemRect->left * infoPtr->tabHeight +
369 ((lStyle & TCS_BUTTONS) ? itemRect->left * BUTTON_SPACINGX : 0);
370 itemRect->right = itemRect->left + infoPtr->tabHeight;
372 else if (lStyle & TCS_BOTTOM)
374 itemRect->bottom = clientRect.bottom - itemRect->top * infoPtr->tabHeight -
375 ((lStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : SELECTED_TAB_OFFSET);
376 itemRect->top = itemRect->bottom - infoPtr->tabHeight;
378 else /* not TCS_BOTTOM and not TCS_VERTICAL */
380 itemRect->top = clientRect.top + itemRect->top * infoPtr->tabHeight +
381 ((lStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : SELECTED_TAB_OFFSET);
382 itemRect->bottom = itemRect->top + infoPtr->tabHeight;
386 * "scroll" it to make sure the item at the very left of the
387 * tab control is the leftmost visible tab.
389 if(lStyle & TCS_VERTICAL)
391 OffsetRect(itemRect,
393 -TAB_GetItem(infoPtr, infoPtr->leftmostVisible)->rect.top);
396 * Move the rectangle so the first item is slightly offset from
397 * the bottom of the tab control.
399 OffsetRect(itemRect,
401 SELECTED_TAB_OFFSET);
403 } else
405 OffsetRect(itemRect,
406 -TAB_GetItem(infoPtr, infoPtr->leftmostVisible)->rect.left,
410 * Move the rectangle so the first item is slightly offset from
411 * the left of the tab control.
413 OffsetRect(itemRect,
414 SELECTED_TAB_OFFSET,
417 TRACE("item %d tab h=%d, rect=(%d,%d)-(%d,%d)\n",
418 itemIndex, infoPtr->tabHeight,
419 itemRect->left, itemRect->top, itemRect->right, itemRect->bottom);
421 /* Now, calculate the position of the item as if it were selected. */
422 if (selectedRect!=NULL)
424 CopyRect(selectedRect, itemRect);
426 /* The rectangle of a selected item is a bit wider. */
427 if(lStyle & TCS_VERTICAL)
428 InflateRect(selectedRect, 0, SELECTED_TAB_OFFSET);
429 else
430 InflateRect(selectedRect, SELECTED_TAB_OFFSET, 0);
432 /* If it also a bit higher. */
433 if ((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
435 selectedRect->left -= 2; /* the border is thicker on the right */
436 selectedRect->right += SELECTED_TAB_OFFSET;
438 else if (lStyle & TCS_VERTICAL)
440 selectedRect->left -= SELECTED_TAB_OFFSET;
441 selectedRect->right += 1;
443 else if (lStyle & TCS_BOTTOM)
445 selectedRect->bottom += SELECTED_TAB_OFFSET;
447 else /* not TCS_BOTTOM and not TCS_VERTICAL */
449 selectedRect->top -= SELECTED_TAB_OFFSET;
450 selectedRect->bottom -= 1;
454 /* Check for visibility */
455 if (lStyle & TCS_VERTICAL)
456 return (itemRect->top < clientRect.bottom) && (itemRect->bottom > clientRect.top);
457 else
458 return (itemRect->left < clientRect.right) && (itemRect->right > clientRect.left);
461 static inline BOOL
462 TAB_GetItemRect(const TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
464 return TAB_InternalGetItemRect(infoPtr, (INT)wParam, (LPRECT)lParam, (LPRECT)NULL);
467 /******************************************************************************
468 * TAB_KeyUp
470 * This method is called to handle keyboard input
472 static LRESULT TAB_KeyUp(TAB_INFO* infoPtr, WPARAM keyCode)
474 int newItem = -1;
476 switch (keyCode)
478 case VK_LEFT:
479 newItem = infoPtr->uFocus - 1;
480 break;
481 case VK_RIGHT:
482 newItem = infoPtr->uFocus + 1;
483 break;
487 * If we changed to a valid item, change the selection
489 if (newItem >= 0 &&
490 newItem < infoPtr->uNumItem &&
491 infoPtr->uFocus != newItem)
493 if (!TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGING))
495 infoPtr->iSelected = newItem;
496 infoPtr->uFocus = newItem;
497 TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGE);
499 TAB_EnsureSelectionVisible(infoPtr);
500 TAB_InvalidateTabArea(infoPtr);
504 return 0;
507 /******************************************************************************
508 * TAB_FocusChanging
510 * This method is called whenever the focus goes in or out of this control
511 * it is used to update the visual state of the control.
513 static void TAB_FocusChanging(const TAB_INFO *infoPtr)
515 RECT selectedRect;
516 BOOL isVisible;
519 * Get the rectangle for the item.
521 isVisible = TAB_InternalGetItemRect(infoPtr,
522 infoPtr->uFocus,
523 NULL,
524 &selectedRect);
527 * If the rectangle is not completely invisible, invalidate that
528 * portion of the window.
530 if (isVisible)
532 TRACE("invalidate (%d,%d)-(%d,%d)\n",
533 selectedRect.left,selectedRect.top,
534 selectedRect.right,selectedRect.bottom);
535 InvalidateRect(infoPtr->hwnd, &selectedRect, TRUE);
539 static INT TAB_InternalHitTest (const TAB_INFO *infoPtr, POINT pt, UINT *flags)
541 RECT rect;
542 INT iCount;
544 for (iCount = 0; iCount < infoPtr->uNumItem; iCount++)
546 TAB_InternalGetItemRect(infoPtr, iCount, &rect, NULL);
548 if (PtInRect(&rect, pt))
550 *flags = TCHT_ONITEM;
551 return iCount;
555 *flags = TCHT_NOWHERE;
556 return -1;
559 static inline LRESULT
560 TAB_HitTest (const TAB_INFO *infoPtr, LPTCHITTESTINFO lptest)
562 return TAB_InternalHitTest (infoPtr, lptest->pt, &lptest->flags);
565 /******************************************************************************
566 * TAB_NCHitTest
568 * Napster v2b5 has a tab control for its main navigation which has a client
569 * area that covers the whole area of the dialog pages.
570 * That's why it receives all msgs for that area and the underlying dialog ctrls
571 * are dead.
572 * So I decided that we should handle WM_NCHITTEST here and return
573 * HTTRANSPARENT if we don't hit the tab control buttons.
574 * FIXME: WM_NCHITTEST handling correct ? Fix it if you know that Windows
575 * doesn't do it that way. Maybe depends on tab control styles ?
577 static inline LRESULT
578 TAB_NCHitTest (const TAB_INFO *infoPtr, LPARAM lParam)
580 POINT pt;
581 UINT dummyflag;
583 pt.x = (short)LOWORD(lParam);
584 pt.y = (short)HIWORD(lParam);
585 ScreenToClient(infoPtr->hwnd, &pt);
587 if (TAB_InternalHitTest(infoPtr, pt, &dummyflag) == -1)
588 return HTTRANSPARENT;
589 else
590 return HTCLIENT;
593 static LRESULT
594 TAB_LButtonDown (TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
596 POINT pt;
597 INT newItem;
598 UINT dummy;
600 if (infoPtr->hwndToolTip)
601 TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
602 WM_LBUTTONDOWN, wParam, lParam);
604 if (GetWindowLongW(infoPtr->hwnd, GWL_STYLE) & TCS_FOCUSONBUTTONDOWN ) {
605 SetFocus (infoPtr->hwnd);
608 if (infoPtr->hwndToolTip)
609 TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
610 WM_LBUTTONDOWN, wParam, lParam);
612 pt.x = (short)LOWORD(lParam);
613 pt.y = (short)HIWORD(lParam);
615 newItem = TAB_InternalHitTest (infoPtr, pt, &dummy);
617 TRACE("On Tab, item %d\n", newItem);
619 if (newItem != -1 && infoPtr->iSelected != newItem)
621 if (!TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGING))
623 infoPtr->iSelected = newItem;
624 infoPtr->uFocus = newItem;
625 TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGE);
627 TAB_EnsureSelectionVisible(infoPtr);
629 TAB_InvalidateTabArea(infoPtr);
632 return 0;
635 static inline LRESULT
636 TAB_LButtonUp (const TAB_INFO *infoPtr)
638 TAB_SendSimpleNotify(infoPtr, NM_CLICK);
640 return 0;
643 static inline LRESULT
644 TAB_RButtonDown (const TAB_INFO *infoPtr)
646 TAB_SendSimpleNotify(infoPtr, NM_RCLICK);
647 return 0;
650 /******************************************************************************
651 * TAB_DrawLoneItemInterior
653 * This calls TAB_DrawItemInterior. However, TAB_DrawItemInterior is normally
654 * called by TAB_DrawItem which is normally called by TAB_Refresh which sets
655 * up the device context and font. This routine does the same setup but
656 * only calls TAB_DrawItemInterior for the single specified item.
658 static void
659 TAB_DrawLoneItemInterior(const TAB_INFO* infoPtr, int iItem)
661 HDC hdc = GetDC(infoPtr->hwnd);
662 RECT r, rC;
664 /* Clip UpDown control to not draw over it */
665 if (infoPtr->needsScrolling)
667 GetWindowRect(infoPtr->hwnd, &rC);
668 GetWindowRect(infoPtr->hwndUpDown, &r);
669 ExcludeClipRect(hdc, r.left - rC.left, r.top - rC.top, r.right - rC.left, r.bottom - rC.top);
671 TAB_DrawItemInterior(infoPtr, hdc, iItem, NULL);
672 ReleaseDC(infoPtr->hwnd, hdc);
675 /* update a tab after hottracking - invalidate it or just redraw the interior,
676 * based on whether theming is used or not */
677 static inline void hottrack_refresh(const TAB_INFO *infoPtr, int tabIndex)
679 if (tabIndex == -1) return;
681 if (GetWindowTheme (infoPtr->hwnd))
683 RECT rect;
684 TAB_InternalGetItemRect(infoPtr, tabIndex, &rect, NULL);
685 InvalidateRect (infoPtr->hwnd, &rect, FALSE);
687 else
688 TAB_DrawLoneItemInterior(infoPtr, tabIndex);
691 /******************************************************************************
692 * TAB_HotTrackTimerProc
694 * When a mouse-move event causes a tab to be highlighted (hot-tracking), a
695 * timer is setup so we can check if the mouse is moved out of our window.
696 * (We don't get an event when the mouse leaves, the mouse-move events just
697 * stop being delivered to our window and just start being delivered to
698 * another window.) This function is called when the timer triggers so
699 * we can check if the mouse has left our window. If so, we un-highlight
700 * the hot-tracked tab.
702 static void CALLBACK
703 TAB_HotTrackTimerProc
705 HWND hwnd, /* handle of window for timer messages */
706 UINT uMsg, /* WM_TIMER message */
707 UINT_PTR idEvent, /* timer identifier */
708 DWORD dwTime /* current system time */
711 TAB_INFO* infoPtr = TAB_GetInfoPtr(hwnd);
713 if (infoPtr != NULL && infoPtr->iHotTracked >= 0)
715 POINT pt;
718 ** If we can't get the cursor position, or if the cursor is outside our
719 ** window, we un-highlight the hot-tracked tab. Note that the cursor is
720 ** "outside" even if it is within our bounding rect if another window
721 ** overlaps. Note also that the case where the cursor stayed within our
722 ** window but has moved off the hot-tracked tab will be handled by the
723 ** WM_MOUSEMOVE event.
725 if (!GetCursorPos(&pt) || WindowFromPoint(pt) != hwnd)
727 /* Redraw iHotTracked to look normal */
728 INT iRedraw = infoPtr->iHotTracked;
729 infoPtr->iHotTracked = -1;
730 hottrack_refresh (infoPtr, iRedraw);
732 /* Kill this timer */
733 KillTimer(hwnd, TAB_HOTTRACK_TIMER);
738 /******************************************************************************
739 * TAB_RecalcHotTrack
741 * If a tab control has the TCS_HOTTRACK style, then the tab under the mouse
742 * should be highlighted. This function determines which tab in a tab control,
743 * if any, is under the mouse and records that information. The caller may
744 * supply output parameters to receive the item number of the tab item which
745 * was highlighted but isn't any longer and of the tab item which is now
746 * highlighted but wasn't previously. The caller can use this information to
747 * selectively redraw those tab items.
749 * If the caller has a mouse position, it can supply it through the pos
750 * parameter. For example, TAB_MouseMove does this. Otherwise, the caller
751 * supplies NULL and this function determines the current mouse position
752 * itself.
754 static void
755 TAB_RecalcHotTrack
757 TAB_INFO* infoPtr,
758 const LPARAM* pos,
759 int* out_redrawLeave,
760 int* out_redrawEnter
763 int item = -1;
766 if (out_redrawLeave != NULL)
767 *out_redrawLeave = -1;
768 if (out_redrawEnter != NULL)
769 *out_redrawEnter = -1;
771 if ((GetWindowLongW(infoPtr->hwnd, GWL_STYLE) & TCS_HOTTRACK)
772 || GetWindowTheme (infoPtr->hwnd))
774 POINT pt;
775 UINT flags;
777 if (pos == NULL)
779 GetCursorPos(&pt);
780 ScreenToClient(infoPtr->hwnd, &pt);
782 else
784 pt.x = (short)LOWORD(*pos);
785 pt.y = (short)HIWORD(*pos);
788 item = TAB_InternalHitTest(infoPtr, pt, &flags);
791 if (item != infoPtr->iHotTracked)
793 if (infoPtr->iHotTracked >= 0)
795 /* Mark currently hot-tracked to be redrawn to look normal */
796 if (out_redrawLeave != NULL)
797 *out_redrawLeave = infoPtr->iHotTracked;
799 if (item < 0)
801 /* Kill timer which forces recheck of mouse pos */
802 KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
805 else
807 /* Start timer so we recheck mouse pos */
808 UINT timerID = SetTimer
810 infoPtr->hwnd,
811 TAB_HOTTRACK_TIMER,
812 TAB_HOTTRACK_TIMER_INTERVAL,
813 TAB_HotTrackTimerProc
816 if (timerID == 0)
817 return; /* Hot tracking not available */
820 infoPtr->iHotTracked = item;
822 if (item >= 0)
824 /* Mark new hot-tracked to be redrawn to look highlighted */
825 if (out_redrawEnter != NULL)
826 *out_redrawEnter = item;
831 /******************************************************************************
832 * TAB_MouseMove
834 * Handles the mouse-move event. Updates tooltips. Updates hot-tracking.
836 static LRESULT
837 TAB_MouseMove (TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
839 int redrawLeave;
840 int redrawEnter;
842 if (infoPtr->hwndToolTip)
843 TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
844 WM_LBUTTONDOWN, wParam, lParam);
846 /* Determine which tab to highlight. Redraw tabs which change highlight
847 ** status. */
848 TAB_RecalcHotTrack(infoPtr, &lParam, &redrawLeave, &redrawEnter);
850 hottrack_refresh (infoPtr, redrawLeave);
851 hottrack_refresh (infoPtr, redrawEnter);
853 return 0;
856 /******************************************************************************
857 * TAB_AdjustRect
859 * Calculates the tab control's display area given the window rectangle or
860 * the window rectangle given the requested display rectangle.
862 static LRESULT TAB_AdjustRect(const TAB_INFO *infoPtr, WPARAM fLarger, LPRECT prc)
864 DWORD lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
865 LONG *iRightBottom, *iLeftTop;
867 TRACE ("hwnd=%p fLarger=%ld (%d,%d)-(%d,%d)\n", infoPtr->hwnd, fLarger, prc->left, prc->top, prc->right, prc->bottom);
869 if(lStyle & TCS_VERTICAL)
871 iRightBottom = &(prc->right);
872 iLeftTop = &(prc->left);
874 else
876 iRightBottom = &(prc->bottom);
877 iLeftTop = &(prc->top);
880 if (fLarger) /* Go from display rectangle */
882 /* Add the height of the tabs. */
883 if (lStyle & TCS_BOTTOM)
884 *iRightBottom += infoPtr->tabHeight * infoPtr->uNumRows;
885 else
886 *iLeftTop -= infoPtr->tabHeight * infoPtr->uNumRows +
887 ((lStyle & TCS_BUTTONS)? 3 * (infoPtr->uNumRows - 1) : 0);
889 /* Inflate the rectangle for the padding */
890 InflateRect(prc, DISPLAY_AREA_PADDINGX, DISPLAY_AREA_PADDINGY);
892 /* Inflate for the border */
893 InflateRect(prc, CONTROL_BORDER_SIZEX, CONTROL_BORDER_SIZEY);
895 else /* Go from window rectangle. */
897 /* Deflate the rectangle for the border */
898 InflateRect(prc, -CONTROL_BORDER_SIZEX, -CONTROL_BORDER_SIZEY);
900 /* Deflate the rectangle for the padding */
901 InflateRect(prc, -DISPLAY_AREA_PADDINGX, -DISPLAY_AREA_PADDINGY);
903 /* Remove the height of the tabs. */
904 if (lStyle & TCS_BOTTOM)
905 *iRightBottom -= infoPtr->tabHeight * infoPtr->uNumRows;
906 else
907 *iLeftTop += (infoPtr->tabHeight) * infoPtr->uNumRows +
908 ((lStyle & TCS_BUTTONS)? 3 * (infoPtr->uNumRows - 1) : 0);
911 return 0;
914 /******************************************************************************
915 * TAB_OnHScroll
917 * This method will handle the notification from the scroll control and
918 * perform the scrolling operation on the tab control.
920 static LRESULT TAB_OnHScroll(TAB_INFO *infoPtr, int nScrollCode, int nPos, HWND hwndScroll)
922 if(nScrollCode == SB_THUMBPOSITION && nPos != infoPtr->leftmostVisible)
924 if(nPos < infoPtr->leftmostVisible)
925 infoPtr->leftmostVisible--;
926 else
927 infoPtr->leftmostVisible++;
929 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
930 TAB_InvalidateTabArea(infoPtr);
931 SendMessageW(infoPtr->hwndUpDown, UDM_SETPOS, 0,
932 MAKELONG(infoPtr->leftmostVisible, 0));
935 return 0;
938 /******************************************************************************
939 * TAB_SetupScrolling
941 * This method will check the current scrolling state and make sure the
942 * scrolling control is displayed (or not).
944 static void TAB_SetupScrolling(
945 HWND hwnd,
946 TAB_INFO* infoPtr,
947 const RECT* clientRect)
949 static const WCHAR msctls_updown32W[] = { 'm','s','c','t','l','s','_','u','p','d','o','w','n','3','2',0 };
950 static const WCHAR emptyW[] = { 0 };
951 INT maxRange = 0;
952 DWORD lStyle = GetWindowLongW(hwnd, GWL_STYLE);
954 if (infoPtr->needsScrolling)
956 RECT controlPos;
957 INT vsize, tabwidth;
960 * Calculate the position of the scroll control.
962 if(lStyle & TCS_VERTICAL)
964 controlPos.right = clientRect->right;
965 controlPos.left = controlPos.right - 2 * GetSystemMetrics(SM_CXHSCROLL);
967 if (lStyle & TCS_BOTTOM)
969 controlPos.top = clientRect->bottom - infoPtr->tabHeight;
970 controlPos.bottom = controlPos.top + GetSystemMetrics(SM_CYHSCROLL);
972 else
974 controlPos.bottom = clientRect->top + infoPtr->tabHeight;
975 controlPos.top = controlPos.bottom - GetSystemMetrics(SM_CYHSCROLL);
978 else
980 controlPos.right = clientRect->right;
981 controlPos.left = controlPos.right - 2 * GetSystemMetrics(SM_CXHSCROLL);
983 if (lStyle & TCS_BOTTOM)
985 controlPos.top = clientRect->bottom - infoPtr->tabHeight;
986 controlPos.bottom = controlPos.top + GetSystemMetrics(SM_CYHSCROLL);
988 else
990 controlPos.bottom = clientRect->top + infoPtr->tabHeight;
991 controlPos.top = controlPos.bottom - GetSystemMetrics(SM_CYHSCROLL);
996 * If we don't have a scroll control yet, we want to create one.
997 * If we have one, we want to make sure it's positioned properly.
999 if (infoPtr->hwndUpDown==0)
1001 infoPtr->hwndUpDown = CreateWindowW(msctls_updown32W, emptyW,
1002 WS_VISIBLE | WS_CHILD | UDS_HORZ,
1003 controlPos.left, controlPos.top,
1004 controlPos.right - controlPos.left,
1005 controlPos.bottom - controlPos.top,
1006 hwnd, NULL, NULL, NULL);
1008 else
1010 SetWindowPos(infoPtr->hwndUpDown,
1011 NULL,
1012 controlPos.left, controlPos.top,
1013 controlPos.right - controlPos.left,
1014 controlPos.bottom - controlPos.top,
1015 SWP_SHOWWINDOW | SWP_NOZORDER);
1018 /* Now calculate upper limit of the updown control range.
1019 * We do this by calculating how many tabs will be offscreen when the
1020 * last tab is visible.
1022 if(infoPtr->uNumItem)
1024 vsize = clientRect->right - (controlPos.right - controlPos.left + 1);
1025 maxRange = infoPtr->uNumItem;
1026 tabwidth = TAB_GetItem(infoPtr, infoPtr->uNumItem - 1)->rect.right;
1028 for(; maxRange > 0; maxRange--)
1030 if(tabwidth - TAB_GetItem(infoPtr,maxRange - 1)->rect.left > vsize)
1031 break;
1034 if(maxRange == infoPtr->uNumItem)
1035 maxRange--;
1038 else
1040 /* If we once had a scroll control... hide it */
1041 if (infoPtr->hwndUpDown!=0)
1042 ShowWindow(infoPtr->hwndUpDown, SW_HIDE);
1044 if (infoPtr->hwndUpDown)
1045 SendMessageW(infoPtr->hwndUpDown, UDM_SETRANGE32, 0, maxRange);
1048 /******************************************************************************
1049 * TAB_SetItemBounds
1051 * This method will calculate the position rectangles of all the items in the
1052 * control. The rectangle calculated starts at 0 for the first item in the
1053 * list and ignores scrolling and selection.
1054 * It also uses the current font to determine the height of the tab row and
1055 * it checks if all the tabs fit in the client area of the window. If they
1056 * don't, a scrolling control is added.
1058 static void TAB_SetItemBounds (TAB_INFO *infoPtr)
1060 LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1061 TEXTMETRICW fontMetrics;
1062 UINT curItem;
1063 INT curItemLeftPos;
1064 INT curItemRowCount;
1065 HFONT hFont, hOldFont;
1066 HDC hdc;
1067 RECT clientRect;
1068 INT iTemp;
1069 RECT* rcItem;
1070 INT iIndex;
1071 INT icon_width = 0;
1074 * We need to get text information so we need a DC and we need to select
1075 * a font.
1077 hdc = GetDC(infoPtr->hwnd);
1079 hFont = infoPtr->hFont ? infoPtr->hFont : GetStockObject (SYSTEM_FONT);
1080 hOldFont = SelectObject (hdc, hFont);
1083 * We will base the rectangle calculations on the client rectangle
1084 * of the control.
1086 GetClientRect(infoPtr->hwnd, &clientRect);
1088 /* if TCS_VERTICAL then swap the height and width so this code places the
1089 tabs along the top of the rectangle and we can just rotate them after
1090 rather than duplicate all of the below code */
1091 if(lStyle & TCS_VERTICAL)
1093 iTemp = clientRect.bottom;
1094 clientRect.bottom = clientRect.right;
1095 clientRect.right = iTemp;
1098 /* Now use hPadding and vPadding */
1099 infoPtr->uHItemPadding = infoPtr->uHItemPadding_s;
1100 infoPtr->uVItemPadding = infoPtr->uVItemPadding_s;
1102 /* The leftmost item will be "0" aligned */
1103 curItemLeftPos = 0;
1104 curItemRowCount = infoPtr->uNumItem ? 1 : 0;
1106 if (!(infoPtr->fHeightSet))
1108 int item_height;
1109 int icon_height = 0;
1111 /* Use the current font to determine the height of a tab. */
1112 GetTextMetricsW(hdc, &fontMetrics);
1114 /* Get the icon height */
1115 if (infoPtr->himl)
1116 ImageList_GetIconSize(infoPtr->himl, 0, &icon_height);
1118 /* Take the highest between font or icon */
1119 if (fontMetrics.tmHeight > icon_height)
1120 item_height = fontMetrics.tmHeight + 2;
1121 else
1122 item_height = icon_height;
1125 * Make sure there is enough space for the letters + icon + growing the
1126 * selected item + extra space for the selected item.
1128 infoPtr->tabHeight = item_height +
1129 ((lStyle & TCS_BUTTONS) ? 2 : 1) *
1130 infoPtr->uVItemPadding;
1132 TRACE("tabH=%d, tmH=%d, iconh=%d\n",
1133 infoPtr->tabHeight, fontMetrics.tmHeight, icon_height);
1136 TRACE("client right=%d\n", clientRect.right);
1138 /* Get the icon width */
1139 if (infoPtr->himl)
1141 ImageList_GetIconSize(infoPtr->himl, &icon_width, 0);
1143 if (lStyle & TCS_FIXEDWIDTH)
1144 icon_width += 4;
1145 else
1146 /* Add padding if icon is present */
1147 icon_width += infoPtr->uHItemPadding;
1150 for (curItem = 0; curItem < infoPtr->uNumItem; curItem++)
1152 TAB_ITEM *curr = TAB_GetItem(infoPtr, curItem);
1154 /* Set the leftmost position of the tab. */
1155 curr->rect.left = curItemLeftPos;
1157 if (lStyle & TCS_FIXEDWIDTH)
1159 curr->rect.right = curr->rect.left +
1160 max(infoPtr->tabWidth, icon_width);
1162 else if (!curr->pszText)
1164 /* If no text use minimum tab width including padding. */
1165 if (infoPtr->tabMinWidth < 0)
1166 curr->rect.right = curr->rect.left + GET_DEFAULT_MIN_TAB_WIDTH(infoPtr);
1167 else
1169 curr->rect.right = curr->rect.left + infoPtr->tabMinWidth;
1171 /* Add extra padding if icon is present */
1172 if (infoPtr->himl && infoPtr->tabMinWidth > 0 && infoPtr->tabMinWidth < DEFAULT_MIN_TAB_WIDTH
1173 && infoPtr->uHItemPadding > 1)
1174 curr->rect.right += EXTRA_ICON_PADDING * (infoPtr->uHItemPadding-1);
1177 else
1179 int tabwidth;
1180 SIZE size;
1181 /* Calculate how wide the tab is depending on the text it contains */
1182 GetTextExtentPoint32W(hdc, curr->pszText,
1183 lstrlenW(curr->pszText), &size);
1185 tabwidth = size.cx + icon_width + 2 * infoPtr->uHItemPadding;
1187 if (infoPtr->tabMinWidth < 0)
1188 tabwidth = max(tabwidth, GET_DEFAULT_MIN_TAB_WIDTH(infoPtr));
1189 else
1190 tabwidth = max(tabwidth, infoPtr->tabMinWidth);
1192 curr->rect.right = curr->rect.left + tabwidth;
1193 TRACE("for <%s>, l,r=%d,%d\n",
1194 debugstr_w(curr->pszText), curr->rect.left, curr->rect.right);
1198 * Check if this is a multiline tab control and if so
1199 * check to see if we should wrap the tabs
1201 * Wrap all these tabs. We will arrange them evenly later.
1205 if (((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)) &&
1206 (curr->rect.right >
1207 (clientRect.right - CONTROL_BORDER_SIZEX - DISPLAY_AREA_PADDINGX)))
1209 curr->rect.right -= curr->rect.left;
1211 curr->rect.left = 0;
1212 curItemRowCount++;
1213 TRACE("wrapping <%s>, l,r=%d,%d\n", debugstr_w(curr->pszText),
1214 curr->rect.left, curr->rect.right);
1217 curr->rect.bottom = 0;
1218 curr->rect.top = curItemRowCount - 1;
1220 TRACE("Rect: T %i, L %i, B %i, R %i\n", curr->rect.top,
1221 curr->rect.left, curr->rect.bottom, curr->rect.right);
1224 * The leftmost position of the next item is the rightmost position
1225 * of this one.
1227 if (lStyle & TCS_BUTTONS)
1229 curItemLeftPos = curr->rect.right + BUTTON_SPACINGX;
1230 if (lStyle & TCS_FLATBUTTONS)
1231 curItemLeftPos += FLAT_BTN_SPACINGX;
1233 else
1234 curItemLeftPos = curr->rect.right;
1237 if (!((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)))
1240 * Check if we need a scrolling control.
1242 infoPtr->needsScrolling = (curItemLeftPos + (2 * SELECTED_TAB_OFFSET) >
1243 clientRect.right);
1245 /* Don't need scrolling, then update infoPtr->leftmostVisible */
1246 if(!infoPtr->needsScrolling)
1247 infoPtr->leftmostVisible = 0;
1249 else
1252 * No scrolling in Multiline or Vertical styles.
1254 infoPtr->needsScrolling = FALSE;
1255 infoPtr->leftmostVisible = 0;
1257 TAB_SetupScrolling(infoPtr->hwnd, infoPtr, &clientRect);
1259 /* Set the number of rows */
1260 infoPtr->uNumRows = curItemRowCount;
1262 /* Arrange all tabs evenly if style says so */
1263 if (!(lStyle & TCS_RAGGEDRIGHT) &&
1264 ((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)) &&
1265 (infoPtr->uNumItem > 0) &&
1266 (infoPtr->uNumRows > 1))
1268 INT tabPerRow,remTab,iRow;
1269 UINT iItm;
1270 INT iCount=0;
1273 * Ok windows tries to even out the rows. place the same
1274 * number of tabs in each row. So lets give that a shot
1277 tabPerRow = infoPtr->uNumItem / (infoPtr->uNumRows);
1278 remTab = infoPtr->uNumItem % (infoPtr->uNumRows);
1280 for (iItm=0,iRow=0,iCount=0,curItemLeftPos=0;
1281 iItm<infoPtr->uNumItem;
1282 iItm++,iCount++)
1284 /* normalize the current rect */
1285 TAB_ITEM *curr = TAB_GetItem(infoPtr, iItm);
1287 /* shift the item to the left side of the clientRect */
1288 curr->rect.right -= curr->rect.left;
1289 curr->rect.left = 0;
1291 TRACE("r=%d, cl=%d, cl.r=%d, iCount=%d, iRow=%d, uNumRows=%d, remTab=%d, tabPerRow=%d\n",
1292 curr->rect.right, curItemLeftPos, clientRect.right,
1293 iCount, iRow, infoPtr->uNumRows, remTab, tabPerRow);
1295 /* if we have reached the maximum number of tabs on this row */
1296 /* move to the next row, reset our current item left position and */
1297 /* the count of items on this row */
1299 if (lStyle & TCS_VERTICAL) {
1300 /* Vert: Add the remaining tabs in the *last* remainder rows */
1301 if (iCount >= ((iRow>=(INT)infoPtr->uNumRows - remTab)?tabPerRow + 1:tabPerRow)) {
1302 iRow++;
1303 curItemLeftPos = 0;
1304 iCount = 0;
1306 } else {
1307 /* Horz: Add the remaining tabs in the *first* remainder rows */
1308 if (iCount >= ((iRow<remTab)?tabPerRow + 1:tabPerRow)) {
1309 iRow++;
1310 curItemLeftPos = 0;
1311 iCount = 0;
1315 /* shift the item to the right to place it as the next item in this row */
1316 curr->rect.left += curItemLeftPos;
1317 curr->rect.right += curItemLeftPos;
1318 curr->rect.top = iRow;
1319 if (lStyle & TCS_BUTTONS)
1321 curItemLeftPos = curr->rect.right + 1;
1322 if (lStyle & TCS_FLATBUTTONS)
1323 curItemLeftPos += FLAT_BTN_SPACINGX;
1325 else
1326 curItemLeftPos = curr->rect.right;
1328 TRACE("arranging <%s>, l,r=%d,%d, row=%d\n",
1329 debugstr_w(curr->pszText), curr->rect.left,
1330 curr->rect.right, curr->rect.top);
1334 * Justify the rows
1337 INT widthDiff, iIndexStart=0, iIndexEnd=0;
1338 INT remainder;
1339 INT iCount=0;
1341 while(iIndexStart < infoPtr->uNumItem)
1343 TAB_ITEM *start = TAB_GetItem(infoPtr, iIndexStart);
1346 * find the index of the row
1348 /* find the first item on the next row */
1349 for (iIndexEnd=iIndexStart;
1350 (iIndexEnd < infoPtr->uNumItem) &&
1351 (TAB_GetItem(infoPtr, iIndexEnd)->rect.top ==
1352 start->rect.top) ;
1353 iIndexEnd++)
1354 /* intentionally blank */;
1357 * we need to justify these tabs so they fill the whole given
1358 * client area
1361 /* find the amount of space remaining on this row */
1362 widthDiff = clientRect.right - (2 * SELECTED_TAB_OFFSET) -
1363 TAB_GetItem(infoPtr, iIndexEnd - 1)->rect.right;
1365 /* iCount is the number of tab items on this row */
1366 iCount = iIndexEnd - iIndexStart;
1368 if (iCount > 1)
1370 remainder = widthDiff % iCount;
1371 widthDiff = widthDiff / iCount;
1372 /* add widthDiff/iCount, or extra space/items on row, to each item on this row */
1373 for (iIndex=iIndexStart, iCount=0; iIndex < iIndexEnd; iIndex++, iCount++)
1375 TAB_ITEM *item = TAB_GetItem(infoPtr, iIndex);
1377 item->rect.left += iCount * widthDiff;
1378 item->rect.right += (iCount + 1) * widthDiff;
1380 TRACE("adjusting 1 <%s>, l,r=%d,%d\n",
1381 debugstr_w(item->pszText),
1382 item->rect.left, item->rect.right);
1385 TAB_GetItem(infoPtr, iIndex - 1)->rect.right += remainder;
1387 else /* we have only one item on this row, make it take up the entire row */
1389 start->rect.left = clientRect.left;
1390 start->rect.right = clientRect.right - 4;
1392 TRACE("adjusting 2 <%s>, l,r=%d,%d\n",
1393 debugstr_w(start->pszText),
1394 start->rect.left, start->rect.right);
1399 iIndexStart = iIndexEnd;
1404 /* if TCS_VERTICAL rotate the tabs so they are along the side of the clientRect */
1405 if(lStyle & TCS_VERTICAL)
1407 RECT rcOriginal;
1408 for(iIndex = 0; iIndex < infoPtr->uNumItem; iIndex++)
1410 rcItem = &TAB_GetItem(infoPtr, iIndex)->rect;
1412 rcOriginal = *rcItem;
1414 /* this is rotating the items by 90 degrees clockwise around the center of the control */
1415 rcItem->top = (rcOriginal.left - clientRect.left);
1416 rcItem->bottom = rcItem->top + (rcOriginal.right - rcOriginal.left);
1417 rcItem->left = rcOriginal.top;
1418 rcItem->right = rcOriginal.bottom;
1422 TAB_EnsureSelectionVisible(infoPtr);
1423 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
1425 /* Cleanup */
1426 SelectObject (hdc, hOldFont);
1427 ReleaseDC (infoPtr->hwnd, hdc);
1431 static void
1432 TAB_EraseTabInterior(const TAB_INFO *infoPtr, HDC hdc, INT iItem, RECT *drawRect)
1434 LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1435 HBRUSH hbr = CreateSolidBrush (comctl32_color.clrBtnFace);
1436 BOOL deleteBrush = TRUE;
1437 RECT rTemp = *drawRect;
1439 InflateRect(&rTemp, -2, -2);
1440 if (lStyle & TCS_BUTTONS)
1442 if (iItem == infoPtr->iSelected)
1444 /* Background color */
1445 if (!(lStyle & TCS_OWNERDRAWFIXED))
1447 DeleteObject(hbr);
1448 hbr = GetSysColorBrush(COLOR_SCROLLBAR);
1450 SetTextColor(hdc, comctl32_color.clr3dFace);
1451 SetBkColor(hdc, comctl32_color.clr3dHilight);
1453 /* if COLOR_WINDOW happens to be the same as COLOR_3DHILIGHT
1454 * we better use 0x55aa bitmap brush to make scrollbar's background
1455 * look different from the window background.
1457 if (comctl32_color.clr3dHilight == comctl32_color.clrWindow)
1458 hbr = COMCTL32_hPattern55AABrush;
1460 deleteBrush = FALSE;
1462 FillRect(hdc, &rTemp, hbr);
1464 else /* ! selected */
1466 if (lStyle & TCS_FLATBUTTONS)
1468 FillRect(hdc, drawRect, hbr);
1469 if (iItem == infoPtr->iHotTracked)
1470 DrawEdge(hdc, drawRect, EDGE_RAISED, BF_SOFT|BF_RECT);
1472 else
1473 FillRect(hdc, &rTemp, hbr);
1477 else /* !TCS_BUTTONS */
1479 if (!GetWindowTheme (infoPtr->hwnd))
1480 FillRect(hdc, &rTemp, hbr);
1483 /* Cleanup */
1484 if (deleteBrush) DeleteObject(hbr);
1487 /******************************************************************************
1488 * TAB_DrawItemInterior
1490 * This method is used to draw the interior (text and icon) of a single tab
1491 * into the tab control.
1493 static void
1494 TAB_DrawItemInterior(const TAB_INFO *infoPtr, HDC hdc, INT iItem, RECT *drawRect)
1496 LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1498 RECT localRect;
1500 HPEN htextPen;
1501 HPEN holdPen;
1502 INT oldBkMode;
1503 HFONT hOldFont;
1505 /* if (drawRect == NULL) */
1507 BOOL isVisible;
1508 RECT itemRect;
1509 RECT selectedRect;
1512 * Get the rectangle for the item.
1514 isVisible = TAB_InternalGetItemRect(infoPtr, iItem, &itemRect, &selectedRect);
1515 if (!isVisible)
1516 return;
1519 * Make sure drawRect points to something valid; simplifies code.
1521 drawRect = &localRect;
1524 * This logic copied from the part of TAB_DrawItem which draws
1525 * the tab background. It's important to keep it in sync. I
1526 * would have liked to avoid code duplication, but couldn't figure
1527 * out how without making spaghetti of TAB_DrawItem.
1529 if (iItem == infoPtr->iSelected)
1530 *drawRect = selectedRect;
1531 else
1532 *drawRect = itemRect;
1534 if (lStyle & TCS_BUTTONS)
1536 if (iItem == infoPtr->iSelected)
1538 drawRect->left += 4;
1539 drawRect->top += 4;
1540 drawRect->right -= 4;
1541 drawRect->bottom -= 1;
1543 else
1545 drawRect->left += 2;
1546 drawRect->top += 2;
1547 drawRect->right -= 2;
1548 drawRect->bottom -= 2;
1551 else
1553 if ((lStyle & TCS_VERTICAL) && (lStyle & TCS_BOTTOM))
1555 if (iItem != infoPtr->iSelected)
1557 drawRect->left += 2;
1558 drawRect->top += 2;
1559 drawRect->bottom -= 2;
1562 else if (lStyle & TCS_VERTICAL)
1564 if (iItem == infoPtr->iSelected)
1566 drawRect->right += 1;
1568 else
1570 drawRect->top += 2;
1571 drawRect->right -= 2;
1572 drawRect->bottom -= 2;
1575 else if (lStyle & TCS_BOTTOM)
1577 if (iItem == infoPtr->iSelected)
1579 drawRect->top -= 2;
1581 else
1583 InflateRect(drawRect, -2, -2);
1584 drawRect->bottom += 2;
1587 else
1589 if (iItem == infoPtr->iSelected)
1591 drawRect->bottom += 3;
1593 else
1595 drawRect->bottom -= 2;
1596 InflateRect(drawRect, -2, 0);
1601 TRACE("drawRect=(%d,%d)-(%d,%d)\n",
1602 drawRect->left, drawRect->top, drawRect->right, drawRect->bottom);
1604 /* Clear interior */
1605 TAB_EraseTabInterior (infoPtr, hdc, iItem, drawRect);
1607 /* Draw the focus rectangle */
1608 if (!(lStyle & TCS_FOCUSNEVER) &&
1609 (GetFocus() == infoPtr->hwnd) &&
1610 (iItem == infoPtr->uFocus) )
1612 RECT rFocus = *drawRect;
1613 InflateRect(&rFocus, -3, -3);
1614 if (lStyle & TCS_BOTTOM && !(lStyle & TCS_VERTICAL))
1615 rFocus.top -= 3;
1616 if (lStyle & TCS_BUTTONS)
1618 rFocus.left -= 3;
1619 rFocus.top -= 3;
1622 DrawFocusRect(hdc, &rFocus);
1626 * Text pen
1628 htextPen = CreatePen( PS_SOLID, 1, GetSysColor(COLOR_BTNTEXT) );
1629 holdPen = SelectObject(hdc, htextPen);
1630 hOldFont = SelectObject(hdc, infoPtr->hFont);
1633 * Setup for text output
1635 oldBkMode = SetBkMode(hdc, TRANSPARENT);
1636 if (!GetWindowTheme (infoPtr->hwnd) || (lStyle & TCS_BUTTONS))
1637 SetTextColor(hdc, (((lStyle & TCS_HOTTRACK) && (iItem == infoPtr->iHotTracked)
1638 && !(lStyle & TCS_FLATBUTTONS))
1639 | (TAB_GetItem(infoPtr, iItem)->dwState & TCIS_HIGHLIGHTED)) ?
1640 comctl32_color.clrHighlight : comctl32_color.clrBtnText);
1643 * if owner draw, tell the owner to draw
1645 if ((lStyle & TCS_OWNERDRAWFIXED) && GetParent(infoPtr->hwnd))
1647 DRAWITEMSTRUCT dis;
1648 UINT id;
1650 drawRect->top += 2;
1651 drawRect->right -= 1;
1652 if ( iItem == infoPtr->iSelected )
1654 drawRect->right -= 1;
1655 drawRect->left += 1;
1659 * get the control id
1661 id = (UINT)GetWindowLongPtrW( infoPtr->hwnd, GWLP_ID );
1664 * put together the DRAWITEMSTRUCT
1666 dis.CtlType = ODT_TAB;
1667 dis.CtlID = id;
1668 dis.itemID = iItem;
1669 dis.itemAction = ODA_DRAWENTIRE;
1670 dis.itemState = 0;
1671 if ( iItem == infoPtr->iSelected )
1672 dis.itemState |= ODS_SELECTED;
1673 if (infoPtr->uFocus == iItem)
1674 dis.itemState |= ODS_FOCUS;
1675 dis.hwndItem = infoPtr->hwnd;
1676 dis.hDC = hdc;
1677 CopyRect(&dis.rcItem,drawRect);
1678 dis.itemData = (ULONG_PTR)TAB_GetItem(infoPtr, iItem)->extra;
1681 * send the draw message
1683 SendMessageW( infoPtr->hwndNotify, WM_DRAWITEM, (WPARAM)id, (LPARAM)&dis );
1685 else
1687 TAB_ITEM *item = TAB_GetItem(infoPtr, iItem);
1688 RECT rcTemp;
1689 RECT rcImage;
1691 /* used to center the icon and text in the tab */
1692 RECT rcText;
1693 INT center_offset_h, center_offset_v;
1695 /* set rcImage to drawRect, we will use top & left in our ImageList_Draw call */
1696 rcImage = *drawRect;
1698 rcTemp = *drawRect;
1700 rcText.left = rcText.top = rcText.right = rcText.bottom = 0;
1702 /* get the rectangle that the text fits in */
1703 if (item->pszText)
1705 DrawTextW(hdc, item->pszText, -1, &rcText, DT_CALCRECT);
1708 * If not owner draw, then do the drawing ourselves.
1710 * Draw the icon.
1712 if (infoPtr->himl && item->iImage != -1)
1714 INT cx;
1715 INT cy;
1717 ImageList_GetIconSize(infoPtr->himl, &cx, &cy);
1719 if(lStyle & TCS_VERTICAL)
1721 center_offset_h = ((drawRect->bottom - drawRect->top) - (cy + infoPtr->uHItemPadding + (rcText.right - rcText.left))) / 2;
1722 center_offset_v = (drawRect->left + (drawRect->right - drawRect->left) - cx) / 2;
1724 else
1726 center_offset_h = ((drawRect->right - drawRect->left) - (cx + infoPtr->uHItemPadding + (rcText.right - rcText.left))) / 2;
1727 center_offset_v = (drawRect->top + (drawRect->bottom - drawRect->top) - cy) / 2;
1730 /* if an item is selected, the icon is shifted up instead of down */
1731 if (iItem == infoPtr->iSelected)
1732 center_offset_v -= infoPtr->uVItemPadding / 2;
1733 else
1734 center_offset_v += infoPtr->uVItemPadding / 2;
1736 if (lStyle & TCS_FIXEDWIDTH && lStyle & (TCS_FORCELABELLEFT | TCS_FORCEICONLEFT))
1737 center_offset_h = infoPtr->uHItemPadding;
1739 if (center_offset_h < 2)
1740 center_offset_h = 2;
1742 if (center_offset_v < 0)
1743 center_offset_v = 0;
1745 TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%d,%d)-(%d,%d), textlen=%d\n",
1746 debugstr_w(item->pszText), center_offset_h, center_offset_v,
1747 drawRect->left, drawRect->top, drawRect->right, drawRect->bottom,
1748 (rcText.right-rcText.left));
1750 if((lStyle & TCS_VERTICAL) && (lStyle & TCS_BOTTOM))
1752 rcImage.top = drawRect->top + center_offset_h;
1753 /* if tab is TCS_VERTICAL and TCS_BOTTOM, the text is drawn from the */
1754 /* right side of the tab, but the image still uses the left as its x position */
1755 /* this keeps the image always drawn off of the same side of the tab */
1756 rcImage.left = drawRect->right - cx - center_offset_v;
1757 drawRect->top += cy + infoPtr->uHItemPadding;
1759 else if(lStyle & TCS_VERTICAL)
1761 rcImage.top = drawRect->bottom - cy - center_offset_h;
1762 rcImage.left = drawRect->left + center_offset_v;
1763 drawRect->bottom -= cy + infoPtr->uHItemPadding;
1765 else /* normal style, whether TCS_BOTTOM or not */
1767 rcImage.left = drawRect->left + center_offset_h;
1768 rcImage.top = drawRect->top + center_offset_v;
1769 drawRect->left += cx + infoPtr->uHItemPadding;
1772 TRACE("drawing image=%d, left=%d, top=%d\n",
1773 item->iImage, rcImage.left, rcImage.top-1);
1774 ImageList_Draw
1776 infoPtr->himl,
1777 item->iImage,
1778 hdc,
1779 rcImage.left,
1780 rcImage.top,
1781 ILD_NORMAL
1785 /* Now position text */
1786 if (lStyle & TCS_FIXEDWIDTH && lStyle & TCS_FORCELABELLEFT)
1787 center_offset_h = infoPtr->uHItemPadding;
1788 else
1789 if(lStyle & TCS_VERTICAL)
1790 center_offset_h = ((drawRect->bottom - drawRect->top) - (rcText.right - rcText.left)) / 2;
1791 else
1792 center_offset_h = ((drawRect->right - drawRect->left) - (rcText.right - rcText.left)) / 2;
1794 if(lStyle & TCS_VERTICAL)
1796 if(lStyle & TCS_BOTTOM)
1797 drawRect->top+=center_offset_h;
1798 else
1799 drawRect->bottom-=center_offset_h;
1801 center_offset_v = ((drawRect->right - drawRect->left) - (rcText.bottom - rcText.top)) / 2;
1803 else
1805 drawRect->left += center_offset_h;
1806 center_offset_v = ((drawRect->bottom - drawRect->top) - (rcText.bottom - rcText.top)) / 2;
1809 /* if an item is selected, the text is shifted up instead of down */
1810 if (iItem == infoPtr->iSelected)
1811 center_offset_v -= infoPtr->uVItemPadding / 2;
1812 else
1813 center_offset_v += infoPtr->uVItemPadding / 2;
1815 if (center_offset_v < 0)
1816 center_offset_v = 0;
1818 if(lStyle & TCS_VERTICAL)
1819 drawRect->left += center_offset_v;
1820 else
1821 drawRect->top += center_offset_v;
1823 /* Draw the text */
1824 if(lStyle & TCS_VERTICAL) /* if we are vertical rotate the text and each character */
1826 static const WCHAR ArialW[] = { 'A','r','i','a','l',0 };
1827 LOGFONTW logfont;
1828 HFONT hFont = 0;
1829 INT nEscapement = 900;
1830 INT nOrientation = 900;
1832 if(lStyle & TCS_BOTTOM)
1834 nEscapement = -900;
1835 nOrientation = -900;
1838 /* to get a font with the escapement and orientation we are looking for, we need to */
1839 /* call CreateFontIndirectA, which requires us to set the values of the logfont we pass in */
1840 if (!GetObjectW((infoPtr->hFont) ?
1841 infoPtr->hFont : GetStockObject(SYSTEM_FONT),
1842 sizeof(LOGFONTW),&logfont))
1844 INT iPointSize = 9;
1846 lstrcpyW(logfont.lfFaceName, ArialW);
1847 logfont.lfHeight = -MulDiv(iPointSize, GetDeviceCaps(hdc, LOGPIXELSY),
1848 72);
1849 logfont.lfWeight = FW_NORMAL;
1850 logfont.lfItalic = 0;
1851 logfont.lfUnderline = 0;
1852 logfont.lfStrikeOut = 0;
1855 logfont.lfEscapement = nEscapement;
1856 logfont.lfOrientation = nOrientation;
1857 hFont = CreateFontIndirectW(&logfont);
1858 SelectObject(hdc, hFont);
1860 if (item->pszText)
1862 ExtTextOutW(hdc,
1863 (lStyle & TCS_BOTTOM) ? drawRect->right : drawRect->left,
1864 (!(lStyle & TCS_BOTTOM)) ? drawRect->bottom : drawRect->top,
1865 ETO_CLIPPED,
1866 drawRect,
1867 item->pszText,
1868 lstrlenW(item->pszText),
1872 DeleteObject(hFont);
1874 else
1876 TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%d,%d)-(%d,%d), textlen=%d\n",
1877 debugstr_w(item->pszText), center_offset_h, center_offset_v,
1878 drawRect->left, drawRect->top, drawRect->right, drawRect->bottom,
1879 (rcText.right-rcText.left));
1880 if (item->pszText)
1882 DrawTextW
1884 hdc,
1885 item->pszText,
1886 lstrlenW(item->pszText),
1887 drawRect,
1888 DT_LEFT | DT_SINGLELINE
1893 *drawRect = rcTemp; /* restore drawRect */
1897 * Cleanup
1899 SelectObject(hdc, hOldFont);
1900 SetBkMode(hdc, oldBkMode);
1901 SelectObject(hdc, holdPen);
1902 DeleteObject( htextPen );
1905 /******************************************************************************
1906 * TAB_DrawItem
1908 * This method is used to draw a single tab into the tab control.
1910 static void TAB_DrawItem(const TAB_INFO *infoPtr, HDC hdc, INT iItem)
1912 LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1913 RECT itemRect;
1914 RECT selectedRect;
1915 BOOL isVisible;
1916 RECT r, fillRect, r1;
1917 INT clRight = 0;
1918 INT clBottom = 0;
1919 COLORREF bkgnd, corner;
1920 HTHEME theme;
1923 * Get the rectangle for the item.
1925 isVisible = TAB_InternalGetItemRect(infoPtr,
1926 iItem,
1927 &itemRect,
1928 &selectedRect);
1930 if (isVisible)
1932 RECT rUD, rC;
1934 /* Clip UpDown control to not draw over it */
1935 if (infoPtr->needsScrolling)
1937 GetWindowRect(infoPtr->hwnd, &rC);
1938 GetWindowRect(infoPtr->hwndUpDown, &rUD);
1939 ExcludeClipRect(hdc, rUD.left - rC.left, rUD.top - rC.top, rUD.right - rC.left, rUD.bottom - rC.top);
1942 /* If you need to see what the control is doing,
1943 * then override these variables. They will change what
1944 * fill colors are used for filling the tabs, and the
1945 * corners when drawing the edge.
1947 bkgnd = comctl32_color.clrBtnFace;
1948 corner = comctl32_color.clrBtnFace;
1950 if (lStyle & TCS_BUTTONS)
1952 /* Get item rectangle */
1953 r = itemRect;
1955 /* Separators between flat buttons */
1956 if (lStyle & TCS_FLATBUTTONS)
1958 r1 = r;
1959 r1.right += (FLAT_BTN_SPACINGX -2);
1960 DrawEdge(hdc, &r1, EDGE_ETCHED, BF_RIGHT);
1963 if (iItem == infoPtr->iSelected)
1965 DrawEdge(hdc, &r, EDGE_SUNKEN, BF_SOFT|BF_RECT);
1967 OffsetRect(&r, 1, 1);
1969 else /* ! selected */
1971 if (!(lStyle & TCS_FLATBUTTONS))
1972 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_RECT);
1975 else /* !TCS_BUTTONS */
1977 /* We draw a rectangle of different sizes depending on the selection
1978 * state. */
1979 if (iItem == infoPtr->iSelected) {
1980 RECT rect;
1981 GetClientRect (infoPtr->hwnd, &rect);
1982 clRight = rect.right;
1983 clBottom = rect.bottom;
1984 r = selectedRect;
1986 else
1987 r = itemRect;
1990 * Erase the background. (Delay it but setup rectangle.)
1991 * This is necessary when drawing the selected item since it is larger
1992 * than the others, it might overlap with stuff already drawn by the
1993 * other tabs
1995 fillRect = r;
1997 /* Draw themed tabs - but only if they are at the top.
1998 * Windows draws even side or bottom tabs themed, with wacky results.
1999 * However, since in Wine apps may get themed that did not opt in via
2000 * a manifest avoid theming when we know the result will be wrong */
2001 if ((theme = GetWindowTheme (infoPtr->hwnd))
2002 && ((lStyle & (TCS_VERTICAL | TCS_BOTTOM)) == 0))
2004 static const int partIds[8] = {
2005 /* Normal item */
2006 TABP_TABITEM,
2007 TABP_TABITEMLEFTEDGE,
2008 TABP_TABITEMRIGHTEDGE,
2009 TABP_TABITEMBOTHEDGE,
2010 /* Selected tab */
2011 TABP_TOPTABITEM,
2012 TABP_TOPTABITEMLEFTEDGE,
2013 TABP_TOPTABITEMRIGHTEDGE,
2014 TABP_TOPTABITEMBOTHEDGE,
2016 int partIndex = 0;
2017 int stateId = TIS_NORMAL;
2019 /* selected and unselected tabs have different parts */
2020 if (iItem == infoPtr->iSelected)
2021 partIndex += 4;
2022 /* The part also differs on the position of a tab on a line.
2023 * "Visually" determining the position works well enough. */
2024 if(selectedRect.left == 0)
2025 partIndex += 1;
2026 if(selectedRect.right == clRight)
2027 partIndex += 2;
2029 if (iItem == infoPtr->iSelected)
2030 stateId = TIS_SELECTED;
2031 else if (iItem == infoPtr->iHotTracked)
2032 stateId = TIS_HOT;
2033 else if (iItem == infoPtr->uFocus)
2034 stateId = TIS_FOCUSED;
2036 /* Adjust rectangle for bottommost row */
2037 if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2038 r.bottom += 3;
2040 DrawThemeBackground (theme, hdc, partIds[partIndex], stateId, &r, NULL);
2041 GetThemeBackgroundContentRect (theme, hdc, partIds[partIndex], stateId, &r, &r);
2043 else if(lStyle & TCS_VERTICAL)
2045 /* These are for adjusting the drawing of a Selected tab */
2046 /* The initial values are for the normal case of non-Selected */
2047 int ZZ = 1; /* Do not strech if selected */
2048 if (iItem == infoPtr->iSelected) {
2049 ZZ = 0;
2051 /* if leftmost draw the line longer */
2052 if(selectedRect.top == 0)
2053 fillRect.top += CONTROL_BORDER_SIZEY;
2054 /* if rightmost draw the line longer */
2055 if(selectedRect.bottom == clBottom)
2056 fillRect.bottom -= CONTROL_BORDER_SIZEY;
2059 if (lStyle & TCS_BOTTOM)
2061 /* Adjust both rectangles to match native */
2062 r.left += (1-ZZ);
2064 TRACE("<right> item=%d, fill=(%d,%d)-(%d,%d), edge=(%d,%d)-(%d,%d)\n",
2065 iItem,
2066 fillRect.left,fillRect.top,fillRect.right,fillRect.bottom,
2067 r.left,r.top,r.right,r.bottom);
2069 /* Clear interior */
2070 SetBkColor(hdc, bkgnd);
2071 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2073 /* Draw rectangular edge around tab */
2074 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_RIGHT|BF_TOP|BF_BOTTOM);
2076 /* Now erase the top corner and draw diagonal edge */
2077 SetBkColor(hdc, corner);
2078 r1.left = r.right - ROUND_CORNER_SIZE - 1;
2079 r1.top = r.top;
2080 r1.right = r.right;
2081 r1.bottom = r1.top + ROUND_CORNER_SIZE;
2082 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2083 r1.right--;
2084 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPLEFT);
2086 /* Now erase the bottom corner and draw diagonal edge */
2087 r1.left = r.right - ROUND_CORNER_SIZE - 1;
2088 r1.bottom = r.bottom;
2089 r1.right = r.right;
2090 r1.top = r1.bottom - ROUND_CORNER_SIZE;
2091 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2092 r1.right--;
2093 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMLEFT);
2095 if ((iItem == infoPtr->iSelected) && (selectedRect.top == 0)) {
2096 r1 = r;
2097 r1.right = r1.left;
2098 r1.left--;
2099 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_TOP);
2103 else
2105 TRACE("<left> item=%d, fill=(%d,%d)-(%d,%d), edge=(%d,%d)-(%d,%d)\n",
2106 iItem,
2107 fillRect.left,fillRect.top,fillRect.right,fillRect.bottom,
2108 r.left,r.top,r.right,r.bottom);
2110 /* Clear interior */
2111 SetBkColor(hdc, bkgnd);
2112 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2114 /* Draw rectangular edge around tab */
2115 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_TOP|BF_BOTTOM);
2117 /* Now erase the top corner and draw diagonal edge */
2118 SetBkColor(hdc, corner);
2119 r1.left = r.left;
2120 r1.top = r.top;
2121 r1.right = r1.left + ROUND_CORNER_SIZE + 1;
2122 r1.bottom = r1.top + ROUND_CORNER_SIZE;
2123 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2124 r1.left++;
2125 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPRIGHT);
2127 /* Now erase the bottom corner and draw diagonal edge */
2128 r1.left = r.left;
2129 r1.bottom = r.bottom;
2130 r1.right = r1.left + ROUND_CORNER_SIZE + 1;
2131 r1.top = r1.bottom - ROUND_CORNER_SIZE;
2132 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2133 r1.left++;
2134 DrawEdge(hdc, &r1, EDGE_SUNKEN, BF_DIAGONAL_ENDTOPLEFT);
2137 else /* ! TCS_VERTICAL */
2139 /* These are for adjusting the drawing of a Selected tab */
2140 /* The initial values are for the normal case of non-Selected */
2141 if (iItem == infoPtr->iSelected) {
2142 /* if leftmost draw the line longer */
2143 if(selectedRect.left == 0)
2144 fillRect.left += CONTROL_BORDER_SIZEX;
2145 /* if rightmost draw the line longer */
2146 if(selectedRect.right == clRight)
2147 fillRect.right -= CONTROL_BORDER_SIZEX;
2150 if (lStyle & TCS_BOTTOM)
2152 /* Adjust both rectangles for topmost row */
2153 if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2155 fillRect.top -= 2;
2156 r.top -= 1;
2159 TRACE("<bottom> item=%d, fill=(%d,%d)-(%d,%d), edge=(%d,%d)-(%d,%d)\n",
2160 iItem,
2161 fillRect.left,fillRect.top,fillRect.right,fillRect.bottom,
2162 r.left,r.top,r.right,r.bottom);
2164 /* Clear interior */
2165 SetBkColor(hdc, bkgnd);
2166 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2168 /* Draw rectangular edge around tab */
2169 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_BOTTOM|BF_RIGHT);
2171 /* Now erase the righthand corner and draw diagonal edge */
2172 SetBkColor(hdc, corner);
2173 r1.left = r.right - ROUND_CORNER_SIZE;
2174 r1.bottom = r.bottom;
2175 r1.right = r.right;
2176 r1.top = r1.bottom - ROUND_CORNER_SIZE - 1;
2177 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2178 r1.bottom--;
2179 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMLEFT);
2181 /* Now erase the lefthand corner and draw diagonal edge */
2182 r1.left = r.left;
2183 r1.bottom = r.bottom;
2184 r1.right = r1.left + ROUND_CORNER_SIZE;
2185 r1.top = r1.bottom - ROUND_CORNER_SIZE - 1;
2186 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2187 r1.bottom--;
2188 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPLEFT);
2190 if (iItem == infoPtr->iSelected)
2192 r.top += 2;
2193 r.left += 1;
2194 if (selectedRect.left == 0)
2196 r1 = r;
2197 r1.bottom = r1.top;
2198 r1.top--;
2199 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_LEFT);
2204 else
2206 /* Adjust both rectangles for bottommost row */
2207 if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2209 fillRect.bottom += 3;
2210 r.bottom += 2;
2213 TRACE("<top> item=%d, fill=(%d,%d)-(%d,%d), edge=(%d,%d)-(%d,%d)\n",
2214 iItem,
2215 fillRect.left,fillRect.top,fillRect.right,fillRect.bottom,
2216 r.left,r.top,r.right,r.bottom);
2218 /* Clear interior */
2219 SetBkColor(hdc, bkgnd);
2220 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2222 /* Draw rectangular edge around tab */
2223 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_TOP|BF_RIGHT);
2225 /* Now erase the righthand corner and draw diagonal edge */
2226 SetBkColor(hdc, corner);
2227 r1.left = r.right - ROUND_CORNER_SIZE;
2228 r1.top = r.top;
2229 r1.right = r.right;
2230 r1.bottom = r1.top + ROUND_CORNER_SIZE + 1;
2231 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2232 r1.top++;
2233 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMRIGHT);
2235 /* Now erase the lefthand corner and draw diagonal edge */
2236 r1.left = r.left;
2237 r1.top = r.top;
2238 r1.right = r1.left + ROUND_CORNER_SIZE;
2239 r1.bottom = r1.top + ROUND_CORNER_SIZE + 1;
2240 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2241 r1.top++;
2242 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPRIGHT);
2247 TAB_DumpItemInternal(infoPtr, iItem);
2249 /* This modifies r to be the text rectangle. */
2250 TAB_DrawItemInterior(infoPtr, hdc, iItem, &r);
2254 /******************************************************************************
2255 * TAB_DrawBorder
2257 * This method is used to draw the raised border around the tab control
2258 * "content" area.
2260 static void TAB_DrawBorder(const TAB_INFO *infoPtr, HDC hdc)
2262 RECT rect;
2263 DWORD lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
2264 HTHEME theme = GetWindowTheme (infoPtr->hwnd);
2266 GetClientRect (infoPtr->hwnd, &rect);
2269 * Adjust for the style
2272 if (infoPtr->uNumItem)
2274 if ((lStyle & TCS_BOTTOM) && !(lStyle & TCS_VERTICAL))
2275 rect.bottom -= infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2276 else if((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
2277 rect.right -= infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2278 else if(lStyle & TCS_VERTICAL)
2279 rect.left += infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2280 else /* not TCS_VERTICAL and not TCS_BOTTOM */
2281 rect.top += infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2284 TRACE("border=(%d,%d)-(%d,%d)\n",
2285 rect.left, rect.top, rect.right, rect.bottom);
2287 if (theme)
2288 DrawThemeBackground (theme, hdc, TABP_PANE, 0, &rect, NULL);
2289 else
2290 DrawEdge(hdc, &rect, EDGE_RAISED, BF_SOFT|BF_RECT);
2293 /******************************************************************************
2294 * TAB_Refresh
2296 * This method repaints the tab control..
2298 static void TAB_Refresh (TAB_INFO *infoPtr, HDC hdc)
2300 HFONT hOldFont;
2301 INT i;
2303 if (!infoPtr->DoRedraw)
2304 return;
2306 hOldFont = SelectObject (hdc, infoPtr->hFont);
2308 if (GetWindowLongW(infoPtr->hwnd, GWL_STYLE) & TCS_BUTTONS)
2310 for (i = 0; i < infoPtr->uNumItem; i++)
2311 TAB_DrawItem (infoPtr, hdc, i);
2313 else
2315 /* Draw all the non selected item first */
2316 for (i = 0; i < infoPtr->uNumItem; i++)
2318 if (i != infoPtr->iSelected)
2319 TAB_DrawItem (infoPtr, hdc, i);
2322 /* Now, draw the border, draw it before the selected item
2323 * since the selected item overwrites part of the border. */
2324 TAB_DrawBorder (infoPtr, hdc);
2326 /* Then, draw the selected item */
2327 TAB_DrawItem (infoPtr, hdc, infoPtr->iSelected);
2329 /* If we haven't set the current focus yet, set it now.
2330 * Only happens when we first paint the tab controls */
2331 if (infoPtr->uFocus == -1)
2332 TAB_SetCurFocus(infoPtr, infoPtr->iSelected);
2335 SelectObject (hdc, hOldFont);
2338 static inline DWORD TAB_GetRowCount (const TAB_INFO *infoPtr)
2340 return infoPtr->uNumRows;
2343 static inline LRESULT TAB_SetRedraw (TAB_INFO *infoPtr, BOOL doRedraw)
2345 infoPtr->DoRedraw = doRedraw;
2346 return 0;
2349 /******************************************************************************
2350 * TAB_EnsureSelectionVisible
2352 * This method will make sure that the current selection is completely
2353 * visible by scrolling until it is.
2355 static void TAB_EnsureSelectionVisible(
2356 TAB_INFO* infoPtr)
2358 INT iSelected = infoPtr->iSelected;
2359 LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
2360 INT iOrigLeftmostVisible = infoPtr->leftmostVisible;
2362 /* set the items row to the bottommost row or topmost row depending on
2363 * style */
2364 if ((infoPtr->uNumRows > 1) && !(lStyle & TCS_BUTTONS))
2366 TAB_ITEM *selected = TAB_GetItem(infoPtr, iSelected);
2367 INT newselected;
2368 INT iTargetRow;
2370 if(lStyle & TCS_VERTICAL)
2371 newselected = selected->rect.left;
2372 else
2373 newselected = selected->rect.top;
2375 /* the target row is always (number of rows - 1)
2376 as row 0 is furthest from the clientRect */
2377 iTargetRow = infoPtr->uNumRows - 1;
2379 if (newselected != iTargetRow)
2381 UINT i;
2382 if(lStyle & TCS_VERTICAL)
2384 for (i=0; i < infoPtr->uNumItem; i++)
2386 /* move everything in the row of the selected item to the iTargetRow */
2387 TAB_ITEM *item = TAB_GetItem(infoPtr, i);
2389 if (item->rect.left == newselected )
2390 item->rect.left = iTargetRow;
2391 else
2393 if (item->rect.left > newselected)
2394 item->rect.left-=1;
2398 else
2400 for (i=0; i < infoPtr->uNumItem; i++)
2402 TAB_ITEM *item = TAB_GetItem(infoPtr, i);
2404 if (item->rect.top == newselected )
2405 item->rect.top = iTargetRow;
2406 else
2408 if (item->rect.top > newselected)
2409 item->rect.top-=1;
2413 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
2418 * Do the trivial cases first.
2420 if ( (!infoPtr->needsScrolling) ||
2421 (infoPtr->hwndUpDown==0) || (lStyle & TCS_VERTICAL))
2422 return;
2424 if (infoPtr->leftmostVisible >= iSelected)
2426 infoPtr->leftmostVisible = iSelected;
2428 else
2430 TAB_ITEM *selected = TAB_GetItem(infoPtr, iSelected);
2431 RECT r;
2432 INT width;
2433 UINT i;
2435 /* Calculate the part of the client area that is visible */
2436 GetClientRect(infoPtr->hwnd, &r);
2437 width = r.right;
2439 GetClientRect(infoPtr->hwndUpDown, &r);
2440 width -= r.right;
2442 if ((selected->rect.right -
2443 selected->rect.left) >= width )
2445 /* Special case: width of selected item is greater than visible
2446 * part of control.
2448 infoPtr->leftmostVisible = iSelected;
2450 else
2452 for (i = infoPtr->leftmostVisible; i < infoPtr->uNumItem; i++)
2454 if ((selected->rect.right - TAB_GetItem(infoPtr, i)->rect.left) < width)
2455 break;
2457 infoPtr->leftmostVisible = i;
2461 if (infoPtr->leftmostVisible != iOrigLeftmostVisible)
2462 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
2464 SendMessageW(infoPtr->hwndUpDown, UDM_SETPOS, 0,
2465 MAKELONG(infoPtr->leftmostVisible, 0));
2468 /******************************************************************************
2469 * TAB_InvalidateTabArea
2471 * This method will invalidate the portion of the control that contains the
2472 * tabs. It is called when the state of the control changes and needs
2473 * to be redisplayed
2475 static void TAB_InvalidateTabArea(const TAB_INFO *infoPtr)
2477 RECT clientRect, rInvalidate, rAdjClient;
2478 DWORD lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
2479 INT lastRow = infoPtr->uNumRows - 1;
2480 RECT rect;
2482 if (lastRow < 0) return;
2484 GetClientRect(infoPtr->hwnd, &clientRect);
2485 rInvalidate = clientRect;
2486 rAdjClient = clientRect;
2488 TAB_AdjustRect(infoPtr, 0, &rAdjClient);
2490 TAB_InternalGetItemRect(infoPtr, infoPtr->uNumItem-1 , &rect, NULL);
2491 if ((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
2493 rInvalidate.left = rAdjClient.right;
2494 if (infoPtr->uNumRows == 1)
2495 rInvalidate.bottom = clientRect.top + rect.bottom + 2 * SELECTED_TAB_OFFSET;
2497 else if(lStyle & TCS_VERTICAL)
2499 rInvalidate.right = rAdjClient.left;
2500 if (infoPtr->uNumRows == 1)
2501 rInvalidate.bottom = clientRect.top + rect.bottom + 2 * SELECTED_TAB_OFFSET;
2503 else if (lStyle & TCS_BOTTOM)
2505 rInvalidate.top = rAdjClient.bottom;
2506 if (infoPtr->uNumRows == 1)
2507 rInvalidate.right = clientRect.left + rect.right + 2 * SELECTED_TAB_OFFSET;
2509 else
2511 rInvalidate.bottom = rAdjClient.top;
2512 if (infoPtr->uNumRows == 1)
2513 rInvalidate.right = clientRect.left + rect.right + 2 * SELECTED_TAB_OFFSET;
2516 /* Punch out the updown control */
2517 if (infoPtr->needsScrolling && (rInvalidate.right > 0)) {
2518 RECT r;
2519 GetClientRect(infoPtr->hwndUpDown, &r);
2520 if (rInvalidate.right > clientRect.right - r.left)
2521 rInvalidate.right = rInvalidate.right - (r.right - r.left);
2522 else
2523 rInvalidate.right = clientRect.right - r.left;
2526 TRACE("invalidate (%d,%d)-(%d,%d)\n",
2527 rInvalidate.left, rInvalidate.top,
2528 rInvalidate.right, rInvalidate.bottom);
2530 InvalidateRect(infoPtr->hwnd, &rInvalidate, TRUE);
2533 static inline LRESULT TAB_Paint (TAB_INFO *infoPtr, HDC hdcPaint)
2535 HDC hdc;
2536 PAINTSTRUCT ps;
2538 if (hdcPaint)
2539 hdc = hdcPaint;
2540 else
2542 hdc = BeginPaint (infoPtr->hwnd, &ps);
2543 TRACE("erase %d, rect=(%d,%d)-(%d,%d)\n",
2544 ps.fErase,
2545 ps.rcPaint.left,ps.rcPaint.top,ps.rcPaint.right,ps.rcPaint.bottom);
2548 TAB_Refresh (infoPtr, hdc);
2550 if (!hdcPaint)
2551 EndPaint (infoPtr->hwnd, &ps);
2553 return 0;
2556 static LRESULT
2557 TAB_InsertItemT (TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam, BOOL bUnicode)
2559 TAB_ITEM *item;
2560 TCITEMW *pti;
2561 INT iItem;
2562 RECT rect;
2564 GetClientRect (infoPtr->hwnd, &rect);
2565 TRACE("Rect: %p T %i, L %i, B %i, R %i\n", infoPtr->hwnd,
2566 rect.top, rect.left, rect.bottom, rect.right);
2568 pti = (TCITEMW *)lParam;
2569 iItem = (INT)wParam;
2571 if (iItem < 0) return -1;
2572 if (iItem > infoPtr->uNumItem)
2573 iItem = infoPtr->uNumItem;
2575 TAB_DumpItemExternalT(pti, iItem, bUnicode);
2578 if (infoPtr->uNumItem == 0) {
2579 infoPtr->items = Alloc (TAB_ITEM_SIZE(infoPtr));
2580 infoPtr->uNumItem++;
2581 infoPtr->iSelected = 0;
2583 else {
2584 LPBYTE oldItems = (LPBYTE)infoPtr->items;
2586 infoPtr->uNumItem++;
2587 infoPtr->items = Alloc (TAB_ITEM_SIZE(infoPtr) * infoPtr->uNumItem);
2589 /* pre insert copy */
2590 if (iItem > 0) {
2591 memcpy (infoPtr->items, oldItems,
2592 iItem * TAB_ITEM_SIZE(infoPtr));
2595 /* post insert copy */
2596 if (iItem < infoPtr->uNumItem - 1) {
2597 memcpy (TAB_GetItem(infoPtr, iItem + 1),
2598 oldItems + iItem * TAB_ITEM_SIZE(infoPtr),
2599 (infoPtr->uNumItem - iItem - 1) * TAB_ITEM_SIZE(infoPtr));
2603 if (iItem <= infoPtr->iSelected)
2604 infoPtr->iSelected++;
2606 Free (oldItems);
2609 item = TAB_GetItem(infoPtr, iItem);
2611 item->pszText = NULL;
2613 if (pti->mask & TCIF_TEXT)
2615 if (bUnicode)
2616 Str_SetPtrW (&item->pszText, pti->pszText);
2617 else
2618 Str_SetPtrAtoW (&item->pszText, (LPSTR)pti->pszText);
2621 if (pti->mask & TCIF_IMAGE)
2622 item->iImage = pti->iImage;
2623 else
2624 item->iImage = -1;
2626 if (pti->mask & TCIF_PARAM)
2627 memcpy(item->extra, &pti->lParam, infoPtr->cbInfo);
2628 else
2629 memset(item->extra, 0, infoPtr->cbInfo);
2631 TAB_SetItemBounds(infoPtr);
2632 if (infoPtr->uNumItem > 1)
2633 TAB_InvalidateTabArea(infoPtr);
2634 else
2635 InvalidateRect(infoPtr->hwnd, NULL, TRUE);
2637 TRACE("[%p]: added item %d %s\n",
2638 infoPtr->hwnd, iItem, debugstr_w(item->pszText));
2640 return iItem;
2643 static LRESULT
2644 TAB_SetItemSize (TAB_INFO *infoPtr, LPARAM lParam)
2646 LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
2647 LONG lResult = 0;
2648 BOOL bNeedPaint = FALSE;
2650 lResult = MAKELONG(infoPtr->tabWidth, infoPtr->tabHeight);
2652 /* UNDOCUMENTED: If requested Width or Height is 0 this means that program wants to use auto size. */
2653 if (lStyle & TCS_FIXEDWIDTH && (infoPtr->tabWidth != (INT)LOWORD(lParam)))
2655 infoPtr->tabWidth = (INT)LOWORD(lParam);
2656 bNeedPaint = TRUE;
2659 if (infoPtr->tabHeight != (INT)HIWORD(lParam))
2661 if ((infoPtr->fHeightSet = ((INT)HIWORD(lParam) != 0)))
2662 infoPtr->tabHeight = (INT)HIWORD(lParam);
2664 bNeedPaint = TRUE;
2666 TRACE("was h=%d,w=%d, now h=%d,w=%d\n",
2667 HIWORD(lResult), LOWORD(lResult),
2668 infoPtr->tabHeight, infoPtr->tabWidth);
2670 if (bNeedPaint)
2672 TAB_SetItemBounds(infoPtr);
2673 RedrawWindow(infoPtr->hwnd, NULL, NULL, RDW_ERASE | RDW_INVALIDATE | RDW_UPDATENOW);
2676 return lResult;
2679 static inline LRESULT TAB_SetMinTabWidth (TAB_INFO *infoPtr, INT cx)
2681 INT oldcx = 0;
2683 TRACE("(%p,%d)\n", infoPtr, cx);
2685 oldcx = infoPtr->tabMinWidth;
2686 infoPtr->tabMinWidth = cx;
2687 TAB_SetItemBounds(infoPtr);
2688 return oldcx;
2691 static inline LRESULT
2692 TAB_HighlightItem (TAB_INFO *infoPtr, INT iItem, BOOL fHighlight)
2694 LPDWORD lpState;
2696 TRACE("(%p,%d,%s)\n", infoPtr, iItem, fHighlight ? "true" : "false");
2698 if (!infoPtr || iItem < 0 || iItem >= infoPtr->uNumItem)
2699 return FALSE;
2701 lpState = &TAB_GetItem(infoPtr, iItem)->dwState;
2703 if (fHighlight)
2704 *lpState |= TCIS_HIGHLIGHTED;
2705 else
2706 *lpState &= ~TCIS_HIGHLIGHTED;
2708 return TRUE;
2711 static LRESULT
2712 TAB_SetItemT (TAB_INFO *infoPtr, INT iItem, LPTCITEMW tabItem, BOOL bUnicode)
2714 TAB_ITEM *wineItem;
2716 TRACE("(%p,%d,%p,%s)\n", infoPtr, iItem, tabItem, bUnicode ? "true" : "false");
2718 if (iItem < 0 || iItem >= infoPtr->uNumItem)
2719 return FALSE;
2721 TAB_DumpItemExternalT(tabItem, iItem, bUnicode);
2723 wineItem = TAB_GetItem(infoPtr, iItem);
2725 if (tabItem->mask & TCIF_IMAGE)
2726 wineItem->iImage = tabItem->iImage;
2728 if (tabItem->mask & TCIF_PARAM)
2729 memcpy(wineItem->extra, &tabItem->lParam, infoPtr->cbInfo);
2731 if (tabItem->mask & TCIF_RTLREADING)
2732 FIXME("TCIF_RTLREADING\n");
2734 if (tabItem->mask & TCIF_STATE)
2735 wineItem->dwState = tabItem->dwState;
2737 if (tabItem->mask & TCIF_TEXT)
2739 Free(wineItem->pszText);
2740 wineItem->pszText = NULL;
2741 if (bUnicode)
2742 Str_SetPtrW(&wineItem->pszText, tabItem->pszText);
2743 else
2744 Str_SetPtrAtoW(&wineItem->pszText, (LPSTR)tabItem->pszText);
2747 /* Update and repaint tabs */
2748 TAB_SetItemBounds(infoPtr);
2749 TAB_InvalidateTabArea(infoPtr);
2751 return TRUE;
2754 static inline LRESULT TAB_GetItemCount (const TAB_INFO *infoPtr)
2756 return infoPtr->uNumItem;
2760 static LRESULT
2761 TAB_GetItemT (TAB_INFO *infoPtr, INT iItem, LPTCITEMW tabItem, BOOL bUnicode)
2763 TAB_ITEM *wineItem;
2765 TRACE("(%p,%d,%p,%s)\n", infoPtr, iItem, tabItem, bUnicode ? "true" : "false");
2767 if (iItem < 0 || iItem >= infoPtr->uNumItem)
2768 return FALSE;
2770 wineItem = TAB_GetItem(infoPtr, iItem);
2772 if (tabItem->mask & TCIF_IMAGE)
2773 tabItem->iImage = wineItem->iImage;
2775 if (tabItem->mask & TCIF_PARAM)
2776 memcpy(&tabItem->lParam, wineItem->extra, infoPtr->cbInfo);
2778 if (tabItem->mask & TCIF_RTLREADING)
2779 FIXME("TCIF_RTLREADING\n");
2781 if (tabItem->mask & TCIF_STATE)
2782 tabItem->dwState = wineItem->dwState;
2784 if (tabItem->mask & TCIF_TEXT)
2786 if (bUnicode)
2787 Str_GetPtrW (wineItem->pszText, tabItem->pszText, tabItem->cchTextMax);
2788 else
2789 Str_GetPtrWtoA (wineItem->pszText, (LPSTR)tabItem->pszText, tabItem->cchTextMax);
2792 TAB_DumpItemExternalT(tabItem, iItem, bUnicode);
2794 return TRUE;
2798 static LRESULT TAB_DeleteItem (TAB_INFO *infoPtr, INT iItem)
2800 BOOL bResult = FALSE;
2802 TRACE("(%p, %d)\n", infoPtr, iItem);
2804 if ((iItem >= 0) && (iItem < infoPtr->uNumItem))
2806 TAB_ITEM *item = TAB_GetItem(infoPtr, iItem);
2807 LPBYTE oldItems = (LPBYTE)infoPtr->items;
2809 TAB_InvalidateTabArea(infoPtr);
2810 Free(item->pszText);
2811 infoPtr->uNumItem--;
2813 if (!infoPtr->uNumItem)
2815 infoPtr->items = NULL;
2816 if (infoPtr->iHotTracked >= 0)
2818 KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
2819 infoPtr->iHotTracked = -1;
2822 else
2824 infoPtr->items = Alloc(TAB_ITEM_SIZE(infoPtr) * infoPtr->uNumItem);
2826 if (iItem > 0)
2827 memcpy(infoPtr->items, oldItems, iItem * TAB_ITEM_SIZE(infoPtr));
2829 if (iItem < infoPtr->uNumItem)
2830 memcpy(TAB_GetItem(infoPtr, iItem),
2831 oldItems + (iItem + 1) * TAB_ITEM_SIZE(infoPtr),
2832 (infoPtr->uNumItem - iItem) * TAB_ITEM_SIZE(infoPtr));
2834 if (iItem <= infoPtr->iHotTracked)
2836 /* When tabs move left/up, the hot track item may change */
2837 FIXME("Recalc hot track\n");
2840 Free(oldItems);
2842 /* Readjust the selected index */
2843 if ((iItem == infoPtr->iSelected) && (iItem > 0))
2844 infoPtr->iSelected--;
2846 if (iItem < infoPtr->iSelected)
2847 infoPtr->iSelected--;
2849 if (infoPtr->uNumItem == 0)
2850 infoPtr->iSelected = -1;
2852 /* Reposition and repaint tabs */
2853 TAB_SetItemBounds(infoPtr);
2855 bResult = TRUE;
2858 return bResult;
2861 static inline LRESULT TAB_DeleteAllItems (TAB_INFO *infoPtr)
2863 TRACE("(%p)\n", infoPtr);
2864 while (infoPtr->uNumItem)
2865 TAB_DeleteItem (infoPtr, 0);
2866 return TRUE;
2870 static inline LRESULT TAB_GetFont (const TAB_INFO *infoPtr)
2872 TRACE("(%p) returning %p\n", infoPtr, infoPtr->hFont);
2873 return (LRESULT)infoPtr->hFont;
2876 static inline LRESULT TAB_SetFont (TAB_INFO *infoPtr, HFONT hNewFont)
2878 TRACE("(%p,%p)\n", infoPtr, hNewFont);
2880 infoPtr->hFont = hNewFont;
2882 TAB_SetItemBounds(infoPtr);
2884 TAB_InvalidateTabArea(infoPtr);
2886 return 0;
2890 static inline LRESULT TAB_GetImageList (const TAB_INFO *infoPtr)
2892 TRACE("\n");
2893 return (LRESULT)infoPtr->himl;
2896 static inline LRESULT TAB_SetImageList (TAB_INFO *infoPtr, HIMAGELIST himlNew)
2898 HIMAGELIST himlPrev = infoPtr->himl;
2899 TRACE("\n");
2900 infoPtr->himl = himlNew;
2901 TAB_SetItemBounds(infoPtr);
2902 InvalidateRect(infoPtr->hwnd, NULL, TRUE);
2903 return (LRESULT)himlPrev;
2906 static inline LRESULT TAB_GetUnicodeFormat (const TAB_INFO *infoPtr)
2908 return infoPtr->bUnicode;
2911 static inline LRESULT TAB_SetUnicodeFormat (TAB_INFO *infoPtr, BOOL bUnicode)
2913 BOOL bTemp = infoPtr->bUnicode;
2915 infoPtr->bUnicode = bUnicode;
2917 return bTemp;
2920 static inline LRESULT TAB_Size (TAB_INFO *infoPtr)
2922 /* I'm not really sure what the following code was meant to do.
2923 This is what it is doing:
2924 When WM_SIZE is sent with SIZE_RESTORED, the control
2925 gets positioned in the top left corner.
2927 RECT parent_rect;
2928 HWND parent;
2929 UINT uPosFlags,cx,cy;
2931 uPosFlags=0;
2932 if (!wParam) {
2933 parent = GetParent (hwnd);
2934 GetClientRect(parent, &parent_rect);
2935 cx=LOWORD (lParam);
2936 cy=HIWORD (lParam);
2937 if (GetWindowLongW(hwnd, GWL_STYLE) & CCS_NORESIZE)
2938 uPosFlags |= (SWP_NOSIZE | SWP_NOMOVE);
2940 SetWindowPos (hwnd, 0, parent_rect.left, parent_rect.top,
2941 cx, cy, uPosFlags | SWP_NOZORDER);
2942 } else {
2943 FIXME("WM_SIZE flag %x %lx not handled\n", wParam, lParam);
2944 } */
2946 /* Recompute the size/position of the tabs. */
2947 TAB_SetItemBounds (infoPtr);
2949 /* Force a repaint of the control. */
2950 InvalidateRect(infoPtr->hwnd, NULL, TRUE);
2952 return 0;
2956 static LRESULT TAB_Create (HWND hwnd, WPARAM wParam, LPARAM lParam)
2958 TAB_INFO *infoPtr;
2959 TEXTMETRICW fontMetrics;
2960 HDC hdc;
2961 HFONT hOldFont;
2962 DWORD dwStyle;
2964 infoPtr = (TAB_INFO *)Alloc (sizeof(TAB_INFO));
2966 SetWindowLongPtrW(hwnd, 0, (DWORD_PTR)infoPtr);
2968 infoPtr->hwnd = hwnd;
2969 infoPtr->hwndNotify = ((LPCREATESTRUCTW)lParam)->hwndParent;
2970 infoPtr->uNumItem = 0;
2971 infoPtr->uNumRows = 0;
2972 infoPtr->uHItemPadding = 6;
2973 infoPtr->uVItemPadding = 3;
2974 infoPtr->uHItemPadding_s = 6;
2975 infoPtr->uVItemPadding_s = 3;
2976 infoPtr->hFont = 0;
2977 infoPtr->items = 0;
2978 infoPtr->hcurArrow = LoadCursorW (0, (LPWSTR)IDC_ARROW);
2979 infoPtr->iSelected = -1;
2980 infoPtr->iHotTracked = -1;
2981 infoPtr->uFocus = -1;
2982 infoPtr->hwndToolTip = 0;
2983 infoPtr->DoRedraw = TRUE;
2984 infoPtr->needsScrolling = FALSE;
2985 infoPtr->hwndUpDown = 0;
2986 infoPtr->leftmostVisible = 0;
2987 infoPtr->fHeightSet = FALSE;
2988 infoPtr->bUnicode = IsWindowUnicode (hwnd);
2989 infoPtr->cbInfo = sizeof(LPARAM);
2991 TRACE("Created tab control, hwnd [%p]\n", hwnd);
2993 /* The tab control always has the WS_CLIPSIBLINGS style. Even
2994 if you don't specify it in CreateWindow. This is necessary in
2995 order for paint to work correctly. This follows windows behaviour. */
2996 dwStyle = GetWindowLongW(hwnd, GWL_STYLE);
2997 SetWindowLongW(hwnd, GWL_STYLE, dwStyle|WS_CLIPSIBLINGS);
2999 if (dwStyle & TCS_TOOLTIPS) {
3000 /* Create tooltip control */
3001 infoPtr->hwndToolTip =
3002 CreateWindowExW (0, TOOLTIPS_CLASSW, NULL, WS_POPUP,
3003 CW_USEDEFAULT, CW_USEDEFAULT,
3004 CW_USEDEFAULT, CW_USEDEFAULT,
3005 hwnd, 0, 0, 0);
3007 /* Send NM_TOOLTIPSCREATED notification */
3008 if (infoPtr->hwndToolTip) {
3009 NMTOOLTIPSCREATED nmttc;
3011 nmttc.hdr.hwndFrom = hwnd;
3012 nmttc.hdr.idFrom = GetWindowLongPtrW(hwnd, GWLP_ID);
3013 nmttc.hdr.code = NM_TOOLTIPSCREATED;
3014 nmttc.hwndToolTips = infoPtr->hwndToolTip;
3016 SendMessageW (infoPtr->hwndNotify, WM_NOTIFY,
3017 (WPARAM)GetWindowLongPtrW(hwnd, GWLP_ID), (LPARAM)&nmttc);
3021 OpenThemeData (infoPtr->hwnd, themeClass);
3024 * We need to get text information so we need a DC and we need to select
3025 * a font.
3027 hdc = GetDC(hwnd);
3028 hOldFont = SelectObject (hdc, GetStockObject (SYSTEM_FONT));
3030 /* Use the system font to determine the initial height of a tab. */
3031 GetTextMetricsW(hdc, &fontMetrics);
3034 * Make sure there is enough space for the letters + growing the
3035 * selected item + extra space for the selected item.
3037 infoPtr->tabHeight = fontMetrics.tmHeight + SELECTED_TAB_OFFSET +
3038 ((dwStyle & TCS_BUTTONS) ? 2 : 1) *
3039 infoPtr->uVItemPadding;
3041 /* Initialize the width of a tab. */
3042 if (dwStyle & TCS_FIXEDWIDTH)
3043 infoPtr->tabWidth = DEFAULT_TAB_WIDTH_FIXED;
3045 infoPtr->tabMinWidth = -1;
3047 TRACE("tabH=%d, tabW=%d\n", infoPtr->tabHeight, infoPtr->tabWidth);
3049 SelectObject (hdc, hOldFont);
3050 ReleaseDC(hwnd, hdc);
3052 return 0;
3055 static LRESULT
3056 TAB_Destroy (TAB_INFO *infoPtr)
3058 UINT iItem;
3060 if (!infoPtr)
3061 return 0;
3063 SetWindowLongPtrW(infoPtr->hwnd, 0, 0);
3065 if (infoPtr->items) {
3066 for (iItem = 0; iItem < infoPtr->uNumItem; iItem++) {
3067 if (TAB_GetItem(infoPtr, iItem)->pszText)
3068 Free (TAB_GetItem(infoPtr, iItem)->pszText);
3070 Free (infoPtr->items);
3073 if (infoPtr->hwndToolTip)
3074 DestroyWindow (infoPtr->hwndToolTip);
3076 if (infoPtr->hwndUpDown)
3077 DestroyWindow(infoPtr->hwndUpDown);
3079 if (infoPtr->iHotTracked >= 0)
3080 KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
3082 CloseThemeData (GetWindowTheme (infoPtr->hwnd));
3084 Free (infoPtr);
3085 return 0;
3088 /* update theme after a WM_THEMECHANGED message */
3089 static LRESULT theme_changed(const TAB_INFO *infoPtr)
3091 HTHEME theme = GetWindowTheme (infoPtr->hwnd);
3092 CloseThemeData (theme);
3093 OpenThemeData (infoPtr->hwnd, themeClass);
3094 return 0;
3097 static LRESULT TAB_NCCalcSize(HWND hwnd, WPARAM wParam, LPARAM lParam)
3099 if (!wParam)
3100 return 0;
3101 return WVR_ALIGNTOP;
3104 static inline LRESULT
3105 TAB_SetItemExtra (TAB_INFO *infoPtr, INT cbInfo)
3107 if (!infoPtr || cbInfo <= 0)
3108 return FALSE;
3110 if (infoPtr->uNumItem)
3112 /* FIXME: MSDN says this is not allowed, but this hasn't been verified */
3113 return FALSE;
3116 infoPtr->cbInfo = cbInfo;
3117 return TRUE;
3120 static LRESULT WINAPI
3121 TAB_WindowProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
3123 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
3125 TRACE("hwnd=%p msg=%x wParam=%lx lParam=%lx\n", hwnd, uMsg, wParam, lParam);
3126 if (!infoPtr && (uMsg != WM_CREATE))
3127 return DefWindowProcW (hwnd, uMsg, wParam, lParam);
3129 switch (uMsg)
3131 case TCM_GETIMAGELIST:
3132 return TAB_GetImageList (infoPtr);
3134 case TCM_SETIMAGELIST:
3135 return TAB_SetImageList (infoPtr, (HIMAGELIST)lParam);
3137 case TCM_GETITEMCOUNT:
3138 return TAB_GetItemCount (infoPtr);
3140 case TCM_GETITEMA:
3141 case TCM_GETITEMW:
3142 return TAB_GetItemT (infoPtr, (INT)wParam, (LPTCITEMW)lParam, uMsg == TCM_GETITEMW);
3144 case TCM_SETITEMA:
3145 case TCM_SETITEMW:
3146 return TAB_SetItemT (infoPtr, (INT)wParam, (LPTCITEMW)lParam, uMsg == TCM_SETITEMW);
3148 case TCM_DELETEITEM:
3149 return TAB_DeleteItem (infoPtr, (INT)wParam);
3151 case TCM_DELETEALLITEMS:
3152 return TAB_DeleteAllItems (infoPtr);
3154 case TCM_GETITEMRECT:
3155 return TAB_GetItemRect (infoPtr, wParam, lParam);
3157 case TCM_GETCURSEL:
3158 return TAB_GetCurSel (infoPtr);
3160 case TCM_HITTEST:
3161 return TAB_HitTest (infoPtr, (LPTCHITTESTINFO)lParam);
3163 case TCM_SETCURSEL:
3164 return TAB_SetCurSel (infoPtr, (INT)wParam);
3166 case TCM_INSERTITEMA:
3167 case TCM_INSERTITEMW:
3168 return TAB_InsertItemT (infoPtr, wParam, lParam, uMsg == TCM_INSERTITEMW);
3170 case TCM_SETITEMEXTRA:
3171 return TAB_SetItemExtra (infoPtr, (int)wParam);
3173 case TCM_ADJUSTRECT:
3174 return TAB_AdjustRect (infoPtr, (BOOL)wParam, (LPRECT)lParam);
3176 case TCM_SETITEMSIZE:
3177 return TAB_SetItemSize (infoPtr, lParam);
3179 case TCM_REMOVEIMAGE:
3180 FIXME("Unimplemented msg TCM_REMOVEIMAGE\n");
3181 return 0;
3183 case TCM_SETPADDING:
3184 return TAB_SetPadding (infoPtr, lParam);
3186 case TCM_GETROWCOUNT:
3187 return TAB_GetRowCount(infoPtr);
3189 case TCM_GETUNICODEFORMAT:
3190 return TAB_GetUnicodeFormat (infoPtr);
3192 case TCM_SETUNICODEFORMAT:
3193 return TAB_SetUnicodeFormat (infoPtr, (BOOL)wParam);
3195 case TCM_HIGHLIGHTITEM:
3196 return TAB_HighlightItem (infoPtr, (INT)wParam, (BOOL)LOWORD(lParam));
3198 case TCM_GETTOOLTIPS:
3199 return TAB_GetToolTips (infoPtr);
3201 case TCM_SETTOOLTIPS:
3202 return TAB_SetToolTips (infoPtr, (HWND)wParam);
3204 case TCM_GETCURFOCUS:
3205 return TAB_GetCurFocus (infoPtr);
3207 case TCM_SETCURFOCUS:
3208 return TAB_SetCurFocus (infoPtr, (INT)wParam);
3210 case TCM_SETMINTABWIDTH:
3211 return TAB_SetMinTabWidth(infoPtr, (INT)lParam);
3213 case TCM_DESELECTALL:
3214 FIXME("Unimplemented msg TCM_DESELECTALL\n");
3215 return 0;
3217 case TCM_GETEXTENDEDSTYLE:
3218 FIXME("Unimplemented msg TCM_GETEXTENDEDSTYLE\n");
3219 return 0;
3221 case TCM_SETEXTENDEDSTYLE:
3222 FIXME("Unimplemented msg TCM_SETEXTENDEDSTYLE\n");
3223 return 0;
3225 case WM_GETFONT:
3226 return TAB_GetFont (infoPtr);
3228 case WM_SETFONT:
3229 return TAB_SetFont (infoPtr, (HFONT)wParam);
3231 case WM_CREATE:
3232 return TAB_Create (hwnd, wParam, lParam);
3234 case WM_NCDESTROY:
3235 return TAB_Destroy (infoPtr);
3237 case WM_GETDLGCODE:
3238 return DLGC_WANTARROWS | DLGC_WANTCHARS;
3240 case WM_LBUTTONDOWN:
3241 return TAB_LButtonDown (infoPtr, wParam, lParam);
3243 case WM_LBUTTONUP:
3244 return TAB_LButtonUp (infoPtr);
3246 case WM_NOTIFY:
3247 return SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, wParam, lParam);
3249 case WM_RBUTTONDOWN:
3250 return TAB_RButtonDown (infoPtr);
3252 case WM_MOUSEMOVE:
3253 return TAB_MouseMove (infoPtr, wParam, lParam);
3255 case WM_PRINTCLIENT:
3256 case WM_PAINT:
3257 return TAB_Paint (infoPtr, (HDC)wParam);
3259 case WM_SIZE:
3260 return TAB_Size (infoPtr);
3262 case WM_SETREDRAW:
3263 return TAB_SetRedraw (infoPtr, (BOOL)wParam);
3265 case WM_HSCROLL:
3266 return TAB_OnHScroll(infoPtr, (int)LOWORD(wParam), (int)HIWORD(wParam), (HWND)lParam);
3268 case WM_STYLECHANGED:
3269 TAB_SetItemBounds (infoPtr);
3270 InvalidateRect(hwnd, NULL, TRUE);
3271 return 0;
3273 case WM_SYSCOLORCHANGE:
3274 COMCTL32_RefreshSysColors();
3275 return 0;
3277 case WM_THEMECHANGED:
3278 return theme_changed (infoPtr);
3280 case WM_KILLFOCUS:
3281 case WM_SETFOCUS:
3282 TAB_FocusChanging(infoPtr);
3283 break; /* Don't disturb normal focus behavior */
3285 case WM_KEYUP:
3286 return TAB_KeyUp(infoPtr, wParam);
3287 case WM_NCHITTEST:
3288 return TAB_NCHitTest(infoPtr, lParam);
3290 case WM_NCCALCSIZE:
3291 return TAB_NCCalcSize(hwnd, wParam, lParam);
3293 default:
3294 if (uMsg >= WM_USER && uMsg < WM_APP)
3295 WARN("unknown msg %04x wp=%08lx lp=%08lx\n",
3296 uMsg, wParam, lParam);
3297 break;
3299 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
3303 void
3304 TAB_Register (void)
3306 WNDCLASSW wndClass;
3308 ZeroMemory (&wndClass, sizeof(WNDCLASSW));
3309 wndClass.style = CS_GLOBALCLASS | CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
3310 wndClass.lpfnWndProc = TAB_WindowProc;
3311 wndClass.cbClsExtra = 0;
3312 wndClass.cbWndExtra = sizeof(TAB_INFO *);
3313 wndClass.hCursor = LoadCursorW (0, (LPWSTR)IDC_ARROW);
3314 wndClass.hbrBackground = (HBRUSH)(COLOR_BTNFACE+1);
3315 wndClass.lpszClassName = WC_TABCONTROLW;
3317 RegisterClassW (&wndClass);
3321 void
3322 TAB_Unregister (void)
3324 UnregisterClassW (WC_TABCONTROLW, NULL);