programs/start: use SEE_MASK_NO_CONSOLE flag as default.
[wine/dcerpc.git] / dlls / comctl32 / tab.c
blob830bc7e3b235e72269ac61132bdebd9b441c10b3
1 /*
2 * Tab control
4 * Copyright 1998 Anders Carlsson
5 * Copyright 1999 Alex Priem <alexp@sci.kun.nl>
6 * Copyright 1999 Francis Beaudet
7 * Copyright 2003 Vitaliy Margolen
9 * This library is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Lesser General Public
11 * License as published by the Free Software Foundation; either
12 * version 2.1 of the License, or (at your option) any later version.
14 * This library is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Lesser General Public License for more details.
19 * You should have received a copy of the GNU Lesser General Public
20 * License along with this library; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
23 * NOTES
25 * This code was audited for completeness against the documented features
26 * of Comctl32.dll version 6.0 on May. 20, 2005, by James Hawkins.
28 * Unless otherwise noted, we believe this code to be complete, as per
29 * the specification mentioned above.
30 * If you discover missing features, or bugs, please note them below.
32 * TODO:
34 * Styles:
35 * TCS_MULTISELECT
36 * TCS_RIGHT
37 * TCS_RIGHTJUSTIFY
38 * TCS_SCROLLOPPOSITE
39 * TCS_SINGLELINE
40 * TCIF_RTLREADING
42 * Extended Styles:
43 * TCS_EX_FLATSEPARATORS
44 * TCS_EX_REGISTERDROP
46 * States:
47 * TCIS_BUTTONPRESSED
49 * Notifications:
50 * NM_RELEASEDCAPTURE
51 * TCN_FOCUSCHANGE
52 * TCN_GETOBJECT
53 * TCN_KEYDOWN
55 * Messages:
56 * TCM_REMOVEIMAGE
57 * TCM_DESELECTALL
58 * TCM_GETEXTENDEDSTYLE
59 * TCM_SETEXTENDEDSTYLE
61 * Macros:
62 * TabCtrl_AdjustRect
66 #include <stdarg.h>
67 #include <string.h>
69 #include "windef.h"
70 #include "winbase.h"
71 #include "wingdi.h"
72 #include "winuser.h"
73 #include "winnls.h"
74 #include "commctrl.h"
75 #include "comctl32.h"
76 #include "uxtheme.h"
77 #include "tmschema.h"
78 #include "wine/debug.h"
79 #include <math.h>
81 WINE_DEFAULT_DEBUG_CHANNEL(tab);
83 typedef struct
85 DWORD dwState;
86 LPWSTR pszText;
87 INT iImage;
88 RECT rect; /* bounding rectangle of the item relative to the
89 * leftmost item (the leftmost item, 0, would have a
90 * "left" member of 0 in this rectangle)
92 * additionally the top member holds the row number
93 * and bottom is unused and should be 0 */
94 BYTE extra[1]; /* Space for caller supplied info, variable size */
95 } TAB_ITEM;
97 /* The size of a tab item depends on how much extra data is requested */
98 #define TAB_ITEM_SIZE(infoPtr) (FIELD_OFFSET(TAB_ITEM, extra[(infoPtr)->cbInfo]))
100 typedef struct
102 HWND hwnd; /* Tab control window */
103 HWND hwndNotify; /* notification window (parent) */
104 UINT uNumItem; /* number of tab items */
105 UINT uNumRows; /* number of tab rows */
106 INT tabHeight; /* height of the tab row */
107 INT tabWidth; /* width of tabs */
108 INT tabMinWidth; /* minimum width of items */
109 USHORT uHItemPadding; /* amount of horizontal padding, in pixels */
110 USHORT uVItemPadding; /* amount of vertical padding, in pixels */
111 USHORT uHItemPadding_s; /* Set amount of horizontal padding, in pixels */
112 USHORT uVItemPadding_s; /* Set amount of vertical padding, in pixels */
113 HFONT hFont; /* handle to the current font */
114 HCURSOR hcurArrow; /* handle to the current cursor */
115 HIMAGELIST himl; /* handle to an image list (may be 0) */
116 HWND hwndToolTip; /* handle to tab's tooltip */
117 INT leftmostVisible; /* Used for scrolling, this member contains
118 * the index of the first visible item */
119 INT iSelected; /* the currently selected item */
120 INT iHotTracked; /* the highlighted item under the mouse */
121 INT uFocus; /* item which has the focus */
122 TAB_ITEM* items; /* pointer to an array of TAB_ITEM's */
123 BOOL DoRedraw; /* flag for redrawing when tab contents is changed*/
124 BOOL needsScrolling; /* TRUE if the size of the tabs is greater than
125 * the size of the control */
126 BOOL fHeightSet; /* was the height of the tabs explicitly set? */
127 BOOL bUnicode; /* Unicode control? */
128 HWND hwndUpDown; /* Updown control used for scrolling */
129 INT cbInfo; /* Number of bytes of caller supplied info per tab */
130 } TAB_INFO;
132 /******************************************************************************
133 * Positioning constants
135 #define SELECTED_TAB_OFFSET 2
136 #define ROUND_CORNER_SIZE 2
137 #define DISPLAY_AREA_PADDINGX 2
138 #define DISPLAY_AREA_PADDINGY 2
139 #define CONTROL_BORDER_SIZEX 2
140 #define CONTROL_BORDER_SIZEY 2
141 #define BUTTON_SPACINGX 3
142 #define BUTTON_SPACINGY 3
143 #define FLAT_BTN_SPACINGX 8
144 #define DEFAULT_MIN_TAB_WIDTH 54
145 #define DEFAULT_TAB_WIDTH_FIXED 96
146 #define DEFAULT_PADDING_X 6
147 #define EXTRA_ICON_PADDING 3
149 #define TAB_GetInfoPtr(hwnd) ((TAB_INFO *)GetWindowLongPtrW(hwnd,0))
150 /* Since items are variable sized, cannot directly access them */
151 #define TAB_GetItem(info,i) \
152 ((TAB_ITEM*)((LPBYTE)info->items + (i) * TAB_ITEM_SIZE(info)))
154 #define GET_DEFAULT_MIN_TAB_WIDTH(infoPtr) (DEFAULT_MIN_TAB_WIDTH - (DEFAULT_PADDING_X - (infoPtr)->uHItemPadding) * 2)
156 /******************************************************************************
157 * Hot-tracking timer constants
159 #define TAB_HOTTRACK_TIMER 1
160 #define TAB_HOTTRACK_TIMER_INTERVAL 100 /* milliseconds */
162 static const WCHAR themeClass[] = { 'T','a','b',0 };
164 /******************************************************************************
165 * Prototypes
167 static void TAB_InvalidateTabArea(const TAB_INFO *);
168 static void TAB_EnsureSelectionVisible(TAB_INFO *);
169 static void TAB_DrawItemInterior(const TAB_INFO *, HDC, INT, RECT*);
171 static BOOL
172 TAB_SendSimpleNotify (const TAB_INFO *infoPtr, UINT code)
174 NMHDR nmhdr;
176 nmhdr.hwndFrom = infoPtr->hwnd;
177 nmhdr.idFrom = GetWindowLongPtrW(infoPtr->hwnd, GWLP_ID);
178 nmhdr.code = code;
180 return (BOOL) SendMessageW (infoPtr->hwndNotify, WM_NOTIFY,
181 (WPARAM) nmhdr.idFrom, (LPARAM) &nmhdr);
184 static void
185 TAB_RelayEvent (HWND hwndTip, HWND hwndMsg, UINT uMsg,
186 WPARAM wParam, LPARAM lParam)
188 MSG msg;
190 msg.hwnd = hwndMsg;
191 msg.message = uMsg;
192 msg.wParam = wParam;
193 msg.lParam = lParam;
194 msg.time = GetMessageTime ();
195 msg.pt.x = (short)LOWORD(GetMessagePos ());
196 msg.pt.y = (short)HIWORD(GetMessagePos ());
198 SendMessageW (hwndTip, TTM_RELAYEVENT, 0, (LPARAM)&msg);
201 static void
202 TAB_DumpItemExternalT(const TCITEMW *pti, UINT iItem, BOOL isW)
204 if (TRACE_ON(tab)) {
205 TRACE("external tab %d, mask=0x%08x, dwState=0x%08x, dwStateMask=0x%08x, cchTextMax=0x%08x\n",
206 iItem, pti->mask, pti->dwState, pti->dwStateMask, pti->cchTextMax);
207 TRACE("external tab %d, iImage=%d, lParam=0x%08lx, pszTextW=%s\n",
208 iItem, pti->iImage, pti->lParam, isW ? debugstr_w(pti->pszText) : debugstr_a((LPSTR)pti->pszText));
212 static void
213 TAB_DumpItemInternal(const TAB_INFO *infoPtr, UINT iItem)
215 if (TRACE_ON(tab)) {
216 TAB_ITEM *ti;
218 ti = TAB_GetItem(infoPtr, iItem);
219 TRACE("tab %d, dwState=0x%08x, pszText=%s, iImage=%d\n",
220 iItem, ti->dwState, debugstr_w(ti->pszText), ti->iImage);
221 TRACE("tab %d, rect.left=%d, rect.top(row)=%d\n",
222 iItem, ti->rect.left, ti->rect.top);
226 /* RETURNS
227 * the index of the selected tab, or -1 if no tab is selected. */
228 static inline LRESULT TAB_GetCurSel (const TAB_INFO *infoPtr)
230 return infoPtr->iSelected;
233 /* RETURNS
234 * the index of the tab item that has the focus. */
235 static inline LRESULT
236 TAB_GetCurFocus (const TAB_INFO *infoPtr)
238 return infoPtr->uFocus;
241 static inline LRESULT TAB_GetToolTips (const TAB_INFO *infoPtr)
243 if (infoPtr == NULL) return 0;
244 return (LRESULT)infoPtr->hwndToolTip;
247 static inline LRESULT TAB_SetCurSel (TAB_INFO *infoPtr, INT iItem)
249 INT prevItem = infoPtr->iSelected;
251 if (iItem < 0)
252 infoPtr->iSelected=-1;
253 else if (iItem >= infoPtr->uNumItem)
254 return -1;
255 else {
256 if (infoPtr->iSelected != iItem) {
257 infoPtr->iSelected=iItem;
258 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=(%d,%d)-(%d,%d)\n",
417 itemIndex, infoPtr->tabHeight,
418 itemRect->left, itemRect->top, itemRect->right, itemRect->bottom);
420 /* Now, calculate the position of the item as if it were selected. */
421 if (selectedRect!=NULL)
423 CopyRect(selectedRect, itemRect);
425 /* The rectangle of a selected item is a bit wider. */
426 if(lStyle & TCS_VERTICAL)
427 InflateRect(selectedRect, 0, SELECTED_TAB_OFFSET);
428 else
429 InflateRect(selectedRect, SELECTED_TAB_OFFSET, 0);
431 /* If it also a bit higher. */
432 if ((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
434 selectedRect->left -= 2; /* the border is thicker on the right */
435 selectedRect->right += SELECTED_TAB_OFFSET;
437 else if (lStyle & TCS_VERTICAL)
439 selectedRect->left -= SELECTED_TAB_OFFSET;
440 selectedRect->right += 1;
442 else if (lStyle & TCS_BOTTOM)
444 selectedRect->bottom += SELECTED_TAB_OFFSET;
446 else /* not TCS_BOTTOM and not TCS_VERTICAL */
448 selectedRect->top -= SELECTED_TAB_OFFSET;
449 selectedRect->bottom -= 1;
453 /* Check for visibility */
454 if (lStyle & TCS_VERTICAL)
455 return (itemRect->top < clientRect.bottom) && (itemRect->bottom > clientRect.top);
456 else
457 return (itemRect->left < clientRect.right) && (itemRect->right > clientRect.left);
460 static inline BOOL
461 TAB_GetItemRect(const TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
463 return TAB_InternalGetItemRect(infoPtr, (INT)wParam, (LPRECT)lParam, (LPRECT)NULL);
466 /******************************************************************************
467 * TAB_KeyUp
469 * This method is called to handle keyboard input
471 static LRESULT TAB_KeyUp(TAB_INFO* infoPtr, WPARAM keyCode)
473 int newItem = -1;
475 switch (keyCode)
477 case VK_LEFT:
478 newItem = infoPtr->uFocus - 1;
479 break;
480 case VK_RIGHT:
481 newItem = infoPtr->uFocus + 1;
482 break;
486 * If we changed to a valid item, change the selection
488 if (newItem >= 0 &&
489 newItem < infoPtr->uNumItem &&
490 infoPtr->uFocus != newItem)
492 if (!TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGING))
494 infoPtr->iSelected = newItem;
495 infoPtr->uFocus = newItem;
496 TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGE);
498 TAB_EnsureSelectionVisible(infoPtr);
499 TAB_InvalidateTabArea(infoPtr);
503 return 0;
506 /******************************************************************************
507 * TAB_FocusChanging
509 * This method is called whenever the focus goes in or out of this control
510 * it is used to update the visual state of the control.
512 static void TAB_FocusChanging(const TAB_INFO *infoPtr)
514 RECT selectedRect;
515 BOOL isVisible;
518 * Get the rectangle for the item.
520 isVisible = TAB_InternalGetItemRect(infoPtr,
521 infoPtr->uFocus,
522 NULL,
523 &selectedRect);
526 * If the rectangle is not completely invisible, invalidate that
527 * portion of the window.
529 if (isVisible)
531 TRACE("invalidate (%d,%d)-(%d,%d)\n",
532 selectedRect.left,selectedRect.top,
533 selectedRect.right,selectedRect.bottom);
534 InvalidateRect(infoPtr->hwnd, &selectedRect, TRUE);
538 static INT TAB_InternalHitTest (const TAB_INFO *infoPtr, POINT pt, UINT *flags)
540 RECT rect;
541 INT iCount;
543 for (iCount = 0; iCount < infoPtr->uNumItem; iCount++)
545 TAB_InternalGetItemRect(infoPtr, iCount, &rect, NULL);
547 if (PtInRect(&rect, pt))
549 *flags = TCHT_ONITEM;
550 return iCount;
554 *flags = TCHT_NOWHERE;
555 return -1;
558 static inline LRESULT
559 TAB_HitTest (const TAB_INFO *infoPtr, LPTCHITTESTINFO lptest)
561 return TAB_InternalHitTest (infoPtr, lptest->pt, &lptest->flags);
564 /******************************************************************************
565 * TAB_NCHitTest
567 * Napster v2b5 has a tab control for its main navigation which has a client
568 * area that covers the whole area of the dialog pages.
569 * That's why it receives all msgs for that area and the underlying dialog ctrls
570 * are dead.
571 * So I decided that we should handle WM_NCHITTEST here and return
572 * HTTRANSPARENT if we don't hit the tab control buttons.
573 * FIXME: WM_NCHITTEST handling correct ? Fix it if you know that Windows
574 * doesn't do it that way. Maybe depends on tab control styles ?
576 static inline LRESULT
577 TAB_NCHitTest (const TAB_INFO *infoPtr, LPARAM lParam)
579 POINT pt;
580 UINT dummyflag;
582 pt.x = (short)LOWORD(lParam);
583 pt.y = (short)HIWORD(lParam);
584 ScreenToClient(infoPtr->hwnd, &pt);
586 if (TAB_InternalHitTest(infoPtr, pt, &dummyflag) == -1)
587 return HTTRANSPARENT;
588 else
589 return HTCLIENT;
592 static LRESULT
593 TAB_LButtonDown (TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
595 POINT pt;
596 INT newItem;
597 UINT dummy;
599 if (infoPtr->hwndToolTip)
600 TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
601 WM_LBUTTONDOWN, wParam, lParam);
603 if (GetWindowLongW(infoPtr->hwnd, GWL_STYLE) & TCS_FOCUSONBUTTONDOWN ) {
604 SetFocus (infoPtr->hwnd);
607 if (infoPtr->hwndToolTip)
608 TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
609 WM_LBUTTONDOWN, wParam, lParam);
611 pt.x = (short)LOWORD(lParam);
612 pt.y = (short)HIWORD(lParam);
614 newItem = TAB_InternalHitTest (infoPtr, pt, &dummy);
616 TRACE("On Tab, item %d\n", newItem);
618 if (newItem != -1 && infoPtr->iSelected != newItem)
620 if (!TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGING))
622 infoPtr->iSelected = newItem;
623 infoPtr->uFocus = newItem;
624 TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGE);
626 TAB_EnsureSelectionVisible(infoPtr);
628 TAB_InvalidateTabArea(infoPtr);
631 return 0;
634 static inline LRESULT
635 TAB_LButtonUp (const TAB_INFO *infoPtr)
637 TAB_SendSimpleNotify(infoPtr, NM_CLICK);
639 return 0;
642 static inline LRESULT
643 TAB_RButtonDown (const TAB_INFO *infoPtr)
645 TAB_SendSimpleNotify(infoPtr, NM_RCLICK);
646 return 0;
649 /******************************************************************************
650 * TAB_DrawLoneItemInterior
652 * This calls TAB_DrawItemInterior. However, TAB_DrawItemInterior is normally
653 * called by TAB_DrawItem which is normally called by TAB_Refresh which sets
654 * up the device context and font. This routine does the same setup but
655 * only calls TAB_DrawItemInterior for the single specified item.
657 static void
658 TAB_DrawLoneItemInterior(const TAB_INFO* infoPtr, int iItem)
660 HDC hdc = GetDC(infoPtr->hwnd);
661 RECT r, rC;
663 /* Clip UpDown control to not draw over it */
664 if (infoPtr->needsScrolling)
666 GetWindowRect(infoPtr->hwnd, &rC);
667 GetWindowRect(infoPtr->hwndUpDown, &r);
668 ExcludeClipRect(hdc, r.left - rC.left, r.top - rC.top, r.right - rC.left, r.bottom - rC.top);
670 TAB_DrawItemInterior(infoPtr, hdc, iItem, NULL);
671 ReleaseDC(infoPtr->hwnd, hdc);
674 /* update a tab after hottracking - invalidate it or just redraw the interior,
675 * based on whether theming is used or not */
676 static inline void hottrack_refresh(const TAB_INFO *infoPtr, int tabIndex)
678 if (tabIndex == -1) return;
680 if (GetWindowTheme (infoPtr->hwnd))
682 RECT rect;
683 TAB_InternalGetItemRect(infoPtr, tabIndex, &rect, NULL);
684 InvalidateRect (infoPtr->hwnd, &rect, FALSE);
686 else
687 TAB_DrawLoneItemInterior(infoPtr, tabIndex);
690 /******************************************************************************
691 * TAB_HotTrackTimerProc
693 * When a mouse-move event causes a tab to be highlighted (hot-tracking), a
694 * timer is setup so we can check if the mouse is moved out of our window.
695 * (We don't get an event when the mouse leaves, the mouse-move events just
696 * stop being delivered to our window and just start being delivered to
697 * another window.) This function is called when the timer triggers so
698 * we can check if the mouse has left our window. If so, we un-highlight
699 * the hot-tracked tab.
701 static void CALLBACK
702 TAB_HotTrackTimerProc
704 HWND hwnd, /* handle of window for timer messages */
705 UINT uMsg, /* WM_TIMER message */
706 UINT_PTR idEvent, /* timer identifier */
707 DWORD dwTime /* current system time */
710 TAB_INFO* infoPtr = TAB_GetInfoPtr(hwnd);
712 if (infoPtr != NULL && infoPtr->iHotTracked >= 0)
714 POINT pt;
717 ** If we can't get the cursor position, or if the cursor is outside our
718 ** window, we un-highlight the hot-tracked tab. Note that the cursor is
719 ** "outside" even if it is within our bounding rect if another window
720 ** overlaps. Note also that the case where the cursor stayed within our
721 ** window but has moved off the hot-tracked tab will be handled by the
722 ** WM_MOUSEMOVE event.
724 if (!GetCursorPos(&pt) || WindowFromPoint(pt) != hwnd)
726 /* Redraw iHotTracked to look normal */
727 INT iRedraw = infoPtr->iHotTracked;
728 infoPtr->iHotTracked = -1;
729 hottrack_refresh (infoPtr, iRedraw);
731 /* Kill this timer */
732 KillTimer(hwnd, TAB_HOTTRACK_TIMER);
737 /******************************************************************************
738 * TAB_RecalcHotTrack
740 * If a tab control has the TCS_HOTTRACK style, then the tab under the mouse
741 * should be highlighted. This function determines which tab in a tab control,
742 * if any, is under the mouse and records that information. The caller may
743 * supply output parameters to receive the item number of the tab item which
744 * was highlighted but isn't any longer and of the tab item which is now
745 * highlighted but wasn't previously. The caller can use this information to
746 * selectively redraw those tab items.
748 * If the caller has a mouse position, it can supply it through the pos
749 * parameter. For example, TAB_MouseMove does this. Otherwise, the caller
750 * supplies NULL and this function determines the current mouse position
751 * itself.
753 static void
754 TAB_RecalcHotTrack
756 TAB_INFO* infoPtr,
757 const LPARAM* pos,
758 int* out_redrawLeave,
759 int* out_redrawEnter
762 int item = -1;
765 if (out_redrawLeave != NULL)
766 *out_redrawLeave = -1;
767 if (out_redrawEnter != NULL)
768 *out_redrawEnter = -1;
770 if ((GetWindowLongW(infoPtr->hwnd, GWL_STYLE) & TCS_HOTTRACK)
771 || GetWindowTheme (infoPtr->hwnd))
773 POINT pt;
774 UINT flags;
776 if (pos == NULL)
778 GetCursorPos(&pt);
779 ScreenToClient(infoPtr->hwnd, &pt);
781 else
783 pt.x = (short)LOWORD(*pos);
784 pt.y = (short)HIWORD(*pos);
787 item = TAB_InternalHitTest(infoPtr, pt, &flags);
790 if (item != infoPtr->iHotTracked)
792 if (infoPtr->iHotTracked >= 0)
794 /* Mark currently hot-tracked to be redrawn to look normal */
795 if (out_redrawLeave != NULL)
796 *out_redrawLeave = infoPtr->iHotTracked;
798 if (item < 0)
800 /* Kill timer which forces recheck of mouse pos */
801 KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
804 else
806 /* Start timer so we recheck mouse pos */
807 UINT timerID = SetTimer
809 infoPtr->hwnd,
810 TAB_HOTTRACK_TIMER,
811 TAB_HOTTRACK_TIMER_INTERVAL,
812 TAB_HotTrackTimerProc
815 if (timerID == 0)
816 return; /* Hot tracking not available */
819 infoPtr->iHotTracked = item;
821 if (item >= 0)
823 /* Mark new hot-tracked to be redrawn to look highlighted */
824 if (out_redrawEnter != NULL)
825 *out_redrawEnter = item;
830 /******************************************************************************
831 * TAB_MouseMove
833 * Handles the mouse-move event. Updates tooltips. Updates hot-tracking.
835 static LRESULT
836 TAB_MouseMove (TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
838 int redrawLeave;
839 int redrawEnter;
841 if (infoPtr->hwndToolTip)
842 TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
843 WM_LBUTTONDOWN, wParam, lParam);
845 /* Determine which tab to highlight. Redraw tabs which change highlight
846 ** status. */
847 TAB_RecalcHotTrack(infoPtr, &lParam, &redrawLeave, &redrawEnter);
849 hottrack_refresh (infoPtr, redrawLeave);
850 hottrack_refresh (infoPtr, redrawEnter);
852 return 0;
855 /******************************************************************************
856 * TAB_AdjustRect
858 * Calculates the tab control's display area given the window rectangle or
859 * the window rectangle given the requested display rectangle.
861 static LRESULT TAB_AdjustRect(const TAB_INFO *infoPtr, WPARAM fLarger, LPRECT prc)
863 DWORD lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
864 LONG *iRightBottom, *iLeftTop;
866 TRACE ("hwnd=%p fLarger=%ld (%d,%d)-(%d,%d)\n", infoPtr->hwnd, fLarger, prc->left, prc->top, prc->right, prc->bottom);
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: T %i, L %i, B %i, R %i\n", curr->rect.top,
1220 curr->rect.left, curr->rect.bottom, curr->rect.right);
1223 * The leftmost position of the next item is the rightmost position
1224 * of this one.
1226 if (lStyle & TCS_BUTTONS)
1228 curItemLeftPos = curr->rect.right + BUTTON_SPACINGX;
1229 if (lStyle & TCS_FLATBUTTONS)
1230 curItemLeftPos += FLAT_BTN_SPACINGX;
1232 else
1233 curItemLeftPos = curr->rect.right;
1236 if (!((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)))
1239 * Check if we need a scrolling control.
1241 infoPtr->needsScrolling = (curItemLeftPos + (2 * SELECTED_TAB_OFFSET) >
1242 clientRect.right);
1244 /* Don't need scrolling, then update infoPtr->leftmostVisible */
1245 if(!infoPtr->needsScrolling)
1246 infoPtr->leftmostVisible = 0;
1248 else
1251 * No scrolling in Multiline or Vertical styles.
1253 infoPtr->needsScrolling = FALSE;
1254 infoPtr->leftmostVisible = 0;
1256 TAB_SetupScrolling(infoPtr->hwnd, infoPtr, &clientRect);
1258 /* Set the number of rows */
1259 infoPtr->uNumRows = curItemRowCount;
1261 /* Arrange all tabs evenly if style says so */
1262 if (!(lStyle & TCS_RAGGEDRIGHT) &&
1263 ((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)) &&
1264 (infoPtr->uNumItem > 0) &&
1265 (infoPtr->uNumRows > 1))
1267 INT tabPerRow,remTab,iRow;
1268 UINT iItm;
1269 INT iCount=0;
1272 * Ok windows tries to even out the rows. place the same
1273 * number of tabs in each row. So lets give that a shot
1276 tabPerRow = infoPtr->uNumItem / (infoPtr->uNumRows);
1277 remTab = infoPtr->uNumItem % (infoPtr->uNumRows);
1279 for (iItm=0,iRow=0,iCount=0,curItemLeftPos=0;
1280 iItm<infoPtr->uNumItem;
1281 iItm++,iCount++)
1283 /* normalize the current rect */
1284 TAB_ITEM *curr = TAB_GetItem(infoPtr, iItm);
1286 /* shift the item to the left side of the clientRect */
1287 curr->rect.right -= curr->rect.left;
1288 curr->rect.left = 0;
1290 TRACE("r=%d, cl=%d, cl.r=%d, iCount=%d, iRow=%d, uNumRows=%d, remTab=%d, tabPerRow=%d\n",
1291 curr->rect.right, curItemLeftPos, clientRect.right,
1292 iCount, iRow, infoPtr->uNumRows, remTab, tabPerRow);
1294 /* if we have reached the maximum number of tabs on this row */
1295 /* move to the next row, reset our current item left position and */
1296 /* the count of items on this row */
1298 if (lStyle & TCS_VERTICAL) {
1299 /* Vert: Add the remaining tabs in the *last* remainder rows */
1300 if (iCount >= ((iRow>=(INT)infoPtr->uNumRows - remTab)?tabPerRow + 1:tabPerRow)) {
1301 iRow++;
1302 curItemLeftPos = 0;
1303 iCount = 0;
1305 } else {
1306 /* Horz: Add the remaining tabs in the *first* remainder rows */
1307 if (iCount >= ((iRow<remTab)?tabPerRow + 1:tabPerRow)) {
1308 iRow++;
1309 curItemLeftPos = 0;
1310 iCount = 0;
1314 /* shift the item to the right to place it as the next item in this row */
1315 curr->rect.left += curItemLeftPos;
1316 curr->rect.right += curItemLeftPos;
1317 curr->rect.top = iRow;
1318 if (lStyle & TCS_BUTTONS)
1320 curItemLeftPos = curr->rect.right + 1;
1321 if (lStyle & TCS_FLATBUTTONS)
1322 curItemLeftPos += FLAT_BTN_SPACINGX;
1324 else
1325 curItemLeftPos = curr->rect.right;
1327 TRACE("arranging <%s>, l,r=%d,%d, row=%d\n",
1328 debugstr_w(curr->pszText), curr->rect.left,
1329 curr->rect.right, curr->rect.top);
1333 * Justify the rows
1336 INT widthDiff, iIndexStart=0, iIndexEnd=0;
1337 INT remainder;
1338 INT iCount=0;
1340 while(iIndexStart < infoPtr->uNumItem)
1342 TAB_ITEM *start = TAB_GetItem(infoPtr, iIndexStart);
1345 * find the index of the row
1347 /* find the first item on the next row */
1348 for (iIndexEnd=iIndexStart;
1349 (iIndexEnd < infoPtr->uNumItem) &&
1350 (TAB_GetItem(infoPtr, iIndexEnd)->rect.top ==
1351 start->rect.top) ;
1352 iIndexEnd++)
1353 /* intentionally blank */;
1356 * we need to justify these tabs so they fill the whole given
1357 * client area
1360 /* find the amount of space remaining on this row */
1361 widthDiff = clientRect.right - (2 * SELECTED_TAB_OFFSET) -
1362 TAB_GetItem(infoPtr, iIndexEnd - 1)->rect.right;
1364 /* iCount is the number of tab items on this row */
1365 iCount = iIndexEnd - iIndexStart;
1367 if (iCount > 1)
1369 remainder = widthDiff % iCount;
1370 widthDiff = widthDiff / iCount;
1371 /* add widthDiff/iCount, or extra space/items on row, to each item on this row */
1372 for (iIndex=iIndexStart, iCount=0; iIndex < iIndexEnd; iIndex++, iCount++)
1374 TAB_ITEM *item = TAB_GetItem(infoPtr, iIndex);
1376 item->rect.left += iCount * widthDiff;
1377 item->rect.right += (iCount + 1) * widthDiff;
1379 TRACE("adjusting 1 <%s>, l,r=%d,%d\n",
1380 debugstr_w(item->pszText),
1381 item->rect.left, item->rect.right);
1384 TAB_GetItem(infoPtr, iIndex - 1)->rect.right += remainder;
1386 else /* we have only one item on this row, make it take up the entire row */
1388 start->rect.left = clientRect.left;
1389 start->rect.right = clientRect.right - 4;
1391 TRACE("adjusting 2 <%s>, l,r=%d,%d\n",
1392 debugstr_w(start->pszText),
1393 start->rect.left, start->rect.right);
1398 iIndexStart = iIndexEnd;
1403 /* if TCS_VERTICAL rotate the tabs so they are along the side of the clientRect */
1404 if(lStyle & TCS_VERTICAL)
1406 RECT rcOriginal;
1407 for(iIndex = 0; iIndex < infoPtr->uNumItem; iIndex++)
1409 rcItem = &TAB_GetItem(infoPtr, iIndex)->rect;
1411 rcOriginal = *rcItem;
1413 /* this is rotating the items by 90 degrees clockwise around the center of the control */
1414 rcItem->top = (rcOriginal.left - clientRect.left);
1415 rcItem->bottom = rcItem->top + (rcOriginal.right - rcOriginal.left);
1416 rcItem->left = rcOriginal.top;
1417 rcItem->right = rcOriginal.bottom;
1421 TAB_EnsureSelectionVisible(infoPtr);
1422 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
1424 /* Cleanup */
1425 SelectObject (hdc, hOldFont);
1426 ReleaseDC (infoPtr->hwnd, hdc);
1430 static void
1431 TAB_EraseTabInterior(const TAB_INFO *infoPtr, HDC hdc, INT iItem, RECT *drawRect)
1433 LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1434 HBRUSH hbr = CreateSolidBrush (comctl32_color.clrBtnFace);
1435 BOOL deleteBrush = TRUE;
1436 RECT rTemp = *drawRect;
1438 InflateRect(&rTemp, -2, -2);
1439 if (lStyle & TCS_BUTTONS)
1441 if (iItem == infoPtr->iSelected)
1443 /* Background color */
1444 if (!(lStyle & TCS_OWNERDRAWFIXED))
1446 DeleteObject(hbr);
1447 hbr = GetSysColorBrush(COLOR_SCROLLBAR);
1449 SetTextColor(hdc, comctl32_color.clr3dFace);
1450 SetBkColor(hdc, comctl32_color.clr3dHilight);
1452 /* if COLOR_WINDOW happens to be the same as COLOR_3DHILIGHT
1453 * we better use 0x55aa bitmap brush to make scrollbar's background
1454 * look different from the window background.
1456 if (comctl32_color.clr3dHilight == comctl32_color.clrWindow)
1457 hbr = COMCTL32_hPattern55AABrush;
1459 deleteBrush = FALSE;
1461 FillRect(hdc, &rTemp, hbr);
1463 else /* ! selected */
1465 if (lStyle & TCS_FLATBUTTONS)
1467 FillRect(hdc, drawRect, hbr);
1468 if (iItem == infoPtr->iHotTracked)
1469 DrawEdge(hdc, drawRect, EDGE_RAISED, BF_SOFT|BF_RECT);
1471 else
1472 FillRect(hdc, &rTemp, hbr);
1476 else /* !TCS_BUTTONS */
1478 if (!GetWindowTheme (infoPtr->hwnd))
1479 FillRect(hdc, &rTemp, hbr);
1482 /* Cleanup */
1483 if (deleteBrush) DeleteObject(hbr);
1486 /******************************************************************************
1487 * TAB_DrawItemInterior
1489 * This method is used to draw the interior (text and icon) of a single tab
1490 * into the tab control.
1492 static void
1493 TAB_DrawItemInterior(const TAB_INFO *infoPtr, HDC hdc, INT iItem, RECT *drawRect)
1495 LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1497 RECT localRect;
1499 HPEN htextPen;
1500 HPEN holdPen;
1501 INT oldBkMode;
1502 HFONT hOldFont;
1504 /* if (drawRect == NULL) */
1506 BOOL isVisible;
1507 RECT itemRect;
1508 RECT selectedRect;
1511 * Get the rectangle for the item.
1513 isVisible = TAB_InternalGetItemRect(infoPtr, iItem, &itemRect, &selectedRect);
1514 if (!isVisible)
1515 return;
1518 * Make sure drawRect points to something valid; simplifies code.
1520 drawRect = &localRect;
1523 * This logic copied from the part of TAB_DrawItem which draws
1524 * the tab background. It's important to keep it in sync. I
1525 * would have liked to avoid code duplication, but couldn't figure
1526 * out how without making spaghetti of TAB_DrawItem.
1528 if (iItem == infoPtr->iSelected)
1529 *drawRect = selectedRect;
1530 else
1531 *drawRect = itemRect;
1533 if (lStyle & TCS_BUTTONS)
1535 if (iItem == infoPtr->iSelected)
1537 drawRect->left += 4;
1538 drawRect->top += 4;
1539 drawRect->right -= 4;
1540 drawRect->bottom -= 1;
1542 else
1544 drawRect->left += 2;
1545 drawRect->top += 2;
1546 drawRect->right -= 2;
1547 drawRect->bottom -= 2;
1550 else
1552 if ((lStyle & TCS_VERTICAL) && (lStyle & TCS_BOTTOM))
1554 if (iItem != infoPtr->iSelected)
1556 drawRect->left += 2;
1557 drawRect->top += 2;
1558 drawRect->bottom -= 2;
1561 else if (lStyle & TCS_VERTICAL)
1563 if (iItem == infoPtr->iSelected)
1565 drawRect->right += 1;
1567 else
1569 drawRect->top += 2;
1570 drawRect->right -= 2;
1571 drawRect->bottom -= 2;
1574 else if (lStyle & TCS_BOTTOM)
1576 if (iItem == infoPtr->iSelected)
1578 drawRect->top -= 2;
1580 else
1582 InflateRect(drawRect, -2, -2);
1583 drawRect->bottom += 2;
1586 else
1588 if (iItem == infoPtr->iSelected)
1590 drawRect->bottom += 3;
1592 else
1594 drawRect->bottom -= 2;
1595 InflateRect(drawRect, -2, 0);
1600 TRACE("drawRect=(%d,%d)-(%d,%d)\n",
1601 drawRect->left, drawRect->top, drawRect->right, drawRect->bottom);
1603 /* Clear interior */
1604 TAB_EraseTabInterior (infoPtr, hdc, iItem, drawRect);
1606 /* Draw the focus rectangle */
1607 if (!(lStyle & TCS_FOCUSNEVER) &&
1608 (GetFocus() == infoPtr->hwnd) &&
1609 (iItem == infoPtr->uFocus) )
1611 RECT rFocus = *drawRect;
1612 InflateRect(&rFocus, -3, -3);
1613 if (lStyle & TCS_BOTTOM && !(lStyle & TCS_VERTICAL))
1614 rFocus.top -= 3;
1615 if (lStyle & TCS_BUTTONS)
1617 rFocus.left -= 3;
1618 rFocus.top -= 3;
1621 DrawFocusRect(hdc, &rFocus);
1625 * Text pen
1627 htextPen = CreatePen( PS_SOLID, 1, GetSysColor(COLOR_BTNTEXT) );
1628 holdPen = SelectObject(hdc, htextPen);
1629 hOldFont = SelectObject(hdc, infoPtr->hFont);
1632 * Setup for text output
1634 oldBkMode = SetBkMode(hdc, TRANSPARENT);
1635 if (!GetWindowTheme (infoPtr->hwnd) || (lStyle & TCS_BUTTONS))
1636 SetTextColor(hdc, (((lStyle & TCS_HOTTRACK) && (iItem == infoPtr->iHotTracked)
1637 && !(lStyle & TCS_FLATBUTTONS))
1638 | (TAB_GetItem(infoPtr, iItem)->dwState & TCIS_HIGHLIGHTED)) ?
1639 comctl32_color.clrHighlight : comctl32_color.clrBtnText);
1642 * if owner draw, tell the owner to draw
1644 if ((lStyle & TCS_OWNERDRAWFIXED) && GetParent(infoPtr->hwnd))
1646 DRAWITEMSTRUCT dis;
1647 UINT id;
1649 drawRect->top += 2;
1650 drawRect->right -= 1;
1651 if ( iItem == infoPtr->iSelected )
1653 drawRect->right -= 1;
1654 drawRect->left += 1;
1658 * get the control id
1660 id = (UINT)GetWindowLongPtrW( infoPtr->hwnd, GWLP_ID );
1663 * put together the DRAWITEMSTRUCT
1665 dis.CtlType = ODT_TAB;
1666 dis.CtlID = id;
1667 dis.itemID = iItem;
1668 dis.itemAction = ODA_DRAWENTIRE;
1669 dis.itemState = 0;
1670 if ( iItem == infoPtr->iSelected )
1671 dis.itemState |= ODS_SELECTED;
1672 if (infoPtr->uFocus == iItem)
1673 dis.itemState |= ODS_FOCUS;
1674 dis.hwndItem = infoPtr->hwnd;
1675 dis.hDC = hdc;
1676 CopyRect(&dis.rcItem,drawRect);
1677 dis.itemData = (ULONG_PTR)TAB_GetItem(infoPtr, iItem)->extra;
1680 * send the draw message
1682 SendMessageW( infoPtr->hwndNotify, WM_DRAWITEM, (WPARAM)id, (LPARAM)&dis );
1684 else
1686 TAB_ITEM *item = TAB_GetItem(infoPtr, iItem);
1687 RECT rcTemp;
1688 RECT rcImage;
1690 /* used to center the icon and text in the tab */
1691 RECT rcText;
1692 INT center_offset_h, center_offset_v;
1694 /* set rcImage to drawRect, we will use top & left in our ImageList_Draw call */
1695 rcImage = *drawRect;
1697 rcTemp = *drawRect;
1699 rcText.left = rcText.top = rcText.right = rcText.bottom = 0;
1701 /* get the rectangle that the text fits in */
1702 if (item->pszText)
1704 DrawTextW(hdc, item->pszText, -1, &rcText, DT_CALCRECT);
1707 * If not owner draw, then do the drawing ourselves.
1709 * Draw the icon.
1711 if (infoPtr->himl && item->iImage != -1)
1713 INT cx;
1714 INT cy;
1716 ImageList_GetIconSize(infoPtr->himl, &cx, &cy);
1718 if(lStyle & TCS_VERTICAL)
1720 center_offset_h = ((drawRect->bottom - drawRect->top) - (cy + infoPtr->uHItemPadding + (rcText.right - rcText.left))) / 2;
1721 center_offset_v = (drawRect->left + (drawRect->right - drawRect->left) - cx) / 2;
1723 else
1725 center_offset_h = ((drawRect->right - drawRect->left) - (cx + infoPtr->uHItemPadding + (rcText.right - rcText.left))) / 2;
1726 center_offset_v = (drawRect->top + (drawRect->bottom - drawRect->top) - cy) / 2;
1729 /* if an item is selected, the icon is shifted up instead of down */
1730 if (iItem == infoPtr->iSelected)
1731 center_offset_v -= infoPtr->uVItemPadding / 2;
1732 else
1733 center_offset_v += infoPtr->uVItemPadding / 2;
1735 if (lStyle & TCS_FIXEDWIDTH && lStyle & (TCS_FORCELABELLEFT | TCS_FORCEICONLEFT))
1736 center_offset_h = infoPtr->uHItemPadding;
1738 if (center_offset_h < 2)
1739 center_offset_h = 2;
1741 if (center_offset_v < 0)
1742 center_offset_v = 0;
1744 TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%d,%d)-(%d,%d), textlen=%d\n",
1745 debugstr_w(item->pszText), center_offset_h, center_offset_v,
1746 drawRect->left, drawRect->top, drawRect->right, drawRect->bottom,
1747 (rcText.right-rcText.left));
1749 if((lStyle & TCS_VERTICAL) && (lStyle & TCS_BOTTOM))
1751 rcImage.top = drawRect->top + center_offset_h;
1752 /* if tab is TCS_VERTICAL and TCS_BOTTOM, the text is drawn from the */
1753 /* right side of the tab, but the image still uses the left as its x position */
1754 /* this keeps the image always drawn off of the same side of the tab */
1755 rcImage.left = drawRect->right - cx - center_offset_v;
1756 drawRect->top += cy + infoPtr->uHItemPadding;
1758 else if(lStyle & TCS_VERTICAL)
1760 rcImage.top = drawRect->bottom - cy - center_offset_h;
1761 rcImage.left = drawRect->left + center_offset_v;
1762 drawRect->bottom -= cy + infoPtr->uHItemPadding;
1764 else /* normal style, whether TCS_BOTTOM or not */
1766 rcImage.left = drawRect->left + center_offset_h;
1767 rcImage.top = drawRect->top + center_offset_v;
1768 drawRect->left += cx + infoPtr->uHItemPadding;
1771 TRACE("drawing image=%d, left=%d, top=%d\n",
1772 item->iImage, rcImage.left, rcImage.top-1);
1773 ImageList_Draw
1775 infoPtr->himl,
1776 item->iImage,
1777 hdc,
1778 rcImage.left,
1779 rcImage.top,
1780 ILD_NORMAL
1784 /* Now position text */
1785 if (lStyle & TCS_FIXEDWIDTH && lStyle & TCS_FORCELABELLEFT)
1786 center_offset_h = infoPtr->uHItemPadding;
1787 else
1788 if(lStyle & TCS_VERTICAL)
1789 center_offset_h = ((drawRect->bottom - drawRect->top) - (rcText.right - rcText.left)) / 2;
1790 else
1791 center_offset_h = ((drawRect->right - drawRect->left) - (rcText.right - rcText.left)) / 2;
1793 if(lStyle & TCS_VERTICAL)
1795 if(lStyle & TCS_BOTTOM)
1796 drawRect->top+=center_offset_h;
1797 else
1798 drawRect->bottom-=center_offset_h;
1800 center_offset_v = ((drawRect->right - drawRect->left) - (rcText.bottom - rcText.top)) / 2;
1802 else
1804 drawRect->left += center_offset_h;
1805 center_offset_v = ((drawRect->bottom - drawRect->top) - (rcText.bottom - rcText.top)) / 2;
1808 /* if an item is selected, the text is shifted up instead of down */
1809 if (iItem == infoPtr->iSelected)
1810 center_offset_v -= infoPtr->uVItemPadding / 2;
1811 else
1812 center_offset_v += infoPtr->uVItemPadding / 2;
1814 if (center_offset_v < 0)
1815 center_offset_v = 0;
1817 if(lStyle & TCS_VERTICAL)
1818 drawRect->left += center_offset_v;
1819 else
1820 drawRect->top += center_offset_v;
1822 /* Draw the text */
1823 if(lStyle & TCS_VERTICAL) /* if we are vertical rotate the text and each character */
1825 static const WCHAR ArialW[] = { 'A','r','i','a','l',0 };
1826 LOGFONTW logfont;
1827 HFONT hFont = 0;
1828 INT nEscapement = 900;
1829 INT nOrientation = 900;
1831 if(lStyle & TCS_BOTTOM)
1833 nEscapement = -900;
1834 nOrientation = -900;
1837 /* to get a font with the escapement and orientation we are looking for, we need to */
1838 /* call CreateFontIndirectA, which requires us to set the values of the logfont we pass in */
1839 if (!GetObjectW((infoPtr->hFont) ?
1840 infoPtr->hFont : GetStockObject(SYSTEM_FONT),
1841 sizeof(LOGFONTW),&logfont))
1843 INT iPointSize = 9;
1845 lstrcpyW(logfont.lfFaceName, ArialW);
1846 logfont.lfHeight = -MulDiv(iPointSize, GetDeviceCaps(hdc, LOGPIXELSY),
1847 72);
1848 logfont.lfWeight = FW_NORMAL;
1849 logfont.lfItalic = 0;
1850 logfont.lfUnderline = 0;
1851 logfont.lfStrikeOut = 0;
1854 logfont.lfEscapement = nEscapement;
1855 logfont.lfOrientation = nOrientation;
1856 hFont = CreateFontIndirectW(&logfont);
1857 SelectObject(hdc, hFont);
1859 if (item->pszText)
1861 ExtTextOutW(hdc,
1862 (lStyle & TCS_BOTTOM) ? drawRect->right : drawRect->left,
1863 (!(lStyle & TCS_BOTTOM)) ? drawRect->bottom : drawRect->top,
1864 ETO_CLIPPED,
1865 drawRect,
1866 item->pszText,
1867 lstrlenW(item->pszText),
1871 DeleteObject(hFont);
1873 else
1875 TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%d,%d)-(%d,%d), textlen=%d\n",
1876 debugstr_w(item->pszText), center_offset_h, center_offset_v,
1877 drawRect->left, drawRect->top, drawRect->right, drawRect->bottom,
1878 (rcText.right-rcText.left));
1879 if (item->pszText)
1881 DrawTextW
1883 hdc,
1884 item->pszText,
1885 lstrlenW(item->pszText),
1886 drawRect,
1887 DT_LEFT | DT_SINGLELINE
1892 *drawRect = rcTemp; /* restore drawRect */
1896 * Cleanup
1898 SelectObject(hdc, hOldFont);
1899 SetBkMode(hdc, oldBkMode);
1900 SelectObject(hdc, holdPen);
1901 DeleteObject( htextPen );
1904 /******************************************************************************
1905 * TAB_DrawItem
1907 * This method is used to draw a single tab into the tab control.
1909 static void TAB_DrawItem(const TAB_INFO *infoPtr, HDC hdc, INT iItem)
1911 LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1912 RECT itemRect;
1913 RECT selectedRect;
1914 BOOL isVisible;
1915 RECT r, fillRect, r1;
1916 INT clRight = 0;
1917 INT clBottom = 0;
1918 COLORREF bkgnd, corner;
1919 HTHEME theme;
1922 * Get the rectangle for the item.
1924 isVisible = TAB_InternalGetItemRect(infoPtr,
1925 iItem,
1926 &itemRect,
1927 &selectedRect);
1929 if (isVisible)
1931 RECT rUD, rC;
1933 /* Clip UpDown control to not draw over it */
1934 if (infoPtr->needsScrolling)
1936 GetWindowRect(infoPtr->hwnd, &rC);
1937 GetWindowRect(infoPtr->hwndUpDown, &rUD);
1938 ExcludeClipRect(hdc, rUD.left - rC.left, rUD.top - rC.top, rUD.right - rC.left, rUD.bottom - rC.top);
1941 /* If you need to see what the control is doing,
1942 * then override these variables. They will change what
1943 * fill colors are used for filling the tabs, and the
1944 * corners when drawing the edge.
1946 bkgnd = comctl32_color.clrBtnFace;
1947 corner = comctl32_color.clrBtnFace;
1949 if (lStyle & TCS_BUTTONS)
1951 /* Get item rectangle */
1952 r = itemRect;
1954 /* Separators between flat buttons */
1955 if (lStyle & TCS_FLATBUTTONS)
1957 r1 = r;
1958 r1.right += (FLAT_BTN_SPACINGX -2);
1959 DrawEdge(hdc, &r1, EDGE_ETCHED, BF_RIGHT);
1962 if (iItem == infoPtr->iSelected)
1964 DrawEdge(hdc, &r, EDGE_SUNKEN, BF_SOFT|BF_RECT);
1966 OffsetRect(&r, 1, 1);
1968 else /* ! selected */
1970 if (!(lStyle & TCS_FLATBUTTONS))
1971 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_RECT);
1974 else /* !TCS_BUTTONS */
1976 /* We draw a rectangle of different sizes depending on the selection
1977 * state. */
1978 if (iItem == infoPtr->iSelected) {
1979 RECT rect;
1980 GetClientRect (infoPtr->hwnd, &rect);
1981 clRight = rect.right;
1982 clBottom = rect.bottom;
1983 r = selectedRect;
1985 else
1986 r = itemRect;
1989 * Erase the background. (Delay it but setup rectangle.)
1990 * This is necessary when drawing the selected item since it is larger
1991 * than the others, it might overlap with stuff already drawn by the
1992 * other tabs
1994 fillRect = r;
1996 /* Draw themed tabs - but only if they are at the top.
1997 * Windows draws even side or bottom tabs themed, with wacky results.
1998 * However, since in Wine apps may get themed that did not opt in via
1999 * a manifest avoid theming when we know the result will be wrong */
2000 if ((theme = GetWindowTheme (infoPtr->hwnd))
2001 && ((lStyle & (TCS_VERTICAL | TCS_BOTTOM)) == 0))
2003 static const int partIds[8] = {
2004 /* Normal item */
2005 TABP_TABITEM,
2006 TABP_TABITEMLEFTEDGE,
2007 TABP_TABITEMRIGHTEDGE,
2008 TABP_TABITEMBOTHEDGE,
2009 /* Selected tab */
2010 TABP_TOPTABITEM,
2011 TABP_TOPTABITEMLEFTEDGE,
2012 TABP_TOPTABITEMRIGHTEDGE,
2013 TABP_TOPTABITEMBOTHEDGE,
2015 int partIndex = 0;
2016 int stateId = TIS_NORMAL;
2018 /* selected and unselected tabs have different parts */
2019 if (iItem == infoPtr->iSelected)
2020 partIndex += 4;
2021 /* The part also differs on the position of a tab on a line.
2022 * "Visually" determining the position works well enough. */
2023 if(selectedRect.left == 0)
2024 partIndex += 1;
2025 if(selectedRect.right == clRight)
2026 partIndex += 2;
2028 if (iItem == infoPtr->iSelected)
2029 stateId = TIS_SELECTED;
2030 else if (iItem == infoPtr->iHotTracked)
2031 stateId = TIS_HOT;
2032 else if (iItem == infoPtr->uFocus)
2033 stateId = TIS_FOCUSED;
2035 /* Adjust rectangle for bottommost row */
2036 if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2037 r.bottom += 3;
2039 DrawThemeBackground (theme, hdc, partIds[partIndex], stateId, &r, NULL);
2040 GetThemeBackgroundContentRect (theme, hdc, partIds[partIndex], stateId, &r, &r);
2042 else if(lStyle & TCS_VERTICAL)
2044 /* These are for adjusting the drawing of a Selected tab */
2045 /* The initial values are for the normal case of non-Selected */
2046 int ZZ = 1; /* Do not strech if selected */
2047 if (iItem == infoPtr->iSelected) {
2048 ZZ = 0;
2050 /* if leftmost draw the line longer */
2051 if(selectedRect.top == 0)
2052 fillRect.top += CONTROL_BORDER_SIZEY;
2053 /* if rightmost draw the line longer */
2054 if(selectedRect.bottom == clBottom)
2055 fillRect.bottom -= CONTROL_BORDER_SIZEY;
2058 if (lStyle & TCS_BOTTOM)
2060 /* Adjust both rectangles to match native */
2061 r.left += (1-ZZ);
2063 TRACE("<right> item=%d, fill=(%d,%d)-(%d,%d), edge=(%d,%d)-(%d,%d)\n",
2064 iItem,
2065 fillRect.left,fillRect.top,fillRect.right,fillRect.bottom,
2066 r.left,r.top,r.right,r.bottom);
2068 /* Clear interior */
2069 SetBkColor(hdc, bkgnd);
2070 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2072 /* Draw rectangular edge around tab */
2073 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_RIGHT|BF_TOP|BF_BOTTOM);
2075 /* Now erase the top corner and draw diagonal edge */
2076 SetBkColor(hdc, corner);
2077 r1.left = r.right - ROUND_CORNER_SIZE - 1;
2078 r1.top = r.top;
2079 r1.right = r.right;
2080 r1.bottom = r1.top + ROUND_CORNER_SIZE;
2081 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2082 r1.right--;
2083 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPLEFT);
2085 /* Now erase the bottom corner and draw diagonal edge */
2086 r1.left = r.right - ROUND_CORNER_SIZE - 1;
2087 r1.bottom = r.bottom;
2088 r1.right = r.right;
2089 r1.top = r1.bottom - ROUND_CORNER_SIZE;
2090 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2091 r1.right--;
2092 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMLEFT);
2094 if ((iItem == infoPtr->iSelected) && (selectedRect.top == 0)) {
2095 r1 = r;
2096 r1.right = r1.left;
2097 r1.left--;
2098 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_TOP);
2102 else
2104 TRACE("<left> item=%d, fill=(%d,%d)-(%d,%d), edge=(%d,%d)-(%d,%d)\n",
2105 iItem,
2106 fillRect.left,fillRect.top,fillRect.right,fillRect.bottom,
2107 r.left,r.top,r.right,r.bottom);
2109 /* Clear interior */
2110 SetBkColor(hdc, bkgnd);
2111 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2113 /* Draw rectangular edge around tab */
2114 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_TOP|BF_BOTTOM);
2116 /* Now erase the top corner and draw diagonal edge */
2117 SetBkColor(hdc, corner);
2118 r1.left = r.left;
2119 r1.top = r.top;
2120 r1.right = r1.left + ROUND_CORNER_SIZE + 1;
2121 r1.bottom = r1.top + ROUND_CORNER_SIZE;
2122 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2123 r1.left++;
2124 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPRIGHT);
2126 /* Now erase the bottom corner and draw diagonal edge */
2127 r1.left = r.left;
2128 r1.bottom = r.bottom;
2129 r1.right = r1.left + ROUND_CORNER_SIZE + 1;
2130 r1.top = r1.bottom - ROUND_CORNER_SIZE;
2131 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2132 r1.left++;
2133 DrawEdge(hdc, &r1, EDGE_SUNKEN, BF_DIAGONAL_ENDTOPLEFT);
2136 else /* ! TCS_VERTICAL */
2138 /* These are for adjusting the drawing of a Selected tab */
2139 /* The initial values are for the normal case of non-Selected */
2140 if (iItem == infoPtr->iSelected) {
2141 /* if leftmost draw the line longer */
2142 if(selectedRect.left == 0)
2143 fillRect.left += CONTROL_BORDER_SIZEX;
2144 /* if rightmost draw the line longer */
2145 if(selectedRect.right == clRight)
2146 fillRect.right -= CONTROL_BORDER_SIZEX;
2149 if (lStyle & TCS_BOTTOM)
2151 /* Adjust both rectangles for topmost row */
2152 if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2154 fillRect.top -= 2;
2155 r.top -= 1;
2158 TRACE("<bottom> item=%d, fill=(%d,%d)-(%d,%d), edge=(%d,%d)-(%d,%d)\n",
2159 iItem,
2160 fillRect.left,fillRect.top,fillRect.right,fillRect.bottom,
2161 r.left,r.top,r.right,r.bottom);
2163 /* Clear interior */
2164 SetBkColor(hdc, bkgnd);
2165 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2167 /* Draw rectangular edge around tab */
2168 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_BOTTOM|BF_RIGHT);
2170 /* Now erase the righthand corner and draw diagonal edge */
2171 SetBkColor(hdc, corner);
2172 r1.left = r.right - ROUND_CORNER_SIZE;
2173 r1.bottom = r.bottom;
2174 r1.right = r.right;
2175 r1.top = r1.bottom - ROUND_CORNER_SIZE - 1;
2176 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2177 r1.bottom--;
2178 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMLEFT);
2180 /* Now erase the lefthand corner and draw diagonal edge */
2181 r1.left = r.left;
2182 r1.bottom = r.bottom;
2183 r1.right = r1.left + ROUND_CORNER_SIZE;
2184 r1.top = r1.bottom - ROUND_CORNER_SIZE - 1;
2185 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2186 r1.bottom--;
2187 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPLEFT);
2189 if (iItem == infoPtr->iSelected)
2191 r.top += 2;
2192 r.left += 1;
2193 if (selectedRect.left == 0)
2195 r1 = r;
2196 r1.bottom = r1.top;
2197 r1.top--;
2198 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_LEFT);
2203 else
2205 /* Adjust both rectangles for bottommost row */
2206 if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2208 fillRect.bottom += 3;
2209 r.bottom += 2;
2212 TRACE("<top> item=%d, fill=(%d,%d)-(%d,%d), edge=(%d,%d)-(%d,%d)\n",
2213 iItem,
2214 fillRect.left,fillRect.top,fillRect.right,fillRect.bottom,
2215 r.left,r.top,r.right,r.bottom);
2217 /* Clear interior */
2218 SetBkColor(hdc, bkgnd);
2219 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2221 /* Draw rectangular edge around tab */
2222 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_TOP|BF_RIGHT);
2224 /* Now erase the righthand corner and draw diagonal edge */
2225 SetBkColor(hdc, corner);
2226 r1.left = r.right - ROUND_CORNER_SIZE;
2227 r1.top = r.top;
2228 r1.right = r.right;
2229 r1.bottom = r1.top + ROUND_CORNER_SIZE + 1;
2230 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2231 r1.top++;
2232 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMRIGHT);
2234 /* Now erase the lefthand corner and draw diagonal edge */
2235 r1.left = r.left;
2236 r1.top = r.top;
2237 r1.right = r1.left + ROUND_CORNER_SIZE;
2238 r1.bottom = r1.top + ROUND_CORNER_SIZE + 1;
2239 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2240 r1.top++;
2241 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPRIGHT);
2246 TAB_DumpItemInternal(infoPtr, iItem);
2248 /* This modifies r to be the text rectangle. */
2249 TAB_DrawItemInterior(infoPtr, hdc, iItem, &r);
2253 /******************************************************************************
2254 * TAB_DrawBorder
2256 * This method is used to draw the raised border around the tab control
2257 * "content" area.
2259 static void TAB_DrawBorder(const TAB_INFO *infoPtr, HDC hdc)
2261 RECT rect;
2262 DWORD lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
2263 HTHEME theme = GetWindowTheme (infoPtr->hwnd);
2265 GetClientRect (infoPtr->hwnd, &rect);
2268 * Adjust for the style
2271 if (infoPtr->uNumItem)
2273 if ((lStyle & TCS_BOTTOM) && !(lStyle & TCS_VERTICAL))
2274 rect.bottom -= infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2275 else if((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
2276 rect.right -= infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2277 else if(lStyle & TCS_VERTICAL)
2278 rect.left += infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2279 else /* not TCS_VERTICAL and not TCS_BOTTOM */
2280 rect.top += infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2283 TRACE("border=(%d,%d)-(%d,%d)\n",
2284 rect.left, rect.top, rect.right, rect.bottom);
2286 if (theme)
2287 DrawThemeBackground (theme, hdc, TABP_PANE, 0, &rect, NULL);
2288 else
2289 DrawEdge(hdc, &rect, EDGE_RAISED, BF_SOFT|BF_RECT);
2292 /******************************************************************************
2293 * TAB_Refresh
2295 * This method repaints the tab control..
2297 static void TAB_Refresh (TAB_INFO *infoPtr, HDC hdc)
2299 HFONT hOldFont;
2300 INT i;
2302 if (!infoPtr->DoRedraw)
2303 return;
2305 hOldFont = SelectObject (hdc, infoPtr->hFont);
2307 if (GetWindowLongW(infoPtr->hwnd, GWL_STYLE) & TCS_BUTTONS)
2309 for (i = 0; i < infoPtr->uNumItem; i++)
2310 TAB_DrawItem (infoPtr, hdc, i);
2312 else
2314 /* Draw all the non selected item first */
2315 for (i = 0; i < infoPtr->uNumItem; i++)
2317 if (i != infoPtr->iSelected)
2318 TAB_DrawItem (infoPtr, hdc, i);
2321 /* Now, draw the border, draw it before the selected item
2322 * since the selected item overwrites part of the border. */
2323 TAB_DrawBorder (infoPtr, hdc);
2325 /* Then, draw the selected item */
2326 TAB_DrawItem (infoPtr, hdc, infoPtr->iSelected);
2328 /* If we haven't set the current focus yet, set it now.
2329 * Only happens when we first paint the tab controls */
2330 if (infoPtr->uFocus == -1)
2331 TAB_SetCurFocus(infoPtr, infoPtr->iSelected);
2334 SelectObject (hdc, hOldFont);
2337 static inline DWORD TAB_GetRowCount (const TAB_INFO *infoPtr)
2339 return infoPtr->uNumRows;
2342 static inline LRESULT TAB_SetRedraw (TAB_INFO *infoPtr, BOOL doRedraw)
2344 infoPtr->DoRedraw = doRedraw;
2345 return 0;
2348 /******************************************************************************
2349 * TAB_EnsureSelectionVisible
2351 * This method will make sure that the current selection is completely
2352 * visible by scrolling until it is.
2354 static void TAB_EnsureSelectionVisible(
2355 TAB_INFO* infoPtr)
2357 INT iSelected = infoPtr->iSelected;
2358 LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
2359 INT iOrigLeftmostVisible = infoPtr->leftmostVisible;
2361 /* set the items row to the bottommost row or topmost row depending on
2362 * style */
2363 if ((infoPtr->uNumRows > 1) && !(lStyle & TCS_BUTTONS))
2365 TAB_ITEM *selected = TAB_GetItem(infoPtr, iSelected);
2366 INT newselected;
2367 INT iTargetRow;
2369 if(lStyle & TCS_VERTICAL)
2370 newselected = selected->rect.left;
2371 else
2372 newselected = selected->rect.top;
2374 /* the target row is always (number of rows - 1)
2375 as row 0 is furthest from the clientRect */
2376 iTargetRow = infoPtr->uNumRows - 1;
2378 if (newselected != iTargetRow)
2380 UINT i;
2381 if(lStyle & TCS_VERTICAL)
2383 for (i=0; i < infoPtr->uNumItem; i++)
2385 /* move everything in the row of the selected item to the iTargetRow */
2386 TAB_ITEM *item = TAB_GetItem(infoPtr, i);
2388 if (item->rect.left == newselected )
2389 item->rect.left = iTargetRow;
2390 else
2392 if (item->rect.left > newselected)
2393 item->rect.left-=1;
2397 else
2399 for (i=0; i < infoPtr->uNumItem; i++)
2401 TAB_ITEM *item = TAB_GetItem(infoPtr, i);
2403 if (item->rect.top == newselected )
2404 item->rect.top = iTargetRow;
2405 else
2407 if (item->rect.top > newselected)
2408 item->rect.top-=1;
2412 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
2417 * Do the trivial cases first.
2419 if ( (!infoPtr->needsScrolling) ||
2420 (infoPtr->hwndUpDown==0) || (lStyle & TCS_VERTICAL))
2421 return;
2423 if (infoPtr->leftmostVisible >= iSelected)
2425 infoPtr->leftmostVisible = iSelected;
2427 else
2429 TAB_ITEM *selected = TAB_GetItem(infoPtr, iSelected);
2430 RECT r;
2431 INT width;
2432 UINT i;
2434 /* Calculate the part of the client area that is visible */
2435 GetClientRect(infoPtr->hwnd, &r);
2436 width = r.right;
2438 GetClientRect(infoPtr->hwndUpDown, &r);
2439 width -= r.right;
2441 if ((selected->rect.right -
2442 selected->rect.left) >= width )
2444 /* Special case: width of selected item is greater than visible
2445 * part of control.
2447 infoPtr->leftmostVisible = iSelected;
2449 else
2451 for (i = infoPtr->leftmostVisible; i < infoPtr->uNumItem; i++)
2453 if ((selected->rect.right - TAB_GetItem(infoPtr, i)->rect.left) < width)
2454 break;
2456 infoPtr->leftmostVisible = i;
2460 if (infoPtr->leftmostVisible != iOrigLeftmostVisible)
2461 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
2463 SendMessageW(infoPtr->hwndUpDown, UDM_SETPOS, 0,
2464 MAKELONG(infoPtr->leftmostVisible, 0));
2467 /******************************************************************************
2468 * TAB_InvalidateTabArea
2470 * This method will invalidate the portion of the control that contains the
2471 * tabs. It is called when the state of the control changes and needs
2472 * to be redisplayed
2474 static void TAB_InvalidateTabArea(const TAB_INFO *infoPtr)
2476 RECT clientRect, rInvalidate, rAdjClient;
2477 DWORD lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
2478 INT lastRow = infoPtr->uNumRows - 1;
2479 RECT rect;
2481 if (lastRow < 0) return;
2483 GetClientRect(infoPtr->hwnd, &clientRect);
2484 rInvalidate = clientRect;
2485 rAdjClient = clientRect;
2487 TAB_AdjustRect(infoPtr, 0, &rAdjClient);
2489 TAB_InternalGetItemRect(infoPtr, infoPtr->uNumItem-1 , &rect, NULL);
2490 if ((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
2492 rInvalidate.left = rAdjClient.right;
2493 if (infoPtr->uNumRows == 1)
2494 rInvalidate.bottom = clientRect.top + rect.bottom + 2 * SELECTED_TAB_OFFSET;
2496 else if(lStyle & TCS_VERTICAL)
2498 rInvalidate.right = rAdjClient.left;
2499 if (infoPtr->uNumRows == 1)
2500 rInvalidate.bottom = clientRect.top + rect.bottom + 2 * SELECTED_TAB_OFFSET;
2502 else if (lStyle & TCS_BOTTOM)
2504 rInvalidate.top = rAdjClient.bottom;
2505 if (infoPtr->uNumRows == 1)
2506 rInvalidate.right = clientRect.left + rect.right + 2 * SELECTED_TAB_OFFSET;
2508 else
2510 rInvalidate.bottom = rAdjClient.top;
2511 if (infoPtr->uNumRows == 1)
2512 rInvalidate.right = clientRect.left + rect.right + 2 * SELECTED_TAB_OFFSET;
2515 /* Punch out the updown control */
2516 if (infoPtr->needsScrolling && (rInvalidate.right > 0)) {
2517 RECT r;
2518 GetClientRect(infoPtr->hwndUpDown, &r);
2519 if (rInvalidate.right > clientRect.right - r.left)
2520 rInvalidate.right = rInvalidate.right - (r.right - r.left);
2521 else
2522 rInvalidate.right = clientRect.right - r.left;
2525 TRACE("invalidate (%d,%d)-(%d,%d)\n",
2526 rInvalidate.left, rInvalidate.top,
2527 rInvalidate.right, rInvalidate.bottom);
2529 InvalidateRect(infoPtr->hwnd, &rInvalidate, TRUE);
2532 static inline LRESULT TAB_Paint (TAB_INFO *infoPtr, HDC hdcPaint)
2534 HDC hdc;
2535 PAINTSTRUCT ps;
2537 if (hdcPaint)
2538 hdc = hdcPaint;
2539 else
2541 hdc = BeginPaint (infoPtr->hwnd, &ps);
2542 TRACE("erase %d, rect=(%d,%d)-(%d,%d)\n",
2543 ps.fErase,
2544 ps.rcPaint.left,ps.rcPaint.top,ps.rcPaint.right,ps.rcPaint.bottom);
2547 TAB_Refresh (infoPtr, hdc);
2549 if (!hdcPaint)
2550 EndPaint (infoPtr->hwnd, &ps);
2552 return 0;
2555 static LRESULT
2556 TAB_InsertItemT (TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam, BOOL bUnicode)
2558 TAB_ITEM *item;
2559 TCITEMW *pti;
2560 INT iItem;
2561 RECT rect;
2563 GetClientRect (infoPtr->hwnd, &rect);
2564 TRACE("Rect: %p T %i, L %i, B %i, R %i\n", infoPtr->hwnd,
2565 rect.top, rect.left, rect.bottom, rect.right);
2567 pti = (TCITEMW *)lParam;
2568 iItem = (INT)wParam;
2570 if (iItem < 0) return -1;
2571 if (iItem > infoPtr->uNumItem)
2572 iItem = infoPtr->uNumItem;
2574 TAB_DumpItemExternalT(pti, iItem, bUnicode);
2577 if (infoPtr->uNumItem == 0) {
2578 infoPtr->items = Alloc (TAB_ITEM_SIZE(infoPtr));
2579 infoPtr->uNumItem++;
2580 infoPtr->iSelected = 0;
2582 else {
2583 LPBYTE oldItems = (LPBYTE)infoPtr->items;
2585 infoPtr->uNumItem++;
2586 infoPtr->items = Alloc (TAB_ITEM_SIZE(infoPtr) * infoPtr->uNumItem);
2588 /* pre insert copy */
2589 if (iItem > 0) {
2590 memcpy (infoPtr->items, oldItems,
2591 iItem * TAB_ITEM_SIZE(infoPtr));
2594 /* post insert copy */
2595 if (iItem < infoPtr->uNumItem - 1) {
2596 memcpy (TAB_GetItem(infoPtr, iItem + 1),
2597 oldItems + iItem * TAB_ITEM_SIZE(infoPtr),
2598 (infoPtr->uNumItem - iItem - 1) * TAB_ITEM_SIZE(infoPtr));
2602 if (iItem <= infoPtr->iSelected)
2603 infoPtr->iSelected++;
2605 Free (oldItems);
2608 item = TAB_GetItem(infoPtr, iItem);
2610 item->pszText = NULL;
2612 if (pti->mask & TCIF_TEXT)
2614 if (bUnicode)
2615 Str_SetPtrW (&item->pszText, pti->pszText);
2616 else
2617 Str_SetPtrAtoW (&item->pszText, (LPSTR)pti->pszText);
2620 if (pti->mask & TCIF_IMAGE)
2621 item->iImage = pti->iImage;
2622 else
2623 item->iImage = -1;
2625 if (pti->mask & TCIF_PARAM)
2626 memcpy(item->extra, &pti->lParam, infoPtr->cbInfo);
2627 else
2628 memset(item->extra, 0, infoPtr->cbInfo);
2630 TAB_SetItemBounds(infoPtr);
2631 if (infoPtr->uNumItem > 1)
2632 TAB_InvalidateTabArea(infoPtr);
2633 else
2634 InvalidateRect(infoPtr->hwnd, NULL, TRUE);
2636 TRACE("[%p]: added item %d %s\n",
2637 infoPtr->hwnd, iItem, debugstr_w(item->pszText));
2639 return iItem;
2642 static LRESULT
2643 TAB_SetItemSize (TAB_INFO *infoPtr, LPARAM lParam)
2645 LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
2646 LONG lResult = 0;
2647 BOOL bNeedPaint = FALSE;
2649 lResult = MAKELONG(infoPtr->tabWidth, infoPtr->tabHeight);
2651 /* UNDOCUMENTED: If requested Width or Height is 0 this means that program wants to use auto size. */
2652 if (lStyle & TCS_FIXEDWIDTH && (infoPtr->tabWidth != (INT)LOWORD(lParam)))
2654 infoPtr->tabWidth = (INT)LOWORD(lParam);
2655 bNeedPaint = TRUE;
2658 if (infoPtr->tabHeight != (INT)HIWORD(lParam))
2660 if ((infoPtr->fHeightSet = ((INT)HIWORD(lParam) != 0)))
2661 infoPtr->tabHeight = (INT)HIWORD(lParam);
2663 bNeedPaint = TRUE;
2665 TRACE("was h=%d,w=%d, now h=%d,w=%d\n",
2666 HIWORD(lResult), LOWORD(lResult),
2667 infoPtr->tabHeight, infoPtr->tabWidth);
2669 if (bNeedPaint)
2671 TAB_SetItemBounds(infoPtr);
2672 RedrawWindow(infoPtr->hwnd, NULL, NULL, RDW_ERASE | RDW_INVALIDATE | RDW_UPDATENOW);
2675 return lResult;
2678 static inline LRESULT TAB_SetMinTabWidth (TAB_INFO *infoPtr, INT cx)
2680 INT oldcx = 0;
2682 TRACE("(%p,%d)\n", infoPtr, cx);
2684 oldcx = infoPtr->tabMinWidth;
2685 infoPtr->tabMinWidth = cx;
2686 TAB_SetItemBounds(infoPtr);
2687 return oldcx;
2690 static inline LRESULT
2691 TAB_HighlightItem (TAB_INFO *infoPtr, INT iItem, BOOL fHighlight)
2693 LPDWORD lpState;
2695 TRACE("(%p,%d,%s)\n", infoPtr, iItem, fHighlight ? "true" : "false");
2697 if (!infoPtr || iItem < 0 || iItem >= infoPtr->uNumItem)
2698 return FALSE;
2700 lpState = &TAB_GetItem(infoPtr, iItem)->dwState;
2702 if (fHighlight)
2703 *lpState |= TCIS_HIGHLIGHTED;
2704 else
2705 *lpState &= ~TCIS_HIGHLIGHTED;
2707 return TRUE;
2710 static LRESULT
2711 TAB_SetItemT (TAB_INFO *infoPtr, INT iItem, LPTCITEMW tabItem, BOOL bUnicode)
2713 TAB_ITEM *wineItem;
2715 TRACE("(%p,%d,%p,%s)\n", infoPtr, iItem, tabItem, bUnicode ? "true" : "false");
2717 if (iItem < 0 || iItem >= infoPtr->uNumItem)
2718 return FALSE;
2720 TAB_DumpItemExternalT(tabItem, iItem, bUnicode);
2722 wineItem = TAB_GetItem(infoPtr, iItem);
2724 if (tabItem->mask & TCIF_IMAGE)
2725 wineItem->iImage = tabItem->iImage;
2727 if (tabItem->mask & TCIF_PARAM)
2728 memcpy(wineItem->extra, &tabItem->lParam, infoPtr->cbInfo);
2730 if (tabItem->mask & TCIF_RTLREADING)
2731 FIXME("TCIF_RTLREADING\n");
2733 if (tabItem->mask & TCIF_STATE)
2734 wineItem->dwState = tabItem->dwState;
2736 if (tabItem->mask & TCIF_TEXT)
2738 Free(wineItem->pszText);
2739 wineItem->pszText = NULL;
2740 if (bUnicode)
2741 Str_SetPtrW(&wineItem->pszText, tabItem->pszText);
2742 else
2743 Str_SetPtrAtoW(&wineItem->pszText, (LPSTR)tabItem->pszText);
2746 /* Update and repaint tabs */
2747 TAB_SetItemBounds(infoPtr);
2748 TAB_InvalidateTabArea(infoPtr);
2750 return TRUE;
2753 static inline LRESULT TAB_GetItemCount (const TAB_INFO *infoPtr)
2755 return infoPtr->uNumItem;
2759 static LRESULT
2760 TAB_GetItemT (TAB_INFO *infoPtr, INT iItem, LPTCITEMW tabItem, BOOL bUnicode)
2762 TAB_ITEM *wineItem;
2764 TRACE("(%p,%d,%p,%s)\n", infoPtr, iItem, tabItem, bUnicode ? "true" : "false");
2766 if (iItem < 0 || iItem >= infoPtr->uNumItem)
2767 return FALSE;
2769 wineItem = TAB_GetItem(infoPtr, iItem);
2771 if (tabItem->mask & TCIF_IMAGE)
2772 tabItem->iImage = wineItem->iImage;
2774 if (tabItem->mask & TCIF_PARAM)
2775 memcpy(&tabItem->lParam, wineItem->extra, infoPtr->cbInfo);
2777 if (tabItem->mask & TCIF_RTLREADING)
2778 FIXME("TCIF_RTLREADING\n");
2780 if (tabItem->mask & TCIF_STATE)
2781 tabItem->dwState = wineItem->dwState;
2783 if (tabItem->mask & TCIF_TEXT)
2785 if (bUnicode)
2786 Str_GetPtrW (wineItem->pszText, tabItem->pszText, tabItem->cchTextMax);
2787 else
2788 Str_GetPtrWtoA (wineItem->pszText, (LPSTR)tabItem->pszText, tabItem->cchTextMax);
2791 TAB_DumpItemExternalT(tabItem, iItem, bUnicode);
2793 return TRUE;
2797 static LRESULT TAB_DeleteItem (TAB_INFO *infoPtr, INT iItem)
2799 BOOL bResult = FALSE;
2801 TRACE("(%p, %d)\n", infoPtr, iItem);
2803 if ((iItem >= 0) && (iItem < infoPtr->uNumItem))
2805 TAB_ITEM *item = TAB_GetItem(infoPtr, iItem);
2806 LPBYTE oldItems = (LPBYTE)infoPtr->items;
2808 TAB_InvalidateTabArea(infoPtr);
2809 Free(item->pszText);
2810 infoPtr->uNumItem--;
2812 if (!infoPtr->uNumItem)
2814 infoPtr->items = NULL;
2815 if (infoPtr->iHotTracked >= 0)
2817 KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
2818 infoPtr->iHotTracked = -1;
2821 else
2823 infoPtr->items = Alloc(TAB_ITEM_SIZE(infoPtr) * infoPtr->uNumItem);
2825 if (iItem > 0)
2826 memcpy(infoPtr->items, oldItems, iItem * TAB_ITEM_SIZE(infoPtr));
2828 if (iItem < infoPtr->uNumItem)
2829 memcpy(TAB_GetItem(infoPtr, iItem),
2830 oldItems + (iItem + 1) * TAB_ITEM_SIZE(infoPtr),
2831 (infoPtr->uNumItem - iItem) * TAB_ITEM_SIZE(infoPtr));
2833 if (iItem <= infoPtr->iHotTracked)
2835 /* When tabs move left/up, the hot track item may change */
2836 FIXME("Recalc hot track\n");
2839 Free(oldItems);
2841 /* Readjust the selected index */
2842 if ((iItem == infoPtr->iSelected) && (iItem > 0))
2843 infoPtr->iSelected--;
2845 if (iItem < infoPtr->iSelected)
2846 infoPtr->iSelected--;
2848 if (infoPtr->uNumItem == 0)
2849 infoPtr->iSelected = -1;
2851 /* Reposition and repaint tabs */
2852 TAB_SetItemBounds(infoPtr);
2854 bResult = TRUE;
2857 return bResult;
2860 static inline LRESULT TAB_DeleteAllItems (TAB_INFO *infoPtr)
2862 TRACE("(%p)\n", infoPtr);
2863 while (infoPtr->uNumItem)
2864 TAB_DeleteItem (infoPtr, 0);
2865 return TRUE;
2869 static inline LRESULT TAB_GetFont (const TAB_INFO *infoPtr)
2871 TRACE("(%p) returning %p\n", infoPtr, infoPtr->hFont);
2872 return (LRESULT)infoPtr->hFont;
2875 static inline LRESULT TAB_SetFont (TAB_INFO *infoPtr, HFONT hNewFont)
2877 TRACE("(%p,%p)\n", infoPtr, hNewFont);
2879 infoPtr->hFont = hNewFont;
2881 TAB_SetItemBounds(infoPtr);
2883 TAB_InvalidateTabArea(infoPtr);
2885 return 0;
2889 static inline LRESULT TAB_GetImageList (const TAB_INFO *infoPtr)
2891 TRACE("\n");
2892 return (LRESULT)infoPtr->himl;
2895 static inline LRESULT TAB_SetImageList (TAB_INFO *infoPtr, HIMAGELIST himlNew)
2897 HIMAGELIST himlPrev = infoPtr->himl;
2898 TRACE("\n");
2899 infoPtr->himl = himlNew;
2900 TAB_SetItemBounds(infoPtr);
2901 InvalidateRect(infoPtr->hwnd, NULL, TRUE);
2902 return (LRESULT)himlPrev;
2905 static inline LRESULT TAB_GetUnicodeFormat (const TAB_INFO *infoPtr)
2907 return infoPtr->bUnicode;
2910 static inline LRESULT TAB_SetUnicodeFormat (TAB_INFO *infoPtr, BOOL bUnicode)
2912 BOOL bTemp = infoPtr->bUnicode;
2914 infoPtr->bUnicode = bUnicode;
2916 return bTemp;
2919 static inline LRESULT TAB_Size (TAB_INFO *infoPtr)
2921 /* I'm not really sure what the following code was meant to do.
2922 This is what it is doing:
2923 When WM_SIZE is sent with SIZE_RESTORED, the control
2924 gets positioned in the top left corner.
2926 RECT parent_rect;
2927 HWND parent;
2928 UINT uPosFlags,cx,cy;
2930 uPosFlags=0;
2931 if (!wParam) {
2932 parent = GetParent (hwnd);
2933 GetClientRect(parent, &parent_rect);
2934 cx=LOWORD (lParam);
2935 cy=HIWORD (lParam);
2936 if (GetWindowLongW(hwnd, GWL_STYLE) & CCS_NORESIZE)
2937 uPosFlags |= (SWP_NOSIZE | SWP_NOMOVE);
2939 SetWindowPos (hwnd, 0, parent_rect.left, parent_rect.top,
2940 cx, cy, uPosFlags | SWP_NOZORDER);
2941 } else {
2942 FIXME("WM_SIZE flag %x %lx not handled\n", wParam, lParam);
2943 } */
2945 /* Recompute the size/position of the tabs. */
2946 TAB_SetItemBounds (infoPtr);
2948 /* Force a repaint of the control. */
2949 InvalidateRect(infoPtr->hwnd, NULL, TRUE);
2951 return 0;
2955 static LRESULT TAB_Create (HWND hwnd, WPARAM wParam, LPARAM lParam)
2957 TAB_INFO *infoPtr;
2958 TEXTMETRICW fontMetrics;
2959 HDC hdc;
2960 HFONT hOldFont;
2961 DWORD dwStyle;
2963 infoPtr = (TAB_INFO *)Alloc (sizeof(TAB_INFO));
2965 SetWindowLongPtrW(hwnd, 0, (DWORD_PTR)infoPtr);
2967 infoPtr->hwnd = hwnd;
2968 infoPtr->hwndNotify = ((LPCREATESTRUCTW)lParam)->hwndParent;
2969 infoPtr->uNumItem = 0;
2970 infoPtr->uNumRows = 0;
2971 infoPtr->uHItemPadding = 6;
2972 infoPtr->uVItemPadding = 3;
2973 infoPtr->uHItemPadding_s = 6;
2974 infoPtr->uVItemPadding_s = 3;
2975 infoPtr->hFont = 0;
2976 infoPtr->items = 0;
2977 infoPtr->hcurArrow = LoadCursorW (0, (LPWSTR)IDC_ARROW);
2978 infoPtr->iSelected = -1;
2979 infoPtr->iHotTracked = -1;
2980 infoPtr->uFocus = -1;
2981 infoPtr->hwndToolTip = 0;
2982 infoPtr->DoRedraw = TRUE;
2983 infoPtr->needsScrolling = FALSE;
2984 infoPtr->hwndUpDown = 0;
2985 infoPtr->leftmostVisible = 0;
2986 infoPtr->fHeightSet = FALSE;
2987 infoPtr->bUnicode = IsWindowUnicode (hwnd);
2988 infoPtr->cbInfo = sizeof(LPARAM);
2990 TRACE("Created tab control, hwnd [%p]\n", hwnd);
2992 /* The tab control always has the WS_CLIPSIBLINGS style. Even
2993 if you don't specify it in CreateWindow. This is necessary in
2994 order for paint to work correctly. This follows windows behaviour. */
2995 dwStyle = GetWindowLongW(hwnd, GWL_STYLE);
2996 SetWindowLongW(hwnd, GWL_STYLE, dwStyle|WS_CLIPSIBLINGS);
2998 if (dwStyle & TCS_TOOLTIPS) {
2999 /* Create tooltip control */
3000 infoPtr->hwndToolTip =
3001 CreateWindowExW (0, TOOLTIPS_CLASSW, NULL, WS_POPUP,
3002 CW_USEDEFAULT, CW_USEDEFAULT,
3003 CW_USEDEFAULT, CW_USEDEFAULT,
3004 hwnd, 0, 0, 0);
3006 /* Send NM_TOOLTIPSCREATED notification */
3007 if (infoPtr->hwndToolTip) {
3008 NMTOOLTIPSCREATED nmttc;
3010 nmttc.hdr.hwndFrom = hwnd;
3011 nmttc.hdr.idFrom = GetWindowLongPtrW(hwnd, GWLP_ID);
3012 nmttc.hdr.code = NM_TOOLTIPSCREATED;
3013 nmttc.hwndToolTips = infoPtr->hwndToolTip;
3015 SendMessageW (infoPtr->hwndNotify, WM_NOTIFY,
3016 (WPARAM)GetWindowLongPtrW(hwnd, GWLP_ID), (LPARAM)&nmttc);
3020 OpenThemeData (infoPtr->hwnd, themeClass);
3023 * We need to get text information so we need a DC and we need to select
3024 * a font.
3026 hdc = GetDC(hwnd);
3027 hOldFont = SelectObject (hdc, GetStockObject (SYSTEM_FONT));
3029 /* Use the system font to determine the initial height of a tab. */
3030 GetTextMetricsW(hdc, &fontMetrics);
3033 * Make sure there is enough space for the letters + growing the
3034 * selected item + extra space for the selected item.
3036 infoPtr->tabHeight = fontMetrics.tmHeight + SELECTED_TAB_OFFSET +
3037 ((dwStyle & TCS_BUTTONS) ? 2 : 1) *
3038 infoPtr->uVItemPadding;
3040 /* Initialize the width of a tab. */
3041 if (dwStyle & TCS_FIXEDWIDTH)
3042 infoPtr->tabWidth = DEFAULT_TAB_WIDTH_FIXED;
3044 infoPtr->tabMinWidth = -1;
3046 TRACE("tabH=%d, tabW=%d\n", infoPtr->tabHeight, infoPtr->tabWidth);
3048 SelectObject (hdc, hOldFont);
3049 ReleaseDC(hwnd, hdc);
3051 return 0;
3054 static LRESULT
3055 TAB_Destroy (TAB_INFO *infoPtr)
3057 UINT iItem;
3059 if (!infoPtr)
3060 return 0;
3062 SetWindowLongPtrW(infoPtr->hwnd, 0, 0);
3064 if (infoPtr->items) {
3065 for (iItem = 0; iItem < infoPtr->uNumItem; iItem++) {
3066 if (TAB_GetItem(infoPtr, iItem)->pszText)
3067 Free (TAB_GetItem(infoPtr, iItem)->pszText);
3069 Free (infoPtr->items);
3072 if (infoPtr->hwndToolTip)
3073 DestroyWindow (infoPtr->hwndToolTip);
3075 if (infoPtr->hwndUpDown)
3076 DestroyWindow(infoPtr->hwndUpDown);
3078 if (infoPtr->iHotTracked >= 0)
3079 KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
3081 CloseThemeData (GetWindowTheme (infoPtr->hwnd));
3083 Free (infoPtr);
3084 return 0;
3087 /* update theme after a WM_THEMECHANGED message */
3088 static LRESULT theme_changed(const TAB_INFO *infoPtr)
3090 HTHEME theme = GetWindowTheme (infoPtr->hwnd);
3091 CloseThemeData (theme);
3092 OpenThemeData (infoPtr->hwnd, themeClass);
3093 return 0;
3096 static LRESULT TAB_NCCalcSize(HWND hwnd, WPARAM wParam, LPARAM lParam)
3098 if (!wParam)
3099 return 0;
3100 return WVR_ALIGNTOP;
3103 static inline LRESULT
3104 TAB_SetItemExtra (TAB_INFO *infoPtr, INT cbInfo)
3106 if (!infoPtr || cbInfo <= 0)
3107 return FALSE;
3109 if (infoPtr->uNumItem)
3111 /* FIXME: MSDN says this is not allowed, but this hasn't been verified */
3112 return FALSE;
3115 infoPtr->cbInfo = cbInfo;
3116 return TRUE;
3119 static LRESULT WINAPI
3120 TAB_WindowProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
3122 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
3124 TRACE("hwnd=%p msg=%x wParam=%lx lParam=%lx\n", hwnd, uMsg, wParam, lParam);
3125 if (!infoPtr && (uMsg != WM_CREATE))
3126 return DefWindowProcW (hwnd, uMsg, wParam, lParam);
3128 switch (uMsg)
3130 case TCM_GETIMAGELIST:
3131 return TAB_GetImageList (infoPtr);
3133 case TCM_SETIMAGELIST:
3134 return TAB_SetImageList (infoPtr, (HIMAGELIST)lParam);
3136 case TCM_GETITEMCOUNT:
3137 return TAB_GetItemCount (infoPtr);
3139 case TCM_GETITEMA:
3140 case TCM_GETITEMW:
3141 return TAB_GetItemT (infoPtr, (INT)wParam, (LPTCITEMW)lParam, uMsg == TCM_GETITEMW);
3143 case TCM_SETITEMA:
3144 case TCM_SETITEMW:
3145 return TAB_SetItemT (infoPtr, (INT)wParam, (LPTCITEMW)lParam, uMsg == TCM_SETITEMW);
3147 case TCM_DELETEITEM:
3148 return TAB_DeleteItem (infoPtr, (INT)wParam);
3150 case TCM_DELETEALLITEMS:
3151 return TAB_DeleteAllItems (infoPtr);
3153 case TCM_GETITEMRECT:
3154 return TAB_GetItemRect (infoPtr, wParam, lParam);
3156 case TCM_GETCURSEL:
3157 return TAB_GetCurSel (infoPtr);
3159 case TCM_HITTEST:
3160 return TAB_HitTest (infoPtr, (LPTCHITTESTINFO)lParam);
3162 case TCM_SETCURSEL:
3163 return TAB_SetCurSel (infoPtr, (INT)wParam);
3165 case TCM_INSERTITEMA:
3166 case TCM_INSERTITEMW:
3167 return TAB_InsertItemT (infoPtr, wParam, lParam, uMsg == TCM_INSERTITEMW);
3169 case TCM_SETITEMEXTRA:
3170 return TAB_SetItemExtra (infoPtr, (int)wParam);
3172 case TCM_ADJUSTRECT:
3173 return TAB_AdjustRect (infoPtr, (BOOL)wParam, (LPRECT)lParam);
3175 case TCM_SETITEMSIZE:
3176 return TAB_SetItemSize (infoPtr, lParam);
3178 case TCM_REMOVEIMAGE:
3179 FIXME("Unimplemented msg TCM_REMOVEIMAGE\n");
3180 return 0;
3182 case TCM_SETPADDING:
3183 return TAB_SetPadding (infoPtr, lParam);
3185 case TCM_GETROWCOUNT:
3186 return TAB_GetRowCount(infoPtr);
3188 case TCM_GETUNICODEFORMAT:
3189 return TAB_GetUnicodeFormat (infoPtr);
3191 case TCM_SETUNICODEFORMAT:
3192 return TAB_SetUnicodeFormat (infoPtr, (BOOL)wParam);
3194 case TCM_HIGHLIGHTITEM:
3195 return TAB_HighlightItem (infoPtr, (INT)wParam, (BOOL)LOWORD(lParam));
3197 case TCM_GETTOOLTIPS:
3198 return TAB_GetToolTips (infoPtr);
3200 case TCM_SETTOOLTIPS:
3201 return TAB_SetToolTips (infoPtr, (HWND)wParam);
3203 case TCM_GETCURFOCUS:
3204 return TAB_GetCurFocus (infoPtr);
3206 case TCM_SETCURFOCUS:
3207 return TAB_SetCurFocus (infoPtr, (INT)wParam);
3209 case TCM_SETMINTABWIDTH:
3210 return TAB_SetMinTabWidth(infoPtr, (INT)lParam);
3212 case TCM_DESELECTALL:
3213 FIXME("Unimplemented msg TCM_DESELECTALL\n");
3214 return 0;
3216 case TCM_GETEXTENDEDSTYLE:
3217 FIXME("Unimplemented msg TCM_GETEXTENDEDSTYLE\n");
3218 return 0;
3220 case TCM_SETEXTENDEDSTYLE:
3221 FIXME("Unimplemented msg TCM_SETEXTENDEDSTYLE\n");
3222 return 0;
3224 case WM_GETFONT:
3225 return TAB_GetFont (infoPtr);
3227 case WM_SETFONT:
3228 return TAB_SetFont (infoPtr, (HFONT)wParam);
3230 case WM_CREATE:
3231 return TAB_Create (hwnd, wParam, lParam);
3233 case WM_NCDESTROY:
3234 return TAB_Destroy (infoPtr);
3236 case WM_GETDLGCODE:
3237 return DLGC_WANTARROWS | DLGC_WANTCHARS;
3239 case WM_LBUTTONDOWN:
3240 return TAB_LButtonDown (infoPtr, wParam, lParam);
3242 case WM_LBUTTONUP:
3243 return TAB_LButtonUp (infoPtr);
3245 case WM_NOTIFY:
3246 return SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, wParam, lParam);
3248 case WM_RBUTTONDOWN:
3249 return TAB_RButtonDown (infoPtr);
3251 case WM_MOUSEMOVE:
3252 return TAB_MouseMove (infoPtr, wParam, lParam);
3254 case WM_PRINTCLIENT:
3255 case WM_PAINT:
3256 return TAB_Paint (infoPtr, (HDC)wParam);
3258 case WM_SIZE:
3259 return TAB_Size (infoPtr);
3261 case WM_SETREDRAW:
3262 return TAB_SetRedraw (infoPtr, (BOOL)wParam);
3264 case WM_HSCROLL:
3265 return TAB_OnHScroll(infoPtr, (int)LOWORD(wParam), (int)HIWORD(wParam), (HWND)lParam);
3267 case WM_STYLECHANGED:
3268 TAB_SetItemBounds (infoPtr);
3269 InvalidateRect(hwnd, NULL, TRUE);
3270 return 0;
3272 case WM_SYSCOLORCHANGE:
3273 COMCTL32_RefreshSysColors();
3274 return 0;
3276 case WM_THEMECHANGED:
3277 return theme_changed (infoPtr);
3279 case WM_KILLFOCUS:
3280 case WM_SETFOCUS:
3281 TAB_FocusChanging(infoPtr);
3282 break; /* Don't disturb normal focus behavior */
3284 case WM_KEYUP:
3285 return TAB_KeyUp(infoPtr, wParam);
3286 case WM_NCHITTEST:
3287 return TAB_NCHitTest(infoPtr, lParam);
3289 case WM_NCCALCSIZE:
3290 return TAB_NCCalcSize(hwnd, wParam, lParam);
3292 default:
3293 if (uMsg >= WM_USER && uMsg < WM_APP)
3294 WARN("unknown msg %04x wp=%08lx lp=%08lx\n",
3295 uMsg, wParam, lParam);
3296 break;
3298 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
3302 void
3303 TAB_Register (void)
3305 WNDCLASSW wndClass;
3307 ZeroMemory (&wndClass, sizeof(WNDCLASSW));
3308 wndClass.style = CS_GLOBALCLASS | CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
3309 wndClass.lpfnWndProc = TAB_WindowProc;
3310 wndClass.cbClsExtra = 0;
3311 wndClass.cbWndExtra = sizeof(TAB_INFO *);
3312 wndClass.hCursor = LoadCursorW (0, (LPWSTR)IDC_ARROW);
3313 wndClass.hbrBackground = (HBRUSH)(COLOR_BTNFACE+1);
3314 wndClass.lpszClassName = WC_TABCONTROLW;
3316 RegisterClassW (&wndClass);
3320 void
3321 TAB_Unregister (void)
3323 UnregisterClassW (WC_TABCONTROLW, NULL);