winhlp32: Use TopicOffset mapping and get rid of unused function.
[wine/wine-kai.git] / dlls / comctl32 / tab.c
blob725cc103f84300e2dc700377fc584c6b58cf1916
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 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=(%s)\n",
418 itemIndex, infoPtr->tabHeight, wine_dbgstr_rect(itemRect));
420 /* Now, calculate the position of the item as if it were selected. */
421 if (selectedRect!=NULL)
423 CopyRect(selectedRect, itemRect);
425 /* The rectangle of a selected item is a bit wider. */
426 if(lStyle & TCS_VERTICAL)
427 InflateRect(selectedRect, 0, SELECTED_TAB_OFFSET);
428 else
429 InflateRect(selectedRect, SELECTED_TAB_OFFSET, 0);
431 /* If it also a bit higher. */
432 if ((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
434 selectedRect->left -= 2; /* the border is thicker on the right */
435 selectedRect->right += SELECTED_TAB_OFFSET;
437 else if (lStyle & TCS_VERTICAL)
439 selectedRect->left -= SELECTED_TAB_OFFSET;
440 selectedRect->right += 1;
442 else if (lStyle & TCS_BOTTOM)
444 selectedRect->bottom += SELECTED_TAB_OFFSET;
446 else /* not TCS_BOTTOM and not TCS_VERTICAL */
448 selectedRect->top -= SELECTED_TAB_OFFSET;
449 selectedRect->bottom -= 1;
453 /* Check for visibility */
454 if (lStyle & TCS_VERTICAL)
455 return (itemRect->top < clientRect.bottom) && (itemRect->bottom > clientRect.top);
456 else
457 return (itemRect->left < clientRect.right) && (itemRect->right > clientRect.left);
460 static inline BOOL
461 TAB_GetItemRect(const TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
463 return TAB_InternalGetItemRect(infoPtr, (INT)wParam, (LPRECT)lParam, (LPRECT)NULL);
466 /******************************************************************************
467 * TAB_KeyUp
469 * This method is called to handle keyboard input
471 static LRESULT TAB_KeyUp(TAB_INFO* infoPtr, WPARAM keyCode)
473 int newItem = -1;
475 switch (keyCode)
477 case VK_LEFT:
478 newItem = infoPtr->uFocus - 1;
479 break;
480 case VK_RIGHT:
481 newItem = infoPtr->uFocus + 1;
482 break;
486 * If we changed to a valid item, change the selection
488 if (newItem >= 0 &&
489 newItem < infoPtr->uNumItem &&
490 infoPtr->uFocus != newItem)
492 if (!TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGING))
494 infoPtr->iSelected = newItem;
495 infoPtr->uFocus = newItem;
496 TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGE);
498 TAB_EnsureSelectionVisible(infoPtr);
499 TAB_InvalidateTabArea(infoPtr);
503 return 0;
506 /******************************************************************************
507 * TAB_FocusChanging
509 * This method is called whenever the focus goes in or out of this control
510 * it is used to update the visual state of the control.
512 static void TAB_FocusChanging(const TAB_INFO *infoPtr)
514 RECT selectedRect;
515 BOOL isVisible;
518 * Get the rectangle for the item.
520 isVisible = TAB_InternalGetItemRect(infoPtr,
521 infoPtr->uFocus,
522 NULL,
523 &selectedRect);
526 * If the rectangle is not completely invisible, invalidate that
527 * portion of the window.
529 if (isVisible)
531 TRACE("invalidate (%s)\n", wine_dbgstr_rect(&selectedRect));
532 InvalidateRect(infoPtr->hwnd, &selectedRect, TRUE);
536 static INT TAB_InternalHitTest (const TAB_INFO *infoPtr, POINT pt, UINT *flags)
538 RECT rect;
539 INT iCount;
541 for (iCount = 0; iCount < infoPtr->uNumItem; iCount++)
543 TAB_InternalGetItemRect(infoPtr, iCount, &rect, NULL);
545 if (PtInRect(&rect, pt))
547 *flags = TCHT_ONITEM;
548 return iCount;
552 *flags = TCHT_NOWHERE;
553 return -1;
556 static inline LRESULT
557 TAB_HitTest (const TAB_INFO *infoPtr, LPTCHITTESTINFO lptest)
559 return TAB_InternalHitTest (infoPtr, lptest->pt, &lptest->flags);
562 /******************************************************************************
563 * TAB_NCHitTest
565 * Napster v2b5 has a tab control for its main navigation which has a client
566 * area that covers the whole area of the dialog pages.
567 * That's why it receives all msgs for that area and the underlying dialog ctrls
568 * are dead.
569 * So I decided that we should handle WM_NCHITTEST here and return
570 * HTTRANSPARENT if we don't hit the tab control buttons.
571 * FIXME: WM_NCHITTEST handling correct ? Fix it if you know that Windows
572 * doesn't do it that way. Maybe depends on tab control styles ?
574 static inline LRESULT
575 TAB_NCHitTest (const TAB_INFO *infoPtr, LPARAM lParam)
577 POINT pt;
578 UINT dummyflag;
580 pt.x = (short)LOWORD(lParam);
581 pt.y = (short)HIWORD(lParam);
582 ScreenToClient(infoPtr->hwnd, &pt);
584 if (TAB_InternalHitTest(infoPtr, pt, &dummyflag) == -1)
585 return HTTRANSPARENT;
586 else
587 return HTCLIENT;
590 static LRESULT
591 TAB_LButtonDown (TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
593 POINT pt;
594 INT newItem;
595 UINT dummy;
597 if (infoPtr->hwndToolTip)
598 TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
599 WM_LBUTTONDOWN, wParam, lParam);
601 if (GetWindowLongW(infoPtr->hwnd, GWL_STYLE) & TCS_FOCUSONBUTTONDOWN ) {
602 SetFocus (infoPtr->hwnd);
605 if (infoPtr->hwndToolTip)
606 TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
607 WM_LBUTTONDOWN, wParam, lParam);
609 pt.x = (short)LOWORD(lParam);
610 pt.y = (short)HIWORD(lParam);
612 newItem = TAB_InternalHitTest (infoPtr, pt, &dummy);
614 TRACE("On Tab, item %d\n", newItem);
616 if (newItem != -1 && infoPtr->iSelected != newItem)
618 if (!TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGING))
620 infoPtr->iSelected = newItem;
621 infoPtr->uFocus = newItem;
622 TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGE);
624 TAB_EnsureSelectionVisible(infoPtr);
626 TAB_InvalidateTabArea(infoPtr);
629 return 0;
632 static inline LRESULT
633 TAB_LButtonUp (const TAB_INFO *infoPtr)
635 TAB_SendSimpleNotify(infoPtr, NM_CLICK);
637 return 0;
640 static inline LRESULT
641 TAB_RButtonDown (const TAB_INFO *infoPtr)
643 TAB_SendSimpleNotify(infoPtr, NM_RCLICK);
644 return 0;
647 /******************************************************************************
648 * TAB_DrawLoneItemInterior
650 * This calls TAB_DrawItemInterior. However, TAB_DrawItemInterior is normally
651 * called by TAB_DrawItem which is normally called by TAB_Refresh which sets
652 * up the device context and font. This routine does the same setup but
653 * only calls TAB_DrawItemInterior for the single specified item.
655 static void
656 TAB_DrawLoneItemInterior(const TAB_INFO* infoPtr, int iItem)
658 HDC hdc = GetDC(infoPtr->hwnd);
659 RECT r, rC;
661 /* Clip UpDown control to not draw over it */
662 if (infoPtr->needsScrolling)
664 GetWindowRect(infoPtr->hwnd, &rC);
665 GetWindowRect(infoPtr->hwndUpDown, &r);
666 ExcludeClipRect(hdc, r.left - rC.left, r.top - rC.top, r.right - rC.left, r.bottom - rC.top);
668 TAB_DrawItemInterior(infoPtr, hdc, iItem, NULL);
669 ReleaseDC(infoPtr->hwnd, hdc);
672 /* update a tab after hottracking - invalidate it or just redraw the interior,
673 * based on whether theming is used or not */
674 static inline void hottrack_refresh(const TAB_INFO *infoPtr, int tabIndex)
676 if (tabIndex == -1) return;
678 if (GetWindowTheme (infoPtr->hwnd))
680 RECT rect;
681 TAB_InternalGetItemRect(infoPtr, tabIndex, &rect, NULL);
682 InvalidateRect (infoPtr->hwnd, &rect, FALSE);
684 else
685 TAB_DrawLoneItemInterior(infoPtr, tabIndex);
688 /******************************************************************************
689 * TAB_HotTrackTimerProc
691 * When a mouse-move event causes a tab to be highlighted (hot-tracking), a
692 * timer is setup so we can check if the mouse is moved out of our window.
693 * (We don't get an event when the mouse leaves, the mouse-move events just
694 * stop being delivered to our window and just start being delivered to
695 * another window.) This function is called when the timer triggers so
696 * we can check if the mouse has left our window. If so, we un-highlight
697 * the hot-tracked tab.
699 static void CALLBACK
700 TAB_HotTrackTimerProc
702 HWND hwnd, /* handle of window for timer messages */
703 UINT uMsg, /* WM_TIMER message */
704 UINT_PTR idEvent, /* timer identifier */
705 DWORD dwTime /* current system time */
708 TAB_INFO* infoPtr = TAB_GetInfoPtr(hwnd);
710 if (infoPtr != NULL && infoPtr->iHotTracked >= 0)
712 POINT pt;
715 ** If we can't get the cursor position, or if the cursor is outside our
716 ** window, we un-highlight the hot-tracked tab. Note that the cursor is
717 ** "outside" even if it is within our bounding rect if another window
718 ** overlaps. Note also that the case where the cursor stayed within our
719 ** window but has moved off the hot-tracked tab will be handled by the
720 ** WM_MOUSEMOVE event.
722 if (!GetCursorPos(&pt) || WindowFromPoint(pt) != hwnd)
724 /* Redraw iHotTracked to look normal */
725 INT iRedraw = infoPtr->iHotTracked;
726 infoPtr->iHotTracked = -1;
727 hottrack_refresh (infoPtr, iRedraw);
729 /* Kill this timer */
730 KillTimer(hwnd, TAB_HOTTRACK_TIMER);
735 /******************************************************************************
736 * TAB_RecalcHotTrack
738 * If a tab control has the TCS_HOTTRACK style, then the tab under the mouse
739 * should be highlighted. This function determines which tab in a tab control,
740 * if any, is under the mouse and records that information. The caller may
741 * supply output parameters to receive the item number of the tab item which
742 * was highlighted but isn't any longer and of the tab item which is now
743 * highlighted but wasn't previously. The caller can use this information to
744 * selectively redraw those tab items.
746 * If the caller has a mouse position, it can supply it through the pos
747 * parameter. For example, TAB_MouseMove does this. Otherwise, the caller
748 * supplies NULL and this function determines the current mouse position
749 * itself.
751 static void
752 TAB_RecalcHotTrack
754 TAB_INFO* infoPtr,
755 const LPARAM* pos,
756 int* out_redrawLeave,
757 int* out_redrawEnter
760 int item = -1;
763 if (out_redrawLeave != NULL)
764 *out_redrawLeave = -1;
765 if (out_redrawEnter != NULL)
766 *out_redrawEnter = -1;
768 if ((GetWindowLongW(infoPtr->hwnd, GWL_STYLE) & TCS_HOTTRACK)
769 || GetWindowTheme (infoPtr->hwnd))
771 POINT pt;
772 UINT flags;
774 if (pos == NULL)
776 GetCursorPos(&pt);
777 ScreenToClient(infoPtr->hwnd, &pt);
779 else
781 pt.x = (short)LOWORD(*pos);
782 pt.y = (short)HIWORD(*pos);
785 item = TAB_InternalHitTest(infoPtr, pt, &flags);
788 if (item != infoPtr->iHotTracked)
790 if (infoPtr->iHotTracked >= 0)
792 /* Mark currently hot-tracked to be redrawn to look normal */
793 if (out_redrawLeave != NULL)
794 *out_redrawLeave = infoPtr->iHotTracked;
796 if (item < 0)
798 /* Kill timer which forces recheck of mouse pos */
799 KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
802 else
804 /* Start timer so we recheck mouse pos */
805 UINT timerID = SetTimer
807 infoPtr->hwnd,
808 TAB_HOTTRACK_TIMER,
809 TAB_HOTTRACK_TIMER_INTERVAL,
810 TAB_HotTrackTimerProc
813 if (timerID == 0)
814 return; /* Hot tracking not available */
817 infoPtr->iHotTracked = item;
819 if (item >= 0)
821 /* Mark new hot-tracked to be redrawn to look highlighted */
822 if (out_redrawEnter != NULL)
823 *out_redrawEnter = item;
828 /******************************************************************************
829 * TAB_MouseMove
831 * Handles the mouse-move event. Updates tooltips. Updates hot-tracking.
833 static LRESULT
834 TAB_MouseMove (TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
836 int redrawLeave;
837 int redrawEnter;
839 if (infoPtr->hwndToolTip)
840 TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
841 WM_LBUTTONDOWN, wParam, lParam);
843 /* Determine which tab to highlight. Redraw tabs which change highlight
844 ** status. */
845 TAB_RecalcHotTrack(infoPtr, &lParam, &redrawLeave, &redrawEnter);
847 hottrack_refresh (infoPtr, redrawLeave);
848 hottrack_refresh (infoPtr, redrawEnter);
850 return 0;
853 /******************************************************************************
854 * TAB_AdjustRect
856 * Calculates the tab control's display area given the window rectangle or
857 * the window rectangle given the requested display rectangle.
859 static LRESULT TAB_AdjustRect(const TAB_INFO *infoPtr, WPARAM fLarger, LPRECT prc)
861 DWORD lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
862 LONG *iRightBottom, *iLeftTop;
864 TRACE ("hwnd=%p fLarger=%ld (%s)\n", infoPtr->hwnd, fLarger,
865 wine_dbgstr_rect(prc));
867 if(lStyle & TCS_VERTICAL)
869 iRightBottom = &(prc->right);
870 iLeftTop = &(prc->left);
872 else
874 iRightBottom = &(prc->bottom);
875 iLeftTop = &(prc->top);
878 if (fLarger) /* Go from display rectangle */
880 /* Add the height of the tabs. */
881 if (lStyle & TCS_BOTTOM)
882 *iRightBottom += infoPtr->tabHeight * infoPtr->uNumRows;
883 else
884 *iLeftTop -= infoPtr->tabHeight * infoPtr->uNumRows +
885 ((lStyle & TCS_BUTTONS)? 3 * (infoPtr->uNumRows - 1) : 0);
887 /* Inflate the rectangle for the padding */
888 InflateRect(prc, DISPLAY_AREA_PADDINGX, DISPLAY_AREA_PADDINGY);
890 /* Inflate for the border */
891 InflateRect(prc, CONTROL_BORDER_SIZEX, CONTROL_BORDER_SIZEY);
893 else /* Go from window rectangle. */
895 /* Deflate the rectangle for the border */
896 InflateRect(prc, -CONTROL_BORDER_SIZEX, -CONTROL_BORDER_SIZEY);
898 /* Deflate the rectangle for the padding */
899 InflateRect(prc, -DISPLAY_AREA_PADDINGX, -DISPLAY_AREA_PADDINGY);
901 /* Remove the height of the tabs. */
902 if (lStyle & TCS_BOTTOM)
903 *iRightBottom -= infoPtr->tabHeight * infoPtr->uNumRows;
904 else
905 *iLeftTop += (infoPtr->tabHeight) * infoPtr->uNumRows +
906 ((lStyle & TCS_BUTTONS)? 3 * (infoPtr->uNumRows - 1) : 0);
909 return 0;
912 /******************************************************************************
913 * TAB_OnHScroll
915 * This method will handle the notification from the scroll control and
916 * perform the scrolling operation on the tab control.
918 static LRESULT TAB_OnHScroll(TAB_INFO *infoPtr, int nScrollCode, int nPos, HWND hwndScroll)
920 if(nScrollCode == SB_THUMBPOSITION && nPos != infoPtr->leftmostVisible)
922 if(nPos < infoPtr->leftmostVisible)
923 infoPtr->leftmostVisible--;
924 else
925 infoPtr->leftmostVisible++;
927 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
928 TAB_InvalidateTabArea(infoPtr);
929 SendMessageW(infoPtr->hwndUpDown, UDM_SETPOS, 0,
930 MAKELONG(infoPtr->leftmostVisible, 0));
933 return 0;
936 /******************************************************************************
937 * TAB_SetupScrolling
939 * This method will check the current scrolling state and make sure the
940 * scrolling control is displayed (or not).
942 static void TAB_SetupScrolling(
943 HWND hwnd,
944 TAB_INFO* infoPtr,
945 const RECT* clientRect)
947 static const WCHAR msctls_updown32W[] = { 'm','s','c','t','l','s','_','u','p','d','o','w','n','3','2',0 };
948 static const WCHAR emptyW[] = { 0 };
949 INT maxRange = 0;
950 DWORD lStyle = GetWindowLongW(hwnd, GWL_STYLE);
952 if (infoPtr->needsScrolling)
954 RECT controlPos;
955 INT vsize, tabwidth;
958 * Calculate the position of the scroll control.
960 if(lStyle & TCS_VERTICAL)
962 controlPos.right = clientRect->right;
963 controlPos.left = controlPos.right - 2 * GetSystemMetrics(SM_CXHSCROLL);
965 if (lStyle & TCS_BOTTOM)
967 controlPos.top = clientRect->bottom - infoPtr->tabHeight;
968 controlPos.bottom = controlPos.top + GetSystemMetrics(SM_CYHSCROLL);
970 else
972 controlPos.bottom = clientRect->top + infoPtr->tabHeight;
973 controlPos.top = controlPos.bottom - GetSystemMetrics(SM_CYHSCROLL);
976 else
978 controlPos.right = clientRect->right;
979 controlPos.left = controlPos.right - 2 * GetSystemMetrics(SM_CXHSCROLL);
981 if (lStyle & TCS_BOTTOM)
983 controlPos.top = clientRect->bottom - infoPtr->tabHeight;
984 controlPos.bottom = controlPos.top + GetSystemMetrics(SM_CYHSCROLL);
986 else
988 controlPos.bottom = clientRect->top + infoPtr->tabHeight;
989 controlPos.top = controlPos.bottom - GetSystemMetrics(SM_CYHSCROLL);
994 * If we don't have a scroll control yet, we want to create one.
995 * If we have one, we want to make sure it's positioned properly.
997 if (infoPtr->hwndUpDown==0)
999 infoPtr->hwndUpDown = CreateWindowW(msctls_updown32W, emptyW,
1000 WS_VISIBLE | WS_CHILD | UDS_HORZ,
1001 controlPos.left, controlPos.top,
1002 controlPos.right - controlPos.left,
1003 controlPos.bottom - controlPos.top,
1004 hwnd, NULL, NULL, NULL);
1006 else
1008 SetWindowPos(infoPtr->hwndUpDown,
1009 NULL,
1010 controlPos.left, controlPos.top,
1011 controlPos.right - controlPos.left,
1012 controlPos.bottom - controlPos.top,
1013 SWP_SHOWWINDOW | SWP_NOZORDER);
1016 /* Now calculate upper limit of the updown control range.
1017 * We do this by calculating how many tabs will be offscreen when the
1018 * last tab is visible.
1020 if(infoPtr->uNumItem)
1022 vsize = clientRect->right - (controlPos.right - controlPos.left + 1);
1023 maxRange = infoPtr->uNumItem;
1024 tabwidth = TAB_GetItem(infoPtr, infoPtr->uNumItem - 1)->rect.right;
1026 for(; maxRange > 0; maxRange--)
1028 if(tabwidth - TAB_GetItem(infoPtr,maxRange - 1)->rect.left > vsize)
1029 break;
1032 if(maxRange == infoPtr->uNumItem)
1033 maxRange--;
1036 else
1038 /* If we once had a scroll control... hide it */
1039 if (infoPtr->hwndUpDown!=0)
1040 ShowWindow(infoPtr->hwndUpDown, SW_HIDE);
1042 if (infoPtr->hwndUpDown)
1043 SendMessageW(infoPtr->hwndUpDown, UDM_SETRANGE32, 0, maxRange);
1046 /******************************************************************************
1047 * TAB_SetItemBounds
1049 * This method will calculate the position rectangles of all the items in the
1050 * control. The rectangle calculated starts at 0 for the first item in the
1051 * list and ignores scrolling and selection.
1052 * It also uses the current font to determine the height of the tab row and
1053 * it checks if all the tabs fit in the client area of the window. If they
1054 * don't, a scrolling control is added.
1056 static void TAB_SetItemBounds (TAB_INFO *infoPtr)
1058 LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1059 TEXTMETRICW fontMetrics;
1060 UINT curItem;
1061 INT curItemLeftPos;
1062 INT curItemRowCount;
1063 HFONT hFont, hOldFont;
1064 HDC hdc;
1065 RECT clientRect;
1066 INT iTemp;
1067 RECT* rcItem;
1068 INT iIndex;
1069 INT icon_width = 0;
1072 * We need to get text information so we need a DC and we need to select
1073 * a font.
1075 hdc = GetDC(infoPtr->hwnd);
1077 hFont = infoPtr->hFont ? infoPtr->hFont : GetStockObject (SYSTEM_FONT);
1078 hOldFont = SelectObject (hdc, hFont);
1081 * We will base the rectangle calculations on the client rectangle
1082 * of the control.
1084 GetClientRect(infoPtr->hwnd, &clientRect);
1086 /* if TCS_VERTICAL then swap the height and width so this code places the
1087 tabs along the top of the rectangle and we can just rotate them after
1088 rather than duplicate all of the below code */
1089 if(lStyle & TCS_VERTICAL)
1091 iTemp = clientRect.bottom;
1092 clientRect.bottom = clientRect.right;
1093 clientRect.right = iTemp;
1096 /* Now use hPadding and vPadding */
1097 infoPtr->uHItemPadding = infoPtr->uHItemPadding_s;
1098 infoPtr->uVItemPadding = infoPtr->uVItemPadding_s;
1100 /* The leftmost item will be "0" aligned */
1101 curItemLeftPos = 0;
1102 curItemRowCount = infoPtr->uNumItem ? 1 : 0;
1104 if (!(infoPtr->fHeightSet))
1106 int item_height;
1107 int icon_height = 0;
1109 /* Use the current font to determine the height of a tab. */
1110 GetTextMetricsW(hdc, &fontMetrics);
1112 /* Get the icon height */
1113 if (infoPtr->himl)
1114 ImageList_GetIconSize(infoPtr->himl, 0, &icon_height);
1116 /* Take the highest between font or icon */
1117 if (fontMetrics.tmHeight > icon_height)
1118 item_height = fontMetrics.tmHeight + 2;
1119 else
1120 item_height = icon_height;
1123 * Make sure there is enough space for the letters + icon + growing the
1124 * selected item + extra space for the selected item.
1126 infoPtr->tabHeight = item_height +
1127 ((lStyle & TCS_BUTTONS) ? 2 : 1) *
1128 infoPtr->uVItemPadding;
1130 TRACE("tabH=%d, tmH=%d, iconh=%d\n",
1131 infoPtr->tabHeight, fontMetrics.tmHeight, icon_height);
1134 TRACE("client right=%d\n", clientRect.right);
1136 /* Get the icon width */
1137 if (infoPtr->himl)
1139 ImageList_GetIconSize(infoPtr->himl, &icon_width, 0);
1141 if (lStyle & TCS_FIXEDWIDTH)
1142 icon_width += 4;
1143 else
1144 /* Add padding if icon is present */
1145 icon_width += infoPtr->uHItemPadding;
1148 for (curItem = 0; curItem < infoPtr->uNumItem; curItem++)
1150 TAB_ITEM *curr = TAB_GetItem(infoPtr, curItem);
1152 /* Set the leftmost position of the tab. */
1153 curr->rect.left = curItemLeftPos;
1155 if (lStyle & TCS_FIXEDWIDTH)
1157 curr->rect.right = curr->rect.left +
1158 max(infoPtr->tabWidth, icon_width);
1160 else if (!curr->pszText)
1162 /* If no text use minimum tab width including padding. */
1163 if (infoPtr->tabMinWidth < 0)
1164 curr->rect.right = curr->rect.left + GET_DEFAULT_MIN_TAB_WIDTH(infoPtr);
1165 else
1167 curr->rect.right = curr->rect.left + infoPtr->tabMinWidth;
1169 /* Add extra padding if icon is present */
1170 if (infoPtr->himl && infoPtr->tabMinWidth > 0 && infoPtr->tabMinWidth < DEFAULT_MIN_TAB_WIDTH
1171 && infoPtr->uHItemPadding > 1)
1172 curr->rect.right += EXTRA_ICON_PADDING * (infoPtr->uHItemPadding-1);
1175 else
1177 int tabwidth;
1178 SIZE size;
1179 /* Calculate how wide the tab is depending on the text it contains */
1180 GetTextExtentPoint32W(hdc, curr->pszText,
1181 lstrlenW(curr->pszText), &size);
1183 tabwidth = size.cx + icon_width + 2 * infoPtr->uHItemPadding;
1185 if (infoPtr->tabMinWidth < 0)
1186 tabwidth = max(tabwidth, GET_DEFAULT_MIN_TAB_WIDTH(infoPtr));
1187 else
1188 tabwidth = max(tabwidth, infoPtr->tabMinWidth);
1190 curr->rect.right = curr->rect.left + tabwidth;
1191 TRACE("for <%s>, l,r=%d,%d\n",
1192 debugstr_w(curr->pszText), curr->rect.left, curr->rect.right);
1196 * Check if this is a multiline tab control and if so
1197 * check to see if we should wrap the tabs
1199 * Wrap all these tabs. We will arrange them evenly later.
1203 if (((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)) &&
1204 (curr->rect.right >
1205 (clientRect.right - CONTROL_BORDER_SIZEX - DISPLAY_AREA_PADDINGX)))
1207 curr->rect.right -= curr->rect.left;
1209 curr->rect.left = 0;
1210 curItemRowCount++;
1211 TRACE("wrapping <%s>, l,r=%d,%d\n", debugstr_w(curr->pszText),
1212 curr->rect.left, curr->rect.right);
1215 curr->rect.bottom = 0;
1216 curr->rect.top = curItemRowCount - 1;
1218 TRACE("Rect: %s\n", wine_dbgstr_rect(&curr->rect));
1221 * The leftmost position of the next item is the rightmost position
1222 * of this one.
1224 if (lStyle & TCS_BUTTONS)
1226 curItemLeftPos = curr->rect.right + BUTTON_SPACINGX;
1227 if (lStyle & TCS_FLATBUTTONS)
1228 curItemLeftPos += FLAT_BTN_SPACINGX;
1230 else
1231 curItemLeftPos = curr->rect.right;
1234 if (!((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)))
1237 * Check if we need a scrolling control.
1239 infoPtr->needsScrolling = (curItemLeftPos + (2 * SELECTED_TAB_OFFSET) >
1240 clientRect.right);
1242 /* Don't need scrolling, then update infoPtr->leftmostVisible */
1243 if(!infoPtr->needsScrolling)
1244 infoPtr->leftmostVisible = 0;
1246 else
1249 * No scrolling in Multiline or Vertical styles.
1251 infoPtr->needsScrolling = FALSE;
1252 infoPtr->leftmostVisible = 0;
1254 TAB_SetupScrolling(infoPtr->hwnd, infoPtr, &clientRect);
1256 /* Set the number of rows */
1257 infoPtr->uNumRows = curItemRowCount;
1259 /* Arrange all tabs evenly if style says so */
1260 if (!(lStyle & TCS_RAGGEDRIGHT) &&
1261 ((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)) &&
1262 (infoPtr->uNumItem > 0) &&
1263 (infoPtr->uNumRows > 1))
1265 INT tabPerRow,remTab,iRow;
1266 UINT iItm;
1267 INT iCount=0;
1270 * Ok windows tries to even out the rows. place the same
1271 * number of tabs in each row. So lets give that a shot
1274 tabPerRow = infoPtr->uNumItem / (infoPtr->uNumRows);
1275 remTab = infoPtr->uNumItem % (infoPtr->uNumRows);
1277 for (iItm=0,iRow=0,iCount=0,curItemLeftPos=0;
1278 iItm<infoPtr->uNumItem;
1279 iItm++,iCount++)
1281 /* normalize the current rect */
1282 TAB_ITEM *curr = TAB_GetItem(infoPtr, iItm);
1284 /* shift the item to the left side of the clientRect */
1285 curr->rect.right -= curr->rect.left;
1286 curr->rect.left = 0;
1288 TRACE("r=%d, cl=%d, cl.r=%d, iCount=%d, iRow=%d, uNumRows=%d, remTab=%d, tabPerRow=%d\n",
1289 curr->rect.right, curItemLeftPos, clientRect.right,
1290 iCount, iRow, infoPtr->uNumRows, remTab, tabPerRow);
1292 /* if we have reached the maximum number of tabs on this row */
1293 /* move to the next row, reset our current item left position and */
1294 /* the count of items on this row */
1296 if (lStyle & TCS_VERTICAL) {
1297 /* Vert: Add the remaining tabs in the *last* remainder rows */
1298 if (iCount >= ((iRow>=(INT)infoPtr->uNumRows - remTab)?tabPerRow + 1:tabPerRow)) {
1299 iRow++;
1300 curItemLeftPos = 0;
1301 iCount = 0;
1303 } else {
1304 /* Horz: Add the remaining tabs in the *first* remainder rows */
1305 if (iCount >= ((iRow<remTab)?tabPerRow + 1:tabPerRow)) {
1306 iRow++;
1307 curItemLeftPos = 0;
1308 iCount = 0;
1312 /* shift the item to the right to place it as the next item in this row */
1313 curr->rect.left += curItemLeftPos;
1314 curr->rect.right += curItemLeftPos;
1315 curr->rect.top = iRow;
1316 if (lStyle & TCS_BUTTONS)
1318 curItemLeftPos = curr->rect.right + 1;
1319 if (lStyle & TCS_FLATBUTTONS)
1320 curItemLeftPos += FLAT_BTN_SPACINGX;
1322 else
1323 curItemLeftPos = curr->rect.right;
1325 TRACE("arranging <%s>, l,r=%d,%d, row=%d\n",
1326 debugstr_w(curr->pszText), curr->rect.left,
1327 curr->rect.right, curr->rect.top);
1331 * Justify the rows
1334 INT widthDiff, iIndexStart=0, iIndexEnd=0;
1335 INT remainder;
1336 INT iCount=0;
1338 while(iIndexStart < infoPtr->uNumItem)
1340 TAB_ITEM *start = TAB_GetItem(infoPtr, iIndexStart);
1343 * find the index of the row
1345 /* find the first item on the next row */
1346 for (iIndexEnd=iIndexStart;
1347 (iIndexEnd < infoPtr->uNumItem) &&
1348 (TAB_GetItem(infoPtr, iIndexEnd)->rect.top ==
1349 start->rect.top) ;
1350 iIndexEnd++)
1351 /* intentionally blank */;
1354 * we need to justify these tabs so they fill the whole given
1355 * client area
1358 /* find the amount of space remaining on this row */
1359 widthDiff = clientRect.right - (2 * SELECTED_TAB_OFFSET) -
1360 TAB_GetItem(infoPtr, iIndexEnd - 1)->rect.right;
1362 /* iCount is the number of tab items on this row */
1363 iCount = iIndexEnd - iIndexStart;
1365 if (iCount > 1)
1367 remainder = widthDiff % iCount;
1368 widthDiff = widthDiff / iCount;
1369 /* add widthDiff/iCount, or extra space/items on row, to each item on this row */
1370 for (iIndex=iIndexStart, iCount=0; iIndex < iIndexEnd; iIndex++, iCount++)
1372 TAB_ITEM *item = TAB_GetItem(infoPtr, iIndex);
1374 item->rect.left += iCount * widthDiff;
1375 item->rect.right += (iCount + 1) * widthDiff;
1377 TRACE("adjusting 1 <%s>, l,r=%d,%d\n",
1378 debugstr_w(item->pszText),
1379 item->rect.left, item->rect.right);
1382 TAB_GetItem(infoPtr, iIndex - 1)->rect.right += remainder;
1384 else /* we have only one item on this row, make it take up the entire row */
1386 start->rect.left = clientRect.left;
1387 start->rect.right = clientRect.right - 4;
1389 TRACE("adjusting 2 <%s>, l,r=%d,%d\n",
1390 debugstr_w(start->pszText),
1391 start->rect.left, start->rect.right);
1396 iIndexStart = iIndexEnd;
1401 /* if TCS_VERTICAL rotate the tabs so they are along the side of the clientRect */
1402 if(lStyle & TCS_VERTICAL)
1404 RECT rcOriginal;
1405 for(iIndex = 0; iIndex < infoPtr->uNumItem; iIndex++)
1407 rcItem = &TAB_GetItem(infoPtr, iIndex)->rect;
1409 rcOriginal = *rcItem;
1411 /* this is rotating the items by 90 degrees clockwise around the center of the control */
1412 rcItem->top = (rcOriginal.left - clientRect.left);
1413 rcItem->bottom = rcItem->top + (rcOriginal.right - rcOriginal.left);
1414 rcItem->left = rcOriginal.top;
1415 rcItem->right = rcOriginal.bottom;
1419 TAB_EnsureSelectionVisible(infoPtr);
1420 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
1422 /* Cleanup */
1423 SelectObject (hdc, hOldFont);
1424 ReleaseDC (infoPtr->hwnd, hdc);
1428 static void
1429 TAB_EraseTabInterior(const TAB_INFO *infoPtr, HDC hdc, INT iItem, RECT *drawRect)
1431 LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1432 HBRUSH hbr = CreateSolidBrush (comctl32_color.clrBtnFace);
1433 BOOL deleteBrush = TRUE;
1434 RECT rTemp = *drawRect;
1436 InflateRect(&rTemp, -2, -2);
1437 if (lStyle & TCS_BUTTONS)
1439 if (iItem == infoPtr->iSelected)
1441 /* Background color */
1442 if (!(lStyle & TCS_OWNERDRAWFIXED))
1444 DeleteObject(hbr);
1445 hbr = GetSysColorBrush(COLOR_SCROLLBAR);
1447 SetTextColor(hdc, comctl32_color.clr3dFace);
1448 SetBkColor(hdc, comctl32_color.clr3dHilight);
1450 /* if COLOR_WINDOW happens to be the same as COLOR_3DHILIGHT
1451 * we better use 0x55aa bitmap brush to make scrollbar's background
1452 * look different from the window background.
1454 if (comctl32_color.clr3dHilight == comctl32_color.clrWindow)
1455 hbr = COMCTL32_hPattern55AABrush;
1457 deleteBrush = FALSE;
1459 FillRect(hdc, &rTemp, hbr);
1461 else /* ! selected */
1463 if (lStyle & TCS_FLATBUTTONS)
1465 FillRect(hdc, drawRect, hbr);
1466 if (iItem == infoPtr->iHotTracked)
1467 DrawEdge(hdc, drawRect, EDGE_RAISED, BF_SOFT|BF_RECT);
1469 else
1470 FillRect(hdc, &rTemp, hbr);
1474 else /* !TCS_BUTTONS */
1476 if (!GetWindowTheme (infoPtr->hwnd))
1477 FillRect(hdc, &rTemp, hbr);
1480 /* Cleanup */
1481 if (deleteBrush) DeleteObject(hbr);
1484 /******************************************************************************
1485 * TAB_DrawItemInterior
1487 * This method is used to draw the interior (text and icon) of a single tab
1488 * into the tab control.
1490 static void
1491 TAB_DrawItemInterior(const TAB_INFO *infoPtr, HDC hdc, INT iItem, RECT *drawRect)
1493 LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1495 RECT localRect;
1497 HPEN htextPen;
1498 HPEN holdPen;
1499 INT oldBkMode;
1500 HFONT hOldFont;
1502 /* if (drawRect == NULL) */
1504 BOOL isVisible;
1505 RECT itemRect;
1506 RECT selectedRect;
1509 * Get the rectangle for the item.
1511 isVisible = TAB_InternalGetItemRect(infoPtr, iItem, &itemRect, &selectedRect);
1512 if (!isVisible)
1513 return;
1516 * Make sure drawRect points to something valid; simplifies code.
1518 drawRect = &localRect;
1521 * This logic copied from the part of TAB_DrawItem which draws
1522 * the tab background. It's important to keep it in sync. I
1523 * would have liked to avoid code duplication, but couldn't figure
1524 * out how without making spaghetti of TAB_DrawItem.
1526 if (iItem == infoPtr->iSelected)
1527 *drawRect = selectedRect;
1528 else
1529 *drawRect = itemRect;
1531 if (lStyle & TCS_BUTTONS)
1533 if (iItem == infoPtr->iSelected)
1535 drawRect->left += 4;
1536 drawRect->top += 4;
1537 drawRect->right -= 4;
1538 drawRect->bottom -= 1;
1540 else
1542 drawRect->left += 2;
1543 drawRect->top += 2;
1544 drawRect->right -= 2;
1545 drawRect->bottom -= 2;
1548 else
1550 if ((lStyle & TCS_VERTICAL) && (lStyle & TCS_BOTTOM))
1552 if (iItem != infoPtr->iSelected)
1554 drawRect->left += 2;
1555 drawRect->top += 2;
1556 drawRect->bottom -= 2;
1559 else if (lStyle & TCS_VERTICAL)
1561 if (iItem == infoPtr->iSelected)
1563 drawRect->right += 1;
1565 else
1567 drawRect->top += 2;
1568 drawRect->right -= 2;
1569 drawRect->bottom -= 2;
1572 else if (lStyle & TCS_BOTTOM)
1574 if (iItem == infoPtr->iSelected)
1576 drawRect->top -= 2;
1578 else
1580 InflateRect(drawRect, -2, -2);
1581 drawRect->bottom += 2;
1584 else
1586 if (iItem == infoPtr->iSelected)
1588 drawRect->bottom += 3;
1590 else
1592 drawRect->bottom -= 2;
1593 InflateRect(drawRect, -2, 0);
1598 TRACE("drawRect=(%s)\n", wine_dbgstr_rect(drawRect));
1600 /* Clear interior */
1601 TAB_EraseTabInterior (infoPtr, hdc, iItem, drawRect);
1603 /* Draw the focus rectangle */
1604 if (!(lStyle & TCS_FOCUSNEVER) &&
1605 (GetFocus() == infoPtr->hwnd) &&
1606 (iItem == infoPtr->uFocus) )
1608 RECT rFocus = *drawRect;
1609 InflateRect(&rFocus, -3, -3);
1610 if (lStyle & TCS_BOTTOM && !(lStyle & TCS_VERTICAL))
1611 rFocus.top -= 3;
1612 if (lStyle & TCS_BUTTONS)
1614 rFocus.left -= 3;
1615 rFocus.top -= 3;
1618 DrawFocusRect(hdc, &rFocus);
1622 * Text pen
1624 htextPen = CreatePen( PS_SOLID, 1, GetSysColor(COLOR_BTNTEXT) );
1625 holdPen = SelectObject(hdc, htextPen);
1626 hOldFont = SelectObject(hdc, infoPtr->hFont);
1629 * Setup for text output
1631 oldBkMode = SetBkMode(hdc, TRANSPARENT);
1632 if (!GetWindowTheme (infoPtr->hwnd) || (lStyle & TCS_BUTTONS))
1633 SetTextColor(hdc, (((lStyle & TCS_HOTTRACK) && (iItem == infoPtr->iHotTracked)
1634 && !(lStyle & TCS_FLATBUTTONS))
1635 | (TAB_GetItem(infoPtr, iItem)->dwState & TCIS_HIGHLIGHTED)) ?
1636 comctl32_color.clrHighlight : comctl32_color.clrBtnText);
1639 * if owner draw, tell the owner to draw
1641 if ((lStyle & TCS_OWNERDRAWFIXED) && GetParent(infoPtr->hwnd))
1643 DRAWITEMSTRUCT dis;
1644 UINT id;
1646 drawRect->top += 2;
1647 drawRect->right -= 1;
1648 if ( iItem == infoPtr->iSelected )
1650 drawRect->right -= 1;
1651 drawRect->left += 1;
1655 * get the control id
1657 id = (UINT)GetWindowLongPtrW( infoPtr->hwnd, GWLP_ID );
1660 * put together the DRAWITEMSTRUCT
1662 dis.CtlType = ODT_TAB;
1663 dis.CtlID = id;
1664 dis.itemID = iItem;
1665 dis.itemAction = ODA_DRAWENTIRE;
1666 dis.itemState = 0;
1667 if ( iItem == infoPtr->iSelected )
1668 dis.itemState |= ODS_SELECTED;
1669 if (infoPtr->uFocus == iItem)
1670 dis.itemState |= ODS_FOCUS;
1671 dis.hwndItem = infoPtr->hwnd;
1672 dis.hDC = hdc;
1673 CopyRect(&dis.rcItem,drawRect);
1674 dis.itemData = (ULONG_PTR)TAB_GetItem(infoPtr, iItem)->extra;
1677 * send the draw message
1679 SendMessageW( infoPtr->hwndNotify, WM_DRAWITEM, (WPARAM)id, (LPARAM)&dis );
1681 else
1683 TAB_ITEM *item = TAB_GetItem(infoPtr, iItem);
1684 RECT rcTemp;
1685 RECT rcImage;
1687 /* used to center the icon and text in the tab */
1688 RECT rcText;
1689 INT center_offset_h, center_offset_v;
1691 /* set rcImage to drawRect, we will use top & left in our ImageList_Draw call */
1692 rcImage = *drawRect;
1694 rcTemp = *drawRect;
1696 rcText.left = rcText.top = rcText.right = rcText.bottom = 0;
1698 /* get the rectangle that the text fits in */
1699 if (item->pszText)
1701 DrawTextW(hdc, item->pszText, -1, &rcText, DT_CALCRECT);
1704 * If not owner draw, then do the drawing ourselves.
1706 * Draw the icon.
1708 if (infoPtr->himl && item->iImage != -1)
1710 INT cx;
1711 INT cy;
1713 ImageList_GetIconSize(infoPtr->himl, &cx, &cy);
1715 if(lStyle & TCS_VERTICAL)
1717 center_offset_h = ((drawRect->bottom - drawRect->top) - (cy + infoPtr->uHItemPadding + (rcText.right - rcText.left))) / 2;
1718 center_offset_v = ((drawRect->right - drawRect->left) - cx) / 2;
1720 else
1722 center_offset_h = ((drawRect->right - drawRect->left) - (cx + infoPtr->uHItemPadding + (rcText.right - rcText.left))) / 2;
1723 center_offset_v = ((drawRect->bottom - drawRect->top) - cy) / 2;
1726 /* if an item is selected, the icon is shifted up instead of down */
1727 if (iItem == infoPtr->iSelected)
1728 center_offset_v -= infoPtr->uVItemPadding / 2;
1729 else
1730 center_offset_v += infoPtr->uVItemPadding / 2;
1732 if (lStyle & TCS_FIXEDWIDTH && lStyle & (TCS_FORCELABELLEFT | TCS_FORCEICONLEFT))
1733 center_offset_h = infoPtr->uHItemPadding;
1735 if (center_offset_h < 2)
1736 center_offset_h = 2;
1738 if (center_offset_v < 0)
1739 center_offset_v = 0;
1741 TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%s), textlen=%d\n",
1742 debugstr_w(item->pszText), center_offset_h, center_offset_v,
1743 wine_dbgstr_rect(drawRect), (rcText.right-rcText.left));
1745 if((lStyle & TCS_VERTICAL) && (lStyle & TCS_BOTTOM))
1747 rcImage.top = drawRect->top + center_offset_h;
1748 /* if tab is TCS_VERTICAL and TCS_BOTTOM, the text is drawn from the */
1749 /* right side of the tab, but the image still uses the left as its x position */
1750 /* this keeps the image always drawn off of the same side of the tab */
1751 rcImage.left = drawRect->right - cx - center_offset_v;
1752 drawRect->top += cy + infoPtr->uHItemPadding;
1754 else if(lStyle & TCS_VERTICAL)
1756 rcImage.top = drawRect->bottom - cy - center_offset_h;
1757 rcImage.left = drawRect->left + center_offset_v;
1758 drawRect->bottom -= cy + infoPtr->uHItemPadding;
1760 else /* normal style, whether TCS_BOTTOM or not */
1762 rcImage.left = drawRect->left + center_offset_h;
1763 rcImage.top = drawRect->top + center_offset_v;
1764 drawRect->left += cx + infoPtr->uHItemPadding;
1767 TRACE("drawing image=%d, left=%d, top=%d\n",
1768 item->iImage, rcImage.left, rcImage.top-1);
1769 ImageList_Draw
1771 infoPtr->himl,
1772 item->iImage,
1773 hdc,
1774 rcImage.left,
1775 rcImage.top,
1776 ILD_NORMAL
1780 /* Now position text */
1781 if (lStyle & TCS_FIXEDWIDTH && lStyle & TCS_FORCELABELLEFT)
1782 center_offset_h = infoPtr->uHItemPadding;
1783 else
1784 if(lStyle & TCS_VERTICAL)
1785 center_offset_h = ((drawRect->bottom - drawRect->top) - (rcText.right - rcText.left)) / 2;
1786 else
1787 center_offset_h = ((drawRect->right - drawRect->left) - (rcText.right - rcText.left)) / 2;
1789 if(lStyle & TCS_VERTICAL)
1791 if(lStyle & TCS_BOTTOM)
1792 drawRect->top+=center_offset_h;
1793 else
1794 drawRect->bottom-=center_offset_h;
1796 center_offset_v = ((drawRect->right - drawRect->left) - (rcText.bottom - rcText.top)) / 2;
1798 else
1800 drawRect->left += center_offset_h;
1801 center_offset_v = ((drawRect->bottom - drawRect->top) - (rcText.bottom - rcText.top)) / 2;
1804 /* if an item is selected, the text is shifted up instead of down */
1805 if (iItem == infoPtr->iSelected)
1806 center_offset_v -= infoPtr->uVItemPadding / 2;
1807 else
1808 center_offset_v += infoPtr->uVItemPadding / 2;
1810 if (center_offset_v < 0)
1811 center_offset_v = 0;
1813 if(lStyle & TCS_VERTICAL)
1814 drawRect->left += center_offset_v;
1815 else
1816 drawRect->top += center_offset_v;
1818 /* Draw the text */
1819 if(lStyle & TCS_VERTICAL) /* if we are vertical rotate the text and each character */
1821 static const WCHAR ArialW[] = { 'A','r','i','a','l',0 };
1822 LOGFONTW logfont;
1823 HFONT hFont = 0;
1824 INT nEscapement = 900;
1825 INT nOrientation = 900;
1827 if(lStyle & TCS_BOTTOM)
1829 nEscapement = -900;
1830 nOrientation = -900;
1833 /* to get a font with the escapement and orientation we are looking for, we need to */
1834 /* call CreateFontIndirectA, which requires us to set the values of the logfont we pass in */
1835 if (!GetObjectW((infoPtr->hFont) ?
1836 infoPtr->hFont : GetStockObject(SYSTEM_FONT),
1837 sizeof(LOGFONTW),&logfont))
1839 INT iPointSize = 9;
1841 lstrcpyW(logfont.lfFaceName, ArialW);
1842 logfont.lfHeight = -MulDiv(iPointSize, GetDeviceCaps(hdc, LOGPIXELSY),
1843 72);
1844 logfont.lfWeight = FW_NORMAL;
1845 logfont.lfItalic = 0;
1846 logfont.lfUnderline = 0;
1847 logfont.lfStrikeOut = 0;
1850 logfont.lfEscapement = nEscapement;
1851 logfont.lfOrientation = nOrientation;
1852 hFont = CreateFontIndirectW(&logfont);
1853 SelectObject(hdc, hFont);
1855 if (item->pszText)
1857 ExtTextOutW(hdc,
1858 (lStyle & TCS_BOTTOM) ? drawRect->right : drawRect->left,
1859 (!(lStyle & TCS_BOTTOM)) ? drawRect->bottom : drawRect->top,
1860 ETO_CLIPPED,
1861 drawRect,
1862 item->pszText,
1863 lstrlenW(item->pszText),
1867 DeleteObject(hFont);
1869 else
1871 TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%s), textlen=%d\n",
1872 debugstr_w(item->pszText), center_offset_h, center_offset_v,
1873 wine_dbgstr_rect(drawRect), (rcText.right-rcText.left));
1874 if (item->pszText)
1876 DrawTextW
1878 hdc,
1879 item->pszText,
1880 lstrlenW(item->pszText),
1881 drawRect,
1882 DT_LEFT | DT_SINGLELINE
1887 *drawRect = rcTemp; /* restore drawRect */
1891 * Cleanup
1893 SelectObject(hdc, hOldFont);
1894 SetBkMode(hdc, oldBkMode);
1895 SelectObject(hdc, holdPen);
1896 DeleteObject( htextPen );
1899 /******************************************************************************
1900 * TAB_DrawItem
1902 * This method is used to draw a single tab into the tab control.
1904 static void TAB_DrawItem(const TAB_INFO *infoPtr, HDC hdc, INT iItem)
1906 LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1907 RECT itemRect;
1908 RECT selectedRect;
1909 BOOL isVisible;
1910 RECT r, fillRect, r1;
1911 INT clRight = 0;
1912 INT clBottom = 0;
1913 COLORREF bkgnd, corner;
1914 HTHEME theme;
1917 * Get the rectangle for the item.
1919 isVisible = TAB_InternalGetItemRect(infoPtr,
1920 iItem,
1921 &itemRect,
1922 &selectedRect);
1924 if (isVisible)
1926 RECT rUD, rC;
1928 /* Clip UpDown control to not draw over it */
1929 if (infoPtr->needsScrolling)
1931 GetWindowRect(infoPtr->hwnd, &rC);
1932 GetWindowRect(infoPtr->hwndUpDown, &rUD);
1933 ExcludeClipRect(hdc, rUD.left - rC.left, rUD.top - rC.top, rUD.right - rC.left, rUD.bottom - rC.top);
1936 /* If you need to see what the control is doing,
1937 * then override these variables. They will change what
1938 * fill colors are used for filling the tabs, and the
1939 * corners when drawing the edge.
1941 bkgnd = comctl32_color.clrBtnFace;
1942 corner = comctl32_color.clrBtnFace;
1944 if (lStyle & TCS_BUTTONS)
1946 /* Get item rectangle */
1947 r = itemRect;
1949 /* Separators between flat buttons */
1950 if (lStyle & TCS_FLATBUTTONS)
1952 r1 = r;
1953 r1.right += (FLAT_BTN_SPACINGX -2);
1954 DrawEdge(hdc, &r1, EDGE_ETCHED, BF_RIGHT);
1957 if (iItem == infoPtr->iSelected)
1959 DrawEdge(hdc, &r, EDGE_SUNKEN, BF_SOFT|BF_RECT);
1961 OffsetRect(&r, 1, 1);
1963 else /* ! selected */
1965 if (!(lStyle & TCS_FLATBUTTONS))
1966 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_RECT);
1969 else /* !TCS_BUTTONS */
1971 /* We draw a rectangle of different sizes depending on the selection
1972 * state. */
1973 if (iItem == infoPtr->iSelected) {
1974 RECT rect;
1975 GetClientRect (infoPtr->hwnd, &rect);
1976 clRight = rect.right;
1977 clBottom = rect.bottom;
1978 r = selectedRect;
1980 else
1981 r = itemRect;
1984 * Erase the background. (Delay it but setup rectangle.)
1985 * This is necessary when drawing the selected item since it is larger
1986 * than the others, it might overlap with stuff already drawn by the
1987 * other tabs
1989 fillRect = r;
1991 /* Draw themed tabs - but only if they are at the top.
1992 * Windows draws even side or bottom tabs themed, with wacky results.
1993 * However, since in Wine apps may get themed that did not opt in via
1994 * a manifest avoid theming when we know the result will be wrong */
1995 if ((theme = GetWindowTheme (infoPtr->hwnd))
1996 && ((lStyle & (TCS_VERTICAL | TCS_BOTTOM)) == 0))
1998 static const int partIds[8] = {
1999 /* Normal item */
2000 TABP_TABITEM,
2001 TABP_TABITEMLEFTEDGE,
2002 TABP_TABITEMRIGHTEDGE,
2003 TABP_TABITEMBOTHEDGE,
2004 /* Selected tab */
2005 TABP_TOPTABITEM,
2006 TABP_TOPTABITEMLEFTEDGE,
2007 TABP_TOPTABITEMRIGHTEDGE,
2008 TABP_TOPTABITEMBOTHEDGE,
2010 int partIndex = 0;
2011 int stateId = TIS_NORMAL;
2013 /* selected and unselected tabs have different parts */
2014 if (iItem == infoPtr->iSelected)
2015 partIndex += 4;
2016 /* The part also differs on the position of a tab on a line.
2017 * "Visually" determining the position works well enough. */
2018 if(selectedRect.left == 0)
2019 partIndex += 1;
2020 if(selectedRect.right == clRight)
2021 partIndex += 2;
2023 if (iItem == infoPtr->iSelected)
2024 stateId = TIS_SELECTED;
2025 else if (iItem == infoPtr->iHotTracked)
2026 stateId = TIS_HOT;
2027 else if (iItem == infoPtr->uFocus)
2028 stateId = TIS_FOCUSED;
2030 /* Adjust rectangle for bottommost row */
2031 if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2032 r.bottom += 3;
2034 DrawThemeBackground (theme, hdc, partIds[partIndex], stateId, &r, NULL);
2035 GetThemeBackgroundContentRect (theme, hdc, partIds[partIndex], stateId, &r, &r);
2037 else if(lStyle & TCS_VERTICAL)
2039 /* These are for adjusting the drawing of a Selected tab */
2040 /* The initial values are for the normal case of non-Selected */
2041 int ZZ = 1; /* Do not stretch if selected */
2042 if (iItem == infoPtr->iSelected) {
2043 ZZ = 0;
2045 /* if leftmost draw the line longer */
2046 if(selectedRect.top == 0)
2047 fillRect.top += CONTROL_BORDER_SIZEY;
2048 /* if rightmost draw the line longer */
2049 if(selectedRect.bottom == clBottom)
2050 fillRect.bottom -= CONTROL_BORDER_SIZEY;
2053 if (lStyle & TCS_BOTTOM)
2055 /* Adjust both rectangles to match native */
2056 r.left += (1-ZZ);
2058 TRACE("<right> item=%d, fill=(%s), edge=(%s)\n",
2059 iItem, wine_dbgstr_rect(&fillRect), wine_dbgstr_rect(&r));
2061 /* Clear interior */
2062 SetBkColor(hdc, bkgnd);
2063 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2065 /* Draw rectangular edge around tab */
2066 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_RIGHT|BF_TOP|BF_BOTTOM);
2068 /* Now erase the top corner and draw diagonal edge */
2069 SetBkColor(hdc, corner);
2070 r1.left = r.right - ROUND_CORNER_SIZE - 1;
2071 r1.top = r.top;
2072 r1.right = r.right;
2073 r1.bottom = r1.top + ROUND_CORNER_SIZE;
2074 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2075 r1.right--;
2076 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPLEFT);
2078 /* Now erase the bottom corner and draw diagonal edge */
2079 r1.left = r.right - ROUND_CORNER_SIZE - 1;
2080 r1.bottom = r.bottom;
2081 r1.right = r.right;
2082 r1.top = r1.bottom - ROUND_CORNER_SIZE;
2083 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2084 r1.right--;
2085 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMLEFT);
2087 if ((iItem == infoPtr->iSelected) && (selectedRect.top == 0)) {
2088 r1 = r;
2089 r1.right = r1.left;
2090 r1.left--;
2091 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_TOP);
2095 else
2097 TRACE("<left> item=%d, fill=(%s), edge=(%s)\n",
2098 iItem, wine_dbgstr_rect(&fillRect), wine_dbgstr_rect(&r));
2100 /* Clear interior */
2101 SetBkColor(hdc, bkgnd);
2102 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2104 /* Draw rectangular edge around tab */
2105 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_TOP|BF_BOTTOM);
2107 /* Now erase the top corner and draw diagonal edge */
2108 SetBkColor(hdc, corner);
2109 r1.left = r.left;
2110 r1.top = r.top;
2111 r1.right = r1.left + ROUND_CORNER_SIZE + 1;
2112 r1.bottom = r1.top + ROUND_CORNER_SIZE;
2113 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2114 r1.left++;
2115 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPRIGHT);
2117 /* Now erase the bottom corner and draw diagonal edge */
2118 r1.left = r.left;
2119 r1.bottom = r.bottom;
2120 r1.right = r1.left + ROUND_CORNER_SIZE + 1;
2121 r1.top = r1.bottom - ROUND_CORNER_SIZE;
2122 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2123 r1.left++;
2124 DrawEdge(hdc, &r1, EDGE_SUNKEN, BF_DIAGONAL_ENDTOPLEFT);
2127 else /* ! TCS_VERTICAL */
2129 /* These are for adjusting the drawing of a Selected tab */
2130 /* The initial values are for the normal case of non-Selected */
2131 if (iItem == infoPtr->iSelected) {
2132 /* if leftmost draw the line longer */
2133 if(selectedRect.left == 0)
2134 fillRect.left += CONTROL_BORDER_SIZEX;
2135 /* if rightmost draw the line longer */
2136 if(selectedRect.right == clRight)
2137 fillRect.right -= CONTROL_BORDER_SIZEX;
2140 if (lStyle & TCS_BOTTOM)
2142 /* Adjust both rectangles for topmost row */
2143 if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2145 fillRect.top -= 2;
2146 r.top -= 1;
2149 TRACE("<bottom> item=%d, fill=(%s), edge=(%s)\n",
2150 iItem, wine_dbgstr_rect(&fillRect), wine_dbgstr_rect(&r));
2152 /* Clear interior */
2153 SetBkColor(hdc, bkgnd);
2154 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2156 /* Draw rectangular edge around tab */
2157 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_BOTTOM|BF_RIGHT);
2159 /* Now erase the righthand corner and draw diagonal edge */
2160 SetBkColor(hdc, corner);
2161 r1.left = r.right - ROUND_CORNER_SIZE;
2162 r1.bottom = r.bottom;
2163 r1.right = r.right;
2164 r1.top = r1.bottom - ROUND_CORNER_SIZE - 1;
2165 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2166 r1.bottom--;
2167 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMLEFT);
2169 /* Now erase the lefthand corner and draw diagonal edge */
2170 r1.left = r.left;
2171 r1.bottom = r.bottom;
2172 r1.right = r1.left + ROUND_CORNER_SIZE;
2173 r1.top = r1.bottom - ROUND_CORNER_SIZE - 1;
2174 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2175 r1.bottom--;
2176 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPLEFT);
2178 if (iItem == infoPtr->iSelected)
2180 r.top += 2;
2181 r.left += 1;
2182 if (selectedRect.left == 0)
2184 r1 = r;
2185 r1.bottom = r1.top;
2186 r1.top--;
2187 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_LEFT);
2192 else
2194 /* Adjust both rectangles for bottommost row */
2195 if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2197 fillRect.bottom += 3;
2198 r.bottom += 2;
2201 TRACE("<top> item=%d, fill=(%s), edge=(%s)\n",
2202 iItem, wine_dbgstr_rect(&fillRect), wine_dbgstr_rect(&r));
2204 /* Clear interior */
2205 SetBkColor(hdc, bkgnd);
2206 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2208 /* Draw rectangular edge around tab */
2209 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_TOP|BF_RIGHT);
2211 /* Now erase the righthand corner and draw diagonal edge */
2212 SetBkColor(hdc, corner);
2213 r1.left = r.right - ROUND_CORNER_SIZE;
2214 r1.top = r.top;
2215 r1.right = r.right;
2216 r1.bottom = r1.top + ROUND_CORNER_SIZE + 1;
2217 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2218 r1.top++;
2219 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMRIGHT);
2221 /* Now erase the lefthand corner and draw diagonal edge */
2222 r1.left = r.left;
2223 r1.top = r.top;
2224 r1.right = r1.left + ROUND_CORNER_SIZE;
2225 r1.bottom = r1.top + ROUND_CORNER_SIZE + 1;
2226 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2227 r1.top++;
2228 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPRIGHT);
2233 TAB_DumpItemInternal(infoPtr, iItem);
2235 /* This modifies r to be the text rectangle. */
2236 TAB_DrawItemInterior(infoPtr, hdc, iItem, &r);
2240 /******************************************************************************
2241 * TAB_DrawBorder
2243 * This method is used to draw the raised border around the tab control
2244 * "content" area.
2246 static void TAB_DrawBorder(const TAB_INFO *infoPtr, HDC hdc)
2248 RECT rect;
2249 DWORD lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
2250 HTHEME theme = GetWindowTheme (infoPtr->hwnd);
2252 GetClientRect (infoPtr->hwnd, &rect);
2255 * Adjust for the style
2258 if (infoPtr->uNumItem)
2260 if ((lStyle & TCS_BOTTOM) && !(lStyle & TCS_VERTICAL))
2261 rect.bottom -= infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2262 else if((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
2263 rect.right -= infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2264 else if(lStyle & TCS_VERTICAL)
2265 rect.left += infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2266 else /* not TCS_VERTICAL and not TCS_BOTTOM */
2267 rect.top += infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2270 TRACE("border=(%s)\n", wine_dbgstr_rect(&rect));
2272 if (theme)
2273 DrawThemeBackground (theme, hdc, TABP_PANE, 0, &rect, NULL);
2274 else
2275 DrawEdge(hdc, &rect, EDGE_RAISED, BF_SOFT|BF_RECT);
2278 /******************************************************************************
2279 * TAB_Refresh
2281 * This method repaints the tab control..
2283 static void TAB_Refresh (TAB_INFO *infoPtr, HDC hdc)
2285 HFONT hOldFont;
2286 INT i;
2288 if (!infoPtr->DoRedraw)
2289 return;
2291 hOldFont = SelectObject (hdc, infoPtr->hFont);
2293 if (GetWindowLongW(infoPtr->hwnd, GWL_STYLE) & TCS_BUTTONS)
2295 for (i = 0; i < infoPtr->uNumItem; i++)
2296 TAB_DrawItem (infoPtr, hdc, i);
2298 else
2300 /* Draw all the non selected item first */
2301 for (i = 0; i < infoPtr->uNumItem; i++)
2303 if (i != infoPtr->iSelected)
2304 TAB_DrawItem (infoPtr, hdc, i);
2307 /* Now, draw the border, draw it before the selected item
2308 * since the selected item overwrites part of the border. */
2309 TAB_DrawBorder (infoPtr, hdc);
2311 /* Then, draw the selected item */
2312 TAB_DrawItem (infoPtr, hdc, infoPtr->iSelected);
2315 SelectObject (hdc, hOldFont);
2318 static inline DWORD TAB_GetRowCount (const TAB_INFO *infoPtr)
2320 return infoPtr->uNumRows;
2323 static inline LRESULT TAB_SetRedraw (TAB_INFO *infoPtr, BOOL doRedraw)
2325 infoPtr->DoRedraw = doRedraw;
2326 return 0;
2329 /******************************************************************************
2330 * TAB_EnsureSelectionVisible
2332 * This method will make sure that the current selection is completely
2333 * visible by scrolling until it is.
2335 static void TAB_EnsureSelectionVisible(
2336 TAB_INFO* infoPtr)
2338 INT iSelected = infoPtr->iSelected;
2339 LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
2340 INT iOrigLeftmostVisible = infoPtr->leftmostVisible;
2342 /* set the items row to the bottommost row or topmost row depending on
2343 * style */
2344 if ((infoPtr->uNumRows > 1) && !(lStyle & TCS_BUTTONS))
2346 TAB_ITEM *selected = TAB_GetItem(infoPtr, iSelected);
2347 INT newselected;
2348 INT iTargetRow;
2350 if(lStyle & TCS_VERTICAL)
2351 newselected = selected->rect.left;
2352 else
2353 newselected = selected->rect.top;
2355 /* the target row is always (number of rows - 1)
2356 as row 0 is furthest from the clientRect */
2357 iTargetRow = infoPtr->uNumRows - 1;
2359 if (newselected != iTargetRow)
2361 UINT i;
2362 if(lStyle & TCS_VERTICAL)
2364 for (i=0; i < infoPtr->uNumItem; i++)
2366 /* move everything in the row of the selected item to the iTargetRow */
2367 TAB_ITEM *item = TAB_GetItem(infoPtr, i);
2369 if (item->rect.left == newselected )
2370 item->rect.left = iTargetRow;
2371 else
2373 if (item->rect.left > newselected)
2374 item->rect.left-=1;
2378 else
2380 for (i=0; i < infoPtr->uNumItem; i++)
2382 TAB_ITEM *item = TAB_GetItem(infoPtr, i);
2384 if (item->rect.top == newselected )
2385 item->rect.top = iTargetRow;
2386 else
2388 if (item->rect.top > newselected)
2389 item->rect.top-=1;
2393 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
2398 * Do the trivial cases first.
2400 if ( (!infoPtr->needsScrolling) ||
2401 (infoPtr->hwndUpDown==0) || (lStyle & TCS_VERTICAL))
2402 return;
2404 if (infoPtr->leftmostVisible >= iSelected)
2406 infoPtr->leftmostVisible = iSelected;
2408 else
2410 TAB_ITEM *selected = TAB_GetItem(infoPtr, iSelected);
2411 RECT r;
2412 INT width;
2413 UINT i;
2415 /* Calculate the part of the client area that is visible */
2416 GetClientRect(infoPtr->hwnd, &r);
2417 width = r.right;
2419 GetClientRect(infoPtr->hwndUpDown, &r);
2420 width -= r.right;
2422 if ((selected->rect.right -
2423 selected->rect.left) >= width )
2425 /* Special case: width of selected item is greater than visible
2426 * part of control.
2428 infoPtr->leftmostVisible = iSelected;
2430 else
2432 for (i = infoPtr->leftmostVisible; i < infoPtr->uNumItem; i++)
2434 if ((selected->rect.right - TAB_GetItem(infoPtr, i)->rect.left) < width)
2435 break;
2437 infoPtr->leftmostVisible = i;
2441 if (infoPtr->leftmostVisible != iOrigLeftmostVisible)
2442 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
2444 SendMessageW(infoPtr->hwndUpDown, UDM_SETPOS, 0,
2445 MAKELONG(infoPtr->leftmostVisible, 0));
2448 /******************************************************************************
2449 * TAB_InvalidateTabArea
2451 * This method will invalidate the portion of the control that contains the
2452 * tabs. It is called when the state of the control changes and needs
2453 * to be redisplayed
2455 static void TAB_InvalidateTabArea(const TAB_INFO *infoPtr)
2457 RECT clientRect, rInvalidate, rAdjClient;
2458 DWORD lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
2459 INT lastRow = infoPtr->uNumRows - 1;
2460 RECT rect;
2462 if (lastRow < 0) return;
2464 GetClientRect(infoPtr->hwnd, &clientRect);
2465 rInvalidate = clientRect;
2466 rAdjClient = clientRect;
2468 TAB_AdjustRect(infoPtr, 0, &rAdjClient);
2470 TAB_InternalGetItemRect(infoPtr, infoPtr->uNumItem-1 , &rect, NULL);
2471 if ((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
2473 rInvalidate.left = rAdjClient.right;
2474 if (infoPtr->uNumRows == 1)
2475 rInvalidate.bottom = clientRect.top + rect.bottom + 2 * SELECTED_TAB_OFFSET;
2477 else if(lStyle & TCS_VERTICAL)
2479 rInvalidate.right = rAdjClient.left;
2480 if (infoPtr->uNumRows == 1)
2481 rInvalidate.bottom = clientRect.top + rect.bottom + 2 * SELECTED_TAB_OFFSET;
2483 else if (lStyle & TCS_BOTTOM)
2485 rInvalidate.top = rAdjClient.bottom;
2486 if (infoPtr->uNumRows == 1)
2487 rInvalidate.right = clientRect.left + rect.right + 2 * SELECTED_TAB_OFFSET;
2489 else
2491 rInvalidate.bottom = rAdjClient.top;
2492 if (infoPtr->uNumRows == 1)
2493 rInvalidate.right = clientRect.left + rect.right + 2 * SELECTED_TAB_OFFSET;
2496 /* Punch out the updown control */
2497 if (infoPtr->needsScrolling && (rInvalidate.right > 0)) {
2498 RECT r;
2499 GetClientRect(infoPtr->hwndUpDown, &r);
2500 if (rInvalidate.right > clientRect.right - r.left)
2501 rInvalidate.right = rInvalidate.right - (r.right - r.left);
2502 else
2503 rInvalidate.right = clientRect.right - r.left;
2506 TRACE("invalidate (%s)\n", wine_dbgstr_rect(&rInvalidate));
2508 InvalidateRect(infoPtr->hwnd, &rInvalidate, TRUE);
2511 static inline LRESULT TAB_Paint (TAB_INFO *infoPtr, HDC hdcPaint)
2513 HDC hdc;
2514 PAINTSTRUCT ps;
2516 if (hdcPaint)
2517 hdc = hdcPaint;
2518 else
2520 hdc = BeginPaint (infoPtr->hwnd, &ps);
2521 TRACE("erase %d, rect=(%s)\n", ps.fErase, wine_dbgstr_rect(&ps.rcPaint));
2524 TAB_Refresh (infoPtr, hdc);
2526 if (!hdcPaint)
2527 EndPaint (infoPtr->hwnd, &ps);
2529 return 0;
2532 static LRESULT
2533 TAB_InsertItemT (TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam, BOOL bUnicode)
2535 TAB_ITEM *item;
2536 TCITEMW *pti;
2537 INT iItem;
2538 RECT rect;
2540 GetClientRect (infoPtr->hwnd, &rect);
2541 TRACE("Rect: %p %s\n", infoPtr->hwnd, wine_dbgstr_rect(&rect));
2543 pti = (TCITEMW *)lParam;
2544 iItem = (INT)wParam;
2546 if (iItem < 0) return -1;
2547 if (iItem > infoPtr->uNumItem)
2548 iItem = infoPtr->uNumItem;
2550 TAB_DumpItemExternalT(pti, iItem, bUnicode);
2553 if (infoPtr->uNumItem == 0) {
2554 infoPtr->items = Alloc (TAB_ITEM_SIZE(infoPtr));
2555 infoPtr->uNumItem++;
2556 infoPtr->iSelected = 0;
2558 else {
2559 LPBYTE oldItems = (LPBYTE)infoPtr->items;
2561 infoPtr->uNumItem++;
2562 infoPtr->items = Alloc (TAB_ITEM_SIZE(infoPtr) * infoPtr->uNumItem);
2564 /* pre insert copy */
2565 if (iItem > 0) {
2566 memcpy (infoPtr->items, oldItems,
2567 iItem * TAB_ITEM_SIZE(infoPtr));
2570 /* post insert copy */
2571 if (iItem < infoPtr->uNumItem - 1) {
2572 memcpy (TAB_GetItem(infoPtr, iItem + 1),
2573 oldItems + iItem * TAB_ITEM_SIZE(infoPtr),
2574 (infoPtr->uNumItem - iItem - 1) * TAB_ITEM_SIZE(infoPtr));
2578 if (iItem <= infoPtr->iSelected)
2579 infoPtr->iSelected++;
2581 Free (oldItems);
2584 item = TAB_GetItem(infoPtr, iItem);
2586 item->pszText = NULL;
2588 if (pti->mask & TCIF_TEXT)
2590 if (bUnicode)
2591 Str_SetPtrW (&item->pszText, pti->pszText);
2592 else
2593 Str_SetPtrAtoW (&item->pszText, (LPSTR)pti->pszText);
2596 if (pti->mask & TCIF_IMAGE)
2597 item->iImage = pti->iImage;
2598 else
2599 item->iImage = -1;
2601 if (pti->mask & TCIF_PARAM)
2602 memcpy(item->extra, &pti->lParam, infoPtr->cbInfo);
2603 else
2604 memset(item->extra, 0, infoPtr->cbInfo);
2606 TAB_SetItemBounds(infoPtr);
2607 if (infoPtr->uNumItem > 1)
2608 TAB_InvalidateTabArea(infoPtr);
2609 else
2610 InvalidateRect(infoPtr->hwnd, NULL, TRUE);
2612 TRACE("[%p]: added item %d %s\n",
2613 infoPtr->hwnd, iItem, debugstr_w(item->pszText));
2615 /* If we haven't set the current focus yet, set it now. */
2616 if (infoPtr->uFocus == -1)
2617 TAB_SetCurFocus(infoPtr, iItem);
2619 return iItem;
2622 static LRESULT
2623 TAB_SetItemSize (TAB_INFO *infoPtr, LPARAM lParam)
2625 LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
2626 LONG lResult = 0;
2627 BOOL bNeedPaint = FALSE;
2629 lResult = MAKELONG(infoPtr->tabWidth, infoPtr->tabHeight);
2631 /* UNDOCUMENTED: If requested Width or Height is 0 this means that program wants to use auto size. */
2632 if (lStyle & TCS_FIXEDWIDTH && (infoPtr->tabWidth != (INT)LOWORD(lParam)))
2634 infoPtr->tabWidth = (INT)LOWORD(lParam);
2635 bNeedPaint = TRUE;
2638 if (infoPtr->tabHeight != (INT)HIWORD(lParam))
2640 if ((infoPtr->fHeightSet = ((INT)HIWORD(lParam) != 0)))
2641 infoPtr->tabHeight = (INT)HIWORD(lParam);
2643 bNeedPaint = TRUE;
2645 TRACE("was h=%d,w=%d, now h=%d,w=%d\n",
2646 HIWORD(lResult), LOWORD(lResult),
2647 infoPtr->tabHeight, infoPtr->tabWidth);
2649 if (bNeedPaint)
2651 TAB_SetItemBounds(infoPtr);
2652 RedrawWindow(infoPtr->hwnd, NULL, NULL, RDW_ERASE | RDW_INVALIDATE | RDW_UPDATENOW);
2655 return lResult;
2658 static inline LRESULT TAB_SetMinTabWidth (TAB_INFO *infoPtr, INT cx)
2660 INT oldcx = 0;
2662 TRACE("(%p,%d)\n", infoPtr, cx);
2664 oldcx = infoPtr->tabMinWidth;
2665 infoPtr->tabMinWidth = cx;
2666 TAB_SetItemBounds(infoPtr);
2667 return oldcx;
2670 static inline LRESULT
2671 TAB_HighlightItem (TAB_INFO *infoPtr, INT iItem, BOOL fHighlight)
2673 LPDWORD lpState;
2675 TRACE("(%p,%d,%s)\n", infoPtr, iItem, fHighlight ? "true" : "false");
2677 if (!infoPtr || iItem < 0 || iItem >= infoPtr->uNumItem)
2678 return FALSE;
2680 lpState = &TAB_GetItem(infoPtr, iItem)->dwState;
2682 if (fHighlight)
2683 *lpState |= TCIS_HIGHLIGHTED;
2684 else
2685 *lpState &= ~TCIS_HIGHLIGHTED;
2687 return TRUE;
2690 static LRESULT
2691 TAB_SetItemT (TAB_INFO *infoPtr, INT iItem, LPTCITEMW tabItem, BOOL bUnicode)
2693 TAB_ITEM *wineItem;
2695 TRACE("(%p,%d,%p,%s)\n", infoPtr, iItem, tabItem, bUnicode ? "true" : "false");
2697 if (iItem < 0 || iItem >= infoPtr->uNumItem)
2698 return FALSE;
2700 TAB_DumpItemExternalT(tabItem, iItem, bUnicode);
2702 wineItem = TAB_GetItem(infoPtr, iItem);
2704 if (tabItem->mask & TCIF_IMAGE)
2705 wineItem->iImage = tabItem->iImage;
2707 if (tabItem->mask & TCIF_PARAM)
2708 memcpy(wineItem->extra, &tabItem->lParam, infoPtr->cbInfo);
2710 if (tabItem->mask & TCIF_RTLREADING)
2711 FIXME("TCIF_RTLREADING\n");
2713 if (tabItem->mask & TCIF_STATE)
2714 wineItem->dwState = tabItem->dwState;
2716 if (tabItem->mask & TCIF_TEXT)
2718 Free(wineItem->pszText);
2719 wineItem->pszText = NULL;
2720 if (bUnicode)
2721 Str_SetPtrW(&wineItem->pszText, tabItem->pszText);
2722 else
2723 Str_SetPtrAtoW(&wineItem->pszText, (LPSTR)tabItem->pszText);
2726 /* Update and repaint tabs */
2727 TAB_SetItemBounds(infoPtr);
2728 TAB_InvalidateTabArea(infoPtr);
2730 return TRUE;
2733 static inline LRESULT TAB_GetItemCount (const TAB_INFO *infoPtr)
2735 return infoPtr->uNumItem;
2739 static LRESULT
2740 TAB_GetItemT (TAB_INFO *infoPtr, INT iItem, LPTCITEMW tabItem, BOOL bUnicode)
2742 TAB_ITEM *wineItem;
2744 TRACE("(%p,%d,%p,%s)\n", infoPtr, iItem, tabItem, bUnicode ? "true" : "false");
2746 if (iItem < 0 || iItem >= infoPtr->uNumItem)
2747 return FALSE;
2749 wineItem = TAB_GetItem(infoPtr, iItem);
2751 if (tabItem->mask & TCIF_IMAGE)
2752 tabItem->iImage = wineItem->iImage;
2754 if (tabItem->mask & TCIF_PARAM)
2755 memcpy(&tabItem->lParam, wineItem->extra, infoPtr->cbInfo);
2757 if (tabItem->mask & TCIF_RTLREADING)
2758 FIXME("TCIF_RTLREADING\n");
2760 if (tabItem->mask & TCIF_STATE)
2761 tabItem->dwState = wineItem->dwState;
2763 if (tabItem->mask & TCIF_TEXT)
2765 if (bUnicode)
2766 Str_GetPtrW (wineItem->pszText, tabItem->pszText, tabItem->cchTextMax);
2767 else
2768 Str_GetPtrWtoA (wineItem->pszText, (LPSTR)tabItem->pszText, tabItem->cchTextMax);
2771 TAB_DumpItemExternalT(tabItem, iItem, bUnicode);
2773 return TRUE;
2777 static LRESULT TAB_DeleteItem (TAB_INFO *infoPtr, INT iItem)
2779 BOOL bResult = FALSE;
2781 TRACE("(%p, %d)\n", infoPtr, iItem);
2783 if ((iItem >= 0) && (iItem < infoPtr->uNumItem))
2785 TAB_ITEM *item = TAB_GetItem(infoPtr, iItem);
2786 LPBYTE oldItems = (LPBYTE)infoPtr->items;
2788 TAB_InvalidateTabArea(infoPtr);
2789 Free(item->pszText);
2790 infoPtr->uNumItem--;
2792 if (!infoPtr->uNumItem)
2794 infoPtr->items = NULL;
2795 if (infoPtr->iHotTracked >= 0)
2797 KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
2798 infoPtr->iHotTracked = -1;
2801 else
2803 infoPtr->items = Alloc(TAB_ITEM_SIZE(infoPtr) * infoPtr->uNumItem);
2805 if (iItem > 0)
2806 memcpy(infoPtr->items, oldItems, iItem * TAB_ITEM_SIZE(infoPtr));
2808 if (iItem < infoPtr->uNumItem)
2809 memcpy(TAB_GetItem(infoPtr, iItem),
2810 oldItems + (iItem + 1) * TAB_ITEM_SIZE(infoPtr),
2811 (infoPtr->uNumItem - iItem) * TAB_ITEM_SIZE(infoPtr));
2813 if (iItem <= infoPtr->iHotTracked)
2815 /* When tabs move left/up, the hot track item may change */
2816 FIXME("Recalc hot track\n");
2819 Free(oldItems);
2821 /* Readjust the selected index */
2822 if ((iItem == infoPtr->iSelected) && (iItem > 0))
2823 infoPtr->iSelected--;
2825 if (iItem < infoPtr->iSelected)
2826 infoPtr->iSelected--;
2828 if (infoPtr->uNumItem == 0)
2829 infoPtr->iSelected = -1;
2831 /* Reposition and repaint tabs */
2832 TAB_SetItemBounds(infoPtr);
2834 bResult = TRUE;
2837 return bResult;
2840 static inline LRESULT TAB_DeleteAllItems (TAB_INFO *infoPtr)
2842 TRACE("(%p)\n", infoPtr);
2843 while (infoPtr->uNumItem)
2844 TAB_DeleteItem (infoPtr, 0);
2845 return TRUE;
2849 static inline LRESULT TAB_GetFont (const TAB_INFO *infoPtr)
2851 TRACE("(%p) returning %p\n", infoPtr, infoPtr->hFont);
2852 return (LRESULT)infoPtr->hFont;
2855 static inline LRESULT TAB_SetFont (TAB_INFO *infoPtr, HFONT hNewFont)
2857 TRACE("(%p,%p)\n", infoPtr, hNewFont);
2859 infoPtr->hFont = hNewFont;
2861 TAB_SetItemBounds(infoPtr);
2863 TAB_InvalidateTabArea(infoPtr);
2865 return 0;
2869 static inline LRESULT TAB_GetImageList (const TAB_INFO *infoPtr)
2871 TRACE("\n");
2872 return (LRESULT)infoPtr->himl;
2875 static inline LRESULT TAB_SetImageList (TAB_INFO *infoPtr, HIMAGELIST himlNew)
2877 HIMAGELIST himlPrev = infoPtr->himl;
2878 TRACE("\n");
2879 infoPtr->himl = himlNew;
2880 TAB_SetItemBounds(infoPtr);
2881 InvalidateRect(infoPtr->hwnd, NULL, TRUE);
2882 return (LRESULT)himlPrev;
2885 static inline LRESULT TAB_GetUnicodeFormat (const TAB_INFO *infoPtr)
2887 return infoPtr->bUnicode;
2890 static inline LRESULT TAB_SetUnicodeFormat (TAB_INFO *infoPtr, BOOL bUnicode)
2892 BOOL bTemp = infoPtr->bUnicode;
2894 infoPtr->bUnicode = bUnicode;
2896 return bTemp;
2899 static inline LRESULT TAB_Size (TAB_INFO *infoPtr)
2901 /* I'm not really sure what the following code was meant to do.
2902 This is what it is doing:
2903 When WM_SIZE is sent with SIZE_RESTORED, the control
2904 gets positioned in the top left corner.
2906 RECT parent_rect;
2907 HWND parent;
2908 UINT uPosFlags,cx,cy;
2910 uPosFlags=0;
2911 if (!wParam) {
2912 parent = GetParent (hwnd);
2913 GetClientRect(parent, &parent_rect);
2914 cx=LOWORD (lParam);
2915 cy=HIWORD (lParam);
2916 if (GetWindowLongW(hwnd, GWL_STYLE) & CCS_NORESIZE)
2917 uPosFlags |= (SWP_NOSIZE | SWP_NOMOVE);
2919 SetWindowPos (hwnd, 0, parent_rect.left, parent_rect.top,
2920 cx, cy, uPosFlags | SWP_NOZORDER);
2921 } else {
2922 FIXME("WM_SIZE flag %x %lx not handled\n", wParam, lParam);
2923 } */
2925 /* Recompute the size/position of the tabs. */
2926 TAB_SetItemBounds (infoPtr);
2928 /* Force a repaint of the control. */
2929 InvalidateRect(infoPtr->hwnd, NULL, TRUE);
2931 return 0;
2935 static LRESULT TAB_Create (HWND hwnd, WPARAM wParam, LPARAM lParam)
2937 TAB_INFO *infoPtr;
2938 TEXTMETRICW fontMetrics;
2939 HDC hdc;
2940 HFONT hOldFont;
2941 DWORD dwStyle;
2943 infoPtr = (TAB_INFO *)Alloc (sizeof(TAB_INFO));
2945 SetWindowLongPtrW(hwnd, 0, (DWORD_PTR)infoPtr);
2947 infoPtr->hwnd = hwnd;
2948 infoPtr->hwndNotify = ((LPCREATESTRUCTW)lParam)->hwndParent;
2949 infoPtr->uNumItem = 0;
2950 infoPtr->uNumRows = 0;
2951 infoPtr->uHItemPadding = 6;
2952 infoPtr->uVItemPadding = 3;
2953 infoPtr->uHItemPadding_s = 6;
2954 infoPtr->uVItemPadding_s = 3;
2955 infoPtr->hFont = 0;
2956 infoPtr->items = 0;
2957 infoPtr->hcurArrow = LoadCursorW (0, (LPWSTR)IDC_ARROW);
2958 infoPtr->iSelected = -1;
2959 infoPtr->iHotTracked = -1;
2960 infoPtr->uFocus = -1;
2961 infoPtr->hwndToolTip = 0;
2962 infoPtr->DoRedraw = TRUE;
2963 infoPtr->needsScrolling = FALSE;
2964 infoPtr->hwndUpDown = 0;
2965 infoPtr->leftmostVisible = 0;
2966 infoPtr->fHeightSet = FALSE;
2967 infoPtr->bUnicode = IsWindowUnicode (hwnd);
2968 infoPtr->cbInfo = sizeof(LPARAM);
2970 TRACE("Created tab control, hwnd [%p]\n", hwnd);
2972 /* The tab control always has the WS_CLIPSIBLINGS style. Even
2973 if you don't specify it in CreateWindow. This is necessary in
2974 order for paint to work correctly. This follows windows behaviour. */
2975 dwStyle = GetWindowLongW(hwnd, GWL_STYLE);
2976 SetWindowLongW(hwnd, GWL_STYLE, dwStyle|WS_CLIPSIBLINGS);
2978 if (dwStyle & TCS_TOOLTIPS) {
2979 /* Create tooltip control */
2980 infoPtr->hwndToolTip =
2981 CreateWindowExW (0, TOOLTIPS_CLASSW, NULL, WS_POPUP,
2982 CW_USEDEFAULT, CW_USEDEFAULT,
2983 CW_USEDEFAULT, CW_USEDEFAULT,
2984 hwnd, 0, 0, 0);
2986 /* Send NM_TOOLTIPSCREATED notification */
2987 if (infoPtr->hwndToolTip) {
2988 NMTOOLTIPSCREATED nmttc;
2990 nmttc.hdr.hwndFrom = hwnd;
2991 nmttc.hdr.idFrom = GetWindowLongPtrW(hwnd, GWLP_ID);
2992 nmttc.hdr.code = NM_TOOLTIPSCREATED;
2993 nmttc.hwndToolTips = infoPtr->hwndToolTip;
2995 SendMessageW (infoPtr->hwndNotify, WM_NOTIFY,
2996 (WPARAM)GetWindowLongPtrW(hwnd, GWLP_ID), (LPARAM)&nmttc);
3000 OpenThemeData (infoPtr->hwnd, themeClass);
3003 * We need to get text information so we need a DC and we need to select
3004 * a font.
3006 hdc = GetDC(hwnd);
3007 hOldFont = SelectObject (hdc, GetStockObject (SYSTEM_FONT));
3009 /* Use the system font to determine the initial height of a tab. */
3010 GetTextMetricsW(hdc, &fontMetrics);
3013 * Make sure there is enough space for the letters + growing the
3014 * selected item + extra space for the selected item.
3016 infoPtr->tabHeight = fontMetrics.tmHeight + SELECTED_TAB_OFFSET +
3017 ((dwStyle & TCS_BUTTONS) ? 2 : 1) *
3018 infoPtr->uVItemPadding;
3020 /* Initialize the width of a tab. */
3021 if (dwStyle & TCS_FIXEDWIDTH)
3022 infoPtr->tabWidth = DEFAULT_TAB_WIDTH_FIXED;
3024 infoPtr->tabMinWidth = -1;
3026 TRACE("tabH=%d, tabW=%d\n", infoPtr->tabHeight, infoPtr->tabWidth);
3028 SelectObject (hdc, hOldFont);
3029 ReleaseDC(hwnd, hdc);
3031 return 0;
3034 static LRESULT
3035 TAB_Destroy (TAB_INFO *infoPtr)
3037 UINT iItem;
3039 if (!infoPtr)
3040 return 0;
3042 SetWindowLongPtrW(infoPtr->hwnd, 0, 0);
3044 if (infoPtr->items) {
3045 for (iItem = 0; iItem < infoPtr->uNumItem; iItem++) {
3046 Free (TAB_GetItem(infoPtr, iItem)->pszText);
3048 Free (infoPtr->items);
3051 if (infoPtr->hwndToolTip)
3052 DestroyWindow (infoPtr->hwndToolTip);
3054 if (infoPtr->hwndUpDown)
3055 DestroyWindow(infoPtr->hwndUpDown);
3057 if (infoPtr->iHotTracked >= 0)
3058 KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
3060 CloseThemeData (GetWindowTheme (infoPtr->hwnd));
3062 Free (infoPtr);
3063 return 0;
3066 /* update theme after a WM_THEMECHANGED message */
3067 static LRESULT theme_changed(const TAB_INFO *infoPtr)
3069 HTHEME theme = GetWindowTheme (infoPtr->hwnd);
3070 CloseThemeData (theme);
3071 OpenThemeData (infoPtr->hwnd, themeClass);
3072 return 0;
3075 static LRESULT TAB_NCCalcSize(HWND hwnd, WPARAM wParam, LPARAM lParam)
3077 if (!wParam)
3078 return 0;
3079 return WVR_ALIGNTOP;
3082 static inline LRESULT
3083 TAB_SetItemExtra (TAB_INFO *infoPtr, INT cbInfo)
3085 if (!infoPtr || cbInfo <= 0)
3086 return FALSE;
3088 if (infoPtr->uNumItem)
3090 /* FIXME: MSDN says this is not allowed, but this hasn't been verified */
3091 return FALSE;
3094 infoPtr->cbInfo = cbInfo;
3095 return TRUE;
3098 static LRESULT WINAPI
3099 TAB_WindowProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
3101 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
3103 TRACE("hwnd=%p msg=%x wParam=%lx lParam=%lx\n", hwnd, uMsg, wParam, lParam);
3104 if (!infoPtr && (uMsg != WM_CREATE))
3105 return DefWindowProcW (hwnd, uMsg, wParam, lParam);
3107 switch (uMsg)
3109 case TCM_GETIMAGELIST:
3110 return TAB_GetImageList (infoPtr);
3112 case TCM_SETIMAGELIST:
3113 return TAB_SetImageList (infoPtr, (HIMAGELIST)lParam);
3115 case TCM_GETITEMCOUNT:
3116 return TAB_GetItemCount (infoPtr);
3118 case TCM_GETITEMA:
3119 case TCM_GETITEMW:
3120 return TAB_GetItemT (infoPtr, (INT)wParam, (LPTCITEMW)lParam, uMsg == TCM_GETITEMW);
3122 case TCM_SETITEMA:
3123 case TCM_SETITEMW:
3124 return TAB_SetItemT (infoPtr, (INT)wParam, (LPTCITEMW)lParam, uMsg == TCM_SETITEMW);
3126 case TCM_DELETEITEM:
3127 return TAB_DeleteItem (infoPtr, (INT)wParam);
3129 case TCM_DELETEALLITEMS:
3130 return TAB_DeleteAllItems (infoPtr);
3132 case TCM_GETITEMRECT:
3133 return TAB_GetItemRect (infoPtr, wParam, lParam);
3135 case TCM_GETCURSEL:
3136 return TAB_GetCurSel (infoPtr);
3138 case TCM_HITTEST:
3139 return TAB_HitTest (infoPtr, (LPTCHITTESTINFO)lParam);
3141 case TCM_SETCURSEL:
3142 return TAB_SetCurSel (infoPtr, (INT)wParam);
3144 case TCM_INSERTITEMA:
3145 case TCM_INSERTITEMW:
3146 return TAB_InsertItemT (infoPtr, wParam, lParam, uMsg == TCM_INSERTITEMW);
3148 case TCM_SETITEMEXTRA:
3149 return TAB_SetItemExtra (infoPtr, (int)wParam);
3151 case TCM_ADJUSTRECT:
3152 return TAB_AdjustRect (infoPtr, (BOOL)wParam, (LPRECT)lParam);
3154 case TCM_SETITEMSIZE:
3155 return TAB_SetItemSize (infoPtr, lParam);
3157 case TCM_REMOVEIMAGE:
3158 FIXME("Unimplemented msg TCM_REMOVEIMAGE\n");
3159 return 0;
3161 case TCM_SETPADDING:
3162 return TAB_SetPadding (infoPtr, lParam);
3164 case TCM_GETROWCOUNT:
3165 return TAB_GetRowCount(infoPtr);
3167 case TCM_GETUNICODEFORMAT:
3168 return TAB_GetUnicodeFormat (infoPtr);
3170 case TCM_SETUNICODEFORMAT:
3171 return TAB_SetUnicodeFormat (infoPtr, (BOOL)wParam);
3173 case TCM_HIGHLIGHTITEM:
3174 return TAB_HighlightItem (infoPtr, (INT)wParam, (BOOL)LOWORD(lParam));
3176 case TCM_GETTOOLTIPS:
3177 return TAB_GetToolTips (infoPtr);
3179 case TCM_SETTOOLTIPS:
3180 return TAB_SetToolTips (infoPtr, (HWND)wParam);
3182 case TCM_GETCURFOCUS:
3183 return TAB_GetCurFocus (infoPtr);
3185 case TCM_SETCURFOCUS:
3186 return TAB_SetCurFocus (infoPtr, (INT)wParam);
3188 case TCM_SETMINTABWIDTH:
3189 return TAB_SetMinTabWidth(infoPtr, (INT)lParam);
3191 case TCM_DESELECTALL:
3192 FIXME("Unimplemented msg TCM_DESELECTALL\n");
3193 return 0;
3195 case TCM_GETEXTENDEDSTYLE:
3196 FIXME("Unimplemented msg TCM_GETEXTENDEDSTYLE\n");
3197 return 0;
3199 case TCM_SETEXTENDEDSTYLE:
3200 FIXME("Unimplemented msg TCM_SETEXTENDEDSTYLE\n");
3201 return 0;
3203 case WM_GETFONT:
3204 return TAB_GetFont (infoPtr);
3206 case WM_SETFONT:
3207 return TAB_SetFont (infoPtr, (HFONT)wParam);
3209 case WM_CREATE:
3210 return TAB_Create (hwnd, wParam, lParam);
3212 case WM_NCDESTROY:
3213 return TAB_Destroy (infoPtr);
3215 case WM_GETDLGCODE:
3216 return DLGC_WANTARROWS | DLGC_WANTCHARS;
3218 case WM_LBUTTONDOWN:
3219 return TAB_LButtonDown (infoPtr, wParam, lParam);
3221 case WM_LBUTTONUP:
3222 return TAB_LButtonUp (infoPtr);
3224 case WM_NOTIFY:
3225 return SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, wParam, lParam);
3227 case WM_RBUTTONDOWN:
3228 return TAB_RButtonDown (infoPtr);
3230 case WM_MOUSEMOVE:
3231 return TAB_MouseMove (infoPtr, wParam, lParam);
3233 case WM_PRINTCLIENT:
3234 case WM_PAINT:
3235 return TAB_Paint (infoPtr, (HDC)wParam);
3237 case WM_SIZE:
3238 return TAB_Size (infoPtr);
3240 case WM_SETREDRAW:
3241 return TAB_SetRedraw (infoPtr, (BOOL)wParam);
3243 case WM_HSCROLL:
3244 return TAB_OnHScroll(infoPtr, (int)LOWORD(wParam), (int)HIWORD(wParam), (HWND)lParam);
3246 case WM_STYLECHANGED:
3247 TAB_SetItemBounds (infoPtr);
3248 InvalidateRect(hwnd, NULL, TRUE);
3249 return 0;
3251 case WM_SYSCOLORCHANGE:
3252 COMCTL32_RefreshSysColors();
3253 return 0;
3255 case WM_THEMECHANGED:
3256 return theme_changed (infoPtr);
3258 case WM_KILLFOCUS:
3259 case WM_SETFOCUS:
3260 TAB_FocusChanging(infoPtr);
3261 break; /* Don't disturb normal focus behavior */
3263 case WM_KEYUP:
3264 return TAB_KeyUp(infoPtr, wParam);
3265 case WM_NCHITTEST:
3266 return TAB_NCHitTest(infoPtr, lParam);
3268 case WM_NCCALCSIZE:
3269 return TAB_NCCalcSize(hwnd, wParam, lParam);
3271 default:
3272 if (uMsg >= WM_USER && uMsg < WM_APP && !COMCTL32_IsReflectedMessage(uMsg))
3273 WARN("unknown msg %04x wp=%08lx lp=%08lx\n",
3274 uMsg, wParam, lParam);
3275 break;
3277 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
3281 void
3282 TAB_Register (void)
3284 WNDCLASSW wndClass;
3286 ZeroMemory (&wndClass, sizeof(WNDCLASSW));
3287 wndClass.style = CS_GLOBALCLASS | CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
3288 wndClass.lpfnWndProc = TAB_WindowProc;
3289 wndClass.cbClsExtra = 0;
3290 wndClass.cbWndExtra = sizeof(TAB_INFO *);
3291 wndClass.hCursor = LoadCursorW (0, (LPWSTR)IDC_ARROW);
3292 wndClass.hbrBackground = (HBRUSH)(COLOR_BTNFACE+1);
3293 wndClass.lpszClassName = WC_TABCONTROLW;
3295 RegisterClassW (&wndClass);
3299 void
3300 TAB_Unregister (void)
3302 UnregisterClassW (WC_TABCONTROLW, NULL);