comctl32: Remove redundant NULL checks before Free() (found by Smatch).
[wine/multimedia.git] / dlls / comctl32 / tab.c
blob711c3202b3fb89d83d9d5aa9978d0ac0343061dd
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(TAB_INFO *);
168 static void TAB_EnsureSelectionVisible(TAB_INFO *);
169 static void TAB_DrawItemInterior(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(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(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 * NOTE
236 * we have not to return negative value
237 * TODO
238 * test for windows */
239 static inline LRESULT
240 TAB_GetCurFocus (const TAB_INFO *infoPtr)
242 if (infoPtr->uFocus<0)
244 FIXME("we have not to return negative value\n");
245 return 0;
247 return infoPtr->uFocus;
250 static inline LRESULT TAB_GetToolTips (const TAB_INFO *infoPtr)
252 if (infoPtr == NULL) return 0;
253 return (LRESULT)infoPtr->hwndToolTip;
256 static inline LRESULT TAB_SetCurSel (TAB_INFO *infoPtr, INT iItem)
258 INT prevItem = -1;
260 if (iItem >= 0 && iItem < infoPtr->uNumItem) {
261 prevItem=infoPtr->iSelected;
262 if (infoPtr->iSelected != iItem) {
263 infoPtr->iSelected=iItem;
264 TAB_EnsureSelectionVisible(infoPtr);
265 TAB_InvalidateTabArea(infoPtr);
268 return prevItem;
271 static LRESULT TAB_SetCurFocus (TAB_INFO *infoPtr, INT iItem)
273 if (iItem < 0 || iItem >= infoPtr->uNumItem) return 0;
275 if (GetWindowLongW(infoPtr->hwnd, GWL_STYLE) & TCS_BUTTONS) {
276 FIXME("Should set input focus\n");
277 } else {
278 int oldFocus = infoPtr->uFocus;
279 if (infoPtr->iSelected != iItem || oldFocus == -1 ) {
280 infoPtr->uFocus = iItem;
281 if (oldFocus != -1) {
282 if (!TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGING)) {
283 infoPtr->iSelected = iItem;
284 TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGE);
286 else
287 infoPtr->iSelected = iItem;
288 TAB_EnsureSelectionVisible(infoPtr);
289 TAB_InvalidateTabArea(infoPtr);
293 return 0;
296 static inline LRESULT
297 TAB_SetToolTips (TAB_INFO *infoPtr, HWND hwndToolTip)
299 if (infoPtr)
300 infoPtr->hwndToolTip = hwndToolTip;
301 return 0;
304 static inline LRESULT
305 TAB_SetPadding (TAB_INFO *infoPtr, LPARAM lParam)
307 if (infoPtr)
309 infoPtr->uHItemPadding_s=LOWORD(lParam);
310 infoPtr->uVItemPadding_s=HIWORD(lParam);
312 return 0;
315 /******************************************************************************
316 * TAB_InternalGetItemRect
318 * This method will calculate the rectangle representing a given tab item in
319 * client coordinates. This method takes scrolling into account.
321 * This method returns TRUE if the item is visible in the window and FALSE
322 * if it is completely outside the client area.
324 static BOOL TAB_InternalGetItemRect(
325 const TAB_INFO* infoPtr,
326 INT itemIndex,
327 RECT* itemRect,
328 RECT* selectedRect)
330 RECT tmpItemRect,clientRect;
331 LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
333 /* Perform a sanity check and a trivial visibility check. */
334 if ( (infoPtr->uNumItem <= 0) ||
335 (itemIndex >= infoPtr->uNumItem) ||
336 (!((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)) && (itemIndex < infoPtr->leftmostVisible)) )
338 TRACE("Not Visible\n");
339 /* need to initialize these to empty rects */
340 if (itemRect)
342 memset(itemRect,0,sizeof(RECT));
343 itemRect->bottom = infoPtr->tabHeight;
345 if (selectedRect)
346 memset(selectedRect,0,sizeof(RECT));
347 return FALSE;
351 * Avoid special cases in this procedure by assigning the "out"
352 * parameters if the caller didn't supply them
354 if (itemRect == NULL)
355 itemRect = &tmpItemRect;
357 /* Retrieve the unmodified item rect. */
358 *itemRect = TAB_GetItem(infoPtr,itemIndex)->rect;
360 /* calculate the times bottom and top based on the row */
361 GetClientRect(infoPtr->hwnd, &clientRect);
363 if ((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
365 itemRect->right = clientRect.right - SELECTED_TAB_OFFSET - itemRect->left * infoPtr->tabHeight -
366 ((lStyle & TCS_BUTTONS) ? itemRect->left * BUTTON_SPACINGX : 0);
367 itemRect->left = itemRect->right - infoPtr->tabHeight;
369 else if (lStyle & TCS_VERTICAL)
371 itemRect->left = clientRect.left + SELECTED_TAB_OFFSET + itemRect->left * infoPtr->tabHeight +
372 ((lStyle & TCS_BUTTONS) ? itemRect->left * BUTTON_SPACINGX : 0);
373 itemRect->right = itemRect->left + infoPtr->tabHeight;
375 else if (lStyle & TCS_BOTTOM)
377 itemRect->bottom = clientRect.bottom - itemRect->top * infoPtr->tabHeight -
378 ((lStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : SELECTED_TAB_OFFSET);
379 itemRect->top = itemRect->bottom - infoPtr->tabHeight;
381 else /* not TCS_BOTTOM and not TCS_VERTICAL */
383 itemRect->top = clientRect.top + itemRect->top * infoPtr->tabHeight +
384 ((lStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : SELECTED_TAB_OFFSET);
385 itemRect->bottom = itemRect->top + infoPtr->tabHeight;
389 * "scroll" it to make sure the item at the very left of the
390 * tab control is the leftmost visible tab.
392 if(lStyle & TCS_VERTICAL)
394 OffsetRect(itemRect,
396 -TAB_GetItem(infoPtr, infoPtr->leftmostVisible)->rect.top);
399 * Move the rectangle so the first item is slightly offset from
400 * the bottom of the tab control.
402 OffsetRect(itemRect,
404 SELECTED_TAB_OFFSET);
406 } else
408 OffsetRect(itemRect,
409 -TAB_GetItem(infoPtr, infoPtr->leftmostVisible)->rect.left,
413 * Move the rectangle so the first item is slightly offset from
414 * the left of the tab control.
416 OffsetRect(itemRect,
417 SELECTED_TAB_OFFSET,
420 TRACE("item %d tab h=%d, rect=(%d,%d)-(%d,%d)\n",
421 itemIndex, infoPtr->tabHeight,
422 itemRect->left, itemRect->top, itemRect->right, itemRect->bottom);
424 /* Now, calculate the position of the item as if it were selected. */
425 if (selectedRect!=NULL)
427 CopyRect(selectedRect, itemRect);
429 /* The rectangle of a selected item is a bit wider. */
430 if(lStyle & TCS_VERTICAL)
431 InflateRect(selectedRect, 0, SELECTED_TAB_OFFSET);
432 else
433 InflateRect(selectedRect, SELECTED_TAB_OFFSET, 0);
435 /* If it also a bit higher. */
436 if ((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
438 selectedRect->left -= 2; /* the border is thicker on the right */
439 selectedRect->right += SELECTED_TAB_OFFSET;
441 else if (lStyle & TCS_VERTICAL)
443 selectedRect->left -= SELECTED_TAB_OFFSET;
444 selectedRect->right += 1;
446 else if (lStyle & TCS_BOTTOM)
448 selectedRect->bottom += SELECTED_TAB_OFFSET;
450 else /* not TCS_BOTTOM and not TCS_VERTICAL */
452 selectedRect->top -= SELECTED_TAB_OFFSET;
453 selectedRect->bottom -= 1;
457 /* Check for visibility */
458 if (lStyle & TCS_VERTICAL)
459 return (itemRect->top < clientRect.bottom) && (itemRect->bottom > clientRect.top);
460 else
461 return (itemRect->left < clientRect.right) && (itemRect->right > clientRect.left);
464 static inline BOOL
465 TAB_GetItemRect(TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
467 return TAB_InternalGetItemRect(infoPtr, (INT)wParam, (LPRECT)lParam, (LPRECT)NULL);
470 /******************************************************************************
471 * TAB_KeyUp
473 * This method is called to handle keyboard input
475 static LRESULT TAB_KeyUp(TAB_INFO* infoPtr, WPARAM keyCode)
477 int newItem = -1;
479 switch (keyCode)
481 case VK_LEFT:
482 newItem = infoPtr->uFocus - 1;
483 break;
484 case VK_RIGHT:
485 newItem = infoPtr->uFocus + 1;
486 break;
490 * If we changed to a valid item, change the selection
492 if (newItem >= 0 &&
493 newItem < infoPtr->uNumItem &&
494 infoPtr->uFocus != newItem)
496 if (!TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGING))
498 infoPtr->iSelected = newItem;
499 infoPtr->uFocus = newItem;
500 TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGE);
502 TAB_EnsureSelectionVisible(infoPtr);
503 TAB_InvalidateTabArea(infoPtr);
507 return 0;
510 /******************************************************************************
511 * TAB_FocusChanging
513 * This method is called whenever the focus goes in or out of this control
514 * it is used to update the visual state of the control.
516 static void TAB_FocusChanging(const TAB_INFO *infoPtr)
518 RECT selectedRect;
519 BOOL isVisible;
522 * Get the rectangle for the item.
524 isVisible = TAB_InternalGetItemRect(infoPtr,
525 infoPtr->uFocus,
526 NULL,
527 &selectedRect);
530 * If the rectangle is not completely invisible, invalidate that
531 * portion of the window.
533 if (isVisible)
535 TRACE("invalidate (%d,%d)-(%d,%d)\n",
536 selectedRect.left,selectedRect.top,
537 selectedRect.right,selectedRect.bottom);
538 InvalidateRect(infoPtr->hwnd, &selectedRect, TRUE);
542 static INT TAB_InternalHitTest (
543 TAB_INFO* infoPtr,
544 POINT pt,
545 UINT* flags)
548 RECT rect;
549 INT iCount;
551 for (iCount = 0; iCount < infoPtr->uNumItem; iCount++)
553 TAB_InternalGetItemRect(infoPtr, iCount, &rect, NULL);
555 if (PtInRect(&rect, pt))
557 *flags = TCHT_ONITEM;
558 return iCount;
562 *flags = TCHT_NOWHERE;
563 return -1;
566 static inline LRESULT
567 TAB_HitTest (TAB_INFO *infoPtr, LPTCHITTESTINFO lptest)
569 return TAB_InternalHitTest (infoPtr, lptest->pt, &lptest->flags);
572 /******************************************************************************
573 * TAB_NCHitTest
575 * Napster v2b5 has a tab control for its main navigation which has a client
576 * area that covers the whole area of the dialog pages.
577 * That's why it receives all msgs for that area and the underlying dialog ctrls
578 * are dead.
579 * So I decided that we should handle WM_NCHITTEST here and return
580 * HTTRANSPARENT if we don't hit the tab control buttons.
581 * FIXME: WM_NCHITTEST handling correct ? Fix it if you know that Windows
582 * doesn't do it that way. Maybe depends on tab control styles ?
584 static inline LRESULT
585 TAB_NCHitTest (TAB_INFO *infoPtr, LPARAM lParam)
587 POINT pt;
588 UINT dummyflag;
590 pt.x = (short)LOWORD(lParam);
591 pt.y = (short)HIWORD(lParam);
592 ScreenToClient(infoPtr->hwnd, &pt);
594 if (TAB_InternalHitTest(infoPtr, pt, &dummyflag) == -1)
595 return HTTRANSPARENT;
596 else
597 return HTCLIENT;
600 static LRESULT
601 TAB_LButtonDown (TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
603 POINT pt;
604 INT newItem;
605 UINT dummy;
607 if (infoPtr->hwndToolTip)
608 TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
609 WM_LBUTTONDOWN, wParam, lParam);
611 if (GetWindowLongW(infoPtr->hwnd, GWL_STYLE) & TCS_FOCUSONBUTTONDOWN ) {
612 SetFocus (infoPtr->hwnd);
615 if (infoPtr->hwndToolTip)
616 TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
617 WM_LBUTTONDOWN, wParam, lParam);
619 pt.x = (short)LOWORD(lParam);
620 pt.y = (short)HIWORD(lParam);
622 newItem = TAB_InternalHitTest (infoPtr, pt, &dummy);
624 TRACE("On Tab, item %d\n", newItem);
626 if (newItem != -1 && infoPtr->iSelected != newItem)
628 if (!TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGING))
630 infoPtr->iSelected = newItem;
631 infoPtr->uFocus = newItem;
632 TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGE);
634 TAB_EnsureSelectionVisible(infoPtr);
636 TAB_InvalidateTabArea(infoPtr);
639 return 0;
642 static inline LRESULT
643 TAB_LButtonUp (const TAB_INFO *infoPtr)
645 TAB_SendSimpleNotify(infoPtr, NM_CLICK);
647 return 0;
650 static inline LRESULT
651 TAB_RButtonDown (const TAB_INFO *infoPtr)
653 TAB_SendSimpleNotify(infoPtr, NM_RCLICK);
654 return 0;
657 /******************************************************************************
658 * TAB_DrawLoneItemInterior
660 * This calls TAB_DrawItemInterior. However, TAB_DrawItemInterior is normally
661 * called by TAB_DrawItem which is normally called by TAB_Refresh which sets
662 * up the device context and font. This routine does the same setup but
663 * only calls TAB_DrawItemInterior for the single specified item.
665 static void
666 TAB_DrawLoneItemInterior(TAB_INFO* infoPtr, int iItem)
668 HDC hdc = GetDC(infoPtr->hwnd);
669 RECT r, rC;
671 /* Clip UpDown control to not draw over it */
672 if (infoPtr->needsScrolling)
674 GetWindowRect(infoPtr->hwnd, &rC);
675 GetWindowRect(infoPtr->hwndUpDown, &r);
676 ExcludeClipRect(hdc, r.left - rC.left, r.top - rC.top, r.right - rC.left, r.bottom - rC.top);
678 TAB_DrawItemInterior(infoPtr, hdc, iItem, NULL);
679 ReleaseDC(infoPtr->hwnd, hdc);
682 /* update a tab after hottracking - invalidate it or just redraw the interior,
683 * based on whether theming is used or not */
684 static inline void hottrack_refresh (TAB_INFO* infoPtr, int tabIndex)
686 if (tabIndex == -1) return;
688 if (GetWindowTheme (infoPtr->hwnd))
690 RECT rect;
691 TAB_InternalGetItemRect(infoPtr, tabIndex, &rect, NULL);
692 InvalidateRect (infoPtr->hwnd, &rect, FALSE);
694 else
695 TAB_DrawLoneItemInterior(infoPtr, tabIndex);
698 /******************************************************************************
699 * TAB_HotTrackTimerProc
701 * When a mouse-move event causes a tab to be highlighted (hot-tracking), a
702 * timer is setup so we can check if the mouse is moved out of our window.
703 * (We don't get an event when the mouse leaves, the mouse-move events just
704 * stop being delivered to our window and just start being delivered to
705 * another window.) This function is called when the timer triggers so
706 * we can check if the mouse has left our window. If so, we un-highlight
707 * the hot-tracked tab.
709 static void CALLBACK
710 TAB_HotTrackTimerProc
712 HWND hwnd, /* handle of window for timer messages */
713 UINT uMsg, /* WM_TIMER message */
714 UINT_PTR idEvent, /* timer identifier */
715 DWORD dwTime /* current system time */
718 TAB_INFO* infoPtr = TAB_GetInfoPtr(hwnd);
720 if (infoPtr != NULL && infoPtr->iHotTracked >= 0)
722 POINT pt;
725 ** If we can't get the cursor position, or if the cursor is outside our
726 ** window, we un-highlight the hot-tracked tab. Note that the cursor is
727 ** "outside" even if it is within our bounding rect if another window
728 ** overlaps. Note also that the case where the cursor stayed within our
729 ** window but has moved off the hot-tracked tab will be handled by the
730 ** WM_MOUSEMOVE event.
732 if (!GetCursorPos(&pt) || WindowFromPoint(pt) != hwnd)
734 /* Redraw iHotTracked to look normal */
735 INT iRedraw = infoPtr->iHotTracked;
736 infoPtr->iHotTracked = -1;
737 hottrack_refresh (infoPtr, iRedraw);
739 /* Kill this timer */
740 KillTimer(hwnd, TAB_HOTTRACK_TIMER);
745 /******************************************************************************
746 * TAB_RecalcHotTrack
748 * If a tab control has the TCS_HOTTRACK style, then the tab under the mouse
749 * should be highlighted. This function determines which tab in a tab control,
750 * if any, is under the mouse and records that information. The caller may
751 * supply output parameters to receive the item number of the tab item which
752 * was highlighted but isn't any longer and of the tab item which is now
753 * highlighted but wasn't previously. The caller can use this information to
754 * selectively redraw those tab items.
756 * If the caller has a mouse position, it can supply it through the pos
757 * parameter. For example, TAB_MouseMove does this. Otherwise, the caller
758 * supplies NULL and this function determines the current mouse position
759 * itself.
761 static void
762 TAB_RecalcHotTrack
764 TAB_INFO* infoPtr,
765 const LPARAM* pos,
766 int* out_redrawLeave,
767 int* out_redrawEnter
770 int item = -1;
773 if (out_redrawLeave != NULL)
774 *out_redrawLeave = -1;
775 if (out_redrawEnter != NULL)
776 *out_redrawEnter = -1;
778 if ((GetWindowLongW(infoPtr->hwnd, GWL_STYLE) & TCS_HOTTRACK)
779 || GetWindowTheme (infoPtr->hwnd))
781 POINT pt;
782 UINT flags;
784 if (pos == NULL)
786 GetCursorPos(&pt);
787 ScreenToClient(infoPtr->hwnd, &pt);
789 else
791 pt.x = (short)LOWORD(*pos);
792 pt.y = (short)HIWORD(*pos);
795 item = TAB_InternalHitTest(infoPtr, pt, &flags);
798 if (item != infoPtr->iHotTracked)
800 if (infoPtr->iHotTracked >= 0)
802 /* Mark currently hot-tracked to be redrawn to look normal */
803 if (out_redrawLeave != NULL)
804 *out_redrawLeave = infoPtr->iHotTracked;
806 if (item < 0)
808 /* Kill timer which forces recheck of mouse pos */
809 KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
812 else
814 /* Start timer so we recheck mouse pos */
815 UINT timerID = SetTimer
817 infoPtr->hwnd,
818 TAB_HOTTRACK_TIMER,
819 TAB_HOTTRACK_TIMER_INTERVAL,
820 TAB_HotTrackTimerProc
823 if (timerID == 0)
824 return; /* Hot tracking not available */
827 infoPtr->iHotTracked = item;
829 if (item >= 0)
831 /* Mark new hot-tracked to be redrawn to look highlighted */
832 if (out_redrawEnter != NULL)
833 *out_redrawEnter = item;
838 /******************************************************************************
839 * TAB_MouseMove
841 * Handles the mouse-move event. Updates tooltips. Updates hot-tracking.
843 static LRESULT
844 TAB_MouseMove (TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
846 int redrawLeave;
847 int redrawEnter;
849 if (infoPtr->hwndToolTip)
850 TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
851 WM_LBUTTONDOWN, wParam, lParam);
853 /* Determine which tab to highlight. Redraw tabs which change highlight
854 ** status. */
855 TAB_RecalcHotTrack(infoPtr, &lParam, &redrawLeave, &redrawEnter);
857 hottrack_refresh (infoPtr, redrawLeave);
858 hottrack_refresh (infoPtr, redrawEnter);
860 return 0;
863 /******************************************************************************
864 * TAB_AdjustRect
866 * Calculates the tab control's display area given the window rectangle or
867 * the window rectangle given the requested display rectangle.
869 static LRESULT TAB_AdjustRect(
870 TAB_INFO *infoPtr,
871 WPARAM fLarger,
872 LPRECT prc)
874 DWORD lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
875 LONG *iRightBottom, *iLeftTop;
877 TRACE ("hwnd=%p fLarger=%d (%d,%d)-(%d,%d)\n", infoPtr->hwnd, fLarger, prc->left, prc->top, prc->right, prc->bottom);
879 if(lStyle & TCS_VERTICAL)
881 iRightBottom = &(prc->right);
882 iLeftTop = &(prc->left);
884 else
886 iRightBottom = &(prc->bottom);
887 iLeftTop = &(prc->top);
890 if (fLarger) /* Go from display rectangle */
892 /* Add the height of the tabs. */
893 if (lStyle & TCS_BOTTOM)
894 *iRightBottom += infoPtr->tabHeight * infoPtr->uNumRows;
895 else
896 *iLeftTop -= infoPtr->tabHeight * infoPtr->uNumRows +
897 ((lStyle & TCS_BUTTONS)? 3 * (infoPtr->uNumRows - 1) : 0);
899 /* Inflate the rectangle for the padding */
900 InflateRect(prc, DISPLAY_AREA_PADDINGX, DISPLAY_AREA_PADDINGY);
902 /* Inflate for the border */
903 InflateRect(prc, CONTROL_BORDER_SIZEX, CONTROL_BORDER_SIZEY);
905 else /* Go from window rectangle. */
907 /* Deflate the rectangle for the border */
908 InflateRect(prc, -CONTROL_BORDER_SIZEX, -CONTROL_BORDER_SIZEY);
910 /* Deflate the rectangle for the padding */
911 InflateRect(prc, -DISPLAY_AREA_PADDINGX, -DISPLAY_AREA_PADDINGY);
913 /* Remove the height of the tabs. */
914 if (lStyle & TCS_BOTTOM)
915 *iRightBottom -= infoPtr->tabHeight * infoPtr->uNumRows;
916 else
917 *iLeftTop += (infoPtr->tabHeight) * infoPtr->uNumRows +
918 ((lStyle & TCS_BUTTONS)? 3 * (infoPtr->uNumRows - 1) : 0);
921 return 0;
924 /******************************************************************************
925 * TAB_OnHScroll
927 * This method will handle the notification from the scroll control and
928 * perform the scrolling operation on the tab control.
930 static LRESULT TAB_OnHScroll(
931 TAB_INFO *infoPtr,
932 int nScrollCode,
933 int nPos,
934 HWND hwndScroll)
936 if(nScrollCode == SB_THUMBPOSITION && nPos != infoPtr->leftmostVisible)
938 if(nPos < infoPtr->leftmostVisible)
939 infoPtr->leftmostVisible--;
940 else
941 infoPtr->leftmostVisible++;
943 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
944 TAB_InvalidateTabArea(infoPtr);
945 SendMessageW(infoPtr->hwndUpDown, UDM_SETPOS, 0,
946 MAKELONG(infoPtr->leftmostVisible, 0));
949 return 0;
952 /******************************************************************************
953 * TAB_SetupScrolling
955 * This method will check the current scrolling state and make sure the
956 * scrolling control is displayed (or not).
958 static void TAB_SetupScrolling(
959 HWND hwnd,
960 TAB_INFO* infoPtr,
961 const RECT* clientRect)
963 static const WCHAR msctls_updown32W[] = { 'm','s','c','t','l','s','_','u','p','d','o','w','n','3','2',0 };
964 static const WCHAR emptyW[] = { 0 };
965 INT maxRange = 0;
966 DWORD lStyle = GetWindowLongW(hwnd, GWL_STYLE);
968 if (infoPtr->needsScrolling)
970 RECT controlPos;
971 INT vsize, tabwidth;
974 * Calculate the position of the scroll control.
976 if(lStyle & TCS_VERTICAL)
978 controlPos.right = clientRect->right;
979 controlPos.left = controlPos.right - 2 * GetSystemMetrics(SM_CXHSCROLL);
981 if (lStyle & TCS_BOTTOM)
983 controlPos.top = clientRect->bottom - infoPtr->tabHeight;
984 controlPos.bottom = controlPos.top + GetSystemMetrics(SM_CYHSCROLL);
986 else
988 controlPos.bottom = clientRect->top + infoPtr->tabHeight;
989 controlPos.top = controlPos.bottom - GetSystemMetrics(SM_CYHSCROLL);
992 else
994 controlPos.right = clientRect->right;
995 controlPos.left = controlPos.right - 2 * GetSystemMetrics(SM_CXHSCROLL);
997 if (lStyle & TCS_BOTTOM)
999 controlPos.top = clientRect->bottom - infoPtr->tabHeight;
1000 controlPos.bottom = controlPos.top + GetSystemMetrics(SM_CYHSCROLL);
1002 else
1004 controlPos.bottom = clientRect->top + infoPtr->tabHeight;
1005 controlPos.top = controlPos.bottom - GetSystemMetrics(SM_CYHSCROLL);
1010 * If we don't have a scroll control yet, we want to create one.
1011 * If we have one, we want to make sure it's positioned properly.
1013 if (infoPtr->hwndUpDown==0)
1015 infoPtr->hwndUpDown = CreateWindowW(msctls_updown32W, emptyW,
1016 WS_VISIBLE | WS_CHILD | UDS_HORZ,
1017 controlPos.left, controlPos.top,
1018 controlPos.right - controlPos.left,
1019 controlPos.bottom - controlPos.top,
1020 hwnd, NULL, NULL, NULL);
1022 else
1024 SetWindowPos(infoPtr->hwndUpDown,
1025 NULL,
1026 controlPos.left, controlPos.top,
1027 controlPos.right - controlPos.left,
1028 controlPos.bottom - controlPos.top,
1029 SWP_SHOWWINDOW | SWP_NOZORDER);
1032 /* Now calculate upper limit of the updown control range.
1033 * We do this by calculating how many tabs will be offscreen when the
1034 * last tab is visible.
1036 if(infoPtr->uNumItem)
1038 vsize = clientRect->right - (controlPos.right - controlPos.left + 1);
1039 maxRange = infoPtr->uNumItem;
1040 tabwidth = TAB_GetItem(infoPtr, infoPtr->uNumItem - 1)->rect.right;
1042 for(; maxRange > 0; maxRange--)
1044 if(tabwidth - TAB_GetItem(infoPtr,maxRange - 1)->rect.left > vsize)
1045 break;
1048 if(maxRange == infoPtr->uNumItem)
1049 maxRange--;
1052 else
1054 /* If we once had a scroll control... hide it */
1055 if (infoPtr->hwndUpDown!=0)
1056 ShowWindow(infoPtr->hwndUpDown, SW_HIDE);
1058 if (infoPtr->hwndUpDown)
1059 SendMessageW(infoPtr->hwndUpDown, UDM_SETRANGE32, 0, maxRange);
1062 /******************************************************************************
1063 * TAB_SetItemBounds
1065 * This method will calculate the position rectangles of all the items in the
1066 * control. The rectangle calculated starts at 0 for the first item in the
1067 * list and ignores scrolling and selection.
1068 * It also uses the current font to determine the height of the tab row and
1069 * it checks if all the tabs fit in the client area of the window. If they
1070 * don't, a scrolling control is added.
1072 static void TAB_SetItemBounds (TAB_INFO *infoPtr)
1074 LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1075 TEXTMETRICW fontMetrics;
1076 UINT curItem;
1077 INT curItemLeftPos;
1078 INT curItemRowCount;
1079 HFONT hFont, hOldFont;
1080 HDC hdc;
1081 RECT clientRect;
1082 INT iTemp;
1083 RECT* rcItem;
1084 INT iIndex;
1085 INT icon_width = 0;
1088 * We need to get text information so we need a DC and we need to select
1089 * a font.
1091 hdc = GetDC(infoPtr->hwnd);
1093 hFont = infoPtr->hFont ? infoPtr->hFont : GetStockObject (SYSTEM_FONT);
1094 hOldFont = SelectObject (hdc, hFont);
1097 * We will base the rectangle calculations on the client rectangle
1098 * of the control.
1100 GetClientRect(infoPtr->hwnd, &clientRect);
1102 /* if TCS_VERTICAL then swap the height and width so this code places the
1103 tabs along the top of the rectangle and we can just rotate them after
1104 rather than duplicate all of the below code */
1105 if(lStyle & TCS_VERTICAL)
1107 iTemp = clientRect.bottom;
1108 clientRect.bottom = clientRect.right;
1109 clientRect.right = iTemp;
1112 /* Now use hPadding and vPadding */
1113 infoPtr->uHItemPadding = infoPtr->uHItemPadding_s;
1114 infoPtr->uVItemPadding = infoPtr->uVItemPadding_s;
1116 /* The leftmost item will be "0" aligned */
1117 curItemLeftPos = 0;
1118 curItemRowCount = infoPtr->uNumItem ? 1 : 0;
1120 if (!(infoPtr->fHeightSet))
1122 int item_height;
1123 int icon_height = 0;
1125 /* Use the current font to determine the height of a tab. */
1126 GetTextMetricsW(hdc, &fontMetrics);
1128 /* Get the icon height */
1129 if (infoPtr->himl)
1130 ImageList_GetIconSize(infoPtr->himl, 0, &icon_height);
1132 /* Take the highest between font or icon */
1133 if (fontMetrics.tmHeight > icon_height)
1134 item_height = fontMetrics.tmHeight + 2;
1135 else
1136 item_height = icon_height;
1139 * Make sure there is enough space for the letters + icon + growing the
1140 * selected item + extra space for the selected item.
1142 infoPtr->tabHeight = item_height +
1143 ((lStyle & TCS_BUTTONS) ? 2 : 1) *
1144 infoPtr->uVItemPadding;
1146 TRACE("tabH=%d, tmH=%d, iconh=%d\n",
1147 infoPtr->tabHeight, fontMetrics.tmHeight, icon_height);
1150 TRACE("client right=%d\n", clientRect.right);
1152 /* Get the icon width */
1153 if (infoPtr->himl)
1155 ImageList_GetIconSize(infoPtr->himl, &icon_width, 0);
1157 if (lStyle & TCS_FIXEDWIDTH)
1158 icon_width += 4;
1159 else
1160 /* Add padding if icon is present */
1161 icon_width += infoPtr->uHItemPadding;
1164 for (curItem = 0; curItem < infoPtr->uNumItem; curItem++)
1166 TAB_ITEM *curr = TAB_GetItem(infoPtr, curItem);
1168 /* Set the leftmost position of the tab. */
1169 curr->rect.left = curItemLeftPos;
1171 if (lStyle & TCS_FIXEDWIDTH)
1173 curr->rect.right = curr->rect.left +
1174 max(infoPtr->tabWidth, icon_width);
1176 else if (!curr->pszText)
1178 /* If no text use minimum tab width including padding. */
1179 if (infoPtr->tabMinWidth < 0)
1180 curr->rect.right = curr->rect.left + GET_DEFAULT_MIN_TAB_WIDTH(infoPtr);
1181 else
1183 curr->rect.right = curr->rect.left + infoPtr->tabMinWidth;
1185 /* Add extra padding if icon is present */
1186 if (infoPtr->himl && infoPtr->tabMinWidth > 0 && infoPtr->tabMinWidth < DEFAULT_MIN_TAB_WIDTH
1187 && infoPtr->uHItemPadding > 1)
1188 curr->rect.right += EXTRA_ICON_PADDING * (infoPtr->uHItemPadding-1);
1191 else
1193 int tabwidth;
1194 SIZE size;
1195 /* Calculate how wide the tab is depending on the text it contains */
1196 GetTextExtentPoint32W(hdc, curr->pszText,
1197 lstrlenW(curr->pszText), &size);
1199 tabwidth = size.cx + icon_width + 2 * infoPtr->uHItemPadding;
1201 if (infoPtr->tabMinWidth < 0)
1202 tabwidth = max(tabwidth, GET_DEFAULT_MIN_TAB_WIDTH(infoPtr));
1203 else
1204 tabwidth = max(tabwidth, infoPtr->tabMinWidth);
1206 curr->rect.right = curr->rect.left + tabwidth;
1207 TRACE("for <%s>, l,r=%d,%d\n",
1208 debugstr_w(curr->pszText), curr->rect.left, curr->rect.right);
1212 * Check if this is a multiline tab control and if so
1213 * check to see if we should wrap the tabs
1215 * Wrap all these tabs. We will arrange them evenly later.
1219 if (((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)) &&
1220 (curr->rect.right >
1221 (clientRect.right - CONTROL_BORDER_SIZEX - DISPLAY_AREA_PADDINGX)))
1223 curr->rect.right -= curr->rect.left;
1225 curr->rect.left = 0;
1226 curItemRowCount++;
1227 TRACE("wrapping <%s>, l,r=%d,%d\n", debugstr_w(curr->pszText),
1228 curr->rect.left, curr->rect.right);
1231 curr->rect.bottom = 0;
1232 curr->rect.top = curItemRowCount - 1;
1234 TRACE("Rect: T %i, L %i, B %i, R %i\n", curr->rect.top,
1235 curr->rect.left, curr->rect.bottom, curr->rect.right);
1238 * The leftmost position of the next item is the rightmost position
1239 * of this one.
1241 if (lStyle & TCS_BUTTONS)
1243 curItemLeftPos = curr->rect.right + BUTTON_SPACINGX;
1244 if (lStyle & TCS_FLATBUTTONS)
1245 curItemLeftPos += FLAT_BTN_SPACINGX;
1247 else
1248 curItemLeftPos = curr->rect.right;
1251 if (!((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)))
1254 * Check if we need a scrolling control.
1256 infoPtr->needsScrolling = (curItemLeftPos + (2 * SELECTED_TAB_OFFSET) >
1257 clientRect.right);
1259 /* Don't need scrolling, then update infoPtr->leftmostVisible */
1260 if(!infoPtr->needsScrolling)
1261 infoPtr->leftmostVisible = 0;
1263 else
1266 * No scrolling in Multiline or Vertical styles.
1268 infoPtr->needsScrolling = FALSE;
1269 infoPtr->leftmostVisible = 0;
1271 TAB_SetupScrolling(infoPtr->hwnd, infoPtr, &clientRect);
1273 /* Set the number of rows */
1274 infoPtr->uNumRows = curItemRowCount;
1276 /* Arrange all tabs evenly if style says so */
1277 if (!(lStyle & TCS_RAGGEDRIGHT) &&
1278 ((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)) &&
1279 (infoPtr->uNumItem > 0) &&
1280 (infoPtr->uNumRows > 1))
1282 INT tabPerRow,remTab,iRow;
1283 UINT iItm;
1284 INT iCount=0;
1287 * Ok windows tries to even out the rows. place the same
1288 * number of tabs in each row. So lets give that a shot
1291 tabPerRow = infoPtr->uNumItem / (infoPtr->uNumRows);
1292 remTab = infoPtr->uNumItem % (infoPtr->uNumRows);
1294 for (iItm=0,iRow=0,iCount=0,curItemLeftPos=0;
1295 iItm<infoPtr->uNumItem;
1296 iItm++,iCount++)
1298 /* normalize the current rect */
1299 TAB_ITEM *curr = TAB_GetItem(infoPtr, iItm);
1301 /* shift the item to the left side of the clientRect */
1302 curr->rect.right -= curr->rect.left;
1303 curr->rect.left = 0;
1305 TRACE("r=%d, cl=%d, cl.r=%d, iCount=%d, iRow=%d, uNumRows=%d, remTab=%d, tabPerRow=%d\n",
1306 curr->rect.right, curItemLeftPos, clientRect.right,
1307 iCount, iRow, infoPtr->uNumRows, remTab, tabPerRow);
1309 /* if we have reached the maximum number of tabs on this row */
1310 /* move to the next row, reset our current item left position and */
1311 /* the count of items on this row */
1313 if (lStyle & TCS_VERTICAL) {
1314 /* Vert: Add the remaining tabs in the *last* remainder rows */
1315 if (iCount >= ((iRow>=(INT)infoPtr->uNumRows - remTab)?tabPerRow + 1:tabPerRow)) {
1316 iRow++;
1317 curItemLeftPos = 0;
1318 iCount = 0;
1320 } else {
1321 /* Horz: Add the remaining tabs in the *first* remainder rows */
1322 if (iCount >= ((iRow<remTab)?tabPerRow + 1:tabPerRow)) {
1323 iRow++;
1324 curItemLeftPos = 0;
1325 iCount = 0;
1329 /* shift the item to the right to place it as the next item in this row */
1330 curr->rect.left += curItemLeftPos;
1331 curr->rect.right += curItemLeftPos;
1332 curr->rect.top = iRow;
1333 if (lStyle & TCS_BUTTONS)
1335 curItemLeftPos = curr->rect.right + 1;
1336 if (lStyle & TCS_FLATBUTTONS)
1337 curItemLeftPos += FLAT_BTN_SPACINGX;
1339 else
1340 curItemLeftPos = curr->rect.right;
1342 TRACE("arranging <%s>, l,r=%d,%d, row=%d\n",
1343 debugstr_w(curr->pszText), curr->rect.left,
1344 curr->rect.right, curr->rect.top);
1348 * Justify the rows
1351 INT widthDiff, iIndexStart=0, iIndexEnd=0;
1352 INT remainder;
1353 INT iCount=0;
1355 while(iIndexStart < infoPtr->uNumItem)
1357 TAB_ITEM *start = TAB_GetItem(infoPtr, iIndexStart);
1360 * find the index of the row
1362 /* find the first item on the next row */
1363 for (iIndexEnd=iIndexStart;
1364 (iIndexEnd < infoPtr->uNumItem) &&
1365 (TAB_GetItem(infoPtr, iIndexEnd)->rect.top ==
1366 start->rect.top) ;
1367 iIndexEnd++)
1368 /* intentionally blank */;
1371 * we need to justify these tabs so they fill the whole given
1372 * client area
1375 /* find the amount of space remaining on this row */
1376 widthDiff = clientRect.right - (2 * SELECTED_TAB_OFFSET) -
1377 TAB_GetItem(infoPtr, iIndexEnd - 1)->rect.right;
1379 /* iCount is the number of tab items on this row */
1380 iCount = iIndexEnd - iIndexStart;
1382 if (iCount > 1)
1384 remainder = widthDiff % iCount;
1385 widthDiff = widthDiff / iCount;
1386 /* add widthDiff/iCount, or extra space/items on row, to each item on this row */
1387 for (iIndex=iIndexStart, iCount=0; iIndex < iIndexEnd; iIndex++, iCount++)
1389 TAB_ITEM *item = TAB_GetItem(infoPtr, iIndex);
1391 item->rect.left += iCount * widthDiff;
1392 item->rect.right += (iCount + 1) * widthDiff;
1394 TRACE("adjusting 1 <%s>, l,r=%d,%d\n",
1395 debugstr_w(item->pszText),
1396 item->rect.left, item->rect.right);
1399 TAB_GetItem(infoPtr, iIndex - 1)->rect.right += remainder;
1401 else /* we have only one item on this row, make it take up the entire row */
1403 start->rect.left = clientRect.left;
1404 start->rect.right = clientRect.right - 4;
1406 TRACE("adjusting 2 <%s>, l,r=%d,%d\n",
1407 debugstr_w(start->pszText),
1408 start->rect.left, start->rect.right);
1413 iIndexStart = iIndexEnd;
1418 /* if TCS_VERTICAL rotate the tabs so they are along the side of the clientRect */
1419 if(lStyle & TCS_VERTICAL)
1421 RECT rcOriginal;
1422 for(iIndex = 0; iIndex < infoPtr->uNumItem; iIndex++)
1424 rcItem = &TAB_GetItem(infoPtr, iIndex)->rect;
1426 rcOriginal = *rcItem;
1428 /* this is rotating the items by 90 degrees clockwise around the center of the control */
1429 rcItem->top = (rcOriginal.left - clientRect.left);
1430 rcItem->bottom = rcItem->top + (rcOriginal.right - rcOriginal.left);
1431 rcItem->left = rcOriginal.top;
1432 rcItem->right = rcOriginal.bottom;
1436 TAB_EnsureSelectionVisible(infoPtr);
1437 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
1439 /* Cleanup */
1440 SelectObject (hdc, hOldFont);
1441 ReleaseDC (infoPtr->hwnd, hdc);
1445 static void
1446 TAB_EraseTabInterior
1448 TAB_INFO* infoPtr,
1449 HDC hdc,
1450 INT iItem,
1451 RECT* drawRect
1454 LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1455 HBRUSH hbr = CreateSolidBrush (comctl32_color.clrBtnFace);
1456 BOOL deleteBrush = TRUE;
1457 RECT rTemp = *drawRect;
1459 InflateRect(&rTemp, -2, -2);
1460 if (lStyle & TCS_BUTTONS)
1462 if (iItem == infoPtr->iSelected)
1464 /* Background color */
1465 if (!(lStyle & TCS_OWNERDRAWFIXED))
1467 DeleteObject(hbr);
1468 hbr = GetSysColorBrush(COLOR_SCROLLBAR);
1470 SetTextColor(hdc, comctl32_color.clr3dFace);
1471 SetBkColor(hdc, comctl32_color.clr3dHilight);
1473 /* if COLOR_WINDOW happens to be the same as COLOR_3DHILIGHT
1474 * we better use 0x55aa bitmap brush to make scrollbar's background
1475 * look different from the window background.
1477 if (comctl32_color.clr3dHilight == comctl32_color.clrWindow)
1478 hbr = COMCTL32_hPattern55AABrush;
1480 deleteBrush = FALSE;
1482 FillRect(hdc, &rTemp, hbr);
1484 else /* ! selected */
1486 if (lStyle & TCS_FLATBUTTONS)
1488 FillRect(hdc, drawRect, hbr);
1489 if (iItem == infoPtr->iHotTracked)
1490 DrawEdge(hdc, drawRect, EDGE_RAISED, BF_SOFT|BF_RECT);
1492 else
1493 FillRect(hdc, &rTemp, hbr);
1497 else /* !TCS_BUTTONS */
1499 if (!GetWindowTheme (infoPtr->hwnd))
1500 FillRect(hdc, &rTemp, hbr);
1503 /* Cleanup */
1504 if (deleteBrush) DeleteObject(hbr);
1507 /******************************************************************************
1508 * TAB_DrawItemInterior
1510 * This method is used to draw the interior (text and icon) of a single tab
1511 * into the tab control.
1513 static void
1514 TAB_DrawItemInterior
1516 TAB_INFO* infoPtr,
1517 HDC hdc,
1518 INT iItem,
1519 RECT* drawRect
1522 LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1524 RECT localRect;
1526 HPEN htextPen;
1527 HPEN holdPen;
1528 INT oldBkMode;
1529 HFONT hOldFont;
1531 /* if (drawRect == NULL) */
1533 BOOL isVisible;
1534 RECT itemRect;
1535 RECT selectedRect;
1538 * Get the rectangle for the item.
1540 isVisible = TAB_InternalGetItemRect(infoPtr, iItem, &itemRect, &selectedRect);
1541 if (!isVisible)
1542 return;
1545 * Make sure drawRect points to something valid; simplifies code.
1547 drawRect = &localRect;
1550 * This logic copied from the part of TAB_DrawItem which draws
1551 * the tab background. It's important to keep it in sync. I
1552 * would have liked to avoid code duplication, but couldn't figure
1553 * out how without making spaghetti of TAB_DrawItem.
1555 if (iItem == infoPtr->iSelected)
1556 *drawRect = selectedRect;
1557 else
1558 *drawRect = itemRect;
1560 if (lStyle & TCS_BUTTONS)
1562 if (iItem == infoPtr->iSelected)
1564 drawRect->left += 4;
1565 drawRect->top += 4;
1566 drawRect->right -= 4;
1567 drawRect->bottom -= 1;
1569 else
1571 drawRect->left += 2;
1572 drawRect->top += 2;
1573 drawRect->right -= 2;
1574 drawRect->bottom -= 2;
1577 else
1579 if ((lStyle & TCS_VERTICAL) && (lStyle & TCS_BOTTOM))
1581 if (iItem != infoPtr->iSelected)
1583 drawRect->left += 2;
1584 drawRect->top += 2;
1585 drawRect->bottom -= 2;
1588 else if (lStyle & TCS_VERTICAL)
1590 if (iItem == infoPtr->iSelected)
1592 drawRect->right += 1;
1594 else
1596 drawRect->top += 2;
1597 drawRect->right -= 2;
1598 drawRect->bottom -= 2;
1601 else if (lStyle & TCS_BOTTOM)
1603 if (iItem == infoPtr->iSelected)
1605 drawRect->top -= 2;
1607 else
1609 InflateRect(drawRect, -2, -2);
1610 drawRect->bottom += 2;
1613 else
1615 if (iItem == infoPtr->iSelected)
1617 drawRect->bottom += 3;
1619 else
1621 drawRect->bottom -= 2;
1622 InflateRect(drawRect, -2, 0);
1627 TRACE("drawRect=(%d,%d)-(%d,%d)\n",
1628 drawRect->left, drawRect->top, drawRect->right, drawRect->bottom);
1630 /* Clear interior */
1631 TAB_EraseTabInterior (infoPtr, hdc, iItem, drawRect);
1633 /* Draw the focus rectangle */
1634 if (!(lStyle & TCS_FOCUSNEVER) &&
1635 (GetFocus() == infoPtr->hwnd) &&
1636 (iItem == infoPtr->uFocus) )
1638 RECT rFocus = *drawRect;
1639 InflateRect(&rFocus, -3, -3);
1640 if (lStyle & TCS_BOTTOM && !(lStyle & TCS_VERTICAL))
1641 rFocus.top -= 3;
1642 if (lStyle & TCS_BUTTONS)
1644 rFocus.left -= 3;
1645 rFocus.top -= 3;
1648 DrawFocusRect(hdc, &rFocus);
1652 * Text pen
1654 htextPen = CreatePen( PS_SOLID, 1, GetSysColor(COLOR_BTNTEXT) );
1655 holdPen = SelectObject(hdc, htextPen);
1656 hOldFont = SelectObject(hdc, infoPtr->hFont);
1659 * Setup for text output
1661 oldBkMode = SetBkMode(hdc, TRANSPARENT);
1662 if (!GetWindowTheme (infoPtr->hwnd) || (lStyle & TCS_BUTTONS))
1663 SetTextColor(hdc, (((lStyle & TCS_HOTTRACK) && (iItem == infoPtr->iHotTracked)
1664 && !(lStyle & TCS_FLATBUTTONS))
1665 | (TAB_GetItem(infoPtr, iItem)->dwState & TCIS_HIGHLIGHTED)) ?
1666 comctl32_color.clrHighlight : comctl32_color.clrBtnText);
1669 * if owner draw, tell the owner to draw
1671 if ((lStyle & TCS_OWNERDRAWFIXED) && GetParent(infoPtr->hwnd))
1673 DRAWITEMSTRUCT dis;
1674 UINT id;
1676 drawRect->top += 2;
1677 drawRect->right -= 1;
1678 if ( iItem == infoPtr->iSelected )
1680 drawRect->right -= 1;
1681 drawRect->left += 1;
1685 * get the control id
1687 id = (UINT)GetWindowLongPtrW( infoPtr->hwnd, GWLP_ID );
1690 * put together the DRAWITEMSTRUCT
1692 dis.CtlType = ODT_TAB;
1693 dis.CtlID = id;
1694 dis.itemID = iItem;
1695 dis.itemAction = ODA_DRAWENTIRE;
1696 dis.itemState = 0;
1697 if ( iItem == infoPtr->iSelected )
1698 dis.itemState |= ODS_SELECTED;
1699 if (infoPtr->uFocus == iItem)
1700 dis.itemState |= ODS_FOCUS;
1701 dis.hwndItem = infoPtr->hwnd;
1702 dis.hDC = hdc;
1703 CopyRect(&dis.rcItem,drawRect);
1704 dis.itemData = (ULONG_PTR)TAB_GetItem(infoPtr, iItem)->extra;
1707 * send the draw message
1709 SendMessageW( infoPtr->hwndNotify, WM_DRAWITEM, (WPARAM)id, (LPARAM)&dis );
1711 else
1713 TAB_ITEM *item = TAB_GetItem(infoPtr, iItem);
1714 RECT rcTemp;
1715 RECT rcImage;
1717 /* used to center the icon and text in the tab */
1718 RECT rcText;
1719 INT center_offset_h, center_offset_v;
1721 /* set rcImage to drawRect, we will use top & left in our ImageList_Draw call */
1722 rcImage = *drawRect;
1724 rcTemp = *drawRect;
1726 rcText.left = rcText.top = rcText.right = rcText.bottom = 0;
1728 /* get the rectangle that the text fits in */
1729 if (item->pszText)
1731 DrawTextW(hdc, item->pszText, -1, &rcText, DT_CALCRECT);
1734 * If not owner draw, then do the drawing ourselves.
1736 * Draw the icon.
1738 if (infoPtr->himl && item->iImage != -1)
1740 INT cx;
1741 INT cy;
1743 ImageList_GetIconSize(infoPtr->himl, &cx, &cy);
1745 if(lStyle & TCS_VERTICAL)
1747 center_offset_h = ((drawRect->bottom - drawRect->top) - (cy + infoPtr->uHItemPadding + (rcText.right - rcText.left))) / 2;
1748 center_offset_v = (drawRect->left + (drawRect->right - drawRect->left) - cx) / 2;
1750 else
1752 center_offset_h = ((drawRect->right - drawRect->left) - (cx + infoPtr->uHItemPadding + (rcText.right - rcText.left))) / 2;
1753 center_offset_v = (drawRect->top + (drawRect->bottom - drawRect->top) - cy) / 2;
1756 /* if an item is selected, the icon is shifted up instead of down */
1757 if (iItem == infoPtr->iSelected)
1758 center_offset_v -= infoPtr->uVItemPadding / 2;
1759 else
1760 center_offset_v += infoPtr->uVItemPadding / 2;
1762 if (lStyle & TCS_FIXEDWIDTH && lStyle & (TCS_FORCELABELLEFT | TCS_FORCEICONLEFT))
1763 center_offset_h = infoPtr->uHItemPadding;
1765 if (center_offset_h < 2)
1766 center_offset_h = 2;
1768 if (center_offset_v < 0)
1769 center_offset_v = 0;
1771 TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%d,%d)-(%d,%d), textlen=%d\n",
1772 debugstr_w(item->pszText), center_offset_h, center_offset_v,
1773 drawRect->left, drawRect->top, drawRect->right, drawRect->bottom,
1774 (rcText.right-rcText.left));
1776 if((lStyle & TCS_VERTICAL) && (lStyle & TCS_BOTTOM))
1778 rcImage.top = drawRect->top + center_offset_h;
1779 /* if tab is TCS_VERTICAL and TCS_BOTTOM, the text is drawn from the */
1780 /* right side of the tab, but the image still uses the left as its x position */
1781 /* this keeps the image always drawn off of the same side of the tab */
1782 rcImage.left = drawRect->right - cx - center_offset_v;
1783 drawRect->top += cy + infoPtr->uHItemPadding;
1785 else if(lStyle & TCS_VERTICAL)
1787 rcImage.top = drawRect->bottom - cy - center_offset_h;
1788 rcImage.left = drawRect->left + center_offset_v;
1789 drawRect->bottom -= cy + infoPtr->uHItemPadding;
1791 else /* normal style, whether TCS_BOTTOM or not */
1793 rcImage.left = drawRect->left + center_offset_h;
1794 rcImage.top = drawRect->top + center_offset_v;
1795 drawRect->left += cx + infoPtr->uHItemPadding;
1798 TRACE("drawing image=%d, left=%d, top=%d\n",
1799 item->iImage, rcImage.left, rcImage.top-1);
1800 ImageList_Draw
1802 infoPtr->himl,
1803 item->iImage,
1804 hdc,
1805 rcImage.left,
1806 rcImage.top,
1807 ILD_NORMAL
1811 /* Now position text */
1812 if (lStyle & TCS_FIXEDWIDTH && lStyle & TCS_FORCELABELLEFT)
1813 center_offset_h = infoPtr->uHItemPadding;
1814 else
1815 if(lStyle & TCS_VERTICAL)
1816 center_offset_h = ((drawRect->bottom - drawRect->top) - (rcText.right - rcText.left)) / 2;
1817 else
1818 center_offset_h = ((drawRect->right - drawRect->left) - (rcText.right - rcText.left)) / 2;
1820 if(lStyle & TCS_VERTICAL)
1822 if(lStyle & TCS_BOTTOM)
1823 drawRect->top+=center_offset_h;
1824 else
1825 drawRect->bottom-=center_offset_h;
1827 center_offset_v = ((drawRect->right - drawRect->left) - (rcText.bottom - rcText.top)) / 2;
1829 else
1831 drawRect->left += center_offset_h;
1832 center_offset_v = ((drawRect->bottom - drawRect->top) - (rcText.bottom - rcText.top)) / 2;
1835 /* if an item is selected, the text is shifted up instead of down */
1836 if (iItem == infoPtr->iSelected)
1837 center_offset_v -= infoPtr->uVItemPadding / 2;
1838 else
1839 center_offset_v += infoPtr->uVItemPadding / 2;
1841 if (center_offset_v < 0)
1842 center_offset_v = 0;
1844 if(lStyle & TCS_VERTICAL)
1845 drawRect->left += center_offset_v;
1846 else
1847 drawRect->top += center_offset_v;
1849 /* Draw the text */
1850 if(lStyle & TCS_VERTICAL) /* if we are vertical rotate the text and each character */
1852 static const WCHAR ArialW[] = { 'A','r','i','a','l',0 };
1853 LOGFONTW logfont;
1854 HFONT hFont = 0;
1855 INT nEscapement = 900;
1856 INT nOrientation = 900;
1858 if(lStyle & TCS_BOTTOM)
1860 nEscapement = -900;
1861 nOrientation = -900;
1864 /* to get a font with the escapement and orientation we are looking for, we need to */
1865 /* call CreateFontIndirectA, which requires us to set the values of the logfont we pass in */
1866 if (!GetObjectW((infoPtr->hFont) ?
1867 infoPtr->hFont : GetStockObject(SYSTEM_FONT),
1868 sizeof(LOGFONTW),&logfont))
1870 INT iPointSize = 9;
1872 lstrcpyW(logfont.lfFaceName, ArialW);
1873 logfont.lfHeight = -MulDiv(iPointSize, GetDeviceCaps(hdc, LOGPIXELSY),
1874 72);
1875 logfont.lfWeight = FW_NORMAL;
1876 logfont.lfItalic = 0;
1877 logfont.lfUnderline = 0;
1878 logfont.lfStrikeOut = 0;
1881 logfont.lfEscapement = nEscapement;
1882 logfont.lfOrientation = nOrientation;
1883 hFont = CreateFontIndirectW(&logfont);
1884 SelectObject(hdc, hFont);
1886 if (item->pszText)
1888 ExtTextOutW(hdc,
1889 (lStyle & TCS_BOTTOM) ? drawRect->right : drawRect->left,
1890 (!(lStyle & TCS_BOTTOM)) ? drawRect->bottom : drawRect->top,
1891 ETO_CLIPPED,
1892 drawRect,
1893 item->pszText,
1894 lstrlenW(item->pszText),
1898 DeleteObject(hFont);
1900 else
1902 TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%d,%d)-(%d,%d), textlen=%d\n",
1903 debugstr_w(item->pszText), center_offset_h, center_offset_v,
1904 drawRect->left, drawRect->top, drawRect->right, drawRect->bottom,
1905 (rcText.right-rcText.left));
1906 if (item->pszText)
1908 DrawTextW
1910 hdc,
1911 item->pszText,
1912 lstrlenW(item->pszText),
1913 drawRect,
1914 DT_LEFT | DT_SINGLELINE
1919 *drawRect = rcTemp; /* restore drawRect */
1923 * Cleanup
1925 SelectObject(hdc, hOldFont);
1926 SetBkMode(hdc, oldBkMode);
1927 SelectObject(hdc, holdPen);
1928 DeleteObject( htextPen );
1931 /******************************************************************************
1932 * TAB_DrawItem
1934 * This method is used to draw a single tab into the tab control.
1936 static void TAB_DrawItem(
1937 TAB_INFO *infoPtr,
1938 HDC hdc,
1939 INT iItem)
1941 LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1942 RECT itemRect;
1943 RECT selectedRect;
1944 BOOL isVisible;
1945 RECT r, fillRect, r1;
1946 INT clRight = 0;
1947 INT clBottom = 0;
1948 COLORREF bkgnd, corner;
1949 HTHEME theme;
1952 * Get the rectangle for the item.
1954 isVisible = TAB_InternalGetItemRect(infoPtr,
1955 iItem,
1956 &itemRect,
1957 &selectedRect);
1959 if (isVisible)
1961 RECT rUD, rC;
1963 /* Clip UpDown control to not draw over it */
1964 if (infoPtr->needsScrolling)
1966 GetWindowRect(infoPtr->hwnd, &rC);
1967 GetWindowRect(infoPtr->hwndUpDown, &rUD);
1968 ExcludeClipRect(hdc, rUD.left - rC.left, rUD.top - rC.top, rUD.right - rC.left, rUD.bottom - rC.top);
1971 /* If you need to see what the control is doing,
1972 * then override these variables. They will change what
1973 * fill colors are used for filling the tabs, and the
1974 * corners when drawing the edge.
1976 bkgnd = comctl32_color.clrBtnFace;
1977 corner = comctl32_color.clrBtnFace;
1979 if (lStyle & TCS_BUTTONS)
1981 /* Get item rectangle */
1982 r = itemRect;
1984 /* Separators between flat buttons */
1985 if (lStyle & TCS_FLATBUTTONS)
1987 r1 = r;
1988 r1.right += (FLAT_BTN_SPACINGX -2);
1989 DrawEdge(hdc, &r1, EDGE_ETCHED, BF_RIGHT);
1992 if (iItem == infoPtr->iSelected)
1994 DrawEdge(hdc, &r, EDGE_SUNKEN, BF_SOFT|BF_RECT);
1996 OffsetRect(&r, 1, 1);
1998 else /* ! selected */
2000 if (!(lStyle & TCS_FLATBUTTONS))
2001 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_RECT);
2004 else /* !TCS_BUTTONS */
2006 /* We draw a rectangle of different sizes depending on the selection
2007 * state. */
2008 if (iItem == infoPtr->iSelected) {
2009 RECT rect;
2010 GetClientRect (infoPtr->hwnd, &rect);
2011 clRight = rect.right;
2012 clBottom = rect.bottom;
2013 r = selectedRect;
2015 else
2016 r = itemRect;
2019 * Erase the background. (Delay it but setup rectangle.)
2020 * This is necessary when drawing the selected item since it is larger
2021 * than the others, it might overlap with stuff already drawn by the
2022 * other tabs
2024 fillRect = r;
2026 /* Draw themed tabs - but only if they are at the top.
2027 * Windows draws even side or bottom tabs themed, with wacky results.
2028 * However, since in Wine apps may get themed that did not opt in via
2029 * a manifest avoid theming when we know the result will be wrong */
2030 if ((theme = GetWindowTheme (infoPtr->hwnd))
2031 && ((lStyle & (TCS_VERTICAL | TCS_BOTTOM)) == 0))
2033 static const int partIds[8] = {
2034 /* Normal item */
2035 TABP_TABITEM,
2036 TABP_TABITEMLEFTEDGE,
2037 TABP_TABITEMRIGHTEDGE,
2038 TABP_TABITEMBOTHEDGE,
2039 /* Selected tab */
2040 TABP_TOPTABITEM,
2041 TABP_TOPTABITEMLEFTEDGE,
2042 TABP_TOPTABITEMRIGHTEDGE,
2043 TABP_TOPTABITEMBOTHEDGE,
2045 int partIndex = 0;
2046 int stateId = TIS_NORMAL;
2048 /* selected and unselected tabs have different parts */
2049 if (iItem == infoPtr->iSelected)
2050 partIndex += 4;
2051 /* The part also differs on the position of a tab on a line.
2052 * "Visually" determining the position works well enough. */
2053 if(selectedRect.left == 0)
2054 partIndex += 1;
2055 if(selectedRect.right == clRight)
2056 partIndex += 2;
2058 if (iItem == infoPtr->iSelected)
2059 stateId = TIS_SELECTED;
2060 else if (iItem == infoPtr->iHotTracked)
2061 stateId = TIS_HOT;
2062 else if (iItem == infoPtr->uFocus)
2063 stateId = TIS_FOCUSED;
2065 /* Adjust rectangle for bottommost row */
2066 if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2067 r.bottom += 3;
2069 DrawThemeBackground (theme, hdc, partIds[partIndex], stateId, &r, NULL);
2070 GetThemeBackgroundContentRect (theme, hdc, partIds[partIndex], stateId, &r, &r);
2072 else if(lStyle & TCS_VERTICAL)
2074 /* These are for adjusting the drawing of a Selected tab */
2075 /* The initial values are for the normal case of non-Selected */
2076 int ZZ = 1; /* Do not strech if selected */
2077 if (iItem == infoPtr->iSelected) {
2078 ZZ = 0;
2080 /* if leftmost draw the line longer */
2081 if(selectedRect.top == 0)
2082 fillRect.top += CONTROL_BORDER_SIZEY;
2083 /* if rightmost draw the line longer */
2084 if(selectedRect.bottom == clBottom)
2085 fillRect.bottom -= CONTROL_BORDER_SIZEY;
2088 if (lStyle & TCS_BOTTOM)
2090 /* Adjust both rectangles to match native */
2091 r.left += (1-ZZ);
2093 TRACE("<right> item=%d, fill=(%d,%d)-(%d,%d), edge=(%d,%d)-(%d,%d)\n",
2094 iItem,
2095 fillRect.left,fillRect.top,fillRect.right,fillRect.bottom,
2096 r.left,r.top,r.right,r.bottom);
2098 /* Clear interior */
2099 SetBkColor(hdc, bkgnd);
2100 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2102 /* Draw rectangular edge around tab */
2103 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_RIGHT|BF_TOP|BF_BOTTOM);
2105 /* Now erase the top corner and draw diagonal edge */
2106 SetBkColor(hdc, corner);
2107 r1.left = r.right - ROUND_CORNER_SIZE - 1;
2108 r1.top = r.top;
2109 r1.right = r.right;
2110 r1.bottom = r1.top + ROUND_CORNER_SIZE;
2111 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2112 r1.right--;
2113 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPLEFT);
2115 /* Now erase the bottom corner and draw diagonal edge */
2116 r1.left = r.right - ROUND_CORNER_SIZE - 1;
2117 r1.bottom = r.bottom;
2118 r1.right = r.right;
2119 r1.top = r1.bottom - ROUND_CORNER_SIZE;
2120 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2121 r1.right--;
2122 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMLEFT);
2124 if ((iItem == infoPtr->iSelected) && (selectedRect.top == 0)) {
2125 r1 = r;
2126 r1.right = r1.left;
2127 r1.left--;
2128 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_TOP);
2132 else
2134 TRACE("<left> item=%d, fill=(%d,%d)-(%d,%d), edge=(%d,%d)-(%d,%d)\n",
2135 iItem,
2136 fillRect.left,fillRect.top,fillRect.right,fillRect.bottom,
2137 r.left,r.top,r.right,r.bottom);
2139 /* Clear interior */
2140 SetBkColor(hdc, bkgnd);
2141 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2143 /* Draw rectangular edge around tab */
2144 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_TOP|BF_BOTTOM);
2146 /* Now erase the top corner and draw diagonal edge */
2147 SetBkColor(hdc, corner);
2148 r1.left = r.left;
2149 r1.top = r.top;
2150 r1.right = r1.left + ROUND_CORNER_SIZE + 1;
2151 r1.bottom = r1.top + ROUND_CORNER_SIZE;
2152 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2153 r1.left++;
2154 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPRIGHT);
2156 /* Now erase the bottom corner and draw diagonal edge */
2157 r1.left = r.left;
2158 r1.bottom = r.bottom;
2159 r1.right = r1.left + ROUND_CORNER_SIZE + 1;
2160 r1.top = r1.bottom - ROUND_CORNER_SIZE;
2161 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2162 r1.left++;
2163 DrawEdge(hdc, &r1, EDGE_SUNKEN, BF_DIAGONAL_ENDTOPLEFT);
2166 else /* ! TCS_VERTICAL */
2168 /* These are for adjusting the drawing of a Selected tab */
2169 /* The initial values are for the normal case of non-Selected */
2170 if (iItem == infoPtr->iSelected) {
2171 /* if leftmost draw the line longer */
2172 if(selectedRect.left == 0)
2173 fillRect.left += CONTROL_BORDER_SIZEX;
2174 /* if rightmost draw the line longer */
2175 if(selectedRect.right == clRight)
2176 fillRect.right -= CONTROL_BORDER_SIZEX;
2179 if (lStyle & TCS_BOTTOM)
2181 /* Adjust both rectangles for topmost row */
2182 if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2184 fillRect.top -= 2;
2185 r.top -= 1;
2188 TRACE("<bottom> item=%d, fill=(%d,%d)-(%d,%d), edge=(%d,%d)-(%d,%d)\n",
2189 iItem,
2190 fillRect.left,fillRect.top,fillRect.right,fillRect.bottom,
2191 r.left,r.top,r.right,r.bottom);
2193 /* Clear interior */
2194 SetBkColor(hdc, bkgnd);
2195 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2197 /* Draw rectangular edge around tab */
2198 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_BOTTOM|BF_RIGHT);
2200 /* Now erase the righthand corner and draw diagonal edge */
2201 SetBkColor(hdc, corner);
2202 r1.left = r.right - ROUND_CORNER_SIZE;
2203 r1.bottom = r.bottom;
2204 r1.right = r.right;
2205 r1.top = r1.bottom - ROUND_CORNER_SIZE - 1;
2206 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2207 r1.bottom--;
2208 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMLEFT);
2210 /* Now erase the lefthand corner and draw diagonal edge */
2211 r1.left = r.left;
2212 r1.bottom = r.bottom;
2213 r1.right = r1.left + ROUND_CORNER_SIZE;
2214 r1.top = r1.bottom - ROUND_CORNER_SIZE - 1;
2215 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2216 r1.bottom--;
2217 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPLEFT);
2219 if (iItem == infoPtr->iSelected)
2221 r.top += 2;
2222 r.left += 1;
2223 if (selectedRect.left == 0)
2225 r1 = r;
2226 r1.bottom = r1.top;
2227 r1.top--;
2228 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_LEFT);
2233 else
2235 /* Adjust both rectangles for bottommost row */
2236 if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2238 fillRect.bottom += 3;
2239 r.bottom += 2;
2242 TRACE("<top> item=%d, fill=(%d,%d)-(%d,%d), edge=(%d,%d)-(%d,%d)\n",
2243 iItem,
2244 fillRect.left,fillRect.top,fillRect.right,fillRect.bottom,
2245 r.left,r.top,r.right,r.bottom);
2247 /* Clear interior */
2248 SetBkColor(hdc, bkgnd);
2249 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2251 /* Draw rectangular edge around tab */
2252 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_TOP|BF_RIGHT);
2254 /* Now erase the righthand corner and draw diagonal edge */
2255 SetBkColor(hdc, corner);
2256 r1.left = r.right - ROUND_CORNER_SIZE;
2257 r1.top = r.top;
2258 r1.right = r.right;
2259 r1.bottom = r1.top + ROUND_CORNER_SIZE + 1;
2260 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2261 r1.top++;
2262 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMRIGHT);
2264 /* Now erase the lefthand corner and draw diagonal edge */
2265 r1.left = r.left;
2266 r1.top = r.top;
2267 r1.right = r1.left + ROUND_CORNER_SIZE;
2268 r1.bottom = r1.top + ROUND_CORNER_SIZE + 1;
2269 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2270 r1.top++;
2271 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPRIGHT);
2276 TAB_DumpItemInternal(infoPtr, iItem);
2278 /* This modifies r to be the text rectangle. */
2279 TAB_DrawItemInterior(infoPtr, hdc, iItem, &r);
2283 /******************************************************************************
2284 * TAB_DrawBorder
2286 * This method is used to draw the raised border around the tab control
2287 * "content" area.
2289 static void TAB_DrawBorder (TAB_INFO *infoPtr, HDC hdc)
2291 RECT rect;
2292 DWORD lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
2293 HTHEME theme = GetWindowTheme (infoPtr->hwnd);
2295 GetClientRect (infoPtr->hwnd, &rect);
2298 * Adjust for the style
2301 if (infoPtr->uNumItem)
2303 if ((lStyle & TCS_BOTTOM) && !(lStyle & TCS_VERTICAL))
2304 rect.bottom -= infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2305 else if((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
2306 rect.right -= infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2307 else if(lStyle & TCS_VERTICAL)
2308 rect.left += infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2309 else /* not TCS_VERTICAL and not TCS_BOTTOM */
2310 rect.top += infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2313 TRACE("border=(%d,%d)-(%d,%d)\n",
2314 rect.left, rect.top, rect.right, rect.bottom);
2316 if (theme)
2317 DrawThemeBackground (theme, hdc, TABP_PANE, 0, &rect, NULL);
2318 else
2319 DrawEdge(hdc, &rect, EDGE_RAISED, BF_SOFT|BF_RECT);
2322 /******************************************************************************
2323 * TAB_Refresh
2325 * This method repaints the tab control..
2327 static void TAB_Refresh (TAB_INFO *infoPtr, HDC hdc)
2329 HFONT hOldFont;
2330 INT i;
2332 if (!infoPtr->DoRedraw)
2333 return;
2335 hOldFont = SelectObject (hdc, infoPtr->hFont);
2337 if (GetWindowLongW(infoPtr->hwnd, GWL_STYLE) & TCS_BUTTONS)
2339 for (i = 0; i < infoPtr->uNumItem; i++)
2340 TAB_DrawItem (infoPtr, hdc, i);
2342 else
2344 /* Draw all the non selected item first */
2345 for (i = 0; i < infoPtr->uNumItem; i++)
2347 if (i != infoPtr->iSelected)
2348 TAB_DrawItem (infoPtr, hdc, i);
2351 /* Now, draw the border, draw it before the selected item
2352 * since the selected item overwrites part of the border. */
2353 TAB_DrawBorder (infoPtr, hdc);
2355 /* Then, draw the selected item */
2356 TAB_DrawItem (infoPtr, hdc, infoPtr->iSelected);
2358 /* If we haven't set the current focus yet, set it now.
2359 * Only happens when we first paint the tab controls */
2360 if (infoPtr->uFocus == -1)
2361 TAB_SetCurFocus(infoPtr, infoPtr->iSelected);
2364 SelectObject (hdc, hOldFont);
2367 static inline DWORD TAB_GetRowCount (const TAB_INFO *infoPtr)
2369 return infoPtr->uNumRows;
2372 static inline LRESULT TAB_SetRedraw (TAB_INFO *infoPtr, BOOL doRedraw)
2374 infoPtr->DoRedraw = doRedraw;
2375 return 0;
2378 /******************************************************************************
2379 * TAB_EnsureSelectionVisible
2381 * This method will make sure that the current selection is completely
2382 * visible by scrolling until it is.
2384 static void TAB_EnsureSelectionVisible(
2385 TAB_INFO* infoPtr)
2387 INT iSelected = infoPtr->iSelected;
2388 LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
2389 INT iOrigLeftmostVisible = infoPtr->leftmostVisible;
2391 /* set the items row to the bottommost row or topmost row depending on
2392 * style */
2393 if ((infoPtr->uNumRows > 1) && !(lStyle & TCS_BUTTONS))
2395 TAB_ITEM *selected = TAB_GetItem(infoPtr, iSelected);
2396 INT newselected;
2397 INT iTargetRow;
2399 if(lStyle & TCS_VERTICAL)
2400 newselected = selected->rect.left;
2401 else
2402 newselected = selected->rect.top;
2404 /* the target row is always (number of rows - 1)
2405 as row 0 is furthest from the clientRect */
2406 iTargetRow = infoPtr->uNumRows - 1;
2408 if (newselected != iTargetRow)
2410 UINT i;
2411 if(lStyle & TCS_VERTICAL)
2413 for (i=0; i < infoPtr->uNumItem; i++)
2415 /* move everything in the row of the selected item to the iTargetRow */
2416 TAB_ITEM *item = TAB_GetItem(infoPtr, i);
2418 if (item->rect.left == newselected )
2419 item->rect.left = iTargetRow;
2420 else
2422 if (item->rect.left > newselected)
2423 item->rect.left-=1;
2427 else
2429 for (i=0; i < infoPtr->uNumItem; i++)
2431 TAB_ITEM *item = TAB_GetItem(infoPtr, i);
2433 if (item->rect.top == newselected )
2434 item->rect.top = iTargetRow;
2435 else
2437 if (item->rect.top > newselected)
2438 item->rect.top-=1;
2442 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
2447 * Do the trivial cases first.
2449 if ( (!infoPtr->needsScrolling) ||
2450 (infoPtr->hwndUpDown==0) || (lStyle & TCS_VERTICAL))
2451 return;
2453 if (infoPtr->leftmostVisible >= iSelected)
2455 infoPtr->leftmostVisible = iSelected;
2457 else
2459 TAB_ITEM *selected = TAB_GetItem(infoPtr, iSelected);
2460 RECT r;
2461 INT width;
2462 UINT i;
2464 /* Calculate the part of the client area that is visible */
2465 GetClientRect(infoPtr->hwnd, &r);
2466 width = r.right;
2468 GetClientRect(infoPtr->hwndUpDown, &r);
2469 width -= r.right;
2471 if ((selected->rect.right -
2472 selected->rect.left) >= width )
2474 /* Special case: width of selected item is greater than visible
2475 * part of control.
2477 infoPtr->leftmostVisible = iSelected;
2479 else
2481 for (i = infoPtr->leftmostVisible; i < infoPtr->uNumItem; i++)
2483 if ((selected->rect.right - TAB_GetItem(infoPtr, i)->rect.left) < width)
2484 break;
2486 infoPtr->leftmostVisible = i;
2490 if (infoPtr->leftmostVisible != iOrigLeftmostVisible)
2491 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
2493 SendMessageW(infoPtr->hwndUpDown, UDM_SETPOS, 0,
2494 MAKELONG(infoPtr->leftmostVisible, 0));
2497 /******************************************************************************
2498 * TAB_InvalidateTabArea
2500 * This method will invalidate the portion of the control that contains the
2501 * tabs. It is called when the state of the control changes and needs
2502 * to be redisplayed
2504 static void TAB_InvalidateTabArea(TAB_INFO* infoPtr)
2506 RECT clientRect, rInvalidate, rAdjClient;
2507 DWORD lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
2508 INT lastRow = infoPtr->uNumRows - 1;
2509 RECT rect;
2511 if (lastRow < 0) return;
2513 GetClientRect(infoPtr->hwnd, &clientRect);
2514 rInvalidate = clientRect;
2515 rAdjClient = clientRect;
2517 TAB_AdjustRect(infoPtr, 0, &rAdjClient);
2519 TAB_InternalGetItemRect(infoPtr, infoPtr->uNumItem-1 , &rect, NULL);
2520 if ((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
2522 rInvalidate.left = rAdjClient.right;
2523 if (infoPtr->uNumRows == 1)
2524 rInvalidate.bottom = clientRect.top + rect.bottom + 2 * SELECTED_TAB_OFFSET;
2526 else if(lStyle & TCS_VERTICAL)
2528 rInvalidate.right = rAdjClient.left;
2529 if (infoPtr->uNumRows == 1)
2530 rInvalidate.bottom = clientRect.top + rect.bottom + 2 * SELECTED_TAB_OFFSET;
2532 else if (lStyle & TCS_BOTTOM)
2534 rInvalidate.top = rAdjClient.bottom;
2535 if (infoPtr->uNumRows == 1)
2536 rInvalidate.right = clientRect.left + rect.right + 2 * SELECTED_TAB_OFFSET;
2538 else
2540 rInvalidate.bottom = rAdjClient.top;
2541 if (infoPtr->uNumRows == 1)
2542 rInvalidate.right = clientRect.left + rect.right + 2 * SELECTED_TAB_OFFSET;
2545 /* Punch out the updown control */
2546 if (infoPtr->needsScrolling && (rInvalidate.right > 0)) {
2547 RECT r;
2548 GetClientRect(infoPtr->hwndUpDown, &r);
2549 if (rInvalidate.right > clientRect.right - r.left)
2550 rInvalidate.right = rInvalidate.right - (r.right - r.left);
2551 else
2552 rInvalidate.right = clientRect.right - r.left;
2555 TRACE("invalidate (%d,%d)-(%d,%d)\n",
2556 rInvalidate.left, rInvalidate.top,
2557 rInvalidate.right, rInvalidate.bottom);
2559 InvalidateRect(infoPtr->hwnd, &rInvalidate, TRUE);
2562 static inline LRESULT TAB_Paint (TAB_INFO *infoPtr, HDC hdcPaint)
2564 HDC hdc;
2565 PAINTSTRUCT ps;
2567 if (hdcPaint)
2568 hdc = hdcPaint;
2569 else
2571 hdc = BeginPaint (infoPtr->hwnd, &ps);
2572 TRACE("erase %d, rect=(%d,%d)-(%d,%d)\n",
2573 ps.fErase,
2574 ps.rcPaint.left,ps.rcPaint.top,ps.rcPaint.right,ps.rcPaint.bottom);
2577 TAB_Refresh (infoPtr, hdc);
2579 if (!hdcPaint)
2580 EndPaint (infoPtr->hwnd, &ps);
2582 return 0;
2585 static LRESULT
2586 TAB_InsertItemT (TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam, BOOL bUnicode)
2588 TAB_ITEM *item;
2589 TCITEMW *pti;
2590 INT iItem;
2591 RECT rect;
2593 GetClientRect (infoPtr->hwnd, &rect);
2594 TRACE("Rect: %p T %i, L %i, B %i, R %i\n", infoPtr->hwnd,
2595 rect.top, rect.left, rect.bottom, rect.right);
2597 pti = (TCITEMW *)lParam;
2598 iItem = (INT)wParam;
2600 if (iItem < 0) return -1;
2601 if (iItem > infoPtr->uNumItem)
2602 iItem = infoPtr->uNumItem;
2604 TAB_DumpItemExternalT(pti, iItem, bUnicode);
2607 if (infoPtr->uNumItem == 0) {
2608 infoPtr->items = Alloc (TAB_ITEM_SIZE(infoPtr));
2609 infoPtr->uNumItem++;
2610 infoPtr->iSelected = 0;
2612 else {
2613 LPBYTE oldItems = (LPBYTE)infoPtr->items;
2615 infoPtr->uNumItem++;
2616 infoPtr->items = Alloc (TAB_ITEM_SIZE(infoPtr) * infoPtr->uNumItem);
2618 /* pre insert copy */
2619 if (iItem > 0) {
2620 memcpy (infoPtr->items, oldItems,
2621 iItem * TAB_ITEM_SIZE(infoPtr));
2624 /* post insert copy */
2625 if (iItem < infoPtr->uNumItem - 1) {
2626 memcpy (TAB_GetItem(infoPtr, iItem + 1),
2627 oldItems + iItem * TAB_ITEM_SIZE(infoPtr),
2628 (infoPtr->uNumItem - iItem - 1) * TAB_ITEM_SIZE(infoPtr));
2632 if (iItem <= infoPtr->iSelected)
2633 infoPtr->iSelected++;
2635 Free (oldItems);
2638 item = TAB_GetItem(infoPtr, iItem);
2640 item->pszText = NULL;
2642 if (pti->mask & TCIF_TEXT)
2644 if (bUnicode)
2645 Str_SetPtrW (&item->pszText, pti->pszText);
2646 else
2647 Str_SetPtrAtoW (&item->pszText, (LPSTR)pti->pszText);
2650 if (pti->mask & TCIF_IMAGE)
2651 item->iImage = pti->iImage;
2652 else
2653 item->iImage = -1;
2655 if (pti->mask & TCIF_PARAM)
2656 memcpy(item->extra, &pti->lParam, infoPtr->cbInfo);
2657 else
2658 memset(item->extra, 0, infoPtr->cbInfo);
2660 TAB_SetItemBounds(infoPtr);
2661 if (infoPtr->uNumItem > 1)
2662 TAB_InvalidateTabArea(infoPtr);
2663 else
2664 InvalidateRect(infoPtr->hwnd, NULL, TRUE);
2666 TRACE("[%p]: added item %d %s\n",
2667 infoPtr->hwnd, iItem, debugstr_w(item->pszText));
2669 return iItem;
2672 static LRESULT
2673 TAB_SetItemSize (TAB_INFO *infoPtr, LPARAM lParam)
2675 LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
2676 LONG lResult = 0;
2677 BOOL bNeedPaint = FALSE;
2679 lResult = MAKELONG(infoPtr->tabWidth, infoPtr->tabHeight);
2681 /* UNDOCUMENTED: If requested Width or Height is 0 this means that program wants to use auto size. */
2682 if (lStyle & TCS_FIXEDWIDTH && (infoPtr->tabWidth != (INT)LOWORD(lParam)))
2684 infoPtr->tabWidth = (INT)LOWORD(lParam);
2685 bNeedPaint = TRUE;
2688 if (infoPtr->tabHeight != (INT)HIWORD(lParam))
2690 if ((infoPtr->fHeightSet = ((INT)HIWORD(lParam) != 0)))
2691 infoPtr->tabHeight = (INT)HIWORD(lParam);
2693 bNeedPaint = TRUE;
2695 TRACE("was h=%d,w=%d, now h=%d,w=%d\n",
2696 HIWORD(lResult), LOWORD(lResult),
2697 infoPtr->tabHeight, infoPtr->tabWidth);
2699 if (bNeedPaint)
2701 TAB_SetItemBounds(infoPtr);
2702 RedrawWindow(infoPtr->hwnd, NULL, NULL, RDW_ERASE | RDW_INVALIDATE | RDW_UPDATENOW);
2705 return lResult;
2708 static inline LRESULT TAB_SetMinTabWidth (TAB_INFO *infoPtr, INT cx)
2710 INT oldcx = 0;
2712 TRACE("(%p,%d)\n", infoPtr, cx);
2714 if (infoPtr) {
2715 oldcx = infoPtr->tabMinWidth;
2716 infoPtr->tabMinWidth = cx;
2718 TAB_SetItemBounds(infoPtr);
2720 return oldcx;
2723 static inline LRESULT
2724 TAB_HighlightItem (TAB_INFO *infoPtr, INT iItem, BOOL fHighlight)
2726 LPDWORD lpState;
2728 TRACE("(%p,%d,%s)\n", infoPtr, iItem, fHighlight ? "true" : "false");
2730 if (!infoPtr || iItem < 0 || iItem >= infoPtr->uNumItem)
2731 return FALSE;
2733 lpState = &TAB_GetItem(infoPtr, iItem)->dwState;
2735 if (fHighlight)
2736 *lpState |= TCIS_HIGHLIGHTED;
2737 else
2738 *lpState &= ~TCIS_HIGHLIGHTED;
2740 return TRUE;
2743 static LRESULT
2744 TAB_SetItemT (TAB_INFO *infoPtr, INT iItem, LPTCITEMW tabItem, BOOL bUnicode)
2746 TAB_ITEM *wineItem;
2748 TRACE("(%p,%d,%p,%s)\n", infoPtr, iItem, tabItem, bUnicode ? "true" : "false");
2750 if (iItem < 0 || iItem >= infoPtr->uNumItem)
2751 return FALSE;
2753 TAB_DumpItemExternalT(tabItem, iItem, bUnicode);
2755 wineItem = TAB_GetItem(infoPtr, iItem);
2757 if (tabItem->mask & TCIF_IMAGE)
2758 wineItem->iImage = tabItem->iImage;
2760 if (tabItem->mask & TCIF_PARAM)
2761 memcpy(wineItem->extra, &tabItem->lParam, infoPtr->cbInfo);
2763 if (tabItem->mask & TCIF_RTLREADING)
2764 FIXME("TCIF_RTLREADING\n");
2766 if (tabItem->mask & TCIF_STATE)
2767 wineItem->dwState = tabItem->dwState;
2769 if (tabItem->mask & TCIF_TEXT)
2771 Free(wineItem->pszText);
2772 wineItem->pszText = NULL;
2773 if (bUnicode)
2774 Str_SetPtrW(&wineItem->pszText, tabItem->pszText);
2775 else
2776 Str_SetPtrAtoW(&wineItem->pszText, (LPSTR)tabItem->pszText);
2779 /* Update and repaint tabs */
2780 TAB_SetItemBounds(infoPtr);
2781 TAB_InvalidateTabArea(infoPtr);
2783 return TRUE;
2786 static inline LRESULT TAB_GetItemCount (const TAB_INFO *infoPtr)
2788 return infoPtr->uNumItem;
2792 static LRESULT
2793 TAB_GetItemT (TAB_INFO *infoPtr, INT iItem, LPTCITEMW tabItem, BOOL bUnicode)
2795 TAB_ITEM *wineItem;
2797 TRACE("(%p,%d,%p,%s)\n", infoPtr, iItem, tabItem, bUnicode ? "true" : "false");
2799 if (iItem < 0 || iItem >= infoPtr->uNumItem)
2800 return FALSE;
2802 wineItem = TAB_GetItem(infoPtr, iItem);
2804 if (tabItem->mask & TCIF_IMAGE)
2805 tabItem->iImage = wineItem->iImage;
2807 if (tabItem->mask & TCIF_PARAM)
2808 memcpy(&tabItem->lParam, wineItem->extra, infoPtr->cbInfo);
2810 if (tabItem->mask & TCIF_RTLREADING)
2811 FIXME("TCIF_RTLREADING\n");
2813 if (tabItem->mask & TCIF_STATE)
2814 tabItem->dwState = wineItem->dwState;
2816 if (tabItem->mask & TCIF_TEXT)
2818 if (bUnicode)
2819 Str_GetPtrW (wineItem->pszText, tabItem->pszText, tabItem->cchTextMax);
2820 else
2821 Str_GetPtrWtoA (wineItem->pszText, (LPSTR)tabItem->pszText, tabItem->cchTextMax);
2824 TAB_DumpItemExternalT(tabItem, iItem, bUnicode);
2826 return TRUE;
2830 static LRESULT TAB_DeleteItem (TAB_INFO *infoPtr, INT iItem)
2832 BOOL bResult = FALSE;
2834 TRACE("(%p, %d)\n", infoPtr, iItem);
2836 if ((iItem >= 0) && (iItem < infoPtr->uNumItem))
2838 TAB_ITEM *item = TAB_GetItem(infoPtr, iItem);
2839 LPBYTE oldItems = (LPBYTE)infoPtr->items;
2841 TAB_InvalidateTabArea(infoPtr);
2842 Free(item->pszText);
2843 infoPtr->uNumItem--;
2845 if (!infoPtr->uNumItem)
2847 infoPtr->items = NULL;
2848 if (infoPtr->iHotTracked >= 0)
2850 KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
2851 infoPtr->iHotTracked = -1;
2854 else
2856 infoPtr->items = Alloc(TAB_ITEM_SIZE(infoPtr) * infoPtr->uNumItem);
2858 if (iItem > 0)
2859 memcpy(infoPtr->items, oldItems, iItem * TAB_ITEM_SIZE(infoPtr));
2861 if (iItem < infoPtr->uNumItem)
2862 memcpy(TAB_GetItem(infoPtr, iItem),
2863 oldItems + (iItem + 1) * TAB_ITEM_SIZE(infoPtr),
2864 (infoPtr->uNumItem - iItem) * TAB_ITEM_SIZE(infoPtr));
2866 if (iItem <= infoPtr->iHotTracked)
2868 /* When tabs move left/up, the hot track item may change */
2869 FIXME("Recalc hot track\n");
2872 Free(oldItems);
2874 /* Readjust the selected index */
2875 if ((iItem == infoPtr->iSelected) && (iItem > 0))
2876 infoPtr->iSelected--;
2878 if (iItem < infoPtr->iSelected)
2879 infoPtr->iSelected--;
2881 if (infoPtr->uNumItem == 0)
2882 infoPtr->iSelected = -1;
2884 /* Reposition and repaint tabs */
2885 TAB_SetItemBounds(infoPtr);
2887 bResult = TRUE;
2890 return bResult;
2893 static inline LRESULT TAB_DeleteAllItems (TAB_INFO *infoPtr)
2895 TRACE("(%p)\n", infoPtr);
2896 while (infoPtr->uNumItem)
2897 TAB_DeleteItem (infoPtr, 0);
2898 return TRUE;
2902 static inline LRESULT TAB_GetFont (const TAB_INFO *infoPtr)
2904 TRACE("(%p) returning %p\n", infoPtr, infoPtr->hFont);
2905 return (LRESULT)infoPtr->hFont;
2908 static inline LRESULT TAB_SetFont (TAB_INFO *infoPtr, HFONT hNewFont)
2910 TRACE("(%p,%p)\n", infoPtr, hNewFont);
2912 infoPtr->hFont = hNewFont;
2914 TAB_SetItemBounds(infoPtr);
2916 TAB_InvalidateTabArea(infoPtr);
2918 return 0;
2922 static inline LRESULT TAB_GetImageList (const TAB_INFO *infoPtr)
2924 TRACE("\n");
2925 return (LRESULT)infoPtr->himl;
2928 static inline LRESULT TAB_SetImageList (TAB_INFO *infoPtr, HIMAGELIST himlNew)
2930 HIMAGELIST himlPrev = infoPtr->himl;
2931 TRACE("\n");
2932 infoPtr->himl = himlNew;
2933 return (LRESULT)himlPrev;
2936 static inline LRESULT TAB_GetUnicodeFormat (const TAB_INFO *infoPtr)
2938 return infoPtr->bUnicode;
2941 static inline LRESULT TAB_SetUnicodeFormat (TAB_INFO *infoPtr, BOOL bUnicode)
2943 BOOL bTemp = infoPtr->bUnicode;
2945 infoPtr->bUnicode = bUnicode;
2947 return bTemp;
2950 static inline LRESULT TAB_Size (TAB_INFO *infoPtr)
2952 /* I'm not really sure what the following code was meant to do.
2953 This is what it is doing:
2954 When WM_SIZE is sent with SIZE_RESTORED, the control
2955 gets positioned in the top left corner.
2957 RECT parent_rect;
2958 HWND parent;
2959 UINT uPosFlags,cx,cy;
2961 uPosFlags=0;
2962 if (!wParam) {
2963 parent = GetParent (hwnd);
2964 GetClientRect(parent, &parent_rect);
2965 cx=LOWORD (lParam);
2966 cy=HIWORD (lParam);
2967 if (GetWindowLongW(hwnd, GWL_STYLE) & CCS_NORESIZE)
2968 uPosFlags |= (SWP_NOSIZE | SWP_NOMOVE);
2970 SetWindowPos (hwnd, 0, parent_rect.left, parent_rect.top,
2971 cx, cy, uPosFlags | SWP_NOZORDER);
2972 } else {
2973 FIXME("WM_SIZE flag %x %lx not handled\n", wParam, lParam);
2974 } */
2976 /* Recompute the size/position of the tabs. */
2977 TAB_SetItemBounds (infoPtr);
2979 /* Force a repaint of the control. */
2980 InvalidateRect(infoPtr->hwnd, NULL, TRUE);
2982 return 0;
2986 static LRESULT TAB_Create (HWND hwnd, WPARAM wParam, LPARAM lParam)
2988 TAB_INFO *infoPtr;
2989 TEXTMETRICW fontMetrics;
2990 HDC hdc;
2991 HFONT hOldFont;
2992 DWORD dwStyle;
2994 infoPtr = (TAB_INFO *)Alloc (sizeof(TAB_INFO));
2996 SetWindowLongPtrW(hwnd, 0, (DWORD_PTR)infoPtr);
2998 infoPtr->hwnd = hwnd;
2999 infoPtr->hwndNotify = ((LPCREATESTRUCTW)lParam)->hwndParent;
3000 infoPtr->uNumItem = 0;
3001 infoPtr->uNumRows = 0;
3002 infoPtr->uHItemPadding = 6;
3003 infoPtr->uVItemPadding = 3;
3004 infoPtr->uHItemPadding_s = 6;
3005 infoPtr->uVItemPadding_s = 3;
3006 infoPtr->hFont = 0;
3007 infoPtr->items = 0;
3008 infoPtr->hcurArrow = LoadCursorW (0, (LPWSTR)IDC_ARROW);
3009 infoPtr->iSelected = -1;
3010 infoPtr->iHotTracked = -1;
3011 infoPtr->uFocus = -1;
3012 infoPtr->hwndToolTip = 0;
3013 infoPtr->DoRedraw = TRUE;
3014 infoPtr->needsScrolling = FALSE;
3015 infoPtr->hwndUpDown = 0;
3016 infoPtr->leftmostVisible = 0;
3017 infoPtr->fHeightSet = FALSE;
3018 infoPtr->bUnicode = IsWindowUnicode (hwnd);
3019 infoPtr->cbInfo = sizeof(LPARAM);
3021 TRACE("Created tab control, hwnd [%p]\n", hwnd);
3023 /* The tab control always has the WS_CLIPSIBLINGS style. Even
3024 if you don't specify it in CreateWindow. This is necessary in
3025 order for paint to work correctly. This follows windows behaviour. */
3026 dwStyle = GetWindowLongW(hwnd, GWL_STYLE);
3027 SetWindowLongW(hwnd, GWL_STYLE, dwStyle|WS_CLIPSIBLINGS);
3029 if (dwStyle & TCS_TOOLTIPS) {
3030 /* Create tooltip control */
3031 infoPtr->hwndToolTip =
3032 CreateWindowExW (0, TOOLTIPS_CLASSW, NULL, WS_POPUP,
3033 CW_USEDEFAULT, CW_USEDEFAULT,
3034 CW_USEDEFAULT, CW_USEDEFAULT,
3035 hwnd, 0, 0, 0);
3037 /* Send NM_TOOLTIPSCREATED notification */
3038 if (infoPtr->hwndToolTip) {
3039 NMTOOLTIPSCREATED nmttc;
3041 nmttc.hdr.hwndFrom = hwnd;
3042 nmttc.hdr.idFrom = GetWindowLongPtrW(hwnd, GWLP_ID);
3043 nmttc.hdr.code = NM_TOOLTIPSCREATED;
3044 nmttc.hwndToolTips = infoPtr->hwndToolTip;
3046 SendMessageW (infoPtr->hwndNotify, WM_NOTIFY,
3047 (WPARAM)GetWindowLongPtrW(hwnd, GWLP_ID), (LPARAM)&nmttc);
3051 OpenThemeData (infoPtr->hwnd, themeClass);
3054 * We need to get text information so we need a DC and we need to select
3055 * a font.
3057 hdc = GetDC(hwnd);
3058 hOldFont = SelectObject (hdc, GetStockObject (SYSTEM_FONT));
3060 /* Use the system font to determine the initial height of a tab. */
3061 GetTextMetricsW(hdc, &fontMetrics);
3064 * Make sure there is enough space for the letters + growing the
3065 * selected item + extra space for the selected item.
3067 infoPtr->tabHeight = fontMetrics.tmHeight + SELECTED_TAB_OFFSET +
3068 ((dwStyle & TCS_BUTTONS) ? 2 : 1) *
3069 infoPtr->uVItemPadding;
3071 /* Initialize the width of a tab. */
3072 if (dwStyle & TCS_FIXEDWIDTH)
3073 infoPtr->tabWidth = DEFAULT_TAB_WIDTH_FIXED;
3075 infoPtr->tabMinWidth = -1;
3077 TRACE("tabH=%d, tabW=%d\n", infoPtr->tabHeight, infoPtr->tabWidth);
3079 SelectObject (hdc, hOldFont);
3080 ReleaseDC(hwnd, hdc);
3082 return 0;
3085 static LRESULT
3086 TAB_Destroy (TAB_INFO *infoPtr)
3088 UINT iItem;
3090 if (!infoPtr)
3091 return 0;
3093 SetWindowLongPtrW(infoPtr->hwnd, 0, 0);
3095 if (infoPtr->items) {
3096 for (iItem = 0; iItem < infoPtr->uNumItem; iItem++) {
3097 if (TAB_GetItem(infoPtr, iItem)->pszText)
3098 Free (TAB_GetItem(infoPtr, iItem)->pszText);
3100 Free (infoPtr->items);
3103 if (infoPtr->hwndToolTip)
3104 DestroyWindow (infoPtr->hwndToolTip);
3106 if (infoPtr->hwndUpDown)
3107 DestroyWindow(infoPtr->hwndUpDown);
3109 if (infoPtr->iHotTracked >= 0)
3110 KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
3112 CloseThemeData (GetWindowTheme (infoPtr->hwnd));
3114 Free (infoPtr);
3115 return 0;
3118 /* update theme after a WM_THEMECHANGED message */
3119 static LRESULT theme_changed (TAB_INFO* infoPtr)
3121 HTHEME theme = GetWindowTheme (infoPtr->hwnd);
3122 CloseThemeData (theme);
3123 OpenThemeData (infoPtr->hwnd, themeClass);
3124 return 0;
3127 static LRESULT TAB_NCCalcSize(HWND hwnd, WPARAM wParam, LPARAM lParam)
3129 if (!wParam)
3130 return 0;
3131 return WVR_ALIGNTOP;
3134 static inline LRESULT
3135 TAB_SetItemExtra (TAB_INFO *infoPtr, INT cbInfo)
3137 if (!infoPtr || cbInfo <= 0)
3138 return FALSE;
3140 if (infoPtr->uNumItem)
3142 /* FIXME: MSDN says this is not allowed, but this hasn't been verified */
3143 return FALSE;
3146 infoPtr->cbInfo = cbInfo;
3147 return TRUE;
3150 static LRESULT WINAPI
3151 TAB_WindowProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
3153 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
3155 TRACE("hwnd=%p msg=%x wParam=%x lParam=%lx\n", hwnd, uMsg, wParam, lParam);
3156 if (!infoPtr && (uMsg != WM_CREATE))
3157 return DefWindowProcW (hwnd, uMsg, wParam, lParam);
3159 switch (uMsg)
3161 case TCM_GETIMAGELIST:
3162 return TAB_GetImageList (infoPtr);
3164 case TCM_SETIMAGELIST:
3165 return TAB_SetImageList (infoPtr, (HIMAGELIST)lParam);
3167 case TCM_GETITEMCOUNT:
3168 return TAB_GetItemCount (infoPtr);
3170 case TCM_GETITEMA:
3171 case TCM_GETITEMW:
3172 return TAB_GetItemT (infoPtr, (INT)wParam, (LPTCITEMW)lParam, uMsg == TCM_GETITEMW);
3174 case TCM_SETITEMA:
3175 case TCM_SETITEMW:
3176 return TAB_SetItemT (infoPtr, (INT)wParam, (LPTCITEMW)lParam, uMsg == TCM_SETITEMW);
3178 case TCM_DELETEITEM:
3179 return TAB_DeleteItem (infoPtr, (INT)wParam);
3181 case TCM_DELETEALLITEMS:
3182 return TAB_DeleteAllItems (infoPtr);
3184 case TCM_GETITEMRECT:
3185 return TAB_GetItemRect (infoPtr, wParam, lParam);
3187 case TCM_GETCURSEL:
3188 return TAB_GetCurSel (infoPtr);
3190 case TCM_HITTEST:
3191 return TAB_HitTest (infoPtr, (LPTCHITTESTINFO)lParam);
3193 case TCM_SETCURSEL:
3194 return TAB_SetCurSel (infoPtr, (INT)wParam);
3196 case TCM_INSERTITEMA:
3197 case TCM_INSERTITEMW:
3198 return TAB_InsertItemT (infoPtr, wParam, lParam, uMsg == TCM_INSERTITEMW);
3200 case TCM_SETITEMEXTRA:
3201 return TAB_SetItemExtra (infoPtr, (int)wParam);
3203 case TCM_ADJUSTRECT:
3204 return TAB_AdjustRect (infoPtr, (BOOL)wParam, (LPRECT)lParam);
3206 case TCM_SETITEMSIZE:
3207 return TAB_SetItemSize (infoPtr, lParam);
3209 case TCM_REMOVEIMAGE:
3210 FIXME("Unimplemented msg TCM_REMOVEIMAGE\n");
3211 return 0;
3213 case TCM_SETPADDING:
3214 return TAB_SetPadding (infoPtr, lParam);
3216 case TCM_GETROWCOUNT:
3217 return TAB_GetRowCount(infoPtr);
3219 case TCM_GETUNICODEFORMAT:
3220 return TAB_GetUnicodeFormat (infoPtr);
3222 case TCM_SETUNICODEFORMAT:
3223 return TAB_SetUnicodeFormat (infoPtr, (BOOL)wParam);
3225 case TCM_HIGHLIGHTITEM:
3226 return TAB_HighlightItem (infoPtr, (INT)wParam, (BOOL)LOWORD(lParam));
3228 case TCM_GETTOOLTIPS:
3229 return TAB_GetToolTips (infoPtr);
3231 case TCM_SETTOOLTIPS:
3232 return TAB_SetToolTips (infoPtr, (HWND)wParam);
3234 case TCM_GETCURFOCUS:
3235 return TAB_GetCurFocus (infoPtr);
3237 case TCM_SETCURFOCUS:
3238 return TAB_SetCurFocus (infoPtr, (INT)wParam);
3240 case TCM_SETMINTABWIDTH:
3241 return TAB_SetMinTabWidth(infoPtr, (INT)lParam);
3243 case TCM_DESELECTALL:
3244 FIXME("Unimplemented msg TCM_DESELECTALL\n");
3245 return 0;
3247 case TCM_GETEXTENDEDSTYLE:
3248 FIXME("Unimplemented msg TCM_GETEXTENDEDSTYLE\n");
3249 return 0;
3251 case TCM_SETEXTENDEDSTYLE:
3252 FIXME("Unimplemented msg TCM_SETEXTENDEDSTYLE\n");
3253 return 0;
3255 case WM_GETFONT:
3256 return TAB_GetFont (infoPtr);
3258 case WM_SETFONT:
3259 return TAB_SetFont (infoPtr, (HFONT)wParam);
3261 case WM_CREATE:
3262 return TAB_Create (hwnd, wParam, lParam);
3264 case WM_NCDESTROY:
3265 return TAB_Destroy (infoPtr);
3267 case WM_GETDLGCODE:
3268 return DLGC_WANTARROWS | DLGC_WANTCHARS;
3270 case WM_LBUTTONDOWN:
3271 return TAB_LButtonDown (infoPtr, wParam, lParam);
3273 case WM_LBUTTONUP:
3274 return TAB_LButtonUp (infoPtr);
3276 case WM_NOTIFY:
3277 return SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, wParam, lParam);
3279 case WM_RBUTTONDOWN:
3280 return TAB_RButtonDown (infoPtr);
3282 case WM_MOUSEMOVE:
3283 return TAB_MouseMove (infoPtr, wParam, lParam);
3285 case WM_PRINTCLIENT:
3286 case WM_PAINT:
3287 return TAB_Paint (infoPtr, (HDC)wParam);
3289 case WM_SIZE:
3290 return TAB_Size (infoPtr);
3292 case WM_SETREDRAW:
3293 return TAB_SetRedraw (infoPtr, (BOOL)wParam);
3295 case WM_HSCROLL:
3296 return TAB_OnHScroll(infoPtr, (int)LOWORD(wParam), (int)HIWORD(wParam), (HWND)lParam);
3298 case WM_STYLECHANGED:
3299 TAB_SetItemBounds (infoPtr);
3300 InvalidateRect(hwnd, NULL, TRUE);
3301 return 0;
3303 case WM_SYSCOLORCHANGE:
3304 COMCTL32_RefreshSysColors();
3305 return 0;
3307 case WM_THEMECHANGED:
3308 return theme_changed (infoPtr);
3310 case WM_KILLFOCUS:
3311 case WM_SETFOCUS:
3312 TAB_FocusChanging(infoPtr);
3313 break; /* Don't disturb normal focus behavior */
3315 case WM_KEYUP:
3316 return TAB_KeyUp(infoPtr, wParam);
3317 case WM_NCHITTEST:
3318 return TAB_NCHitTest(infoPtr, lParam);
3320 case WM_NCCALCSIZE:
3321 return TAB_NCCalcSize(hwnd, wParam, lParam);
3323 default:
3324 if (uMsg >= WM_USER && uMsg < WM_APP)
3325 WARN("unknown msg %04x wp=%08x lp=%08lx\n",
3326 uMsg, wParam, lParam);
3327 break;
3329 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
3333 void
3334 TAB_Register (void)
3336 WNDCLASSW wndClass;
3338 ZeroMemory (&wndClass, sizeof(WNDCLASSW));
3339 wndClass.style = CS_GLOBALCLASS | CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
3340 wndClass.lpfnWndProc = TAB_WindowProc;
3341 wndClass.cbClsExtra = 0;
3342 wndClass.cbWndExtra = sizeof(TAB_INFO *);
3343 wndClass.hCursor = LoadCursorW (0, (LPWSTR)IDC_ARROW);
3344 wndClass.hbrBackground = (HBRUSH)(COLOR_BTNFACE+1);
3345 wndClass.lpszClassName = WC_TABCONTROLW;
3347 RegisterClassW (&wndClass);
3351 void
3352 TAB_Unregister (void)
3354 UnregisterClassW (WC_TABCONTROLW, NULL);