4 * Copyright 1998 Anders Carlsson
5 * Copyright 1999 Alex Priem <alexp@sci.kun.nl>
6 * Copyright 1999 Francis Beaudet
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public
10 * License as published by the Free Software Foundation; either
11 * version 2.1 of the License, or (at your option) any later version.
13 * This library is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Lesser General Public License for more details.
18 * You should have received a copy of the GNU Lesser General Public
19 * License along with this library; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24 * Unicode support (under construction)
27 * UpDown control not displayed until after a tab is clicked on
40 #include "wine/debug.h"
43 WINE_DEFAULT_DEBUG_CHANNEL(tab
);
52 RECT rect
; /* bounding rectangle of the item relative to the
53 * leftmost item (the leftmost item, 0, would have a
54 * "left" member of 0 in this rectangle)
56 * additionally the top member hold the row number
57 * and bottom is unused and should be 0 */
62 UINT uNumItem
; /* number of tab items */
63 UINT uNumRows
; /* number of tab rows */
64 INT tabHeight
; /* height of the tab row */
65 INT tabWidth
; /* width of tabs */
66 INT tabMinWidth
; /* minimum width of items */
67 USHORT uHItemPadding
; /* amount of horizontal padding, in pixels */
68 USHORT uVItemPadding
; /* amount of vertical padding, in pixels */
69 HFONT hFont
; /* handle to the current font */
70 HCURSOR hcurArrow
; /* handle to the current cursor */
71 HIMAGELIST himl
; /* handle to a image list (may be 0) */
72 HWND hwndToolTip
; /* handle to tab's tooltip */
73 INT leftmostVisible
; /* Used for scrolling, this member contains
74 * the index of the first visible item */
75 INT iSelected
; /* the currently selected item */
76 INT iHotTracked
; /* the highlighted item under the mouse */
77 INT uFocus
; /* item which has the focus */
78 TAB_ITEM
* items
; /* pointer to an array of TAB_ITEM's */
79 BOOL DoRedraw
; /* flag for redrawing when tab contents is changed*/
80 BOOL needsScrolling
; /* TRUE if the size of the tabs is greater than
81 * the size of the control */
82 BOOL fHeightSet
; /* was the height of the tabs explicitly set? */
83 BOOL bUnicode
; /* Unicode control? */
84 HWND hwndUpDown
; /* Updown control used for scrolling */
87 /******************************************************************************
88 * Positioning constants
90 #define SELECTED_TAB_OFFSET 2
91 #define ROUND_CORNER_SIZE 2
92 #define DISPLAY_AREA_PADDINGX 2
93 #define DISPLAY_AREA_PADDINGY 2
94 #define CONTROL_BORDER_SIZEX 2
95 #define CONTROL_BORDER_SIZEY 2
96 #define BUTTON_SPACINGX 3
97 #define BUTTON_SPACINGY 4
98 #define FLAT_BTN_SPACINGX 8
99 #define DEFAULT_TAB_WIDTH 96
101 #define TAB_GetInfoPtr(hwnd) ((TAB_INFO *)GetWindowLongA(hwnd,0))
103 /******************************************************************************
104 * Hot-tracking timer constants
106 #define TAB_HOTTRACK_TIMER 1
107 #define TAB_HOTTRACK_TIMER_INTERVAL 100 /* milliseconds */
109 /******************************************************************************
112 static void TAB_Refresh (HWND hwnd
, HDC hdc
);
113 static void TAB_InvalidateTabArea(HWND hwnd
, TAB_INFO
* infoPtr
);
114 static void TAB_EnsureSelectionVisible(HWND hwnd
, TAB_INFO
* infoPtr
);
115 static void TAB_DrawItem(HWND hwnd
, HDC hdc
, INT iItem
);
116 static void TAB_DrawItemInterior(HWND hwnd
, HDC hdc
, INT iItem
, RECT
* drawRect
);
119 TAB_SendSimpleNotify (HWND hwnd
, UINT code
)
123 nmhdr
.hwndFrom
= hwnd
;
124 nmhdr
.idFrom
= GetWindowLongA(hwnd
, GWL_ID
);
127 return (BOOL
) SendMessageA (GetParent (hwnd
), WM_NOTIFY
,
128 (WPARAM
) nmhdr
.idFrom
, (LPARAM
) &nmhdr
);
132 TAB_RelayEvent (HWND hwndTip
, HWND hwndMsg
, UINT uMsg
,
133 WPARAM wParam
, LPARAM lParam
)
141 msg
.time
= GetMessageTime ();
142 msg
.pt
.x
= LOWORD(GetMessagePos ());
143 msg
.pt
.y
= HIWORD(GetMessagePos ());
145 SendMessageA (hwndTip
, TTM_RELAYEVENT
, 0, (LPARAM
)&msg
);
149 TAB_DumpItemExternalA(TCITEMA
*pti
, UINT iItem
)
152 TRACE("external tab %d, mask=0x%08x, dwState=0x%08x, dwStateMask=0x%08x, cchTextMax=0x%08x\n",
153 iItem
, pti
->mask
, pti
->dwState
, pti
->dwStateMask
, pti
->cchTextMax
);
154 TRACE("external tab %d, iImage=%d, lParam=0x%08lx, pszTextA=%s\n",
155 iItem
, pti
->iImage
, pti
->lParam
, debugstr_a(pti
->pszText
));
161 TAB_DumpItemExternalW(TCITEMW
*pti
, UINT iItem
)
164 TRACE("external tab %d, mask=0x%08x, dwState=0x%08lx, dwStateMask=0x%08lx, cchTextMax=0x%08x\n",
165 iItem
, pti
->mask
, pti
->dwState
, pti
->dwStateMask
, pti
->cchTextMax
);
166 TRACE("external tab %d, iImage=%d, lParam=0x%08lx, pszTextW=%s\n",
167 iItem
, pti
->iImage
, pti
->lParam
, debugstr_w(pti
->pszText
));
172 TAB_DumpItemInternal(TAB_INFO
*infoPtr
, UINT iItem
)
177 ti
= &infoPtr
->items
[iItem
];
178 TRACE("tab %d, mask=0x%08x, dwState=0x%08lx, pszText=%s, iImage=%d\n",
179 iItem
, ti
->mask
, ti
->dwState
, debugstr_w(ti
->pszText
),
181 TRACE("tab %d, lParam=0x%08lx, rect.left=%ld, rect.top(row)=%ld\n",
182 iItem
, ti
->lParam
, ti
->rect
.left
, ti
->rect
.top
);
187 TAB_GetCurSel (HWND hwnd
)
189 TAB_INFO
*infoPtr
= TAB_GetInfoPtr(hwnd
);
191 return infoPtr
->iSelected
;
195 TAB_GetCurFocus (HWND hwnd
)
197 TAB_INFO
*infoPtr
= TAB_GetInfoPtr(hwnd
);
199 return infoPtr
->uFocus
;
203 TAB_GetToolTips (HWND hwnd
, WPARAM wParam
, LPARAM lParam
)
205 TAB_INFO
*infoPtr
= TAB_GetInfoPtr(hwnd
);
207 if (infoPtr
== NULL
) return 0;
208 return (LRESULT
)infoPtr
->hwndToolTip
;
212 TAB_SetCurSel (HWND hwnd
,WPARAM wParam
)
214 TAB_INFO
*infoPtr
= TAB_GetInfoPtr(hwnd
);
215 INT iItem
= (INT
)wParam
;
219 if ((iItem
>= 0) && (iItem
< infoPtr
->uNumItem
)) {
220 prevItem
=infoPtr
->iSelected
;
221 infoPtr
->iSelected
=iItem
;
222 TAB_EnsureSelectionVisible(hwnd
, infoPtr
);
223 TAB_InvalidateTabArea(hwnd
, infoPtr
);
229 TAB_SetCurFocus (HWND hwnd
,WPARAM wParam
)
231 TAB_INFO
*infoPtr
= TAB_GetInfoPtr(hwnd
);
232 INT iItem
=(INT
) wParam
;
234 if ((iItem
< 0) || (iItem
>= infoPtr
->uNumItem
)) return 0;
236 if (GetWindowLongA(hwnd
, GWL_STYLE
) & TCS_BUTTONS
) {
237 FIXME("Should set input focus\n");
239 int oldFocus
= infoPtr
->uFocus
;
240 if (infoPtr
->iSelected
!= iItem
|| infoPtr
->uFocus
== -1 ) {
241 infoPtr
->uFocus
= iItem
;
242 if (oldFocus
!= -1) {
243 if (TAB_SendSimpleNotify(hwnd
, TCN_SELCHANGING
)!=TRUE
) {
244 infoPtr
->iSelected
= iItem
;
245 TAB_SendSimpleNotify(hwnd
, TCN_SELCHANGE
);
248 infoPtr
->iSelected
= iItem
;
249 TAB_EnsureSelectionVisible(hwnd
, infoPtr
);
250 TAB_InvalidateTabArea(hwnd
, infoPtr
);
258 TAB_SetToolTips (HWND hwnd
, WPARAM wParam
, LPARAM lParam
)
260 TAB_INFO
*infoPtr
= TAB_GetInfoPtr(hwnd
);
262 if (infoPtr
== NULL
) return 0;
263 infoPtr
->hwndToolTip
= (HWND
)wParam
;
268 TAB_SetPadding (HWND hwnd
, WPARAM wParam
, LPARAM lParam
)
270 TAB_INFO
*infoPtr
= TAB_GetInfoPtr(hwnd
);
272 if (infoPtr
== NULL
) return 0;
273 infoPtr
->uHItemPadding
=LOWORD(lParam
);
274 infoPtr
->uVItemPadding
=HIWORD(lParam
);
278 /******************************************************************************
279 * TAB_InternalGetItemRect
281 * This method will calculate the rectangle representing a given tab item in
282 * client coordinates. This method takes scrolling into account.
284 * This method returns TRUE if the item is visible in the window and FALSE
285 * if it is completely outside the client area.
287 static BOOL
TAB_InternalGetItemRect(
294 RECT tmpItemRect
,clientRect
;
295 LONG lStyle
= GetWindowLongA(hwnd
, GWL_STYLE
);
297 /* Perform a sanity check and a trivial visibility check. */
298 if ( (infoPtr
->uNumItem
<= 0) ||
299 (itemIndex
>= infoPtr
->uNumItem
) ||
300 (!((lStyle
& TCS_MULTILINE
) || (lStyle
& TCS_VERTICAL
)) && (itemIndex
< infoPtr
->leftmostVisible
)) )
304 * Avoid special cases in this procedure by assigning the "out"
305 * parameters if the caller didn't supply them
307 if (itemRect
== NULL
)
308 itemRect
= &tmpItemRect
;
310 /* Retrieve the unmodified item rect. */
311 *itemRect
= infoPtr
->items
[itemIndex
].rect
;
313 /* calculate the times bottom and top based on the row */
314 GetClientRect(hwnd
, &clientRect
);
316 if ((lStyle
& TCS_BOTTOM
) && !(lStyle
& TCS_VERTICAL
))
318 itemRect
->bottom
= clientRect
.bottom
-
319 SELECTED_TAB_OFFSET
-
320 itemRect
->top
* (infoPtr
->tabHeight
- 2) -
321 ((lStyle
& TCS_BUTTONS
) ? itemRect
->top
* BUTTON_SPACINGY
: 0);
323 itemRect
->top
= clientRect
.bottom
-
325 itemRect
->top
* (infoPtr
->tabHeight
- 2) -
326 ((lStyle
& TCS_BUTTONS
) ? itemRect
->top
* BUTTON_SPACINGY
: 0);
328 else if((lStyle
& TCS_BOTTOM
) && (lStyle
& TCS_VERTICAL
))
330 itemRect
->right
= clientRect
.right
- SELECTED_TAB_OFFSET
- itemRect
->left
* (infoPtr
->tabHeight
- 2) -
331 ((lStyle
& TCS_BUTTONS
) ? itemRect
->left
* BUTTON_SPACINGY
: 0);
332 itemRect
->left
= clientRect
.right
- infoPtr
->tabHeight
- itemRect
->left
* (infoPtr
->tabHeight
- 2) -
333 ((lStyle
& TCS_BUTTONS
) ? itemRect
->left
* BUTTON_SPACINGY
: 0);
335 else if((lStyle
& TCS_VERTICAL
) && !(lStyle
& TCS_BOTTOM
))
337 itemRect
->right
= clientRect
.left
+ infoPtr
->tabHeight
+ itemRect
->left
* (infoPtr
->tabHeight
- 2) +
338 ((lStyle
& TCS_BUTTONS
) ? itemRect
->left
* BUTTON_SPACINGY
: 0);
339 itemRect
->left
= clientRect
.left
+ SELECTED_TAB_OFFSET
+ itemRect
->left
* (infoPtr
->tabHeight
- 2) +
340 ((lStyle
& TCS_BUTTONS
) ? itemRect
->left
* BUTTON_SPACINGY
: 0);
342 else if(!(lStyle
& TCS_VERTICAL
) && !(lStyle
& TCS_BOTTOM
)) /* not TCS_BOTTOM and not TCS_VERTICAL */
344 itemRect
->bottom
= clientRect
.top
+
346 itemRect
->top
* (infoPtr
->tabHeight
- 2) +
347 ((lStyle
& TCS_BUTTONS
) ? itemRect
->top
* BUTTON_SPACINGY
: SELECTED_TAB_OFFSET
);
348 itemRect
->top
= clientRect
.top
+
349 itemRect
->top
* (infoPtr
->tabHeight
- 2) +
350 ((lStyle
& TCS_BUTTONS
) ? itemRect
->top
* BUTTON_SPACINGY
: SELECTED_TAB_OFFSET
);
354 * "scroll" it to make sure the item at the very left of the
355 * tab control is the leftmost visible tab.
357 if(lStyle
& TCS_VERTICAL
)
361 -(clientRect
.bottom
- infoPtr
->items
[infoPtr
->leftmostVisible
].rect
.bottom
));
364 * Move the rectangle so the first item is slightly offset from
365 * the bottom of the tab control.
369 -SELECTED_TAB_OFFSET
);
374 -infoPtr
->items
[infoPtr
->leftmostVisible
].rect
.left
,
378 * Move the rectangle so the first item is slightly offset from
379 * the left of the tab control.
385 TRACE("item %d tab h=%d, rect=(%ld,%ld)-(%ld,%ld)\n",
386 itemIndex
, infoPtr
->tabHeight
,
387 itemRect
->left
, itemRect
->top
, itemRect
->right
, itemRect
->bottom
);
389 /* Now, calculate the position of the item as if it were selected. */
390 if (selectedRect
!=NULL
)
392 CopyRect(selectedRect
, itemRect
);
394 /* The rectangle of a selected item is a bit wider. */
395 if(lStyle
& TCS_VERTICAL
)
396 InflateRect(selectedRect
, 0, SELECTED_TAB_OFFSET
);
398 InflateRect(selectedRect
, SELECTED_TAB_OFFSET
, 0);
400 /* If it also a bit higher. */
401 if ((lStyle
& TCS_BOTTOM
) && !(lStyle
& TCS_VERTICAL
))
403 selectedRect
->top
-= 2; /* the border is thicker on the bottom */
404 selectedRect
->bottom
+= SELECTED_TAB_OFFSET
;
406 else if((lStyle
& TCS_BOTTOM
) && (lStyle
& TCS_VERTICAL
))
408 selectedRect
->left
-= 2; /* the border is thicker on the right */
409 selectedRect
->right
+= SELECTED_TAB_OFFSET
;
411 else if(lStyle
& TCS_VERTICAL
)
413 selectedRect
->left
-= SELECTED_TAB_OFFSET
;
414 selectedRect
->right
+= 1;
418 selectedRect
->top
-= SELECTED_TAB_OFFSET
;
419 selectedRect
->bottom
+= 1;
426 static BOOL
TAB_GetItemRect(HWND hwnd
, WPARAM wParam
, LPARAM lParam
)
428 return TAB_InternalGetItemRect(hwnd
, TAB_GetInfoPtr(hwnd
), (INT
)wParam
,
429 (LPRECT
)lParam
, (LPRECT
)NULL
);
432 /******************************************************************************
435 * This method is called to handle keyboard input
437 static LRESULT
TAB_KeyUp(
441 TAB_INFO
* infoPtr
= TAB_GetInfoPtr(hwnd
);
447 newItem
= infoPtr
->uFocus
- 1;
450 newItem
= infoPtr
->uFocus
+ 1;
455 * If we changed to a valid item, change the selection
457 if ((newItem
>= 0) &&
458 (newItem
< infoPtr
->uNumItem
) &&
459 (infoPtr
->uFocus
!= newItem
))
461 if (!TAB_SendSimpleNotify(hwnd
, TCN_SELCHANGING
))
463 infoPtr
->iSelected
= newItem
;
464 infoPtr
->uFocus
= newItem
;
465 TAB_SendSimpleNotify(hwnd
, TCN_SELCHANGE
);
467 TAB_EnsureSelectionVisible(hwnd
, infoPtr
);
468 TAB_InvalidateTabArea(hwnd
, infoPtr
);
475 /******************************************************************************
478 * This method is called whenever the focus goes in or out of this control
479 * it is used to update the visual state of the control.
481 static LRESULT
TAB_FocusChanging(
487 TAB_INFO
*infoPtr
= TAB_GetInfoPtr(hwnd
);
492 * Get the rectangle for the item.
494 isVisible
= TAB_InternalGetItemRect(hwnd
,
501 * If the rectangle is not completely invisible, invalidate that
502 * portion of the window.
506 TRACE("invalidate (%ld,%ld)-(%ld,%ld)\n",
507 selectedRect
.left
,selectedRect
.top
,
508 selectedRect
.right
,selectedRect
.bottom
);
509 InvalidateRect(hwnd
, &selectedRect
, TRUE
);
513 * Don't otherwise disturb normal behavior.
515 return DefWindowProcA (hwnd
, uMsg
, wParam
, lParam
);
518 static INT
TAB_InternalHitTest (
528 for (iCount
= 0; iCount
< infoPtr
->uNumItem
; iCount
++)
530 TAB_InternalGetItemRect(hwnd
, infoPtr
, iCount
, &rect
, NULL
);
532 if (PtInRect(&rect
, pt
))
534 *flags
= TCHT_ONITEM
;
539 *flags
= TCHT_NOWHERE
;
544 TAB_HitTest (HWND hwnd
, WPARAM wParam
, LPARAM lParam
)
546 TAB_INFO
*infoPtr
= TAB_GetInfoPtr(hwnd
);
547 LPTCHITTESTINFO lptest
= (LPTCHITTESTINFO
) lParam
;
549 return TAB_InternalHitTest (hwnd
, infoPtr
, lptest
->pt
, &lptest
->flags
);
552 /******************************************************************************
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
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 ?
565 TAB_NCHitTest (HWND hwnd
, LPARAM lParam
)
567 TAB_INFO
*infoPtr
= TAB_GetInfoPtr(hwnd
);
571 pt
.x
= LOWORD(lParam
);
572 pt
.y
= HIWORD(lParam
);
573 ScreenToClient(hwnd
, &pt
);
575 if (TAB_InternalHitTest(hwnd
, infoPtr
, pt
, &dummyflag
) == -1)
576 return HTTRANSPARENT
;
582 TAB_LButtonDown (HWND hwnd
, WPARAM wParam
, LPARAM lParam
)
584 TAB_INFO
*infoPtr
= TAB_GetInfoPtr(hwnd
);
588 if (infoPtr
->hwndToolTip
)
589 TAB_RelayEvent (infoPtr
->hwndToolTip
, hwnd
,
590 WM_LBUTTONDOWN
, wParam
, lParam
);
592 if (GetWindowLongA(hwnd
, GWL_STYLE
) & TCS_FOCUSONBUTTONDOWN
) {
596 if (infoPtr
->hwndToolTip
)
597 TAB_RelayEvent (infoPtr
->hwndToolTip
, hwnd
,
598 WM_LBUTTONDOWN
, wParam
, lParam
);
600 pt
.x
= (INT
)LOWORD(lParam
);
601 pt
.y
= (INT
)HIWORD(lParam
);
603 newItem
= TAB_InternalHitTest (hwnd
, infoPtr
, pt
, &dummy
);
605 TRACE("On Tab, item %d\n", newItem
);
607 if ((newItem
!= -1) && (infoPtr
->iSelected
!= newItem
))
609 if (TAB_SendSimpleNotify(hwnd
, TCN_SELCHANGING
) != TRUE
)
611 infoPtr
->iSelected
= newItem
;
612 infoPtr
->uFocus
= newItem
;
613 TAB_SendSimpleNotify(hwnd
, TCN_SELCHANGE
);
615 TAB_EnsureSelectionVisible(hwnd
, infoPtr
);
617 TAB_InvalidateTabArea(hwnd
, infoPtr
);
624 TAB_LButtonUp (HWND hwnd
, WPARAM wParam
, LPARAM lParam
)
626 TAB_SendSimpleNotify(hwnd
, NM_CLICK
);
632 TAB_RButtonDown (HWND hwnd
, WPARAM wParam
, LPARAM lParam
)
634 TAB_SendSimpleNotify(hwnd
, NM_RCLICK
);
638 /******************************************************************************
639 * TAB_DrawLoneItemInterior
641 * This calls TAB_DrawItemInterior. However, TAB_DrawItemInterior is normally
642 * called by TAB_DrawItem which is normally called by TAB_Refresh which sets
643 * up the device context and font. This routine does the same setup but
644 * only calls TAB_DrawItemInterior for the single specified item.
647 TAB_DrawLoneItemInterior(HWND hwnd
, TAB_INFO
* infoPtr
, int iItem
)
649 HDC hdc
= GetDC(hwnd
);
650 HFONT hOldFont
= SelectObject(hdc
, infoPtr
->hFont
);
651 TAB_DrawItemInterior(hwnd
, hdc
, iItem
, NULL
);
652 SelectObject(hdc
, hOldFont
);
653 ReleaseDC(hwnd
, hdc
);
656 /******************************************************************************
657 * TAB_HotTrackTimerProc
659 * When a mouse-move event causes a tab to be highlighted (hot-tracking), a
660 * timer is setup so we can check if the mouse is moved out of our window.
661 * (We don't get an event when the mouse leaves, the mouse-move events just
662 * stop being delivered to our window and just start being delivered to
663 * another window.) This function is called when the timer triggers so
664 * we can check if the mouse has left our window. If so, we un-highlight
665 * the hot-tracked tab.
668 TAB_HotTrackTimerProc
670 HWND hwnd
, /* handle of window for timer messages */
671 UINT uMsg
, /* WM_TIMER message */
672 UINT idEvent
, /* timer identifier */
673 DWORD dwTime
/* current system time */
676 TAB_INFO
* infoPtr
= TAB_GetInfoPtr(hwnd
);
678 if (infoPtr
!= NULL
&& infoPtr
->iHotTracked
>= 0)
683 ** If we can't get the cursor position, or if the cursor is outside our
684 ** window, we un-highlight the hot-tracked tab. Note that the cursor is
685 ** "outside" even if it is within our bounding rect if another window
686 ** overlaps. Note also that the case where the cursor stayed within our
687 ** window but has moved off the hot-tracked tab will be handled by the
688 ** WM_MOUSEMOVE event.
690 if (!GetCursorPos(&pt
) || WindowFromPoint(pt
) != hwnd
)
692 /* Redraw iHotTracked to look normal */
693 INT iRedraw
= infoPtr
->iHotTracked
;
694 infoPtr
->iHotTracked
= -1;
695 TAB_DrawLoneItemInterior(hwnd
, infoPtr
, iRedraw
);
697 /* Kill this timer */
698 KillTimer(hwnd
, TAB_HOTTRACK_TIMER
);
703 /******************************************************************************
706 * If a tab control has the TCS_HOTTRACK style, then the tab under the mouse
707 * should be highlighted. This function determines which tab in a tab control,
708 * if any, is under the mouse and records that information. The caller may
709 * supply output parameters to receive the item number of the tab item which
710 * was highlighted but isn't any longer and of the tab item which is now
711 * highlighted but wasn't previously. The caller can use this information to
712 * selectively redraw those tab items.
714 * If the caller has a mouse position, it can supply it through the pos
715 * parameter. For example, TAB_MouseMove does this. Otherwise, the caller
716 * supplies NULL and this function determines the current mouse position
724 int* out_redrawLeave
,
728 TAB_INFO
* infoPtr
= TAB_GetInfoPtr(hwnd
);
733 if (out_redrawLeave
!= NULL
)
734 *out_redrawLeave
= -1;
735 if (out_redrawEnter
!= NULL
)
736 *out_redrawEnter
= -1;
738 if (GetWindowLongA(hwnd
, GWL_STYLE
) & TCS_HOTTRACK
)
746 ScreenToClient(hwnd
, &pt
);
754 item
= TAB_InternalHitTest(hwnd
, infoPtr
, pt
, &flags
);
757 if (item
!= infoPtr
->iHotTracked
)
759 if (infoPtr
->iHotTracked
>= 0)
761 /* Mark currently hot-tracked to be redrawn to look normal */
762 if (out_redrawLeave
!= NULL
)
763 *out_redrawLeave
= infoPtr
->iHotTracked
;
767 /* Kill timer which forces recheck of mouse pos */
768 KillTimer(hwnd
, TAB_HOTTRACK_TIMER
);
773 /* Start timer so we recheck mouse pos */
774 UINT timerID
= SetTimer
778 TAB_HOTTRACK_TIMER_INTERVAL
,
779 TAB_HotTrackTimerProc
783 return; /* Hot tracking not available */
786 infoPtr
->iHotTracked
= item
;
790 /* Mark new hot-tracked to be redrawn to look highlighted */
791 if (out_redrawEnter
!= NULL
)
792 *out_redrawEnter
= item
;
797 /******************************************************************************
800 * Handles the mouse-move event. Updates tooltips. Updates hot-tracking.
803 TAB_MouseMove (HWND hwnd
, WPARAM wParam
, LPARAM lParam
)
808 TAB_INFO
*infoPtr
= TAB_GetInfoPtr(hwnd
);
810 if (infoPtr
->hwndToolTip
)
811 TAB_RelayEvent (infoPtr
->hwndToolTip
, hwnd
,
812 WM_LBUTTONDOWN
, wParam
, lParam
);
814 /* Determine which tab to highlight. Redraw tabs which change highlight
816 TAB_RecalcHotTrack(hwnd
, &lParam
, &redrawLeave
, &redrawEnter
);
818 if (redrawLeave
!= -1)
819 TAB_DrawLoneItemInterior(hwnd
, infoPtr
, redrawLeave
);
820 if (redrawEnter
!= -1)
821 TAB_DrawLoneItemInterior(hwnd
, infoPtr
, redrawEnter
);
826 /******************************************************************************
829 * Calculates the tab control's display area given the window rectangle or
830 * the window rectangle given the requested display rectangle.
832 static LRESULT
TAB_AdjustRect(
837 TAB_INFO
*infoPtr
= TAB_GetInfoPtr(hwnd
);
838 DWORD lStyle
= GetWindowLongA(hwnd
, GWL_STYLE
);
840 if(lStyle
& TCS_VERTICAL
)
842 if (fLarger
) /* Go from display rectangle */
844 /* Add the height of the tabs. */
845 if (lStyle
& TCS_BOTTOM
)
846 prc
->right
+= (infoPtr
->tabHeight
- 2) * infoPtr
->uNumRows
+ 2;
848 prc
->left
-= (infoPtr
->tabHeight
- 2) * infoPtr
->uNumRows
+ 2;
850 /* FIXME: not sure if these InflateRect's need to have different values for TCS_VERTICAL */
851 /* Inflate the rectangle for the padding */
852 InflateRect(prc
, DISPLAY_AREA_PADDINGX
, DISPLAY_AREA_PADDINGY
);
854 /* Inflate for the border */
855 InflateRect(prc
, CONTROL_BORDER_SIZEX
, CONTROL_BORDER_SIZEX
);
857 else /* Go from window rectangle. */
859 /* FIXME: not sure if these InflateRect's need to have different values for TCS_VERTICAL */
860 /* Deflate the rectangle for the border */
861 InflateRect(prc
, -CONTROL_BORDER_SIZEX
, -CONTROL_BORDER_SIZEX
);
863 /* Deflate the rectangle for the padding */
864 InflateRect(prc
, -DISPLAY_AREA_PADDINGX
, -DISPLAY_AREA_PADDINGY
);
866 /* Remove the height of the tabs. */
867 if (lStyle
& TCS_BOTTOM
)
868 prc
->right
-= (infoPtr
->tabHeight
- 2) * infoPtr
->uNumRows
+ 2;
870 prc
->left
+= (infoPtr
->tabHeight
- 2) * infoPtr
->uNumRows
+ 2;
874 if (fLarger
) /* Go from display rectangle */
876 /* Add the height of the tabs. */
877 if (lStyle
& TCS_BOTTOM
)
878 prc
->bottom
+= (infoPtr
->tabHeight
- 2) * infoPtr
->uNumRows
+ 2;
880 prc
->top
-= (infoPtr
->tabHeight
- 2) * infoPtr
->uNumRows
+ 2;
882 /* Inflate the rectangle for the padding */
883 InflateRect(prc
, DISPLAY_AREA_PADDINGX
, DISPLAY_AREA_PADDINGY
);
885 /* Inflate for the border */
886 InflateRect(prc
, CONTROL_BORDER_SIZEX
, CONTROL_BORDER_SIZEX
);
888 else /* Go from window rectangle. */
890 /* Deflate the rectangle for the border */
891 InflateRect(prc
, -CONTROL_BORDER_SIZEX
, -CONTROL_BORDER_SIZEX
);
893 /* Deflate the rectangle for the padding */
894 InflateRect(prc
, -DISPLAY_AREA_PADDINGX
, -DISPLAY_AREA_PADDINGY
);
896 /* Remove the height of the tabs. */
897 if (lStyle
& TCS_BOTTOM
)
898 prc
->bottom
-= (infoPtr
->tabHeight
- 2) * infoPtr
->uNumRows
+ 2;
900 prc
->top
+= (infoPtr
->tabHeight
- 2) * infoPtr
->uNumRows
+ 2;
907 /******************************************************************************
910 * This method will handle the notification from the scroll control and
911 * perform the scrolling operation on the tab control.
913 static LRESULT
TAB_OnHScroll(
919 TAB_INFO
*infoPtr
= TAB_GetInfoPtr(hwnd
);
921 if(nScrollCode
== SB_THUMBPOSITION
&& nPos
!= infoPtr
->leftmostVisible
)
923 if(nPos
< infoPtr
->leftmostVisible
)
924 infoPtr
->leftmostVisible
--;
926 infoPtr
->leftmostVisible
++;
928 TAB_RecalcHotTrack(hwnd
, NULL
, NULL
, NULL
);
929 TAB_InvalidateTabArea(hwnd
, infoPtr
);
930 SendMessageA(infoPtr
->hwndUpDown
, UDM_SETPOS
, 0,
931 MAKELONG(infoPtr
->leftmostVisible
, 0));
937 /******************************************************************************
940 * This method will check the current scrolling state and make sure the
941 * scrolling control is displayed (or not).
943 static void TAB_SetupScrolling(
946 const RECT
* clientRect
)
949 DWORD lStyle
= GetWindowLongA(hwnd
, GWL_STYLE
);
951 if (infoPtr
->needsScrolling
)
957 * Calculate the position of the scroll control.
959 if(lStyle
& TCS_VERTICAL
)
961 controlPos
.right
= clientRect
->right
;
962 controlPos
.left
= controlPos
.right
- 2 * GetSystemMetrics(SM_CXHSCROLL
);
964 if (lStyle
& TCS_BOTTOM
)
966 controlPos
.top
= clientRect
->bottom
- infoPtr
->tabHeight
;
967 controlPos
.bottom
= controlPos
.top
+ GetSystemMetrics(SM_CYHSCROLL
);
971 controlPos
.bottom
= clientRect
->top
+ infoPtr
->tabHeight
;
972 controlPos
.top
= controlPos
.bottom
- GetSystemMetrics(SM_CYHSCROLL
);
977 controlPos
.right
= clientRect
->right
;
978 controlPos
.left
= controlPos
.right
- 2 * GetSystemMetrics(SM_CXHSCROLL
);
980 if (lStyle
& TCS_BOTTOM
)
982 controlPos
.top
= clientRect
->bottom
- infoPtr
->tabHeight
;
983 controlPos
.bottom
= controlPos
.top
+ GetSystemMetrics(SM_CYHSCROLL
);
987 controlPos
.bottom
= clientRect
->top
+ infoPtr
->tabHeight
;
988 controlPos
.top
= controlPos
.bottom
- GetSystemMetrics(SM_CYHSCROLL
);
993 * If we don't have a scroll control yet, we want to create one.
994 * If we have one, we want to make sure it's positioned properly.
996 if (infoPtr
->hwndUpDown
==0)
998 infoPtr
->hwndUpDown
= CreateWindowA("msctls_updown32",
1000 WS_VISIBLE
| WS_CHILD
| UDS_HORZ
,
1001 controlPos
.left
, controlPos
.top
,
1002 controlPos
.right
- controlPos
.left
,
1003 controlPos
.bottom
- controlPos
.top
,
1011 SetWindowPos(infoPtr
->hwndUpDown
,
1013 controlPos
.left
, controlPos
.top
,
1014 controlPos
.right
- controlPos
.left
,
1015 controlPos
.bottom
- controlPos
.top
,
1016 SWP_SHOWWINDOW
| SWP_NOZORDER
);
1019 /* Now calculate upper limit of the updown control range.
1020 * We do this by calculating how many tabs will be offscreen when the
1021 * last tab is visible.
1023 if(infoPtr
->uNumItem
)
1025 vsize
= clientRect
->right
- (controlPos
.right
- controlPos
.left
+ 1);
1026 maxRange
= infoPtr
->uNumItem
;
1027 tabwidth
= infoPtr
->items
[maxRange
- 1].rect
.right
;
1029 for(; maxRange
> 0; maxRange
--)
1031 if(tabwidth
- infoPtr
->items
[maxRange
- 1].rect
.left
> vsize
)
1035 if(maxRange
== infoPtr
->uNumItem
)
1041 /* If we once had a scroll control... hide it */
1042 if (infoPtr
->hwndUpDown
!=0)
1043 ShowWindow(infoPtr
->hwndUpDown
, SW_HIDE
);
1045 if (infoPtr
->hwndUpDown
)
1046 SendMessageA(infoPtr
->hwndUpDown
, UDM_SETRANGE32
, 0, maxRange
);
1049 /******************************************************************************
1052 * This method will calculate the position rectangles of all the items in the
1053 * control. The rectangle calculated starts at 0 for the first item in the
1054 * list and ignores scrolling and selection.
1055 * It also uses the current font to determine the height of the tab row and
1056 * it checks if all the tabs fit in the client area of the window. If they
1057 * dont, a scrolling control is added.
1059 static void TAB_SetItemBounds (HWND hwnd
)
1061 TAB_INFO
* infoPtr
= TAB_GetInfoPtr(hwnd
);
1062 LONG lStyle
= GetWindowLongA(hwnd
, GWL_STYLE
);
1063 TEXTMETRICA fontMetrics
;
1066 INT curItemRowCount
;
1067 HFONT hFont
, hOldFont
;
1077 * We need to get text information so we need a DC and we need to select
1082 hFont
= infoPtr
->hFont
? infoPtr
->hFont
: GetStockObject (SYSTEM_FONT
);
1083 hOldFont
= SelectObject (hdc
, hFont
);
1086 * We will base the rectangle calculations on the client rectangle
1089 GetClientRect(hwnd
, &clientRect
);
1091 /* if TCS_VERTICAL then swap the height and width so this code places the
1092 tabs along the top of the rectangle and we can just rotate them after
1093 rather than duplicate all of the below code */
1094 if(lStyle
& TCS_VERTICAL
)
1096 iTemp
= clientRect
.bottom
;
1097 clientRect
.bottom
= clientRect
.right
;
1098 clientRect
.right
= iTemp
;
1101 /* The leftmost item will be "0" aligned */
1103 curItemRowCount
= infoPtr
->uNumItem
? 1 : 0;
1105 if (!(infoPtr
->fHeightSet
))
1108 int icon_height
= 0;
1110 /* Use the current font to determine the height of a tab. */
1111 GetTextMetricsA(hdc
, &fontMetrics
);
1113 /* Get the icon height */
1115 ImageList_GetIconSize(infoPtr
->himl
, 0, &icon_height
);
1117 /* Take the highest between font or icon */
1118 if (fontMetrics
.tmHeight
> icon_height
)
1119 item_height
= fontMetrics
.tmHeight
+ 2;
1121 item_height
= icon_height
;
1124 * Make sure there is enough space for the letters + icon + growing the
1125 * selected item + extra space for the selected item.
1127 infoPtr
->tabHeight
= item_height
+ SELECTED_TAB_OFFSET
+
1128 ((lStyle
& TCS_BUTTONS
) ? 2 : 1) *
1129 infoPtr
->uVItemPadding
;
1131 TRACE("tabH=%d, tmH=%ld, iconh=%d\n",
1132 infoPtr
->tabHeight
, fontMetrics
.tmHeight
, icon_height
);
1135 TRACE("client right=%ld\n", clientRect
.right
);
1137 /* Get the icon width */
1140 ImageList_GetIconSize(infoPtr
->himl
, &icon_width
, 0);
1142 if (lStyle
& TCS_FIXEDWIDTH
)
1145 /* Add padding if icon is present */
1146 icon_width
+= infoPtr
->uHItemPadding
;
1149 for (curItem
= 0; curItem
< infoPtr
->uNumItem
; curItem
++)
1151 /* Set the leftmost position of the tab. */
1152 infoPtr
->items
[curItem
].rect
.left
= curItemLeftPos
;
1154 if (lStyle
& TCS_FIXEDWIDTH
)
1156 infoPtr
->items
[curItem
].rect
.right
= infoPtr
->items
[curItem
].rect
.left
+
1157 max(infoPtr
->tabWidth
, icon_width
);
1163 /* Calculate how wide the tab is depending on the text it contains */
1164 GetTextExtentPoint32W(hdc
, infoPtr
->items
[curItem
].pszText
,
1165 lstrlenW(infoPtr
->items
[curItem
].pszText
), &size
);
1167 infoPtr
->items
[curItem
].rect
.right
= infoPtr
->items
[curItem
].rect
.left
+
1168 size
.cx
+ icon_width
+
1169 num
* infoPtr
->uHItemPadding
;
1170 TRACE("for <%s>, l,r=%ld,%ld, num=%d\n",
1171 debugstr_w(infoPtr
->items
[curItem
].pszText
),
1172 infoPtr
->items
[curItem
].rect
.left
,
1173 infoPtr
->items
[curItem
].rect
.right
,
1178 * Check if this is a multiline tab control and if so
1179 * check to see if we should wrap the tabs
1181 * Because we are going to arange all these tabs evenly
1182 * really we are basically just counting rows at this point
1186 if (((lStyle
& TCS_MULTILINE
) || (lStyle
& TCS_VERTICAL
)) &&
1187 (infoPtr
->items
[curItem
].rect
.right
> clientRect
.right
))
1189 infoPtr
->items
[curItem
].rect
.right
-=
1190 infoPtr
->items
[curItem
].rect
.left
;
1192 infoPtr
->items
[curItem
].rect
.left
= 0;
1194 TRACE("wrapping <%s>, l,r=%ld,%ld\n",
1195 debugstr_w(infoPtr
->items
[curItem
].pszText
),
1196 infoPtr
->items
[curItem
].rect
.left
,
1197 infoPtr
->items
[curItem
].rect
.right
);
1200 infoPtr
->items
[curItem
].rect
.bottom
= 0;
1201 infoPtr
->items
[curItem
].rect
.top
= curItemRowCount
- 1;
1203 TRACE("TextSize: %li\n", size
.cx
);
1204 TRACE("Rect: T %li, L %li, B %li, R %li\n",
1205 infoPtr
->items
[curItem
].rect
.top
,
1206 infoPtr
->items
[curItem
].rect
.left
,
1207 infoPtr
->items
[curItem
].rect
.bottom
,
1208 infoPtr
->items
[curItem
].rect
.right
);
1211 * The leftmost position of the next item is the rightmost position
1214 if (lStyle
& TCS_BUTTONS
)
1216 curItemLeftPos
= infoPtr
->items
[curItem
].rect
.right
+ BUTTON_SPACINGX
;
1217 if (lStyle
& TCS_FLATBUTTONS
)
1218 curItemLeftPos
+= FLAT_BTN_SPACINGX
;
1221 curItemLeftPos
= infoPtr
->items
[curItem
].rect
.right
;
1224 if (!((lStyle
& TCS_MULTILINE
) || (lStyle
& TCS_VERTICAL
)))
1227 * Check if we need a scrolling control.
1229 infoPtr
->needsScrolling
= (curItemLeftPos
+ (2 * SELECTED_TAB_OFFSET
) >
1232 /* Don't need scrolling, then update infoPtr->leftmostVisible */
1233 if(!infoPtr
->needsScrolling
)
1234 infoPtr
->leftmostVisible
= 0;
1236 TAB_SetupScrolling(hwnd
, infoPtr
, &clientRect
);
1239 /* Set the number of rows */
1240 infoPtr
->uNumRows
= curItemRowCount
;
1242 if (((lStyle
& TCS_MULTILINE
) || (lStyle
& TCS_VERTICAL
)) && (infoPtr
->uNumItem
> 0))
1244 INT widthDiff
, remainder
;
1245 INT tabPerRow
,remTab
;
1247 INT iIndexStart
=0,iIndexEnd
=0, iCount
=0;
1250 * Ok windows tries to even out the rows. place the same
1251 * number of tabs in each row. So lets give that a shot
1254 tabPerRow
= infoPtr
->uNumItem
/ (infoPtr
->uNumRows
);
1255 remTab
= infoPtr
->uNumItem
% (infoPtr
->uNumRows
);
1257 for (iItm
=0,iRow
=0,iCount
=0,curItemLeftPos
=0;
1258 iItm
<infoPtr
->uNumItem
;
1261 /* normalize the current rect */
1263 /* shift the item to the left side of the clientRect */
1264 infoPtr
->items
[iItm
].rect
.right
-=
1265 infoPtr
->items
[iItm
].rect
.left
;
1266 infoPtr
->items
[iItm
].rect
.left
= 0;
1268 TRACE("r=%ld, cl=%d, cl.r=%ld, iCount=%d, iRow=%d, uNumRows=%d, remTab=%d, tabPerRow=%d\n",
1269 infoPtr
->items
[iItm
].rect
.right
,
1270 curItemLeftPos
, clientRect
.right
,
1271 iCount
, iRow
, infoPtr
->uNumRows
, remTab
, tabPerRow
);
1273 /* if we have reached the maximum number of tabs on this row */
1274 /* move to the next row, reset our current item left position and */
1275 /* the count of items on this row */
1277 /* ************ FIXME FIXME FIXME *************** */
1281 /* if item n and n+1 are in the same row, */
1282 /* then the display has n+1 lower (toward the */
1283 /* bottom) than n. We do it just the */
1286 /* ************ FIXME FIXME FIXME *************** */
1288 if (lStyle
& TCS_VERTICAL
) {
1289 /* Vert: Add the remaining tabs in the *last* remainder rows */
1290 if (iCount
>= ((iRow
>=(INT
)infoPtr
->uNumRows
- remTab
)?tabPerRow
+ 1:tabPerRow
)) {
1296 /* Horz: Add the remaining tabs in the *first* remainder rows */
1297 if (iCount
>= ((iRow
<remTab
)?tabPerRow
+ 1:tabPerRow
)) {
1304 /* shift the item to the right to place it as the next item in this row */
1305 infoPtr
->items
[iItm
].rect
.left
+= curItemLeftPos
;
1306 infoPtr
->items
[iItm
].rect
.right
+= curItemLeftPos
;
1307 infoPtr
->items
[iItm
].rect
.top
= iRow
;
1308 if (lStyle
& TCS_BUTTONS
)
1310 curItemLeftPos
= infoPtr
->items
[iItm
].rect
.right
+ 1;
1311 if (lStyle
& TCS_FLATBUTTONS
)
1312 curItemLeftPos
+= FLAT_BTN_SPACINGX
;
1315 curItemLeftPos
= infoPtr
->items
[iItm
].rect
.right
;
1317 TRACE("arranging <%s>, l,r=%ld,%ld, row=%ld\n",
1318 debugstr_w(infoPtr
->items
[iItm
].pszText
),
1319 infoPtr
->items
[iItm
].rect
.left
,
1320 infoPtr
->items
[iItm
].rect
.right
,
1321 infoPtr
->items
[iItm
].rect
.top
);
1328 while(iIndexStart
< infoPtr
->uNumItem
)
1331 * find the indexs of the row
1333 /* find the first item on the next row */
1334 for (iIndexEnd
=iIndexStart
;
1335 (iIndexEnd
< infoPtr
->uNumItem
) &&
1336 (infoPtr
->items
[iIndexEnd
].rect
.top
==
1337 infoPtr
->items
[iIndexStart
].rect
.top
) ;
1339 /* intentionally blank */;
1342 * we need to justify these tabs so they fill the whole given
1346 /* find the amount of space remaining on this row */
1347 widthDiff
= clientRect
.right
- (2 * SELECTED_TAB_OFFSET
) -
1348 infoPtr
->items
[iIndexEnd
- 1].rect
.right
;
1350 /* iCount is the number of tab items on this row */
1351 iCount
= iIndexEnd
- iIndexStart
;
1356 remainder
= widthDiff
% iCount
;
1357 widthDiff
= widthDiff
/ iCount
;
1358 /* add widthDiff/iCount, or extra space/items on row, to each item on this row */
1359 for (iIndex
=iIndexStart
,iCount
=0; iIndex
< iIndexEnd
;
1362 infoPtr
->items
[iIndex
].rect
.left
+= iCount
* widthDiff
;
1363 infoPtr
->items
[iIndex
].rect
.right
+= (iCount
+ 1) * widthDiff
;
1365 TRACE("adjusting 1 <%s>, l,r=%ld,%ld\n",
1366 debugstr_w(infoPtr
->items
[iIndex
].pszText
),
1367 infoPtr
->items
[iIndex
].rect
.left
,
1368 infoPtr
->items
[iIndex
].rect
.right
);
1371 infoPtr
->items
[iIndex
- 1].rect
.right
+= remainder
;
1373 else /* we have only one item on this row, make it take up the entire row */
1375 infoPtr
->items
[iIndexStart
].rect
.left
= clientRect
.left
;
1376 infoPtr
->items
[iIndexStart
].rect
.right
= clientRect
.right
- 4;
1378 TRACE("adjusting 2 <%s>, l,r=%ld,%ld\n",
1379 debugstr_w(infoPtr
->items
[iIndexStart
].pszText
),
1380 infoPtr
->items
[iIndexStart
].rect
.left
,
1381 infoPtr
->items
[iIndexStart
].rect
.right
);
1386 iIndexStart
= iIndexEnd
;
1391 /* if TCS_VERTICAL rotate the tabs so they are along the side of the clientRect */
1392 if(lStyle
& TCS_VERTICAL
)
1395 for(iIndex
= 0; iIndex
< infoPtr
->uNumItem
; iIndex
++)
1397 rcItem
= &(infoPtr
->items
[iIndex
].rect
);
1399 rcOriginal
= *rcItem
;
1401 /* this is rotating the items by 90 degrees around the center of the control */
1402 rcItem
->top
= (clientRect
.right
- (rcOriginal
.left
- clientRect
.left
)) - (rcOriginal
.right
- rcOriginal
.left
);
1403 rcItem
->bottom
= rcItem
->top
+ (rcOriginal
.right
- rcOriginal
.left
);
1404 rcItem
->left
= rcOriginal
.top
;
1405 rcItem
->right
= rcOriginal
.bottom
;
1409 TAB_EnsureSelectionVisible(hwnd
,infoPtr
);
1410 TAB_RecalcHotTrack(hwnd
, NULL
, NULL
, NULL
);
1413 SelectObject (hdc
, hOldFont
);
1414 ReleaseDC (hwnd
, hdc
);
1417 /******************************************************************************
1418 * TAB_DrawItemInterior
1420 * This method is used to draw the interior (text and icon) of a single tab
1421 * into the tab control.
1424 TAB_DrawItemInterior
1432 TAB_INFO
* infoPtr
= TAB_GetInfoPtr(hwnd
);
1433 LONG lStyle
= GetWindowLongA(hwnd
, GWL_STYLE
);
1441 if (drawRect
== NULL
)
1448 * Get the rectangle for the item.
1450 isVisible
= TAB_InternalGetItemRect(hwnd
, infoPtr
, iItem
, &itemRect
, &selectedRect
);
1455 * Make sure drawRect points to something valid; simplifies code.
1457 drawRect
= &localRect
;
1460 * This logic copied from the part of TAB_DrawItem which draws
1461 * the tab background. It's important to keep it in sync. I
1462 * would have liked to avoid code duplication, but couldn't figure
1463 * out how without making spaghetti of TAB_DrawItem.
1465 if (lStyle
& TCS_BUTTONS
)
1467 *drawRect
= itemRect
;
1468 if (iItem
== infoPtr
->iSelected
)
1476 if (iItem
== infoPtr
->iSelected
)
1477 *drawRect
= selectedRect
;
1479 *drawRect
= itemRect
;
1488 htextPen
= CreatePen( PS_SOLID
, 1, GetSysColor(COLOR_BTNTEXT
) );
1489 holdPen
= SelectObject(hdc
, htextPen
);
1491 oldBkMode
= SetBkMode(hdc
, TRANSPARENT
);
1492 SetTextColor(hdc
, ( (iItem
== infoPtr
->iHotTracked
) | (infoPtr
->items
[iItem
].dwState
& TCIS_HIGHLIGHTED
)) ?
1493 comctl32_color
.clrHighlight
: comctl32_color
.clrBtnText
);
1497 * if owner draw, tell the owner to draw
1499 if ((lStyle
& TCS_OWNERDRAWFIXED
) && GetParent(hwnd
))
1505 * get the control id
1507 id
= GetWindowLongA( hwnd
, GWL_ID
);
1510 * put together the DRAWITEMSTRUCT
1512 dis
.CtlType
= ODT_TAB
;
1515 dis
.itemAction
= ODA_DRAWENTIRE
;
1517 if ( iItem
== infoPtr
->iSelected
)
1518 dis
.itemState
|= ODS_SELECTED
;
1519 if (infoPtr
->uFocus
== iItem
)
1520 dis
.itemState
|= ODS_FOCUS
;
1521 dis
.hwndItem
= hwnd
; /* */
1523 CopyRect(&dis
.rcItem
,drawRect
);
1524 dis
.itemData
= infoPtr
->items
[iItem
].lParam
;
1527 * send the draw message
1529 SendMessageA( GetParent(hwnd
), WM_DRAWITEM
, (WPARAM
)id
, (LPARAM
)&dis
);
1540 HFONT hOldFont
= 0; /* stop uninitialized warning */
1542 INT nEscapement
= 0; /* stop uninitialized warning */
1543 INT nOrientation
= 0; /* stop uninitialized warning */
1546 /* used to center the icon and text in the tab */
1548 INT center_offset_h
, center_offset_v
;
1551 * Deflate the rectangle to acount for the padding
1553 if(lStyle
& TCS_VERTICAL
)
1554 InflateRect(drawRect
, -infoPtr
->uVItemPadding
, -infoPtr
->uHItemPadding
);
1556 InflateRect(drawRect
, -infoPtr
->uHItemPadding
, -infoPtr
->uVItemPadding
);
1558 /* set rcImage to drawRect, we will use top & left in our ImageList_Draw call */
1559 rcImage
= *drawRect
;
1563 rcText
.left
= rcText
.top
= rcText
.right
= rcText
.bottom
= 0;
1566 * Setup for text output
1568 oldBkMode
= SetBkMode(hdc
, TRANSPARENT
);
1569 SetTextColor(hdc
, ((iItem
== infoPtr
->iHotTracked
) | (infoPtr
->items
[iItem
].dwState
& TCIS_HIGHLIGHTED
))?
1570 comctl32_color
.clrHighlight
: comctl32_color
.clrBtnText
);
1572 /* get the rectangle that the text fits in */
1573 DrawTextW(hdc
, infoPtr
->items
[iItem
].pszText
, -1,
1574 &rcText
, DT_CALCRECT
);
1577 * If not owner draw, then do the drawing ourselves.
1581 if (infoPtr
->himl
&& (infoPtr
->items
[iItem
].mask
& TCIF_IMAGE
))
1583 ImageList_GetIconSize(infoPtr
->himl
, &cx
, &cy
);
1585 if(lStyle
& TCS_VERTICAL
)
1587 center_offset_h
= ((drawRect
->bottom
- drawRect
->top
) - (cy
+ infoPtr
->uHItemPadding
+ (rcText
.right
- rcText
.left
))) / 2;
1588 center_offset_v
= ((drawRect
->right
- drawRect
->left
) - (cx
+ infoPtr
->uVItemPadding
)) / 2;
1592 center_offset_h
= ((drawRect
->right
- drawRect
->left
) - (cx
+ infoPtr
->uHItemPadding
+ (rcText
.right
- rcText
.left
))) / 2;
1593 center_offset_v
= ((drawRect
->bottom
- drawRect
->top
) - (cy
+ infoPtr
->uVItemPadding
)) / 2;
1596 if ((lStyle
& TCS_FIXEDWIDTH
&&
1597 lStyle
& (TCS_FORCELABELLEFT
| TCS_FORCEICONLEFT
)) ||
1598 (center_offset_h
< 0))
1599 center_offset_h
= 0;
1601 TRACE("for <%s>, c_o=%d, draw=(%ld,%ld)-(%ld,%ld), textlen=%ld\n",
1602 debugstr_w(infoPtr
->items
[iItem
].pszText
), center_offset_h
,
1603 drawRect
->left
, drawRect
->top
, drawRect
->right
, drawRect
->bottom
,
1604 (rcText
.right
-rcText
.left
));
1606 if((lStyle
& TCS_VERTICAL
) && (lStyle
& TCS_BOTTOM
))
1608 rcImage
.top
= drawRect
->top
+ center_offset_h
;
1609 /* if tab is TCS_VERTICAL and TCS_BOTTOM, the text is drawn from the */
1610 /* right side of the tab, but the image still uses the left as its x position */
1611 /* this keeps the image always drawn off of the same side of the tab */
1612 rcImage
.left
= drawRect
->right
- cx
- center_offset_v
;
1613 drawRect
->top
= rcImage
.top
+ (cx
+ infoPtr
->uHItemPadding
);
1615 else if(lStyle
& TCS_VERTICAL
)
1617 rcImage
.top
= drawRect
->bottom
- cy
- center_offset_h
;
1618 rcImage
.left
= drawRect
->left
+ center_offset_v
;
1619 drawRect
->bottom
= rcImage
.top
- infoPtr
->uHItemPadding
;
1621 else /* normal style, whether TCS_BOTTOM or not */
1623 rcImage
.left
= drawRect
->left
+ center_offset_h
+ 3;
1624 drawRect
->left
= rcImage
.left
+ cx
+ infoPtr
->uHItemPadding
;
1625 rcImage
.top
= drawRect
->top
+ center_offset_v
;
1628 TRACE("drawing image=%d, left=%ld, top=%ld\n",
1629 infoPtr
->items
[iItem
].iImage
, rcImage
.left
, rcImage
.top
-1);
1633 infoPtr
->items
[iItem
].iImage
,
1640 else /* no image, so just shift the drawRect borders around */
1642 if(lStyle
& TCS_VERTICAL
)
1644 center_offset_h
= 0;
1646 currently the rcText rect is flawed because the rotated font does not
1647 often match the horizontal font. So leave this as 0
1648 ((drawRect->bottom - drawRect->top) - (rcText.right - rcText.left)) / 2;
1650 if(lStyle
& TCS_BOTTOM
)
1651 drawRect
->top
+=center_offset_h
;
1653 drawRect
->bottom
-=center_offset_h
;
1657 center_offset_h
= ((drawRect
->right
- drawRect
->left
) - (rcText
.right
- rcText
.left
)) / 2;
1658 drawRect
->left
+=center_offset_h
;
1662 if(lStyle
& TCS_VERTICAL
)
1664 center_offset_v
= ((drawRect
->right
- drawRect
->left
) - ((rcText
.bottom
- rcText
.top
) + infoPtr
->uVItemPadding
)) / 2;
1665 drawRect
->left
+= center_offset_v
;
1669 center_offset_v
= ((drawRect
->bottom
- drawRect
->top
) - ((rcText
.bottom
- rcText
.top
) + infoPtr
->uVItemPadding
)) / 2;
1670 drawRect
->top
+= center_offset_v
;
1675 if ((lStyle
& TCS_FIXEDWIDTH
&& lStyle
& TCS_FORCELABELLEFT
) ||
1677 uHorizAlign
= DT_LEFT
;
1679 uHorizAlign
= DT_CENTER
;
1681 if(lStyle
& TCS_VERTICAL
) /* if we are vertical rotate the text and each character */
1683 if(lStyle
& TCS_BOTTOM
)
1686 nOrientation
= -900;
1695 /* to get a font with the escapement and orientation we are looking for, we need to */
1696 /* call CreateFontIndirectA, which requires us to set the values of the logfont we pass in */
1697 if(lStyle
& TCS_VERTICAL
)
1699 if (!GetObjectA((infoPtr
->hFont
) ?
1700 infoPtr
->hFont
: GetStockObject(SYSTEM_FONT
),
1701 sizeof(LOGFONTA
),&logfont
))
1705 lstrcpyA(logfont
.lfFaceName
, "Arial");
1706 logfont
.lfHeight
= -MulDiv(iPointSize
, GetDeviceCaps(hdc
, LOGPIXELSY
),
1708 logfont
.lfWeight
= FW_NORMAL
;
1709 logfont
.lfItalic
= 0;
1710 logfont
.lfUnderline
= 0;
1711 logfont
.lfStrikeOut
= 0;
1714 logfont
.lfEscapement
= nEscapement
;
1715 logfont
.lfOrientation
= nOrientation
;
1716 hFont
= CreateFontIndirectA(&logfont
);
1717 hOldFont
= SelectObject(hdc
, hFont
);
1720 if (lStyle
& TCS_VERTICAL
)
1723 (lStyle
& TCS_BOTTOM
) ? drawRect
->right
: drawRect
->left
,
1724 (!(lStyle
& TCS_BOTTOM
)) ? drawRect
->bottom
: drawRect
->top
,
1727 infoPtr
->items
[iItem
].pszText
,
1728 lstrlenW(infoPtr
->items
[iItem
].pszText
),
1736 infoPtr
->items
[iItem
].pszText
,
1737 lstrlenW(infoPtr
->items
[iItem
].pszText
),
1739 uHorizAlign
| DT_SINGLELINE
1743 /* clean things up */
1744 *drawRect
= rcTemp
; /* restore drawRect */
1746 if(lStyle
& TCS_VERTICAL
)
1748 SelectObject(hdc
, hOldFont
); /* restore the original font */
1750 DeleteObject(hFont
);
1757 SetBkMode(hdc
, oldBkMode
);
1758 SelectObject(hdc
, holdPen
);
1759 DeleteObject( htextPen
);
1762 /******************************************************************************
1765 * This method is used to draw a single tab into the tab control.
1767 static void TAB_DrawItem(
1772 TAB_INFO
*infoPtr
= TAB_GetInfoPtr(hwnd
);
1773 LONG lStyle
= GetWindowLongA(hwnd
, GWL_STYLE
);
1777 RECT r
, fillRect
, r1
;
1780 COLORREF bkgnd
, corner
;
1783 * Get the rectangle for the item.
1785 isVisible
= TAB_InternalGetItemRect(hwnd
,
1793 /* If you need to see what the control is doing,
1794 * then override these variables. They will change what
1795 * fill colors are used for filling the tabs, and the
1796 * corners when drawing the edge.
1798 bkgnd
= comctl32_color
.clrBtnFace
;
1799 corner
= comctl32_color
.clrBtnFace
;
1801 if (lStyle
& TCS_BUTTONS
)
1803 HBRUSH hbr
= CreateSolidBrush (bkgnd
);
1804 BOOL deleteBrush
= TRUE
;
1806 /* Get item rectangle */
1809 /* Separators between flat buttons */
1810 if (lStyle
& TCS_FLATBUTTONS
)
1813 r1
.right
+= (FLAT_BTN_SPACINGX
-2);
1814 DrawEdge(hdc
, &r1
, EDGE_ETCHED
, BF_RIGHT
);
1817 if (iItem
== infoPtr
->iSelected
)
1819 /* Background color */
1820 if (!(lStyle
& TCS_OWNERDRAWFIXED
))
1823 hbr
= GetSysColorBrush(COLOR_SCROLLBAR
);
1825 SetTextColor(hdc
, comctl32_color
.clr3dFace
);
1826 SetBkColor(hdc
, comctl32_color
.clr3dHilight
);
1828 /* if COLOR_WINDOW happens to be the same as COLOR_3DHILIGHT
1829 * we better use 0x55aa bitmap brush to make scrollbar's background
1830 * look different from the window background.
1832 if (comctl32_color
.clr3dHilight
== comctl32_color
.clrWindow
)
1833 hbr
= COMCTL32_hPattern55AABrush
;
1835 deleteBrush
= FALSE
;
1838 /* Clear interior */
1839 FillRect(hdc
, &r
, hbr
);
1841 DrawEdge(hdc
, &r
, EDGE_SUNKEN
, BF_SOFT
|BF_RECT
);
1843 else /* ! selected */
1845 if (!(lStyle
& TCS_FLATBUTTONS
))
1847 /* Clear interior */
1848 FillRect(hdc
, &r
, hbr
);
1850 DrawEdge(hdc
, &r
, EDGE_RAISED
, BF_SOFT
|BF_RECT
);
1855 if (deleteBrush
) DeleteObject(hbr
);
1857 else /* !TCS_BUTTONS */
1859 /* We draw a rectangle of different sizes depending on the selection
1861 if (iItem
== infoPtr
->iSelected
) {
1863 GetClientRect (hwnd
, &rect
);
1864 clRight
= rect
.right
;
1865 clBottom
= rect
.bottom
;
1872 * Erase the background. (Delay it but setup rectangle.)
1873 * This is necessary when drawing the selected item since it is larger
1874 * than the others, it might overlap with stuff already drawn by the
1879 if(lStyle
& TCS_VERTICAL
)
1881 /* These are for adjusting the drawing of a Selected tab */
1882 /* The initial values are for the normal case of non-Selected */
1883 int ZZ
= 1; /* Do not strech if selected */
1884 if (iItem
== infoPtr
->iSelected
) {
1887 /* if leftmost draw the line longer */
1888 if(selectedRect
.top
== 0)
1890 /* if rightmost draw the line longer */
1891 if(selectedRect
.bottom
== clBottom
)
1892 fillRect
.bottom
-= 2;
1895 if (lStyle
& TCS_BOTTOM
)
1897 /* Adjust both rectangles to match native */
1900 TRACE("<left> item=%d, fill=(%ld,%ld)-(%ld,%ld), edge=(%ld,%ld)-(%ld,%ld)\n",
1902 fillRect
.left
,fillRect
.top
,fillRect
.right
,fillRect
.bottom
,
1903 r
.left
,r
.top
,r
.right
,r
.bottom
);
1905 /* Clear interior */
1906 SetBkColor(hdc
, bkgnd
);
1907 ExtTextOutA(hdc
, 0, 0, 2, &fillRect
, NULL
, 0, 0);
1909 /* Draw rectangular edge around tab */
1910 DrawEdge(hdc
, &r
, EDGE_RAISED
, BF_SOFT
|BF_RIGHT
|BF_TOP
|BF_BOTTOM
);
1912 /* Now erase the top corner and draw diagonal edge */
1913 SetBkColor(hdc
, corner
);
1914 r1
.left
= r
.right
- ROUND_CORNER_SIZE
- 1;
1917 r1
.bottom
= r1
.top
+ ROUND_CORNER_SIZE
;
1918 ExtTextOutA(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
1920 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_DIAGONAL_ENDTOPLEFT
);
1922 /* Now erase the bottom corner and draw diagonal edge */
1923 r1
.left
= r
.right
- ROUND_CORNER_SIZE
- 1;
1924 r1
.bottom
= r
.bottom
;
1926 r1
.top
= r1
.bottom
- ROUND_CORNER_SIZE
;
1927 ExtTextOutA(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
1929 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_DIAGONAL_ENDBOTTOMLEFT
);
1931 if ((iItem
== infoPtr
->iSelected
) && (selectedRect
.top
== 0)) {
1935 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_TOP
);
1941 /* Adjust both rectangles to match native */
1942 fillRect
.right
+= (1-ZZ
);
1944 TRACE("<left> item=%d, fill=(%ld,%ld)-(%ld,%ld), edge=(%ld,%ld)-(%ld,%ld)\n",
1946 fillRect
.left
,fillRect
.top
,fillRect
.right
,fillRect
.bottom
,
1947 r
.left
,r
.top
,r
.right
,r
.bottom
);
1949 /* Clear interior */
1950 SetBkColor(hdc
, bkgnd
);
1951 ExtTextOutA(hdc
, 0, 0, 2, &fillRect
, NULL
, 0, 0);
1953 /* Draw rectangular edge around tab */
1954 DrawEdge(hdc
, &r
, EDGE_RAISED
, BF_SOFT
|BF_LEFT
|BF_TOP
|BF_BOTTOM
);
1956 /* Now erase the top corner and draw diagonal edge */
1957 SetBkColor(hdc
, corner
);
1960 r1
.right
= r1
.left
+ ROUND_CORNER_SIZE
+ 1;
1961 r1
.bottom
= r1
.top
+ ROUND_CORNER_SIZE
;
1962 ExtTextOutA(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
1964 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_DIAGONAL_ENDTOPRIGHT
);
1966 /* Now erase the bottom corner and draw diagonal edge */
1968 r1
.bottom
= r
.bottom
;
1969 r1
.right
= r1
.left
+ ROUND_CORNER_SIZE
+ 1;
1970 r1
.top
= r1
.bottom
- ROUND_CORNER_SIZE
;
1971 ExtTextOutA(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
1973 DrawEdge(hdc
, &r1
, EDGE_SUNKEN
, BF_DIAGONAL_ENDTOPLEFT
);
1976 else /* ! TCS_VERTICAL */
1978 /* These are for adjusting the drawing of a Selected tab */
1979 /* The initial values are for the normal case of non-Selected */
1980 int ZZ
= 1; /* Do not strech if selected */
1981 if (iItem
== infoPtr
->iSelected
) {
1984 /* if leftmost draw the line longer */
1985 if(selectedRect
.left
== 0)
1987 /* if rightmost draw the line longer */
1988 if(selectedRect
.right
== clRight
)
1989 fillRect
.right
-= 2;
1992 if (lStyle
& TCS_BOTTOM
)
1995 /* Adjust both rectangles to match native */
2001 TRACE("<bottom> item=%d, fill=(%ld,%ld)-(%ld,%ld), edge=(%ld,%ld)-(%ld,%ld)\n",
2003 fillRect
.left
,fillRect
.top
,fillRect
.right
,fillRect
.bottom
,
2004 r
.left
,r
.top
,r
.right
,r
.bottom
);
2006 /* Clear interior */
2007 SetBkColor(hdc
, bkgnd
);
2008 ExtTextOutA(hdc
, 0, 0, 2, &fillRect
, NULL
, 0, 0);
2010 /* Draw rectangular edge around tab */
2011 DrawEdge(hdc
, &r
, EDGE_RAISED
, BF_SOFT
|BF_LEFT
|BF_BOTTOM
|BF_RIGHT
);
2013 /* Now erase the righthand corner and draw diagonal edge */
2014 SetBkColor(hdc
, corner
);
2015 r1
.left
= r
.right
- ROUND_CORNER_SIZE
;
2016 r1
.bottom
= r
.bottom
;
2018 r1
.top
= r1
.bottom
- ROUND_CORNER_SIZE
- 1;
2019 ExtTextOutA(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2021 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_DIAGONAL_ENDBOTTOMLEFT
);
2023 /* Now erase the lefthand corner and draw diagonal edge */
2025 r1
.bottom
= r
.bottom
;
2026 r1
.right
= r1
.left
+ ROUND_CORNER_SIZE
;
2027 r1
.top
= r1
.bottom
- ROUND_CORNER_SIZE
- 1;
2028 ExtTextOutA(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2030 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_DIAGONAL_ENDTOPLEFT
);
2032 if ((iItem
== infoPtr
->iSelected
) && (selectedRect
.left
== 0)) {
2036 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_LEFT
);
2043 /* Adjust both rectangles to match native */
2044 fillRect
.bottom
+= (1-ZZ
);
2046 TRACE("<top> item=%d, fill=(%ld,%ld)-(%ld,%ld), edge=(%ld,%ld)-(%ld,%ld)\n",
2048 fillRect
.left
,fillRect
.top
,fillRect
.right
,fillRect
.bottom
,
2049 r
.left
,r
.top
,r
.right
,r
.bottom
);
2051 /* Clear interior */
2052 SetBkColor(hdc
, bkgnd
);
2053 ExtTextOutA(hdc
, 0, 0, 2, &fillRect
, NULL
, 0, 0);
2055 /* Draw rectangular edge around tab */
2056 DrawEdge(hdc
, &r
, EDGE_RAISED
, BF_SOFT
|BF_LEFT
|BF_TOP
|BF_RIGHT
);
2058 /* Now erase the righthand corner and draw diagonal edge */
2059 SetBkColor(hdc
, corner
);
2060 r1
.left
= r
.right
- ROUND_CORNER_SIZE
;
2063 r1
.bottom
= r1
.top
+ ROUND_CORNER_SIZE
+ 1;
2064 ExtTextOutA(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2066 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_DIAGONAL_ENDBOTTOMRIGHT
);
2068 /* Now erase the lefthand corner and draw diagonal edge */
2071 r1
.right
= r1
.left
+ ROUND_CORNER_SIZE
;
2072 r1
.bottom
= r1
.top
+ ROUND_CORNER_SIZE
+ 1;
2073 ExtTextOutA(hdc
, 0, 0, 2, &r1
, NULL
, 0, 0);
2075 DrawEdge(hdc
, &r1
, EDGE_RAISED
, BF_SOFT
|BF_DIAGONAL_ENDTOPRIGHT
);
2081 TAB_DumpItemInternal(infoPtr
, iItem
);
2083 /* This modifies r to be the text rectangle. */
2085 HFONT hOldFont
= SelectObject(hdc
, infoPtr
->hFont
);
2086 TAB_DrawItemInterior(hwnd
, hdc
, iItem
, &r
);
2087 SelectObject(hdc
,hOldFont
);
2090 /* Draw the focus rectangle */
2091 if (((lStyle
& TCS_FOCUSNEVER
) == 0) &&
2092 (GetFocus() == hwnd
) &&
2093 (iItem
== infoPtr
->uFocus
) )
2096 InflateRect(&r
, -1, -1);
2098 DrawFocusRect(hdc
, &r
);
2103 /******************************************************************************
2106 * This method is used to draw the raised border around the tab control
2109 static void TAB_DrawBorder (HWND hwnd
, HDC hdc
)
2111 TAB_INFO
*infoPtr
= TAB_GetInfoPtr(hwnd
);
2113 DWORD lStyle
= GetWindowLongA(hwnd
, GWL_STYLE
);
2115 GetClientRect (hwnd
, &rect
);
2118 * Adjust for the style
2121 if (infoPtr
->uNumItem
)
2123 if ((lStyle
& TCS_BOTTOM
) && !(lStyle
& TCS_VERTICAL
))
2125 rect
.bottom
-= (infoPtr
->tabHeight
- 2) * infoPtr
->uNumRows
+ 3;
2127 else if((lStyle
& TCS_BOTTOM
) && (lStyle
& TCS_VERTICAL
))
2129 rect
.right
-= (infoPtr
->tabHeight
- 2) * infoPtr
->uNumRows
+ 2;
2131 else if(lStyle
& TCS_VERTICAL
)
2133 rect
.left
+= (infoPtr
->tabHeight
- 2) * infoPtr
->uNumRows
+ 2;
2135 else /* not TCS_VERTICAL and not TCS_BOTTOM */
2137 rect
.top
+= (infoPtr
->tabHeight
- 2) * infoPtr
->uNumRows
+ 2;
2141 TRACE("border=(%ld,%ld)-(%ld,%ld)\n",
2142 rect
.left
, rect
.top
, rect
.right
, rect
.bottom
);
2144 DrawEdge(hdc
, &rect
, EDGE_RAISED
, BF_SOFT
|BF_RECT
);
2147 /******************************************************************************
2150 * This method repaints the tab control..
2152 static void TAB_Refresh (HWND hwnd
, HDC hdc
)
2154 TAB_INFO
*infoPtr
= TAB_GetInfoPtr(hwnd
);
2158 if (!infoPtr
->DoRedraw
)
2161 hOldFont
= SelectObject (hdc
, infoPtr
->hFont
);
2163 if (GetWindowLongA(hwnd
, GWL_STYLE
) & TCS_BUTTONS
)
2165 for (i
= 0; i
< infoPtr
->uNumItem
; i
++)
2166 TAB_DrawItem (hwnd
, hdc
, i
);
2170 /* Draw all the non selected item first */
2171 for (i
= 0; i
< infoPtr
->uNumItem
; i
++)
2173 if (i
!= infoPtr
->iSelected
)
2174 TAB_DrawItem (hwnd
, hdc
, i
);
2177 /* Now, draw the border, draw it before the selected item
2178 * since the selected item overwrites part of the border. */
2179 TAB_DrawBorder (hwnd
, hdc
);
2181 /* Then, draw the selected item */
2182 TAB_DrawItem (hwnd
, hdc
, infoPtr
->iSelected
);
2184 /* If we haven't set the current focus yet, set it now.
2185 * Only happens when we first paint the tab controls */
2186 if (infoPtr
->uFocus
== -1)
2187 TAB_SetCurFocus(hwnd
, infoPtr
->iSelected
);
2190 SelectObject (hdc
, hOldFont
);
2194 TAB_GetRowCount (HWND hwnd
)
2196 TAB_INFO
*infoPtr
= TAB_GetInfoPtr(hwnd
);
2198 return infoPtr
->uNumRows
;
2202 TAB_SetRedraw (HWND hwnd
, WPARAM wParam
)
2204 TAB_INFO
*infoPtr
= TAB_GetInfoPtr(hwnd
);
2206 infoPtr
->DoRedraw
=(BOOL
) wParam
;
2210 static LRESULT
TAB_EraseBackground(
2217 HBRUSH brush
= CreateSolidBrush(comctl32_color
.clrBtnFace
);
2219 hdc
= givenDC
? givenDC
: GetDC(hwnd
);
2221 GetClientRect(hwnd
, &clientRect
);
2223 FillRect(hdc
, &clientRect
, brush
);
2226 ReleaseDC(hwnd
, hdc
);
2228 DeleteObject(brush
);
2233 /******************************************************************************
2234 * TAB_EnsureSelectionVisible
2236 * This method will make sure that the current selection is completely
2237 * visible by scrolling until it is.
2239 static void TAB_EnsureSelectionVisible(
2243 INT iSelected
= infoPtr
->iSelected
;
2244 LONG lStyle
= GetWindowLongA(hwnd
, GWL_STYLE
);
2245 INT iOrigLeftmostVisible
= infoPtr
->leftmostVisible
;
2247 /* set the items row to the bottommost row or topmost row depending on
2249 if ((infoPtr
->uNumRows
> 1) && !(lStyle
& TCS_BUTTONS
))
2254 if(lStyle
& TCS_VERTICAL
)
2255 newselected
= infoPtr
->items
[iSelected
].rect
.left
;
2257 newselected
= infoPtr
->items
[iSelected
].rect
.top
;
2259 /* the target row is always (number of rows - 1)
2260 as row 0 is furthest from the clientRect */
2261 iTargetRow
= infoPtr
->uNumRows
- 1;
2263 if (newselected
!= iTargetRow
)
2266 if(lStyle
& TCS_VERTICAL
)
2268 for (i
=0; i
< infoPtr
->uNumItem
; i
++)
2270 /* move everything in the row of the selected item to the iTargetRow */
2271 if (infoPtr
->items
[i
].rect
.left
== newselected
)
2272 infoPtr
->items
[i
].rect
.left
= iTargetRow
;
2275 if (infoPtr
->items
[i
].rect
.left
> newselected
)
2276 infoPtr
->items
[i
].rect
.left
-=1;
2282 for (i
=0; i
< infoPtr
->uNumItem
; i
++)
2284 if (infoPtr
->items
[i
].rect
.top
== newselected
)
2285 infoPtr
->items
[i
].rect
.top
= iTargetRow
;
2288 if (infoPtr
->items
[i
].rect
.top
> newselected
)
2289 infoPtr
->items
[i
].rect
.top
-=1;
2293 TAB_RecalcHotTrack(hwnd
, NULL
, NULL
, NULL
);
2298 * Do the trivial cases first.
2300 if ( (!infoPtr
->needsScrolling
) ||
2301 (infoPtr
->hwndUpDown
==0) || (lStyle
& TCS_VERTICAL
))
2304 if (infoPtr
->leftmostVisible
>= iSelected
)
2306 infoPtr
->leftmostVisible
= iSelected
;
2313 /* Calculate the part of the client area that is visible */
2314 GetClientRect(hwnd
, &r
);
2317 GetClientRect(infoPtr
->hwndUpDown
, &r
);
2320 if ((infoPtr
->items
[iSelected
].rect
.right
-
2321 infoPtr
->items
[iSelected
].rect
.left
) >= width
)
2323 /* Special case: width of selected item is greater than visible
2326 infoPtr
->leftmostVisible
= iSelected
;
2330 for (i
= infoPtr
->leftmostVisible
; i
< infoPtr
->uNumItem
; i
++)
2332 if ((infoPtr
->items
[iSelected
].rect
.right
-
2333 infoPtr
->items
[i
].rect
.left
) < width
)
2336 infoPtr
->leftmostVisible
= i
;
2340 if (infoPtr
->leftmostVisible
!= iOrigLeftmostVisible
)
2341 TAB_RecalcHotTrack(hwnd
, NULL
, NULL
, NULL
);
2343 SendMessageA(infoPtr
->hwndUpDown
, UDM_SETPOS
, 0,
2344 MAKELONG(infoPtr
->leftmostVisible
, 0));
2347 /******************************************************************************
2348 * TAB_InvalidateTabArea
2350 * This method will invalidate the portion of the control that contains the
2351 * tabs. It is called when the state of the control changes and needs
2354 static void TAB_InvalidateTabArea(
2359 DWORD lStyle
= GetWindowLongA(hwnd
, GWL_STYLE
);
2360 INT lastRow
= infoPtr
->uNumRows
- 1;
2363 if (lastRow
< 0) return;
2365 GetClientRect(hwnd
, &clientRect
);
2367 TAB_InternalGetItemRect(hwnd
, infoPtr
, infoPtr
->uNumItem
-1 , &rect
, NULL
);
2368 if ((lStyle
& TCS_BOTTOM
) && !(lStyle
& TCS_VERTICAL
))
2370 clientRect
.top
= clientRect
.bottom
-
2371 infoPtr
->tabHeight
-
2372 lastRow
* (infoPtr
->tabHeight
- 2) -
2373 ((lStyle
& TCS_BUTTONS
) ? lastRow
* BUTTON_SPACINGY
: 0) - 3;
2374 clientRect
.right
= clientRect
.left
+ rect
.right
+ 2 * SELECTED_TAB_OFFSET
;
2376 else if((lStyle
& TCS_BOTTOM
) && (lStyle
& TCS_VERTICAL
))
2378 clientRect
.left
= clientRect
.right
- infoPtr
->tabHeight
-
2379 lastRow
* (infoPtr
->tabHeight
- 2) -
2380 ((lStyle
& TCS_BUTTONS
) ? lastRow
* BUTTON_SPACINGY
: 0) - 2;
2381 clientRect
.bottom
= clientRect
.top
+ rect
.bottom
+ 2 * SELECTED_TAB_OFFSET
;
2383 else if(lStyle
& TCS_VERTICAL
)
2385 clientRect
.right
= clientRect
.left
+ infoPtr
->tabHeight
+
2386 lastRow
* (infoPtr
->tabHeight
- 2) -
2387 ((lStyle
& TCS_BUTTONS
) ? lastRow
* BUTTON_SPACINGY
: 0) + 2;
2388 clientRect
.bottom
= clientRect
.top
+ rect
.bottom
+ 2 * SELECTED_TAB_OFFSET
;
2392 clientRect
.bottom
= clientRect
.top
+ infoPtr
->tabHeight
+
2393 lastRow
* (infoPtr
->tabHeight
- 2) +
2394 ((lStyle
& TCS_BUTTONS
) ? lastRow
* BUTTON_SPACINGY
: 0) + 2;
2395 clientRect
.right
= clientRect
.left
+ rect
.right
+ 2 * SELECTED_TAB_OFFSET
;
2398 /* Punch out the updown control */
2399 if (infoPtr
->needsScrolling
&& (clientRect
.right
> 0)) {
2400 GetClientRect(infoPtr
->hwndUpDown
, &r
);
2401 clientRect
.right
= clientRect
.right
- (r
.right
- r
.left
);
2404 TRACE("invalidate (%ld,%ld)-(%ld,%ld)\n",
2405 clientRect
.left
,clientRect
.top
,
2406 clientRect
.right
,clientRect
.bottom
);
2408 InvalidateRect(hwnd
, &clientRect
, TRUE
);
2412 TAB_Paint (HWND hwnd
, WPARAM wParam
)
2419 hdc
= BeginPaint (hwnd
, &ps
);
2420 TRACE("erase %d, rect=(%ld,%ld)-(%ld,%ld)\n",
2422 ps
.rcPaint
.left
,ps
.rcPaint
.top
,ps
.rcPaint
.right
,ps
.rcPaint
.bottom
);
2425 TAB_EraseBackground (hwnd
, hdc
);
2431 TAB_Refresh (hwnd
, hdc
);
2434 EndPaint (hwnd
, &ps
);
2440 TAB_InsertItemA (HWND hwnd
, WPARAM wParam
, LPARAM lParam
)
2442 TAB_INFO
*infoPtr
= TAB_GetInfoPtr(hwnd
);
2447 GetClientRect (hwnd
, &rect
);
2448 TRACE("Rect: %p T %li, L %li, B %li, R %li\n", hwnd
,
2449 rect
.top
, rect
.left
, rect
.bottom
, rect
.right
);
2451 pti
= (TCITEMA
*)lParam
;
2452 iItem
= (INT
)wParam
;
2454 if (iItem
< 0) return -1;
2455 if (iItem
> infoPtr
->uNumItem
)
2456 iItem
= infoPtr
->uNumItem
;
2458 TAB_DumpItemExternalA(pti
, iItem
);
2461 if (infoPtr
->uNumItem
== 0) {
2462 infoPtr
->items
= Alloc (sizeof (TAB_ITEM
));
2463 infoPtr
->uNumItem
++;
2464 infoPtr
->iSelected
= 0;
2467 TAB_ITEM
*oldItems
= infoPtr
->items
;
2469 infoPtr
->uNumItem
++;
2470 infoPtr
->items
= Alloc (sizeof (TAB_ITEM
) * infoPtr
->uNumItem
);
2472 /* pre insert copy */
2474 memcpy (&infoPtr
->items
[0], &oldItems
[0],
2475 iItem
* sizeof(TAB_ITEM
));
2478 /* post insert copy */
2479 if (iItem
< infoPtr
->uNumItem
- 1) {
2480 memcpy (&infoPtr
->items
[iItem
+1], &oldItems
[iItem
],
2481 (infoPtr
->uNumItem
- iItem
- 1) * sizeof(TAB_ITEM
));
2485 if (iItem
<= infoPtr
->iSelected
)
2486 infoPtr
->iSelected
++;
2491 infoPtr
->items
[iItem
].mask
= pti
->mask
;
2492 if (pti
->mask
& TCIF_TEXT
)
2493 Str_SetPtrAtoW (&infoPtr
->items
[iItem
].pszText
, pti
->pszText
);
2495 if (pti
->mask
& TCIF_IMAGE
)
2496 infoPtr
->items
[iItem
].iImage
= pti
->iImage
;
2498 if (pti
->mask
& TCIF_PARAM
)
2499 infoPtr
->items
[iItem
].lParam
= pti
->lParam
;
2501 TAB_SetItemBounds(hwnd
);
2502 if (infoPtr
->uNumItem
> 1)
2503 TAB_InvalidateTabArea(hwnd
, infoPtr
);
2505 InvalidateRect(hwnd
, NULL
, TRUE
);
2507 TRACE("[%p]: added item %d %s\n",
2508 hwnd
, iItem
, debugstr_w(infoPtr
->items
[iItem
].pszText
));
2515 TAB_InsertItemW (HWND hwnd
, WPARAM wParam
, LPARAM lParam
)
2517 TAB_INFO
*infoPtr
= TAB_GetInfoPtr(hwnd
);
2522 GetClientRect (hwnd
, &rect
);
2523 TRACE("Rect: %p T %li, L %li, B %li, R %li\n", hwnd
,
2524 rect
.top
, rect
.left
, rect
.bottom
, rect
.right
);
2526 pti
= (TCITEMW
*)lParam
;
2527 iItem
= (INT
)wParam
;
2529 if (iItem
< 0) return -1;
2530 if (iItem
> infoPtr
->uNumItem
)
2531 iItem
= infoPtr
->uNumItem
;
2533 TAB_DumpItemExternalW(pti
, iItem
);
2535 if (infoPtr
->uNumItem
== 0) {
2536 infoPtr
->items
= Alloc (sizeof (TAB_ITEM
));
2537 infoPtr
->uNumItem
++;
2538 infoPtr
->iSelected
= 0;
2541 TAB_ITEM
*oldItems
= infoPtr
->items
;
2543 infoPtr
->uNumItem
++;
2544 infoPtr
->items
= Alloc (sizeof (TAB_ITEM
) * infoPtr
->uNumItem
);
2546 /* pre insert copy */
2548 memcpy (&infoPtr
->items
[0], &oldItems
[0],
2549 iItem
* sizeof(TAB_ITEM
));
2552 /* post insert copy */
2553 if (iItem
< infoPtr
->uNumItem
- 1) {
2554 memcpy (&infoPtr
->items
[iItem
+1], &oldItems
[iItem
],
2555 (infoPtr
->uNumItem
- iItem
- 1) * sizeof(TAB_ITEM
));
2559 if (iItem
<= infoPtr
->iSelected
)
2560 infoPtr
->iSelected
++;
2565 infoPtr
->items
[iItem
].mask
= pti
->mask
;
2566 if (pti
->mask
& TCIF_TEXT
)
2567 Str_SetPtrW (&infoPtr
->items
[iItem
].pszText
, pti
->pszText
);
2569 if (pti
->mask
& TCIF_IMAGE
)
2570 infoPtr
->items
[iItem
].iImage
= pti
->iImage
;
2572 if (pti
->mask
& TCIF_PARAM
)
2573 infoPtr
->items
[iItem
].lParam
= pti
->lParam
;
2575 TAB_SetItemBounds(hwnd
);
2576 if (infoPtr
->uNumItem
> 1)
2577 TAB_InvalidateTabArea(hwnd
, infoPtr
);
2579 InvalidateRect(hwnd
, NULL
, TRUE
);
2581 TRACE("[%p]: added item %d %s\n",
2582 hwnd
, iItem
, debugstr_w(infoPtr
->items
[iItem
].pszText
));
2589 TAB_SetItemSize (HWND hwnd
, WPARAM wParam
, LPARAM lParam
)
2591 TAB_INFO
*infoPtr
= TAB_GetInfoPtr(hwnd
);
2592 LONG lStyle
= GetWindowLongA(hwnd
, GWL_STYLE
);
2594 BOOL bNeedPaint
= FALSE
;
2596 lResult
= MAKELONG(infoPtr
->tabWidth
, infoPtr
->tabHeight
);
2598 /* UNDOCUMENTED: If requested Width or Height is 0 this means that program wants to use auto size. */
2599 if (lStyle
& TCS_FIXEDWIDTH
&& (infoPtr
->tabWidth
!= (INT
)LOWORD(lParam
)))
2601 infoPtr
->tabWidth
= max((INT
)LOWORD(lParam
), infoPtr
->tabMinWidth
);
2605 if (infoPtr
->tabHeight
!= (INT
)HIWORD(lParam
))
2607 if ((infoPtr
->fHeightSet
= ((INT
)HIWORD(lParam
) != 0)))
2608 infoPtr
->tabHeight
= (INT
)HIWORD(lParam
);
2612 TRACE("was h=%d,w=%d, now h=%d,w=%d\n",
2613 HIWORD(lResult
), LOWORD(lResult
),
2614 infoPtr
->tabHeight
, infoPtr
->tabWidth
);
2618 TAB_SetItemBounds(hwnd
);
2619 RedrawWindow(hwnd
, NULL
, NULL
, RDW_ERASE
| RDW_INVALIDATE
| RDW_UPDATENOW
);
2626 TAB_SetMinTabWidth (HWND hwnd
, LPARAM lParam
)
2628 TAB_INFO
*infoPtr
= TAB_GetInfoPtr(hwnd
);
2629 INT cx
= (INT
)lParam
;
2633 oldcx
= infoPtr
->tabMinWidth
;
2634 infoPtr
->tabMinWidth
= (cx
==-1)?DEFAULT_TAB_WIDTH
:cx
;
2642 TAB_HighlightItem (HWND hwnd
, WPARAM wParam
, LPARAM lParam
)
2644 TAB_INFO
*infoPtr
= TAB_GetInfoPtr(hwnd
);
2645 INT iItem
= (INT
)wParam
;
2646 BOOL fHighlight
= (BOOL
)LOWORD(lParam
);
2648 if ((infoPtr
) && (iItem
>=0) && (iItem
<infoPtr
->uNumItem
)) {
2650 infoPtr
->items
[iItem
].dwState
|= TCIS_HIGHLIGHTED
;
2652 infoPtr
->items
[iItem
].dwState
&= ~TCIS_HIGHLIGHTED
;
2660 TAB_SetItemA (HWND hwnd
, WPARAM wParam
, LPARAM lParam
)
2662 TAB_INFO
*infoPtr
= TAB_GetInfoPtr(hwnd
);
2667 iItem
= (INT
)wParam
;
2668 tabItem
= (LPTCITEMA
)lParam
;
2670 TRACE("%d %p\n", iItem
, tabItem
);
2671 if ((iItem
<0) || (iItem
>=infoPtr
->uNumItem
)) return FALSE
;
2673 TAB_DumpItemExternalA(tabItem
, iItem
);
2675 wineItem
= &infoPtr
->items
[iItem
];
2677 if (tabItem
->mask
& TCIF_IMAGE
)
2678 wineItem
->iImage
= tabItem
->iImage
;
2680 if (tabItem
->mask
& TCIF_PARAM
)
2681 wineItem
->lParam
= tabItem
->lParam
;
2683 if (tabItem
->mask
& TCIF_RTLREADING
)
2684 FIXME("TCIF_RTLREADING\n");
2686 if (tabItem
->mask
& TCIF_STATE
)
2687 wineItem
->dwState
= tabItem
->dwState
;
2689 if (tabItem
->mask
& TCIF_TEXT
)
2690 Str_SetPtrAtoW(&wineItem
->pszText
, tabItem
->pszText
);
2692 /* Update and repaint tabs */
2693 TAB_SetItemBounds(hwnd
);
2694 TAB_InvalidateTabArea(hwnd
,infoPtr
);
2701 TAB_SetItemW (HWND hwnd
, WPARAM wParam
, LPARAM lParam
)
2703 TAB_INFO
*infoPtr
= TAB_GetInfoPtr(hwnd
);
2708 iItem
= (INT
)wParam
;
2709 tabItem
= (LPTCITEMW
)lParam
;
2711 TRACE("%d %p\n", iItem
, tabItem
);
2712 if ((iItem
<0) || (iItem
>=infoPtr
->uNumItem
)) return FALSE
;
2714 TAB_DumpItemExternalW(tabItem
, iItem
);
2716 wineItem
= &infoPtr
->items
[iItem
];
2718 if (tabItem
->mask
& TCIF_IMAGE
)
2719 wineItem
->iImage
= tabItem
->iImage
;
2721 if (tabItem
->mask
& TCIF_PARAM
)
2722 wineItem
->lParam
= tabItem
->lParam
;
2724 if (tabItem
->mask
& TCIF_RTLREADING
)
2725 FIXME("TCIF_RTLREADING\n");
2727 if (tabItem
->mask
& TCIF_STATE
)
2728 wineItem
->dwState
= tabItem
->dwState
;
2730 if (tabItem
->mask
& TCIF_TEXT
)
2731 Str_SetPtrW(&wineItem
->pszText
, tabItem
->pszText
);
2733 /* Update and repaint tabs */
2734 TAB_SetItemBounds(hwnd
);
2735 TAB_InvalidateTabArea(hwnd
,infoPtr
);
2742 TAB_GetItemCount (HWND hwnd
, WPARAM wParam
, LPARAM lParam
)
2744 TAB_INFO
*infoPtr
= TAB_GetInfoPtr(hwnd
);
2746 return infoPtr
->uNumItem
;
2751 TAB_GetItemA (HWND hwnd
, WPARAM wParam
, LPARAM lParam
)
2753 TAB_INFO
*infoPtr
= TAB_GetInfoPtr(hwnd
);
2758 iItem
= (INT
)wParam
;
2759 tabItem
= (LPTCITEMA
)lParam
;
2761 if ((iItem
<0) || (iItem
>=infoPtr
->uNumItem
))
2764 wineItem
= &infoPtr
->items
[iItem
];
2766 if (tabItem
->mask
& TCIF_IMAGE
)
2767 tabItem
->iImage
= wineItem
->iImage
;
2769 if (tabItem
->mask
& TCIF_PARAM
)
2770 tabItem
->lParam
= wineItem
->lParam
;
2772 if (tabItem
->mask
& TCIF_RTLREADING
)
2773 FIXME("TCIF_RTLREADING\n");
2775 if (tabItem
->mask
& TCIF_STATE
)
2776 tabItem
->dwState
= wineItem
->dwState
;
2778 if (tabItem
->mask
& TCIF_TEXT
)
2779 Str_GetPtrWtoA (wineItem
->pszText
, tabItem
->pszText
, tabItem
->cchTextMax
);
2781 TAB_DumpItemExternalA(tabItem
, iItem
);
2788 TAB_GetItemW (HWND hwnd
, WPARAM wParam
, LPARAM lParam
)
2790 TAB_INFO
*infoPtr
= TAB_GetInfoPtr(hwnd
);
2795 iItem
= (INT
)wParam
;
2796 tabItem
= (LPTCITEMW
)lParam
;
2798 if ((iItem
<0) || (iItem
>=infoPtr
->uNumItem
))
2801 wineItem
=& infoPtr
->items
[iItem
];
2803 if (tabItem
->mask
& TCIF_IMAGE
)
2804 tabItem
->iImage
= wineItem
->iImage
;
2806 if (tabItem
->mask
& TCIF_PARAM
)
2807 tabItem
->lParam
= wineItem
->lParam
;
2809 if (tabItem
->mask
& TCIF_RTLREADING
)
2810 FIXME("TCIF_RTLREADING\n");
2812 if (tabItem
->mask
& TCIF_STATE
)
2813 tabItem
->dwState
= wineItem
->dwState
;
2815 if (tabItem
->mask
& TCIF_TEXT
)
2816 Str_GetPtrW (wineItem
->pszText
, tabItem
->pszText
, tabItem
->cchTextMax
);
2818 TAB_DumpItemExternalW(tabItem
, iItem
);
2825 TAB_DeleteItem (HWND hwnd
, WPARAM wParam
, LPARAM lParam
)
2827 TAB_INFO
*infoPtr
= TAB_GetInfoPtr(hwnd
);
2828 INT iItem
= (INT
) wParam
;
2829 BOOL bResult
= FALSE
;
2831 if ((iItem
>= 0) && (iItem
< infoPtr
->uNumItem
))
2833 TAB_ITEM
*oldItems
= infoPtr
->items
;
2835 TAB_InvalidateTabArea(hwnd
, infoPtr
);
2837 infoPtr
->uNumItem
--;
2838 infoPtr
->items
= Alloc(sizeof (TAB_ITEM
) * infoPtr
->uNumItem
);
2841 memcpy(&infoPtr
->items
[0], &oldItems
[0], iItem
* sizeof(TAB_ITEM
));
2843 if (iItem
< infoPtr
->uNumItem
)
2844 memcpy(&infoPtr
->items
[iItem
], &oldItems
[iItem
+ 1],
2845 (infoPtr
->uNumItem
- iItem
) * sizeof(TAB_ITEM
));
2849 /* Readjust the selected index */
2850 if ((iItem
== infoPtr
->iSelected
) && (iItem
> 0))
2851 infoPtr
->iSelected
--;
2853 if (iItem
< infoPtr
->iSelected
)
2854 infoPtr
->iSelected
--;
2856 if (infoPtr
->uNumItem
== 0)
2857 infoPtr
->iSelected
= -1;
2859 /* Reposition and repaint tabs */
2860 TAB_SetItemBounds(hwnd
);
2869 TAB_DeleteAllItems (HWND hwnd
, WPARAM wParam
, LPARAM lParam
)
2871 TAB_INFO
*infoPtr
= TAB_GetInfoPtr(hwnd
);
2873 TAB_InvalidateTabArea(hwnd
,infoPtr
);
2875 Free (infoPtr
->items
);
2876 infoPtr
->uNumItem
= 0;
2877 infoPtr
->iSelected
= -1;
2878 if (infoPtr
->iHotTracked
>= 0)
2879 KillTimer(hwnd
, TAB_HOTTRACK_TIMER
);
2880 infoPtr
->iHotTracked
= -1;
2882 TAB_SetItemBounds(hwnd
);
2888 TAB_GetFont (HWND hwnd
, WPARAM wParam
, LPARAM lParam
)
2890 TAB_INFO
*infoPtr
= TAB_GetInfoPtr(hwnd
);
2893 return (LRESULT
)infoPtr
->hFont
;
2897 TAB_SetFont (HWND hwnd
, WPARAM wParam
, LPARAM lParam
)
2900 TAB_INFO
*infoPtr
= TAB_GetInfoPtr(hwnd
);
2902 TRACE("%x %lx\n",wParam
, lParam
);
2904 infoPtr
->hFont
= (HFONT
)wParam
;
2906 TAB_SetItemBounds(hwnd
);
2908 TAB_InvalidateTabArea(hwnd
, infoPtr
);
2915 TAB_GetImageList (HWND hwnd
, WPARAM wParam
, LPARAM lParam
)
2917 TAB_INFO
*infoPtr
= TAB_GetInfoPtr(hwnd
);
2920 return (LRESULT
)infoPtr
->himl
;
2924 TAB_SetImageList (HWND hwnd
, WPARAM wParam
, LPARAM lParam
)
2926 TAB_INFO
*infoPtr
= TAB_GetInfoPtr(hwnd
);
2927 HIMAGELIST himlPrev
;
2930 himlPrev
= infoPtr
->himl
;
2931 infoPtr
->himl
= (HIMAGELIST
)lParam
;
2932 return (LRESULT
)himlPrev
;
2936 TAB_GetUnicodeFormat (HWND hwnd
)
2938 TAB_INFO
*infoPtr
= TAB_GetInfoPtr (hwnd
);
2939 return infoPtr
->bUnicode
;
2943 TAB_SetUnicodeFormat (HWND hwnd
, WPARAM wParam
)
2945 TAB_INFO
*infoPtr
= TAB_GetInfoPtr (hwnd
);
2946 BOOL bTemp
= infoPtr
->bUnicode
;
2948 infoPtr
->bUnicode
= (BOOL
)wParam
;
2954 TAB_Size (HWND hwnd
, WPARAM wParam
, LPARAM lParam
)
2957 /* I'm not really sure what the following code was meant to do.
2958 This is what it is doing:
2959 When WM_SIZE is sent with SIZE_RESTORED, the control
2960 gets positioned in the top left corner.
2964 UINT uPosFlags,cx,cy;
2968 parent = GetParent (hwnd);
2969 GetClientRect(parent, &parent_rect);
2972 if (GetWindowLongA(hwnd, GWL_STYLE) & CCS_NORESIZE)
2973 uPosFlags |= (SWP_NOSIZE | SWP_NOMOVE);
2975 SetWindowPos (hwnd, 0, parent_rect.left, parent_rect.top,
2976 cx, cy, uPosFlags | SWP_NOZORDER);
2978 FIXME("WM_SIZE flag %x %lx not handled\n", wParam, lParam);
2981 /* Recompute the size/position of the tabs. */
2982 TAB_SetItemBounds (hwnd
);
2984 /* Force a repaint of the control. */
2985 InvalidateRect(hwnd
, NULL
, TRUE
);
2992 TAB_Create (HWND hwnd
, WPARAM wParam
, LPARAM lParam
)
2995 TEXTMETRICA fontMetrics
;
3000 infoPtr
= (TAB_INFO
*)Alloc (sizeof(TAB_INFO
));
3002 SetWindowLongA(hwnd
, 0, (DWORD
)infoPtr
);
3004 infoPtr
->uNumItem
= 0;
3005 infoPtr
->uNumRows
= 0;
3006 infoPtr
->uHItemPadding
= 6;
3007 infoPtr
->uVItemPadding
= 3;
3010 infoPtr
->hcurArrow
= LoadCursorA (0, (LPSTR
)IDC_ARROW
);
3011 infoPtr
->iSelected
= -1;
3012 infoPtr
->iHotTracked
= -1;
3013 infoPtr
->uFocus
= -1;
3014 infoPtr
->hwndToolTip
= 0;
3015 infoPtr
->DoRedraw
= TRUE
;
3016 infoPtr
->needsScrolling
= FALSE
;
3017 infoPtr
->hwndUpDown
= 0;
3018 infoPtr
->leftmostVisible
= 0;
3019 infoPtr
->fHeightSet
= FALSE
;
3020 infoPtr
->bUnicode
= IsWindowUnicode (hwnd
);
3022 TRACE("Created tab control, hwnd [%p]\n", hwnd
);
3024 /* The tab control always has the WS_CLIPSIBLINGS style. Even
3025 if you don't specify it in CreateWindow. This is necessary in
3026 order for paint to work correctly. This follows windows behaviour. */
3027 dwStyle
= GetWindowLongA(hwnd
, GWL_STYLE
);
3028 SetWindowLongA(hwnd
, GWL_STYLE
, dwStyle
|WS_CLIPSIBLINGS
);
3030 if (dwStyle
& TCS_TOOLTIPS
) {
3031 /* Create tooltip control */
3032 infoPtr
->hwndToolTip
=
3033 CreateWindowExA (0, TOOLTIPS_CLASSA
, NULL
, 0,
3034 CW_USEDEFAULT
, CW_USEDEFAULT
,
3035 CW_USEDEFAULT
, CW_USEDEFAULT
,
3038 /* Send NM_TOOLTIPSCREATED notification */
3039 if (infoPtr
->hwndToolTip
) {
3040 NMTOOLTIPSCREATED nmttc
;
3042 nmttc
.hdr
.hwndFrom
= hwnd
;
3043 nmttc
.hdr
.idFrom
= GetWindowLongA(hwnd
, GWL_ID
);
3044 nmttc
.hdr
.code
= NM_TOOLTIPSCREATED
;
3045 nmttc
.hwndToolTips
= infoPtr
->hwndToolTip
;
3047 SendMessageA (GetParent (hwnd
), WM_NOTIFY
,
3048 (WPARAM
)GetWindowLongA(hwnd
, GWL_ID
), (LPARAM
)&nmttc
);
3053 * We need to get text information so we need a DC and we need to select
3057 hOldFont
= SelectObject (hdc
, GetStockObject (SYSTEM_FONT
));
3059 /* Use the system font to determine the initial height of a tab. */
3060 GetTextMetricsA(hdc
, &fontMetrics
);
3063 * Make sure there is enough space for the letters + growing the
3064 * selected item + extra space for the selected item.
3066 infoPtr
->tabHeight
= fontMetrics
.tmHeight
+ SELECTED_TAB_OFFSET
+
3067 ((dwStyle
& TCS_BUTTONS
) ? 2 : 1) *
3068 infoPtr
->uVItemPadding
;
3070 /* Initialize the width of a tab. */
3071 infoPtr
->tabWidth
= DEFAULT_TAB_WIDTH
;
3072 infoPtr
->tabMinWidth
= 0;
3074 TRACE("tabH=%d, tabW=%d\n", infoPtr
->tabHeight
, infoPtr
->tabWidth
);
3076 SelectObject (hdc
, hOldFont
);
3077 ReleaseDC(hwnd
, hdc
);
3083 TAB_Destroy (HWND hwnd
, WPARAM wParam
, LPARAM lParam
)
3085 TAB_INFO
*infoPtr
= TAB_GetInfoPtr(hwnd
);
3091 if (infoPtr
->items
) {
3092 for (iItem
= 0; iItem
< infoPtr
->uNumItem
; iItem
++) {
3093 if (infoPtr
->items
[iItem
].pszText
)
3094 Free (infoPtr
->items
[iItem
].pszText
);
3096 Free (infoPtr
->items
);
3099 if (infoPtr
->hwndToolTip
)
3100 DestroyWindow (infoPtr
->hwndToolTip
);
3102 if (infoPtr
->hwndUpDown
)
3103 DestroyWindow(infoPtr
->hwndUpDown
);
3105 if (infoPtr
->iHotTracked
>= 0)
3106 KillTimer(hwnd
, TAB_HOTTRACK_TIMER
);
3109 SetWindowLongA(hwnd
, 0, 0);
3113 static LRESULT WINAPI
3114 TAB_WindowProc (HWND hwnd
, UINT uMsg
, WPARAM wParam
, LPARAM lParam
)
3117 TRACE("hwnd=%p msg=%x wParam=%x lParam=%lx\n", hwnd
, uMsg
, wParam
, lParam
);
3118 if (!TAB_GetInfoPtr(hwnd
) && (uMsg
!= WM_CREATE
))
3119 return DefWindowProcA (hwnd
, uMsg
, wParam
, lParam
);
3123 case TCM_GETIMAGELIST
:
3124 return TAB_GetImageList (hwnd
, wParam
, lParam
);
3126 case TCM_SETIMAGELIST
:
3127 return TAB_SetImageList (hwnd
, wParam
, lParam
);
3129 case TCM_GETITEMCOUNT
:
3130 return TAB_GetItemCount (hwnd
, wParam
, lParam
);
3133 return TAB_GetItemA (hwnd
, wParam
, lParam
);
3136 return TAB_GetItemW (hwnd
, wParam
, lParam
);
3139 return TAB_SetItemA (hwnd
, wParam
, lParam
);
3142 return TAB_SetItemW (hwnd
, wParam
, lParam
);
3144 case TCM_DELETEITEM
:
3145 return TAB_DeleteItem (hwnd
, wParam
, lParam
);
3147 case TCM_DELETEALLITEMS
:
3148 return TAB_DeleteAllItems (hwnd
, wParam
, lParam
);
3150 case TCM_GETITEMRECT
:
3151 return TAB_GetItemRect (hwnd
, wParam
, lParam
);
3154 return TAB_GetCurSel (hwnd
);
3157 return TAB_HitTest (hwnd
, wParam
, lParam
);
3160 return TAB_SetCurSel (hwnd
, wParam
);
3162 case TCM_INSERTITEMA
:
3163 return TAB_InsertItemA (hwnd
, wParam
, lParam
);
3165 case TCM_INSERTITEMW
:
3166 return TAB_InsertItemW (hwnd
, wParam
, lParam
);
3168 case TCM_SETITEMEXTRA
:
3169 FIXME("Unimplemented msg TCM_SETITEMEXTRA\n");
3172 case TCM_ADJUSTRECT
:
3173 return TAB_AdjustRect (hwnd
, (BOOL
)wParam
, (LPRECT
)lParam
);
3175 case TCM_SETITEMSIZE
:
3176 return TAB_SetItemSize (hwnd
, wParam
, lParam
);
3178 case TCM_REMOVEIMAGE
:
3179 FIXME("Unimplemented msg TCM_REMOVEIMAGE\n");
3182 case TCM_SETPADDING
:
3183 return TAB_SetPadding (hwnd
, wParam
, lParam
);
3185 case TCM_GETROWCOUNT
:
3186 return TAB_GetRowCount(hwnd
);
3188 case TCM_GETUNICODEFORMAT
:
3189 return TAB_GetUnicodeFormat (hwnd
);
3191 case TCM_SETUNICODEFORMAT
:
3192 return TAB_SetUnicodeFormat (hwnd
, wParam
);
3194 case TCM_HIGHLIGHTITEM
:
3195 return TAB_HighlightItem (hwnd
, wParam
, lParam
);
3197 case TCM_GETTOOLTIPS
:
3198 return TAB_GetToolTips (hwnd
, wParam
, lParam
);
3200 case TCM_SETTOOLTIPS
:
3201 return TAB_SetToolTips (hwnd
, wParam
, lParam
);
3203 case TCM_GETCURFOCUS
:
3204 return TAB_GetCurFocus (hwnd
);
3206 case TCM_SETCURFOCUS
:
3207 return TAB_SetCurFocus (hwnd
, wParam
);
3209 case TCM_SETMINTABWIDTH
:
3210 return TAB_SetMinTabWidth(hwnd
, lParam
);
3212 case TCM_DESELECTALL
:
3213 FIXME("Unimplemented msg TCM_DESELECTALL\n");
3216 case TCM_GETEXTENDEDSTYLE
:
3217 FIXME("Unimplemented msg TCM_GETEXTENDEDSTYLE\n");
3220 case TCM_SETEXTENDEDSTYLE
:
3221 FIXME("Unimplemented msg TCM_SETEXTENDEDSTYLE\n");
3225 return TAB_GetFont (hwnd
, wParam
, lParam
);
3228 return TAB_SetFont (hwnd
, wParam
, lParam
);
3231 return TAB_Create (hwnd
, wParam
, lParam
);
3234 return TAB_Destroy (hwnd
, wParam
, lParam
);
3237 return DLGC_WANTARROWS
| DLGC_WANTCHARS
;
3239 case WM_LBUTTONDOWN
:
3240 return TAB_LButtonDown (hwnd
, wParam
, lParam
);
3243 return TAB_LButtonUp (hwnd
, wParam
, lParam
);
3246 return SendMessageA(GetParent(hwnd
), WM_NOTIFY
, wParam
, lParam
);
3248 case WM_RBUTTONDOWN
:
3249 return TAB_RButtonDown (hwnd
, wParam
, lParam
);
3252 return TAB_MouseMove (hwnd
, wParam
, lParam
);
3255 return TAB_EraseBackground (hwnd
, (HDC
)wParam
);
3258 return TAB_Paint (hwnd
, wParam
);
3261 return TAB_Size (hwnd
, wParam
, lParam
);
3264 return TAB_SetRedraw (hwnd
, wParam
);
3267 return TAB_OnHScroll(hwnd
, (int)LOWORD(wParam
), (int)HIWORD(wParam
), (HWND
)lParam
);
3269 case WM_STYLECHANGED
:
3270 TAB_SetItemBounds (hwnd
);
3271 InvalidateRect(hwnd
, NULL
, TRUE
);
3274 case WM_SYSCOLORCHANGE
:
3275 COMCTL32_RefreshSysColors();
3280 return TAB_FocusChanging(hwnd
, uMsg
, wParam
, lParam
);
3283 return TAB_KeyUp(hwnd
, wParam
);
3285 return TAB_NCHitTest(hwnd
, lParam
);
3288 if ((uMsg
>= WM_USER
) && (uMsg
< WM_APP
))
3289 WARN("unknown msg %04x wp=%08x lp=%08lx\n",
3290 uMsg
, wParam
, lParam
);
3291 return DefWindowProcA(hwnd
, uMsg
, wParam
, lParam
);
3303 ZeroMemory (&wndClass
, sizeof(WNDCLASSA
));
3304 wndClass
.style
= CS_GLOBALCLASS
| CS_DBLCLKS
| CS_HREDRAW
| CS_VREDRAW
;
3305 wndClass
.lpfnWndProc
= (WNDPROC
)TAB_WindowProc
;
3306 wndClass
.cbClsExtra
= 0;
3307 wndClass
.cbWndExtra
= sizeof(TAB_INFO
*);
3308 wndClass
.hCursor
= LoadCursorA (0, (LPSTR
)IDC_ARROW
);
3309 wndClass
.hbrBackground
= NULL
;
3310 wndClass
.lpszClassName
= WC_TABCONTROLA
;
3312 RegisterClassA (&wndClass
);
3317 TAB_Unregister (void)
3319 UnregisterClassA (WC_TABCONTROLA
, NULL
);