comctl32/tab: Fix button background filling on TCS_BUTTONS.
[wine/testsucceed.git] / dlls / comctl32 / tab.c
blobb8f638d75234fc08abd381605cd33e291d970429
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_DESELECTALL
57 * TCM_GETEXTENDEDSTYLE
58 * TCM_SETEXTENDEDSTYLE
60 * Macros:
61 * TabCtrl_AdjustRect
65 #include <stdarg.h>
66 #include <string.h>
68 #include "windef.h"
69 #include "winbase.h"
70 #include "wingdi.h"
71 #include "winuser.h"
72 #include "winnls.h"
73 #include "commctrl.h"
74 #include "comctl32.h"
75 #include "uxtheme.h"
76 #include "tmschema.h"
77 #include "wine/debug.h"
78 #include <math.h>
80 WINE_DEFAULT_DEBUG_CHANNEL(tab);
82 typedef struct
84 DWORD dwState;
85 LPWSTR pszText;
86 INT iImage;
87 RECT rect; /* bounding rectangle of the item relative to the
88 * leftmost item (the leftmost item, 0, would have a
89 * "left" member of 0 in this rectangle)
91 * additionally the top member holds the row number
92 * and bottom is unused and should be 0 */
93 BYTE extra[1]; /* Space for caller supplied info, variable size */
94 } TAB_ITEM;
96 /* The size of a tab item depends on how much extra data is requested */
97 #define TAB_ITEM_SIZE(infoPtr) (FIELD_OFFSET(TAB_ITEM, extra[(infoPtr)->cbInfo]))
99 typedef struct
101 HWND hwnd; /* Tab control window */
102 HWND hwndNotify; /* notification window (parent) */
103 UINT uNumItem; /* number of tab items */
104 UINT uNumRows; /* number of tab rows */
105 INT tabHeight; /* height of the tab row */
106 INT tabWidth; /* width of tabs */
107 INT tabMinWidth; /* minimum width of items */
108 USHORT uHItemPadding; /* amount of horizontal padding, in pixels */
109 USHORT uVItemPadding; /* amount of vertical padding, in pixels */
110 USHORT uHItemPadding_s; /* Set amount of horizontal padding, in pixels */
111 USHORT uVItemPadding_s; /* Set amount of vertical padding, in pixels */
112 HFONT hFont; /* handle to the current font */
113 HCURSOR hcurArrow; /* handle to the current cursor */
114 HIMAGELIST himl; /* handle to an image list (may be 0) */
115 HWND hwndToolTip; /* handle to tab's tooltip */
116 INT leftmostVisible; /* Used for scrolling, this member contains
117 * the index of the first visible item */
118 INT iSelected; /* the currently selected item */
119 INT iHotTracked; /* the highlighted item under the mouse */
120 INT uFocus; /* item which has the focus */
121 TAB_ITEM* items; /* pointer to an array of TAB_ITEM's */
122 BOOL DoRedraw; /* flag for redrawing when tab contents is changed*/
123 BOOL needsScrolling; /* TRUE if the size of the tabs is greater than
124 * the size of the control */
125 BOOL fHeightSet; /* was the height of the tabs explicitly set? */
126 BOOL bUnicode; /* Unicode control? */
127 HWND hwndUpDown; /* Updown control used for scrolling */
128 INT cbInfo; /* Number of bytes of caller supplied info per tab */
129 } TAB_INFO;
131 /******************************************************************************
132 * Positioning constants
134 #define SELECTED_TAB_OFFSET 2
135 #define ROUND_CORNER_SIZE 2
136 #define DISPLAY_AREA_PADDINGX 2
137 #define DISPLAY_AREA_PADDINGY 2
138 #define CONTROL_BORDER_SIZEX 2
139 #define CONTROL_BORDER_SIZEY 2
140 #define BUTTON_SPACINGX 3
141 #define BUTTON_SPACINGY 3
142 #define FLAT_BTN_SPACINGX 8
143 #define DEFAULT_MIN_TAB_WIDTH 54
144 #define DEFAULT_PADDING_X 6
145 #define EXTRA_ICON_PADDING 3
147 #define TAB_GetInfoPtr(hwnd) ((TAB_INFO *)GetWindowLongPtrW(hwnd,0))
148 /* Since items are variable sized, cannot directly access them */
149 #define TAB_GetItem(info,i) \
150 ((TAB_ITEM*)((LPBYTE)info->items + (i) * TAB_ITEM_SIZE(info)))
152 #define GET_DEFAULT_MIN_TAB_WIDTH(infoPtr) (DEFAULT_MIN_TAB_WIDTH - (DEFAULT_PADDING_X - (infoPtr)->uHItemPadding) * 2)
154 /******************************************************************************
155 * Hot-tracking timer constants
157 #define TAB_HOTTRACK_TIMER 1
158 #define TAB_HOTTRACK_TIMER_INTERVAL 100 /* milliseconds */
160 static const WCHAR themeClass[] = { 'T','a','b',0 };
162 /******************************************************************************
163 * Prototypes
165 static void TAB_InvalidateTabArea(const TAB_INFO *);
166 static void TAB_EnsureSelectionVisible(TAB_INFO *);
167 static void TAB_DrawItemInterior(const TAB_INFO *, HDC, INT, RECT*);
169 static BOOL
170 TAB_SendSimpleNotify (const TAB_INFO *infoPtr, UINT code)
172 NMHDR nmhdr;
174 nmhdr.hwndFrom = infoPtr->hwnd;
175 nmhdr.idFrom = GetWindowLongPtrW(infoPtr->hwnd, GWLP_ID);
176 nmhdr.code = code;
178 return (BOOL) SendMessageW (infoPtr->hwndNotify, WM_NOTIFY,
179 nmhdr.idFrom, (LPARAM) &nmhdr);
182 static void
183 TAB_RelayEvent (HWND hwndTip, HWND hwndMsg, UINT uMsg,
184 WPARAM wParam, LPARAM lParam)
186 MSG msg;
188 msg.hwnd = hwndMsg;
189 msg.message = uMsg;
190 msg.wParam = wParam;
191 msg.lParam = lParam;
192 msg.time = GetMessageTime ();
193 msg.pt.x = (short)LOWORD(GetMessagePos ());
194 msg.pt.y = (short)HIWORD(GetMessagePos ());
196 SendMessageW (hwndTip, TTM_RELAYEVENT, 0, (LPARAM)&msg);
199 static void
200 TAB_DumpItemExternalT(const TCITEMW *pti, UINT iItem, BOOL isW)
202 if (TRACE_ON(tab)) {
203 TRACE("external tab %d, mask=0x%08x, dwState=0x%08x, dwStateMask=0x%08x, cchTextMax=0x%08x\n",
204 iItem, pti->mask, pti->dwState, pti->dwStateMask, pti->cchTextMax);
205 TRACE("external tab %d, iImage=%d, lParam=0x%08lx, pszTextW=%s\n",
206 iItem, pti->iImage, pti->lParam, isW ? debugstr_w(pti->pszText) : debugstr_a((LPSTR)pti->pszText));
210 static void
211 TAB_DumpItemInternal(const TAB_INFO *infoPtr, UINT iItem)
213 if (TRACE_ON(tab)) {
214 TAB_ITEM *ti;
216 ti = TAB_GetItem(infoPtr, iItem);
217 TRACE("tab %d, dwState=0x%08x, pszText=%s, iImage=%d\n",
218 iItem, ti->dwState, debugstr_w(ti->pszText), ti->iImage);
219 TRACE("tab %d, rect.left=%d, rect.top(row)=%d\n",
220 iItem, ti->rect.left, ti->rect.top);
224 /* RETURNS
225 * the index of the selected tab, or -1 if no tab is selected. */
226 static inline LRESULT TAB_GetCurSel (const TAB_INFO *infoPtr)
228 return infoPtr->iSelected;
231 /* RETURNS
232 * the index of the tab item that has the focus. */
233 static inline LRESULT
234 TAB_GetCurFocus (const TAB_INFO *infoPtr)
236 return infoPtr->uFocus;
239 static inline LRESULT TAB_GetToolTips (const TAB_INFO *infoPtr)
241 if (infoPtr == NULL) return 0;
242 return (LRESULT)infoPtr->hwndToolTip;
245 static inline LRESULT TAB_SetCurSel (TAB_INFO *infoPtr, INT iItem)
247 INT prevItem = infoPtr->iSelected;
249 if (iItem < 0)
250 infoPtr->iSelected=-1;
251 else if (iItem >= infoPtr->uNumItem)
252 return -1;
253 else {
254 if (infoPtr->iSelected != iItem) {
255 infoPtr->iSelected=iItem;
256 infoPtr->uFocus=iItem;
257 TAB_EnsureSelectionVisible(infoPtr);
258 TAB_InvalidateTabArea(infoPtr);
261 return prevItem;
264 static LRESULT TAB_SetCurFocus (TAB_INFO *infoPtr, INT iItem)
266 if (iItem < 0)
267 infoPtr->uFocus = -1;
268 else if (iItem < infoPtr->uNumItem) {
269 if (GetWindowLongW(infoPtr->hwnd, GWL_STYLE) & TCS_BUTTONS) {
270 FIXME("Should set input focus\n");
271 } else {
272 int oldFocus = infoPtr->uFocus;
273 if (infoPtr->iSelected != iItem || oldFocus == -1 ) {
274 infoPtr->uFocus = iItem;
275 if (oldFocus != -1) {
276 if (!TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGING)) {
277 infoPtr->iSelected = iItem;
278 TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGE);
280 else
281 infoPtr->iSelected = iItem;
282 TAB_EnsureSelectionVisible(infoPtr);
283 TAB_InvalidateTabArea(infoPtr);
288 return 0;
291 static inline LRESULT
292 TAB_SetToolTips (TAB_INFO *infoPtr, HWND hwndToolTip)
294 if (infoPtr)
295 infoPtr->hwndToolTip = hwndToolTip;
296 return 0;
299 static inline LRESULT
300 TAB_SetPadding (TAB_INFO *infoPtr, LPARAM lParam)
302 if (infoPtr)
304 infoPtr->uHItemPadding_s=LOWORD(lParam);
305 infoPtr->uVItemPadding_s=HIWORD(lParam);
307 return 0;
310 /******************************************************************************
311 * TAB_InternalGetItemRect
313 * This method will calculate the rectangle representing a given tab item in
314 * client coordinates. This method takes scrolling into account.
316 * This method returns TRUE if the item is visible in the window and FALSE
317 * if it is completely outside the client area.
319 static BOOL TAB_InternalGetItemRect(
320 const TAB_INFO* infoPtr,
321 INT itemIndex,
322 RECT* itemRect,
323 RECT* selectedRect)
325 RECT tmpItemRect,clientRect;
326 LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
328 /* Perform a sanity check and a trivial visibility check. */
329 if ( (infoPtr->uNumItem <= 0) ||
330 (itemIndex >= infoPtr->uNumItem) ||
331 (!((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)) && (itemIndex < infoPtr->leftmostVisible)) )
333 TRACE("Not Visible\n");
334 /* need to initialize these to empty rects */
335 if (itemRect)
337 memset(itemRect,0,sizeof(RECT));
338 itemRect->bottom = infoPtr->tabHeight;
340 if (selectedRect)
341 memset(selectedRect,0,sizeof(RECT));
342 return FALSE;
346 * Avoid special cases in this procedure by assigning the "out"
347 * parameters if the caller didn't supply them
349 if (itemRect == NULL)
350 itemRect = &tmpItemRect;
352 /* Retrieve the unmodified item rect. */
353 *itemRect = TAB_GetItem(infoPtr,itemIndex)->rect;
355 /* calculate the times bottom and top based on the row */
356 GetClientRect(infoPtr->hwnd, &clientRect);
358 if ((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
360 itemRect->right = clientRect.right - SELECTED_TAB_OFFSET - itemRect->left * infoPtr->tabHeight -
361 ((lStyle & TCS_BUTTONS) ? itemRect->left * BUTTON_SPACINGX : 0);
362 itemRect->left = itemRect->right - infoPtr->tabHeight;
364 else if (lStyle & TCS_VERTICAL)
366 itemRect->left = clientRect.left + SELECTED_TAB_OFFSET + itemRect->left * infoPtr->tabHeight +
367 ((lStyle & TCS_BUTTONS) ? itemRect->left * BUTTON_SPACINGX : 0);
368 itemRect->right = itemRect->left + infoPtr->tabHeight;
370 else if (lStyle & TCS_BOTTOM)
372 itemRect->bottom = clientRect.bottom - itemRect->top * infoPtr->tabHeight -
373 ((lStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : SELECTED_TAB_OFFSET);
374 itemRect->top = itemRect->bottom - infoPtr->tabHeight;
376 else /* not TCS_BOTTOM and not TCS_VERTICAL */
378 itemRect->top = clientRect.top + itemRect->top * infoPtr->tabHeight +
379 ((lStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : SELECTED_TAB_OFFSET);
380 itemRect->bottom = itemRect->top + infoPtr->tabHeight;
384 * "scroll" it to make sure the item at the very left of the
385 * tab control is the leftmost visible tab.
387 if(lStyle & TCS_VERTICAL)
389 OffsetRect(itemRect,
391 -TAB_GetItem(infoPtr, infoPtr->leftmostVisible)->rect.top);
394 * Move the rectangle so the first item is slightly offset from
395 * the bottom of the tab control.
397 OffsetRect(itemRect,
399 SELECTED_TAB_OFFSET);
401 } else
403 OffsetRect(itemRect,
404 -TAB_GetItem(infoPtr, infoPtr->leftmostVisible)->rect.left,
408 * Move the rectangle so the first item is slightly offset from
409 * the left of the tab control.
411 OffsetRect(itemRect,
412 SELECTED_TAB_OFFSET,
415 TRACE("item %d tab h=%d, rect=(%s)\n",
416 itemIndex, infoPtr->tabHeight, wine_dbgstr_rect(itemRect));
418 /* Now, calculate the position of the item as if it were selected. */
419 if (selectedRect!=NULL)
421 CopyRect(selectedRect, itemRect);
423 /* The rectangle of a selected item is a bit wider. */
424 if(lStyle & TCS_VERTICAL)
425 InflateRect(selectedRect, 0, SELECTED_TAB_OFFSET);
426 else
427 InflateRect(selectedRect, SELECTED_TAB_OFFSET, 0);
429 /* If it also a bit higher. */
430 if ((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
432 selectedRect->left -= 2; /* the border is thicker on the right */
433 selectedRect->right += SELECTED_TAB_OFFSET;
435 else if (lStyle & TCS_VERTICAL)
437 selectedRect->left -= SELECTED_TAB_OFFSET;
438 selectedRect->right += 1;
440 else if (lStyle & TCS_BOTTOM)
442 selectedRect->bottom += SELECTED_TAB_OFFSET;
444 else /* not TCS_BOTTOM and not TCS_VERTICAL */
446 selectedRect->top -= SELECTED_TAB_OFFSET;
447 selectedRect->bottom -= 1;
451 /* Check for visibility */
452 if (lStyle & TCS_VERTICAL)
453 return (itemRect->top < clientRect.bottom) && (itemRect->bottom > clientRect.top);
454 else
455 return (itemRect->left < clientRect.right) && (itemRect->right > clientRect.left);
458 static inline BOOL
459 TAB_GetItemRect(const TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
461 return TAB_InternalGetItemRect(infoPtr, wParam, (LPRECT)lParam, NULL);
464 /******************************************************************************
465 * TAB_KeyUp
467 * This method is called to handle keyboard input
469 static LRESULT TAB_KeyUp(TAB_INFO* infoPtr, WPARAM keyCode)
471 int newItem = -1;
473 switch (keyCode)
475 case VK_LEFT:
476 newItem = infoPtr->uFocus - 1;
477 break;
478 case VK_RIGHT:
479 newItem = infoPtr->uFocus + 1;
480 break;
484 * If we changed to a valid item, change the selection
486 if (newItem >= 0 &&
487 newItem < infoPtr->uNumItem &&
488 infoPtr->uFocus != newItem)
490 if (!TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGING))
492 infoPtr->iSelected = newItem;
493 infoPtr->uFocus = newItem;
494 TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGE);
496 TAB_EnsureSelectionVisible(infoPtr);
497 TAB_InvalidateTabArea(infoPtr);
501 return 0;
504 /******************************************************************************
505 * TAB_FocusChanging
507 * This method is called whenever the focus goes in or out of this control
508 * it is used to update the visual state of the control.
510 static void TAB_FocusChanging(const TAB_INFO *infoPtr)
512 RECT selectedRect;
513 BOOL isVisible;
516 * Get the rectangle for the item.
518 isVisible = TAB_InternalGetItemRect(infoPtr,
519 infoPtr->uFocus,
520 NULL,
521 &selectedRect);
524 * If the rectangle is not completely invisible, invalidate that
525 * portion of the window.
527 if (isVisible)
529 TRACE("invalidate (%s)\n", wine_dbgstr_rect(&selectedRect));
530 InvalidateRect(infoPtr->hwnd, &selectedRect, TRUE);
534 static INT TAB_InternalHitTest (const TAB_INFO *infoPtr, POINT pt, UINT *flags)
536 RECT rect;
537 INT iCount;
539 for (iCount = 0; iCount < infoPtr->uNumItem; iCount++)
541 TAB_InternalGetItemRect(infoPtr, iCount, &rect, NULL);
543 if (PtInRect(&rect, pt))
545 *flags = TCHT_ONITEM;
546 return iCount;
550 *flags = TCHT_NOWHERE;
551 return -1;
554 static inline LRESULT
555 TAB_HitTest (const TAB_INFO *infoPtr, LPTCHITTESTINFO lptest)
557 return TAB_InternalHitTest (infoPtr, lptest->pt, &lptest->flags);
560 /******************************************************************************
561 * TAB_NCHitTest
563 * Napster v2b5 has a tab control for its main navigation which has a client
564 * area that covers the whole area of the dialog pages.
565 * That's why it receives all msgs for that area and the underlying dialog ctrls
566 * are dead.
567 * So I decided that we should handle WM_NCHITTEST here and return
568 * HTTRANSPARENT if we don't hit the tab control buttons.
569 * FIXME: WM_NCHITTEST handling correct ? Fix it if you know that Windows
570 * doesn't do it that way. Maybe depends on tab control styles ?
572 static inline LRESULT
573 TAB_NCHitTest (const TAB_INFO *infoPtr, LPARAM lParam)
575 POINT pt;
576 UINT dummyflag;
578 pt.x = (short)LOWORD(lParam);
579 pt.y = (short)HIWORD(lParam);
580 ScreenToClient(infoPtr->hwnd, &pt);
582 if (TAB_InternalHitTest(infoPtr, pt, &dummyflag) == -1)
583 return HTTRANSPARENT;
584 else
585 return HTCLIENT;
588 static LRESULT
589 TAB_LButtonDown (TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
591 POINT pt;
592 INT newItem;
593 UINT dummy;
595 if (infoPtr->hwndToolTip)
596 TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
597 WM_LBUTTONDOWN, wParam, lParam);
599 if (GetWindowLongW(infoPtr->hwnd, GWL_STYLE) & TCS_FOCUSONBUTTONDOWN ) {
600 SetFocus (infoPtr->hwnd);
603 if (infoPtr->hwndToolTip)
604 TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
605 WM_LBUTTONDOWN, wParam, lParam);
607 pt.x = (short)LOWORD(lParam);
608 pt.y = (short)HIWORD(lParam);
610 newItem = TAB_InternalHitTest (infoPtr, pt, &dummy);
612 TRACE("On Tab, item %d\n", newItem);
614 if (newItem != -1 && infoPtr->iSelected != newItem)
616 if (!TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGING))
618 infoPtr->iSelected = newItem;
619 infoPtr->uFocus = newItem;
620 TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGE);
622 TAB_EnsureSelectionVisible(infoPtr);
624 TAB_InvalidateTabArea(infoPtr);
627 return 0;
630 static inline LRESULT
631 TAB_LButtonUp (const TAB_INFO *infoPtr)
633 TAB_SendSimpleNotify(infoPtr, NM_CLICK);
635 return 0;
638 static inline LRESULT
639 TAB_RButtonDown (const TAB_INFO *infoPtr)
641 TAB_SendSimpleNotify(infoPtr, NM_RCLICK);
642 return 0;
645 /******************************************************************************
646 * TAB_DrawLoneItemInterior
648 * This calls TAB_DrawItemInterior. However, TAB_DrawItemInterior is normally
649 * called by TAB_DrawItem which is normally called by TAB_Refresh which sets
650 * up the device context and font. This routine does the same setup but
651 * only calls TAB_DrawItemInterior for the single specified item.
653 static void
654 TAB_DrawLoneItemInterior(const TAB_INFO* infoPtr, int iItem)
656 HDC hdc = GetDC(infoPtr->hwnd);
657 RECT r, rC;
659 /* Clip UpDown control to not draw over it */
660 if (infoPtr->needsScrolling)
662 GetWindowRect(infoPtr->hwnd, &rC);
663 GetWindowRect(infoPtr->hwndUpDown, &r);
664 ExcludeClipRect(hdc, r.left - rC.left, r.top - rC.top, r.right - rC.left, r.bottom - rC.top);
666 TAB_DrawItemInterior(infoPtr, hdc, iItem, NULL);
667 ReleaseDC(infoPtr->hwnd, hdc);
670 /* update a tab after hottracking - invalidate it or just redraw the interior,
671 * based on whether theming is used or not */
672 static inline void hottrack_refresh(const TAB_INFO *infoPtr, int tabIndex)
674 if (tabIndex == -1) return;
676 if (GetWindowTheme (infoPtr->hwnd))
678 RECT rect;
679 TAB_InternalGetItemRect(infoPtr, tabIndex, &rect, NULL);
680 InvalidateRect (infoPtr->hwnd, &rect, FALSE);
682 else
683 TAB_DrawLoneItemInterior(infoPtr, tabIndex);
686 /******************************************************************************
687 * TAB_HotTrackTimerProc
689 * When a mouse-move event causes a tab to be highlighted (hot-tracking), a
690 * timer is setup so we can check if the mouse is moved out of our window.
691 * (We don't get an event when the mouse leaves, the mouse-move events just
692 * stop being delivered to our window and just start being delivered to
693 * another window.) This function is called when the timer triggers so
694 * we can check if the mouse has left our window. If so, we un-highlight
695 * the hot-tracked tab.
697 static void CALLBACK
698 TAB_HotTrackTimerProc
700 HWND hwnd, /* handle of window for timer messages */
701 UINT uMsg, /* WM_TIMER message */
702 UINT_PTR idEvent, /* timer identifier */
703 DWORD dwTime /* current system time */
706 TAB_INFO* infoPtr = TAB_GetInfoPtr(hwnd);
708 if (infoPtr != NULL && infoPtr->iHotTracked >= 0)
710 POINT pt;
713 ** If we can't get the cursor position, or if the cursor is outside our
714 ** window, we un-highlight the hot-tracked tab. Note that the cursor is
715 ** "outside" even if it is within our bounding rect if another window
716 ** overlaps. Note also that the case where the cursor stayed within our
717 ** window but has moved off the hot-tracked tab will be handled by the
718 ** WM_MOUSEMOVE event.
720 if (!GetCursorPos(&pt) || WindowFromPoint(pt) != hwnd)
722 /* Redraw iHotTracked to look normal */
723 INT iRedraw = infoPtr->iHotTracked;
724 infoPtr->iHotTracked = -1;
725 hottrack_refresh (infoPtr, iRedraw);
727 /* Kill this timer */
728 KillTimer(hwnd, TAB_HOTTRACK_TIMER);
733 /******************************************************************************
734 * TAB_RecalcHotTrack
736 * If a tab control has the TCS_HOTTRACK style, then the tab under the mouse
737 * should be highlighted. This function determines which tab in a tab control,
738 * if any, is under the mouse and records that information. The caller may
739 * supply output parameters to receive the item number of the tab item which
740 * was highlighted but isn't any longer and of the tab item which is now
741 * highlighted but wasn't previously. The caller can use this information to
742 * selectively redraw those tab items.
744 * If the caller has a mouse position, it can supply it through the pos
745 * parameter. For example, TAB_MouseMove does this. Otherwise, the caller
746 * supplies NULL and this function determines the current mouse position
747 * itself.
749 static void
750 TAB_RecalcHotTrack
752 TAB_INFO* infoPtr,
753 const LPARAM* pos,
754 int* out_redrawLeave,
755 int* out_redrawEnter
758 int item = -1;
761 if (out_redrawLeave != NULL)
762 *out_redrawLeave = -1;
763 if (out_redrawEnter != NULL)
764 *out_redrawEnter = -1;
766 if ((GetWindowLongW(infoPtr->hwnd, GWL_STYLE) & TCS_HOTTRACK)
767 || GetWindowTheme (infoPtr->hwnd))
769 POINT pt;
770 UINT flags;
772 if (pos == NULL)
774 GetCursorPos(&pt);
775 ScreenToClient(infoPtr->hwnd, &pt);
777 else
779 pt.x = (short)LOWORD(*pos);
780 pt.y = (short)HIWORD(*pos);
783 item = TAB_InternalHitTest(infoPtr, pt, &flags);
786 if (item != infoPtr->iHotTracked)
788 if (infoPtr->iHotTracked >= 0)
790 /* Mark currently hot-tracked to be redrawn to look normal */
791 if (out_redrawLeave != NULL)
792 *out_redrawLeave = infoPtr->iHotTracked;
794 if (item < 0)
796 /* Kill timer which forces recheck of mouse pos */
797 KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
800 else
802 /* Start timer so we recheck mouse pos */
803 UINT timerID = SetTimer
805 infoPtr->hwnd,
806 TAB_HOTTRACK_TIMER,
807 TAB_HOTTRACK_TIMER_INTERVAL,
808 TAB_HotTrackTimerProc
811 if (timerID == 0)
812 return; /* Hot tracking not available */
815 infoPtr->iHotTracked = item;
817 if (item >= 0)
819 /* Mark new hot-tracked to be redrawn to look highlighted */
820 if (out_redrawEnter != NULL)
821 *out_redrawEnter = item;
826 /******************************************************************************
827 * TAB_MouseMove
829 * Handles the mouse-move event. Updates tooltips. Updates hot-tracking.
831 static LRESULT
832 TAB_MouseMove (TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
834 int redrawLeave;
835 int redrawEnter;
837 if (infoPtr->hwndToolTip)
838 TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
839 WM_LBUTTONDOWN, wParam, lParam);
841 /* Determine which tab to highlight. Redraw tabs which change highlight
842 ** status. */
843 TAB_RecalcHotTrack(infoPtr, &lParam, &redrawLeave, &redrawEnter);
845 hottrack_refresh (infoPtr, redrawLeave);
846 hottrack_refresh (infoPtr, redrawEnter);
848 return 0;
851 /******************************************************************************
852 * TAB_AdjustRect
854 * Calculates the tab control's display area given the window rectangle or
855 * the window rectangle given the requested display rectangle.
857 static LRESULT TAB_AdjustRect(const TAB_INFO *infoPtr, WPARAM fLarger, LPRECT prc)
859 DWORD lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
860 LONG *iRightBottom, *iLeftTop;
862 TRACE ("hwnd=%p fLarger=%ld (%s)\n", infoPtr->hwnd, fLarger,
863 wine_dbgstr_rect(prc));
865 if (!prc) return -1;
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)
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 if (lStyle & TCS_BUTTONS)
1438 if (iItem == infoPtr->iSelected)
1440 /* Background color */
1441 if (!(lStyle & TCS_OWNERDRAWFIXED))
1443 DeleteObject(hbr);
1444 hbr = GetSysColorBrush(COLOR_SCROLLBAR);
1446 SetTextColor(hdc, comctl32_color.clr3dFace);
1447 SetBkColor(hdc, comctl32_color.clr3dHilight);
1449 /* if COLOR_WINDOW happens to be the same as COLOR_3DHILIGHT
1450 * we better use 0x55aa bitmap brush to make scrollbar's background
1451 * look different from the window background.
1453 if (comctl32_color.clr3dHilight == comctl32_color.clrWindow)
1454 hbr = COMCTL32_hPattern55AABrush;
1456 deleteBrush = FALSE;
1458 FillRect(hdc, &rTemp, hbr);
1460 else /* ! selected */
1462 if (lStyle & TCS_FLATBUTTONS)
1464 FillRect(hdc, drawRect, hbr);
1465 if (iItem == infoPtr->iHotTracked)
1466 DrawEdge(hdc, drawRect, EDGE_RAISED, BF_SOFT|BF_RECT);
1468 else
1469 FillRect(hdc, &rTemp, hbr);
1473 else /* !TCS_BUTTONS */
1475 InflateRect(&rTemp, -2, -2);
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 if (infoPtr->tabMinWidth < 0)
2665 oldcx = DEFAULT_MIN_TAB_WIDTH;
2666 else
2667 oldcx = infoPtr->tabMinWidth;
2668 infoPtr->tabMinWidth = cx;
2669 TAB_SetItemBounds(infoPtr);
2670 return oldcx;
2673 static inline LRESULT
2674 TAB_HighlightItem (TAB_INFO *infoPtr, INT iItem, BOOL fHighlight)
2676 LPDWORD lpState;
2678 TRACE("(%p,%d,%s)\n", infoPtr, iItem, fHighlight ? "true" : "false");
2680 if (!infoPtr || iItem < 0 || iItem >= infoPtr->uNumItem)
2681 return FALSE;
2683 lpState = &TAB_GetItem(infoPtr, iItem)->dwState;
2685 if (fHighlight)
2686 *lpState |= TCIS_HIGHLIGHTED;
2687 else
2688 *lpState &= ~TCIS_HIGHLIGHTED;
2690 return TRUE;
2693 static LRESULT
2694 TAB_SetItemT (TAB_INFO *infoPtr, INT iItem, LPTCITEMW tabItem, BOOL bUnicode)
2696 TAB_ITEM *wineItem;
2698 TRACE("(%p,%d,%p,%s)\n", infoPtr, iItem, tabItem, bUnicode ? "true" : "false");
2700 if (iItem < 0 || iItem >= infoPtr->uNumItem)
2701 return FALSE;
2703 TAB_DumpItemExternalT(tabItem, iItem, bUnicode);
2705 wineItem = TAB_GetItem(infoPtr, iItem);
2707 if (tabItem->mask & TCIF_IMAGE)
2708 wineItem->iImage = tabItem->iImage;
2710 if (tabItem->mask & TCIF_PARAM)
2711 memcpy(wineItem->extra, &tabItem->lParam, infoPtr->cbInfo);
2713 if (tabItem->mask & TCIF_RTLREADING)
2714 FIXME("TCIF_RTLREADING\n");
2716 if (tabItem->mask & TCIF_STATE)
2717 wineItem->dwState = tabItem->dwState;
2719 if (tabItem->mask & TCIF_TEXT)
2721 Free(wineItem->pszText);
2722 wineItem->pszText = NULL;
2723 if (bUnicode)
2724 Str_SetPtrW(&wineItem->pszText, tabItem->pszText);
2725 else
2726 Str_SetPtrAtoW(&wineItem->pszText, (LPSTR)tabItem->pszText);
2729 /* Update and repaint tabs */
2730 TAB_SetItemBounds(infoPtr);
2731 TAB_InvalidateTabArea(infoPtr);
2733 return TRUE;
2736 static inline LRESULT TAB_GetItemCount (const TAB_INFO *infoPtr)
2738 return infoPtr->uNumItem;
2742 static LRESULT
2743 TAB_GetItemT (TAB_INFO *infoPtr, INT iItem, LPTCITEMW tabItem, BOOL bUnicode)
2745 TAB_ITEM *wineItem;
2747 TRACE("(%p,%d,%p,%s)\n", infoPtr, iItem, tabItem, bUnicode ? "true" : "false");
2749 if (iItem < 0 || iItem >= infoPtr->uNumItem)
2750 return FALSE;
2752 wineItem = TAB_GetItem(infoPtr, iItem);
2754 if (tabItem->mask & TCIF_IMAGE)
2755 tabItem->iImage = wineItem->iImage;
2757 if (tabItem->mask & TCIF_PARAM)
2758 memcpy(&tabItem->lParam, wineItem->extra, infoPtr->cbInfo);
2760 if (tabItem->mask & TCIF_RTLREADING)
2761 FIXME("TCIF_RTLREADING\n");
2763 if (tabItem->mask & TCIF_STATE)
2764 tabItem->dwState = wineItem->dwState;
2766 if (tabItem->mask & TCIF_TEXT)
2768 if (bUnicode)
2769 Str_GetPtrW (wineItem->pszText, tabItem->pszText, tabItem->cchTextMax);
2770 else
2771 Str_GetPtrWtoA (wineItem->pszText, (LPSTR)tabItem->pszText, tabItem->cchTextMax);
2774 TAB_DumpItemExternalT(tabItem, iItem, bUnicode);
2776 return TRUE;
2780 static LRESULT TAB_DeleteItem (TAB_INFO *infoPtr, INT iItem)
2782 BOOL bResult = FALSE;
2784 TRACE("(%p, %d)\n", infoPtr, iItem);
2786 if ((iItem >= 0) && (iItem < infoPtr->uNumItem))
2788 TAB_ITEM *item = TAB_GetItem(infoPtr, iItem);
2789 LPBYTE oldItems = (LPBYTE)infoPtr->items;
2791 TAB_InvalidateTabArea(infoPtr);
2792 Free(item->pszText);
2793 infoPtr->uNumItem--;
2795 if (!infoPtr->uNumItem)
2797 infoPtr->items = NULL;
2798 if (infoPtr->iHotTracked >= 0)
2800 KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
2801 infoPtr->iHotTracked = -1;
2804 else
2806 infoPtr->items = Alloc(TAB_ITEM_SIZE(infoPtr) * infoPtr->uNumItem);
2808 if (iItem > 0)
2809 memcpy(infoPtr->items, oldItems, iItem * TAB_ITEM_SIZE(infoPtr));
2811 if (iItem < infoPtr->uNumItem)
2812 memcpy(TAB_GetItem(infoPtr, iItem),
2813 oldItems + (iItem + 1) * TAB_ITEM_SIZE(infoPtr),
2814 (infoPtr->uNumItem - iItem) * TAB_ITEM_SIZE(infoPtr));
2816 if (iItem <= infoPtr->iHotTracked)
2818 /* When tabs move left/up, the hot track item may change */
2819 FIXME("Recalc hot track\n");
2822 Free(oldItems);
2824 /* Readjust the selected index */
2825 if ((iItem == infoPtr->iSelected) && (iItem > 0))
2826 infoPtr->iSelected--;
2828 if (iItem < infoPtr->iSelected)
2829 infoPtr->iSelected--;
2831 if (infoPtr->uNumItem == 0)
2832 infoPtr->iSelected = -1;
2834 /* Reposition and repaint tabs */
2835 TAB_SetItemBounds(infoPtr);
2837 bResult = TRUE;
2840 return bResult;
2843 static inline LRESULT TAB_DeleteAllItems (TAB_INFO *infoPtr)
2845 TRACE("(%p)\n", infoPtr);
2846 while (infoPtr->uNumItem)
2847 TAB_DeleteItem (infoPtr, 0);
2848 return TRUE;
2852 static inline LRESULT TAB_GetFont (const TAB_INFO *infoPtr)
2854 TRACE("(%p) returning %p\n", infoPtr, infoPtr->hFont);
2855 return (LRESULT)infoPtr->hFont;
2858 static inline LRESULT TAB_SetFont (TAB_INFO *infoPtr, HFONT hNewFont)
2860 TRACE("(%p,%p)\n", infoPtr, hNewFont);
2862 infoPtr->hFont = hNewFont;
2864 TAB_SetItemBounds(infoPtr);
2866 TAB_InvalidateTabArea(infoPtr);
2868 return 0;
2872 static inline LRESULT TAB_GetImageList (const TAB_INFO *infoPtr)
2874 TRACE("\n");
2875 return (LRESULT)infoPtr->himl;
2878 static inline LRESULT TAB_SetImageList (TAB_INFO *infoPtr, HIMAGELIST himlNew)
2880 HIMAGELIST himlPrev = infoPtr->himl;
2881 TRACE("\n");
2882 infoPtr->himl = himlNew;
2883 TAB_SetItemBounds(infoPtr);
2884 InvalidateRect(infoPtr->hwnd, NULL, TRUE);
2885 return (LRESULT)himlPrev;
2888 static inline LRESULT TAB_GetUnicodeFormat (const TAB_INFO *infoPtr)
2890 return infoPtr->bUnicode;
2893 static inline LRESULT TAB_SetUnicodeFormat (TAB_INFO *infoPtr, BOOL bUnicode)
2895 BOOL bTemp = infoPtr->bUnicode;
2897 infoPtr->bUnicode = bUnicode;
2899 return bTemp;
2902 static inline LRESULT TAB_Size (TAB_INFO *infoPtr)
2904 /* I'm not really sure what the following code was meant to do.
2905 This is what it is doing:
2906 When WM_SIZE is sent with SIZE_RESTORED, the control
2907 gets positioned in the top left corner.
2909 RECT parent_rect;
2910 HWND parent;
2911 UINT uPosFlags,cx,cy;
2913 uPosFlags=0;
2914 if (!wParam) {
2915 parent = GetParent (hwnd);
2916 GetClientRect(parent, &parent_rect);
2917 cx=LOWORD (lParam);
2918 cy=HIWORD (lParam);
2919 if (GetWindowLongW(hwnd, GWL_STYLE) & CCS_NORESIZE)
2920 uPosFlags |= (SWP_NOSIZE | SWP_NOMOVE);
2922 SetWindowPos (hwnd, 0, parent_rect.left, parent_rect.top,
2923 cx, cy, uPosFlags | SWP_NOZORDER);
2924 } else {
2925 FIXME("WM_SIZE flag %x %lx not handled\n", wParam, lParam);
2926 } */
2928 /* Recompute the size/position of the tabs. */
2929 TAB_SetItemBounds (infoPtr);
2931 /* Force a repaint of the control. */
2932 InvalidateRect(infoPtr->hwnd, NULL, TRUE);
2934 return 0;
2938 static LRESULT TAB_Create (HWND hwnd, LPARAM lParam)
2940 TAB_INFO *infoPtr;
2941 TEXTMETRICW fontMetrics;
2942 HDC hdc;
2943 HFONT hOldFont;
2944 DWORD dwStyle;
2946 infoPtr = Alloc (sizeof(TAB_INFO));
2948 SetWindowLongPtrW(hwnd, 0, (DWORD_PTR)infoPtr);
2950 infoPtr->hwnd = hwnd;
2951 infoPtr->hwndNotify = ((LPCREATESTRUCTW)lParam)->hwndParent;
2952 infoPtr->uNumItem = 0;
2953 infoPtr->uNumRows = 0;
2954 infoPtr->uHItemPadding = 6;
2955 infoPtr->uVItemPadding = 3;
2956 infoPtr->uHItemPadding_s = 6;
2957 infoPtr->uVItemPadding_s = 3;
2958 infoPtr->hFont = 0;
2959 infoPtr->items = 0;
2960 infoPtr->hcurArrow = LoadCursorW (0, (LPWSTR)IDC_ARROW);
2961 infoPtr->iSelected = -1;
2962 infoPtr->iHotTracked = -1;
2963 infoPtr->uFocus = -1;
2964 infoPtr->hwndToolTip = 0;
2965 infoPtr->DoRedraw = TRUE;
2966 infoPtr->needsScrolling = FALSE;
2967 infoPtr->hwndUpDown = 0;
2968 infoPtr->leftmostVisible = 0;
2969 infoPtr->fHeightSet = FALSE;
2970 infoPtr->bUnicode = IsWindowUnicode (hwnd);
2971 infoPtr->cbInfo = sizeof(LPARAM);
2973 TRACE("Created tab control, hwnd [%p]\n", hwnd);
2975 /* The tab control always has the WS_CLIPSIBLINGS style. Even
2976 if you don't specify it in CreateWindow. This is necessary in
2977 order for paint to work correctly. This follows windows behaviour. */
2978 dwStyle = GetWindowLongW(hwnd, GWL_STYLE);
2979 SetWindowLongW(hwnd, GWL_STYLE, dwStyle|WS_CLIPSIBLINGS);
2981 if (dwStyle & TCS_TOOLTIPS) {
2982 /* Create tooltip control */
2983 infoPtr->hwndToolTip =
2984 CreateWindowExW (0, TOOLTIPS_CLASSW, NULL, WS_POPUP,
2985 CW_USEDEFAULT, CW_USEDEFAULT,
2986 CW_USEDEFAULT, CW_USEDEFAULT,
2987 hwnd, 0, 0, 0);
2989 /* Send NM_TOOLTIPSCREATED notification */
2990 if (infoPtr->hwndToolTip) {
2991 NMTOOLTIPSCREATED nmttc;
2993 nmttc.hdr.hwndFrom = hwnd;
2994 nmttc.hdr.idFrom = GetWindowLongPtrW(hwnd, GWLP_ID);
2995 nmttc.hdr.code = NM_TOOLTIPSCREATED;
2996 nmttc.hwndToolTips = infoPtr->hwndToolTip;
2998 SendMessageW (infoPtr->hwndNotify, WM_NOTIFY,
2999 (WPARAM)GetWindowLongPtrW(hwnd, GWLP_ID), (LPARAM)&nmttc);
3003 OpenThemeData (infoPtr->hwnd, themeClass);
3006 * We need to get text information so we need a DC and we need to select
3007 * a font.
3009 hdc = GetDC(hwnd);
3010 hOldFont = SelectObject (hdc, GetStockObject (SYSTEM_FONT));
3012 /* Use the system font to determine the initial height of a tab. */
3013 GetTextMetricsW(hdc, &fontMetrics);
3016 * Make sure there is enough space for the letters + growing the
3017 * selected item + extra space for the selected item.
3019 infoPtr->tabHeight = fontMetrics.tmHeight + SELECTED_TAB_OFFSET +
3020 ((dwStyle & TCS_BUTTONS) ? 2 : 1) *
3021 infoPtr->uVItemPadding;
3023 /* Initialize the width of a tab. */
3024 if (dwStyle & TCS_FIXEDWIDTH)
3025 infoPtr->tabWidth = GetDeviceCaps(hdc, LOGPIXELSX);
3027 infoPtr->tabMinWidth = -1;
3029 TRACE("tabH=%d, tabW=%d\n", infoPtr->tabHeight, infoPtr->tabWidth);
3031 SelectObject (hdc, hOldFont);
3032 ReleaseDC(hwnd, hdc);
3034 return 0;
3037 static LRESULT
3038 TAB_Destroy (TAB_INFO *infoPtr)
3040 UINT iItem;
3042 if (!infoPtr)
3043 return 0;
3045 SetWindowLongPtrW(infoPtr->hwnd, 0, 0);
3047 if (infoPtr->items) {
3048 for (iItem = 0; iItem < infoPtr->uNumItem; iItem++) {
3049 Free (TAB_GetItem(infoPtr, iItem)->pszText);
3051 Free (infoPtr->items);
3054 if (infoPtr->hwndToolTip)
3055 DestroyWindow (infoPtr->hwndToolTip);
3057 if (infoPtr->hwndUpDown)
3058 DestroyWindow(infoPtr->hwndUpDown);
3060 if (infoPtr->iHotTracked >= 0)
3061 KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
3063 CloseThemeData (GetWindowTheme (infoPtr->hwnd));
3065 Free (infoPtr);
3066 return 0;
3069 /* update theme after a WM_THEMECHANGED message */
3070 static LRESULT theme_changed(const TAB_INFO *infoPtr)
3072 HTHEME theme = GetWindowTheme (infoPtr->hwnd);
3073 CloseThemeData (theme);
3074 OpenThemeData (infoPtr->hwnd, themeClass);
3075 return 0;
3078 static LRESULT TAB_NCCalcSize(WPARAM wParam)
3080 if (!wParam)
3081 return 0;
3082 return WVR_ALIGNTOP;
3085 static inline LRESULT
3086 TAB_SetItemExtra (TAB_INFO *infoPtr, INT cbInfo)
3088 if (!infoPtr || cbInfo <= 0)
3089 return FALSE;
3091 if (infoPtr->uNumItem)
3093 /* FIXME: MSDN says this is not allowed, but this hasn't been verified */
3094 return FALSE;
3097 infoPtr->cbInfo = cbInfo;
3098 return TRUE;
3101 static LRESULT TAB_RemoveImage (TAB_INFO *infoPtr, INT image)
3103 if (!infoPtr)
3104 return 0;
3106 if (ImageList_Remove (infoPtr->himl, image))
3108 INT i, *idx;
3109 RECT r;
3111 /* shift indices, repaint items if needed */
3112 for (i = 0; i < infoPtr->uNumItem; i++)
3114 idx = &infoPtr->items[i].iImage;
3115 if (*idx >= image)
3117 if (*idx == image)
3118 *idx = -1;
3119 else
3120 (*idx)--;
3122 /* repaint item */
3123 if (TAB_InternalGetItemRect (infoPtr, i, &r, NULL))
3124 InvalidateRect (infoPtr->hwnd, &r, TRUE);
3129 return 0;
3132 static LRESULT WINAPI
3133 TAB_WindowProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
3135 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
3137 TRACE("hwnd=%p msg=%x wParam=%lx lParam=%lx\n", hwnd, uMsg, wParam, lParam);
3138 if (!infoPtr && (uMsg != WM_CREATE))
3139 return DefWindowProcW (hwnd, uMsg, wParam, lParam);
3141 switch (uMsg)
3143 case TCM_GETIMAGELIST:
3144 return TAB_GetImageList (infoPtr);
3146 case TCM_SETIMAGELIST:
3147 return TAB_SetImageList (infoPtr, (HIMAGELIST)lParam);
3149 case TCM_GETITEMCOUNT:
3150 return TAB_GetItemCount (infoPtr);
3152 case TCM_GETITEMA:
3153 case TCM_GETITEMW:
3154 return TAB_GetItemT (infoPtr, (INT)wParam, (LPTCITEMW)lParam, uMsg == TCM_GETITEMW);
3156 case TCM_SETITEMA:
3157 case TCM_SETITEMW:
3158 return TAB_SetItemT (infoPtr, (INT)wParam, (LPTCITEMW)lParam, uMsg == TCM_SETITEMW);
3160 case TCM_DELETEITEM:
3161 return TAB_DeleteItem (infoPtr, (INT)wParam);
3163 case TCM_DELETEALLITEMS:
3164 return TAB_DeleteAllItems (infoPtr);
3166 case TCM_GETITEMRECT:
3167 return TAB_GetItemRect (infoPtr, wParam, lParam);
3169 case TCM_GETCURSEL:
3170 return TAB_GetCurSel (infoPtr);
3172 case TCM_HITTEST:
3173 return TAB_HitTest (infoPtr, (LPTCHITTESTINFO)lParam);
3175 case TCM_SETCURSEL:
3176 return TAB_SetCurSel (infoPtr, (INT)wParam);
3178 case TCM_INSERTITEMA:
3179 case TCM_INSERTITEMW:
3180 return TAB_InsertItemT (infoPtr, wParam, lParam, uMsg == TCM_INSERTITEMW);
3182 case TCM_SETITEMEXTRA:
3183 return TAB_SetItemExtra (infoPtr, (int)wParam);
3185 case TCM_ADJUSTRECT:
3186 return TAB_AdjustRect (infoPtr, (BOOL)wParam, (LPRECT)lParam);
3188 case TCM_SETITEMSIZE:
3189 return TAB_SetItemSize (infoPtr, lParam);
3191 case TCM_REMOVEIMAGE:
3192 return TAB_RemoveImage (infoPtr, wParam);
3194 case TCM_SETPADDING:
3195 return TAB_SetPadding (infoPtr, lParam);
3197 case TCM_GETROWCOUNT:
3198 return TAB_GetRowCount(infoPtr);
3200 case TCM_GETUNICODEFORMAT:
3201 return TAB_GetUnicodeFormat (infoPtr);
3203 case TCM_SETUNICODEFORMAT:
3204 return TAB_SetUnicodeFormat (infoPtr, (BOOL)wParam);
3206 case TCM_HIGHLIGHTITEM:
3207 return TAB_HighlightItem (infoPtr, (INT)wParam, (BOOL)LOWORD(lParam));
3209 case TCM_GETTOOLTIPS:
3210 return TAB_GetToolTips (infoPtr);
3212 case TCM_SETTOOLTIPS:
3213 return TAB_SetToolTips (infoPtr, (HWND)wParam);
3215 case TCM_GETCURFOCUS:
3216 return TAB_GetCurFocus (infoPtr);
3218 case TCM_SETCURFOCUS:
3219 return TAB_SetCurFocus (infoPtr, (INT)wParam);
3221 case TCM_SETMINTABWIDTH:
3222 return TAB_SetMinTabWidth(infoPtr, (INT)lParam);
3224 case TCM_DESELECTALL:
3225 FIXME("Unimplemented msg TCM_DESELECTALL\n");
3226 return 0;
3228 case TCM_GETEXTENDEDSTYLE:
3229 FIXME("Unimplemented msg TCM_GETEXTENDEDSTYLE\n");
3230 return 0;
3232 case TCM_SETEXTENDEDSTYLE:
3233 FIXME("Unimplemented msg TCM_SETEXTENDEDSTYLE\n");
3234 return 0;
3236 case WM_GETFONT:
3237 return TAB_GetFont (infoPtr);
3239 case WM_SETFONT:
3240 return TAB_SetFont (infoPtr, (HFONT)wParam);
3242 case WM_CREATE:
3243 return TAB_Create (hwnd, lParam);
3245 case WM_NCDESTROY:
3246 return TAB_Destroy (infoPtr);
3248 case WM_GETDLGCODE:
3249 return DLGC_WANTARROWS | DLGC_WANTCHARS;
3251 case WM_LBUTTONDOWN:
3252 return TAB_LButtonDown (infoPtr, wParam, lParam);
3254 case WM_LBUTTONUP:
3255 return TAB_LButtonUp (infoPtr);
3257 case WM_NOTIFY:
3258 return SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, wParam, lParam);
3260 case WM_RBUTTONDOWN:
3261 return TAB_RButtonDown (infoPtr);
3263 case WM_MOUSEMOVE:
3264 return TAB_MouseMove (infoPtr, wParam, lParam);
3266 case WM_PRINTCLIENT:
3267 case WM_PAINT:
3268 return TAB_Paint (infoPtr, (HDC)wParam);
3270 case WM_SIZE:
3271 return TAB_Size (infoPtr);
3273 case WM_SETREDRAW:
3274 return TAB_SetRedraw (infoPtr, (BOOL)wParam);
3276 case WM_HSCROLL:
3277 return TAB_OnHScroll(infoPtr, (int)LOWORD(wParam), (int)HIWORD(wParam));
3279 case WM_STYLECHANGED:
3280 TAB_SetItemBounds (infoPtr);
3281 InvalidateRect(hwnd, NULL, TRUE);
3282 return 0;
3284 case WM_SYSCOLORCHANGE:
3285 COMCTL32_RefreshSysColors();
3286 return 0;
3288 case WM_THEMECHANGED:
3289 return theme_changed (infoPtr);
3291 case WM_KILLFOCUS:
3292 case WM_SETFOCUS:
3293 TAB_FocusChanging(infoPtr);
3294 break; /* Don't disturb normal focus behavior */
3296 case WM_KEYUP:
3297 return TAB_KeyUp(infoPtr, wParam);
3298 case WM_NCHITTEST:
3299 return TAB_NCHitTest(infoPtr, lParam);
3301 case WM_NCCALCSIZE:
3302 return TAB_NCCalcSize(wParam);
3304 default:
3305 if (uMsg >= WM_USER && uMsg < WM_APP && !COMCTL32_IsReflectedMessage(uMsg))
3306 WARN("unknown msg %04x wp=%08lx lp=%08lx\n",
3307 uMsg, wParam, lParam);
3308 break;
3310 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
3314 void
3315 TAB_Register (void)
3317 WNDCLASSW wndClass;
3319 ZeroMemory (&wndClass, sizeof(WNDCLASSW));
3320 wndClass.style = CS_GLOBALCLASS | CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
3321 wndClass.lpfnWndProc = TAB_WindowProc;
3322 wndClass.cbClsExtra = 0;
3323 wndClass.cbWndExtra = sizeof(TAB_INFO *);
3324 wndClass.hCursor = LoadCursorW (0, (LPWSTR)IDC_ARROW);
3325 wndClass.hbrBackground = (HBRUSH)(COLOR_BTNFACE+1);
3326 wndClass.lpszClassName = WC_TABCONTROLW;
3328 RegisterClassW (&wndClass);
3332 void
3333 TAB_Unregister (void)
3335 UnregisterClassW (WC_TABCONTROLW, NULL);