wined3d: Rename basetexture functions to reflect that they're not COM functions.
[wine/wine64.git] / dlls / comctl32 / tab.c
blob6a9b8e85dc24cd362d064741e93cdc381f76589e
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_PADDING_X 6
146 #define EXTRA_ICON_PADDING 3
148 #define TAB_GetInfoPtr(hwnd) ((TAB_INFO *)GetWindowLongPtrW(hwnd,0))
149 /* Since items are variable sized, cannot directly access them */
150 #define TAB_GetItem(info,i) \
151 ((TAB_ITEM*)((LPBYTE)info->items + (i) * TAB_ITEM_SIZE(info)))
153 #define GET_DEFAULT_MIN_TAB_WIDTH(infoPtr) (DEFAULT_MIN_TAB_WIDTH - (DEFAULT_PADDING_X - (infoPtr)->uHItemPadding) * 2)
155 /******************************************************************************
156 * Hot-tracking timer constants
158 #define TAB_HOTTRACK_TIMER 1
159 #define TAB_HOTTRACK_TIMER_INTERVAL 100 /* milliseconds */
161 static const WCHAR themeClass[] = { 'T','a','b',0 };
163 /******************************************************************************
164 * Prototypes
166 static void TAB_InvalidateTabArea(const TAB_INFO *);
167 static void TAB_EnsureSelectionVisible(TAB_INFO *);
168 static void TAB_DrawItemInterior(const TAB_INFO *, HDC, INT, RECT*);
170 static BOOL
171 TAB_SendSimpleNotify (const TAB_INFO *infoPtr, UINT code)
173 NMHDR nmhdr;
175 nmhdr.hwndFrom = infoPtr->hwnd;
176 nmhdr.idFrom = GetWindowLongPtrW(infoPtr->hwnd, GWLP_ID);
177 nmhdr.code = code;
179 return (BOOL) SendMessageW (infoPtr->hwndNotify, WM_NOTIFY,
180 nmhdr.idFrom, (LPARAM) &nmhdr);
183 static void
184 TAB_RelayEvent (HWND hwndTip, HWND hwndMsg, UINT uMsg,
185 WPARAM wParam, LPARAM lParam)
187 MSG msg;
189 msg.hwnd = hwndMsg;
190 msg.message = uMsg;
191 msg.wParam = wParam;
192 msg.lParam = lParam;
193 msg.time = GetMessageTime ();
194 msg.pt.x = (short)LOWORD(GetMessagePos ());
195 msg.pt.y = (short)HIWORD(GetMessagePos ());
197 SendMessageW (hwndTip, TTM_RELAYEVENT, 0, (LPARAM)&msg);
200 static void
201 TAB_DumpItemExternalT(const TCITEMW *pti, UINT iItem, BOOL isW)
203 if (TRACE_ON(tab)) {
204 TRACE("external tab %d, mask=0x%08x, dwState=0x%08x, dwStateMask=0x%08x, cchTextMax=0x%08x\n",
205 iItem, pti->mask, pti->dwState, pti->dwStateMask, pti->cchTextMax);
206 TRACE("external tab %d, iImage=%d, lParam=0x%08lx, pszTextW=%s\n",
207 iItem, pti->iImage, pti->lParam, isW ? debugstr_w(pti->pszText) : debugstr_a((LPSTR)pti->pszText));
211 static void
212 TAB_DumpItemInternal(const TAB_INFO *infoPtr, UINT iItem)
214 if (TRACE_ON(tab)) {
215 TAB_ITEM *ti;
217 ti = TAB_GetItem(infoPtr, iItem);
218 TRACE("tab %d, dwState=0x%08x, pszText=%s, iImage=%d\n",
219 iItem, ti->dwState, debugstr_w(ti->pszText), ti->iImage);
220 TRACE("tab %d, rect.left=%d, rect.top(row)=%d\n",
221 iItem, ti->rect.left, ti->rect.top);
225 /* RETURNS
226 * the index of the selected tab, or -1 if no tab is selected. */
227 static inline LRESULT TAB_GetCurSel (const TAB_INFO *infoPtr)
229 return infoPtr->iSelected;
232 /* RETURNS
233 * the index of the tab item that has the focus. */
234 static inline LRESULT
235 TAB_GetCurFocus (const TAB_INFO *infoPtr)
237 return infoPtr->uFocus;
240 static inline LRESULT TAB_GetToolTips (const TAB_INFO *infoPtr)
242 if (infoPtr == NULL) return 0;
243 return (LRESULT)infoPtr->hwndToolTip;
246 static inline LRESULT TAB_SetCurSel (TAB_INFO *infoPtr, INT iItem)
248 INT prevItem = infoPtr->iSelected;
250 if (iItem < 0)
251 infoPtr->iSelected=-1;
252 else if (iItem >= infoPtr->uNumItem)
253 return -1;
254 else {
255 if (infoPtr->iSelected != iItem) {
256 infoPtr->iSelected=iItem;
257 infoPtr->uFocus=iItem;
258 TAB_EnsureSelectionVisible(infoPtr);
259 TAB_InvalidateTabArea(infoPtr);
262 return prevItem;
265 static LRESULT TAB_SetCurFocus (TAB_INFO *infoPtr, INT iItem)
267 if (iItem < 0)
268 infoPtr->uFocus = -1;
269 else if (iItem < infoPtr->uNumItem) {
270 if (GetWindowLongW(infoPtr->hwnd, GWL_STYLE) & TCS_BUTTONS) {
271 FIXME("Should set input focus\n");
272 } else {
273 int oldFocus = infoPtr->uFocus;
274 if (infoPtr->iSelected != iItem || oldFocus == -1 ) {
275 infoPtr->uFocus = iItem;
276 if (oldFocus != -1) {
277 if (!TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGING)) {
278 infoPtr->iSelected = iItem;
279 TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGE);
281 else
282 infoPtr->iSelected = iItem;
283 TAB_EnsureSelectionVisible(infoPtr);
284 TAB_InvalidateTabArea(infoPtr);
289 return 0;
292 static inline LRESULT
293 TAB_SetToolTips (TAB_INFO *infoPtr, HWND hwndToolTip)
295 if (infoPtr)
296 infoPtr->hwndToolTip = hwndToolTip;
297 return 0;
300 static inline LRESULT
301 TAB_SetPadding (TAB_INFO *infoPtr, LPARAM lParam)
303 if (infoPtr)
305 infoPtr->uHItemPadding_s=LOWORD(lParam);
306 infoPtr->uVItemPadding_s=HIWORD(lParam);
308 return 0;
311 /******************************************************************************
312 * TAB_InternalGetItemRect
314 * This method will calculate the rectangle representing a given tab item in
315 * client coordinates. This method takes scrolling into account.
317 * This method returns TRUE if the item is visible in the window and FALSE
318 * if it is completely outside the client area.
320 static BOOL TAB_InternalGetItemRect(
321 const TAB_INFO* infoPtr,
322 INT itemIndex,
323 RECT* itemRect,
324 RECT* selectedRect)
326 RECT tmpItemRect,clientRect;
327 LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
329 /* Perform a sanity check and a trivial visibility check. */
330 if ( (infoPtr->uNumItem <= 0) ||
331 (itemIndex >= infoPtr->uNumItem) ||
332 (!((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)) && (itemIndex < infoPtr->leftmostVisible)) )
334 TRACE("Not Visible\n");
335 /* need to initialize these to empty rects */
336 if (itemRect)
338 memset(itemRect,0,sizeof(RECT));
339 itemRect->bottom = infoPtr->tabHeight;
341 if (selectedRect)
342 memset(selectedRect,0,sizeof(RECT));
343 return FALSE;
347 * Avoid special cases in this procedure by assigning the "out"
348 * parameters if the caller didn't supply them
350 if (itemRect == NULL)
351 itemRect = &tmpItemRect;
353 /* Retrieve the unmodified item rect. */
354 *itemRect = TAB_GetItem(infoPtr,itemIndex)->rect;
356 /* calculate the times bottom and top based on the row */
357 GetClientRect(infoPtr->hwnd, &clientRect);
359 if ((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
361 itemRect->right = clientRect.right - SELECTED_TAB_OFFSET - itemRect->left * infoPtr->tabHeight -
362 ((lStyle & TCS_BUTTONS) ? itemRect->left * BUTTON_SPACINGX : 0);
363 itemRect->left = itemRect->right - infoPtr->tabHeight;
365 else if (lStyle & TCS_VERTICAL)
367 itemRect->left = clientRect.left + SELECTED_TAB_OFFSET + itemRect->left * infoPtr->tabHeight +
368 ((lStyle & TCS_BUTTONS) ? itemRect->left * BUTTON_SPACINGX : 0);
369 itemRect->right = itemRect->left + infoPtr->tabHeight;
371 else if (lStyle & TCS_BOTTOM)
373 itemRect->bottom = clientRect.bottom - itemRect->top * infoPtr->tabHeight -
374 ((lStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : SELECTED_TAB_OFFSET);
375 itemRect->top = itemRect->bottom - infoPtr->tabHeight;
377 else /* not TCS_BOTTOM and not TCS_VERTICAL */
379 itemRect->top = clientRect.top + itemRect->top * infoPtr->tabHeight +
380 ((lStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : SELECTED_TAB_OFFSET);
381 itemRect->bottom = itemRect->top + infoPtr->tabHeight;
385 * "scroll" it to make sure the item at the very left of the
386 * tab control is the leftmost visible tab.
388 if(lStyle & TCS_VERTICAL)
390 OffsetRect(itemRect,
392 -TAB_GetItem(infoPtr, infoPtr->leftmostVisible)->rect.top);
395 * Move the rectangle so the first item is slightly offset from
396 * the bottom of the tab control.
398 OffsetRect(itemRect,
400 SELECTED_TAB_OFFSET);
402 } else
404 OffsetRect(itemRect,
405 -TAB_GetItem(infoPtr, infoPtr->leftmostVisible)->rect.left,
409 * Move the rectangle so the first item is slightly offset from
410 * the left of the tab control.
412 OffsetRect(itemRect,
413 SELECTED_TAB_OFFSET,
416 TRACE("item %d tab h=%d, rect=(%s)\n",
417 itemIndex, infoPtr->tabHeight, wine_dbgstr_rect(itemRect));
419 /* Now, calculate the position of the item as if it were selected. */
420 if (selectedRect!=NULL)
422 CopyRect(selectedRect, itemRect);
424 /* The rectangle of a selected item is a bit wider. */
425 if(lStyle & TCS_VERTICAL)
426 InflateRect(selectedRect, 0, SELECTED_TAB_OFFSET);
427 else
428 InflateRect(selectedRect, SELECTED_TAB_OFFSET, 0);
430 /* If it also a bit higher. */
431 if ((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
433 selectedRect->left -= 2; /* the border is thicker on the right */
434 selectedRect->right += SELECTED_TAB_OFFSET;
436 else if (lStyle & TCS_VERTICAL)
438 selectedRect->left -= SELECTED_TAB_OFFSET;
439 selectedRect->right += 1;
441 else if (lStyle & TCS_BOTTOM)
443 selectedRect->bottom += SELECTED_TAB_OFFSET;
445 else /* not TCS_BOTTOM and not TCS_VERTICAL */
447 selectedRect->top -= SELECTED_TAB_OFFSET;
448 selectedRect->bottom -= 1;
452 /* Check for visibility */
453 if (lStyle & TCS_VERTICAL)
454 return (itemRect->top < clientRect.bottom) && (itemRect->bottom > clientRect.top);
455 else
456 return (itemRect->left < clientRect.right) && (itemRect->right > clientRect.left);
459 static inline BOOL
460 TAB_GetItemRect(const TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
462 return TAB_InternalGetItemRect(infoPtr, wParam, (LPRECT)lParam, NULL);
465 /******************************************************************************
466 * TAB_KeyUp
468 * This method is called to handle keyboard input
470 static LRESULT TAB_KeyUp(TAB_INFO* infoPtr, WPARAM keyCode)
472 int newItem = -1;
474 switch (keyCode)
476 case VK_LEFT:
477 newItem = infoPtr->uFocus - 1;
478 break;
479 case VK_RIGHT:
480 newItem = infoPtr->uFocus + 1;
481 break;
485 * If we changed to a valid item, change the selection
487 if (newItem >= 0 &&
488 newItem < infoPtr->uNumItem &&
489 infoPtr->uFocus != newItem)
491 if (!TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGING))
493 infoPtr->iSelected = newItem;
494 infoPtr->uFocus = newItem;
495 TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGE);
497 TAB_EnsureSelectionVisible(infoPtr);
498 TAB_InvalidateTabArea(infoPtr);
502 return 0;
505 /******************************************************************************
506 * TAB_FocusChanging
508 * This method is called whenever the focus goes in or out of this control
509 * it is used to update the visual state of the control.
511 static void TAB_FocusChanging(const TAB_INFO *infoPtr)
513 RECT selectedRect;
514 BOOL isVisible;
517 * Get the rectangle for the item.
519 isVisible = TAB_InternalGetItemRect(infoPtr,
520 infoPtr->uFocus,
521 NULL,
522 &selectedRect);
525 * If the rectangle is not completely invisible, invalidate that
526 * portion of the window.
528 if (isVisible)
530 TRACE("invalidate (%s)\n", wine_dbgstr_rect(&selectedRect));
531 InvalidateRect(infoPtr->hwnd, &selectedRect, TRUE);
535 static INT TAB_InternalHitTest (const TAB_INFO *infoPtr, POINT pt, UINT *flags)
537 RECT rect;
538 INT iCount;
540 for (iCount = 0; iCount < infoPtr->uNumItem; iCount++)
542 TAB_InternalGetItemRect(infoPtr, iCount, &rect, NULL);
544 if (PtInRect(&rect, pt))
546 *flags = TCHT_ONITEM;
547 return iCount;
551 *flags = TCHT_NOWHERE;
552 return -1;
555 static inline LRESULT
556 TAB_HitTest (const TAB_INFO *infoPtr, LPTCHITTESTINFO lptest)
558 return TAB_InternalHitTest (infoPtr, lptest->pt, &lptest->flags);
561 /******************************************************************************
562 * TAB_NCHitTest
564 * Napster v2b5 has a tab control for its main navigation which has a client
565 * area that covers the whole area of the dialog pages.
566 * That's why it receives all msgs for that area and the underlying dialog ctrls
567 * are dead.
568 * So I decided that we should handle WM_NCHITTEST here and return
569 * HTTRANSPARENT if we don't hit the tab control buttons.
570 * FIXME: WM_NCHITTEST handling correct ? Fix it if you know that Windows
571 * doesn't do it that way. Maybe depends on tab control styles ?
573 static inline LRESULT
574 TAB_NCHitTest (const TAB_INFO *infoPtr, LPARAM lParam)
576 POINT pt;
577 UINT dummyflag;
579 pt.x = (short)LOWORD(lParam);
580 pt.y = (short)HIWORD(lParam);
581 ScreenToClient(infoPtr->hwnd, &pt);
583 if (TAB_InternalHitTest(infoPtr, pt, &dummyflag) == -1)
584 return HTTRANSPARENT;
585 else
586 return HTCLIENT;
589 static LRESULT
590 TAB_LButtonDown (TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
592 POINT pt;
593 INT newItem;
594 UINT dummy;
596 if (infoPtr->hwndToolTip)
597 TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
598 WM_LBUTTONDOWN, wParam, lParam);
600 if (GetWindowLongW(infoPtr->hwnd, GWL_STYLE) & TCS_FOCUSONBUTTONDOWN ) {
601 SetFocus (infoPtr->hwnd);
604 if (infoPtr->hwndToolTip)
605 TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
606 WM_LBUTTONDOWN, wParam, lParam);
608 pt.x = (short)LOWORD(lParam);
609 pt.y = (short)HIWORD(lParam);
611 newItem = TAB_InternalHitTest (infoPtr, pt, &dummy);
613 TRACE("On Tab, item %d\n", newItem);
615 if (newItem != -1 && infoPtr->iSelected != newItem)
617 if (!TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGING))
619 infoPtr->iSelected = newItem;
620 infoPtr->uFocus = newItem;
621 TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGE);
623 TAB_EnsureSelectionVisible(infoPtr);
625 TAB_InvalidateTabArea(infoPtr);
628 return 0;
631 static inline LRESULT
632 TAB_LButtonUp (const TAB_INFO *infoPtr)
634 TAB_SendSimpleNotify(infoPtr, NM_CLICK);
636 return 0;
639 static inline LRESULT
640 TAB_RButtonDown (const TAB_INFO *infoPtr)
642 TAB_SendSimpleNotify(infoPtr, NM_RCLICK);
643 return 0;
646 /******************************************************************************
647 * TAB_DrawLoneItemInterior
649 * This calls TAB_DrawItemInterior. However, TAB_DrawItemInterior is normally
650 * called by TAB_DrawItem which is normally called by TAB_Refresh which sets
651 * up the device context and font. This routine does the same setup but
652 * only calls TAB_DrawItemInterior for the single specified item.
654 static void
655 TAB_DrawLoneItemInterior(const TAB_INFO* infoPtr, int iItem)
657 HDC hdc = GetDC(infoPtr->hwnd);
658 RECT r, rC;
660 /* Clip UpDown control to not draw over it */
661 if (infoPtr->needsScrolling)
663 GetWindowRect(infoPtr->hwnd, &rC);
664 GetWindowRect(infoPtr->hwndUpDown, &r);
665 ExcludeClipRect(hdc, r.left - rC.left, r.top - rC.top, r.right - rC.left, r.bottom - rC.top);
667 TAB_DrawItemInterior(infoPtr, hdc, iItem, NULL);
668 ReleaseDC(infoPtr->hwnd, hdc);
671 /* update a tab after hottracking - invalidate it or just redraw the interior,
672 * based on whether theming is used or not */
673 static inline void hottrack_refresh(const TAB_INFO *infoPtr, int tabIndex)
675 if (tabIndex == -1) return;
677 if (GetWindowTheme (infoPtr->hwnd))
679 RECT rect;
680 TAB_InternalGetItemRect(infoPtr, tabIndex, &rect, NULL);
681 InvalidateRect (infoPtr->hwnd, &rect, FALSE);
683 else
684 TAB_DrawLoneItemInterior(infoPtr, tabIndex);
687 /******************************************************************************
688 * TAB_HotTrackTimerProc
690 * When a mouse-move event causes a tab to be highlighted (hot-tracking), a
691 * timer is setup so we can check if the mouse is moved out of our window.
692 * (We don't get an event when the mouse leaves, the mouse-move events just
693 * stop being delivered to our window and just start being delivered to
694 * another window.) This function is called when the timer triggers so
695 * we can check if the mouse has left our window. If so, we un-highlight
696 * the hot-tracked tab.
698 static void CALLBACK
699 TAB_HotTrackTimerProc
701 HWND hwnd, /* handle of window for timer messages */
702 UINT uMsg, /* WM_TIMER message */
703 UINT_PTR idEvent, /* timer identifier */
704 DWORD dwTime /* current system time */
707 TAB_INFO* infoPtr = TAB_GetInfoPtr(hwnd);
709 if (infoPtr != NULL && infoPtr->iHotTracked >= 0)
711 POINT pt;
714 ** If we can't get the cursor position, or if the cursor is outside our
715 ** window, we un-highlight the hot-tracked tab. Note that the cursor is
716 ** "outside" even if it is within our bounding rect if another window
717 ** overlaps. Note also that the case where the cursor stayed within our
718 ** window but has moved off the hot-tracked tab will be handled by the
719 ** WM_MOUSEMOVE event.
721 if (!GetCursorPos(&pt) || WindowFromPoint(pt) != hwnd)
723 /* Redraw iHotTracked to look normal */
724 INT iRedraw = infoPtr->iHotTracked;
725 infoPtr->iHotTracked = -1;
726 hottrack_refresh (infoPtr, iRedraw);
728 /* Kill this timer */
729 KillTimer(hwnd, TAB_HOTTRACK_TIMER);
734 /******************************************************************************
735 * TAB_RecalcHotTrack
737 * If a tab control has the TCS_HOTTRACK style, then the tab under the mouse
738 * should be highlighted. This function determines which tab in a tab control,
739 * if any, is under the mouse and records that information. The caller may
740 * supply output parameters to receive the item number of the tab item which
741 * was highlighted but isn't any longer and of the tab item which is now
742 * highlighted but wasn't previously. The caller can use this information to
743 * selectively redraw those tab items.
745 * If the caller has a mouse position, it can supply it through the pos
746 * parameter. For example, TAB_MouseMove does this. Otherwise, the caller
747 * supplies NULL and this function determines the current mouse position
748 * itself.
750 static void
751 TAB_RecalcHotTrack
753 TAB_INFO* infoPtr,
754 const LPARAM* pos,
755 int* out_redrawLeave,
756 int* out_redrawEnter
759 int item = -1;
762 if (out_redrawLeave != NULL)
763 *out_redrawLeave = -1;
764 if (out_redrawEnter != NULL)
765 *out_redrawEnter = -1;
767 if ((GetWindowLongW(infoPtr->hwnd, GWL_STYLE) & TCS_HOTTRACK)
768 || GetWindowTheme (infoPtr->hwnd))
770 POINT pt;
771 UINT flags;
773 if (pos == NULL)
775 GetCursorPos(&pt);
776 ScreenToClient(infoPtr->hwnd, &pt);
778 else
780 pt.x = (short)LOWORD(*pos);
781 pt.y = (short)HIWORD(*pos);
784 item = TAB_InternalHitTest(infoPtr, pt, &flags);
787 if (item != infoPtr->iHotTracked)
789 if (infoPtr->iHotTracked >= 0)
791 /* Mark currently hot-tracked to be redrawn to look normal */
792 if (out_redrawLeave != NULL)
793 *out_redrawLeave = infoPtr->iHotTracked;
795 if (item < 0)
797 /* Kill timer which forces recheck of mouse pos */
798 KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
801 else
803 /* Start timer so we recheck mouse pos */
804 UINT timerID = SetTimer
806 infoPtr->hwnd,
807 TAB_HOTTRACK_TIMER,
808 TAB_HOTTRACK_TIMER_INTERVAL,
809 TAB_HotTrackTimerProc
812 if (timerID == 0)
813 return; /* Hot tracking not available */
816 infoPtr->iHotTracked = item;
818 if (item >= 0)
820 /* Mark new hot-tracked to be redrawn to look highlighted */
821 if (out_redrawEnter != NULL)
822 *out_redrawEnter = item;
827 /******************************************************************************
828 * TAB_MouseMove
830 * Handles the mouse-move event. Updates tooltips. Updates hot-tracking.
832 static LRESULT
833 TAB_MouseMove (TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
835 int redrawLeave;
836 int redrawEnter;
838 if (infoPtr->hwndToolTip)
839 TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
840 WM_LBUTTONDOWN, wParam, lParam);
842 /* Determine which tab to highlight. Redraw tabs which change highlight
843 ** status. */
844 TAB_RecalcHotTrack(infoPtr, &lParam, &redrawLeave, &redrawEnter);
846 hottrack_refresh (infoPtr, redrawLeave);
847 hottrack_refresh (infoPtr, redrawEnter);
849 return 0;
852 /******************************************************************************
853 * TAB_AdjustRect
855 * Calculates the tab control's display area given the window rectangle or
856 * the window rectangle given the requested display rectangle.
858 static LRESULT TAB_AdjustRect(const TAB_INFO *infoPtr, WPARAM fLarger, LPRECT prc)
860 DWORD lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
861 LONG *iRightBottom, *iLeftTop;
863 TRACE ("hwnd=%p fLarger=%ld (%s)\n", infoPtr->hwnd, fLarger,
864 wine_dbgstr_rect(prc));
866 if (!prc) return -1;
868 if(lStyle & TCS_VERTICAL)
870 iRightBottom = &(prc->right);
871 iLeftTop = &(prc->left);
873 else
875 iRightBottom = &(prc->bottom);
876 iLeftTop = &(prc->top);
879 if (fLarger) /* Go from display rectangle */
881 /* Add the height of the tabs. */
882 if (lStyle & TCS_BOTTOM)
883 *iRightBottom += infoPtr->tabHeight * infoPtr->uNumRows;
884 else
885 *iLeftTop -= infoPtr->tabHeight * infoPtr->uNumRows +
886 ((lStyle & TCS_BUTTONS)? 3 * (infoPtr->uNumRows - 1) : 0);
888 /* Inflate the rectangle for the padding */
889 InflateRect(prc, DISPLAY_AREA_PADDINGX, DISPLAY_AREA_PADDINGY);
891 /* Inflate for the border */
892 InflateRect(prc, CONTROL_BORDER_SIZEX, CONTROL_BORDER_SIZEY);
894 else /* Go from window rectangle. */
896 /* Deflate the rectangle for the border */
897 InflateRect(prc, -CONTROL_BORDER_SIZEX, -CONTROL_BORDER_SIZEY);
899 /* Deflate the rectangle for the padding */
900 InflateRect(prc, -DISPLAY_AREA_PADDINGX, -DISPLAY_AREA_PADDINGY);
902 /* Remove the height of the tabs. */
903 if (lStyle & TCS_BOTTOM)
904 *iRightBottom -= infoPtr->tabHeight * infoPtr->uNumRows;
905 else
906 *iLeftTop += (infoPtr->tabHeight) * infoPtr->uNumRows +
907 ((lStyle & TCS_BUTTONS)? 3 * (infoPtr->uNumRows - 1) : 0);
910 return 0;
913 /******************************************************************************
914 * TAB_OnHScroll
916 * This method will handle the notification from the scroll control and
917 * perform the scrolling operation on the tab control.
919 static LRESULT TAB_OnHScroll(TAB_INFO *infoPtr, int nScrollCode, int nPos, HWND hwndScroll)
921 if(nScrollCode == SB_THUMBPOSITION && nPos != infoPtr->leftmostVisible)
923 if(nPos < infoPtr->leftmostVisible)
924 infoPtr->leftmostVisible--;
925 else
926 infoPtr->leftmostVisible++;
928 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
929 TAB_InvalidateTabArea(infoPtr);
930 SendMessageW(infoPtr->hwndUpDown, UDM_SETPOS, 0,
931 MAKELONG(infoPtr->leftmostVisible, 0));
934 return 0;
937 /******************************************************************************
938 * TAB_SetupScrolling
940 * This method will check the current scrolling state and make sure the
941 * scrolling control is displayed (or not).
943 static void TAB_SetupScrolling(
944 HWND hwnd,
945 TAB_INFO* infoPtr,
946 const RECT* clientRect)
948 static const WCHAR msctls_updown32W[] = { 'm','s','c','t','l','s','_','u','p','d','o','w','n','3','2',0 };
949 static const WCHAR emptyW[] = { 0 };
950 INT maxRange = 0;
951 DWORD lStyle = GetWindowLongW(hwnd, GWL_STYLE);
953 if (infoPtr->needsScrolling)
955 RECT controlPos;
956 INT vsize, tabwidth;
959 * Calculate the position of the scroll control.
961 if(lStyle & TCS_VERTICAL)
963 controlPos.right = clientRect->right;
964 controlPos.left = controlPos.right - 2 * GetSystemMetrics(SM_CXHSCROLL);
966 if (lStyle & TCS_BOTTOM)
968 controlPos.top = clientRect->bottom - infoPtr->tabHeight;
969 controlPos.bottom = controlPos.top + GetSystemMetrics(SM_CYHSCROLL);
971 else
973 controlPos.bottom = clientRect->top + infoPtr->tabHeight;
974 controlPos.top = controlPos.bottom - GetSystemMetrics(SM_CYHSCROLL);
977 else
979 controlPos.right = clientRect->right;
980 controlPos.left = controlPos.right - 2 * GetSystemMetrics(SM_CXHSCROLL);
982 if (lStyle & TCS_BOTTOM)
984 controlPos.top = clientRect->bottom - infoPtr->tabHeight;
985 controlPos.bottom = controlPos.top + GetSystemMetrics(SM_CYHSCROLL);
987 else
989 controlPos.bottom = clientRect->top + infoPtr->tabHeight;
990 controlPos.top = controlPos.bottom - GetSystemMetrics(SM_CYHSCROLL);
995 * If we don't have a scroll control yet, we want to create one.
996 * If we have one, we want to make sure it's positioned properly.
998 if (infoPtr->hwndUpDown==0)
1000 infoPtr->hwndUpDown = CreateWindowW(msctls_updown32W, emptyW,
1001 WS_VISIBLE | WS_CHILD | UDS_HORZ,
1002 controlPos.left, controlPos.top,
1003 controlPos.right - controlPos.left,
1004 controlPos.bottom - controlPos.top,
1005 hwnd, NULL, NULL, NULL);
1007 else
1009 SetWindowPos(infoPtr->hwndUpDown,
1010 NULL,
1011 controlPos.left, controlPos.top,
1012 controlPos.right - controlPos.left,
1013 controlPos.bottom - controlPos.top,
1014 SWP_SHOWWINDOW | SWP_NOZORDER);
1017 /* Now calculate upper limit of the updown control range.
1018 * We do this by calculating how many tabs will be offscreen when the
1019 * last tab is visible.
1021 if(infoPtr->uNumItem)
1023 vsize = clientRect->right - (controlPos.right - controlPos.left + 1);
1024 maxRange = infoPtr->uNumItem;
1025 tabwidth = TAB_GetItem(infoPtr, infoPtr->uNumItem - 1)->rect.right;
1027 for(; maxRange > 0; maxRange--)
1029 if(tabwidth - TAB_GetItem(infoPtr,maxRange - 1)->rect.left > vsize)
1030 break;
1033 if(maxRange == infoPtr->uNumItem)
1034 maxRange--;
1037 else
1039 /* If we once had a scroll control... hide it */
1040 if (infoPtr->hwndUpDown!=0)
1041 ShowWindow(infoPtr->hwndUpDown, SW_HIDE);
1043 if (infoPtr->hwndUpDown)
1044 SendMessageW(infoPtr->hwndUpDown, UDM_SETRANGE32, 0, maxRange);
1047 /******************************************************************************
1048 * TAB_SetItemBounds
1050 * This method will calculate the position rectangles of all the items in the
1051 * control. The rectangle calculated starts at 0 for the first item in the
1052 * list and ignores scrolling and selection.
1053 * It also uses the current font to determine the height of the tab row and
1054 * it checks if all the tabs fit in the client area of the window. If they
1055 * don't, a scrolling control is added.
1057 static void TAB_SetItemBounds (TAB_INFO *infoPtr)
1059 LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1060 TEXTMETRICW fontMetrics;
1061 UINT curItem;
1062 INT curItemLeftPos;
1063 INT curItemRowCount;
1064 HFONT hFont, hOldFont;
1065 HDC hdc;
1066 RECT clientRect;
1067 INT iTemp;
1068 RECT* rcItem;
1069 INT iIndex;
1070 INT icon_width = 0;
1073 * We need to get text information so we need a DC and we need to select
1074 * a font.
1076 hdc = GetDC(infoPtr->hwnd);
1078 hFont = infoPtr->hFont ? infoPtr->hFont : GetStockObject (SYSTEM_FONT);
1079 hOldFont = SelectObject (hdc, hFont);
1082 * We will base the rectangle calculations on the client rectangle
1083 * of the control.
1085 GetClientRect(infoPtr->hwnd, &clientRect);
1087 /* if TCS_VERTICAL then swap the height and width so this code places the
1088 tabs along the top of the rectangle and we can just rotate them after
1089 rather than duplicate all of the below code */
1090 if(lStyle & TCS_VERTICAL)
1092 iTemp = clientRect.bottom;
1093 clientRect.bottom = clientRect.right;
1094 clientRect.right = iTemp;
1097 /* Now use hPadding and vPadding */
1098 infoPtr->uHItemPadding = infoPtr->uHItemPadding_s;
1099 infoPtr->uVItemPadding = infoPtr->uVItemPadding_s;
1101 /* The leftmost item will be "0" aligned */
1102 curItemLeftPos = 0;
1103 curItemRowCount = infoPtr->uNumItem ? 1 : 0;
1105 if (!(infoPtr->fHeightSet))
1107 int item_height;
1108 int icon_height = 0;
1110 /* Use the current font to determine the height of a tab. */
1111 GetTextMetricsW(hdc, &fontMetrics);
1113 /* Get the icon height */
1114 if (infoPtr->himl)
1115 ImageList_GetIconSize(infoPtr->himl, 0, &icon_height);
1117 /* Take the highest between font or icon */
1118 if (fontMetrics.tmHeight > icon_height)
1119 item_height = fontMetrics.tmHeight + 2;
1120 else
1121 item_height = icon_height;
1124 * Make sure there is enough space for the letters + icon + growing the
1125 * selected item + extra space for the selected item.
1127 infoPtr->tabHeight = item_height +
1128 ((lStyle & TCS_BUTTONS) ? 2 : 1) *
1129 infoPtr->uVItemPadding;
1131 TRACE("tabH=%d, tmH=%d, iconh=%d\n",
1132 infoPtr->tabHeight, fontMetrics.tmHeight, icon_height);
1135 TRACE("client right=%d\n", clientRect.right);
1137 /* Get the icon width */
1138 if (infoPtr->himl)
1140 ImageList_GetIconSize(infoPtr->himl, &icon_width, 0);
1142 if (lStyle & TCS_FIXEDWIDTH)
1143 icon_width += 4;
1144 else
1145 /* Add padding if icon is present */
1146 icon_width += infoPtr->uHItemPadding;
1149 for (curItem = 0; curItem < infoPtr->uNumItem; curItem++)
1151 TAB_ITEM *curr = TAB_GetItem(infoPtr, curItem);
1153 /* Set the leftmost position of the tab. */
1154 curr->rect.left = curItemLeftPos;
1156 if (lStyle & TCS_FIXEDWIDTH)
1158 curr->rect.right = curr->rect.left +
1159 max(infoPtr->tabWidth, icon_width);
1161 else if (!curr->pszText)
1163 /* If no text use minimum tab width including padding. */
1164 if (infoPtr->tabMinWidth < 0)
1165 curr->rect.right = curr->rect.left + GET_DEFAULT_MIN_TAB_WIDTH(infoPtr);
1166 else
1168 curr->rect.right = curr->rect.left + infoPtr->tabMinWidth;
1170 /* Add extra padding if icon is present */
1171 if (infoPtr->himl && infoPtr->tabMinWidth > 0 && infoPtr->tabMinWidth < DEFAULT_MIN_TAB_WIDTH
1172 && infoPtr->uHItemPadding > 1)
1173 curr->rect.right += EXTRA_ICON_PADDING * (infoPtr->uHItemPadding-1);
1176 else
1178 int tabwidth;
1179 SIZE size;
1180 /* Calculate how wide the tab is depending on the text it contains */
1181 GetTextExtentPoint32W(hdc, curr->pszText,
1182 lstrlenW(curr->pszText), &size);
1184 tabwidth = size.cx + icon_width + 2 * infoPtr->uHItemPadding;
1186 if (infoPtr->tabMinWidth < 0)
1187 tabwidth = max(tabwidth, GET_DEFAULT_MIN_TAB_WIDTH(infoPtr));
1188 else
1189 tabwidth = max(tabwidth, infoPtr->tabMinWidth);
1191 curr->rect.right = curr->rect.left + tabwidth;
1192 TRACE("for <%s>, l,r=%d,%d\n",
1193 debugstr_w(curr->pszText), curr->rect.left, curr->rect.right);
1197 * Check if this is a multiline tab control and if so
1198 * check to see if we should wrap the tabs
1200 * Wrap all these tabs. We will arrange them evenly later.
1204 if (((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)) &&
1205 (curr->rect.right >
1206 (clientRect.right - CONTROL_BORDER_SIZEX - DISPLAY_AREA_PADDINGX)))
1208 curr->rect.right -= curr->rect.left;
1210 curr->rect.left = 0;
1211 curItemRowCount++;
1212 TRACE("wrapping <%s>, l,r=%d,%d\n", debugstr_w(curr->pszText),
1213 curr->rect.left, curr->rect.right);
1216 curr->rect.bottom = 0;
1217 curr->rect.top = curItemRowCount - 1;
1219 TRACE("Rect: %s\n", wine_dbgstr_rect(&curr->rect));
1222 * The leftmost position of the next item is the rightmost position
1223 * of this one.
1225 if (lStyle & TCS_BUTTONS)
1227 curItemLeftPos = curr->rect.right + BUTTON_SPACINGX;
1228 if (lStyle & TCS_FLATBUTTONS)
1229 curItemLeftPos += FLAT_BTN_SPACINGX;
1231 else
1232 curItemLeftPos = curr->rect.right;
1235 if (!((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)))
1238 * Check if we need a scrolling control.
1240 infoPtr->needsScrolling = (curItemLeftPos + (2 * SELECTED_TAB_OFFSET) >
1241 clientRect.right);
1243 /* Don't need scrolling, then update infoPtr->leftmostVisible */
1244 if(!infoPtr->needsScrolling)
1245 infoPtr->leftmostVisible = 0;
1247 else
1250 * No scrolling in Multiline or Vertical styles.
1252 infoPtr->needsScrolling = FALSE;
1253 infoPtr->leftmostVisible = 0;
1255 TAB_SetupScrolling(infoPtr->hwnd, infoPtr, &clientRect);
1257 /* Set the number of rows */
1258 infoPtr->uNumRows = curItemRowCount;
1260 /* Arrange all tabs evenly if style says so */
1261 if (!(lStyle & TCS_RAGGEDRIGHT) &&
1262 ((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)) &&
1263 (infoPtr->uNumItem > 0) &&
1264 (infoPtr->uNumRows > 1))
1266 INT tabPerRow,remTab,iRow;
1267 UINT iItm;
1268 INT iCount=0;
1271 * Ok windows tries to even out the rows. place the same
1272 * number of tabs in each row. So lets give that a shot
1275 tabPerRow = infoPtr->uNumItem / (infoPtr->uNumRows);
1276 remTab = infoPtr->uNumItem % (infoPtr->uNumRows);
1278 for (iItm=0,iRow=0,iCount=0,curItemLeftPos=0;
1279 iItm<infoPtr->uNumItem;
1280 iItm++,iCount++)
1282 /* normalize the current rect */
1283 TAB_ITEM *curr = TAB_GetItem(infoPtr, iItm);
1285 /* shift the item to the left side of the clientRect */
1286 curr->rect.right -= curr->rect.left;
1287 curr->rect.left = 0;
1289 TRACE("r=%d, cl=%d, cl.r=%d, iCount=%d, iRow=%d, uNumRows=%d, remTab=%d, tabPerRow=%d\n",
1290 curr->rect.right, curItemLeftPos, clientRect.right,
1291 iCount, iRow, infoPtr->uNumRows, remTab, tabPerRow);
1293 /* if we have reached the maximum number of tabs on this row */
1294 /* move to the next row, reset our current item left position and */
1295 /* the count of items on this row */
1297 if (lStyle & TCS_VERTICAL) {
1298 /* Vert: Add the remaining tabs in the *last* remainder rows */
1299 if (iCount >= ((iRow>=(INT)infoPtr->uNumRows - remTab)?tabPerRow + 1:tabPerRow)) {
1300 iRow++;
1301 curItemLeftPos = 0;
1302 iCount = 0;
1304 } else {
1305 /* Horz: Add the remaining tabs in the *first* remainder rows */
1306 if (iCount >= ((iRow<remTab)?tabPerRow + 1:tabPerRow)) {
1307 iRow++;
1308 curItemLeftPos = 0;
1309 iCount = 0;
1313 /* shift the item to the right to place it as the next item in this row */
1314 curr->rect.left += curItemLeftPos;
1315 curr->rect.right += curItemLeftPos;
1316 curr->rect.top = iRow;
1317 if (lStyle & TCS_BUTTONS)
1319 curItemLeftPos = curr->rect.right + 1;
1320 if (lStyle & TCS_FLATBUTTONS)
1321 curItemLeftPos += FLAT_BTN_SPACINGX;
1323 else
1324 curItemLeftPos = curr->rect.right;
1326 TRACE("arranging <%s>, l,r=%d,%d, row=%d\n",
1327 debugstr_w(curr->pszText), curr->rect.left,
1328 curr->rect.right, curr->rect.top);
1332 * Justify the rows
1335 INT widthDiff, iIndexStart=0, iIndexEnd=0;
1336 INT remainder;
1337 INT iCount=0;
1339 while(iIndexStart < infoPtr->uNumItem)
1341 TAB_ITEM *start = TAB_GetItem(infoPtr, iIndexStart);
1344 * find the index of the row
1346 /* find the first item on the next row */
1347 for (iIndexEnd=iIndexStart;
1348 (iIndexEnd < infoPtr->uNumItem) &&
1349 (TAB_GetItem(infoPtr, iIndexEnd)->rect.top ==
1350 start->rect.top) ;
1351 iIndexEnd++)
1352 /* intentionally blank */;
1355 * we need to justify these tabs so they fill the whole given
1356 * client area
1359 /* find the amount of space remaining on this row */
1360 widthDiff = clientRect.right - (2 * SELECTED_TAB_OFFSET) -
1361 TAB_GetItem(infoPtr, iIndexEnd - 1)->rect.right;
1363 /* iCount is the number of tab items on this row */
1364 iCount = iIndexEnd - iIndexStart;
1366 if (iCount > 1)
1368 remainder = widthDiff % iCount;
1369 widthDiff = widthDiff / iCount;
1370 /* add widthDiff/iCount, or extra space/items on row, to each item on this row */
1371 for (iIndex=iIndexStart, iCount=0; iIndex < iIndexEnd; iIndex++, iCount++)
1373 TAB_ITEM *item = TAB_GetItem(infoPtr, iIndex);
1375 item->rect.left += iCount * widthDiff;
1376 item->rect.right += (iCount + 1) * widthDiff;
1378 TRACE("adjusting 1 <%s>, l,r=%d,%d\n",
1379 debugstr_w(item->pszText),
1380 item->rect.left, item->rect.right);
1383 TAB_GetItem(infoPtr, iIndex - 1)->rect.right += remainder;
1385 else /* we have only one item on this row, make it take up the entire row */
1387 start->rect.left = clientRect.left;
1388 start->rect.right = clientRect.right - 4;
1390 TRACE("adjusting 2 <%s>, l,r=%d,%d\n",
1391 debugstr_w(start->pszText),
1392 start->rect.left, start->rect.right);
1397 iIndexStart = iIndexEnd;
1402 /* if TCS_VERTICAL rotate the tabs so they are along the side of the clientRect */
1403 if(lStyle & TCS_VERTICAL)
1405 RECT rcOriginal;
1406 for(iIndex = 0; iIndex < infoPtr->uNumItem; iIndex++)
1408 rcItem = &TAB_GetItem(infoPtr, iIndex)->rect;
1410 rcOriginal = *rcItem;
1412 /* this is rotating the items by 90 degrees clockwise around the center of the control */
1413 rcItem->top = (rcOriginal.left - clientRect.left);
1414 rcItem->bottom = rcItem->top + (rcOriginal.right - rcOriginal.left);
1415 rcItem->left = rcOriginal.top;
1416 rcItem->right = rcOriginal.bottom;
1420 TAB_EnsureSelectionVisible(infoPtr);
1421 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
1423 /* Cleanup */
1424 SelectObject (hdc, hOldFont);
1425 ReleaseDC (infoPtr->hwnd, hdc);
1429 static void
1430 TAB_EraseTabInterior(const TAB_INFO *infoPtr, HDC hdc, INT iItem, RECT *drawRect)
1432 LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1433 HBRUSH hbr = CreateSolidBrush (comctl32_color.clrBtnFace);
1434 BOOL deleteBrush = TRUE;
1435 RECT rTemp = *drawRect;
1437 InflateRect(&rTemp, -2, -2);
1438 if (lStyle & TCS_BUTTONS)
1440 if (iItem == infoPtr->iSelected)
1442 /* Background color */
1443 if (!(lStyle & TCS_OWNERDRAWFIXED))
1445 DeleteObject(hbr);
1446 hbr = GetSysColorBrush(COLOR_SCROLLBAR);
1448 SetTextColor(hdc, comctl32_color.clr3dFace);
1449 SetBkColor(hdc, comctl32_color.clr3dHilight);
1451 /* if COLOR_WINDOW happens to be the same as COLOR_3DHILIGHT
1452 * we better use 0x55aa bitmap brush to make scrollbar's background
1453 * look different from the window background.
1455 if (comctl32_color.clr3dHilight == comctl32_color.clrWindow)
1456 hbr = COMCTL32_hPattern55AABrush;
1458 deleteBrush = FALSE;
1460 FillRect(hdc, &rTemp, hbr);
1462 else /* ! selected */
1464 if (lStyle & TCS_FLATBUTTONS)
1466 FillRect(hdc, drawRect, hbr);
1467 if (iItem == infoPtr->iHotTracked)
1468 DrawEdge(hdc, drawRect, EDGE_RAISED, BF_SOFT|BF_RECT);
1470 else
1471 FillRect(hdc, &rTemp, hbr);
1475 else /* !TCS_BUTTONS */
1477 if (!GetWindowTheme (infoPtr->hwnd))
1478 FillRect(hdc, &rTemp, hbr);
1481 /* Cleanup */
1482 if (deleteBrush) DeleteObject(hbr);
1485 /******************************************************************************
1486 * TAB_DrawItemInterior
1488 * This method is used to draw the interior (text and icon) of a single tab
1489 * into the tab control.
1491 static void
1492 TAB_DrawItemInterior(const TAB_INFO *infoPtr, HDC hdc, INT iItem, RECT *drawRect)
1494 LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1496 RECT localRect;
1498 HPEN htextPen;
1499 HPEN holdPen;
1500 INT oldBkMode;
1501 HFONT hOldFont;
1503 /* if (drawRect == NULL) */
1505 BOOL isVisible;
1506 RECT itemRect;
1507 RECT selectedRect;
1510 * Get the rectangle for the item.
1512 isVisible = TAB_InternalGetItemRect(infoPtr, iItem, &itemRect, &selectedRect);
1513 if (!isVisible)
1514 return;
1517 * Make sure drawRect points to something valid; simplifies code.
1519 drawRect = &localRect;
1522 * This logic copied from the part of TAB_DrawItem which draws
1523 * the tab background. It's important to keep it in sync. I
1524 * would have liked to avoid code duplication, but couldn't figure
1525 * out how without making spaghetti of TAB_DrawItem.
1527 if (iItem == infoPtr->iSelected)
1528 *drawRect = selectedRect;
1529 else
1530 *drawRect = itemRect;
1532 if (lStyle & TCS_BUTTONS)
1534 if (iItem == infoPtr->iSelected)
1536 drawRect->left += 4;
1537 drawRect->top += 4;
1538 drawRect->right -= 4;
1539 drawRect->bottom -= 1;
1541 else
1543 drawRect->left += 2;
1544 drawRect->top += 2;
1545 drawRect->right -= 2;
1546 drawRect->bottom -= 2;
1549 else
1551 if ((lStyle & TCS_VERTICAL) && (lStyle & TCS_BOTTOM))
1553 if (iItem != infoPtr->iSelected)
1555 drawRect->left += 2;
1556 drawRect->top += 2;
1557 drawRect->bottom -= 2;
1560 else if (lStyle & TCS_VERTICAL)
1562 if (iItem == infoPtr->iSelected)
1564 drawRect->right += 1;
1566 else
1568 drawRect->top += 2;
1569 drawRect->right -= 2;
1570 drawRect->bottom -= 2;
1573 else if (lStyle & TCS_BOTTOM)
1575 if (iItem == infoPtr->iSelected)
1577 drawRect->top -= 2;
1579 else
1581 InflateRect(drawRect, -2, -2);
1582 drawRect->bottom += 2;
1585 else
1587 if (iItem == infoPtr->iSelected)
1589 drawRect->bottom += 3;
1591 else
1593 drawRect->bottom -= 2;
1594 InflateRect(drawRect, -2, 0);
1599 TRACE("drawRect=(%s)\n", wine_dbgstr_rect(drawRect));
1601 /* Clear interior */
1602 TAB_EraseTabInterior (infoPtr, hdc, iItem, drawRect);
1604 /* Draw the focus rectangle */
1605 if (!(lStyle & TCS_FOCUSNEVER) &&
1606 (GetFocus() == infoPtr->hwnd) &&
1607 (iItem == infoPtr->uFocus) )
1609 RECT rFocus = *drawRect;
1610 InflateRect(&rFocus, -3, -3);
1611 if (lStyle & TCS_BOTTOM && !(lStyle & TCS_VERTICAL))
1612 rFocus.top -= 3;
1613 if (lStyle & TCS_BUTTONS)
1615 rFocus.left -= 3;
1616 rFocus.top -= 3;
1619 DrawFocusRect(hdc, &rFocus);
1623 * Text pen
1625 htextPen = CreatePen( PS_SOLID, 1, GetSysColor(COLOR_BTNTEXT) );
1626 holdPen = SelectObject(hdc, htextPen);
1627 hOldFont = SelectObject(hdc, infoPtr->hFont);
1630 * Setup for text output
1632 oldBkMode = SetBkMode(hdc, TRANSPARENT);
1633 if (!GetWindowTheme (infoPtr->hwnd) || (lStyle & TCS_BUTTONS))
1634 SetTextColor(hdc, (((lStyle & TCS_HOTTRACK) && (iItem == infoPtr->iHotTracked)
1635 && !(lStyle & TCS_FLATBUTTONS))
1636 | (TAB_GetItem(infoPtr, iItem)->dwState & TCIS_HIGHLIGHTED)) ?
1637 comctl32_color.clrHighlight : comctl32_color.clrBtnText);
1640 * if owner draw, tell the owner to draw
1642 if ((lStyle & TCS_OWNERDRAWFIXED) && GetParent(infoPtr->hwnd))
1644 DRAWITEMSTRUCT dis;
1645 UINT id;
1647 drawRect->top += 2;
1648 drawRect->right -= 1;
1649 if ( iItem == infoPtr->iSelected )
1651 drawRect->right -= 1;
1652 drawRect->left += 1;
1656 * get the control id
1658 id = (UINT)GetWindowLongPtrW( infoPtr->hwnd, GWLP_ID );
1661 * put together the DRAWITEMSTRUCT
1663 dis.CtlType = ODT_TAB;
1664 dis.CtlID = id;
1665 dis.itemID = iItem;
1666 dis.itemAction = ODA_DRAWENTIRE;
1667 dis.itemState = 0;
1668 if ( iItem == infoPtr->iSelected )
1669 dis.itemState |= ODS_SELECTED;
1670 if (infoPtr->uFocus == iItem)
1671 dis.itemState |= ODS_FOCUS;
1672 dis.hwndItem = infoPtr->hwnd;
1673 dis.hDC = hdc;
1674 CopyRect(&dis.rcItem,drawRect);
1675 dis.itemData = (ULONG_PTR)TAB_GetItem(infoPtr, iItem)->extra;
1678 * send the draw message
1680 SendMessageW( infoPtr->hwndNotify, WM_DRAWITEM, (WPARAM)id, (LPARAM)&dis );
1682 else
1684 TAB_ITEM *item = TAB_GetItem(infoPtr, iItem);
1685 RECT rcTemp;
1686 RECT rcImage;
1688 /* used to center the icon and text in the tab */
1689 RECT rcText;
1690 INT center_offset_h, center_offset_v;
1692 /* set rcImage to drawRect, we will use top & left in our ImageList_Draw call */
1693 rcImage = *drawRect;
1695 rcTemp = *drawRect;
1697 rcText.left = rcText.top = rcText.right = rcText.bottom = 0;
1699 /* get the rectangle that the text fits in */
1700 if (item->pszText)
1702 DrawTextW(hdc, item->pszText, -1, &rcText, DT_CALCRECT);
1705 * If not owner draw, then do the drawing ourselves.
1707 * Draw the icon.
1709 if (infoPtr->himl && item->iImage != -1)
1711 INT cx;
1712 INT cy;
1714 ImageList_GetIconSize(infoPtr->himl, &cx, &cy);
1716 if(lStyle & TCS_VERTICAL)
1718 center_offset_h = ((drawRect->bottom - drawRect->top) - (cy + infoPtr->uHItemPadding + (rcText.right - rcText.left))) / 2;
1719 center_offset_v = ((drawRect->right - drawRect->left) - cx) / 2;
1721 else
1723 center_offset_h = ((drawRect->right - drawRect->left) - (cx + infoPtr->uHItemPadding + (rcText.right - rcText.left))) / 2;
1724 center_offset_v = ((drawRect->bottom - drawRect->top) - cy) / 2;
1727 /* if an item is selected, the icon is shifted up instead of down */
1728 if (iItem == infoPtr->iSelected)
1729 center_offset_v -= infoPtr->uVItemPadding / 2;
1730 else
1731 center_offset_v += infoPtr->uVItemPadding / 2;
1733 if (lStyle & TCS_FIXEDWIDTH && lStyle & (TCS_FORCELABELLEFT | TCS_FORCEICONLEFT))
1734 center_offset_h = infoPtr->uHItemPadding;
1736 if (center_offset_h < 2)
1737 center_offset_h = 2;
1739 if (center_offset_v < 0)
1740 center_offset_v = 0;
1742 TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%s), textlen=%d\n",
1743 debugstr_w(item->pszText), center_offset_h, center_offset_v,
1744 wine_dbgstr_rect(drawRect), (rcText.right-rcText.left));
1746 if((lStyle & TCS_VERTICAL) && (lStyle & TCS_BOTTOM))
1748 rcImage.top = drawRect->top + center_offset_h;
1749 /* if tab is TCS_VERTICAL and TCS_BOTTOM, the text is drawn from the */
1750 /* right side of the tab, but the image still uses the left as its x position */
1751 /* this keeps the image always drawn off of the same side of the tab */
1752 rcImage.left = drawRect->right - cx - center_offset_v;
1753 drawRect->top += cy + infoPtr->uHItemPadding;
1755 else if(lStyle & TCS_VERTICAL)
1757 rcImage.top = drawRect->bottom - cy - center_offset_h;
1758 rcImage.left = drawRect->left + center_offset_v;
1759 drawRect->bottom -= cy + infoPtr->uHItemPadding;
1761 else /* normal style, whether TCS_BOTTOM or not */
1763 rcImage.left = drawRect->left + center_offset_h;
1764 rcImage.top = drawRect->top + center_offset_v;
1765 drawRect->left += cx + infoPtr->uHItemPadding;
1768 TRACE("drawing image=%d, left=%d, top=%d\n",
1769 item->iImage, rcImage.left, rcImage.top-1);
1770 ImageList_Draw
1772 infoPtr->himl,
1773 item->iImage,
1774 hdc,
1775 rcImage.left,
1776 rcImage.top,
1777 ILD_NORMAL
1781 /* Now position text */
1782 if (lStyle & TCS_FIXEDWIDTH && lStyle & TCS_FORCELABELLEFT)
1783 center_offset_h = infoPtr->uHItemPadding;
1784 else
1785 if(lStyle & TCS_VERTICAL)
1786 center_offset_h = ((drawRect->bottom - drawRect->top) - (rcText.right - rcText.left)) / 2;
1787 else
1788 center_offset_h = ((drawRect->right - drawRect->left) - (rcText.right - rcText.left)) / 2;
1790 if(lStyle & TCS_VERTICAL)
1792 if(lStyle & TCS_BOTTOM)
1793 drawRect->top+=center_offset_h;
1794 else
1795 drawRect->bottom-=center_offset_h;
1797 center_offset_v = ((drawRect->right - drawRect->left) - (rcText.bottom - rcText.top)) / 2;
1799 else
1801 drawRect->left += center_offset_h;
1802 center_offset_v = ((drawRect->bottom - drawRect->top) - (rcText.bottom - rcText.top)) / 2;
1805 /* if an item is selected, the text is shifted up instead of down */
1806 if (iItem == infoPtr->iSelected)
1807 center_offset_v -= infoPtr->uVItemPadding / 2;
1808 else
1809 center_offset_v += infoPtr->uVItemPadding / 2;
1811 if (center_offset_v < 0)
1812 center_offset_v = 0;
1814 if(lStyle & TCS_VERTICAL)
1815 drawRect->left += center_offset_v;
1816 else
1817 drawRect->top += center_offset_v;
1819 /* Draw the text */
1820 if(lStyle & TCS_VERTICAL) /* if we are vertical rotate the text and each character */
1822 static const WCHAR ArialW[] = { 'A','r','i','a','l',0 };
1823 LOGFONTW logfont;
1824 HFONT hFont = 0;
1825 INT nEscapement = 900;
1826 INT nOrientation = 900;
1828 if(lStyle & TCS_BOTTOM)
1830 nEscapement = -900;
1831 nOrientation = -900;
1834 /* to get a font with the escapement and orientation we are looking for, we need to */
1835 /* call CreateFontIndirectA, which requires us to set the values of the logfont we pass in */
1836 if (!GetObjectW((infoPtr->hFont) ?
1837 infoPtr->hFont : GetStockObject(SYSTEM_FONT),
1838 sizeof(LOGFONTW),&logfont))
1840 INT iPointSize = 9;
1842 lstrcpyW(logfont.lfFaceName, ArialW);
1843 logfont.lfHeight = -MulDiv(iPointSize, GetDeviceCaps(hdc, LOGPIXELSY),
1844 72);
1845 logfont.lfWeight = FW_NORMAL;
1846 logfont.lfItalic = 0;
1847 logfont.lfUnderline = 0;
1848 logfont.lfStrikeOut = 0;
1851 logfont.lfEscapement = nEscapement;
1852 logfont.lfOrientation = nOrientation;
1853 hFont = CreateFontIndirectW(&logfont);
1854 SelectObject(hdc, hFont);
1856 if (item->pszText)
1858 ExtTextOutW(hdc,
1859 (lStyle & TCS_BOTTOM) ? drawRect->right : drawRect->left,
1860 (!(lStyle & TCS_BOTTOM)) ? drawRect->bottom : drawRect->top,
1861 ETO_CLIPPED,
1862 drawRect,
1863 item->pszText,
1864 lstrlenW(item->pszText),
1868 DeleteObject(hFont);
1870 else
1872 TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%s), textlen=%d\n",
1873 debugstr_w(item->pszText), center_offset_h, center_offset_v,
1874 wine_dbgstr_rect(drawRect), (rcText.right-rcText.left));
1875 if (item->pszText)
1877 DrawTextW
1879 hdc,
1880 item->pszText,
1881 lstrlenW(item->pszText),
1882 drawRect,
1883 DT_LEFT | DT_SINGLELINE
1888 *drawRect = rcTemp; /* restore drawRect */
1892 * Cleanup
1894 SelectObject(hdc, hOldFont);
1895 SetBkMode(hdc, oldBkMode);
1896 SelectObject(hdc, holdPen);
1897 DeleteObject( htextPen );
1900 /******************************************************************************
1901 * TAB_DrawItem
1903 * This method is used to draw a single tab into the tab control.
1905 static void TAB_DrawItem(const TAB_INFO *infoPtr, HDC hdc, INT iItem)
1907 LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1908 RECT itemRect;
1909 RECT selectedRect;
1910 BOOL isVisible;
1911 RECT r, fillRect, r1;
1912 INT clRight = 0;
1913 INT clBottom = 0;
1914 COLORREF bkgnd, corner;
1915 HTHEME theme;
1918 * Get the rectangle for the item.
1920 isVisible = TAB_InternalGetItemRect(infoPtr,
1921 iItem,
1922 &itemRect,
1923 &selectedRect);
1925 if (isVisible)
1927 RECT rUD, rC;
1929 /* Clip UpDown control to not draw over it */
1930 if (infoPtr->needsScrolling)
1932 GetWindowRect(infoPtr->hwnd, &rC);
1933 GetWindowRect(infoPtr->hwndUpDown, &rUD);
1934 ExcludeClipRect(hdc, rUD.left - rC.left, rUD.top - rC.top, rUD.right - rC.left, rUD.bottom - rC.top);
1937 /* If you need to see what the control is doing,
1938 * then override these variables. They will change what
1939 * fill colors are used for filling the tabs, and the
1940 * corners when drawing the edge.
1942 bkgnd = comctl32_color.clrBtnFace;
1943 corner = comctl32_color.clrBtnFace;
1945 if (lStyle & TCS_BUTTONS)
1947 /* Get item rectangle */
1948 r = itemRect;
1950 /* Separators between flat buttons */
1951 if (lStyle & TCS_FLATBUTTONS)
1953 r1 = r;
1954 r1.right += (FLAT_BTN_SPACINGX -2);
1955 DrawEdge(hdc, &r1, EDGE_ETCHED, BF_RIGHT);
1958 if (iItem == infoPtr->iSelected)
1960 DrawEdge(hdc, &r, EDGE_SUNKEN, BF_SOFT|BF_RECT);
1962 OffsetRect(&r, 1, 1);
1964 else /* ! selected */
1966 if (!(lStyle & TCS_FLATBUTTONS))
1967 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_RECT);
1970 else /* !TCS_BUTTONS */
1972 /* We draw a rectangle of different sizes depending on the selection
1973 * state. */
1974 if (iItem == infoPtr->iSelected) {
1975 RECT rect;
1976 GetClientRect (infoPtr->hwnd, &rect);
1977 clRight = rect.right;
1978 clBottom = rect.bottom;
1979 r = selectedRect;
1981 else
1982 r = itemRect;
1985 * Erase the background. (Delay it but setup rectangle.)
1986 * This is necessary when drawing the selected item since it is larger
1987 * than the others, it might overlap with stuff already drawn by the
1988 * other tabs
1990 fillRect = r;
1992 /* Draw themed tabs - but only if they are at the top.
1993 * Windows draws even side or bottom tabs themed, with wacky results.
1994 * However, since in Wine apps may get themed that did not opt in via
1995 * a manifest avoid theming when we know the result will be wrong */
1996 if ((theme = GetWindowTheme (infoPtr->hwnd))
1997 && ((lStyle & (TCS_VERTICAL | TCS_BOTTOM)) == 0))
1999 static const int partIds[8] = {
2000 /* Normal item */
2001 TABP_TABITEM,
2002 TABP_TABITEMLEFTEDGE,
2003 TABP_TABITEMRIGHTEDGE,
2004 TABP_TABITEMBOTHEDGE,
2005 /* Selected tab */
2006 TABP_TOPTABITEM,
2007 TABP_TOPTABITEMLEFTEDGE,
2008 TABP_TOPTABITEMRIGHTEDGE,
2009 TABP_TOPTABITEMBOTHEDGE,
2011 int partIndex = 0;
2012 int stateId = TIS_NORMAL;
2014 /* selected and unselected tabs have different parts */
2015 if (iItem == infoPtr->iSelected)
2016 partIndex += 4;
2017 /* The part also differs on the position of a tab on a line.
2018 * "Visually" determining the position works well enough. */
2019 if(selectedRect.left == 0)
2020 partIndex += 1;
2021 if(selectedRect.right == clRight)
2022 partIndex += 2;
2024 if (iItem == infoPtr->iSelected)
2025 stateId = TIS_SELECTED;
2026 else if (iItem == infoPtr->iHotTracked)
2027 stateId = TIS_HOT;
2028 else if (iItem == infoPtr->uFocus)
2029 stateId = TIS_FOCUSED;
2031 /* Adjust rectangle for bottommost row */
2032 if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2033 r.bottom += 3;
2035 DrawThemeBackground (theme, hdc, partIds[partIndex], stateId, &r, NULL);
2036 GetThemeBackgroundContentRect (theme, hdc, partIds[partIndex], stateId, &r, &r);
2038 else if(lStyle & TCS_VERTICAL)
2040 /* These are for adjusting the drawing of a Selected tab */
2041 /* The initial values are for the normal case of non-Selected */
2042 int ZZ = 1; /* Do not stretch if selected */
2043 if (iItem == infoPtr->iSelected) {
2044 ZZ = 0;
2046 /* if leftmost draw the line longer */
2047 if(selectedRect.top == 0)
2048 fillRect.top += CONTROL_BORDER_SIZEY;
2049 /* if rightmost draw the line longer */
2050 if(selectedRect.bottom == clBottom)
2051 fillRect.bottom -= CONTROL_BORDER_SIZEY;
2054 if (lStyle & TCS_BOTTOM)
2056 /* Adjust both rectangles to match native */
2057 r.left += (1-ZZ);
2059 TRACE("<right> item=%d, fill=(%s), edge=(%s)\n",
2060 iItem, wine_dbgstr_rect(&fillRect), wine_dbgstr_rect(&r));
2062 /* Clear interior */
2063 SetBkColor(hdc, bkgnd);
2064 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2066 /* Draw rectangular edge around tab */
2067 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_RIGHT|BF_TOP|BF_BOTTOM);
2069 /* Now erase the top corner and draw diagonal edge */
2070 SetBkColor(hdc, corner);
2071 r1.left = r.right - ROUND_CORNER_SIZE - 1;
2072 r1.top = r.top;
2073 r1.right = r.right;
2074 r1.bottom = r1.top + ROUND_CORNER_SIZE;
2075 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2076 r1.right--;
2077 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPLEFT);
2079 /* Now erase the bottom corner and draw diagonal edge */
2080 r1.left = r.right - ROUND_CORNER_SIZE - 1;
2081 r1.bottom = r.bottom;
2082 r1.right = r.right;
2083 r1.top = r1.bottom - ROUND_CORNER_SIZE;
2084 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2085 r1.right--;
2086 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMLEFT);
2088 if ((iItem == infoPtr->iSelected) && (selectedRect.top == 0)) {
2089 r1 = r;
2090 r1.right = r1.left;
2091 r1.left--;
2092 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_TOP);
2096 else
2098 TRACE("<left> item=%d, fill=(%s), edge=(%s)\n",
2099 iItem, wine_dbgstr_rect(&fillRect), wine_dbgstr_rect(&r));
2101 /* Clear interior */
2102 SetBkColor(hdc, bkgnd);
2103 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2105 /* Draw rectangular edge around tab */
2106 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_TOP|BF_BOTTOM);
2108 /* Now erase the top corner and draw diagonal edge */
2109 SetBkColor(hdc, corner);
2110 r1.left = r.left;
2111 r1.top = r.top;
2112 r1.right = r1.left + ROUND_CORNER_SIZE + 1;
2113 r1.bottom = r1.top + ROUND_CORNER_SIZE;
2114 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2115 r1.left++;
2116 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPRIGHT);
2118 /* Now erase the bottom corner and draw diagonal edge */
2119 r1.left = r.left;
2120 r1.bottom = r.bottom;
2121 r1.right = r1.left + ROUND_CORNER_SIZE + 1;
2122 r1.top = r1.bottom - ROUND_CORNER_SIZE;
2123 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2124 r1.left++;
2125 DrawEdge(hdc, &r1, EDGE_SUNKEN, BF_DIAGONAL_ENDTOPLEFT);
2128 else /* ! TCS_VERTICAL */
2130 /* These are for adjusting the drawing of a Selected tab */
2131 /* The initial values are for the normal case of non-Selected */
2132 if (iItem == infoPtr->iSelected) {
2133 /* if leftmost draw the line longer */
2134 if(selectedRect.left == 0)
2135 fillRect.left += CONTROL_BORDER_SIZEX;
2136 /* if rightmost draw the line longer */
2137 if(selectedRect.right == clRight)
2138 fillRect.right -= CONTROL_BORDER_SIZEX;
2141 if (lStyle & TCS_BOTTOM)
2143 /* Adjust both rectangles for topmost row */
2144 if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2146 fillRect.top -= 2;
2147 r.top -= 1;
2150 TRACE("<bottom> item=%d, fill=(%s), edge=(%s)\n",
2151 iItem, wine_dbgstr_rect(&fillRect), wine_dbgstr_rect(&r));
2153 /* Clear interior */
2154 SetBkColor(hdc, bkgnd);
2155 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2157 /* Draw rectangular edge around tab */
2158 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_BOTTOM|BF_RIGHT);
2160 /* Now erase the righthand corner and draw diagonal edge */
2161 SetBkColor(hdc, corner);
2162 r1.left = r.right - ROUND_CORNER_SIZE;
2163 r1.bottom = r.bottom;
2164 r1.right = r.right;
2165 r1.top = r1.bottom - ROUND_CORNER_SIZE - 1;
2166 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2167 r1.bottom--;
2168 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMLEFT);
2170 /* Now erase the lefthand corner and draw diagonal edge */
2171 r1.left = r.left;
2172 r1.bottom = r.bottom;
2173 r1.right = r1.left + ROUND_CORNER_SIZE;
2174 r1.top = r1.bottom - ROUND_CORNER_SIZE - 1;
2175 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2176 r1.bottom--;
2177 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPLEFT);
2179 if (iItem == infoPtr->iSelected)
2181 r.top += 2;
2182 r.left += 1;
2183 if (selectedRect.left == 0)
2185 r1 = r;
2186 r1.bottom = r1.top;
2187 r1.top--;
2188 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_LEFT);
2193 else
2195 /* Adjust both rectangles for bottommost row */
2196 if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2198 fillRect.bottom += 3;
2199 r.bottom += 2;
2202 TRACE("<top> item=%d, fill=(%s), edge=(%s)\n",
2203 iItem, wine_dbgstr_rect(&fillRect), wine_dbgstr_rect(&r));
2205 /* Clear interior */
2206 SetBkColor(hdc, bkgnd);
2207 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2209 /* Draw rectangular edge around tab */
2210 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_TOP|BF_RIGHT);
2212 /* Now erase the righthand corner and draw diagonal edge */
2213 SetBkColor(hdc, corner);
2214 r1.left = r.right - ROUND_CORNER_SIZE;
2215 r1.top = r.top;
2216 r1.right = r.right;
2217 r1.bottom = r1.top + ROUND_CORNER_SIZE + 1;
2218 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2219 r1.top++;
2220 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMRIGHT);
2222 /* Now erase the lefthand corner and draw diagonal edge */
2223 r1.left = r.left;
2224 r1.top = r.top;
2225 r1.right = r1.left + ROUND_CORNER_SIZE;
2226 r1.bottom = r1.top + ROUND_CORNER_SIZE + 1;
2227 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2228 r1.top++;
2229 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPRIGHT);
2234 TAB_DumpItemInternal(infoPtr, iItem);
2236 /* This modifies r to be the text rectangle. */
2237 TAB_DrawItemInterior(infoPtr, hdc, iItem, &r);
2241 /******************************************************************************
2242 * TAB_DrawBorder
2244 * This method is used to draw the raised border around the tab control
2245 * "content" area.
2247 static void TAB_DrawBorder(const TAB_INFO *infoPtr, HDC hdc)
2249 RECT rect;
2250 DWORD lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
2251 HTHEME theme = GetWindowTheme (infoPtr->hwnd);
2253 GetClientRect (infoPtr->hwnd, &rect);
2256 * Adjust for the style
2259 if (infoPtr->uNumItem)
2261 if ((lStyle & TCS_BOTTOM) && !(lStyle & TCS_VERTICAL))
2262 rect.bottom -= infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2263 else if((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
2264 rect.right -= infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2265 else if(lStyle & TCS_VERTICAL)
2266 rect.left += infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2267 else /* not TCS_VERTICAL and not TCS_BOTTOM */
2268 rect.top += infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2271 TRACE("border=(%s)\n", wine_dbgstr_rect(&rect));
2273 if (theme)
2274 DrawThemeBackground (theme, hdc, TABP_PANE, 0, &rect, NULL);
2275 else
2276 DrawEdge(hdc, &rect, EDGE_RAISED, BF_SOFT|BF_RECT);
2279 /******************************************************************************
2280 * TAB_Refresh
2282 * This method repaints the tab control..
2284 static void TAB_Refresh (TAB_INFO *infoPtr, HDC hdc)
2286 HFONT hOldFont;
2287 INT i;
2289 if (!infoPtr->DoRedraw)
2290 return;
2292 hOldFont = SelectObject (hdc, infoPtr->hFont);
2294 if (GetWindowLongW(infoPtr->hwnd, GWL_STYLE) & TCS_BUTTONS)
2296 for (i = 0; i < infoPtr->uNumItem; i++)
2297 TAB_DrawItem (infoPtr, hdc, i);
2299 else
2301 /* Draw all the non selected item first */
2302 for (i = 0; i < infoPtr->uNumItem; i++)
2304 if (i != infoPtr->iSelected)
2305 TAB_DrawItem (infoPtr, hdc, i);
2308 /* Now, draw the border, draw it before the selected item
2309 * since the selected item overwrites part of the border. */
2310 TAB_DrawBorder (infoPtr, hdc);
2312 /* Then, draw the selected item */
2313 TAB_DrawItem (infoPtr, hdc, infoPtr->iSelected);
2316 SelectObject (hdc, hOldFont);
2319 static inline DWORD TAB_GetRowCount (const TAB_INFO *infoPtr)
2321 return infoPtr->uNumRows;
2324 static inline LRESULT TAB_SetRedraw (TAB_INFO *infoPtr, BOOL doRedraw)
2326 infoPtr->DoRedraw = doRedraw;
2327 return 0;
2330 /******************************************************************************
2331 * TAB_EnsureSelectionVisible
2333 * This method will make sure that the current selection is completely
2334 * visible by scrolling until it is.
2336 static void TAB_EnsureSelectionVisible(
2337 TAB_INFO* infoPtr)
2339 INT iSelected = infoPtr->iSelected;
2340 LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
2341 INT iOrigLeftmostVisible = infoPtr->leftmostVisible;
2343 /* set the items row to the bottommost row or topmost row depending on
2344 * style */
2345 if ((infoPtr->uNumRows > 1) && !(lStyle & TCS_BUTTONS))
2347 TAB_ITEM *selected = TAB_GetItem(infoPtr, iSelected);
2348 INT newselected;
2349 INT iTargetRow;
2351 if(lStyle & TCS_VERTICAL)
2352 newselected = selected->rect.left;
2353 else
2354 newselected = selected->rect.top;
2356 /* the target row is always (number of rows - 1)
2357 as row 0 is furthest from the clientRect */
2358 iTargetRow = infoPtr->uNumRows - 1;
2360 if (newselected != iTargetRow)
2362 UINT i;
2363 if(lStyle & TCS_VERTICAL)
2365 for (i=0; i < infoPtr->uNumItem; i++)
2367 /* move everything in the row of the selected item to the iTargetRow */
2368 TAB_ITEM *item = TAB_GetItem(infoPtr, i);
2370 if (item->rect.left == newselected )
2371 item->rect.left = iTargetRow;
2372 else
2374 if (item->rect.left > newselected)
2375 item->rect.left-=1;
2379 else
2381 for (i=0; i < infoPtr->uNumItem; i++)
2383 TAB_ITEM *item = TAB_GetItem(infoPtr, i);
2385 if (item->rect.top == newselected )
2386 item->rect.top = iTargetRow;
2387 else
2389 if (item->rect.top > newselected)
2390 item->rect.top-=1;
2394 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
2399 * Do the trivial cases first.
2401 if ( (!infoPtr->needsScrolling) ||
2402 (infoPtr->hwndUpDown==0) || (lStyle & TCS_VERTICAL))
2403 return;
2405 if (infoPtr->leftmostVisible >= iSelected)
2407 infoPtr->leftmostVisible = iSelected;
2409 else
2411 TAB_ITEM *selected = TAB_GetItem(infoPtr, iSelected);
2412 RECT r;
2413 INT width;
2414 UINT i;
2416 /* Calculate the part of the client area that is visible */
2417 GetClientRect(infoPtr->hwnd, &r);
2418 width = r.right;
2420 GetClientRect(infoPtr->hwndUpDown, &r);
2421 width -= r.right;
2423 if ((selected->rect.right -
2424 selected->rect.left) >= width )
2426 /* Special case: width of selected item is greater than visible
2427 * part of control.
2429 infoPtr->leftmostVisible = iSelected;
2431 else
2433 for (i = infoPtr->leftmostVisible; i < infoPtr->uNumItem; i++)
2435 if ((selected->rect.right - TAB_GetItem(infoPtr, i)->rect.left) < width)
2436 break;
2438 infoPtr->leftmostVisible = i;
2442 if (infoPtr->leftmostVisible != iOrigLeftmostVisible)
2443 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
2445 SendMessageW(infoPtr->hwndUpDown, UDM_SETPOS, 0,
2446 MAKELONG(infoPtr->leftmostVisible, 0));
2449 /******************************************************************************
2450 * TAB_InvalidateTabArea
2452 * This method will invalidate the portion of the control that contains the
2453 * tabs. It is called when the state of the control changes and needs
2454 * to be redisplayed
2456 static void TAB_InvalidateTabArea(const TAB_INFO *infoPtr)
2458 RECT clientRect, rInvalidate, rAdjClient;
2459 DWORD lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
2460 INT lastRow = infoPtr->uNumRows - 1;
2461 RECT rect;
2463 if (lastRow < 0) return;
2465 GetClientRect(infoPtr->hwnd, &clientRect);
2466 rInvalidate = clientRect;
2467 rAdjClient = clientRect;
2469 TAB_AdjustRect(infoPtr, 0, &rAdjClient);
2471 TAB_InternalGetItemRect(infoPtr, infoPtr->uNumItem-1 , &rect, NULL);
2472 if ((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
2474 rInvalidate.left = rAdjClient.right;
2475 if (infoPtr->uNumRows == 1)
2476 rInvalidate.bottom = clientRect.top + rect.bottom + 2 * SELECTED_TAB_OFFSET;
2478 else if(lStyle & TCS_VERTICAL)
2480 rInvalidate.right = rAdjClient.left;
2481 if (infoPtr->uNumRows == 1)
2482 rInvalidate.bottom = clientRect.top + rect.bottom + 2 * SELECTED_TAB_OFFSET;
2484 else if (lStyle & TCS_BOTTOM)
2486 rInvalidate.top = rAdjClient.bottom;
2487 if (infoPtr->uNumRows == 1)
2488 rInvalidate.right = clientRect.left + rect.right + 2 * SELECTED_TAB_OFFSET;
2490 else
2492 rInvalidate.bottom = rAdjClient.top;
2493 if (infoPtr->uNumRows == 1)
2494 rInvalidate.right = clientRect.left + rect.right + 2 * SELECTED_TAB_OFFSET;
2497 /* Punch out the updown control */
2498 if (infoPtr->needsScrolling && (rInvalidate.right > 0)) {
2499 RECT r;
2500 GetClientRect(infoPtr->hwndUpDown, &r);
2501 if (rInvalidate.right > clientRect.right - r.left)
2502 rInvalidate.right = rInvalidate.right - (r.right - r.left);
2503 else
2504 rInvalidate.right = clientRect.right - r.left;
2507 TRACE("invalidate (%s)\n", wine_dbgstr_rect(&rInvalidate));
2509 InvalidateRect(infoPtr->hwnd, &rInvalidate, TRUE);
2512 static inline LRESULT TAB_Paint (TAB_INFO *infoPtr, HDC hdcPaint)
2514 HDC hdc;
2515 PAINTSTRUCT ps;
2517 if (hdcPaint)
2518 hdc = hdcPaint;
2519 else
2521 hdc = BeginPaint (infoPtr->hwnd, &ps);
2522 TRACE("erase %d, rect=(%s)\n", ps.fErase, wine_dbgstr_rect(&ps.rcPaint));
2525 TAB_Refresh (infoPtr, hdc);
2527 if (!hdcPaint)
2528 EndPaint (infoPtr->hwnd, &ps);
2530 return 0;
2533 static LRESULT
2534 TAB_InsertItemT (TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam, BOOL bUnicode)
2536 TAB_ITEM *item;
2537 TCITEMW *pti;
2538 INT iItem;
2539 RECT rect;
2541 GetClientRect (infoPtr->hwnd, &rect);
2542 TRACE("Rect: %p %s\n", infoPtr->hwnd, wine_dbgstr_rect(&rect));
2544 pti = (TCITEMW *)lParam;
2545 iItem = (INT)wParam;
2547 if (iItem < 0) return -1;
2548 if (iItem > infoPtr->uNumItem)
2549 iItem = infoPtr->uNumItem;
2551 TAB_DumpItemExternalT(pti, iItem, bUnicode);
2554 if (infoPtr->uNumItem == 0) {
2555 infoPtr->items = Alloc (TAB_ITEM_SIZE(infoPtr));
2556 infoPtr->uNumItem++;
2557 infoPtr->iSelected = 0;
2559 else {
2560 LPBYTE oldItems = (LPBYTE)infoPtr->items;
2562 infoPtr->uNumItem++;
2563 infoPtr->items = Alloc (TAB_ITEM_SIZE(infoPtr) * infoPtr->uNumItem);
2565 /* pre insert copy */
2566 if (iItem > 0) {
2567 memcpy (infoPtr->items, oldItems,
2568 iItem * TAB_ITEM_SIZE(infoPtr));
2571 /* post insert copy */
2572 if (iItem < infoPtr->uNumItem - 1) {
2573 memcpy (TAB_GetItem(infoPtr, iItem + 1),
2574 oldItems + iItem * TAB_ITEM_SIZE(infoPtr),
2575 (infoPtr->uNumItem - iItem - 1) * TAB_ITEM_SIZE(infoPtr));
2579 if (iItem <= infoPtr->iSelected)
2580 infoPtr->iSelected++;
2582 Free (oldItems);
2585 item = TAB_GetItem(infoPtr, iItem);
2587 item->pszText = NULL;
2589 if (pti->mask & TCIF_TEXT)
2591 if (bUnicode)
2592 Str_SetPtrW (&item->pszText, pti->pszText);
2593 else
2594 Str_SetPtrAtoW (&item->pszText, (LPSTR)pti->pszText);
2597 if (pti->mask & TCIF_IMAGE)
2598 item->iImage = pti->iImage;
2599 else
2600 item->iImage = -1;
2602 if (pti->mask & TCIF_PARAM)
2603 memcpy(item->extra, &pti->lParam, infoPtr->cbInfo);
2604 else
2605 memset(item->extra, 0, infoPtr->cbInfo);
2607 TAB_SetItemBounds(infoPtr);
2608 if (infoPtr->uNumItem > 1)
2609 TAB_InvalidateTabArea(infoPtr);
2610 else
2611 InvalidateRect(infoPtr->hwnd, NULL, TRUE);
2613 TRACE("[%p]: added item %d %s\n",
2614 infoPtr->hwnd, iItem, debugstr_w(item->pszText));
2616 /* If we haven't set the current focus yet, set it now. */
2617 if (infoPtr->uFocus == -1)
2618 TAB_SetCurFocus(infoPtr, iItem);
2620 return iItem;
2623 static LRESULT
2624 TAB_SetItemSize (TAB_INFO *infoPtr, LPARAM lParam)
2626 LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
2627 LONG lResult = 0;
2628 BOOL bNeedPaint = FALSE;
2630 lResult = MAKELONG(infoPtr->tabWidth, infoPtr->tabHeight);
2632 /* UNDOCUMENTED: If requested Width or Height is 0 this means that program wants to use auto size. */
2633 if (lStyle & TCS_FIXEDWIDTH && (infoPtr->tabWidth != (INT)LOWORD(lParam)))
2635 infoPtr->tabWidth = (INT)LOWORD(lParam);
2636 bNeedPaint = TRUE;
2639 if (infoPtr->tabHeight != (INT)HIWORD(lParam))
2641 if ((infoPtr->fHeightSet = ((INT)HIWORD(lParam) != 0)))
2642 infoPtr->tabHeight = (INT)HIWORD(lParam);
2644 bNeedPaint = TRUE;
2646 TRACE("was h=%d,w=%d, now h=%d,w=%d\n",
2647 HIWORD(lResult), LOWORD(lResult),
2648 infoPtr->tabHeight, infoPtr->tabWidth);
2650 if (bNeedPaint)
2652 TAB_SetItemBounds(infoPtr);
2653 RedrawWindow(infoPtr->hwnd, NULL, NULL, RDW_ERASE | RDW_INVALIDATE | RDW_UPDATENOW);
2656 return lResult;
2659 static inline LRESULT TAB_SetMinTabWidth (TAB_INFO *infoPtr, INT cx)
2661 INT oldcx = 0;
2663 TRACE("(%p,%d)\n", infoPtr, cx);
2665 oldcx = infoPtr->tabMinWidth;
2666 infoPtr->tabMinWidth = cx;
2667 TAB_SetItemBounds(infoPtr);
2668 return oldcx;
2671 static inline LRESULT
2672 TAB_HighlightItem (TAB_INFO *infoPtr, INT iItem, BOOL fHighlight)
2674 LPDWORD lpState;
2676 TRACE("(%p,%d,%s)\n", infoPtr, iItem, fHighlight ? "true" : "false");
2678 if (!infoPtr || iItem < 0 || iItem >= infoPtr->uNumItem)
2679 return FALSE;
2681 lpState = &TAB_GetItem(infoPtr, iItem)->dwState;
2683 if (fHighlight)
2684 *lpState |= TCIS_HIGHLIGHTED;
2685 else
2686 *lpState &= ~TCIS_HIGHLIGHTED;
2688 return TRUE;
2691 static LRESULT
2692 TAB_SetItemT (TAB_INFO *infoPtr, INT iItem, LPTCITEMW tabItem, BOOL bUnicode)
2694 TAB_ITEM *wineItem;
2696 TRACE("(%p,%d,%p,%s)\n", infoPtr, iItem, tabItem, bUnicode ? "true" : "false");
2698 if (iItem < 0 || iItem >= infoPtr->uNumItem)
2699 return FALSE;
2701 TAB_DumpItemExternalT(tabItem, iItem, bUnicode);
2703 wineItem = TAB_GetItem(infoPtr, iItem);
2705 if (tabItem->mask & TCIF_IMAGE)
2706 wineItem->iImage = tabItem->iImage;
2708 if (tabItem->mask & TCIF_PARAM)
2709 memcpy(wineItem->extra, &tabItem->lParam, infoPtr->cbInfo);
2711 if (tabItem->mask & TCIF_RTLREADING)
2712 FIXME("TCIF_RTLREADING\n");
2714 if (tabItem->mask & TCIF_STATE)
2715 wineItem->dwState = tabItem->dwState;
2717 if (tabItem->mask & TCIF_TEXT)
2719 Free(wineItem->pszText);
2720 wineItem->pszText = NULL;
2721 if (bUnicode)
2722 Str_SetPtrW(&wineItem->pszText, tabItem->pszText);
2723 else
2724 Str_SetPtrAtoW(&wineItem->pszText, (LPSTR)tabItem->pszText);
2727 /* Update and repaint tabs */
2728 TAB_SetItemBounds(infoPtr);
2729 TAB_InvalidateTabArea(infoPtr);
2731 return TRUE;
2734 static inline LRESULT TAB_GetItemCount (const TAB_INFO *infoPtr)
2736 return infoPtr->uNumItem;
2740 static LRESULT
2741 TAB_GetItemT (TAB_INFO *infoPtr, INT iItem, LPTCITEMW tabItem, BOOL bUnicode)
2743 TAB_ITEM *wineItem;
2745 TRACE("(%p,%d,%p,%s)\n", infoPtr, iItem, tabItem, bUnicode ? "true" : "false");
2747 if (iItem < 0 || iItem >= infoPtr->uNumItem)
2748 return FALSE;
2750 wineItem = TAB_GetItem(infoPtr, iItem);
2752 if (tabItem->mask & TCIF_IMAGE)
2753 tabItem->iImage = wineItem->iImage;
2755 if (tabItem->mask & TCIF_PARAM)
2756 memcpy(&tabItem->lParam, wineItem->extra, infoPtr->cbInfo);
2758 if (tabItem->mask & TCIF_RTLREADING)
2759 FIXME("TCIF_RTLREADING\n");
2761 if (tabItem->mask & TCIF_STATE)
2762 tabItem->dwState = wineItem->dwState;
2764 if (tabItem->mask & TCIF_TEXT)
2766 if (bUnicode)
2767 Str_GetPtrW (wineItem->pszText, tabItem->pszText, tabItem->cchTextMax);
2768 else
2769 Str_GetPtrWtoA (wineItem->pszText, (LPSTR)tabItem->pszText, tabItem->cchTextMax);
2772 TAB_DumpItemExternalT(tabItem, iItem, bUnicode);
2774 return TRUE;
2778 static LRESULT TAB_DeleteItem (TAB_INFO *infoPtr, INT iItem)
2780 BOOL bResult = FALSE;
2782 TRACE("(%p, %d)\n", infoPtr, iItem);
2784 if ((iItem >= 0) && (iItem < infoPtr->uNumItem))
2786 TAB_ITEM *item = TAB_GetItem(infoPtr, iItem);
2787 LPBYTE oldItems = (LPBYTE)infoPtr->items;
2789 TAB_InvalidateTabArea(infoPtr);
2790 Free(item->pszText);
2791 infoPtr->uNumItem--;
2793 if (!infoPtr->uNumItem)
2795 infoPtr->items = NULL;
2796 if (infoPtr->iHotTracked >= 0)
2798 KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
2799 infoPtr->iHotTracked = -1;
2802 else
2804 infoPtr->items = Alloc(TAB_ITEM_SIZE(infoPtr) * infoPtr->uNumItem);
2806 if (iItem > 0)
2807 memcpy(infoPtr->items, oldItems, iItem * TAB_ITEM_SIZE(infoPtr));
2809 if (iItem < infoPtr->uNumItem)
2810 memcpy(TAB_GetItem(infoPtr, iItem),
2811 oldItems + (iItem + 1) * TAB_ITEM_SIZE(infoPtr),
2812 (infoPtr->uNumItem - iItem) * TAB_ITEM_SIZE(infoPtr));
2814 if (iItem <= infoPtr->iHotTracked)
2816 /* When tabs move left/up, the hot track item may change */
2817 FIXME("Recalc hot track\n");
2820 Free(oldItems);
2822 /* Readjust the selected index */
2823 if ((iItem == infoPtr->iSelected) && (iItem > 0))
2824 infoPtr->iSelected--;
2826 if (iItem < infoPtr->iSelected)
2827 infoPtr->iSelected--;
2829 if (infoPtr->uNumItem == 0)
2830 infoPtr->iSelected = -1;
2832 /* Reposition and repaint tabs */
2833 TAB_SetItemBounds(infoPtr);
2835 bResult = TRUE;
2838 return bResult;
2841 static inline LRESULT TAB_DeleteAllItems (TAB_INFO *infoPtr)
2843 TRACE("(%p)\n", infoPtr);
2844 while (infoPtr->uNumItem)
2845 TAB_DeleteItem (infoPtr, 0);
2846 return TRUE;
2850 static inline LRESULT TAB_GetFont (const TAB_INFO *infoPtr)
2852 TRACE("(%p) returning %p\n", infoPtr, infoPtr->hFont);
2853 return (LRESULT)infoPtr->hFont;
2856 static inline LRESULT TAB_SetFont (TAB_INFO *infoPtr, HFONT hNewFont)
2858 TRACE("(%p,%p)\n", infoPtr, hNewFont);
2860 infoPtr->hFont = hNewFont;
2862 TAB_SetItemBounds(infoPtr);
2864 TAB_InvalidateTabArea(infoPtr);
2866 return 0;
2870 static inline LRESULT TAB_GetImageList (const TAB_INFO *infoPtr)
2872 TRACE("\n");
2873 return (LRESULT)infoPtr->himl;
2876 static inline LRESULT TAB_SetImageList (TAB_INFO *infoPtr, HIMAGELIST himlNew)
2878 HIMAGELIST himlPrev = infoPtr->himl;
2879 TRACE("\n");
2880 infoPtr->himl = himlNew;
2881 TAB_SetItemBounds(infoPtr);
2882 InvalidateRect(infoPtr->hwnd, NULL, TRUE);
2883 return (LRESULT)himlPrev;
2886 static inline LRESULT TAB_GetUnicodeFormat (const TAB_INFO *infoPtr)
2888 return infoPtr->bUnicode;
2891 static inline LRESULT TAB_SetUnicodeFormat (TAB_INFO *infoPtr, BOOL bUnicode)
2893 BOOL bTemp = infoPtr->bUnicode;
2895 infoPtr->bUnicode = bUnicode;
2897 return bTemp;
2900 static inline LRESULT TAB_Size (TAB_INFO *infoPtr)
2902 /* I'm not really sure what the following code was meant to do.
2903 This is what it is doing:
2904 When WM_SIZE is sent with SIZE_RESTORED, the control
2905 gets positioned in the top left corner.
2907 RECT parent_rect;
2908 HWND parent;
2909 UINT uPosFlags,cx,cy;
2911 uPosFlags=0;
2912 if (!wParam) {
2913 parent = GetParent (hwnd);
2914 GetClientRect(parent, &parent_rect);
2915 cx=LOWORD (lParam);
2916 cy=HIWORD (lParam);
2917 if (GetWindowLongW(hwnd, GWL_STYLE) & CCS_NORESIZE)
2918 uPosFlags |= (SWP_NOSIZE | SWP_NOMOVE);
2920 SetWindowPos (hwnd, 0, parent_rect.left, parent_rect.top,
2921 cx, cy, uPosFlags | SWP_NOZORDER);
2922 } else {
2923 FIXME("WM_SIZE flag %x %lx not handled\n", wParam, lParam);
2924 } */
2926 /* Recompute the size/position of the tabs. */
2927 TAB_SetItemBounds (infoPtr);
2929 /* Force a repaint of the control. */
2930 InvalidateRect(infoPtr->hwnd, NULL, TRUE);
2932 return 0;
2936 static LRESULT TAB_Create (HWND hwnd, WPARAM wParam, LPARAM lParam)
2938 TAB_INFO *infoPtr;
2939 TEXTMETRICW fontMetrics;
2940 HDC hdc;
2941 HFONT hOldFont;
2942 DWORD dwStyle;
2944 infoPtr = Alloc (sizeof(TAB_INFO));
2946 SetWindowLongPtrW(hwnd, 0, (DWORD_PTR)infoPtr);
2948 infoPtr->hwnd = hwnd;
2949 infoPtr->hwndNotify = ((LPCREATESTRUCTW)lParam)->hwndParent;
2950 infoPtr->uNumItem = 0;
2951 infoPtr->uNumRows = 0;
2952 infoPtr->uHItemPadding = 6;
2953 infoPtr->uVItemPadding = 3;
2954 infoPtr->uHItemPadding_s = 6;
2955 infoPtr->uVItemPadding_s = 3;
2956 infoPtr->hFont = 0;
2957 infoPtr->items = 0;
2958 infoPtr->hcurArrow = LoadCursorW (0, (LPWSTR)IDC_ARROW);
2959 infoPtr->iSelected = -1;
2960 infoPtr->iHotTracked = -1;
2961 infoPtr->uFocus = -1;
2962 infoPtr->hwndToolTip = 0;
2963 infoPtr->DoRedraw = TRUE;
2964 infoPtr->needsScrolling = FALSE;
2965 infoPtr->hwndUpDown = 0;
2966 infoPtr->leftmostVisible = 0;
2967 infoPtr->fHeightSet = FALSE;
2968 infoPtr->bUnicode = IsWindowUnicode (hwnd);
2969 infoPtr->cbInfo = sizeof(LPARAM);
2971 TRACE("Created tab control, hwnd [%p]\n", hwnd);
2973 /* The tab control always has the WS_CLIPSIBLINGS style. Even
2974 if you don't specify it in CreateWindow. This is necessary in
2975 order for paint to work correctly. This follows windows behaviour. */
2976 dwStyle = GetWindowLongW(hwnd, GWL_STYLE);
2977 SetWindowLongW(hwnd, GWL_STYLE, dwStyle|WS_CLIPSIBLINGS);
2979 if (dwStyle & TCS_TOOLTIPS) {
2980 /* Create tooltip control */
2981 infoPtr->hwndToolTip =
2982 CreateWindowExW (0, TOOLTIPS_CLASSW, NULL, WS_POPUP,
2983 CW_USEDEFAULT, CW_USEDEFAULT,
2984 CW_USEDEFAULT, CW_USEDEFAULT,
2985 hwnd, 0, 0, 0);
2987 /* Send NM_TOOLTIPSCREATED notification */
2988 if (infoPtr->hwndToolTip) {
2989 NMTOOLTIPSCREATED nmttc;
2991 nmttc.hdr.hwndFrom = hwnd;
2992 nmttc.hdr.idFrom = GetWindowLongPtrW(hwnd, GWLP_ID);
2993 nmttc.hdr.code = NM_TOOLTIPSCREATED;
2994 nmttc.hwndToolTips = infoPtr->hwndToolTip;
2996 SendMessageW (infoPtr->hwndNotify, WM_NOTIFY,
2997 (WPARAM)GetWindowLongPtrW(hwnd, GWLP_ID), (LPARAM)&nmttc);
3001 OpenThemeData (infoPtr->hwnd, themeClass);
3004 * We need to get text information so we need a DC and we need to select
3005 * a font.
3007 hdc = GetDC(hwnd);
3008 hOldFont = SelectObject (hdc, GetStockObject (SYSTEM_FONT));
3010 /* Use the system font to determine the initial height of a tab. */
3011 GetTextMetricsW(hdc, &fontMetrics);
3014 * Make sure there is enough space for the letters + growing the
3015 * selected item + extra space for the selected item.
3017 infoPtr->tabHeight = fontMetrics.tmHeight + SELECTED_TAB_OFFSET +
3018 ((dwStyle & TCS_BUTTONS) ? 2 : 1) *
3019 infoPtr->uVItemPadding;
3021 /* Initialize the width of a tab. */
3022 if (dwStyle & TCS_FIXEDWIDTH)
3023 infoPtr->tabWidth = GetDeviceCaps(hdc, LOGPIXELSX);
3025 infoPtr->tabMinWidth = -1;
3027 TRACE("tabH=%d, tabW=%d\n", infoPtr->tabHeight, infoPtr->tabWidth);
3029 SelectObject (hdc, hOldFont);
3030 ReleaseDC(hwnd, hdc);
3032 return 0;
3035 static LRESULT
3036 TAB_Destroy (TAB_INFO *infoPtr)
3038 UINT iItem;
3040 if (!infoPtr)
3041 return 0;
3043 SetWindowLongPtrW(infoPtr->hwnd, 0, 0);
3045 if (infoPtr->items) {
3046 for (iItem = 0; iItem < infoPtr->uNumItem; iItem++) {
3047 Free (TAB_GetItem(infoPtr, iItem)->pszText);
3049 Free (infoPtr->items);
3052 if (infoPtr->hwndToolTip)
3053 DestroyWindow (infoPtr->hwndToolTip);
3055 if (infoPtr->hwndUpDown)
3056 DestroyWindow(infoPtr->hwndUpDown);
3058 if (infoPtr->iHotTracked >= 0)
3059 KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
3061 CloseThemeData (GetWindowTheme (infoPtr->hwnd));
3063 Free (infoPtr);
3064 return 0;
3067 /* update theme after a WM_THEMECHANGED message */
3068 static LRESULT theme_changed(const TAB_INFO *infoPtr)
3070 HTHEME theme = GetWindowTheme (infoPtr->hwnd);
3071 CloseThemeData (theme);
3072 OpenThemeData (infoPtr->hwnd, themeClass);
3073 return 0;
3076 static LRESULT TAB_NCCalcSize(HWND hwnd, WPARAM wParam, LPARAM lParam)
3078 if (!wParam)
3079 return 0;
3080 return WVR_ALIGNTOP;
3083 static inline LRESULT
3084 TAB_SetItemExtra (TAB_INFO *infoPtr, INT cbInfo)
3086 if (!infoPtr || cbInfo <= 0)
3087 return FALSE;
3089 if (infoPtr->uNumItem)
3091 /* FIXME: MSDN says this is not allowed, but this hasn't been verified */
3092 return FALSE;
3095 infoPtr->cbInfo = cbInfo;
3096 return TRUE;
3099 static LRESULT WINAPI
3100 TAB_WindowProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
3102 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
3104 TRACE("hwnd=%p msg=%x wParam=%lx lParam=%lx\n", hwnd, uMsg, wParam, lParam);
3105 if (!infoPtr && (uMsg != WM_CREATE))
3106 return DefWindowProcW (hwnd, uMsg, wParam, lParam);
3108 switch (uMsg)
3110 case TCM_GETIMAGELIST:
3111 return TAB_GetImageList (infoPtr);
3113 case TCM_SETIMAGELIST:
3114 return TAB_SetImageList (infoPtr, (HIMAGELIST)lParam);
3116 case TCM_GETITEMCOUNT:
3117 return TAB_GetItemCount (infoPtr);
3119 case TCM_GETITEMA:
3120 case TCM_GETITEMW:
3121 return TAB_GetItemT (infoPtr, (INT)wParam, (LPTCITEMW)lParam, uMsg == TCM_GETITEMW);
3123 case TCM_SETITEMA:
3124 case TCM_SETITEMW:
3125 return TAB_SetItemT (infoPtr, (INT)wParam, (LPTCITEMW)lParam, uMsg == TCM_SETITEMW);
3127 case TCM_DELETEITEM:
3128 return TAB_DeleteItem (infoPtr, (INT)wParam);
3130 case TCM_DELETEALLITEMS:
3131 return TAB_DeleteAllItems (infoPtr);
3133 case TCM_GETITEMRECT:
3134 return TAB_GetItemRect (infoPtr, wParam, lParam);
3136 case TCM_GETCURSEL:
3137 return TAB_GetCurSel (infoPtr);
3139 case TCM_HITTEST:
3140 return TAB_HitTest (infoPtr, (LPTCHITTESTINFO)lParam);
3142 case TCM_SETCURSEL:
3143 return TAB_SetCurSel (infoPtr, (INT)wParam);
3145 case TCM_INSERTITEMA:
3146 case TCM_INSERTITEMW:
3147 return TAB_InsertItemT (infoPtr, wParam, lParam, uMsg == TCM_INSERTITEMW);
3149 case TCM_SETITEMEXTRA:
3150 return TAB_SetItemExtra (infoPtr, (int)wParam);
3152 case TCM_ADJUSTRECT:
3153 return TAB_AdjustRect (infoPtr, (BOOL)wParam, (LPRECT)lParam);
3155 case TCM_SETITEMSIZE:
3156 return TAB_SetItemSize (infoPtr, lParam);
3158 case TCM_REMOVEIMAGE:
3159 FIXME("Unimplemented msg TCM_REMOVEIMAGE\n");
3160 return 0;
3162 case TCM_SETPADDING:
3163 return TAB_SetPadding (infoPtr, lParam);
3165 case TCM_GETROWCOUNT:
3166 return TAB_GetRowCount(infoPtr);
3168 case TCM_GETUNICODEFORMAT:
3169 return TAB_GetUnicodeFormat (infoPtr);
3171 case TCM_SETUNICODEFORMAT:
3172 return TAB_SetUnicodeFormat (infoPtr, (BOOL)wParam);
3174 case TCM_HIGHLIGHTITEM:
3175 return TAB_HighlightItem (infoPtr, (INT)wParam, (BOOL)LOWORD(lParam));
3177 case TCM_GETTOOLTIPS:
3178 return TAB_GetToolTips (infoPtr);
3180 case TCM_SETTOOLTIPS:
3181 return TAB_SetToolTips (infoPtr, (HWND)wParam);
3183 case TCM_GETCURFOCUS:
3184 return TAB_GetCurFocus (infoPtr);
3186 case TCM_SETCURFOCUS:
3187 return TAB_SetCurFocus (infoPtr, (INT)wParam);
3189 case TCM_SETMINTABWIDTH:
3190 return TAB_SetMinTabWidth(infoPtr, (INT)lParam);
3192 case TCM_DESELECTALL:
3193 FIXME("Unimplemented msg TCM_DESELECTALL\n");
3194 return 0;
3196 case TCM_GETEXTENDEDSTYLE:
3197 FIXME("Unimplemented msg TCM_GETEXTENDEDSTYLE\n");
3198 return 0;
3200 case TCM_SETEXTENDEDSTYLE:
3201 FIXME("Unimplemented msg TCM_SETEXTENDEDSTYLE\n");
3202 return 0;
3204 case WM_GETFONT:
3205 return TAB_GetFont (infoPtr);
3207 case WM_SETFONT:
3208 return TAB_SetFont (infoPtr, (HFONT)wParam);
3210 case WM_CREATE:
3211 return TAB_Create (hwnd, wParam, lParam);
3213 case WM_NCDESTROY:
3214 return TAB_Destroy (infoPtr);
3216 case WM_GETDLGCODE:
3217 return DLGC_WANTARROWS | DLGC_WANTCHARS;
3219 case WM_LBUTTONDOWN:
3220 return TAB_LButtonDown (infoPtr, wParam, lParam);
3222 case WM_LBUTTONUP:
3223 return TAB_LButtonUp (infoPtr);
3225 case WM_NOTIFY:
3226 return SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, wParam, lParam);
3228 case WM_RBUTTONDOWN:
3229 return TAB_RButtonDown (infoPtr);
3231 case WM_MOUSEMOVE:
3232 return TAB_MouseMove (infoPtr, wParam, lParam);
3234 case WM_PRINTCLIENT:
3235 case WM_PAINT:
3236 return TAB_Paint (infoPtr, (HDC)wParam);
3238 case WM_SIZE:
3239 return TAB_Size (infoPtr);
3241 case WM_SETREDRAW:
3242 return TAB_SetRedraw (infoPtr, (BOOL)wParam);
3244 case WM_HSCROLL:
3245 return TAB_OnHScroll(infoPtr, (int)LOWORD(wParam), (int)HIWORD(wParam), (HWND)lParam);
3247 case WM_STYLECHANGED:
3248 TAB_SetItemBounds (infoPtr);
3249 InvalidateRect(hwnd, NULL, TRUE);
3250 return 0;
3252 case WM_SYSCOLORCHANGE:
3253 COMCTL32_RefreshSysColors();
3254 return 0;
3256 case WM_THEMECHANGED:
3257 return theme_changed (infoPtr);
3259 case WM_KILLFOCUS:
3260 case WM_SETFOCUS:
3261 TAB_FocusChanging(infoPtr);
3262 break; /* Don't disturb normal focus behavior */
3264 case WM_KEYUP:
3265 return TAB_KeyUp(infoPtr, wParam);
3266 case WM_NCHITTEST:
3267 return TAB_NCHitTest(infoPtr, lParam);
3269 case WM_NCCALCSIZE:
3270 return TAB_NCCalcSize(hwnd, wParam, lParam);
3272 default:
3273 if (uMsg >= WM_USER && uMsg < WM_APP && !COMCTL32_IsReflectedMessage(uMsg))
3274 WARN("unknown msg %04x wp=%08lx lp=%08lx\n",
3275 uMsg, wParam, lParam);
3276 break;
3278 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
3282 void
3283 TAB_Register (void)
3285 WNDCLASSW wndClass;
3287 ZeroMemory (&wndClass, sizeof(WNDCLASSW));
3288 wndClass.style = CS_GLOBALCLASS | CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
3289 wndClass.lpfnWndProc = TAB_WindowProc;
3290 wndClass.cbClsExtra = 0;
3291 wndClass.cbWndExtra = sizeof(TAB_INFO *);
3292 wndClass.hCursor = LoadCursorW (0, (LPWSTR)IDC_ARROW);
3293 wndClass.hbrBackground = (HBRUSH)(COLOR_BTNFACE+1);
3294 wndClass.lpszClassName = WC_TABCONTROLW;
3296 RegisterClassW (&wndClass);
3300 void
3301 TAB_Unregister (void)
3303 UnregisterClassW (WC_TABCONTROLW, NULL);