Fix a regression in IE where the Favourites menu didn't appear
[wine/testsucceed.git] / dlls / comctl32 / tab.c
blob043db7e2092fe7e6a29cba2dacd1a52a00cdd5e6
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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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 "wine/debug.h"
77 #include <math.h>
79 WINE_DEFAULT_DEBUG_CHANNEL(tab);
81 typedef struct
83 UINT mask;
84 DWORD dwState;
85 LPWSTR pszText;
86 INT iImage;
87 RECT rect; /* bounding rectangle of the item relative to the
88 * leftmost item (the leftmost item, 0, would have a
89 * "left" member of 0 in this rectangle)
91 * additionally the top member holds the row number
92 * and bottom is unused and should be 0 */
93 BYTE extra[1]; /* Space for caller supplied info, variable size */
94 } TAB_ITEM;
96 /* The size of a tab item depends on how much extra data is requested */
97 #define TAB_ITEM_SIZE(infoPtr) (sizeof(TAB_ITEM) - sizeof(BYTE) + infoPtr->cbInfo)
99 typedef struct
101 HWND hwnd; /* Tab control window */
102 HWND hwndNotify; /* notification window (parent) */
103 UINT uNumItem; /* number of tab items */
104 UINT uNumRows; /* number of tab rows */
105 INT tabHeight; /* height of the tab row */
106 INT tabWidth; /* width of tabs */
107 INT tabMinWidth; /* minimum width of items */
108 USHORT uHItemPadding; /* amount of horizontal padding, in pixels */
109 USHORT uVItemPadding; /* amount of vertical padding, in pixels */
110 USHORT uHItemPadding_s; /* Set amount of horizontal padding, in pixels */
111 USHORT uVItemPadding_s; /* Set amount of vertical padding, in pixels */
112 HFONT hFont; /* handle to the current font */
113 HCURSOR hcurArrow; /* handle to the current cursor */
114 HIMAGELIST himl; /* handle to an image list (may be 0) */
115 HWND hwndToolTip; /* handle to tab's tooltip */
116 INT leftmostVisible; /* Used for scrolling, this member contains
117 * the index of the first visible item */
118 INT iSelected; /* the currently selected item */
119 INT iHotTracked; /* the highlighted item under the mouse */
120 INT uFocus; /* item which has the focus */
121 TAB_ITEM* items; /* pointer to an array of TAB_ITEM's */
122 BOOL DoRedraw; /* flag for redrawing when tab contents is changed*/
123 BOOL needsScrolling; /* TRUE if the size of the tabs is greater than
124 * the size of the control */
125 BOOL fHeightSet; /* was the height of the tabs explicitly set? */
126 BOOL bUnicode; /* Unicode control? */
127 HWND hwndUpDown; /* Updown control used for scrolling */
128 INT cbInfo; /* Number of bytes of caller supplied info per tab */
129 } TAB_INFO;
131 /******************************************************************************
132 * Positioning constants
134 #define SELECTED_TAB_OFFSET 2
135 #define ROUND_CORNER_SIZE 2
136 #define DISPLAY_AREA_PADDINGX 2
137 #define DISPLAY_AREA_PADDINGY 2
138 #define CONTROL_BORDER_SIZEX 2
139 #define CONTROL_BORDER_SIZEY 2
140 #define BUTTON_SPACINGX 3
141 #define BUTTON_SPACINGY 3
142 #define FLAT_BTN_SPACINGX 8
143 #define DEFAULT_TAB_WIDTH 96
145 #define TAB_GetInfoPtr(hwnd) ((TAB_INFO *)GetWindowLongPtrW(hwnd,0))
146 /* Since items are variable sized, cannot directly access them */
147 #define TAB_GetItem(info,i) \
148 ((TAB_ITEM*)((LPBYTE)info->items + (i) * TAB_ITEM_SIZE(info)))
150 /******************************************************************************
151 * Hot-tracking timer constants
153 #define TAB_HOTTRACK_TIMER 1
154 #define TAB_HOTTRACK_TIMER_INTERVAL 100 /* milliseconds */
156 /******************************************************************************
157 * Prototypes
159 static void TAB_InvalidateTabArea(TAB_INFO *);
160 static void TAB_EnsureSelectionVisible(TAB_INFO *);
161 static void TAB_DrawItemInterior(TAB_INFO *, HDC, INT, RECT*);
163 static BOOL
164 TAB_SendSimpleNotify (const TAB_INFO *infoPtr, UINT code)
166 NMHDR nmhdr;
168 nmhdr.hwndFrom = infoPtr->hwnd;
169 nmhdr.idFrom = GetWindowLongPtrW(infoPtr->hwnd, GWLP_ID);
170 nmhdr.code = code;
172 return (BOOL) SendMessageW (infoPtr->hwndNotify, WM_NOTIFY,
173 (WPARAM) nmhdr.idFrom, (LPARAM) &nmhdr);
176 static void
177 TAB_RelayEvent (HWND hwndTip, HWND hwndMsg, UINT uMsg,
178 WPARAM wParam, LPARAM lParam)
180 MSG msg;
182 msg.hwnd = hwndMsg;
183 msg.message = uMsg;
184 msg.wParam = wParam;
185 msg.lParam = lParam;
186 msg.time = GetMessageTime ();
187 msg.pt.x = LOWORD(GetMessagePos ());
188 msg.pt.y = HIWORD(GetMessagePos ());
190 SendMessageW (hwndTip, TTM_RELAYEVENT, 0, (LPARAM)&msg);
193 static void
194 TAB_DumpItemExternalT(TCITEMW *pti, UINT iItem, BOOL isW)
196 if (TRACE_ON(tab)) {
197 TRACE("external tab %d, mask=0x%08x, dwState=0x%08lx, dwStateMask=0x%08lx, cchTextMax=0x%08x\n",
198 iItem, pti->mask, pti->dwState, pti->dwStateMask, pti->cchTextMax);
199 TRACE("external tab %d, iImage=%d, lParam=0x%08lx, pszTextW=%s\n",
200 iItem, pti->iImage, pti->lParam, isW ? debugstr_w(pti->pszText) : debugstr_a((LPSTR)pti->pszText));
204 static void
205 TAB_DumpItemInternal(TAB_INFO *infoPtr, UINT iItem)
207 if (TRACE_ON(tab)) {
208 TAB_ITEM *ti;
210 ti = TAB_GetItem(infoPtr, iItem);
211 TRACE("tab %d, mask=0x%08x, dwState=0x%08lx, pszText=%s, iImage=%d\n",
212 iItem, ti->mask, ti->dwState, debugstr_w(ti->pszText),
213 ti->iImage);
214 TRACE("tab %d, rect.left=%ld, rect.top(row)=%ld\n",
215 iItem, ti->rect.left, ti->rect.top);
219 /* RETURNS
220 * the index of the selected tab, or -1 if no tab is selected. */
221 static inline LRESULT TAB_GetCurSel (const TAB_INFO *infoPtr)
223 return infoPtr->iSelected;
226 /* RETURNS
227 * the index of the tab item that has the focus
228 * NOTE
229 * we have not to return negative value
230 * TODO
231 * test for windows */
232 static inline LRESULT
233 TAB_GetCurFocus (const TAB_INFO *infoPtr)
235 if (infoPtr->uFocus<0)
237 FIXME("we have not to return negative value");
238 return 0;
240 return infoPtr->uFocus;
243 static inline LRESULT TAB_GetToolTips (const TAB_INFO *infoPtr)
245 if (infoPtr == NULL) return 0;
246 return (LRESULT)infoPtr->hwndToolTip;
249 static inline LRESULT TAB_SetCurSel (TAB_INFO *infoPtr, INT iItem)
251 INT prevItem = -1;
253 if (iItem >= 0 && iItem < infoPtr->uNumItem) {
254 prevItem=infoPtr->iSelected;
255 infoPtr->iSelected=iItem;
256 TAB_EnsureSelectionVisible(infoPtr);
257 TAB_InvalidateTabArea(infoPtr);
259 return prevItem;
262 static LRESULT TAB_SetCurFocus (TAB_INFO *infoPtr, INT iItem)
264 if (iItem < 0 || iItem >= infoPtr->uNumItem) return 0;
266 if (GetWindowLongW(infoPtr->hwnd, GWL_STYLE) & TCS_BUTTONS) {
267 FIXME("Should set input focus\n");
268 } else {
269 int oldFocus = infoPtr->uFocus;
270 if (infoPtr->iSelected != iItem || oldFocus == -1 ) {
271 infoPtr->uFocus = iItem;
272 if (oldFocus != -1) {
273 if (!TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGING)) {
274 infoPtr->iSelected = iItem;
275 TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGE);
277 else
278 infoPtr->iSelected = iItem;
279 TAB_EnsureSelectionVisible(infoPtr);
280 TAB_InvalidateTabArea(infoPtr);
284 return 0;
287 static inline LRESULT
288 TAB_SetToolTips (TAB_INFO *infoPtr, HWND hwndToolTip)
290 if (infoPtr)
291 infoPtr->hwndToolTip = hwndToolTip;
292 return 0;
295 static inline LRESULT
296 TAB_SetPadding (TAB_INFO *infoPtr, LPARAM lParam)
298 if (infoPtr)
300 infoPtr->uHItemPadding_s=LOWORD(lParam);
301 infoPtr->uVItemPadding_s=HIWORD(lParam);
303 return 0;
306 /******************************************************************************
307 * TAB_InternalGetItemRect
309 * This method will calculate the rectangle representing a given tab item in
310 * client coordinates. This method takes scrolling into account.
312 * This method returns TRUE if the item is visible in the window and FALSE
313 * if it is completely outside the client area.
315 static BOOL TAB_InternalGetItemRect(
316 const TAB_INFO* infoPtr,
317 INT itemIndex,
318 RECT* itemRect,
319 RECT* selectedRect)
321 RECT tmpItemRect,clientRect;
322 LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
324 /* Perform a sanity check and a trivial visibility check. */
325 if ( (infoPtr->uNumItem <= 0) ||
326 (itemIndex >= infoPtr->uNumItem) ||
327 (!((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)) && (itemIndex < infoPtr->leftmostVisible)) )
328 return FALSE;
331 * Avoid special cases in this procedure by assigning the "out"
332 * parameters if the caller didn't supply them
334 if (itemRect == NULL)
335 itemRect = &tmpItemRect;
337 /* Retrieve the unmodified item rect. */
338 *itemRect = TAB_GetItem(infoPtr,itemIndex)->rect;
340 /* calculate the times bottom and top based on the row */
341 GetClientRect(infoPtr->hwnd, &clientRect);
343 if ((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
345 itemRect->right = clientRect.right - SELECTED_TAB_OFFSET - itemRect->left * infoPtr->tabHeight -
346 ((lStyle & TCS_BUTTONS) ? itemRect->left * BUTTON_SPACINGX : 0);
347 itemRect->left = itemRect->right - infoPtr->tabHeight;
349 else if (lStyle & TCS_VERTICAL)
351 itemRect->left = clientRect.left + SELECTED_TAB_OFFSET + itemRect->left * infoPtr->tabHeight +
352 ((lStyle & TCS_BUTTONS) ? itemRect->left * BUTTON_SPACINGX : 0);
353 itemRect->right = itemRect->left + infoPtr->tabHeight;
355 else if (lStyle & TCS_BOTTOM)
357 itemRect->bottom = clientRect.bottom - itemRect->top * infoPtr->tabHeight -
358 ((lStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : SELECTED_TAB_OFFSET);
359 itemRect->top = itemRect->bottom - infoPtr->tabHeight;
361 else /* not TCS_BOTTOM and not TCS_VERTICAL */
363 itemRect->top = clientRect.top + itemRect->top * infoPtr->tabHeight +
364 ((lStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : SELECTED_TAB_OFFSET);
365 itemRect->bottom = itemRect->top + infoPtr->tabHeight;
369 * "scroll" it to make sure the item at the very left of the
370 * tab control is the leftmost visible tab.
372 if(lStyle & TCS_VERTICAL)
374 OffsetRect(itemRect,
376 -TAB_GetItem(infoPtr, infoPtr->leftmostVisible)->rect.top);
379 * Move the rectangle so the first item is slightly offset from
380 * the bottom of the tab control.
382 OffsetRect(itemRect,
384 SELECTED_TAB_OFFSET);
386 } else
388 OffsetRect(itemRect,
389 -TAB_GetItem(infoPtr, infoPtr->leftmostVisible)->rect.left,
393 * Move the rectangle so the first item is slightly offset from
394 * the left of the tab control.
396 OffsetRect(itemRect,
397 SELECTED_TAB_OFFSET,
400 TRACE("item %d tab h=%d, rect=(%ld,%ld)-(%ld,%ld)\n",
401 itemIndex, infoPtr->tabHeight,
402 itemRect->left, itemRect->top, itemRect->right, itemRect->bottom);
404 /* Now, calculate the position of the item as if it were selected. */
405 if (selectedRect!=NULL)
407 CopyRect(selectedRect, itemRect);
409 /* The rectangle of a selected item is a bit wider. */
410 if(lStyle & TCS_VERTICAL)
411 InflateRect(selectedRect, 0, SELECTED_TAB_OFFSET);
412 else
413 InflateRect(selectedRect, SELECTED_TAB_OFFSET, 0);
415 /* If it also a bit higher. */
416 if ((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
418 selectedRect->left -= 2; /* the border is thicker on the right */
419 selectedRect->right += SELECTED_TAB_OFFSET;
421 else if (lStyle & TCS_VERTICAL)
423 selectedRect->left -= SELECTED_TAB_OFFSET;
424 selectedRect->right += 1;
426 else if (lStyle & TCS_BOTTOM)
428 selectedRect->bottom += SELECTED_TAB_OFFSET;
430 else /* not TCS_BOTTOM and not TCS_VERTICAL */
432 selectedRect->top -= SELECTED_TAB_OFFSET;
433 selectedRect->bottom -= 1;
437 /* Check for visibility */
438 if (lStyle & TCS_VERTICAL)
439 return (itemRect->top < clientRect.bottom) && (itemRect->bottom > clientRect.top);
440 else
441 return (itemRect->left < clientRect.right) && (itemRect->right > clientRect.left);
444 static inline BOOL
445 TAB_GetItemRect(TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
447 return TAB_InternalGetItemRect(infoPtr, (INT)wParam, (LPRECT)lParam, (LPRECT)NULL);
450 /******************************************************************************
451 * TAB_KeyUp
453 * This method is called to handle keyboard input
455 static LRESULT TAB_KeyUp(TAB_INFO* infoPtr, WPARAM keyCode)
457 int newItem = -1;
459 switch (keyCode)
461 case VK_LEFT:
462 newItem = infoPtr->uFocus - 1;
463 break;
464 case VK_RIGHT:
465 newItem = infoPtr->uFocus + 1;
466 break;
470 * If we changed to a valid item, change the selection
472 if (newItem >= 0 &&
473 newItem < infoPtr->uNumItem &&
474 infoPtr->uFocus != newItem)
476 if (!TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGING))
478 infoPtr->iSelected = newItem;
479 infoPtr->uFocus = newItem;
480 TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGE);
482 TAB_EnsureSelectionVisible(infoPtr);
483 TAB_InvalidateTabArea(infoPtr);
487 return 0;
490 /******************************************************************************
491 * TAB_FocusChanging
493 * This method is called whenever the focus goes in or out of this control
494 * it is used to update the visual state of the control.
496 static void TAB_FocusChanging(const TAB_INFO *infoPtr)
498 RECT selectedRect;
499 BOOL isVisible;
502 * Get the rectangle for the item.
504 isVisible = TAB_InternalGetItemRect(infoPtr,
505 infoPtr->uFocus,
506 NULL,
507 &selectedRect);
510 * If the rectangle is not completely invisible, invalidate that
511 * portion of the window.
513 if (isVisible)
515 TRACE("invalidate (%ld,%ld)-(%ld,%ld)\n",
516 selectedRect.left,selectedRect.top,
517 selectedRect.right,selectedRect.bottom);
518 InvalidateRect(infoPtr->hwnd, &selectedRect, TRUE);
522 static INT TAB_InternalHitTest (
523 TAB_INFO* infoPtr,
524 POINT pt,
525 UINT* flags)
528 RECT rect;
529 INT iCount;
531 for (iCount = 0; iCount < infoPtr->uNumItem; iCount++)
533 TAB_InternalGetItemRect(infoPtr, iCount, &rect, NULL);
535 if (PtInRect(&rect, pt))
537 *flags = TCHT_ONITEM;
538 return iCount;
542 *flags = TCHT_NOWHERE;
543 return -1;
546 static inline LRESULT
547 TAB_HitTest (TAB_INFO *infoPtr, LPTCHITTESTINFO lptest)
549 return TAB_InternalHitTest (infoPtr, lptest->pt, &lptest->flags);
552 /******************************************************************************
553 * TAB_NCHitTest
555 * Napster v2b5 has a tab control for its main navigation which has a client
556 * area that covers the whole area of the dialog pages.
557 * That's why it receives all msgs for that area and the underlying dialog ctrls
558 * are dead.
559 * So I decided that we should handle WM_NCHITTEST here and return
560 * HTTRANSPARENT if we don't hit the tab control buttons.
561 * FIXME: WM_NCHITTEST handling correct ? Fix it if you know that Windows
562 * doesn't do it that way. Maybe depends on tab control styles ?
564 static inline LRESULT
565 TAB_NCHitTest (TAB_INFO *infoPtr, LPARAM lParam)
567 POINT pt;
568 UINT dummyflag;
570 pt.x = LOWORD(lParam);
571 pt.y = HIWORD(lParam);
572 ScreenToClient(infoPtr->hwnd, &pt);
574 if (TAB_InternalHitTest(infoPtr, pt, &dummyflag) == -1)
575 return HTTRANSPARENT;
576 else
577 return HTCLIENT;
580 static LRESULT
581 TAB_LButtonDown (TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
583 POINT pt;
584 INT newItem, dummy;
586 if (infoPtr->hwndToolTip)
587 TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
588 WM_LBUTTONDOWN, wParam, lParam);
590 if (GetWindowLongW(infoPtr->hwnd, GWL_STYLE) & TCS_FOCUSONBUTTONDOWN ) {
591 SetFocus (infoPtr->hwnd);
594 if (infoPtr->hwndToolTip)
595 TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
596 WM_LBUTTONDOWN, wParam, lParam);
598 pt.x = (INT)LOWORD(lParam);
599 pt.y = (INT)HIWORD(lParam);
601 newItem = TAB_InternalHitTest (infoPtr, pt, &dummy);
603 TRACE("On Tab, item %d\n", newItem);
605 if (newItem != -1 && infoPtr->iSelected != newItem)
607 if (!TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGING))
609 infoPtr->iSelected = newItem;
610 infoPtr->uFocus = newItem;
611 TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGE);
613 TAB_EnsureSelectionVisible(infoPtr);
615 TAB_InvalidateTabArea(infoPtr);
618 return 0;
621 static inline LRESULT
622 TAB_LButtonUp (const TAB_INFO *infoPtr)
624 TAB_SendSimpleNotify(infoPtr, NM_CLICK);
626 return 0;
629 static inline LRESULT
630 TAB_RButtonDown (const TAB_INFO *infoPtr)
632 TAB_SendSimpleNotify(infoPtr, NM_RCLICK);
633 return 0;
636 /******************************************************************************
637 * TAB_DrawLoneItemInterior
639 * This calls TAB_DrawItemInterior. However, TAB_DrawItemInterior is normally
640 * called by TAB_DrawItem which is normally called by TAB_Refresh which sets
641 * up the device context and font. This routine does the same setup but
642 * only calls TAB_DrawItemInterior for the single specified item.
644 static void
645 TAB_DrawLoneItemInterior(TAB_INFO* infoPtr, int iItem)
647 HDC hdc = GetDC(infoPtr->hwnd);
648 RECT r, rC;
650 /* Clip UpDown control to not draw over it */
651 if (infoPtr->needsScrolling)
653 GetWindowRect(infoPtr->hwnd, &rC);
654 GetWindowRect(infoPtr->hwndUpDown, &r);
655 ExcludeClipRect(hdc, r.left - rC.left, r.top - rC.top, r.right - rC.left, r.bottom - rC.top);
657 TAB_DrawItemInterior(infoPtr, hdc, iItem, NULL);
658 ReleaseDC(infoPtr->hwnd, hdc);
661 /******************************************************************************
662 * TAB_HotTrackTimerProc
664 * When a mouse-move event causes a tab to be highlighted (hot-tracking), a
665 * timer is setup so we can check if the mouse is moved out of our window.
666 * (We don't get an event when the mouse leaves, the mouse-move events just
667 * stop being delivered to our window and just start being delivered to
668 * another window.) This function is called when the timer triggers so
669 * we can check if the mouse has left our window. If so, we un-highlight
670 * the hot-tracked tab.
672 static void CALLBACK
673 TAB_HotTrackTimerProc
675 HWND hwnd, /* handle of window for timer messages */
676 UINT uMsg, /* WM_TIMER message */
677 UINT idEvent, /* timer identifier */
678 DWORD dwTime /* current system time */
681 TAB_INFO* infoPtr = TAB_GetInfoPtr(hwnd);
683 if (infoPtr != NULL && infoPtr->iHotTracked >= 0)
685 POINT pt;
688 ** If we can't get the cursor position, or if the cursor is outside our
689 ** window, we un-highlight the hot-tracked tab. Note that the cursor is
690 ** "outside" even if it is within our bounding rect if another window
691 ** overlaps. Note also that the case where the cursor stayed within our
692 ** window but has moved off the hot-tracked tab will be handled by the
693 ** WM_MOUSEMOVE event.
695 if (!GetCursorPos(&pt) || WindowFromPoint(pt) != hwnd)
697 /* Redraw iHotTracked to look normal */
698 INT iRedraw = infoPtr->iHotTracked;
699 infoPtr->iHotTracked = -1;
700 TAB_DrawLoneItemInterior(infoPtr, iRedraw);
702 /* Kill this timer */
703 KillTimer(hwnd, TAB_HOTTRACK_TIMER);
708 /******************************************************************************
709 * TAB_RecalcHotTrack
711 * If a tab control has the TCS_HOTTRACK style, then the tab under the mouse
712 * should be highlighted. This function determines which tab in a tab control,
713 * if any, is under the mouse and records that information. The caller may
714 * supply output parameters to receive the item number of the tab item which
715 * was highlighted but isn't any longer and of the tab item which is now
716 * highlighted but wasn't previously. The caller can use this information to
717 * selectively redraw those tab items.
719 * If the caller has a mouse position, it can supply it through the pos
720 * parameter. For example, TAB_MouseMove does this. Otherwise, the caller
721 * supplies NULL and this function determines the current mouse position
722 * itself.
724 static void
725 TAB_RecalcHotTrack
727 TAB_INFO* infoPtr,
728 const LPARAM* pos,
729 int* out_redrawLeave,
730 int* out_redrawEnter
733 int item = -1;
736 if (out_redrawLeave != NULL)
737 *out_redrawLeave = -1;
738 if (out_redrawEnter != NULL)
739 *out_redrawEnter = -1;
741 if (GetWindowLongW(infoPtr->hwnd, GWL_STYLE) & TCS_HOTTRACK)
743 POINT pt;
744 UINT flags;
746 if (pos == NULL)
748 GetCursorPos(&pt);
749 ScreenToClient(infoPtr->hwnd, &pt);
751 else
753 pt.x = LOWORD(*pos);
754 pt.y = HIWORD(*pos);
757 item = TAB_InternalHitTest(infoPtr, pt, &flags);
760 if (item != infoPtr->iHotTracked)
762 if (infoPtr->iHotTracked >= 0)
764 /* Mark currently hot-tracked to be redrawn to look normal */
765 if (out_redrawLeave != NULL)
766 *out_redrawLeave = infoPtr->iHotTracked;
768 if (item < 0)
770 /* Kill timer which forces recheck of mouse pos */
771 KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
774 else
776 /* Start timer so we recheck mouse pos */
777 UINT timerID = SetTimer
779 infoPtr->hwnd,
780 TAB_HOTTRACK_TIMER,
781 TAB_HOTTRACK_TIMER_INTERVAL,
782 TAB_HotTrackTimerProc
785 if (timerID == 0)
786 return; /* Hot tracking not available */
789 infoPtr->iHotTracked = item;
791 if (item >= 0)
793 /* Mark new hot-tracked to be redrawn to look highlighted */
794 if (out_redrawEnter != NULL)
795 *out_redrawEnter = item;
800 /******************************************************************************
801 * TAB_MouseMove
803 * Handles the mouse-move event. Updates tooltips. Updates hot-tracking.
805 static LRESULT
806 TAB_MouseMove (TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
808 int redrawLeave;
809 int redrawEnter;
811 if (infoPtr->hwndToolTip)
812 TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
813 WM_LBUTTONDOWN, wParam, lParam);
815 /* Determine which tab to highlight. Redraw tabs which change highlight
816 ** status. */
817 TAB_RecalcHotTrack(infoPtr, &lParam, &redrawLeave, &redrawEnter);
819 if (redrawLeave != -1)
820 TAB_DrawLoneItemInterior(infoPtr, redrawLeave);
821 if (redrawEnter != -1)
822 TAB_DrawLoneItemInterior(infoPtr, redrawEnter);
824 return 0;
827 /******************************************************************************
828 * TAB_AdjustRect
830 * Calculates the tab control's display area given the window rectangle or
831 * the window rectangle given the requested display rectangle.
833 static LRESULT TAB_AdjustRect(
834 TAB_INFO *infoPtr,
835 WPARAM fLarger,
836 LPRECT prc)
838 DWORD lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
839 LONG *iRightBottom, *iLeftTop;
841 TRACE ("hwnd=%p fLarger=%d (%ld,%ld)-(%ld,%ld)\n", infoPtr->hwnd, fLarger, prc->left, prc->top, prc->right, prc->bottom);
843 if(lStyle & TCS_VERTICAL)
845 iRightBottom = &(prc->right);
846 iLeftTop = &(prc->left);
848 else
850 iRightBottom = &(prc->bottom);
851 iLeftTop = &(prc->top);
854 if (fLarger) /* Go from display rectangle */
856 /* Add the height of the tabs. */
857 if (lStyle & TCS_BOTTOM)
858 *iRightBottom += infoPtr->tabHeight * infoPtr->uNumRows;
859 else
860 *iLeftTop -= infoPtr->tabHeight * infoPtr->uNumRows +
861 ((lStyle & TCS_BUTTONS)? 3 * (infoPtr->uNumRows - 1) : 0);
863 /* Inflate the rectangle for the padding */
864 InflateRect(prc, DISPLAY_AREA_PADDINGX, DISPLAY_AREA_PADDINGY);
866 /* Inflate for the border */
867 InflateRect(prc, CONTROL_BORDER_SIZEX, CONTROL_BORDER_SIZEY);
869 else /* Go from window rectangle. */
871 /* Deflate the rectangle for the border */
872 InflateRect(prc, -CONTROL_BORDER_SIZEX, -CONTROL_BORDER_SIZEY);
874 /* Deflate the rectangle for the padding */
875 InflateRect(prc, -DISPLAY_AREA_PADDINGX, -DISPLAY_AREA_PADDINGY);
877 /* Remove the height of the tabs. */
878 if (lStyle & TCS_BOTTOM)
879 *iRightBottom -= infoPtr->tabHeight * infoPtr->uNumRows;
880 else
881 *iLeftTop += (infoPtr->tabHeight) * infoPtr->uNumRows +
882 ((lStyle & TCS_BUTTONS)? 3 * (infoPtr->uNumRows - 1) : 0);
885 return 0;
888 /******************************************************************************
889 * TAB_OnHScroll
891 * This method will handle the notification from the scroll control and
892 * perform the scrolling operation on the tab control.
894 static LRESULT TAB_OnHScroll(
895 TAB_INFO *infoPtr,
896 int nScrollCode,
897 int nPos,
898 HWND hwndScroll)
900 if(nScrollCode == SB_THUMBPOSITION && nPos != infoPtr->leftmostVisible)
902 if(nPos < infoPtr->leftmostVisible)
903 infoPtr->leftmostVisible--;
904 else
905 infoPtr->leftmostVisible++;
907 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
908 TAB_InvalidateTabArea(infoPtr);
909 SendMessageW(infoPtr->hwndUpDown, UDM_SETPOS, 0,
910 MAKELONG(infoPtr->leftmostVisible, 0));
913 return 0;
916 /******************************************************************************
917 * TAB_SetupScrolling
919 * This method will check the current scrolling state and make sure the
920 * scrolling control is displayed (or not).
922 static void TAB_SetupScrolling(
923 HWND hwnd,
924 TAB_INFO* infoPtr,
925 const RECT* clientRect)
927 static const WCHAR msctls_updown32W[] = { 'm','s','c','t','l','s','_','u','p','d','o','w','n','3','2',0 };
928 static const WCHAR emptyW[] = { 0 };
929 INT maxRange = 0;
930 DWORD lStyle = GetWindowLongW(hwnd, GWL_STYLE);
932 if (infoPtr->needsScrolling)
934 RECT controlPos;
935 INT vsize, tabwidth;
938 * Calculate the position of the scroll control.
940 if(lStyle & TCS_VERTICAL)
942 controlPos.right = clientRect->right;
943 controlPos.left = controlPos.right - 2 * GetSystemMetrics(SM_CXHSCROLL);
945 if (lStyle & TCS_BOTTOM)
947 controlPos.top = clientRect->bottom - infoPtr->tabHeight;
948 controlPos.bottom = controlPos.top + GetSystemMetrics(SM_CYHSCROLL);
950 else
952 controlPos.bottom = clientRect->top + infoPtr->tabHeight;
953 controlPos.top = controlPos.bottom - GetSystemMetrics(SM_CYHSCROLL);
956 else
958 controlPos.right = clientRect->right;
959 controlPos.left = controlPos.right - 2 * GetSystemMetrics(SM_CXHSCROLL);
961 if (lStyle & TCS_BOTTOM)
963 controlPos.top = clientRect->bottom - infoPtr->tabHeight;
964 controlPos.bottom = controlPos.top + GetSystemMetrics(SM_CYHSCROLL);
966 else
968 controlPos.bottom = clientRect->top + infoPtr->tabHeight;
969 controlPos.top = controlPos.bottom - GetSystemMetrics(SM_CYHSCROLL);
974 * If we don't have a scroll control yet, we want to create one.
975 * If we have one, we want to make sure it's positioned properly.
977 if (infoPtr->hwndUpDown==0)
979 infoPtr->hwndUpDown = CreateWindowW(msctls_updown32W, emptyW,
980 WS_VISIBLE | WS_CHILD | UDS_HORZ,
981 controlPos.left, controlPos.top,
982 controlPos.right - controlPos.left,
983 controlPos.bottom - controlPos.top,
984 hwnd, NULL, NULL, NULL);
986 else
988 SetWindowPos(infoPtr->hwndUpDown,
989 NULL,
990 controlPos.left, controlPos.top,
991 controlPos.right - controlPos.left,
992 controlPos.bottom - controlPos.top,
993 SWP_SHOWWINDOW | SWP_NOZORDER);
996 /* Now calculate upper limit of the updown control range.
997 * We do this by calculating how many tabs will be offscreen when the
998 * last tab is visible.
1000 if(infoPtr->uNumItem)
1002 vsize = clientRect->right - (controlPos.right - controlPos.left + 1);
1003 maxRange = infoPtr->uNumItem;
1004 tabwidth = TAB_GetItem(infoPtr, infoPtr->uNumItem - 1)->rect.right;
1006 for(; maxRange > 0; maxRange--)
1008 if(tabwidth - TAB_GetItem(infoPtr,maxRange - 1)->rect.left > vsize)
1009 break;
1012 if(maxRange == infoPtr->uNumItem)
1013 maxRange--;
1016 else
1018 /* If we once had a scroll control... hide it */
1019 if (infoPtr->hwndUpDown!=0)
1020 ShowWindow(infoPtr->hwndUpDown, SW_HIDE);
1022 if (infoPtr->hwndUpDown)
1023 SendMessageW(infoPtr->hwndUpDown, UDM_SETRANGE32, 0, maxRange);
1026 /******************************************************************************
1027 * TAB_SetItemBounds
1029 * This method will calculate the position rectangles of all the items in the
1030 * control. The rectangle calculated starts at 0 for the first item in the
1031 * list and ignores scrolling and selection.
1032 * It also uses the current font to determine the height of the tab row and
1033 * it checks if all the tabs fit in the client area of the window. If they
1034 * don't, a scrolling control is added.
1036 static void TAB_SetItemBounds (TAB_INFO *infoPtr)
1038 LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1039 TEXTMETRICW fontMetrics;
1040 UINT curItem;
1041 INT curItemLeftPos;
1042 INT curItemRowCount;
1043 HFONT hFont, hOldFont;
1044 HDC hdc;
1045 RECT clientRect;
1046 SIZE size;
1047 INT iTemp;
1048 RECT* rcItem;
1049 INT iIndex;
1050 INT icon_width = 0;
1053 * We need to get text information so we need a DC and we need to select
1054 * a font.
1056 hdc = GetDC(infoPtr->hwnd);
1058 hFont = infoPtr->hFont ? infoPtr->hFont : GetStockObject (SYSTEM_FONT);
1059 hOldFont = SelectObject (hdc, hFont);
1062 * We will base the rectangle calculations on the client rectangle
1063 * of the control.
1065 GetClientRect(infoPtr->hwnd, &clientRect);
1067 /* if TCS_VERTICAL then swap the height and width so this code places the
1068 tabs along the top of the rectangle and we can just rotate them after
1069 rather than duplicate all of the below code */
1070 if(lStyle & TCS_VERTICAL)
1072 iTemp = clientRect.bottom;
1073 clientRect.bottom = clientRect.right;
1074 clientRect.right = iTemp;
1077 /* Now use hPadding and vPadding */
1078 infoPtr->uHItemPadding = infoPtr->uHItemPadding_s;
1079 infoPtr->uVItemPadding = infoPtr->uVItemPadding_s;
1081 /* The leftmost item will be "0" aligned */
1082 curItemLeftPos = 0;
1083 curItemRowCount = infoPtr->uNumItem ? 1 : 0;
1085 if (!(infoPtr->fHeightSet))
1087 int item_height;
1088 int icon_height = 0;
1090 /* Use the current font to determine the height of a tab. */
1091 GetTextMetricsW(hdc, &fontMetrics);
1093 /* Get the icon height */
1094 if (infoPtr->himl)
1095 ImageList_GetIconSize(infoPtr->himl, 0, &icon_height);
1097 /* Take the highest between font or icon */
1098 if (fontMetrics.tmHeight > icon_height)
1099 item_height = fontMetrics.tmHeight + 2;
1100 else
1101 item_height = icon_height;
1104 * Make sure there is enough space for the letters + icon + growing the
1105 * selected item + extra space for the selected item.
1107 infoPtr->tabHeight = item_height +
1108 ((lStyle & TCS_BUTTONS) ? 2 : 1) *
1109 infoPtr->uVItemPadding;
1111 TRACE("tabH=%d, tmH=%ld, iconh=%d\n",
1112 infoPtr->tabHeight, fontMetrics.tmHeight, icon_height);
1115 TRACE("client right=%ld\n", clientRect.right);
1117 /* Get the icon width */
1118 if (infoPtr->himl)
1120 ImageList_GetIconSize(infoPtr->himl, &icon_width, 0);
1122 if (lStyle & TCS_FIXEDWIDTH)
1123 icon_width += 4;
1124 else
1125 /* Add padding if icon is present */
1126 icon_width += infoPtr->uHItemPadding;
1129 for (curItem = 0; curItem < infoPtr->uNumItem; curItem++)
1131 TAB_ITEM *curr = TAB_GetItem(infoPtr, curItem);
1133 /* Set the leftmost position of the tab. */
1134 curr->rect.left = curItemLeftPos;
1136 if ((lStyle & TCS_FIXEDWIDTH) || !curr->pszText)
1138 curr->rect.right = curr->rect.left +
1139 max(infoPtr->tabWidth, icon_width);
1141 else
1143 int num = 2;
1145 /* Calculate how wide the tab is depending on the text it contains */
1146 GetTextExtentPoint32W(hdc, curr->pszText,
1147 lstrlenW(curr->pszText), &size);
1149 curr->rect.right = curr->rect.left + size.cx + icon_width +
1150 num * infoPtr->uHItemPadding;
1151 TRACE("for <%s>, l,r=%ld,%ld, num=%d\n",
1152 debugstr_w(curr->pszText), curr->rect.left, curr->rect.right, num);
1156 * Check if this is a multiline tab control and if so
1157 * check to see if we should wrap the tabs
1159 * Wrap all these tabs. We will arrange them evenly later.
1163 if (((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)) &&
1164 (curr->rect.right >
1165 (clientRect.right - CONTROL_BORDER_SIZEX - DISPLAY_AREA_PADDINGX)))
1167 curr->rect.right -= curr->rect.left;
1169 curr->rect.left = 0;
1170 curItemRowCount++;
1171 TRACE("wrapping <%s>, l,r=%ld,%ld\n", debugstr_w(curr->pszText),
1172 curr->rect.left, curr->rect.right);
1175 curr->rect.bottom = 0;
1176 curr->rect.top = curItemRowCount - 1;
1178 TRACE("TextSize: %li\n", size.cx);
1179 TRACE("Rect: T %li, L %li, B %li, R %li\n", curr->rect.top,
1180 curr->rect.left, curr->rect.bottom, curr->rect.right);
1183 * The leftmost position of the next item is the rightmost position
1184 * of this one.
1186 if (lStyle & TCS_BUTTONS)
1188 curItemLeftPos = curr->rect.right + BUTTON_SPACINGX;
1189 if (lStyle & TCS_FLATBUTTONS)
1190 curItemLeftPos += FLAT_BTN_SPACINGX;
1192 else
1193 curItemLeftPos = curr->rect.right;
1196 if (!((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)))
1199 * Check if we need a scrolling control.
1201 infoPtr->needsScrolling = (curItemLeftPos + (2 * SELECTED_TAB_OFFSET) >
1202 clientRect.right);
1204 /* Don't need scrolling, then update infoPtr->leftmostVisible */
1205 if(!infoPtr->needsScrolling)
1206 infoPtr->leftmostVisible = 0;
1208 else
1211 * No scrolling in Multiline or Vertical styles.
1213 infoPtr->needsScrolling = FALSE;
1214 infoPtr->leftmostVisible = 0;
1216 TAB_SetupScrolling(infoPtr->hwnd, infoPtr, &clientRect);
1218 /* Set the number of rows */
1219 infoPtr->uNumRows = curItemRowCount;
1221 /* Arrange all tabs evenly if style says so */
1222 if (!(lStyle & TCS_RAGGEDRIGHT) && ((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)) && (infoPtr->uNumItem > 0))
1224 INT tabPerRow,remTab,iRow;
1225 UINT iItm;
1226 INT iCount=0;
1229 * Ok windows tries to even out the rows. place the same
1230 * number of tabs in each row. So lets give that a shot
1233 tabPerRow = infoPtr->uNumItem / (infoPtr->uNumRows);
1234 remTab = infoPtr->uNumItem % (infoPtr->uNumRows);
1236 for (iItm=0,iRow=0,iCount=0,curItemLeftPos=0;
1237 iItm<infoPtr->uNumItem;
1238 iItm++,iCount++)
1240 /* normalize the current rect */
1241 TAB_ITEM *curr = TAB_GetItem(infoPtr, iItm);
1243 /* shift the item to the left side of the clientRect */
1244 curr->rect.right -= curr->rect.left;
1245 curr->rect.left = 0;
1247 TRACE("r=%ld, cl=%d, cl.r=%ld, iCount=%d, iRow=%d, uNumRows=%d, remTab=%d, tabPerRow=%d\n",
1248 curr->rect.right, curItemLeftPos, clientRect.right,
1249 iCount, iRow, infoPtr->uNumRows, remTab, tabPerRow);
1251 /* if we have reached the maximum number of tabs on this row */
1252 /* move to the next row, reset our current item left position and */
1253 /* the count of items on this row */
1255 if (lStyle & TCS_VERTICAL) {
1256 /* Vert: Add the remaining tabs in the *last* remainder rows */
1257 if (iCount >= ((iRow>=(INT)infoPtr->uNumRows - remTab)?tabPerRow + 1:tabPerRow)) {
1258 iRow++;
1259 curItemLeftPos = 0;
1260 iCount = 0;
1262 } else {
1263 /* Horz: Add the remaining tabs in the *first* remainder rows */
1264 if (iCount >= ((iRow<remTab)?tabPerRow + 1:tabPerRow)) {
1265 iRow++;
1266 curItemLeftPos = 0;
1267 iCount = 0;
1271 /* shift the item to the right to place it as the next item in this row */
1272 curr->rect.left += curItemLeftPos;
1273 curr->rect.right += curItemLeftPos;
1274 curr->rect.top = iRow;
1275 if (lStyle & TCS_BUTTONS)
1277 curItemLeftPos = curr->rect.right + 1;
1278 if (lStyle & TCS_FLATBUTTONS)
1279 curItemLeftPos += FLAT_BTN_SPACINGX;
1281 else
1282 curItemLeftPos = curr->rect.right;
1284 TRACE("arranging <%s>, l,r=%ld,%ld, row=%ld\n",
1285 debugstr_w(curr->pszText), curr->rect.left,
1286 curr->rect.right, curr->rect.top);
1290 * Justify the rows
1293 INT widthDiff, iIndexStart=0, iIndexEnd=0;
1294 INT remainder;
1295 INT iCount=0;
1297 while(iIndexStart < infoPtr->uNumItem)
1299 TAB_ITEM *start = TAB_GetItem(infoPtr, iIndexStart);
1302 * find the index of the row
1304 /* find the first item on the next row */
1305 for (iIndexEnd=iIndexStart;
1306 (iIndexEnd < infoPtr->uNumItem) &&
1307 (TAB_GetItem(infoPtr, iIndexEnd)->rect.top ==
1308 start->rect.top) ;
1309 iIndexEnd++)
1310 /* intentionally blank */;
1313 * we need to justify these tabs so they fill the whole given
1314 * client area
1317 /* find the amount of space remaining on this row */
1318 widthDiff = clientRect.right - (2 * SELECTED_TAB_OFFSET) -
1319 TAB_GetItem(infoPtr, iIndexEnd - 1)->rect.right;
1321 /* iCount is the number of tab items on this row */
1322 iCount = iIndexEnd - iIndexStart;
1324 if (iCount > 1)
1326 remainder = widthDiff % iCount;
1327 widthDiff = widthDiff / iCount;
1328 /* add widthDiff/iCount, or extra space/items on row, to each item on this row */
1329 for (iIndex=iIndexStart, iCount=0; iIndex < iIndexEnd; iIndex++, iCount++)
1331 TAB_ITEM *item = TAB_GetItem(infoPtr, iIndex);
1333 item->rect.left += iCount * widthDiff;
1334 item->rect.right += (iCount + 1) * widthDiff;
1336 TRACE("adjusting 1 <%s>, l,r=%ld,%ld\n",
1337 debugstr_w(item->pszText),
1338 item->rect.left, item->rect.right);
1341 TAB_GetItem(infoPtr, iIndex - 1)->rect.right += remainder;
1343 else /* we have only one item on this row, make it take up the entire row */
1345 start->rect.left = clientRect.left;
1346 start->rect.right = clientRect.right - 4;
1348 TRACE("adjusting 2 <%s>, l,r=%ld,%ld\n",
1349 debugstr_w(start->pszText),
1350 start->rect.left, start->rect.right);
1355 iIndexStart = iIndexEnd;
1360 /* if TCS_VERTICAL rotate the tabs so they are along the side of the clientRect */
1361 if(lStyle & TCS_VERTICAL)
1363 RECT rcOriginal;
1364 for(iIndex = 0; iIndex < infoPtr->uNumItem; iIndex++)
1366 rcItem = &TAB_GetItem(infoPtr, iIndex)->rect;
1368 rcOriginal = *rcItem;
1370 /* this is rotating the items by 90 degrees clockwise around the center of the control */
1371 rcItem->top = (rcOriginal.left - clientRect.left);
1372 rcItem->bottom = rcItem->top + (rcOriginal.right - rcOriginal.left);
1373 rcItem->left = rcOriginal.top;
1374 rcItem->right = rcOriginal.bottom;
1378 TAB_EnsureSelectionVisible(infoPtr);
1379 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
1381 /* Cleanup */
1382 SelectObject (hdc, hOldFont);
1383 ReleaseDC (infoPtr->hwnd, hdc);
1387 static void
1388 TAB_EraseTabInterior
1390 TAB_INFO* infoPtr,
1391 HDC hdc,
1392 INT iItem,
1393 RECT* drawRect
1396 LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1397 HBRUSH hbr = CreateSolidBrush (comctl32_color.clrBtnFace);
1398 BOOL deleteBrush = TRUE;
1399 RECT rTemp = *drawRect;
1401 InflateRect(&rTemp, -2, -2);
1402 if (lStyle & TCS_BUTTONS)
1404 if (iItem == infoPtr->iSelected)
1406 /* Background color */
1407 if (!(lStyle & TCS_OWNERDRAWFIXED))
1409 DeleteObject(hbr);
1410 hbr = GetSysColorBrush(COLOR_SCROLLBAR);
1412 SetTextColor(hdc, comctl32_color.clr3dFace);
1413 SetBkColor(hdc, comctl32_color.clr3dHilight);
1415 /* if COLOR_WINDOW happens to be the same as COLOR_3DHILIGHT
1416 * we better use 0x55aa bitmap brush to make scrollbar's background
1417 * look different from the window background.
1419 if (comctl32_color.clr3dHilight == comctl32_color.clrWindow)
1420 hbr = COMCTL32_hPattern55AABrush;
1422 deleteBrush = FALSE;
1424 FillRect(hdc, &rTemp, hbr);
1426 else /* ! selected */
1428 if (lStyle & TCS_FLATBUTTONS)
1430 FillRect(hdc, drawRect, hbr);
1431 if (iItem == infoPtr->iHotTracked)
1432 DrawEdge(hdc, drawRect, EDGE_RAISED, BF_SOFT|BF_RECT);
1434 else
1435 FillRect(hdc, &rTemp, hbr);
1439 else /* !TCS_BUTTONS */
1441 FillRect(hdc, &rTemp, hbr);
1444 /* Cleanup */
1445 if (deleteBrush) DeleteObject(hbr);
1448 /******************************************************************************
1449 * TAB_DrawItemInterior
1451 * This method is used to draw the interior (text and icon) of a single tab
1452 * into the tab control.
1454 static void
1455 TAB_DrawItemInterior
1457 TAB_INFO* infoPtr,
1458 HDC hdc,
1459 INT iItem,
1460 RECT* drawRect
1463 LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1465 RECT localRect;
1467 HPEN htextPen;
1468 HPEN holdPen;
1469 INT oldBkMode;
1470 HFONT hOldFont;
1472 /* if (drawRect == NULL) */
1474 BOOL isVisible;
1475 RECT itemRect;
1476 RECT selectedRect;
1479 * Get the rectangle for the item.
1481 isVisible = TAB_InternalGetItemRect(infoPtr, iItem, &itemRect, &selectedRect);
1482 if (!isVisible)
1483 return;
1486 * Make sure drawRect points to something valid; simplifies code.
1488 drawRect = &localRect;
1491 * This logic copied from the part of TAB_DrawItem which draws
1492 * the tab background. It's important to keep it in sync. I
1493 * would have liked to avoid code duplication, but couldn't figure
1494 * out how without making spaghetti of TAB_DrawItem.
1496 if (iItem == infoPtr->iSelected)
1497 *drawRect = selectedRect;
1498 else
1499 *drawRect = itemRect;
1501 if (lStyle & TCS_BUTTONS)
1503 if (iItem == infoPtr->iSelected)
1505 drawRect->left += 4;
1506 drawRect->top += 4;
1507 drawRect->right -= 4;
1508 drawRect->bottom -= 1;
1510 else
1512 drawRect->left += 2;
1513 drawRect->top += 2;
1514 drawRect->right -= 2;
1515 drawRect->bottom -= 2;
1518 else
1520 if ((lStyle & TCS_VERTICAL) && (lStyle & TCS_BOTTOM))
1522 if (iItem != infoPtr->iSelected)
1524 drawRect->left += 2;
1525 drawRect->top += 2;
1526 drawRect->bottom -= 2;
1529 else if (lStyle & TCS_VERTICAL)
1531 if (iItem == infoPtr->iSelected)
1533 drawRect->right += 1;
1535 else
1537 drawRect->top += 2;
1538 drawRect->right -= 2;
1539 drawRect->bottom -= 2;
1542 else if (lStyle & TCS_BOTTOM)
1544 if (iItem == infoPtr->iSelected)
1546 drawRect->top -= 2;
1548 else
1550 InflateRect(drawRect, -2, -2);
1551 drawRect->bottom += 2;
1554 else
1556 if (iItem == infoPtr->iSelected)
1558 drawRect->bottom += 3;
1560 else
1562 drawRect->bottom -= 2;
1563 InflateRect(drawRect, -2, 0);
1568 TRACE("drawRect=(%ld,%ld)-(%ld,%ld)\n",
1569 drawRect->left, drawRect->top, drawRect->right, drawRect->bottom);
1571 /* Clear interior */
1572 TAB_EraseTabInterior (infoPtr, hdc, iItem, drawRect);
1574 /* Draw the focus rectangle */
1575 if (!(lStyle & TCS_FOCUSNEVER) &&
1576 (GetFocus() == infoPtr->hwnd) &&
1577 (iItem == infoPtr->uFocus) )
1579 RECT rFocus = *drawRect;
1580 InflateRect(&rFocus, -3, -3);
1581 if (lStyle & TCS_BOTTOM && !(lStyle & TCS_VERTICAL))
1582 rFocus.top -= 3;
1583 if (lStyle & TCS_BUTTONS)
1585 rFocus.left -= 3;
1586 rFocus.top -= 3;
1589 DrawFocusRect(hdc, &rFocus);
1593 * Text pen
1595 htextPen = CreatePen( PS_SOLID, 1, GetSysColor(COLOR_BTNTEXT) );
1596 holdPen = SelectObject(hdc, htextPen);
1597 hOldFont = SelectObject(hdc, infoPtr->hFont);
1600 * Setup for text output
1602 oldBkMode = SetBkMode(hdc, TRANSPARENT);
1603 SetTextColor(hdc, (((iItem == infoPtr->iHotTracked) && !(lStyle & TCS_FLATBUTTONS)) |
1604 (TAB_GetItem(infoPtr, iItem)->dwState & TCIS_HIGHLIGHTED)) ?
1605 comctl32_color.clrHighlight : comctl32_color.clrBtnText);
1608 * if owner draw, tell the owner to draw
1610 if ((lStyle & TCS_OWNERDRAWFIXED) && GetParent(infoPtr->hwnd))
1612 DRAWITEMSTRUCT dis;
1613 UINT id;
1615 drawRect->top += 2;
1616 drawRect->right -= 1;
1617 if ( iItem == infoPtr->iSelected )
1619 drawRect->right -= 1;
1620 drawRect->left += 1;
1624 * get the control id
1626 id = (UINT)GetWindowLongPtrW( infoPtr->hwnd, GWLP_ID );
1629 * put together the DRAWITEMSTRUCT
1631 dis.CtlType = ODT_TAB;
1632 dis.CtlID = id;
1633 dis.itemID = iItem;
1634 dis.itemAction = ODA_DRAWENTIRE;
1635 dis.itemState = 0;
1636 if ( iItem == infoPtr->iSelected )
1637 dis.itemState |= ODS_SELECTED;
1638 if (infoPtr->uFocus == iItem)
1639 dis.itemState |= ODS_FOCUS;
1640 dis.hwndItem = infoPtr->hwnd;
1641 dis.hDC = hdc;
1642 CopyRect(&dis.rcItem,drawRect);
1643 dis.itemData = (ULONG_PTR)TAB_GetItem(infoPtr, iItem)->extra;
1646 * send the draw message
1648 SendMessageW( infoPtr->hwndNotify, WM_DRAWITEM, (WPARAM)id, (LPARAM)&dis );
1650 else
1652 TAB_ITEM *item = TAB_GetItem(infoPtr, iItem);
1653 RECT rcTemp;
1654 RECT rcImage;
1656 /* used to center the icon and text in the tab */
1657 RECT rcText;
1658 INT center_offset_h, center_offset_v;
1660 /* set rcImage to drawRect, we will use top & left in our ImageList_Draw call */
1661 rcImage = *drawRect;
1663 rcTemp = *drawRect;
1665 rcText.left = rcText.top = rcText.right = rcText.bottom = 0;
1667 /* get the rectangle that the text fits in */
1668 if (item->pszText)
1670 DrawTextW(hdc, item->pszText, -1, &rcText, DT_CALCRECT);
1673 * If not owner draw, then do the drawing ourselves.
1675 * Draw the icon.
1677 if (infoPtr->himl && (item->mask & TCIF_IMAGE))
1679 INT cx;
1680 INT cy;
1682 ImageList_GetIconSize(infoPtr->himl, &cx, &cy);
1684 if(lStyle & TCS_VERTICAL)
1686 center_offset_h = ((drawRect->bottom - drawRect->top) - (cy + infoPtr->uHItemPadding + (rcText.right - rcText.left))) / 2;
1687 center_offset_v = ((drawRect->right - drawRect->left) - (cx + infoPtr->uVItemPadding)) / 2;
1689 else
1691 center_offset_h = ((drawRect->right - drawRect->left) - (cx + infoPtr->uHItemPadding + (rcText.right - rcText.left))) / 2;
1692 center_offset_v = ((drawRect->bottom - drawRect->top) - (cy + infoPtr->uVItemPadding)) / 2;
1695 if (lStyle & TCS_FIXEDWIDTH && lStyle & (TCS_FORCELABELLEFT | TCS_FORCEICONLEFT))
1696 center_offset_h = infoPtr->uHItemPadding;
1698 if (center_offset_h < 2)
1699 center_offset_h = 2;
1701 if (center_offset_v < 0)
1702 center_offset_v = 0;
1704 TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%ld,%ld)-(%ld,%ld), textlen=%ld\n",
1705 debugstr_w(item->pszText), center_offset_h, center_offset_v,
1706 drawRect->left, drawRect->top, drawRect->right, drawRect->bottom,
1707 (rcText.right-rcText.left));
1709 if((lStyle & TCS_VERTICAL) && (lStyle & TCS_BOTTOM))
1711 rcImage.top = drawRect->top + center_offset_h;
1712 /* if tab is TCS_VERTICAL and TCS_BOTTOM, the text is drawn from the */
1713 /* right side of the tab, but the image still uses the left as its x position */
1714 /* this keeps the image always drawn off of the same side of the tab */
1715 rcImage.left = drawRect->right - cx - center_offset_v;
1716 drawRect->top += cy + infoPtr->uHItemPadding;
1718 else if(lStyle & TCS_VERTICAL)
1720 rcImage.top = drawRect->bottom - cy - center_offset_h;
1721 rcImage.left = drawRect->left + center_offset_v;
1722 drawRect->bottom -= cy + infoPtr->uHItemPadding;
1724 else /* normal style, whether TCS_BOTTOM or not */
1726 rcImage.left = drawRect->left + center_offset_h;
1727 rcImage.top = drawRect->top + center_offset_v;
1728 drawRect->left += cx + infoPtr->uHItemPadding;
1731 TRACE("drawing image=%d, left=%ld, top=%ld\n",
1732 item->iImage, rcImage.left, rcImage.top-1);
1733 ImageList_Draw
1735 infoPtr->himl,
1736 item->iImage,
1737 hdc,
1738 rcImage.left,
1739 rcImage.top,
1740 ILD_NORMAL
1744 /* Now position text */
1745 if (lStyle & TCS_FIXEDWIDTH && lStyle & TCS_FORCELABELLEFT)
1746 center_offset_h = infoPtr->uHItemPadding;
1747 else
1748 if(lStyle & TCS_VERTICAL)
1749 center_offset_h = ((drawRect->bottom - drawRect->top) - (rcText.right - rcText.left)) / 2;
1750 else
1751 center_offset_h = ((drawRect->right - drawRect->left) - (rcText.right - rcText.left)) / 2;
1753 if(lStyle & TCS_VERTICAL)
1755 if(lStyle & TCS_BOTTOM)
1756 drawRect->top+=center_offset_h;
1757 else
1758 drawRect->bottom-=center_offset_h;
1760 center_offset_v = ((drawRect->right - drawRect->left) - (rcText.bottom - rcText.top)) / 2;
1762 else
1764 drawRect->left += center_offset_h;
1765 center_offset_v = ((drawRect->bottom - drawRect->top) - (rcText.bottom - rcText.top)) / 2;
1768 /* if an item is selected, the text is shifted up instead of down */
1769 if (iItem == infoPtr->iSelected)
1770 center_offset_v -= infoPtr->uVItemPadding / 2;
1771 else
1772 center_offset_v += infoPtr->uVItemPadding / 2;
1774 if (center_offset_v < 0)
1775 center_offset_v = 0;
1777 if(lStyle & TCS_VERTICAL)
1778 drawRect->left += center_offset_v;
1779 else
1780 drawRect->top += center_offset_v;
1782 /* Draw the text */
1783 if(lStyle & TCS_VERTICAL) /* if we are vertical rotate the text and each character */
1785 static const WCHAR ArialW[] = { 'A','r','i','a','l',0 };
1786 LOGFONTW logfont;
1787 HFONT hFont = 0;
1788 INT nEscapement = 900;
1789 INT nOrientation = 900;
1791 if(lStyle & TCS_BOTTOM)
1793 nEscapement = -900;
1794 nOrientation = -900;
1797 /* to get a font with the escapement and orientation we are looking for, we need to */
1798 /* call CreateFontIndirectA, which requires us to set the values of the logfont we pass in */
1799 if (!GetObjectW((infoPtr->hFont) ?
1800 infoPtr->hFont : GetStockObject(SYSTEM_FONT),
1801 sizeof(LOGFONTW),&logfont))
1803 INT iPointSize = 9;
1805 lstrcpyW(logfont.lfFaceName, ArialW);
1806 logfont.lfHeight = -MulDiv(iPointSize, GetDeviceCaps(hdc, LOGPIXELSY),
1807 72);
1808 logfont.lfWeight = FW_NORMAL;
1809 logfont.lfItalic = 0;
1810 logfont.lfUnderline = 0;
1811 logfont.lfStrikeOut = 0;
1814 logfont.lfEscapement = nEscapement;
1815 logfont.lfOrientation = nOrientation;
1816 hFont = CreateFontIndirectW(&logfont);
1817 SelectObject(hdc, hFont);
1819 if (item->pszText)
1821 ExtTextOutW(hdc,
1822 (lStyle & TCS_BOTTOM) ? drawRect->right : drawRect->left,
1823 (!(lStyle & TCS_BOTTOM)) ? drawRect->bottom : drawRect->top,
1824 ETO_CLIPPED,
1825 drawRect,
1826 item->pszText,
1827 lstrlenW(item->pszText),
1831 DeleteObject(hFont);
1833 else
1835 TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%ld,%ld)-(%ld,%ld), textlen=%ld\n",
1836 debugstr_w(item->pszText), center_offset_h, center_offset_v,
1837 drawRect->left, drawRect->top, drawRect->right, drawRect->bottom,
1838 (rcText.right-rcText.left));
1839 if (item->pszText)
1841 DrawTextW
1843 hdc,
1844 item->pszText,
1845 lstrlenW(item->pszText),
1846 drawRect,
1847 DT_LEFT | DT_SINGLELINE
1852 *drawRect = rcTemp; /* restore drawRect */
1856 * Cleanup
1858 SelectObject(hdc, hOldFont);
1859 SetBkMode(hdc, oldBkMode);
1860 SelectObject(hdc, holdPen);
1861 DeleteObject( htextPen );
1864 /******************************************************************************
1865 * TAB_DrawItem
1867 * This method is used to draw a single tab into the tab control.
1869 static void TAB_DrawItem(
1870 TAB_INFO *infoPtr,
1871 HDC hdc,
1872 INT iItem)
1874 LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1875 RECT itemRect;
1876 RECT selectedRect;
1877 BOOL isVisible;
1878 RECT r, fillRect, r1;
1879 INT clRight = 0;
1880 INT clBottom = 0;
1881 COLORREF bkgnd, corner;
1884 * Get the rectangle for the item.
1886 isVisible = TAB_InternalGetItemRect(infoPtr,
1887 iItem,
1888 &itemRect,
1889 &selectedRect);
1891 if (isVisible)
1893 RECT rUD, rC;
1895 /* Clip UpDown control to not draw over it */
1896 if (infoPtr->needsScrolling)
1898 GetWindowRect(infoPtr->hwnd, &rC);
1899 GetWindowRect(infoPtr->hwndUpDown, &rUD);
1900 ExcludeClipRect(hdc, rUD.left - rC.left, rUD.top - rC.top, rUD.right - rC.left, rUD.bottom - rC.top);
1903 /* If you need to see what the control is doing,
1904 * then override these variables. They will change what
1905 * fill colors are used for filling the tabs, and the
1906 * corners when drawing the edge.
1908 bkgnd = comctl32_color.clrBtnFace;
1909 corner = comctl32_color.clrBtnFace;
1911 if (lStyle & TCS_BUTTONS)
1913 /* Get item rectangle */
1914 r = itemRect;
1916 /* Separators between flat buttons */
1917 if (lStyle & TCS_FLATBUTTONS)
1919 r1 = r;
1920 r1.right += (FLAT_BTN_SPACINGX -2);
1921 DrawEdge(hdc, &r1, EDGE_ETCHED, BF_RIGHT);
1924 if (iItem == infoPtr->iSelected)
1926 DrawEdge(hdc, &r, EDGE_SUNKEN, BF_SOFT|BF_RECT);
1928 OffsetRect(&r, 1, 1);
1930 else /* ! selected */
1932 if (!(lStyle & TCS_FLATBUTTONS))
1933 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_RECT);
1936 else /* !TCS_BUTTONS */
1938 /* We draw a rectangle of different sizes depending on the selection
1939 * state. */
1940 if (iItem == infoPtr->iSelected) {
1941 RECT rect;
1942 GetClientRect (infoPtr->hwnd, &rect);
1943 clRight = rect.right;
1944 clBottom = rect.bottom;
1945 r = selectedRect;
1947 else
1948 r = itemRect;
1951 * Erase the background. (Delay it but setup rectangle.)
1952 * This is necessary when drawing the selected item since it is larger
1953 * than the others, it might overlap with stuff already drawn by the
1954 * other tabs
1956 fillRect = r;
1958 if(lStyle & TCS_VERTICAL)
1960 /* These are for adjusting the drawing of a Selected tab */
1961 /* The initial values are for the normal case of non-Selected */
1962 int ZZ = 1; /* Do not strech if selected */
1963 if (iItem == infoPtr->iSelected) {
1964 ZZ = 0;
1966 /* if leftmost draw the line longer */
1967 if(selectedRect.top == 0)
1968 fillRect.top += CONTROL_BORDER_SIZEY;
1969 /* if rightmost draw the line longer */
1970 if(selectedRect.bottom == clBottom)
1971 fillRect.bottom -= CONTROL_BORDER_SIZEY;
1974 if (lStyle & TCS_BOTTOM)
1976 /* Adjust both rectangles to match native */
1977 r.left += (1-ZZ);
1979 TRACE("<right> item=%d, fill=(%ld,%ld)-(%ld,%ld), edge=(%ld,%ld)-(%ld,%ld)\n",
1980 iItem,
1981 fillRect.left,fillRect.top,fillRect.right,fillRect.bottom,
1982 r.left,r.top,r.right,r.bottom);
1984 /* Clear interior */
1985 SetBkColor(hdc, bkgnd);
1986 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
1988 /* Draw rectangular edge around tab */
1989 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_RIGHT|BF_TOP|BF_BOTTOM);
1991 /* Now erase the top corner and draw diagonal edge */
1992 SetBkColor(hdc, corner);
1993 r1.left = r.right - ROUND_CORNER_SIZE - 1;
1994 r1.top = r.top;
1995 r1.right = r.right;
1996 r1.bottom = r1.top + ROUND_CORNER_SIZE;
1997 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
1998 r1.right--;
1999 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPLEFT);
2001 /* Now erase the bottom corner and draw diagonal edge */
2002 r1.left = r.right - ROUND_CORNER_SIZE - 1;
2003 r1.bottom = r.bottom;
2004 r1.right = r.right;
2005 r1.top = r1.bottom - ROUND_CORNER_SIZE;
2006 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2007 r1.right--;
2008 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMLEFT);
2010 if ((iItem == infoPtr->iSelected) && (selectedRect.top == 0)) {
2011 r1 = r;
2012 r1.right = r1.left;
2013 r1.left--;
2014 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_TOP);
2018 else
2020 TRACE("<left> item=%d, fill=(%ld,%ld)-(%ld,%ld), edge=(%ld,%ld)-(%ld,%ld)\n",
2021 iItem,
2022 fillRect.left,fillRect.top,fillRect.right,fillRect.bottom,
2023 r.left,r.top,r.right,r.bottom);
2025 /* Clear interior */
2026 SetBkColor(hdc, bkgnd);
2027 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2029 /* Draw rectangular edge around tab */
2030 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_TOP|BF_BOTTOM);
2032 /* Now erase the top corner and draw diagonal edge */
2033 SetBkColor(hdc, corner);
2034 r1.left = r.left;
2035 r1.top = r.top;
2036 r1.right = r1.left + ROUND_CORNER_SIZE + 1;
2037 r1.bottom = r1.top + ROUND_CORNER_SIZE;
2038 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2039 r1.left++;
2040 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPRIGHT);
2042 /* Now erase the bottom corner and draw diagonal edge */
2043 r1.left = r.left;
2044 r1.bottom = r.bottom;
2045 r1.right = r1.left + ROUND_CORNER_SIZE + 1;
2046 r1.top = r1.bottom - ROUND_CORNER_SIZE;
2047 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2048 r1.left++;
2049 DrawEdge(hdc, &r1, EDGE_SUNKEN, BF_DIAGONAL_ENDTOPLEFT);
2052 else /* ! TCS_VERTICAL */
2054 /* These are for adjusting the drawing of a Selected tab */
2055 /* The initial values are for the normal case of non-Selected */
2056 if (iItem == infoPtr->iSelected) {
2057 /* if leftmost draw the line longer */
2058 if(selectedRect.left == 0)
2059 fillRect.left += CONTROL_BORDER_SIZEX;
2060 /* if rightmost draw the line longer */
2061 if(selectedRect.right == clRight)
2062 fillRect.right -= CONTROL_BORDER_SIZEX;
2065 if (lStyle & TCS_BOTTOM)
2067 /* Adjust both rectangles for topmost row */
2068 if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2070 fillRect.top -= 2;
2071 r.top -= 1;
2074 TRACE("<bottom> item=%d, fill=(%ld,%ld)-(%ld,%ld), edge=(%ld,%ld)-(%ld,%ld)\n",
2075 iItem,
2076 fillRect.left,fillRect.top,fillRect.right,fillRect.bottom,
2077 r.left,r.top,r.right,r.bottom);
2079 /* Clear interior */
2080 SetBkColor(hdc, bkgnd);
2081 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2083 /* Draw rectangular edge around tab */
2084 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_BOTTOM|BF_RIGHT);
2086 /* Now erase the righthand corner and draw diagonal edge */
2087 SetBkColor(hdc, corner);
2088 r1.left = r.right - ROUND_CORNER_SIZE;
2089 r1.bottom = r.bottom;
2090 r1.right = r.right;
2091 r1.top = r1.bottom - ROUND_CORNER_SIZE - 1;
2092 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2093 r1.bottom--;
2094 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMLEFT);
2096 /* Now erase the lefthand corner and draw diagonal edge */
2097 r1.left = r.left;
2098 r1.bottom = r.bottom;
2099 r1.right = r1.left + ROUND_CORNER_SIZE;
2100 r1.top = r1.bottom - ROUND_CORNER_SIZE - 1;
2101 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2102 r1.bottom--;
2103 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPLEFT);
2105 if (iItem == infoPtr->iSelected)
2107 r.top += 2;
2108 r.left += 1;
2109 if (selectedRect.left == 0)
2111 r1 = r;
2112 r1.bottom = r1.top;
2113 r1.top--;
2114 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_LEFT);
2119 else
2121 /* Adjust both rectangles for bottommost row */
2122 if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2124 fillRect.bottom += 3;
2125 r.bottom += 2;
2128 TRACE("<top> item=%d, fill=(%ld,%ld)-(%ld,%ld), edge=(%ld,%ld)-(%ld,%ld)\n",
2129 iItem,
2130 fillRect.left,fillRect.top,fillRect.right,fillRect.bottom,
2131 r.left,r.top,r.right,r.bottom);
2133 /* Clear interior */
2134 SetBkColor(hdc, bkgnd);
2135 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2137 /* Draw rectangular edge around tab */
2138 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_TOP|BF_RIGHT);
2140 /* Now erase the righthand corner and draw diagonal edge */
2141 SetBkColor(hdc, corner);
2142 r1.left = r.right - ROUND_CORNER_SIZE;
2143 r1.top = r.top;
2144 r1.right = r.right;
2145 r1.bottom = r1.top + ROUND_CORNER_SIZE + 1;
2146 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2147 r1.top++;
2148 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMRIGHT);
2150 /* Now erase the lefthand corner and draw diagonal edge */
2151 r1.left = r.left;
2152 r1.top = r.top;
2153 r1.right = r1.left + ROUND_CORNER_SIZE;
2154 r1.bottom = r1.top + ROUND_CORNER_SIZE + 1;
2155 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2156 r1.top++;
2157 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPRIGHT);
2162 TAB_DumpItemInternal(infoPtr, iItem);
2164 /* This modifies r to be the text rectangle. */
2165 TAB_DrawItemInterior(infoPtr, hdc, iItem, &r);
2169 /******************************************************************************
2170 * TAB_DrawBorder
2172 * This method is used to draw the raised border around the tab control
2173 * "content" area.
2175 static void TAB_DrawBorder (TAB_INFO *infoPtr, HDC hdc)
2177 RECT rect;
2178 DWORD lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
2180 GetClientRect (infoPtr->hwnd, &rect);
2183 * Adjust for the style
2186 if (infoPtr->uNumItem)
2188 if ((lStyle & TCS_BOTTOM) && !(lStyle & TCS_VERTICAL))
2189 rect.bottom -= infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2190 else if((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
2191 rect.right -= infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2192 else if(lStyle & TCS_VERTICAL)
2193 rect.left += infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2194 else /* not TCS_VERTICAL and not TCS_BOTTOM */
2195 rect.top += infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2198 TRACE("border=(%ld,%ld)-(%ld,%ld)\n",
2199 rect.left, rect.top, rect.right, rect.bottom);
2201 DrawEdge(hdc, &rect, EDGE_RAISED, BF_SOFT|BF_RECT);
2204 /******************************************************************************
2205 * TAB_Refresh
2207 * This method repaints the tab control..
2209 static void TAB_Refresh (TAB_INFO *infoPtr, HDC hdc)
2211 HFONT hOldFont;
2212 INT i;
2214 if (!infoPtr->DoRedraw)
2215 return;
2217 hOldFont = SelectObject (hdc, infoPtr->hFont);
2219 if (GetWindowLongW(infoPtr->hwnd, GWL_STYLE) & TCS_BUTTONS)
2221 for (i = 0; i < infoPtr->uNumItem; i++)
2222 TAB_DrawItem (infoPtr, hdc, i);
2224 else
2226 /* Draw all the non selected item first */
2227 for (i = 0; i < infoPtr->uNumItem; i++)
2229 if (i != infoPtr->iSelected)
2230 TAB_DrawItem (infoPtr, hdc, i);
2233 /* Now, draw the border, draw it before the selected item
2234 * since the selected item overwrites part of the border. */
2235 TAB_DrawBorder (infoPtr, hdc);
2237 /* Then, draw the selected item */
2238 TAB_DrawItem (infoPtr, hdc, infoPtr->iSelected);
2240 /* If we haven't set the current focus yet, set it now.
2241 * Only happens when we first paint the tab controls */
2242 if (infoPtr->uFocus == -1)
2243 TAB_SetCurFocus(infoPtr, infoPtr->iSelected);
2246 SelectObject (hdc, hOldFont);
2249 static inline DWORD TAB_GetRowCount (const TAB_INFO *infoPtr)
2251 return infoPtr->uNumRows;
2254 static inline LRESULT TAB_SetRedraw (TAB_INFO *infoPtr, BOOL doRedraw)
2256 infoPtr->DoRedraw = doRedraw;
2257 return 0;
2260 /******************************************************************************
2261 * TAB_EnsureSelectionVisible
2263 * This method will make sure that the current selection is completely
2264 * visible by scrolling until it is.
2266 static void TAB_EnsureSelectionVisible(
2267 TAB_INFO* infoPtr)
2269 INT iSelected = infoPtr->iSelected;
2270 LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
2271 INT iOrigLeftmostVisible = infoPtr->leftmostVisible;
2273 /* set the items row to the bottommost row or topmost row depending on
2274 * style */
2275 if ((infoPtr->uNumRows > 1) && !(lStyle & TCS_BUTTONS))
2277 TAB_ITEM *selected = TAB_GetItem(infoPtr, iSelected);
2278 INT newselected;
2279 INT iTargetRow;
2281 if(lStyle & TCS_VERTICAL)
2282 newselected = selected->rect.left;
2283 else
2284 newselected = selected->rect.top;
2286 /* the target row is always (number of rows - 1)
2287 as row 0 is furthest from the clientRect */
2288 iTargetRow = infoPtr->uNumRows - 1;
2290 if (newselected != iTargetRow)
2292 UINT i;
2293 if(lStyle & TCS_VERTICAL)
2295 for (i=0; i < infoPtr->uNumItem; i++)
2297 /* move everything in the row of the selected item to the iTargetRow */
2298 TAB_ITEM *item = TAB_GetItem(infoPtr, i);
2300 if (item->rect.left == newselected )
2301 item->rect.left = iTargetRow;
2302 else
2304 if (item->rect.left > newselected)
2305 item->rect.left-=1;
2309 else
2311 for (i=0; i < infoPtr->uNumItem; i++)
2313 TAB_ITEM *item = TAB_GetItem(infoPtr, i);
2315 if (item->rect.top == newselected )
2316 item->rect.top = iTargetRow;
2317 else
2319 if (item->rect.top > newselected)
2320 item->rect.top-=1;
2324 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
2329 * Do the trivial cases first.
2331 if ( (!infoPtr->needsScrolling) ||
2332 (infoPtr->hwndUpDown==0) || (lStyle & TCS_VERTICAL))
2333 return;
2335 if (infoPtr->leftmostVisible >= iSelected)
2337 infoPtr->leftmostVisible = iSelected;
2339 else
2341 TAB_ITEM *selected = TAB_GetItem(infoPtr, iSelected);
2342 RECT r;
2343 INT width;
2344 UINT i;
2346 /* Calculate the part of the client area that is visible */
2347 GetClientRect(infoPtr->hwnd, &r);
2348 width = r.right;
2350 GetClientRect(infoPtr->hwndUpDown, &r);
2351 width -= r.right;
2353 if ((selected->rect.right -
2354 selected->rect.left) >= width )
2356 /* Special case: width of selected item is greater than visible
2357 * part of control.
2359 infoPtr->leftmostVisible = iSelected;
2361 else
2363 for (i = infoPtr->leftmostVisible; i < infoPtr->uNumItem; i++)
2365 if ((selected->rect.right - TAB_GetItem(infoPtr, i)->rect.left) < width)
2366 break;
2368 infoPtr->leftmostVisible = i;
2372 if (infoPtr->leftmostVisible != iOrigLeftmostVisible)
2373 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
2375 SendMessageW(infoPtr->hwndUpDown, UDM_SETPOS, 0,
2376 MAKELONG(infoPtr->leftmostVisible, 0));
2379 /******************************************************************************
2380 * TAB_InvalidateTabArea
2382 * This method will invalidate the portion of the control that contains the
2383 * tabs. It is called when the state of the control changes and needs
2384 * to be redisplayed
2386 static void TAB_InvalidateTabArea(TAB_INFO* infoPtr)
2388 RECT clientRect, rInvalidate, rAdjClient;
2389 DWORD lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
2390 INT lastRow = infoPtr->uNumRows - 1;
2391 RECT rect;
2393 if (lastRow < 0) return;
2395 GetClientRect(infoPtr->hwnd, &clientRect);
2396 rInvalidate = clientRect;
2397 rAdjClient = clientRect;
2399 TAB_AdjustRect(infoPtr, 0, &rAdjClient);
2401 TAB_InternalGetItemRect(infoPtr, infoPtr->uNumItem-1 , &rect, NULL);
2402 if ((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
2404 rInvalidate.left = rAdjClient.right;
2405 if (infoPtr->uNumRows == 1)
2406 rInvalidate.bottom = clientRect.top + rect.bottom + 2 * SELECTED_TAB_OFFSET;
2408 else if(lStyle & TCS_VERTICAL)
2410 rInvalidate.right = rAdjClient.left;
2411 if (infoPtr->uNumRows == 1)
2412 rInvalidate.bottom = clientRect.top + rect.bottom + 2 * SELECTED_TAB_OFFSET;
2414 else if (lStyle & TCS_BOTTOM)
2416 rInvalidate.top = rAdjClient.bottom;
2417 if (infoPtr->uNumRows == 1)
2418 rInvalidate.right = clientRect.left + rect.right + 2 * SELECTED_TAB_OFFSET;
2420 else
2422 rInvalidate.bottom = rAdjClient.top;
2423 if (infoPtr->uNumRows == 1)
2424 rInvalidate.right = clientRect.left + rect.right + 2 * SELECTED_TAB_OFFSET;
2427 /* Punch out the updown control */
2428 if (infoPtr->needsScrolling && (rInvalidate.right > 0)) {
2429 RECT r;
2430 GetClientRect(infoPtr->hwndUpDown, &r);
2431 if (rInvalidate.right > clientRect.right - r.left)
2432 rInvalidate.right = rInvalidate.right - (r.right - r.left);
2433 else
2434 rInvalidate.right = clientRect.right - r.left;
2437 TRACE("invalidate (%ld,%ld)-(%ld,%ld)\n",
2438 rInvalidate.left, rInvalidate.top,
2439 rInvalidate.right, rInvalidate.bottom);
2441 InvalidateRect(infoPtr->hwnd, &rInvalidate, TRUE);
2444 static inline LRESULT TAB_Paint (TAB_INFO *infoPtr, HDC hdcPaint)
2446 HDC hdc;
2447 PAINTSTRUCT ps;
2449 if (hdcPaint)
2450 hdc = hdcPaint;
2451 else
2453 hdc = BeginPaint (infoPtr->hwnd, &ps);
2454 TRACE("erase %d, rect=(%ld,%ld)-(%ld,%ld)\n",
2455 ps.fErase,
2456 ps.rcPaint.left,ps.rcPaint.top,ps.rcPaint.right,ps.rcPaint.bottom);
2459 TAB_Refresh (infoPtr, hdc);
2461 if (!hdcPaint)
2462 EndPaint (infoPtr->hwnd, &ps);
2464 return 0;
2467 static LRESULT
2468 TAB_InsertItemT (TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam, BOOL bUnicode)
2470 TAB_ITEM *item;
2471 TCITEMW *pti;
2472 INT iItem;
2473 RECT rect;
2475 GetClientRect (infoPtr->hwnd, &rect);
2476 TRACE("Rect: %p T %li, L %li, B %li, R %li\n", infoPtr->hwnd,
2477 rect.top, rect.left, rect.bottom, rect.right);
2479 pti = (TCITEMW *)lParam;
2480 iItem = (INT)wParam;
2482 if (iItem < 0) return -1;
2483 if (iItem > infoPtr->uNumItem)
2484 iItem = infoPtr->uNumItem;
2486 TAB_DumpItemExternalT(pti, iItem, bUnicode);
2489 if (infoPtr->uNumItem == 0) {
2490 infoPtr->items = Alloc (TAB_ITEM_SIZE(infoPtr));
2491 infoPtr->uNumItem++;
2492 infoPtr->iSelected = 0;
2494 else {
2495 LPBYTE oldItems = (LPBYTE)infoPtr->items;
2497 infoPtr->uNumItem++;
2498 infoPtr->items = Alloc (TAB_ITEM_SIZE(infoPtr) * infoPtr->uNumItem);
2500 /* pre insert copy */
2501 if (iItem > 0) {
2502 memcpy (infoPtr->items, oldItems,
2503 iItem * TAB_ITEM_SIZE(infoPtr));
2506 /* post insert copy */
2507 if (iItem < infoPtr->uNumItem - 1) {
2508 memcpy (TAB_GetItem(infoPtr, iItem + 1),
2509 oldItems + iItem * TAB_ITEM_SIZE(infoPtr),
2510 (infoPtr->uNumItem - iItem - 1) * TAB_ITEM_SIZE(infoPtr));
2514 if (iItem <= infoPtr->iSelected)
2515 infoPtr->iSelected++;
2517 Free (oldItems);
2520 item = TAB_GetItem(infoPtr, iItem);
2522 item->mask = pti->mask;
2523 item->pszText = NULL;
2525 if (pti->mask & TCIF_TEXT)
2527 if (bUnicode)
2528 Str_SetPtrW (&item->pszText, pti->pszText);
2529 else
2530 Str_SetPtrAtoW (&item->pszText, (LPSTR)pti->pszText);
2533 if (pti->mask & TCIF_IMAGE)
2534 item->iImage = pti->iImage;
2535 else
2536 item->iImage = -1;
2538 if (pti->mask & TCIF_PARAM)
2539 memcpy(item->extra, &pti->lParam, infoPtr->cbInfo);
2540 else
2541 memset(item->extra, 0, infoPtr->cbInfo);
2543 TAB_SetItemBounds(infoPtr);
2544 if (infoPtr->uNumItem > 1)
2545 TAB_InvalidateTabArea(infoPtr);
2546 else
2547 InvalidateRect(infoPtr->hwnd, NULL, TRUE);
2549 TRACE("[%p]: added item %d %s\n",
2550 infoPtr->hwnd, iItem, debugstr_w(item->pszText));
2552 return iItem;
2555 static LRESULT
2556 TAB_SetItemSize (TAB_INFO *infoPtr, LPARAM lParam)
2558 LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
2559 LONG lResult = 0;
2560 BOOL bNeedPaint = FALSE;
2562 lResult = MAKELONG(infoPtr->tabWidth, infoPtr->tabHeight);
2564 /* UNDOCUMENTED: If requested Width or Height is 0 this means that program wants to use auto size. */
2565 if (lStyle & TCS_FIXEDWIDTH && (infoPtr->tabWidth != (INT)LOWORD(lParam)))
2567 infoPtr->tabWidth = max((INT)LOWORD(lParam), infoPtr->tabMinWidth);
2568 bNeedPaint = TRUE;
2571 if (infoPtr->tabHeight != (INT)HIWORD(lParam))
2573 if ((infoPtr->fHeightSet = ((INT)HIWORD(lParam) != 0)))
2574 infoPtr->tabHeight = (INT)HIWORD(lParam);
2576 bNeedPaint = TRUE;
2578 TRACE("was h=%d,w=%d, now h=%d,w=%d\n",
2579 HIWORD(lResult), LOWORD(lResult),
2580 infoPtr->tabHeight, infoPtr->tabWidth);
2582 if (bNeedPaint)
2584 TAB_SetItemBounds(infoPtr);
2585 RedrawWindow(infoPtr->hwnd, NULL, NULL, RDW_ERASE | RDW_INVALIDATE | RDW_UPDATENOW);
2588 return lResult;
2591 static inline LRESULT TAB_SetMinTabWidth (TAB_INFO *infoPtr, INT cx)
2593 INT oldcx = 0;
2595 TRACE("(%p,%d)\n", infoPtr, cx);
2597 if (infoPtr) {
2598 oldcx = infoPtr->tabMinWidth;
2599 infoPtr->tabMinWidth = (cx==-1)?DEFAULT_TAB_WIDTH:cx;
2602 return oldcx;
2605 static inline LRESULT
2606 TAB_HighlightItem (TAB_INFO *infoPtr, INT iItem, BOOL fHighlight)
2608 LPDWORD lpState;
2610 TRACE("(%p,%d,%s)\n", infoPtr, iItem, fHighlight ? "true" : "false");
2612 if (!infoPtr || iItem < 0 || iItem >= infoPtr->uNumItem)
2613 return FALSE;
2615 lpState = &TAB_GetItem(infoPtr, iItem)->dwState;
2617 if (fHighlight)
2618 *lpState |= TCIS_HIGHLIGHTED;
2619 else
2620 *lpState &= ~TCIS_HIGHLIGHTED;
2622 return TRUE;
2625 static LRESULT
2626 TAB_SetItemT (TAB_INFO *infoPtr, INT iItem, LPTCITEMW tabItem, BOOL bUnicode)
2628 TAB_ITEM *wineItem;
2630 TRACE("(%p,%d,%p,%s)\n", infoPtr, iItem, tabItem, bUnicode ? "true" : "false");
2632 if (iItem < 0 || iItem >= infoPtr->uNumItem)
2633 return FALSE;
2635 TAB_DumpItemExternalT(tabItem, iItem, bUnicode);
2637 wineItem = TAB_GetItem(infoPtr, iItem);
2639 if (tabItem->mask & TCIF_IMAGE)
2640 wineItem->iImage = tabItem->iImage;
2642 if (tabItem->mask & TCIF_PARAM)
2643 memcpy(wineItem->extra, &tabItem->lParam, infoPtr->cbInfo);
2645 if (tabItem->mask & TCIF_RTLREADING)
2646 FIXME("TCIF_RTLREADING\n");
2648 if (tabItem->mask & TCIF_STATE)
2649 wineItem->dwState = tabItem->dwState;
2651 if (tabItem->mask & TCIF_TEXT)
2653 if (wineItem->pszText)
2655 Free(wineItem->pszText);
2656 wineItem->pszText = NULL;
2658 if (bUnicode)
2659 Str_SetPtrW(&wineItem->pszText, tabItem->pszText);
2660 else
2661 Str_SetPtrAtoW(&wineItem->pszText, (LPSTR)tabItem->pszText);
2664 /* Update and repaint tabs */
2665 TAB_SetItemBounds(infoPtr);
2666 TAB_InvalidateTabArea(infoPtr);
2668 return TRUE;
2671 static inline LRESULT TAB_GetItemCount (const TAB_INFO *infoPtr)
2673 return infoPtr->uNumItem;
2677 static LRESULT
2678 TAB_GetItemT (TAB_INFO *infoPtr, INT iItem, LPTCITEMW tabItem, BOOL bUnicode)
2680 TAB_ITEM *wineItem;
2682 TRACE("(%p,%d,%p,%s)\n", infoPtr, iItem, tabItem, bUnicode ? "true" : "false");
2684 if (iItem < 0 || iItem >= infoPtr->uNumItem)
2685 return FALSE;
2687 wineItem = TAB_GetItem(infoPtr, iItem);
2689 if (tabItem->mask & TCIF_IMAGE)
2690 tabItem->iImage = wineItem->iImage;
2692 if (tabItem->mask & TCIF_PARAM)
2693 memcpy(&tabItem->lParam, wineItem->extra, infoPtr->cbInfo);
2695 if (tabItem->mask & TCIF_RTLREADING)
2696 FIXME("TCIF_RTLREADING\n");
2698 if (tabItem->mask & TCIF_STATE)
2699 tabItem->dwState = wineItem->dwState;
2701 if (tabItem->mask & TCIF_TEXT)
2703 if (bUnicode)
2704 Str_GetPtrW (wineItem->pszText, tabItem->pszText, tabItem->cchTextMax);
2705 else
2706 Str_GetPtrWtoA (wineItem->pszText, (LPSTR)tabItem->pszText, tabItem->cchTextMax);
2709 TAB_DumpItemExternalT(tabItem, iItem, bUnicode);
2711 return TRUE;
2715 static LRESULT TAB_DeleteItem (TAB_INFO *infoPtr, INT iItem)
2717 BOOL bResult = FALSE;
2719 TRACE("(%p, %d)\n", infoPtr, iItem);
2721 if ((iItem >= 0) && (iItem < infoPtr->uNumItem))
2723 TAB_ITEM *item = TAB_GetItem(infoPtr, iItem);
2724 LPBYTE oldItems = (LPBYTE)infoPtr->items;
2726 TAB_InvalidateTabArea(infoPtr);
2728 if ((item->mask & TCIF_TEXT) && item->pszText)
2729 Free(item->pszText);
2731 infoPtr->uNumItem--;
2733 if (!infoPtr->uNumItem)
2735 infoPtr->items = NULL;
2736 if (infoPtr->iHotTracked >= 0)
2738 KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
2739 infoPtr->iHotTracked = -1;
2742 else
2744 infoPtr->items = Alloc(TAB_ITEM_SIZE(infoPtr) * infoPtr->uNumItem);
2746 if (iItem > 0)
2747 memcpy(infoPtr->items, oldItems, iItem * TAB_ITEM_SIZE(infoPtr));
2749 if (iItem < infoPtr->uNumItem)
2750 memcpy(TAB_GetItem(infoPtr, iItem),
2751 oldItems + (iItem + 1) * TAB_ITEM_SIZE(infoPtr),
2752 (infoPtr->uNumItem - iItem) * TAB_ITEM_SIZE(infoPtr));
2754 if (iItem <= infoPtr->iHotTracked)
2756 /* When tabs move left/up, the hot track item may change */
2757 FIXME("Recalc hot track");
2760 Free(oldItems);
2762 /* Readjust the selected index */
2763 if ((iItem == infoPtr->iSelected) && (iItem > 0))
2764 infoPtr->iSelected--;
2766 if (iItem < infoPtr->iSelected)
2767 infoPtr->iSelected--;
2769 if (infoPtr->uNumItem == 0)
2770 infoPtr->iSelected = -1;
2772 /* Reposition and repaint tabs */
2773 TAB_SetItemBounds(infoPtr);
2775 bResult = TRUE;
2778 return bResult;
2781 static inline LRESULT TAB_DeleteAllItems (TAB_INFO *infoPtr)
2783 TRACE("(%p)\n", infoPtr);
2784 while (infoPtr->uNumItem)
2785 TAB_DeleteItem (infoPtr, 0);
2786 return TRUE;
2790 static inline LRESULT TAB_GetFont (const TAB_INFO *infoPtr)
2792 TRACE("(%p) returning %p\n", infoPtr, infoPtr->hFont);
2793 return (LRESULT)infoPtr->hFont;
2796 static inline LRESULT TAB_SetFont (TAB_INFO *infoPtr, HFONT hNewFont)
2798 TRACE("(%p,%p)\n", infoPtr, hNewFont);
2800 infoPtr->hFont = hNewFont;
2802 TAB_SetItemBounds(infoPtr);
2804 TAB_InvalidateTabArea(infoPtr);
2806 return 0;
2810 static inline LRESULT TAB_GetImageList (const TAB_INFO *infoPtr)
2812 TRACE("\n");
2813 return (LRESULT)infoPtr->himl;
2816 static inline LRESULT TAB_SetImageList (TAB_INFO *infoPtr, HIMAGELIST himlNew)
2818 HIMAGELIST himlPrev = infoPtr->himl;
2819 TRACE("\n");
2820 infoPtr->himl = himlNew;
2821 return (LRESULT)himlPrev;
2824 static inline LRESULT TAB_GetUnicodeFormat (const TAB_INFO *infoPtr)
2826 return infoPtr->bUnicode;
2829 static inline LRESULT TAB_SetUnicodeFormat (TAB_INFO *infoPtr, BOOL bUnicode)
2831 BOOL bTemp = infoPtr->bUnicode;
2833 infoPtr->bUnicode = bUnicode;
2835 return bTemp;
2838 static inline LRESULT TAB_Size (TAB_INFO *infoPtr)
2840 /* I'm not really sure what the following code was meant to do.
2841 This is what it is doing:
2842 When WM_SIZE is sent with SIZE_RESTORED, the control
2843 gets positioned in the top left corner.
2845 RECT parent_rect;
2846 HWND parent;
2847 UINT uPosFlags,cx,cy;
2849 uPosFlags=0;
2850 if (!wParam) {
2851 parent = GetParent (hwnd);
2852 GetClientRect(parent, &parent_rect);
2853 cx=LOWORD (lParam);
2854 cy=HIWORD (lParam);
2855 if (GetWindowLongW(hwnd, GWL_STYLE) & CCS_NORESIZE)
2856 uPosFlags |= (SWP_NOSIZE | SWP_NOMOVE);
2858 SetWindowPos (hwnd, 0, parent_rect.left, parent_rect.top,
2859 cx, cy, uPosFlags | SWP_NOZORDER);
2860 } else {
2861 FIXME("WM_SIZE flag %x %lx not handled\n", wParam, lParam);
2862 } */
2864 /* Recompute the size/position of the tabs. */
2865 TAB_SetItemBounds (infoPtr);
2867 /* Force a repaint of the control. */
2868 InvalidateRect(infoPtr->hwnd, NULL, TRUE);
2870 return 0;
2874 static LRESULT TAB_Create (HWND hwnd, WPARAM wParam, LPARAM lParam)
2876 TAB_INFO *infoPtr;
2877 TEXTMETRICW fontMetrics;
2878 HDC hdc;
2879 HFONT hOldFont;
2880 DWORD dwStyle;
2882 infoPtr = (TAB_INFO *)Alloc (sizeof(TAB_INFO));
2884 SetWindowLongPtrW(hwnd, 0, (DWORD_PTR)infoPtr);
2886 infoPtr->hwnd = hwnd;
2887 infoPtr->hwndNotify = ((LPCREATESTRUCTW)lParam)->hwndParent;
2888 infoPtr->uNumItem = 0;
2889 infoPtr->uNumRows = 0;
2890 infoPtr->uHItemPadding = 6;
2891 infoPtr->uVItemPadding = 3;
2892 infoPtr->uHItemPadding_s = 6;
2893 infoPtr->uVItemPadding_s = 3;
2894 infoPtr->hFont = 0;
2895 infoPtr->items = 0;
2896 infoPtr->hcurArrow = LoadCursorW (0, (LPWSTR)IDC_ARROW);
2897 infoPtr->iSelected = -1;
2898 infoPtr->iHotTracked = -1;
2899 infoPtr->uFocus = -1;
2900 infoPtr->hwndToolTip = 0;
2901 infoPtr->DoRedraw = TRUE;
2902 infoPtr->needsScrolling = FALSE;
2903 infoPtr->hwndUpDown = 0;
2904 infoPtr->leftmostVisible = 0;
2905 infoPtr->fHeightSet = FALSE;
2906 infoPtr->bUnicode = IsWindowUnicode (hwnd);
2907 infoPtr->cbInfo = sizeof(LPARAM);
2909 TRACE("Created tab control, hwnd [%p]\n", hwnd);
2911 /* The tab control always has the WS_CLIPSIBLINGS style. Even
2912 if you don't specify it in CreateWindow. This is necessary in
2913 order for paint to work correctly. This follows windows behaviour. */
2914 dwStyle = GetWindowLongW(hwnd, GWL_STYLE);
2915 SetWindowLongW(hwnd, GWL_STYLE, dwStyle|WS_CLIPSIBLINGS);
2917 if (dwStyle & TCS_TOOLTIPS) {
2918 /* Create tooltip control */
2919 infoPtr->hwndToolTip =
2920 CreateWindowExW (0, TOOLTIPS_CLASSW, NULL, 0,
2921 CW_USEDEFAULT, CW_USEDEFAULT,
2922 CW_USEDEFAULT, CW_USEDEFAULT,
2923 hwnd, 0, 0, 0);
2925 /* Send NM_TOOLTIPSCREATED notification */
2926 if (infoPtr->hwndToolTip) {
2927 NMTOOLTIPSCREATED nmttc;
2929 nmttc.hdr.hwndFrom = hwnd;
2930 nmttc.hdr.idFrom = GetWindowLongPtrW(hwnd, GWLP_ID);
2931 nmttc.hdr.code = NM_TOOLTIPSCREATED;
2932 nmttc.hwndToolTips = infoPtr->hwndToolTip;
2934 SendMessageW (infoPtr->hwndNotify, WM_NOTIFY,
2935 (WPARAM)GetWindowLongPtrW(hwnd, GWLP_ID), (LPARAM)&nmttc);
2940 * We need to get text information so we need a DC and we need to select
2941 * a font.
2943 hdc = GetDC(hwnd);
2944 hOldFont = SelectObject (hdc, GetStockObject (SYSTEM_FONT));
2946 /* Use the system font to determine the initial height of a tab. */
2947 GetTextMetricsW(hdc, &fontMetrics);
2950 * Make sure there is enough space for the letters + growing the
2951 * selected item + extra space for the selected item.
2953 infoPtr->tabHeight = fontMetrics.tmHeight + SELECTED_TAB_OFFSET +
2954 ((dwStyle & TCS_BUTTONS) ? 2 : 1) *
2955 infoPtr->uVItemPadding;
2957 /* Initialize the width of a tab. */
2958 infoPtr->tabWidth = DEFAULT_TAB_WIDTH;
2959 infoPtr->tabMinWidth = 0;
2961 TRACE("tabH=%d, tabW=%d\n", infoPtr->tabHeight, infoPtr->tabWidth);
2963 SelectObject (hdc, hOldFont);
2964 ReleaseDC(hwnd, hdc);
2966 return 0;
2969 static LRESULT
2970 TAB_Destroy (TAB_INFO *infoPtr)
2972 UINT iItem;
2974 if (!infoPtr)
2975 return 0;
2977 SetWindowLongPtrW(infoPtr->hwnd, 0, 0);
2979 if (infoPtr->items) {
2980 for (iItem = 0; iItem < infoPtr->uNumItem; iItem++) {
2981 if (TAB_GetItem(infoPtr, iItem)->pszText)
2982 Free (TAB_GetItem(infoPtr, iItem)->pszText);
2984 Free (infoPtr->items);
2987 if (infoPtr->hwndToolTip)
2988 DestroyWindow (infoPtr->hwndToolTip);
2990 if (infoPtr->hwndUpDown)
2991 DestroyWindow(infoPtr->hwndUpDown);
2993 if (infoPtr->iHotTracked >= 0)
2994 KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
2996 Free (infoPtr);
2997 return 0;
3000 static LRESULT TAB_NCCalcSize(HWND hwnd, WPARAM wParam, LPARAM lParam)
3002 if (!wParam)
3003 return 0;
3004 return WVR_ALIGNTOP;
3007 static inline LRESULT
3008 TAB_SetItemExtra (TAB_INFO *infoPtr, INT cbInfo)
3010 if (!infoPtr || cbInfo <= 0)
3011 return FALSE;
3013 if (infoPtr->uNumItem)
3015 /* FIXME: MSDN says this is not allowed, but this hasn't been verified */
3016 return FALSE;
3019 infoPtr->cbInfo = cbInfo;
3020 return TRUE;
3023 static LRESULT WINAPI
3024 TAB_WindowProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
3026 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
3028 TRACE("hwnd=%p msg=%x wParam=%x lParam=%lx\n", hwnd, uMsg, wParam, lParam);
3029 if (!infoPtr && (uMsg != WM_CREATE))
3030 return DefWindowProcW (hwnd, uMsg, wParam, lParam);
3032 switch (uMsg)
3034 case TCM_GETIMAGELIST:
3035 return TAB_GetImageList (infoPtr);
3037 case TCM_SETIMAGELIST:
3038 return TAB_SetImageList (infoPtr, (HIMAGELIST)lParam);
3040 case TCM_GETITEMCOUNT:
3041 return TAB_GetItemCount (infoPtr);
3043 case TCM_GETITEMA:
3044 case TCM_GETITEMW:
3045 return TAB_GetItemT (infoPtr, (INT)wParam, (LPTCITEMW)lParam, uMsg == TCM_GETITEMW);
3047 case TCM_SETITEMA:
3048 case TCM_SETITEMW:
3049 return TAB_SetItemT (infoPtr, (INT)wParam, (LPTCITEMW)lParam, uMsg == TCM_SETITEMW);
3051 case TCM_DELETEITEM:
3052 return TAB_DeleteItem (infoPtr, (INT)wParam);
3054 case TCM_DELETEALLITEMS:
3055 return TAB_DeleteAllItems (infoPtr);
3057 case TCM_GETITEMRECT:
3058 return TAB_GetItemRect (infoPtr, wParam, lParam);
3060 case TCM_GETCURSEL:
3061 return TAB_GetCurSel (infoPtr);
3063 case TCM_HITTEST:
3064 return TAB_HitTest (infoPtr, (LPTCHITTESTINFO)lParam);
3066 case TCM_SETCURSEL:
3067 return TAB_SetCurSel (infoPtr, (INT)wParam);
3069 case TCM_INSERTITEMA:
3070 case TCM_INSERTITEMW:
3071 return TAB_InsertItemT (infoPtr, wParam, lParam, uMsg == TCM_INSERTITEMW);
3073 case TCM_SETITEMEXTRA:
3074 return TAB_SetItemExtra (infoPtr, (int)wParam);
3076 case TCM_ADJUSTRECT:
3077 return TAB_AdjustRect (infoPtr, (BOOL)wParam, (LPRECT)lParam);
3079 case TCM_SETITEMSIZE:
3080 return TAB_SetItemSize (infoPtr, lParam);
3082 case TCM_REMOVEIMAGE:
3083 FIXME("Unimplemented msg TCM_REMOVEIMAGE\n");
3084 return 0;
3086 case TCM_SETPADDING:
3087 return TAB_SetPadding (infoPtr, lParam);
3089 case TCM_GETROWCOUNT:
3090 return TAB_GetRowCount(infoPtr);
3092 case TCM_GETUNICODEFORMAT:
3093 return TAB_GetUnicodeFormat (infoPtr);
3095 case TCM_SETUNICODEFORMAT:
3096 return TAB_SetUnicodeFormat (infoPtr, (BOOL)wParam);
3098 case TCM_HIGHLIGHTITEM:
3099 return TAB_HighlightItem (infoPtr, (INT)wParam, (BOOL)LOWORD(lParam));
3101 case TCM_GETTOOLTIPS:
3102 return TAB_GetToolTips (infoPtr);
3104 case TCM_SETTOOLTIPS:
3105 return TAB_SetToolTips (infoPtr, (HWND)wParam);
3107 case TCM_GETCURFOCUS:
3108 return TAB_GetCurFocus (infoPtr);
3110 case TCM_SETCURFOCUS:
3111 return TAB_SetCurFocus (infoPtr, (INT)wParam);
3113 case TCM_SETMINTABWIDTH:
3114 return TAB_SetMinTabWidth(infoPtr, (INT)lParam);
3116 case TCM_DESELECTALL:
3117 FIXME("Unimplemented msg TCM_DESELECTALL\n");
3118 return 0;
3120 case TCM_GETEXTENDEDSTYLE:
3121 FIXME("Unimplemented msg TCM_GETEXTENDEDSTYLE\n");
3122 return 0;
3124 case TCM_SETEXTENDEDSTYLE:
3125 FIXME("Unimplemented msg TCM_SETEXTENDEDSTYLE\n");
3126 return 0;
3128 case WM_GETFONT:
3129 return TAB_GetFont (infoPtr);
3131 case WM_SETFONT:
3132 return TAB_SetFont (infoPtr, (HFONT)wParam);
3134 case WM_CREATE:
3135 return TAB_Create (hwnd, wParam, lParam);
3137 case WM_NCDESTROY:
3138 return TAB_Destroy (infoPtr);
3140 case WM_GETDLGCODE:
3141 return DLGC_WANTARROWS | DLGC_WANTCHARS;
3143 case WM_LBUTTONDOWN:
3144 return TAB_LButtonDown (infoPtr, wParam, lParam);
3146 case WM_LBUTTONUP:
3147 return TAB_LButtonUp (infoPtr);
3149 case WM_NOTIFY:
3150 return SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, wParam, lParam);
3152 case WM_RBUTTONDOWN:
3153 return TAB_RButtonDown (infoPtr);
3155 case WM_MOUSEMOVE:
3156 return TAB_MouseMove (infoPtr, wParam, lParam);
3158 case WM_PAINT:
3159 return TAB_Paint (infoPtr, (HDC)wParam);
3161 case WM_SIZE:
3162 return TAB_Size (infoPtr);
3164 case WM_SETREDRAW:
3165 return TAB_SetRedraw (infoPtr, (BOOL)wParam);
3167 case WM_HSCROLL:
3168 return TAB_OnHScroll(infoPtr, (int)LOWORD(wParam), (int)HIWORD(wParam), (HWND)lParam);
3170 case WM_STYLECHANGED:
3171 TAB_SetItemBounds (infoPtr);
3172 InvalidateRect(hwnd, NULL, TRUE);
3173 return 0;
3175 case WM_SYSCOLORCHANGE:
3176 COMCTL32_RefreshSysColors();
3177 return 0;
3179 case WM_KILLFOCUS:
3180 case WM_SETFOCUS:
3181 TAB_FocusChanging(infoPtr);
3182 break; /* Don't disturb normal focus behavior */
3184 case WM_KEYUP:
3185 return TAB_KeyUp(infoPtr, wParam);
3186 case WM_NCHITTEST:
3187 return TAB_NCHitTest(infoPtr, lParam);
3189 case WM_NCCALCSIZE:
3190 return TAB_NCCalcSize(hwnd, wParam, lParam);
3192 default:
3193 if (uMsg >= WM_USER && uMsg < WM_APP)
3194 WARN("unknown msg %04x wp=%08x lp=%08lx\n",
3195 uMsg, wParam, lParam);
3196 break;
3198 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
3202 void
3203 TAB_Register (void)
3205 WNDCLASSW wndClass;
3207 ZeroMemory (&wndClass, sizeof(WNDCLASSW));
3208 wndClass.style = CS_GLOBALCLASS | CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
3209 wndClass.lpfnWndProc = TAB_WindowProc;
3210 wndClass.cbClsExtra = 0;
3211 wndClass.cbWndExtra = sizeof(TAB_INFO *);
3212 wndClass.hCursor = LoadCursorW (0, (LPWSTR)IDC_ARROW);
3213 wndClass.hbrBackground = (HBRUSH)(COLOR_BTNFACE+1);
3214 wndClass.lpszClassName = WC_TABCONTROLW;
3216 RegisterClassW (&wndClass);
3220 void
3221 TAB_Unregister (void)
3223 UnregisterClassW (WC_TABCONTROLW, NULL);