ole32: Add some tests for the running object table.
[wine.git] / dlls / comctl32 / tab.c
blob5ccc0597a780ff025dec23aecef50d40cb30c645
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 UINT mask;
86 DWORD dwState;
87 LPWSTR pszText;
88 INT iImage;
89 RECT rect; /* bounding rectangle of the item relative to the
90 * leftmost item (the leftmost item, 0, would have a
91 * "left" member of 0 in this rectangle)
93 * additionally the top member holds the row number
94 * and bottom is unused and should be 0 */
95 BYTE extra[1]; /* Space for caller supplied info, variable size */
96 } TAB_ITEM;
98 /* The size of a tab item depends on how much extra data is requested */
99 #define TAB_ITEM_SIZE(infoPtr) (sizeof(TAB_ITEM) - sizeof(BYTE) + infoPtr->cbInfo)
101 typedef struct
103 HWND hwnd; /* Tab control window */
104 HWND hwndNotify; /* notification window (parent) */
105 UINT uNumItem; /* number of tab items */
106 UINT uNumRows; /* number of tab rows */
107 INT tabHeight; /* height of the tab row */
108 INT tabWidth; /* width of tabs */
109 INT tabMinWidth; /* minimum width of items */
110 USHORT uHItemPadding; /* amount of horizontal padding, in pixels */
111 USHORT uVItemPadding; /* amount of vertical padding, in pixels */
112 USHORT uHItemPadding_s; /* Set amount of horizontal padding, in pixels */
113 USHORT uVItemPadding_s; /* Set amount of vertical padding, in pixels */
114 HFONT hFont; /* handle to the current font */
115 HCURSOR hcurArrow; /* handle to the current cursor */
116 HIMAGELIST himl; /* handle to an image list (may be 0) */
117 HWND hwndToolTip; /* handle to tab's tooltip */
118 INT leftmostVisible; /* Used for scrolling, this member contains
119 * the index of the first visible item */
120 INT iSelected; /* the currently selected item */
121 INT iHotTracked; /* the highlighted item under the mouse */
122 INT uFocus; /* item which has the focus */
123 TAB_ITEM* items; /* pointer to an array of TAB_ITEM's */
124 BOOL DoRedraw; /* flag for redrawing when tab contents is changed*/
125 BOOL needsScrolling; /* TRUE if the size of the tabs is greater than
126 * the size of the control */
127 BOOL fHeightSet; /* was the height of the tabs explicitly set? */
128 BOOL bUnicode; /* Unicode control? */
129 HWND hwndUpDown; /* Updown control used for scrolling */
130 INT cbInfo; /* Number of bytes of caller supplied info per tab */
131 } TAB_INFO;
133 /******************************************************************************
134 * Positioning constants
136 #define SELECTED_TAB_OFFSET 2
137 #define ROUND_CORNER_SIZE 2
138 #define DISPLAY_AREA_PADDINGX 2
139 #define DISPLAY_AREA_PADDINGY 2
140 #define CONTROL_BORDER_SIZEX 2
141 #define CONTROL_BORDER_SIZEY 2
142 #define BUTTON_SPACINGX 3
143 #define BUTTON_SPACINGY 3
144 #define FLAT_BTN_SPACINGX 8
145 #define DEFAULT_MIN_TAB_WIDTH 54
146 #define DEFAULT_TAB_WIDTH_FIXED 96
147 #define DEFAULT_PADDING_X 6
148 #define EXTRA_ICON_PADDING 3
150 #define TAB_GetInfoPtr(hwnd) ((TAB_INFO *)GetWindowLongPtrW(hwnd,0))
151 /* Since items are variable sized, cannot directly access them */
152 #define TAB_GetItem(info,i) \
153 ((TAB_ITEM*)((LPBYTE)info->items + (i) * TAB_ITEM_SIZE(info)))
155 #define GET_DEFAULT_MIN_TAB_WIDTH(infoPtr) (DEFAULT_MIN_TAB_WIDTH - (DEFAULT_PADDING_X - (infoPtr)->uHItemPadding) * 2)
157 /******************************************************************************
158 * Hot-tracking timer constants
160 #define TAB_HOTTRACK_TIMER 1
161 #define TAB_HOTTRACK_TIMER_INTERVAL 100 /* milliseconds */
163 static const WCHAR themeClass[] = { 'T','a','b',0 };
165 /******************************************************************************
166 * Prototypes
168 static void TAB_InvalidateTabArea(TAB_INFO *);
169 static void TAB_EnsureSelectionVisible(TAB_INFO *);
170 static void TAB_DrawItemInterior(TAB_INFO *, HDC, INT, RECT*);
172 static BOOL
173 TAB_SendSimpleNotify (const TAB_INFO *infoPtr, UINT code)
175 NMHDR nmhdr;
177 nmhdr.hwndFrom = infoPtr->hwnd;
178 nmhdr.idFrom = GetWindowLongPtrW(infoPtr->hwnd, GWLP_ID);
179 nmhdr.code = code;
181 return (BOOL) SendMessageW (infoPtr->hwndNotify, WM_NOTIFY,
182 (WPARAM) nmhdr.idFrom, (LPARAM) &nmhdr);
185 static void
186 TAB_RelayEvent (HWND hwndTip, HWND hwndMsg, UINT uMsg,
187 WPARAM wParam, LPARAM lParam)
189 MSG msg;
191 msg.hwnd = hwndMsg;
192 msg.message = uMsg;
193 msg.wParam = wParam;
194 msg.lParam = lParam;
195 msg.time = GetMessageTime ();
196 msg.pt.x = (short)LOWORD(GetMessagePos ());
197 msg.pt.y = (short)HIWORD(GetMessagePos ());
199 SendMessageW (hwndTip, TTM_RELAYEVENT, 0, (LPARAM)&msg);
202 static void
203 TAB_DumpItemExternalT(TCITEMW *pti, UINT iItem, BOOL isW)
205 if (TRACE_ON(tab)) {
206 TRACE("external tab %d, mask=0x%08x, dwState=0x%08x, dwStateMask=0x%08x, cchTextMax=0x%08x\n",
207 iItem, pti->mask, pti->dwState, pti->dwStateMask, pti->cchTextMax);
208 TRACE("external tab %d, iImage=%d, lParam=0x%08lx, pszTextW=%s\n",
209 iItem, pti->iImage, pti->lParam, isW ? debugstr_w(pti->pszText) : debugstr_a((LPSTR)pti->pszText));
213 static void
214 TAB_DumpItemInternal(TAB_INFO *infoPtr, UINT iItem)
216 if (TRACE_ON(tab)) {
217 TAB_ITEM *ti;
219 ti = TAB_GetItem(infoPtr, iItem);
220 TRACE("tab %d, mask=0x%08x, dwState=0x%08x, pszText=%s, iImage=%d\n",
221 iItem, ti->mask, ti->dwState, debugstr_w(ti->pszText),
222 ti->iImage);
223 TRACE("tab %d, rect.left=%d, rect.top(row)=%d\n",
224 iItem, ti->rect.left, ti->rect.top);
228 /* RETURNS
229 * the index of the selected tab, or -1 if no tab is selected. */
230 static inline LRESULT TAB_GetCurSel (const TAB_INFO *infoPtr)
232 return infoPtr->iSelected;
235 /* RETURNS
236 * the index of the tab item that has the focus
237 * NOTE
238 * we have not to return negative value
239 * TODO
240 * test for windows */
241 static inline LRESULT
242 TAB_GetCurFocus (const TAB_INFO *infoPtr)
244 if (infoPtr->uFocus<0)
246 FIXME("we have not to return negative value\n");
247 return 0;
249 return infoPtr->uFocus;
252 static inline LRESULT TAB_GetToolTips (const TAB_INFO *infoPtr)
254 if (infoPtr == NULL) return 0;
255 return (LRESULT)infoPtr->hwndToolTip;
258 static inline LRESULT TAB_SetCurSel (TAB_INFO *infoPtr, INT iItem)
260 INT prevItem = -1;
262 if (iItem >= 0 && iItem < infoPtr->uNumItem) {
263 prevItem=infoPtr->iSelected;
264 if (infoPtr->iSelected != iItem) {
265 infoPtr->iSelected=iItem;
266 TAB_EnsureSelectionVisible(infoPtr);
267 TAB_InvalidateTabArea(infoPtr);
270 return prevItem;
273 static LRESULT TAB_SetCurFocus (TAB_INFO *infoPtr, INT iItem)
275 if (iItem < 0 || iItem >= infoPtr->uNumItem) return 0;
277 if (GetWindowLongW(infoPtr->hwnd, GWL_STYLE) & TCS_BUTTONS) {
278 FIXME("Should set input focus\n");
279 } else {
280 int oldFocus = infoPtr->uFocus;
281 if (infoPtr->iSelected != iItem || oldFocus == -1 ) {
282 infoPtr->uFocus = iItem;
283 if (oldFocus != -1) {
284 if (!TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGING)) {
285 infoPtr->iSelected = iItem;
286 TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGE);
288 else
289 infoPtr->iSelected = iItem;
290 TAB_EnsureSelectionVisible(infoPtr);
291 TAB_InvalidateTabArea(infoPtr);
295 return 0;
298 static inline LRESULT
299 TAB_SetToolTips (TAB_INFO *infoPtr, HWND hwndToolTip)
301 if (infoPtr)
302 infoPtr->hwndToolTip = hwndToolTip;
303 return 0;
306 static inline LRESULT
307 TAB_SetPadding (TAB_INFO *infoPtr, LPARAM lParam)
309 if (infoPtr)
311 infoPtr->uHItemPadding_s=LOWORD(lParam);
312 infoPtr->uVItemPadding_s=HIWORD(lParam);
314 return 0;
317 /******************************************************************************
318 * TAB_InternalGetItemRect
320 * This method will calculate the rectangle representing a given tab item in
321 * client coordinates. This method takes scrolling into account.
323 * This method returns TRUE if the item is visible in the window and FALSE
324 * if it is completely outside the client area.
326 static BOOL TAB_InternalGetItemRect(
327 const TAB_INFO* infoPtr,
328 INT itemIndex,
329 RECT* itemRect,
330 RECT* selectedRect)
332 RECT tmpItemRect,clientRect;
333 LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
335 /* Perform a sanity check and a trivial visibility check. */
336 if ( (infoPtr->uNumItem <= 0) ||
337 (itemIndex >= infoPtr->uNumItem) ||
338 (!((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)) && (itemIndex < infoPtr->leftmostVisible)) )
340 TRACE("Not Visible\n");
341 /* need to initialize these to empty rects */
342 if (itemRect)
344 memset(itemRect,0,sizeof(RECT));
345 itemRect->bottom = infoPtr->tabHeight;
347 if (selectedRect)
348 memset(selectedRect,0,sizeof(RECT));
349 return FALSE;
353 * Avoid special cases in this procedure by assigning the "out"
354 * parameters if the caller didn't supply them
356 if (itemRect == NULL)
357 itemRect = &tmpItemRect;
359 /* Retrieve the unmodified item rect. */
360 *itemRect = TAB_GetItem(infoPtr,itemIndex)->rect;
362 /* calculate the times bottom and top based on the row */
363 GetClientRect(infoPtr->hwnd, &clientRect);
365 if ((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
367 itemRect->right = clientRect.right - SELECTED_TAB_OFFSET - itemRect->left * infoPtr->tabHeight -
368 ((lStyle & TCS_BUTTONS) ? itemRect->left * BUTTON_SPACINGX : 0);
369 itemRect->left = itemRect->right - infoPtr->tabHeight;
371 else if (lStyle & TCS_VERTICAL)
373 itemRect->left = clientRect.left + SELECTED_TAB_OFFSET + itemRect->left * infoPtr->tabHeight +
374 ((lStyle & TCS_BUTTONS) ? itemRect->left * BUTTON_SPACINGX : 0);
375 itemRect->right = itemRect->left + infoPtr->tabHeight;
377 else if (lStyle & TCS_BOTTOM)
379 itemRect->bottom = clientRect.bottom - itemRect->top * infoPtr->tabHeight -
380 ((lStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : SELECTED_TAB_OFFSET);
381 itemRect->top = itemRect->bottom - infoPtr->tabHeight;
383 else /* not TCS_BOTTOM and not TCS_VERTICAL */
385 itemRect->top = clientRect.top + itemRect->top * infoPtr->tabHeight +
386 ((lStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : SELECTED_TAB_OFFSET);
387 itemRect->bottom = itemRect->top + infoPtr->tabHeight;
391 * "scroll" it to make sure the item at the very left of the
392 * tab control is the leftmost visible tab.
394 if(lStyle & TCS_VERTICAL)
396 OffsetRect(itemRect,
398 -TAB_GetItem(infoPtr, infoPtr->leftmostVisible)->rect.top);
401 * Move the rectangle so the first item is slightly offset from
402 * the bottom of the tab control.
404 OffsetRect(itemRect,
406 SELECTED_TAB_OFFSET);
408 } else
410 OffsetRect(itemRect,
411 -TAB_GetItem(infoPtr, infoPtr->leftmostVisible)->rect.left,
415 * Move the rectangle so the first item is slightly offset from
416 * the left of the tab control.
418 OffsetRect(itemRect,
419 SELECTED_TAB_OFFSET,
422 TRACE("item %d tab h=%d, rect=(%d,%d)-(%d,%d)\n",
423 itemIndex, infoPtr->tabHeight,
424 itemRect->left, itemRect->top, itemRect->right, itemRect->bottom);
426 /* Now, calculate the position of the item as if it were selected. */
427 if (selectedRect!=NULL)
429 CopyRect(selectedRect, itemRect);
431 /* The rectangle of a selected item is a bit wider. */
432 if(lStyle & TCS_VERTICAL)
433 InflateRect(selectedRect, 0, SELECTED_TAB_OFFSET);
434 else
435 InflateRect(selectedRect, SELECTED_TAB_OFFSET, 0);
437 /* If it also a bit higher. */
438 if ((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
440 selectedRect->left -= 2; /* the border is thicker on the right */
441 selectedRect->right += SELECTED_TAB_OFFSET;
443 else if (lStyle & TCS_VERTICAL)
445 selectedRect->left -= SELECTED_TAB_OFFSET;
446 selectedRect->right += 1;
448 else if (lStyle & TCS_BOTTOM)
450 selectedRect->bottom += SELECTED_TAB_OFFSET;
452 else /* not TCS_BOTTOM and not TCS_VERTICAL */
454 selectedRect->top -= SELECTED_TAB_OFFSET;
455 selectedRect->bottom -= 1;
459 /* Check for visibility */
460 if (lStyle & TCS_VERTICAL)
461 return (itemRect->top < clientRect.bottom) && (itemRect->bottom > clientRect.top);
462 else
463 return (itemRect->left < clientRect.right) && (itemRect->right > clientRect.left);
466 static inline BOOL
467 TAB_GetItemRect(TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
469 return TAB_InternalGetItemRect(infoPtr, (INT)wParam, (LPRECT)lParam, (LPRECT)NULL);
472 /******************************************************************************
473 * TAB_KeyUp
475 * This method is called to handle keyboard input
477 static LRESULT TAB_KeyUp(TAB_INFO* infoPtr, WPARAM keyCode)
479 int newItem = -1;
481 switch (keyCode)
483 case VK_LEFT:
484 newItem = infoPtr->uFocus - 1;
485 break;
486 case VK_RIGHT:
487 newItem = infoPtr->uFocus + 1;
488 break;
492 * If we changed to a valid item, change the selection
494 if (newItem >= 0 &&
495 newItem < infoPtr->uNumItem &&
496 infoPtr->uFocus != newItem)
498 if (!TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGING))
500 infoPtr->iSelected = newItem;
501 infoPtr->uFocus = newItem;
502 TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGE);
504 TAB_EnsureSelectionVisible(infoPtr);
505 TAB_InvalidateTabArea(infoPtr);
509 return 0;
512 /******************************************************************************
513 * TAB_FocusChanging
515 * This method is called whenever the focus goes in or out of this control
516 * it is used to update the visual state of the control.
518 static void TAB_FocusChanging(const TAB_INFO *infoPtr)
520 RECT selectedRect;
521 BOOL isVisible;
524 * Get the rectangle for the item.
526 isVisible = TAB_InternalGetItemRect(infoPtr,
527 infoPtr->uFocus,
528 NULL,
529 &selectedRect);
532 * If the rectangle is not completely invisible, invalidate that
533 * portion of the window.
535 if (isVisible)
537 TRACE("invalidate (%d,%d)-(%d,%d)\n",
538 selectedRect.left,selectedRect.top,
539 selectedRect.right,selectedRect.bottom);
540 InvalidateRect(infoPtr->hwnd, &selectedRect, TRUE);
544 static INT TAB_InternalHitTest (
545 TAB_INFO* infoPtr,
546 POINT pt,
547 UINT* flags)
550 RECT rect;
551 INT iCount;
553 for (iCount = 0; iCount < infoPtr->uNumItem; iCount++)
555 TAB_InternalGetItemRect(infoPtr, iCount, &rect, NULL);
557 if (PtInRect(&rect, pt))
559 *flags = TCHT_ONITEM;
560 return iCount;
564 *flags = TCHT_NOWHERE;
565 return -1;
568 static inline LRESULT
569 TAB_HitTest (TAB_INFO *infoPtr, LPTCHITTESTINFO lptest)
571 return TAB_InternalHitTest (infoPtr, lptest->pt, &lptest->flags);
574 /******************************************************************************
575 * TAB_NCHitTest
577 * Napster v2b5 has a tab control for its main navigation which has a client
578 * area that covers the whole area of the dialog pages.
579 * That's why it receives all msgs for that area and the underlying dialog ctrls
580 * are dead.
581 * So I decided that we should handle WM_NCHITTEST here and return
582 * HTTRANSPARENT if we don't hit the tab control buttons.
583 * FIXME: WM_NCHITTEST handling correct ? Fix it if you know that Windows
584 * doesn't do it that way. Maybe depends on tab control styles ?
586 static inline LRESULT
587 TAB_NCHitTest (TAB_INFO *infoPtr, LPARAM lParam)
589 POINT pt;
590 UINT dummyflag;
592 pt.x = (short)LOWORD(lParam);
593 pt.y = (short)HIWORD(lParam);
594 ScreenToClient(infoPtr->hwnd, &pt);
596 if (TAB_InternalHitTest(infoPtr, pt, &dummyflag) == -1)
597 return HTTRANSPARENT;
598 else
599 return HTCLIENT;
602 static LRESULT
603 TAB_LButtonDown (TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
605 POINT pt;
606 INT newItem;
607 UINT dummy;
609 if (infoPtr->hwndToolTip)
610 TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
611 WM_LBUTTONDOWN, wParam, lParam);
613 if (GetWindowLongW(infoPtr->hwnd, GWL_STYLE) & TCS_FOCUSONBUTTONDOWN ) {
614 SetFocus (infoPtr->hwnd);
617 if (infoPtr->hwndToolTip)
618 TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
619 WM_LBUTTONDOWN, wParam, lParam);
621 pt.x = (short)LOWORD(lParam);
622 pt.y = (short)HIWORD(lParam);
624 newItem = TAB_InternalHitTest (infoPtr, pt, &dummy);
626 TRACE("On Tab, item %d\n", newItem);
628 if (newItem != -1 && infoPtr->iSelected != newItem)
630 if (!TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGING))
632 infoPtr->iSelected = newItem;
633 infoPtr->uFocus = newItem;
634 TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGE);
636 TAB_EnsureSelectionVisible(infoPtr);
638 TAB_InvalidateTabArea(infoPtr);
641 return 0;
644 static inline LRESULT
645 TAB_LButtonUp (const TAB_INFO *infoPtr)
647 TAB_SendSimpleNotify(infoPtr, NM_CLICK);
649 return 0;
652 static inline LRESULT
653 TAB_RButtonDown (const TAB_INFO *infoPtr)
655 TAB_SendSimpleNotify(infoPtr, NM_RCLICK);
656 return 0;
659 /******************************************************************************
660 * TAB_DrawLoneItemInterior
662 * This calls TAB_DrawItemInterior. However, TAB_DrawItemInterior is normally
663 * called by TAB_DrawItem which is normally called by TAB_Refresh which sets
664 * up the device context and font. This routine does the same setup but
665 * only calls TAB_DrawItemInterior for the single specified item.
667 static void
668 TAB_DrawLoneItemInterior(TAB_INFO* infoPtr, int iItem)
670 HDC hdc = GetDC(infoPtr->hwnd);
671 RECT r, rC;
673 /* Clip UpDown control to not draw over it */
674 if (infoPtr->needsScrolling)
676 GetWindowRect(infoPtr->hwnd, &rC);
677 GetWindowRect(infoPtr->hwndUpDown, &r);
678 ExcludeClipRect(hdc, r.left - rC.left, r.top - rC.top, r.right - rC.left, r.bottom - rC.top);
680 TAB_DrawItemInterior(infoPtr, hdc, iItem, NULL);
681 ReleaseDC(infoPtr->hwnd, hdc);
684 /* update a tab after hottracking - invalidate it or just redraw the interior,
685 * based on whether theming is used or not */
686 static inline void hottrack_refresh (TAB_INFO* infoPtr, int tabIndex)
688 if (tabIndex == -1) return;
690 if (GetWindowTheme (infoPtr->hwnd))
692 RECT rect;
693 TAB_InternalGetItemRect(infoPtr, tabIndex, &rect, NULL);
694 InvalidateRect (infoPtr->hwnd, &rect, FALSE);
696 else
697 TAB_DrawLoneItemInterior(infoPtr, tabIndex);
700 /******************************************************************************
701 * TAB_HotTrackTimerProc
703 * When a mouse-move event causes a tab to be highlighted (hot-tracking), a
704 * timer is setup so we can check if the mouse is moved out of our window.
705 * (We don't get an event when the mouse leaves, the mouse-move events just
706 * stop being delivered to our window and just start being delivered to
707 * another window.) This function is called when the timer triggers so
708 * we can check if the mouse has left our window. If so, we un-highlight
709 * the hot-tracked tab.
711 static void CALLBACK
712 TAB_HotTrackTimerProc
714 HWND hwnd, /* handle of window for timer messages */
715 UINT uMsg, /* WM_TIMER message */
716 UINT_PTR idEvent, /* timer identifier */
717 DWORD dwTime /* current system time */
720 TAB_INFO* infoPtr = TAB_GetInfoPtr(hwnd);
722 if (infoPtr != NULL && infoPtr->iHotTracked >= 0)
724 POINT pt;
727 ** If we can't get the cursor position, or if the cursor is outside our
728 ** window, we un-highlight the hot-tracked tab. Note that the cursor is
729 ** "outside" even if it is within our bounding rect if another window
730 ** overlaps. Note also that the case where the cursor stayed within our
731 ** window but has moved off the hot-tracked tab will be handled by the
732 ** WM_MOUSEMOVE event.
734 if (!GetCursorPos(&pt) || WindowFromPoint(pt) != hwnd)
736 /* Redraw iHotTracked to look normal */
737 INT iRedraw = infoPtr->iHotTracked;
738 infoPtr->iHotTracked = -1;
739 hottrack_refresh (infoPtr, iRedraw);
741 /* Kill this timer */
742 KillTimer(hwnd, TAB_HOTTRACK_TIMER);
747 /******************************************************************************
748 * TAB_RecalcHotTrack
750 * If a tab control has the TCS_HOTTRACK style, then the tab under the mouse
751 * should be highlighted. This function determines which tab in a tab control,
752 * if any, is under the mouse and records that information. The caller may
753 * supply output parameters to receive the item number of the tab item which
754 * was highlighted but isn't any longer and of the tab item which is now
755 * highlighted but wasn't previously. The caller can use this information to
756 * selectively redraw those tab items.
758 * If the caller has a mouse position, it can supply it through the pos
759 * parameter. For example, TAB_MouseMove does this. Otherwise, the caller
760 * supplies NULL and this function determines the current mouse position
761 * itself.
763 static void
764 TAB_RecalcHotTrack
766 TAB_INFO* infoPtr,
767 const LPARAM* pos,
768 int* out_redrawLeave,
769 int* out_redrawEnter
772 int item = -1;
775 if (out_redrawLeave != NULL)
776 *out_redrawLeave = -1;
777 if (out_redrawEnter != NULL)
778 *out_redrawEnter = -1;
780 if ((GetWindowLongW(infoPtr->hwnd, GWL_STYLE) & TCS_HOTTRACK)
781 || GetWindowTheme (infoPtr->hwnd))
783 POINT pt;
784 UINT flags;
786 if (pos == NULL)
788 GetCursorPos(&pt);
789 ScreenToClient(infoPtr->hwnd, &pt);
791 else
793 pt.x = (short)LOWORD(*pos);
794 pt.y = (short)HIWORD(*pos);
797 item = TAB_InternalHitTest(infoPtr, pt, &flags);
800 if (item != infoPtr->iHotTracked)
802 if (infoPtr->iHotTracked >= 0)
804 /* Mark currently hot-tracked to be redrawn to look normal */
805 if (out_redrawLeave != NULL)
806 *out_redrawLeave = infoPtr->iHotTracked;
808 if (item < 0)
810 /* Kill timer which forces recheck of mouse pos */
811 KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
814 else
816 /* Start timer so we recheck mouse pos */
817 UINT timerID = SetTimer
819 infoPtr->hwnd,
820 TAB_HOTTRACK_TIMER,
821 TAB_HOTTRACK_TIMER_INTERVAL,
822 TAB_HotTrackTimerProc
825 if (timerID == 0)
826 return; /* Hot tracking not available */
829 infoPtr->iHotTracked = item;
831 if (item >= 0)
833 /* Mark new hot-tracked to be redrawn to look highlighted */
834 if (out_redrawEnter != NULL)
835 *out_redrawEnter = item;
840 /******************************************************************************
841 * TAB_MouseMove
843 * Handles the mouse-move event. Updates tooltips. Updates hot-tracking.
845 static LRESULT
846 TAB_MouseMove (TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
848 int redrawLeave;
849 int redrawEnter;
851 if (infoPtr->hwndToolTip)
852 TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
853 WM_LBUTTONDOWN, wParam, lParam);
855 /* Determine which tab to highlight. Redraw tabs which change highlight
856 ** status. */
857 TAB_RecalcHotTrack(infoPtr, &lParam, &redrawLeave, &redrawEnter);
859 hottrack_refresh (infoPtr, redrawLeave);
860 hottrack_refresh (infoPtr, redrawEnter);
862 return 0;
865 /******************************************************************************
866 * TAB_AdjustRect
868 * Calculates the tab control's display area given the window rectangle or
869 * the window rectangle given the requested display rectangle.
871 static LRESULT TAB_AdjustRect(
872 TAB_INFO *infoPtr,
873 WPARAM fLarger,
874 LPRECT prc)
876 DWORD lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
877 LONG *iRightBottom, *iLeftTop;
879 TRACE ("hwnd=%p fLarger=%d (%d,%d)-(%d,%d)\n", infoPtr->hwnd, fLarger, prc->left, prc->top, prc->right, prc->bottom);
881 if(lStyle & TCS_VERTICAL)
883 iRightBottom = &(prc->right);
884 iLeftTop = &(prc->left);
886 else
888 iRightBottom = &(prc->bottom);
889 iLeftTop = &(prc->top);
892 if (fLarger) /* Go from display rectangle */
894 /* Add the height of the tabs. */
895 if (lStyle & TCS_BOTTOM)
896 *iRightBottom += infoPtr->tabHeight * infoPtr->uNumRows;
897 else
898 *iLeftTop -= infoPtr->tabHeight * infoPtr->uNumRows +
899 ((lStyle & TCS_BUTTONS)? 3 * (infoPtr->uNumRows - 1) : 0);
901 /* Inflate the rectangle for the padding */
902 InflateRect(prc, DISPLAY_AREA_PADDINGX, DISPLAY_AREA_PADDINGY);
904 /* Inflate for the border */
905 InflateRect(prc, CONTROL_BORDER_SIZEX, CONTROL_BORDER_SIZEY);
907 else /* Go from window rectangle. */
909 /* Deflate the rectangle for the border */
910 InflateRect(prc, -CONTROL_BORDER_SIZEX, -CONTROL_BORDER_SIZEY);
912 /* Deflate the rectangle for the padding */
913 InflateRect(prc, -DISPLAY_AREA_PADDINGX, -DISPLAY_AREA_PADDINGY);
915 /* Remove the height of the tabs. */
916 if (lStyle & TCS_BOTTOM)
917 *iRightBottom -= infoPtr->tabHeight * infoPtr->uNumRows;
918 else
919 *iLeftTop += (infoPtr->tabHeight) * infoPtr->uNumRows +
920 ((lStyle & TCS_BUTTONS)? 3 * (infoPtr->uNumRows - 1) : 0);
923 return 0;
926 /******************************************************************************
927 * TAB_OnHScroll
929 * This method will handle the notification from the scroll control and
930 * perform the scrolling operation on the tab control.
932 static LRESULT TAB_OnHScroll(
933 TAB_INFO *infoPtr,
934 int nScrollCode,
935 int nPos,
936 HWND hwndScroll)
938 if(nScrollCode == SB_THUMBPOSITION && nPos != infoPtr->leftmostVisible)
940 if(nPos < infoPtr->leftmostVisible)
941 infoPtr->leftmostVisible--;
942 else
943 infoPtr->leftmostVisible++;
945 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
946 TAB_InvalidateTabArea(infoPtr);
947 SendMessageW(infoPtr->hwndUpDown, UDM_SETPOS, 0,
948 MAKELONG(infoPtr->leftmostVisible, 0));
951 return 0;
954 /******************************************************************************
955 * TAB_SetupScrolling
957 * This method will check the current scrolling state and make sure the
958 * scrolling control is displayed (or not).
960 static void TAB_SetupScrolling(
961 HWND hwnd,
962 TAB_INFO* infoPtr,
963 const RECT* clientRect)
965 static const WCHAR msctls_updown32W[] = { 'm','s','c','t','l','s','_','u','p','d','o','w','n','3','2',0 };
966 static const WCHAR emptyW[] = { 0 };
967 INT maxRange = 0;
968 DWORD lStyle = GetWindowLongW(hwnd, GWL_STYLE);
970 if (infoPtr->needsScrolling)
972 RECT controlPos;
973 INT vsize, tabwidth;
976 * Calculate the position of the scroll control.
978 if(lStyle & TCS_VERTICAL)
980 controlPos.right = clientRect->right;
981 controlPos.left = controlPos.right - 2 * GetSystemMetrics(SM_CXHSCROLL);
983 if (lStyle & TCS_BOTTOM)
985 controlPos.top = clientRect->bottom - infoPtr->tabHeight;
986 controlPos.bottom = controlPos.top + GetSystemMetrics(SM_CYHSCROLL);
988 else
990 controlPos.bottom = clientRect->top + infoPtr->tabHeight;
991 controlPos.top = controlPos.bottom - GetSystemMetrics(SM_CYHSCROLL);
994 else
996 controlPos.right = clientRect->right;
997 controlPos.left = controlPos.right - 2 * GetSystemMetrics(SM_CXHSCROLL);
999 if (lStyle & TCS_BOTTOM)
1001 controlPos.top = clientRect->bottom - infoPtr->tabHeight;
1002 controlPos.bottom = controlPos.top + GetSystemMetrics(SM_CYHSCROLL);
1004 else
1006 controlPos.bottom = clientRect->top + infoPtr->tabHeight;
1007 controlPos.top = controlPos.bottom - GetSystemMetrics(SM_CYHSCROLL);
1012 * If we don't have a scroll control yet, we want to create one.
1013 * If we have one, we want to make sure it's positioned properly.
1015 if (infoPtr->hwndUpDown==0)
1017 infoPtr->hwndUpDown = CreateWindowW(msctls_updown32W, emptyW,
1018 WS_VISIBLE | WS_CHILD | UDS_HORZ,
1019 controlPos.left, controlPos.top,
1020 controlPos.right - controlPos.left,
1021 controlPos.bottom - controlPos.top,
1022 hwnd, NULL, NULL, NULL);
1024 else
1026 SetWindowPos(infoPtr->hwndUpDown,
1027 NULL,
1028 controlPos.left, controlPos.top,
1029 controlPos.right - controlPos.left,
1030 controlPos.bottom - controlPos.top,
1031 SWP_SHOWWINDOW | SWP_NOZORDER);
1034 /* Now calculate upper limit of the updown control range.
1035 * We do this by calculating how many tabs will be offscreen when the
1036 * last tab is visible.
1038 if(infoPtr->uNumItem)
1040 vsize = clientRect->right - (controlPos.right - controlPos.left + 1);
1041 maxRange = infoPtr->uNumItem;
1042 tabwidth = TAB_GetItem(infoPtr, infoPtr->uNumItem - 1)->rect.right;
1044 for(; maxRange > 0; maxRange--)
1046 if(tabwidth - TAB_GetItem(infoPtr,maxRange - 1)->rect.left > vsize)
1047 break;
1050 if(maxRange == infoPtr->uNumItem)
1051 maxRange--;
1054 else
1056 /* If we once had a scroll control... hide it */
1057 if (infoPtr->hwndUpDown!=0)
1058 ShowWindow(infoPtr->hwndUpDown, SW_HIDE);
1060 if (infoPtr->hwndUpDown)
1061 SendMessageW(infoPtr->hwndUpDown, UDM_SETRANGE32, 0, maxRange);
1064 /******************************************************************************
1065 * TAB_SetItemBounds
1067 * This method will calculate the position rectangles of all the items in the
1068 * control. The rectangle calculated starts at 0 for the first item in the
1069 * list and ignores scrolling and selection.
1070 * It also uses the current font to determine the height of the tab row and
1071 * it checks if all the tabs fit in the client area of the window. If they
1072 * don't, a scrolling control is added.
1074 static void TAB_SetItemBounds (TAB_INFO *infoPtr)
1076 LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1077 TEXTMETRICW fontMetrics;
1078 UINT curItem;
1079 INT curItemLeftPos;
1080 INT curItemRowCount;
1081 HFONT hFont, hOldFont;
1082 HDC hdc;
1083 RECT clientRect;
1084 INT iTemp;
1085 RECT* rcItem;
1086 INT iIndex;
1087 INT icon_width = 0;
1090 * We need to get text information so we need a DC and we need to select
1091 * a font.
1093 hdc = GetDC(infoPtr->hwnd);
1095 hFont = infoPtr->hFont ? infoPtr->hFont : GetStockObject (SYSTEM_FONT);
1096 hOldFont = SelectObject (hdc, hFont);
1099 * We will base the rectangle calculations on the client rectangle
1100 * of the control.
1102 GetClientRect(infoPtr->hwnd, &clientRect);
1104 /* if TCS_VERTICAL then swap the height and width so this code places the
1105 tabs along the top of the rectangle and we can just rotate them after
1106 rather than duplicate all of the below code */
1107 if(lStyle & TCS_VERTICAL)
1109 iTemp = clientRect.bottom;
1110 clientRect.bottom = clientRect.right;
1111 clientRect.right = iTemp;
1114 /* Now use hPadding and vPadding */
1115 infoPtr->uHItemPadding = infoPtr->uHItemPadding_s;
1116 infoPtr->uVItemPadding = infoPtr->uVItemPadding_s;
1118 /* The leftmost item will be "0" aligned */
1119 curItemLeftPos = 0;
1120 curItemRowCount = infoPtr->uNumItem ? 1 : 0;
1122 if (!(infoPtr->fHeightSet))
1124 int item_height;
1125 int icon_height = 0;
1127 /* Use the current font to determine the height of a tab. */
1128 GetTextMetricsW(hdc, &fontMetrics);
1130 /* Get the icon height */
1131 if (infoPtr->himl)
1132 ImageList_GetIconSize(infoPtr->himl, 0, &icon_height);
1134 /* Take the highest between font or icon */
1135 if (fontMetrics.tmHeight > icon_height)
1136 item_height = fontMetrics.tmHeight + 2;
1137 else
1138 item_height = icon_height;
1141 * Make sure there is enough space for the letters + icon + growing the
1142 * selected item + extra space for the selected item.
1144 infoPtr->tabHeight = item_height +
1145 ((lStyle & TCS_BUTTONS) ? 2 : 1) *
1146 infoPtr->uVItemPadding;
1148 TRACE("tabH=%d, tmH=%d, iconh=%d\n",
1149 infoPtr->tabHeight, fontMetrics.tmHeight, icon_height);
1152 TRACE("client right=%d\n", clientRect.right);
1154 /* Get the icon width */
1155 if (infoPtr->himl)
1157 ImageList_GetIconSize(infoPtr->himl, &icon_width, 0);
1159 if (lStyle & TCS_FIXEDWIDTH)
1160 icon_width += 4;
1161 else
1162 /* Add padding if icon is present */
1163 icon_width += infoPtr->uHItemPadding;
1166 for (curItem = 0; curItem < infoPtr->uNumItem; curItem++)
1168 TAB_ITEM *curr = TAB_GetItem(infoPtr, curItem);
1170 /* Set the leftmost position of the tab. */
1171 curr->rect.left = curItemLeftPos;
1173 if (lStyle & TCS_FIXEDWIDTH)
1175 curr->rect.right = curr->rect.left +
1176 max(infoPtr->tabWidth, icon_width);
1178 else if (!curr->pszText)
1180 /* If no text use minimum tab width including padding. */
1181 if (infoPtr->tabMinWidth < 0)
1182 curr->rect.right = curr->rect.left + GET_DEFAULT_MIN_TAB_WIDTH(infoPtr);
1183 else
1185 curr->rect.right = curr->rect.left + infoPtr->tabMinWidth;
1187 /* Add extra padding if icon is present */
1188 if (infoPtr->himl && infoPtr->tabMinWidth > 0 && infoPtr->tabMinWidth < DEFAULT_MIN_TAB_WIDTH
1189 && infoPtr->uHItemPadding > 1)
1190 curr->rect.right += EXTRA_ICON_PADDING * (infoPtr->uHItemPadding-1);
1193 else
1195 int tabwidth;
1196 SIZE size;
1197 /* Calculate how wide the tab is depending on the text it contains */
1198 GetTextExtentPoint32W(hdc, curr->pszText,
1199 lstrlenW(curr->pszText), &size);
1201 tabwidth = size.cx + icon_width + 2 * infoPtr->uHItemPadding;
1203 if (infoPtr->tabMinWidth < 0)
1204 tabwidth = max(tabwidth, GET_DEFAULT_MIN_TAB_WIDTH(infoPtr));
1205 else
1206 tabwidth = max(tabwidth, infoPtr->tabMinWidth);
1208 curr->rect.right = curr->rect.left + tabwidth;
1209 TRACE("for <%s>, l,r=%d,%d\n",
1210 debugstr_w(curr->pszText), curr->rect.left, curr->rect.right);
1214 * Check if this is a multiline tab control and if so
1215 * check to see if we should wrap the tabs
1217 * Wrap all these tabs. We will arrange them evenly later.
1221 if (((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)) &&
1222 (curr->rect.right >
1223 (clientRect.right - CONTROL_BORDER_SIZEX - DISPLAY_AREA_PADDINGX)))
1225 curr->rect.right -= curr->rect.left;
1227 curr->rect.left = 0;
1228 curItemRowCount++;
1229 TRACE("wrapping <%s>, l,r=%d,%d\n", debugstr_w(curr->pszText),
1230 curr->rect.left, curr->rect.right);
1233 curr->rect.bottom = 0;
1234 curr->rect.top = curItemRowCount - 1;
1236 TRACE("Rect: T %i, L %i, B %i, R %i\n", curr->rect.top,
1237 curr->rect.left, curr->rect.bottom, curr->rect.right);
1240 * The leftmost position of the next item is the rightmost position
1241 * of this one.
1243 if (lStyle & TCS_BUTTONS)
1245 curItemLeftPos = curr->rect.right + BUTTON_SPACINGX;
1246 if (lStyle & TCS_FLATBUTTONS)
1247 curItemLeftPos += FLAT_BTN_SPACINGX;
1249 else
1250 curItemLeftPos = curr->rect.right;
1253 if (!((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)))
1256 * Check if we need a scrolling control.
1258 infoPtr->needsScrolling = (curItemLeftPos + (2 * SELECTED_TAB_OFFSET) >
1259 clientRect.right);
1261 /* Don't need scrolling, then update infoPtr->leftmostVisible */
1262 if(!infoPtr->needsScrolling)
1263 infoPtr->leftmostVisible = 0;
1265 else
1268 * No scrolling in Multiline or Vertical styles.
1270 infoPtr->needsScrolling = FALSE;
1271 infoPtr->leftmostVisible = 0;
1273 TAB_SetupScrolling(infoPtr->hwnd, infoPtr, &clientRect);
1275 /* Set the number of rows */
1276 infoPtr->uNumRows = curItemRowCount;
1278 /* Arrange all tabs evenly if style says so */
1279 if (!(lStyle & TCS_RAGGEDRIGHT) &&
1280 ((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)) &&
1281 (infoPtr->uNumItem > 0) &&
1282 (infoPtr->uNumRows > 1))
1284 INT tabPerRow,remTab,iRow;
1285 UINT iItm;
1286 INT iCount=0;
1289 * Ok windows tries to even out the rows. place the same
1290 * number of tabs in each row. So lets give that a shot
1293 tabPerRow = infoPtr->uNumItem / (infoPtr->uNumRows);
1294 remTab = infoPtr->uNumItem % (infoPtr->uNumRows);
1296 for (iItm=0,iRow=0,iCount=0,curItemLeftPos=0;
1297 iItm<infoPtr->uNumItem;
1298 iItm++,iCount++)
1300 /* normalize the current rect */
1301 TAB_ITEM *curr = TAB_GetItem(infoPtr, iItm);
1303 /* shift the item to the left side of the clientRect */
1304 curr->rect.right -= curr->rect.left;
1305 curr->rect.left = 0;
1307 TRACE("r=%d, cl=%d, cl.r=%d, iCount=%d, iRow=%d, uNumRows=%d, remTab=%d, tabPerRow=%d\n",
1308 curr->rect.right, curItemLeftPos, clientRect.right,
1309 iCount, iRow, infoPtr->uNumRows, remTab, tabPerRow);
1311 /* if we have reached the maximum number of tabs on this row */
1312 /* move to the next row, reset our current item left position and */
1313 /* the count of items on this row */
1315 if (lStyle & TCS_VERTICAL) {
1316 /* Vert: Add the remaining tabs in the *last* remainder rows */
1317 if (iCount >= ((iRow>=(INT)infoPtr->uNumRows - remTab)?tabPerRow + 1:tabPerRow)) {
1318 iRow++;
1319 curItemLeftPos = 0;
1320 iCount = 0;
1322 } else {
1323 /* Horz: Add the remaining tabs in the *first* remainder rows */
1324 if (iCount >= ((iRow<remTab)?tabPerRow + 1:tabPerRow)) {
1325 iRow++;
1326 curItemLeftPos = 0;
1327 iCount = 0;
1331 /* shift the item to the right to place it as the next item in this row */
1332 curr->rect.left += curItemLeftPos;
1333 curr->rect.right += curItemLeftPos;
1334 curr->rect.top = iRow;
1335 if (lStyle & TCS_BUTTONS)
1337 curItemLeftPos = curr->rect.right + 1;
1338 if (lStyle & TCS_FLATBUTTONS)
1339 curItemLeftPos += FLAT_BTN_SPACINGX;
1341 else
1342 curItemLeftPos = curr->rect.right;
1344 TRACE("arranging <%s>, l,r=%d,%d, row=%d\n",
1345 debugstr_w(curr->pszText), curr->rect.left,
1346 curr->rect.right, curr->rect.top);
1350 * Justify the rows
1353 INT widthDiff, iIndexStart=0, iIndexEnd=0;
1354 INT remainder;
1355 INT iCount=0;
1357 while(iIndexStart < infoPtr->uNumItem)
1359 TAB_ITEM *start = TAB_GetItem(infoPtr, iIndexStart);
1362 * find the index of the row
1364 /* find the first item on the next row */
1365 for (iIndexEnd=iIndexStart;
1366 (iIndexEnd < infoPtr->uNumItem) &&
1367 (TAB_GetItem(infoPtr, iIndexEnd)->rect.top ==
1368 start->rect.top) ;
1369 iIndexEnd++)
1370 /* intentionally blank */;
1373 * we need to justify these tabs so they fill the whole given
1374 * client area
1377 /* find the amount of space remaining on this row */
1378 widthDiff = clientRect.right - (2 * SELECTED_TAB_OFFSET) -
1379 TAB_GetItem(infoPtr, iIndexEnd - 1)->rect.right;
1381 /* iCount is the number of tab items on this row */
1382 iCount = iIndexEnd - iIndexStart;
1384 if (iCount > 1)
1386 remainder = widthDiff % iCount;
1387 widthDiff = widthDiff / iCount;
1388 /* add widthDiff/iCount, or extra space/items on row, to each item on this row */
1389 for (iIndex=iIndexStart, iCount=0; iIndex < iIndexEnd; iIndex++, iCount++)
1391 TAB_ITEM *item = TAB_GetItem(infoPtr, iIndex);
1393 item->rect.left += iCount * widthDiff;
1394 item->rect.right += (iCount + 1) * widthDiff;
1396 TRACE("adjusting 1 <%s>, l,r=%d,%d\n",
1397 debugstr_w(item->pszText),
1398 item->rect.left, item->rect.right);
1401 TAB_GetItem(infoPtr, iIndex - 1)->rect.right += remainder;
1403 else /* we have only one item on this row, make it take up the entire row */
1405 start->rect.left = clientRect.left;
1406 start->rect.right = clientRect.right - 4;
1408 TRACE("adjusting 2 <%s>, l,r=%d,%d\n",
1409 debugstr_w(start->pszText),
1410 start->rect.left, start->rect.right);
1415 iIndexStart = iIndexEnd;
1420 /* if TCS_VERTICAL rotate the tabs so they are along the side of the clientRect */
1421 if(lStyle & TCS_VERTICAL)
1423 RECT rcOriginal;
1424 for(iIndex = 0; iIndex < infoPtr->uNumItem; iIndex++)
1426 rcItem = &TAB_GetItem(infoPtr, iIndex)->rect;
1428 rcOriginal = *rcItem;
1430 /* this is rotating the items by 90 degrees clockwise around the center of the control */
1431 rcItem->top = (rcOriginal.left - clientRect.left);
1432 rcItem->bottom = rcItem->top + (rcOriginal.right - rcOriginal.left);
1433 rcItem->left = rcOriginal.top;
1434 rcItem->right = rcOriginal.bottom;
1438 TAB_EnsureSelectionVisible(infoPtr);
1439 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
1441 /* Cleanup */
1442 SelectObject (hdc, hOldFont);
1443 ReleaseDC (infoPtr->hwnd, hdc);
1447 static void
1448 TAB_EraseTabInterior
1450 TAB_INFO* infoPtr,
1451 HDC hdc,
1452 INT iItem,
1453 RECT* drawRect
1456 LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1457 HBRUSH hbr = CreateSolidBrush (comctl32_color.clrBtnFace);
1458 BOOL deleteBrush = TRUE;
1459 RECT rTemp = *drawRect;
1461 InflateRect(&rTemp, -2, -2);
1462 if (lStyle & TCS_BUTTONS)
1464 if (iItem == infoPtr->iSelected)
1466 /* Background color */
1467 if (!(lStyle & TCS_OWNERDRAWFIXED))
1469 DeleteObject(hbr);
1470 hbr = GetSysColorBrush(COLOR_SCROLLBAR);
1472 SetTextColor(hdc, comctl32_color.clr3dFace);
1473 SetBkColor(hdc, comctl32_color.clr3dHilight);
1475 /* if COLOR_WINDOW happens to be the same as COLOR_3DHILIGHT
1476 * we better use 0x55aa bitmap brush to make scrollbar's background
1477 * look different from the window background.
1479 if (comctl32_color.clr3dHilight == comctl32_color.clrWindow)
1480 hbr = COMCTL32_hPattern55AABrush;
1482 deleteBrush = FALSE;
1484 FillRect(hdc, &rTemp, hbr);
1486 else /* ! selected */
1488 if (lStyle & TCS_FLATBUTTONS)
1490 FillRect(hdc, drawRect, hbr);
1491 if (iItem == infoPtr->iHotTracked)
1492 DrawEdge(hdc, drawRect, EDGE_RAISED, BF_SOFT|BF_RECT);
1494 else
1495 FillRect(hdc, &rTemp, hbr);
1499 else /* !TCS_BUTTONS */
1501 if (!GetWindowTheme (infoPtr->hwnd))
1502 FillRect(hdc, &rTemp, hbr);
1505 /* Cleanup */
1506 if (deleteBrush) DeleteObject(hbr);
1509 /******************************************************************************
1510 * TAB_DrawItemInterior
1512 * This method is used to draw the interior (text and icon) of a single tab
1513 * into the tab control.
1515 static void
1516 TAB_DrawItemInterior
1518 TAB_INFO* infoPtr,
1519 HDC hdc,
1520 INT iItem,
1521 RECT* drawRect
1524 LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1526 RECT localRect;
1528 HPEN htextPen;
1529 HPEN holdPen;
1530 INT oldBkMode;
1531 HFONT hOldFont;
1533 /* if (drawRect == NULL) */
1535 BOOL isVisible;
1536 RECT itemRect;
1537 RECT selectedRect;
1540 * Get the rectangle for the item.
1542 isVisible = TAB_InternalGetItemRect(infoPtr, iItem, &itemRect, &selectedRect);
1543 if (!isVisible)
1544 return;
1547 * Make sure drawRect points to something valid; simplifies code.
1549 drawRect = &localRect;
1552 * This logic copied from the part of TAB_DrawItem which draws
1553 * the tab background. It's important to keep it in sync. I
1554 * would have liked to avoid code duplication, but couldn't figure
1555 * out how without making spaghetti of TAB_DrawItem.
1557 if (iItem == infoPtr->iSelected)
1558 *drawRect = selectedRect;
1559 else
1560 *drawRect = itemRect;
1562 if (lStyle & TCS_BUTTONS)
1564 if (iItem == infoPtr->iSelected)
1566 drawRect->left += 4;
1567 drawRect->top += 4;
1568 drawRect->right -= 4;
1569 drawRect->bottom -= 1;
1571 else
1573 drawRect->left += 2;
1574 drawRect->top += 2;
1575 drawRect->right -= 2;
1576 drawRect->bottom -= 2;
1579 else
1581 if ((lStyle & TCS_VERTICAL) && (lStyle & TCS_BOTTOM))
1583 if (iItem != infoPtr->iSelected)
1585 drawRect->left += 2;
1586 drawRect->top += 2;
1587 drawRect->bottom -= 2;
1590 else if (lStyle & TCS_VERTICAL)
1592 if (iItem == infoPtr->iSelected)
1594 drawRect->right += 1;
1596 else
1598 drawRect->top += 2;
1599 drawRect->right -= 2;
1600 drawRect->bottom -= 2;
1603 else if (lStyle & TCS_BOTTOM)
1605 if (iItem == infoPtr->iSelected)
1607 drawRect->top -= 2;
1609 else
1611 InflateRect(drawRect, -2, -2);
1612 drawRect->bottom += 2;
1615 else
1617 if (iItem == infoPtr->iSelected)
1619 drawRect->bottom += 3;
1621 else
1623 drawRect->bottom -= 2;
1624 InflateRect(drawRect, -2, 0);
1629 TRACE("drawRect=(%d,%d)-(%d,%d)\n",
1630 drawRect->left, drawRect->top, drawRect->right, drawRect->bottom);
1632 /* Clear interior */
1633 TAB_EraseTabInterior (infoPtr, hdc, iItem, drawRect);
1635 /* Draw the focus rectangle */
1636 if (!(lStyle & TCS_FOCUSNEVER) &&
1637 (GetFocus() == infoPtr->hwnd) &&
1638 (iItem == infoPtr->uFocus) )
1640 RECT rFocus = *drawRect;
1641 InflateRect(&rFocus, -3, -3);
1642 if (lStyle & TCS_BOTTOM && !(lStyle & TCS_VERTICAL))
1643 rFocus.top -= 3;
1644 if (lStyle & TCS_BUTTONS)
1646 rFocus.left -= 3;
1647 rFocus.top -= 3;
1650 DrawFocusRect(hdc, &rFocus);
1654 * Text pen
1656 htextPen = CreatePen( PS_SOLID, 1, GetSysColor(COLOR_BTNTEXT) );
1657 holdPen = SelectObject(hdc, htextPen);
1658 hOldFont = SelectObject(hdc, infoPtr->hFont);
1661 * Setup for text output
1663 oldBkMode = SetBkMode(hdc, TRANSPARENT);
1664 if (!GetWindowTheme (infoPtr->hwnd) || (lStyle & TCS_BUTTONS))
1665 SetTextColor(hdc, (((lStyle & TCS_HOTTRACK) && (iItem == infoPtr->iHotTracked)
1666 && !(lStyle & TCS_FLATBUTTONS))
1667 | (TAB_GetItem(infoPtr, iItem)->dwState & TCIS_HIGHLIGHTED)) ?
1668 comctl32_color.clrHighlight : comctl32_color.clrBtnText);
1671 * if owner draw, tell the owner to draw
1673 if ((lStyle & TCS_OWNERDRAWFIXED) && GetParent(infoPtr->hwnd))
1675 DRAWITEMSTRUCT dis;
1676 UINT id;
1678 drawRect->top += 2;
1679 drawRect->right -= 1;
1680 if ( iItem == infoPtr->iSelected )
1682 drawRect->right -= 1;
1683 drawRect->left += 1;
1687 * get the control id
1689 id = (UINT)GetWindowLongPtrW( infoPtr->hwnd, GWLP_ID );
1692 * put together the DRAWITEMSTRUCT
1694 dis.CtlType = ODT_TAB;
1695 dis.CtlID = id;
1696 dis.itemID = iItem;
1697 dis.itemAction = ODA_DRAWENTIRE;
1698 dis.itemState = 0;
1699 if ( iItem == infoPtr->iSelected )
1700 dis.itemState |= ODS_SELECTED;
1701 if (infoPtr->uFocus == iItem)
1702 dis.itemState |= ODS_FOCUS;
1703 dis.hwndItem = infoPtr->hwnd;
1704 dis.hDC = hdc;
1705 CopyRect(&dis.rcItem,drawRect);
1706 dis.itemData = (ULONG_PTR)TAB_GetItem(infoPtr, iItem)->extra;
1709 * send the draw message
1711 SendMessageW( infoPtr->hwndNotify, WM_DRAWITEM, (WPARAM)id, (LPARAM)&dis );
1713 else
1715 TAB_ITEM *item = TAB_GetItem(infoPtr, iItem);
1716 RECT rcTemp;
1717 RECT rcImage;
1719 /* used to center the icon and text in the tab */
1720 RECT rcText;
1721 INT center_offset_h, center_offset_v;
1723 /* set rcImage to drawRect, we will use top & left in our ImageList_Draw call */
1724 rcImage = *drawRect;
1726 rcTemp = *drawRect;
1728 rcText.left = rcText.top = rcText.right = rcText.bottom = 0;
1730 /* get the rectangle that the text fits in */
1731 if (item->pszText)
1733 DrawTextW(hdc, item->pszText, -1, &rcText, DT_CALCRECT);
1736 * If not owner draw, then do the drawing ourselves.
1738 * Draw the icon.
1740 if (infoPtr->himl && (item->mask & TCIF_IMAGE))
1742 INT cx;
1743 INT cy;
1745 ImageList_GetIconSize(infoPtr->himl, &cx, &cy);
1747 if(lStyle & TCS_VERTICAL)
1749 center_offset_h = ((drawRect->bottom - drawRect->top) - (cy + infoPtr->uHItemPadding + (rcText.right - rcText.left))) / 2;
1750 center_offset_v = (drawRect->left + (drawRect->right - drawRect->left) - cx) / 2;
1752 else
1754 center_offset_h = ((drawRect->right - drawRect->left) - (cx + infoPtr->uHItemPadding + (rcText.right - rcText.left))) / 2;
1755 center_offset_v = (drawRect->top + (drawRect->bottom - drawRect->top) - cy) / 2;
1758 /* if an item is selected, the icon is shifted up instead of down */
1759 if (iItem == infoPtr->iSelected)
1760 center_offset_v -= infoPtr->uVItemPadding / 2;
1761 else
1762 center_offset_v += infoPtr->uVItemPadding / 2;
1764 if (lStyle & TCS_FIXEDWIDTH && lStyle & (TCS_FORCELABELLEFT | TCS_FORCEICONLEFT))
1765 center_offset_h = infoPtr->uHItemPadding;
1767 if (center_offset_h < 2)
1768 center_offset_h = 2;
1770 if (center_offset_v < 0)
1771 center_offset_v = 0;
1773 TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%d,%d)-(%d,%d), textlen=%d\n",
1774 debugstr_w(item->pszText), center_offset_h, center_offset_v,
1775 drawRect->left, drawRect->top, drawRect->right, drawRect->bottom,
1776 (rcText.right-rcText.left));
1778 if((lStyle & TCS_VERTICAL) && (lStyle & TCS_BOTTOM))
1780 rcImage.top = drawRect->top + center_offset_h;
1781 /* if tab is TCS_VERTICAL and TCS_BOTTOM, the text is drawn from the */
1782 /* right side of the tab, but the image still uses the left as its x position */
1783 /* this keeps the image always drawn off of the same side of the tab */
1784 rcImage.left = drawRect->right - cx - center_offset_v;
1785 drawRect->top += cy + infoPtr->uHItemPadding;
1787 else if(lStyle & TCS_VERTICAL)
1789 rcImage.top = drawRect->bottom - cy - center_offset_h;
1790 rcImage.left = drawRect->left + center_offset_v;
1791 drawRect->bottom -= cy + infoPtr->uHItemPadding;
1793 else /* normal style, whether TCS_BOTTOM or not */
1795 rcImage.left = drawRect->left + center_offset_h;
1796 rcImage.top = drawRect->top + center_offset_v;
1797 drawRect->left += cx + infoPtr->uHItemPadding;
1800 TRACE("drawing image=%d, left=%d, top=%d\n",
1801 item->iImage, rcImage.left, rcImage.top-1);
1802 ImageList_Draw
1804 infoPtr->himl,
1805 item->iImage,
1806 hdc,
1807 rcImage.left,
1808 rcImage.top,
1809 ILD_NORMAL
1813 /* Now position text */
1814 if (lStyle & TCS_FIXEDWIDTH && lStyle & TCS_FORCELABELLEFT)
1815 center_offset_h = infoPtr->uHItemPadding;
1816 else
1817 if(lStyle & TCS_VERTICAL)
1818 center_offset_h = ((drawRect->bottom - drawRect->top) - (rcText.right - rcText.left)) / 2;
1819 else
1820 center_offset_h = ((drawRect->right - drawRect->left) - (rcText.right - rcText.left)) / 2;
1822 if(lStyle & TCS_VERTICAL)
1824 if(lStyle & TCS_BOTTOM)
1825 drawRect->top+=center_offset_h;
1826 else
1827 drawRect->bottom-=center_offset_h;
1829 center_offset_v = ((drawRect->right - drawRect->left) - (rcText.bottom - rcText.top)) / 2;
1831 else
1833 drawRect->left += center_offset_h;
1834 center_offset_v = ((drawRect->bottom - drawRect->top) - (rcText.bottom - rcText.top)) / 2;
1837 /* if an item is selected, the text is shifted up instead of down */
1838 if (iItem == infoPtr->iSelected)
1839 center_offset_v -= infoPtr->uVItemPadding / 2;
1840 else
1841 center_offset_v += infoPtr->uVItemPadding / 2;
1843 if (center_offset_v < 0)
1844 center_offset_v = 0;
1846 if(lStyle & TCS_VERTICAL)
1847 drawRect->left += center_offset_v;
1848 else
1849 drawRect->top += center_offset_v;
1851 /* Draw the text */
1852 if(lStyle & TCS_VERTICAL) /* if we are vertical rotate the text and each character */
1854 static const WCHAR ArialW[] = { 'A','r','i','a','l',0 };
1855 LOGFONTW logfont;
1856 HFONT hFont = 0;
1857 INT nEscapement = 900;
1858 INT nOrientation = 900;
1860 if(lStyle & TCS_BOTTOM)
1862 nEscapement = -900;
1863 nOrientation = -900;
1866 /* to get a font with the escapement and orientation we are looking for, we need to */
1867 /* call CreateFontIndirectA, which requires us to set the values of the logfont we pass in */
1868 if (!GetObjectW((infoPtr->hFont) ?
1869 infoPtr->hFont : GetStockObject(SYSTEM_FONT),
1870 sizeof(LOGFONTW),&logfont))
1872 INT iPointSize = 9;
1874 lstrcpyW(logfont.lfFaceName, ArialW);
1875 logfont.lfHeight = -MulDiv(iPointSize, GetDeviceCaps(hdc, LOGPIXELSY),
1876 72);
1877 logfont.lfWeight = FW_NORMAL;
1878 logfont.lfItalic = 0;
1879 logfont.lfUnderline = 0;
1880 logfont.lfStrikeOut = 0;
1883 logfont.lfEscapement = nEscapement;
1884 logfont.lfOrientation = nOrientation;
1885 hFont = CreateFontIndirectW(&logfont);
1886 SelectObject(hdc, hFont);
1888 if (item->pszText)
1890 ExtTextOutW(hdc,
1891 (lStyle & TCS_BOTTOM) ? drawRect->right : drawRect->left,
1892 (!(lStyle & TCS_BOTTOM)) ? drawRect->bottom : drawRect->top,
1893 ETO_CLIPPED,
1894 drawRect,
1895 item->pszText,
1896 lstrlenW(item->pszText),
1900 DeleteObject(hFont);
1902 else
1904 TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%d,%d)-(%d,%d), textlen=%d\n",
1905 debugstr_w(item->pszText), center_offset_h, center_offset_v,
1906 drawRect->left, drawRect->top, drawRect->right, drawRect->bottom,
1907 (rcText.right-rcText.left));
1908 if (item->pszText)
1910 DrawTextW
1912 hdc,
1913 item->pszText,
1914 lstrlenW(item->pszText),
1915 drawRect,
1916 DT_LEFT | DT_SINGLELINE
1921 *drawRect = rcTemp; /* restore drawRect */
1925 * Cleanup
1927 SelectObject(hdc, hOldFont);
1928 SetBkMode(hdc, oldBkMode);
1929 SelectObject(hdc, holdPen);
1930 DeleteObject( htextPen );
1933 /******************************************************************************
1934 * TAB_DrawItem
1936 * This method is used to draw a single tab into the tab control.
1938 static void TAB_DrawItem(
1939 TAB_INFO *infoPtr,
1940 HDC hdc,
1941 INT iItem)
1943 LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1944 RECT itemRect;
1945 RECT selectedRect;
1946 BOOL isVisible;
1947 RECT r, fillRect, r1;
1948 INT clRight = 0;
1949 INT clBottom = 0;
1950 COLORREF bkgnd, corner;
1951 HTHEME theme;
1954 * Get the rectangle for the item.
1956 isVisible = TAB_InternalGetItemRect(infoPtr,
1957 iItem,
1958 &itemRect,
1959 &selectedRect);
1961 if (isVisible)
1963 RECT rUD, rC;
1965 /* Clip UpDown control to not draw over it */
1966 if (infoPtr->needsScrolling)
1968 GetWindowRect(infoPtr->hwnd, &rC);
1969 GetWindowRect(infoPtr->hwndUpDown, &rUD);
1970 ExcludeClipRect(hdc, rUD.left - rC.left, rUD.top - rC.top, rUD.right - rC.left, rUD.bottom - rC.top);
1973 /* If you need to see what the control is doing,
1974 * then override these variables. They will change what
1975 * fill colors are used for filling the tabs, and the
1976 * corners when drawing the edge.
1978 bkgnd = comctl32_color.clrBtnFace;
1979 corner = comctl32_color.clrBtnFace;
1981 if (lStyle & TCS_BUTTONS)
1983 /* Get item rectangle */
1984 r = itemRect;
1986 /* Separators between flat buttons */
1987 if (lStyle & TCS_FLATBUTTONS)
1989 r1 = r;
1990 r1.right += (FLAT_BTN_SPACINGX -2);
1991 DrawEdge(hdc, &r1, EDGE_ETCHED, BF_RIGHT);
1994 if (iItem == infoPtr->iSelected)
1996 DrawEdge(hdc, &r, EDGE_SUNKEN, BF_SOFT|BF_RECT);
1998 OffsetRect(&r, 1, 1);
2000 else /* ! selected */
2002 if (!(lStyle & TCS_FLATBUTTONS))
2003 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_RECT);
2006 else /* !TCS_BUTTONS */
2008 /* We draw a rectangle of different sizes depending on the selection
2009 * state. */
2010 if (iItem == infoPtr->iSelected) {
2011 RECT rect;
2012 GetClientRect (infoPtr->hwnd, &rect);
2013 clRight = rect.right;
2014 clBottom = rect.bottom;
2015 r = selectedRect;
2017 else
2018 r = itemRect;
2021 * Erase the background. (Delay it but setup rectangle.)
2022 * This is necessary when drawing the selected item since it is larger
2023 * than the others, it might overlap with stuff already drawn by the
2024 * other tabs
2026 fillRect = r;
2028 /* Draw themed tabs - but only if they are at the top.
2029 * Windows draws even side or bottom tabs themed, with wacky results.
2030 * However, since in Wine apps may get themed that did not opt in via
2031 * a manifest avoid theming when we know the result will be wrong */
2032 if ((theme = GetWindowTheme (infoPtr->hwnd))
2033 && ((lStyle & (TCS_VERTICAL | TCS_BOTTOM)) == 0))
2035 static const int partIds[8] = {
2036 /* Normal item */
2037 TABP_TABITEM,
2038 TABP_TABITEMLEFTEDGE,
2039 TABP_TABITEMRIGHTEDGE,
2040 TABP_TABITEMBOTHEDGE,
2041 /* Selected tab */
2042 TABP_TOPTABITEM,
2043 TABP_TOPTABITEMLEFTEDGE,
2044 TABP_TOPTABITEMRIGHTEDGE,
2045 TABP_TOPTABITEMBOTHEDGE,
2047 int partIndex = 0;
2048 int stateId = TIS_NORMAL;
2050 /* selected and unselected tabs have different parts */
2051 if (iItem == infoPtr->iSelected)
2052 partIndex += 4;
2053 /* The part also differs on the position of a tab on a line.
2054 * "Visually" determining the position works well enough. */
2055 if(selectedRect.left == 0)
2056 partIndex += 1;
2057 if(selectedRect.right == clRight)
2058 partIndex += 2;
2060 if (iItem == infoPtr->iSelected)
2061 stateId = TIS_SELECTED;
2062 else if (iItem == infoPtr->iHotTracked)
2063 stateId = TIS_HOT;
2064 else if (iItem == infoPtr->uFocus)
2065 stateId = TIS_FOCUSED;
2067 /* Adjust rectangle for bottommost row */
2068 if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2069 r.bottom += 3;
2071 DrawThemeBackground (theme, hdc, partIds[partIndex], stateId, &r, NULL);
2072 GetThemeBackgroundContentRect (theme, hdc, partIds[partIndex], stateId, &r, &r);
2074 else if(lStyle & TCS_VERTICAL)
2076 /* These are for adjusting the drawing of a Selected tab */
2077 /* The initial values are for the normal case of non-Selected */
2078 int ZZ = 1; /* Do not strech if selected */
2079 if (iItem == infoPtr->iSelected) {
2080 ZZ = 0;
2082 /* if leftmost draw the line longer */
2083 if(selectedRect.top == 0)
2084 fillRect.top += CONTROL_BORDER_SIZEY;
2085 /* if rightmost draw the line longer */
2086 if(selectedRect.bottom == clBottom)
2087 fillRect.bottom -= CONTROL_BORDER_SIZEY;
2090 if (lStyle & TCS_BOTTOM)
2092 /* Adjust both rectangles to match native */
2093 r.left += (1-ZZ);
2095 TRACE("<right> item=%d, fill=(%d,%d)-(%d,%d), edge=(%d,%d)-(%d,%d)\n",
2096 iItem,
2097 fillRect.left,fillRect.top,fillRect.right,fillRect.bottom,
2098 r.left,r.top,r.right,r.bottom);
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_RIGHT|BF_TOP|BF_BOTTOM);
2107 /* Now erase the top corner and draw diagonal edge */
2108 SetBkColor(hdc, corner);
2109 r1.left = r.right - ROUND_CORNER_SIZE - 1;
2110 r1.top = r.top;
2111 r1.right = r.right;
2112 r1.bottom = r1.top + ROUND_CORNER_SIZE;
2113 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2114 r1.right--;
2115 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPLEFT);
2117 /* Now erase the bottom corner and draw diagonal edge */
2118 r1.left = r.right - ROUND_CORNER_SIZE - 1;
2119 r1.bottom = r.bottom;
2120 r1.right = r.right;
2121 r1.top = r1.bottom - ROUND_CORNER_SIZE;
2122 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2123 r1.right--;
2124 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMLEFT);
2126 if ((iItem == infoPtr->iSelected) && (selectedRect.top == 0)) {
2127 r1 = r;
2128 r1.right = r1.left;
2129 r1.left--;
2130 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_TOP);
2134 else
2136 TRACE("<left> item=%d, fill=(%d,%d)-(%d,%d), edge=(%d,%d)-(%d,%d)\n",
2137 iItem,
2138 fillRect.left,fillRect.top,fillRect.right,fillRect.bottom,
2139 r.left,r.top,r.right,r.bottom);
2141 /* Clear interior */
2142 SetBkColor(hdc, bkgnd);
2143 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2145 /* Draw rectangular edge around tab */
2146 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_TOP|BF_BOTTOM);
2148 /* Now erase the top corner and draw diagonal edge */
2149 SetBkColor(hdc, corner);
2150 r1.left = r.left;
2151 r1.top = r.top;
2152 r1.right = r1.left + ROUND_CORNER_SIZE + 1;
2153 r1.bottom = r1.top + ROUND_CORNER_SIZE;
2154 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2155 r1.left++;
2156 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPRIGHT);
2158 /* Now erase the bottom corner and draw diagonal edge */
2159 r1.left = r.left;
2160 r1.bottom = r.bottom;
2161 r1.right = r1.left + ROUND_CORNER_SIZE + 1;
2162 r1.top = r1.bottom - ROUND_CORNER_SIZE;
2163 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2164 r1.left++;
2165 DrawEdge(hdc, &r1, EDGE_SUNKEN, BF_DIAGONAL_ENDTOPLEFT);
2168 else /* ! TCS_VERTICAL */
2170 /* These are for adjusting the drawing of a Selected tab */
2171 /* The initial values are for the normal case of non-Selected */
2172 if (iItem == infoPtr->iSelected) {
2173 /* if leftmost draw the line longer */
2174 if(selectedRect.left == 0)
2175 fillRect.left += CONTROL_BORDER_SIZEX;
2176 /* if rightmost draw the line longer */
2177 if(selectedRect.right == clRight)
2178 fillRect.right -= CONTROL_BORDER_SIZEX;
2181 if (lStyle & TCS_BOTTOM)
2183 /* Adjust both rectangles for topmost row */
2184 if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2186 fillRect.top -= 2;
2187 r.top -= 1;
2190 TRACE("<bottom> item=%d, fill=(%d,%d)-(%d,%d), edge=(%d,%d)-(%d,%d)\n",
2191 iItem,
2192 fillRect.left,fillRect.top,fillRect.right,fillRect.bottom,
2193 r.left,r.top,r.right,r.bottom);
2195 /* Clear interior */
2196 SetBkColor(hdc, bkgnd);
2197 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2199 /* Draw rectangular edge around tab */
2200 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_BOTTOM|BF_RIGHT);
2202 /* Now erase the righthand corner and draw diagonal edge */
2203 SetBkColor(hdc, corner);
2204 r1.left = r.right - ROUND_CORNER_SIZE;
2205 r1.bottom = r.bottom;
2206 r1.right = r.right;
2207 r1.top = r1.bottom - ROUND_CORNER_SIZE - 1;
2208 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2209 r1.bottom--;
2210 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMLEFT);
2212 /* Now erase the lefthand corner and draw diagonal edge */
2213 r1.left = r.left;
2214 r1.bottom = r.bottom;
2215 r1.right = r1.left + ROUND_CORNER_SIZE;
2216 r1.top = r1.bottom - ROUND_CORNER_SIZE - 1;
2217 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2218 r1.bottom--;
2219 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPLEFT);
2221 if (iItem == infoPtr->iSelected)
2223 r.top += 2;
2224 r.left += 1;
2225 if (selectedRect.left == 0)
2227 r1 = r;
2228 r1.bottom = r1.top;
2229 r1.top--;
2230 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_LEFT);
2235 else
2237 /* Adjust both rectangles for bottommost row */
2238 if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2240 fillRect.bottom += 3;
2241 r.bottom += 2;
2244 TRACE("<top> item=%d, fill=(%d,%d)-(%d,%d), edge=(%d,%d)-(%d,%d)\n",
2245 iItem,
2246 fillRect.left,fillRect.top,fillRect.right,fillRect.bottom,
2247 r.left,r.top,r.right,r.bottom);
2249 /* Clear interior */
2250 SetBkColor(hdc, bkgnd);
2251 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2253 /* Draw rectangular edge around tab */
2254 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_TOP|BF_RIGHT);
2256 /* Now erase the righthand corner and draw diagonal edge */
2257 SetBkColor(hdc, corner);
2258 r1.left = r.right - ROUND_CORNER_SIZE;
2259 r1.top = r.top;
2260 r1.right = r.right;
2261 r1.bottom = r1.top + ROUND_CORNER_SIZE + 1;
2262 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2263 r1.top++;
2264 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMRIGHT);
2266 /* Now erase the lefthand corner and draw diagonal edge */
2267 r1.left = r.left;
2268 r1.top = r.top;
2269 r1.right = r1.left + ROUND_CORNER_SIZE;
2270 r1.bottom = r1.top + ROUND_CORNER_SIZE + 1;
2271 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2272 r1.top++;
2273 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPRIGHT);
2278 TAB_DumpItemInternal(infoPtr, iItem);
2280 /* This modifies r to be the text rectangle. */
2281 TAB_DrawItemInterior(infoPtr, hdc, iItem, &r);
2285 /******************************************************************************
2286 * TAB_DrawBorder
2288 * This method is used to draw the raised border around the tab control
2289 * "content" area.
2291 static void TAB_DrawBorder (TAB_INFO *infoPtr, HDC hdc)
2293 RECT rect;
2294 DWORD lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
2295 HTHEME theme = GetWindowTheme (infoPtr->hwnd);
2297 GetClientRect (infoPtr->hwnd, &rect);
2300 * Adjust for the style
2303 if (infoPtr->uNumItem)
2305 if ((lStyle & TCS_BOTTOM) && !(lStyle & TCS_VERTICAL))
2306 rect.bottom -= infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2307 else if((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
2308 rect.right -= infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2309 else if(lStyle & TCS_VERTICAL)
2310 rect.left += infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2311 else /* not TCS_VERTICAL and not TCS_BOTTOM */
2312 rect.top += infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2315 TRACE("border=(%d,%d)-(%d,%d)\n",
2316 rect.left, rect.top, rect.right, rect.bottom);
2318 if (theme)
2319 DrawThemeBackground (theme, hdc, TABP_PANE, 0, &rect, NULL);
2320 else
2321 DrawEdge(hdc, &rect, EDGE_RAISED, BF_SOFT|BF_RECT);
2324 /******************************************************************************
2325 * TAB_Refresh
2327 * This method repaints the tab control..
2329 static void TAB_Refresh (TAB_INFO *infoPtr, HDC hdc)
2331 HFONT hOldFont;
2332 INT i;
2334 if (!infoPtr->DoRedraw)
2335 return;
2337 hOldFont = SelectObject (hdc, infoPtr->hFont);
2339 if (GetWindowLongW(infoPtr->hwnd, GWL_STYLE) & TCS_BUTTONS)
2341 for (i = 0; i < infoPtr->uNumItem; i++)
2342 TAB_DrawItem (infoPtr, hdc, i);
2344 else
2346 /* Draw all the non selected item first */
2347 for (i = 0; i < infoPtr->uNumItem; i++)
2349 if (i != infoPtr->iSelected)
2350 TAB_DrawItem (infoPtr, hdc, i);
2353 /* Now, draw the border, draw it before the selected item
2354 * since the selected item overwrites part of the border. */
2355 TAB_DrawBorder (infoPtr, hdc);
2357 /* Then, draw the selected item */
2358 TAB_DrawItem (infoPtr, hdc, infoPtr->iSelected);
2360 /* If we haven't set the current focus yet, set it now.
2361 * Only happens when we first paint the tab controls */
2362 if (infoPtr->uFocus == -1)
2363 TAB_SetCurFocus(infoPtr, infoPtr->iSelected);
2366 SelectObject (hdc, hOldFont);
2369 static inline DWORD TAB_GetRowCount (const TAB_INFO *infoPtr)
2371 return infoPtr->uNumRows;
2374 static inline LRESULT TAB_SetRedraw (TAB_INFO *infoPtr, BOOL doRedraw)
2376 infoPtr->DoRedraw = doRedraw;
2377 return 0;
2380 /******************************************************************************
2381 * TAB_EnsureSelectionVisible
2383 * This method will make sure that the current selection is completely
2384 * visible by scrolling until it is.
2386 static void TAB_EnsureSelectionVisible(
2387 TAB_INFO* infoPtr)
2389 INT iSelected = infoPtr->iSelected;
2390 LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
2391 INT iOrigLeftmostVisible = infoPtr->leftmostVisible;
2393 /* set the items row to the bottommost row or topmost row depending on
2394 * style */
2395 if ((infoPtr->uNumRows > 1) && !(lStyle & TCS_BUTTONS))
2397 TAB_ITEM *selected = TAB_GetItem(infoPtr, iSelected);
2398 INT newselected;
2399 INT iTargetRow;
2401 if(lStyle & TCS_VERTICAL)
2402 newselected = selected->rect.left;
2403 else
2404 newselected = selected->rect.top;
2406 /* the target row is always (number of rows - 1)
2407 as row 0 is furthest from the clientRect */
2408 iTargetRow = infoPtr->uNumRows - 1;
2410 if (newselected != iTargetRow)
2412 UINT i;
2413 if(lStyle & TCS_VERTICAL)
2415 for (i=0; i < infoPtr->uNumItem; i++)
2417 /* move everything in the row of the selected item to the iTargetRow */
2418 TAB_ITEM *item = TAB_GetItem(infoPtr, i);
2420 if (item->rect.left == newselected )
2421 item->rect.left = iTargetRow;
2422 else
2424 if (item->rect.left > newselected)
2425 item->rect.left-=1;
2429 else
2431 for (i=0; i < infoPtr->uNumItem; i++)
2433 TAB_ITEM *item = TAB_GetItem(infoPtr, i);
2435 if (item->rect.top == newselected )
2436 item->rect.top = iTargetRow;
2437 else
2439 if (item->rect.top > newselected)
2440 item->rect.top-=1;
2444 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
2449 * Do the trivial cases first.
2451 if ( (!infoPtr->needsScrolling) ||
2452 (infoPtr->hwndUpDown==0) || (lStyle & TCS_VERTICAL))
2453 return;
2455 if (infoPtr->leftmostVisible >= iSelected)
2457 infoPtr->leftmostVisible = iSelected;
2459 else
2461 TAB_ITEM *selected = TAB_GetItem(infoPtr, iSelected);
2462 RECT r;
2463 INT width;
2464 UINT i;
2466 /* Calculate the part of the client area that is visible */
2467 GetClientRect(infoPtr->hwnd, &r);
2468 width = r.right;
2470 GetClientRect(infoPtr->hwndUpDown, &r);
2471 width -= r.right;
2473 if ((selected->rect.right -
2474 selected->rect.left) >= width )
2476 /* Special case: width of selected item is greater than visible
2477 * part of control.
2479 infoPtr->leftmostVisible = iSelected;
2481 else
2483 for (i = infoPtr->leftmostVisible; i < infoPtr->uNumItem; i++)
2485 if ((selected->rect.right - TAB_GetItem(infoPtr, i)->rect.left) < width)
2486 break;
2488 infoPtr->leftmostVisible = i;
2492 if (infoPtr->leftmostVisible != iOrigLeftmostVisible)
2493 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
2495 SendMessageW(infoPtr->hwndUpDown, UDM_SETPOS, 0,
2496 MAKELONG(infoPtr->leftmostVisible, 0));
2499 /******************************************************************************
2500 * TAB_InvalidateTabArea
2502 * This method will invalidate the portion of the control that contains the
2503 * tabs. It is called when the state of the control changes and needs
2504 * to be redisplayed
2506 static void TAB_InvalidateTabArea(TAB_INFO* infoPtr)
2508 RECT clientRect, rInvalidate, rAdjClient;
2509 DWORD lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
2510 INT lastRow = infoPtr->uNumRows - 1;
2511 RECT rect;
2513 if (lastRow < 0) return;
2515 GetClientRect(infoPtr->hwnd, &clientRect);
2516 rInvalidate = clientRect;
2517 rAdjClient = clientRect;
2519 TAB_AdjustRect(infoPtr, 0, &rAdjClient);
2521 TAB_InternalGetItemRect(infoPtr, infoPtr->uNumItem-1 , &rect, NULL);
2522 if ((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
2524 rInvalidate.left = rAdjClient.right;
2525 if (infoPtr->uNumRows == 1)
2526 rInvalidate.bottom = clientRect.top + rect.bottom + 2 * SELECTED_TAB_OFFSET;
2528 else if(lStyle & TCS_VERTICAL)
2530 rInvalidate.right = rAdjClient.left;
2531 if (infoPtr->uNumRows == 1)
2532 rInvalidate.bottom = clientRect.top + rect.bottom + 2 * SELECTED_TAB_OFFSET;
2534 else if (lStyle & TCS_BOTTOM)
2536 rInvalidate.top = rAdjClient.bottom;
2537 if (infoPtr->uNumRows == 1)
2538 rInvalidate.right = clientRect.left + rect.right + 2 * SELECTED_TAB_OFFSET;
2540 else
2542 rInvalidate.bottom = rAdjClient.top;
2543 if (infoPtr->uNumRows == 1)
2544 rInvalidate.right = clientRect.left + rect.right + 2 * SELECTED_TAB_OFFSET;
2547 /* Punch out the updown control */
2548 if (infoPtr->needsScrolling && (rInvalidate.right > 0)) {
2549 RECT r;
2550 GetClientRect(infoPtr->hwndUpDown, &r);
2551 if (rInvalidate.right > clientRect.right - r.left)
2552 rInvalidate.right = rInvalidate.right - (r.right - r.left);
2553 else
2554 rInvalidate.right = clientRect.right - r.left;
2557 TRACE("invalidate (%d,%d)-(%d,%d)\n",
2558 rInvalidate.left, rInvalidate.top,
2559 rInvalidate.right, rInvalidate.bottom);
2561 InvalidateRect(infoPtr->hwnd, &rInvalidate, TRUE);
2564 static inline LRESULT TAB_Paint (TAB_INFO *infoPtr, HDC hdcPaint)
2566 HDC hdc;
2567 PAINTSTRUCT ps;
2569 if (hdcPaint)
2570 hdc = hdcPaint;
2571 else
2573 hdc = BeginPaint (infoPtr->hwnd, &ps);
2574 TRACE("erase %d, rect=(%d,%d)-(%d,%d)\n",
2575 ps.fErase,
2576 ps.rcPaint.left,ps.rcPaint.top,ps.rcPaint.right,ps.rcPaint.bottom);
2579 TAB_Refresh (infoPtr, hdc);
2581 if (!hdcPaint)
2582 EndPaint (infoPtr->hwnd, &ps);
2584 return 0;
2587 static LRESULT
2588 TAB_InsertItemT (TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam, BOOL bUnicode)
2590 TAB_ITEM *item;
2591 TCITEMW *pti;
2592 INT iItem;
2593 RECT rect;
2595 GetClientRect (infoPtr->hwnd, &rect);
2596 TRACE("Rect: %p T %i, L %i, B %i, R %i\n", infoPtr->hwnd,
2597 rect.top, rect.left, rect.bottom, rect.right);
2599 pti = (TCITEMW *)lParam;
2600 iItem = (INT)wParam;
2602 if (iItem < 0) return -1;
2603 if (iItem > infoPtr->uNumItem)
2604 iItem = infoPtr->uNumItem;
2606 TAB_DumpItemExternalT(pti, iItem, bUnicode);
2609 if (infoPtr->uNumItem == 0) {
2610 infoPtr->items = Alloc (TAB_ITEM_SIZE(infoPtr));
2611 infoPtr->uNumItem++;
2612 infoPtr->iSelected = 0;
2614 else {
2615 LPBYTE oldItems = (LPBYTE)infoPtr->items;
2617 infoPtr->uNumItem++;
2618 infoPtr->items = Alloc (TAB_ITEM_SIZE(infoPtr) * infoPtr->uNumItem);
2620 /* pre insert copy */
2621 if (iItem > 0) {
2622 memcpy (infoPtr->items, oldItems,
2623 iItem * TAB_ITEM_SIZE(infoPtr));
2626 /* post insert copy */
2627 if (iItem < infoPtr->uNumItem - 1) {
2628 memcpy (TAB_GetItem(infoPtr, iItem + 1),
2629 oldItems + iItem * TAB_ITEM_SIZE(infoPtr),
2630 (infoPtr->uNumItem - iItem - 1) * TAB_ITEM_SIZE(infoPtr));
2634 if (iItem <= infoPtr->iSelected)
2635 infoPtr->iSelected++;
2637 Free (oldItems);
2640 item = TAB_GetItem(infoPtr, iItem);
2642 item->mask = pti->mask;
2643 item->pszText = NULL;
2645 if (pti->mask & TCIF_TEXT)
2647 if (bUnicode)
2648 Str_SetPtrW (&item->pszText, pti->pszText);
2649 else
2650 Str_SetPtrAtoW (&item->pszText, (LPSTR)pti->pszText);
2653 if (pti->mask & TCIF_IMAGE)
2654 item->iImage = pti->iImage;
2655 else
2656 item->iImage = -1;
2658 if (pti->mask & TCIF_PARAM)
2659 memcpy(item->extra, &pti->lParam, infoPtr->cbInfo);
2660 else
2661 memset(item->extra, 0, infoPtr->cbInfo);
2663 TAB_SetItemBounds(infoPtr);
2664 if (infoPtr->uNumItem > 1)
2665 TAB_InvalidateTabArea(infoPtr);
2666 else
2667 InvalidateRect(infoPtr->hwnd, NULL, TRUE);
2669 TRACE("[%p]: added item %d %s\n",
2670 infoPtr->hwnd, iItem, debugstr_w(item->pszText));
2672 return iItem;
2675 static LRESULT
2676 TAB_SetItemSize (TAB_INFO *infoPtr, LPARAM lParam)
2678 LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
2679 LONG lResult = 0;
2680 BOOL bNeedPaint = FALSE;
2682 lResult = MAKELONG(infoPtr->tabWidth, infoPtr->tabHeight);
2684 /* UNDOCUMENTED: If requested Width or Height is 0 this means that program wants to use auto size. */
2685 if (lStyle & TCS_FIXEDWIDTH && (infoPtr->tabWidth != (INT)LOWORD(lParam)))
2687 infoPtr->tabWidth = (INT)LOWORD(lParam);
2688 bNeedPaint = TRUE;
2691 if (infoPtr->tabHeight != (INT)HIWORD(lParam))
2693 if ((infoPtr->fHeightSet = ((INT)HIWORD(lParam) != 0)))
2694 infoPtr->tabHeight = (INT)HIWORD(lParam);
2696 bNeedPaint = TRUE;
2698 TRACE("was h=%d,w=%d, now h=%d,w=%d\n",
2699 HIWORD(lResult), LOWORD(lResult),
2700 infoPtr->tabHeight, infoPtr->tabWidth);
2702 if (bNeedPaint)
2704 TAB_SetItemBounds(infoPtr);
2705 RedrawWindow(infoPtr->hwnd, NULL, NULL, RDW_ERASE | RDW_INVALIDATE | RDW_UPDATENOW);
2708 return lResult;
2711 static inline LRESULT TAB_SetMinTabWidth (TAB_INFO *infoPtr, INT cx)
2713 INT oldcx = 0;
2715 TRACE("(%p,%d)\n", infoPtr, cx);
2717 if (infoPtr) {
2718 oldcx = infoPtr->tabMinWidth;
2719 infoPtr->tabMinWidth = cx;
2721 TAB_SetItemBounds(infoPtr);
2723 return oldcx;
2726 static inline LRESULT
2727 TAB_HighlightItem (TAB_INFO *infoPtr, INT iItem, BOOL fHighlight)
2729 LPDWORD lpState;
2731 TRACE("(%p,%d,%s)\n", infoPtr, iItem, fHighlight ? "true" : "false");
2733 if (!infoPtr || iItem < 0 || iItem >= infoPtr->uNumItem)
2734 return FALSE;
2736 lpState = &TAB_GetItem(infoPtr, iItem)->dwState;
2738 if (fHighlight)
2739 *lpState |= TCIS_HIGHLIGHTED;
2740 else
2741 *lpState &= ~TCIS_HIGHLIGHTED;
2743 return TRUE;
2746 static LRESULT
2747 TAB_SetItemT (TAB_INFO *infoPtr, INT iItem, LPTCITEMW tabItem, BOOL bUnicode)
2749 TAB_ITEM *wineItem;
2751 TRACE("(%p,%d,%p,%s)\n", infoPtr, iItem, tabItem, bUnicode ? "true" : "false");
2753 if (iItem < 0 || iItem >= infoPtr->uNumItem)
2754 return FALSE;
2756 TAB_DumpItemExternalT(tabItem, iItem, bUnicode);
2758 wineItem = TAB_GetItem(infoPtr, iItem);
2760 if (tabItem->mask & TCIF_IMAGE)
2761 wineItem->iImage = tabItem->iImage;
2763 if (tabItem->mask & TCIF_PARAM)
2764 memcpy(wineItem->extra, &tabItem->lParam, infoPtr->cbInfo);
2766 if (tabItem->mask & TCIF_RTLREADING)
2767 FIXME("TCIF_RTLREADING\n");
2769 if (tabItem->mask & TCIF_STATE)
2770 wineItem->dwState = tabItem->dwState;
2772 if (tabItem->mask & TCIF_TEXT)
2774 if (wineItem->pszText)
2776 Free(wineItem->pszText);
2777 wineItem->pszText = NULL;
2779 if (bUnicode)
2780 Str_SetPtrW(&wineItem->pszText, tabItem->pszText);
2781 else
2782 Str_SetPtrAtoW(&wineItem->pszText, (LPSTR)tabItem->pszText);
2785 /* Update and repaint tabs */
2786 TAB_SetItemBounds(infoPtr);
2787 TAB_InvalidateTabArea(infoPtr);
2789 return TRUE;
2792 static inline LRESULT TAB_GetItemCount (const TAB_INFO *infoPtr)
2794 return infoPtr->uNumItem;
2798 static LRESULT
2799 TAB_GetItemT (TAB_INFO *infoPtr, INT iItem, LPTCITEMW tabItem, BOOL bUnicode)
2801 TAB_ITEM *wineItem;
2803 TRACE("(%p,%d,%p,%s)\n", infoPtr, iItem, tabItem, bUnicode ? "true" : "false");
2805 if (iItem < 0 || iItem >= infoPtr->uNumItem)
2806 return FALSE;
2808 wineItem = TAB_GetItem(infoPtr, iItem);
2810 if (tabItem->mask & TCIF_IMAGE)
2811 tabItem->iImage = wineItem->iImage;
2813 if (tabItem->mask & TCIF_PARAM)
2814 memcpy(&tabItem->lParam, wineItem->extra, infoPtr->cbInfo);
2816 if (tabItem->mask & TCIF_RTLREADING)
2817 FIXME("TCIF_RTLREADING\n");
2819 if (tabItem->mask & TCIF_STATE)
2820 tabItem->dwState = wineItem->dwState;
2822 if (tabItem->mask & TCIF_TEXT)
2824 if (bUnicode)
2825 Str_GetPtrW (wineItem->pszText, tabItem->pszText, tabItem->cchTextMax);
2826 else
2827 Str_GetPtrWtoA (wineItem->pszText, (LPSTR)tabItem->pszText, tabItem->cchTextMax);
2830 TAB_DumpItemExternalT(tabItem, iItem, bUnicode);
2832 return TRUE;
2836 static LRESULT TAB_DeleteItem (TAB_INFO *infoPtr, INT iItem)
2838 BOOL bResult = FALSE;
2840 TRACE("(%p, %d)\n", infoPtr, iItem);
2842 if ((iItem >= 0) && (iItem < infoPtr->uNumItem))
2844 TAB_ITEM *item = TAB_GetItem(infoPtr, iItem);
2845 LPBYTE oldItems = (LPBYTE)infoPtr->items;
2847 TAB_InvalidateTabArea(infoPtr);
2849 if ((item->mask & TCIF_TEXT) && item->pszText)
2850 Free(item->pszText);
2852 infoPtr->uNumItem--;
2854 if (!infoPtr->uNumItem)
2856 infoPtr->items = NULL;
2857 if (infoPtr->iHotTracked >= 0)
2859 KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
2860 infoPtr->iHotTracked = -1;
2863 else
2865 infoPtr->items = Alloc(TAB_ITEM_SIZE(infoPtr) * infoPtr->uNumItem);
2867 if (iItem > 0)
2868 memcpy(infoPtr->items, oldItems, iItem * TAB_ITEM_SIZE(infoPtr));
2870 if (iItem < infoPtr->uNumItem)
2871 memcpy(TAB_GetItem(infoPtr, iItem),
2872 oldItems + (iItem + 1) * TAB_ITEM_SIZE(infoPtr),
2873 (infoPtr->uNumItem - iItem) * TAB_ITEM_SIZE(infoPtr));
2875 if (iItem <= infoPtr->iHotTracked)
2877 /* When tabs move left/up, the hot track item may change */
2878 FIXME("Recalc hot track\n");
2881 Free(oldItems);
2883 /* Readjust the selected index */
2884 if ((iItem == infoPtr->iSelected) && (iItem > 0))
2885 infoPtr->iSelected--;
2887 if (iItem < infoPtr->iSelected)
2888 infoPtr->iSelected--;
2890 if (infoPtr->uNumItem == 0)
2891 infoPtr->iSelected = -1;
2893 /* Reposition and repaint tabs */
2894 TAB_SetItemBounds(infoPtr);
2896 bResult = TRUE;
2899 return bResult;
2902 static inline LRESULT TAB_DeleteAllItems (TAB_INFO *infoPtr)
2904 TRACE("(%p)\n", infoPtr);
2905 while (infoPtr->uNumItem)
2906 TAB_DeleteItem (infoPtr, 0);
2907 return TRUE;
2911 static inline LRESULT TAB_GetFont (const TAB_INFO *infoPtr)
2913 TRACE("(%p) returning %p\n", infoPtr, infoPtr->hFont);
2914 return (LRESULT)infoPtr->hFont;
2917 static inline LRESULT TAB_SetFont (TAB_INFO *infoPtr, HFONT hNewFont)
2919 TRACE("(%p,%p)\n", infoPtr, hNewFont);
2921 infoPtr->hFont = hNewFont;
2923 TAB_SetItemBounds(infoPtr);
2925 TAB_InvalidateTabArea(infoPtr);
2927 return 0;
2931 static inline LRESULT TAB_GetImageList (const TAB_INFO *infoPtr)
2933 TRACE("\n");
2934 return (LRESULT)infoPtr->himl;
2937 static inline LRESULT TAB_SetImageList (TAB_INFO *infoPtr, HIMAGELIST himlNew)
2939 HIMAGELIST himlPrev = infoPtr->himl;
2940 TRACE("\n");
2941 infoPtr->himl = himlNew;
2942 return (LRESULT)himlPrev;
2945 static inline LRESULT TAB_GetUnicodeFormat (const TAB_INFO *infoPtr)
2947 return infoPtr->bUnicode;
2950 static inline LRESULT TAB_SetUnicodeFormat (TAB_INFO *infoPtr, BOOL bUnicode)
2952 BOOL bTemp = infoPtr->bUnicode;
2954 infoPtr->bUnicode = bUnicode;
2956 return bTemp;
2959 static inline LRESULT TAB_Size (TAB_INFO *infoPtr)
2961 /* I'm not really sure what the following code was meant to do.
2962 This is what it is doing:
2963 When WM_SIZE is sent with SIZE_RESTORED, the control
2964 gets positioned in the top left corner.
2966 RECT parent_rect;
2967 HWND parent;
2968 UINT uPosFlags,cx,cy;
2970 uPosFlags=0;
2971 if (!wParam) {
2972 parent = GetParent (hwnd);
2973 GetClientRect(parent, &parent_rect);
2974 cx=LOWORD (lParam);
2975 cy=HIWORD (lParam);
2976 if (GetWindowLongW(hwnd, GWL_STYLE) & CCS_NORESIZE)
2977 uPosFlags |= (SWP_NOSIZE | SWP_NOMOVE);
2979 SetWindowPos (hwnd, 0, parent_rect.left, parent_rect.top,
2980 cx, cy, uPosFlags | SWP_NOZORDER);
2981 } else {
2982 FIXME("WM_SIZE flag %x %lx not handled\n", wParam, lParam);
2983 } */
2985 /* Recompute the size/position of the tabs. */
2986 TAB_SetItemBounds (infoPtr);
2988 /* Force a repaint of the control. */
2989 InvalidateRect(infoPtr->hwnd, NULL, TRUE);
2991 return 0;
2995 static LRESULT TAB_Create (HWND hwnd, WPARAM wParam, LPARAM lParam)
2997 TAB_INFO *infoPtr;
2998 TEXTMETRICW fontMetrics;
2999 HDC hdc;
3000 HFONT hOldFont;
3001 DWORD dwStyle;
3003 infoPtr = (TAB_INFO *)Alloc (sizeof(TAB_INFO));
3005 SetWindowLongPtrW(hwnd, 0, (DWORD_PTR)infoPtr);
3007 infoPtr->hwnd = hwnd;
3008 infoPtr->hwndNotify = ((LPCREATESTRUCTW)lParam)->hwndParent;
3009 infoPtr->uNumItem = 0;
3010 infoPtr->uNumRows = 0;
3011 infoPtr->uHItemPadding = 6;
3012 infoPtr->uVItemPadding = 3;
3013 infoPtr->uHItemPadding_s = 6;
3014 infoPtr->uVItemPadding_s = 3;
3015 infoPtr->hFont = 0;
3016 infoPtr->items = 0;
3017 infoPtr->hcurArrow = LoadCursorW (0, (LPWSTR)IDC_ARROW);
3018 infoPtr->iSelected = -1;
3019 infoPtr->iHotTracked = -1;
3020 infoPtr->uFocus = -1;
3021 infoPtr->hwndToolTip = 0;
3022 infoPtr->DoRedraw = TRUE;
3023 infoPtr->needsScrolling = FALSE;
3024 infoPtr->hwndUpDown = 0;
3025 infoPtr->leftmostVisible = 0;
3026 infoPtr->fHeightSet = FALSE;
3027 infoPtr->bUnicode = IsWindowUnicode (hwnd);
3028 infoPtr->cbInfo = sizeof(LPARAM);
3030 TRACE("Created tab control, hwnd [%p]\n", hwnd);
3032 /* The tab control always has the WS_CLIPSIBLINGS style. Even
3033 if you don't specify it in CreateWindow. This is necessary in
3034 order for paint to work correctly. This follows windows behaviour. */
3035 dwStyle = GetWindowLongW(hwnd, GWL_STYLE);
3036 SetWindowLongW(hwnd, GWL_STYLE, dwStyle|WS_CLIPSIBLINGS);
3038 if (dwStyle & TCS_TOOLTIPS) {
3039 /* Create tooltip control */
3040 infoPtr->hwndToolTip =
3041 CreateWindowExW (0, TOOLTIPS_CLASSW, NULL, WS_POPUP,
3042 CW_USEDEFAULT, CW_USEDEFAULT,
3043 CW_USEDEFAULT, CW_USEDEFAULT,
3044 hwnd, 0, 0, 0);
3046 /* Send NM_TOOLTIPSCREATED notification */
3047 if (infoPtr->hwndToolTip) {
3048 NMTOOLTIPSCREATED nmttc;
3050 nmttc.hdr.hwndFrom = hwnd;
3051 nmttc.hdr.idFrom = GetWindowLongPtrW(hwnd, GWLP_ID);
3052 nmttc.hdr.code = NM_TOOLTIPSCREATED;
3053 nmttc.hwndToolTips = infoPtr->hwndToolTip;
3055 SendMessageW (infoPtr->hwndNotify, WM_NOTIFY,
3056 (WPARAM)GetWindowLongPtrW(hwnd, GWLP_ID), (LPARAM)&nmttc);
3060 OpenThemeData (infoPtr->hwnd, themeClass);
3063 * We need to get text information so we need a DC and we need to select
3064 * a font.
3066 hdc = GetDC(hwnd);
3067 hOldFont = SelectObject (hdc, GetStockObject (SYSTEM_FONT));
3069 /* Use the system font to determine the initial height of a tab. */
3070 GetTextMetricsW(hdc, &fontMetrics);
3073 * Make sure there is enough space for the letters + growing the
3074 * selected item + extra space for the selected item.
3076 infoPtr->tabHeight = fontMetrics.tmHeight + SELECTED_TAB_OFFSET +
3077 ((dwStyle & TCS_BUTTONS) ? 2 : 1) *
3078 infoPtr->uVItemPadding;
3080 /* Initialize the width of a tab. */
3081 if (dwStyle & TCS_FIXEDWIDTH)
3082 infoPtr->tabWidth = DEFAULT_TAB_WIDTH_FIXED;
3084 infoPtr->tabMinWidth = -1;
3086 TRACE("tabH=%d, tabW=%d\n", infoPtr->tabHeight, infoPtr->tabWidth);
3088 SelectObject (hdc, hOldFont);
3089 ReleaseDC(hwnd, hdc);
3091 return 0;
3094 static LRESULT
3095 TAB_Destroy (TAB_INFO *infoPtr)
3097 UINT iItem;
3099 if (!infoPtr)
3100 return 0;
3102 SetWindowLongPtrW(infoPtr->hwnd, 0, 0);
3104 if (infoPtr->items) {
3105 for (iItem = 0; iItem < infoPtr->uNumItem; iItem++) {
3106 if (TAB_GetItem(infoPtr, iItem)->pszText)
3107 Free (TAB_GetItem(infoPtr, iItem)->pszText);
3109 Free (infoPtr->items);
3112 if (infoPtr->hwndToolTip)
3113 DestroyWindow (infoPtr->hwndToolTip);
3115 if (infoPtr->hwndUpDown)
3116 DestroyWindow(infoPtr->hwndUpDown);
3118 if (infoPtr->iHotTracked >= 0)
3119 KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
3121 CloseThemeData (GetWindowTheme (infoPtr->hwnd));
3123 Free (infoPtr);
3124 return 0;
3127 /* update theme after a WM_THEMECHANGED message */
3128 static LRESULT theme_changed (TAB_INFO* infoPtr)
3130 HTHEME theme = GetWindowTheme (infoPtr->hwnd);
3131 CloseThemeData (theme);
3132 OpenThemeData (infoPtr->hwnd, themeClass);
3133 return 0;
3136 static LRESULT TAB_NCCalcSize(HWND hwnd, WPARAM wParam, LPARAM lParam)
3138 if (!wParam)
3139 return 0;
3140 return WVR_ALIGNTOP;
3143 static inline LRESULT
3144 TAB_SetItemExtra (TAB_INFO *infoPtr, INT cbInfo)
3146 if (!infoPtr || cbInfo <= 0)
3147 return FALSE;
3149 if (infoPtr->uNumItem)
3151 /* FIXME: MSDN says this is not allowed, but this hasn't been verified */
3152 return FALSE;
3155 infoPtr->cbInfo = cbInfo;
3156 return TRUE;
3159 static LRESULT WINAPI
3160 TAB_WindowProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
3162 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
3164 TRACE("hwnd=%p msg=%x wParam=%x lParam=%lx\n", hwnd, uMsg, wParam, lParam);
3165 if (!infoPtr && (uMsg != WM_CREATE))
3166 return DefWindowProcW (hwnd, uMsg, wParam, lParam);
3168 switch (uMsg)
3170 case TCM_GETIMAGELIST:
3171 return TAB_GetImageList (infoPtr);
3173 case TCM_SETIMAGELIST:
3174 return TAB_SetImageList (infoPtr, (HIMAGELIST)lParam);
3176 case TCM_GETITEMCOUNT:
3177 return TAB_GetItemCount (infoPtr);
3179 case TCM_GETITEMA:
3180 case TCM_GETITEMW:
3181 return TAB_GetItemT (infoPtr, (INT)wParam, (LPTCITEMW)lParam, uMsg == TCM_GETITEMW);
3183 case TCM_SETITEMA:
3184 case TCM_SETITEMW:
3185 return TAB_SetItemT (infoPtr, (INT)wParam, (LPTCITEMW)lParam, uMsg == TCM_SETITEMW);
3187 case TCM_DELETEITEM:
3188 return TAB_DeleteItem (infoPtr, (INT)wParam);
3190 case TCM_DELETEALLITEMS:
3191 return TAB_DeleteAllItems (infoPtr);
3193 case TCM_GETITEMRECT:
3194 return TAB_GetItemRect (infoPtr, wParam, lParam);
3196 case TCM_GETCURSEL:
3197 return TAB_GetCurSel (infoPtr);
3199 case TCM_HITTEST:
3200 return TAB_HitTest (infoPtr, (LPTCHITTESTINFO)lParam);
3202 case TCM_SETCURSEL:
3203 return TAB_SetCurSel (infoPtr, (INT)wParam);
3205 case TCM_INSERTITEMA:
3206 case TCM_INSERTITEMW:
3207 return TAB_InsertItemT (infoPtr, wParam, lParam, uMsg == TCM_INSERTITEMW);
3209 case TCM_SETITEMEXTRA:
3210 return TAB_SetItemExtra (infoPtr, (int)wParam);
3212 case TCM_ADJUSTRECT:
3213 return TAB_AdjustRect (infoPtr, (BOOL)wParam, (LPRECT)lParam);
3215 case TCM_SETITEMSIZE:
3216 return TAB_SetItemSize (infoPtr, lParam);
3218 case TCM_REMOVEIMAGE:
3219 FIXME("Unimplemented msg TCM_REMOVEIMAGE\n");
3220 return 0;
3222 case TCM_SETPADDING:
3223 return TAB_SetPadding (infoPtr, lParam);
3225 case TCM_GETROWCOUNT:
3226 return TAB_GetRowCount(infoPtr);
3228 case TCM_GETUNICODEFORMAT:
3229 return TAB_GetUnicodeFormat (infoPtr);
3231 case TCM_SETUNICODEFORMAT:
3232 return TAB_SetUnicodeFormat (infoPtr, (BOOL)wParam);
3234 case TCM_HIGHLIGHTITEM:
3235 return TAB_HighlightItem (infoPtr, (INT)wParam, (BOOL)LOWORD(lParam));
3237 case TCM_GETTOOLTIPS:
3238 return TAB_GetToolTips (infoPtr);
3240 case TCM_SETTOOLTIPS:
3241 return TAB_SetToolTips (infoPtr, (HWND)wParam);
3243 case TCM_GETCURFOCUS:
3244 return TAB_GetCurFocus (infoPtr);
3246 case TCM_SETCURFOCUS:
3247 return TAB_SetCurFocus (infoPtr, (INT)wParam);
3249 case TCM_SETMINTABWIDTH:
3250 return TAB_SetMinTabWidth(infoPtr, (INT)lParam);
3252 case TCM_DESELECTALL:
3253 FIXME("Unimplemented msg TCM_DESELECTALL\n");
3254 return 0;
3256 case TCM_GETEXTENDEDSTYLE:
3257 FIXME("Unimplemented msg TCM_GETEXTENDEDSTYLE\n");
3258 return 0;
3260 case TCM_SETEXTENDEDSTYLE:
3261 FIXME("Unimplemented msg TCM_SETEXTENDEDSTYLE\n");
3262 return 0;
3264 case WM_GETFONT:
3265 return TAB_GetFont (infoPtr);
3267 case WM_SETFONT:
3268 return TAB_SetFont (infoPtr, (HFONT)wParam);
3270 case WM_CREATE:
3271 return TAB_Create (hwnd, wParam, lParam);
3273 case WM_NCDESTROY:
3274 return TAB_Destroy (infoPtr);
3276 case WM_GETDLGCODE:
3277 return DLGC_WANTARROWS | DLGC_WANTCHARS;
3279 case WM_LBUTTONDOWN:
3280 return TAB_LButtonDown (infoPtr, wParam, lParam);
3282 case WM_LBUTTONUP:
3283 return TAB_LButtonUp (infoPtr);
3285 case WM_NOTIFY:
3286 return SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, wParam, lParam);
3288 case WM_RBUTTONDOWN:
3289 return TAB_RButtonDown (infoPtr);
3291 case WM_MOUSEMOVE:
3292 return TAB_MouseMove (infoPtr, wParam, lParam);
3294 case WM_PRINTCLIENT:
3295 case WM_PAINT:
3296 return TAB_Paint (infoPtr, (HDC)wParam);
3298 case WM_SIZE:
3299 return TAB_Size (infoPtr);
3301 case WM_SETREDRAW:
3302 return TAB_SetRedraw (infoPtr, (BOOL)wParam);
3304 case WM_HSCROLL:
3305 return TAB_OnHScroll(infoPtr, (int)LOWORD(wParam), (int)HIWORD(wParam), (HWND)lParam);
3307 case WM_STYLECHANGED:
3308 TAB_SetItemBounds (infoPtr);
3309 InvalidateRect(hwnd, NULL, TRUE);
3310 return 0;
3312 case WM_SYSCOLORCHANGE:
3313 COMCTL32_RefreshSysColors();
3314 return 0;
3316 case WM_THEMECHANGED:
3317 return theme_changed (infoPtr);
3319 case WM_KILLFOCUS:
3320 case WM_SETFOCUS:
3321 TAB_FocusChanging(infoPtr);
3322 break; /* Don't disturb normal focus behavior */
3324 case WM_KEYUP:
3325 return TAB_KeyUp(infoPtr, wParam);
3326 case WM_NCHITTEST:
3327 return TAB_NCHitTest(infoPtr, lParam);
3329 case WM_NCCALCSIZE:
3330 return TAB_NCCalcSize(hwnd, wParam, lParam);
3332 default:
3333 if (uMsg >= WM_USER && uMsg < WM_APP)
3334 WARN("unknown msg %04x wp=%08x lp=%08lx\n",
3335 uMsg, wParam, lParam);
3336 break;
3338 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
3342 void
3343 TAB_Register (void)
3345 WNDCLASSW wndClass;
3347 ZeroMemory (&wndClass, sizeof(WNDCLASSW));
3348 wndClass.style = CS_GLOBALCLASS | CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
3349 wndClass.lpfnWndProc = TAB_WindowProc;
3350 wndClass.cbClsExtra = 0;
3351 wndClass.cbWndExtra = sizeof(TAB_INFO *);
3352 wndClass.hCursor = LoadCursorW (0, (LPWSTR)IDC_ARROW);
3353 wndClass.hbrBackground = (HBRUSH)(COLOR_BTNFACE+1);
3354 wndClass.lpszClassName = WC_TABCONTROLW;
3356 RegisterClassW (&wndClass);
3360 void
3361 TAB_Unregister (void)
3363 UnregisterClassW (WC_TABCONTROLW, NULL);