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
35 #include "wine/debug.h"
38 WINE_DEFAULT_DEBUG_CHANNEL(tab);
47 RECT rect; /* bounding rectangle of the item relative to the
48 * leftmost item (the leftmost item, 0, would have a
49 * "left" member of 0 in this rectangle)
51 * additionally the top member hold the row number
52 * and bottom is unused and should be 0 */
57 UINT uNumItem; /* number of tab items */
58 UINT uNumRows; /* number of tab rows */
59 INT tabHeight; /* height of the tab row */
60 INT tabWidth; /* width of tabs */
61 HFONT hFont; /* handle to the current font */
62 HCURSOR hcurArrow; /* handle to the current cursor */
63 HIMAGELIST himl; /* handle to a image list (may be 0) */
64 HWND hwndToolTip; /* handle to tab's tooltip */
65 INT leftmostVisible; /* Used for scrolling, this member contains
66 * the index of the first visible item */
67 INT iSelected; /* the currently selected item */
68 INT iHotTracked; /* the highlighted item under the mouse */
69 INT uFocus; /* item which has the focus */
70 TAB_ITEM* items; /* pointer to an array of TAB_ITEM's */
71 BOOL DoRedraw; /* flag for redrawing when tab contents is changed*/
72 BOOL needsScrolling; /* TRUE if the size of the tabs is greater than
73 * the size of the control */
74 BOOL fSizeSet; /* was the size of the tabs explicitly set? */
75 BOOL bUnicode; /* Unicode control? */
76 HWND hwndUpDown; /* Updown control used for scrolling */
79 /******************************************************************************
80 * Positioning constants
82 #define SELECTED_TAB_OFFSET 2
83 #define HORIZONTAL_ITEM_PADDING 5
84 #define VERTICAL_ITEM_PADDING 3
85 #define ROUND_CORNER_SIZE 2
86 #define DISPLAY_AREA_PADDINGX 2
87 #define DISPLAY_AREA_PADDINGY 2
88 #define CONTROL_BORDER_SIZEX 2
89 #define CONTROL_BORDER_SIZEY 2
90 #define BUTTON_SPACINGX 4
91 #define BUTTON_SPACINGY 4
92 #define FLAT_BTN_SPACINGX 8
93 #define DEFAULT_TAB_WIDTH 96
95 #define TAB_GetInfoPtr(hwnd) ((TAB_INFO *)GetWindowLongA(hwnd,0))
97 /******************************************************************************
98 * Hot-tracking timer constants
100 #define TAB_HOTTRACK_TIMER 1
101 #define TAB_HOTTRACK_TIMER_INTERVAL 100 /* milliseconds */
103 /******************************************************************************
106 static void TAB_Refresh (HWND hwnd, HDC hdc);
107 static void TAB_InvalidateTabArea(HWND hwnd, TAB_INFO* infoPtr);
108 static void TAB_EnsureSelectionVisible(HWND hwnd, TAB_INFO* infoPtr);
109 static void TAB_DrawItem(HWND hwnd, HDC hdc, INT iItem);
110 static void TAB_DrawItemInterior(HWND hwnd, HDC hdc, INT iItem, RECT* drawRect);
113 TAB_SendSimpleNotify (HWND hwnd, UINT code)
117 nmhdr.hwndFrom = hwnd;
118 nmhdr.idFrom = GetWindowLongA(hwnd, GWL_ID);
121 return (BOOL) SendMessageA (GetParent (hwnd), WM_NOTIFY,
122 (WPARAM) nmhdr.idFrom, (LPARAM) &nmhdr);
126 TAB_RelayEvent (HWND hwndTip, HWND hwndMsg, UINT uMsg,
127 WPARAM wParam, LPARAM lParam)
135 msg.time = GetMessageTime ();
136 msg.pt.x = LOWORD(GetMessagePos ());
137 msg.pt.y = HIWORD(GetMessagePos ());
139 SendMessageA (hwndTip, TTM_RELAYEVENT, 0, (LPARAM)&msg);
143 TAB_GetCurSel (HWND hwnd)
145 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
147 return infoPtr->iSelected;
151 TAB_GetCurFocus (HWND hwnd)
153 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
155 return infoPtr->uFocus;
159 TAB_GetToolTips (HWND hwnd, WPARAM wParam, LPARAM lParam)
161 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
163 if (infoPtr == NULL) return 0;
164 return infoPtr->hwndToolTip;
168 TAB_SetCurSel (HWND hwnd,WPARAM wParam)
170 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
171 INT iItem = (INT)wParam;
175 if ((iItem >= 0) && (iItem < infoPtr->uNumItem)) {
176 prevItem=infoPtr->iSelected;
177 infoPtr->iSelected=iItem;
178 TAB_EnsureSelectionVisible(hwnd, infoPtr);
179 TAB_InvalidateTabArea(hwnd, infoPtr);
185 TAB_SetCurFocus (HWND hwnd,WPARAM wParam)
187 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
188 INT iItem=(INT) wParam;
190 if ((iItem < 0) || (iItem >= infoPtr->uNumItem)) return 0;
192 if (GetWindowLongA(hwnd, GWL_STYLE) & TCS_BUTTONS) {
193 FIXME("Should set input focus\n");
195 int oldFocus = infoPtr->uFocus;
196 if (infoPtr->iSelected != iItem || infoPtr->uFocus == -1 ) {
197 infoPtr->uFocus = iItem;
198 if (oldFocus != -1) {
199 if (TAB_SendSimpleNotify(hwnd, TCN_SELCHANGING)!=TRUE) {
200 infoPtr->iSelected = iItem;
201 TAB_SendSimpleNotify(hwnd, TCN_SELCHANGE);
204 infoPtr->iSelected = iItem;
205 TAB_EnsureSelectionVisible(hwnd, infoPtr);
206 TAB_InvalidateTabArea(hwnd, infoPtr);
214 TAB_SetToolTips (HWND hwnd, WPARAM wParam, LPARAM lParam)
216 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
218 if (infoPtr == NULL) return 0;
219 infoPtr->hwndToolTip = (HWND)wParam;
223 /******************************************************************************
224 * TAB_InternalGetItemRect
226 * This method will calculate the rectangle representing a given tab item in
227 * client coordinates. This method takes scrolling into account.
229 * This method returns TRUE if the item is visible in the window and FALSE
230 * if it is completely outside the client area.
232 static BOOL TAB_InternalGetItemRect(
239 RECT tmpItemRect,clientRect;
240 LONG lStyle = GetWindowLongA(hwnd, GWL_STYLE);
242 /* Perform a sanity check and a trivial visibility check. */
243 if ( (infoPtr->uNumItem <= 0) ||
244 (itemIndex >= infoPtr->uNumItem) ||
245 (!((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)) && (itemIndex < infoPtr->leftmostVisible)) )
249 * Avoid special cases in this procedure by assigning the "out"
250 * parameters if the caller didn't supply them
252 if (itemRect == NULL)
253 itemRect = &tmpItemRect;
255 /* Retrieve the unmodified item rect. */
256 *itemRect = infoPtr->items[itemIndex].rect;
258 /* calculate the times bottom and top based on the row */
259 GetClientRect(hwnd, &clientRect);
261 if ((lStyle & TCS_BOTTOM) && !(lStyle & TCS_VERTICAL))
263 itemRect->bottom = clientRect.bottom -
264 SELECTED_TAB_OFFSET -
265 itemRect->top * (infoPtr->tabHeight - 2) -
266 ((lStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : 0);
268 itemRect->top = clientRect.bottom -
270 itemRect->top * (infoPtr->tabHeight - 2) -
271 ((lStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : 0);
273 else if((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
275 itemRect->right = clientRect.right - SELECTED_TAB_OFFSET - itemRect->left * (infoPtr->tabHeight - 2) -
276 ((lStyle & TCS_BUTTONS) ? itemRect->left * BUTTON_SPACINGY : 0);
277 itemRect->left = clientRect.right - infoPtr->tabHeight - itemRect->left * (infoPtr->tabHeight - 2) -
278 ((lStyle & TCS_BUTTONS) ? itemRect->left * BUTTON_SPACINGY : 0);
280 else if((lStyle & TCS_VERTICAL) && !(lStyle & TCS_BOTTOM))
282 itemRect->right = clientRect.left + infoPtr->tabHeight + itemRect->left * (infoPtr->tabHeight - 2) +
283 ((lStyle & TCS_BUTTONS) ? itemRect->left * BUTTON_SPACINGY : 0);
284 itemRect->left = clientRect.left + SELECTED_TAB_OFFSET + itemRect->left * (infoPtr->tabHeight - 2) +
285 ((lStyle & TCS_BUTTONS) ? itemRect->left * BUTTON_SPACINGY : 0);
287 else if(!(lStyle & TCS_VERTICAL) && !(lStyle & TCS_BOTTOM)) /* not TCS_BOTTOM and not TCS_VERTICAL */
289 itemRect->bottom = clientRect.top +
291 itemRect->top * (infoPtr->tabHeight - 2) +
292 ((lStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : 0);
293 itemRect->top = clientRect.top +
294 SELECTED_TAB_OFFSET +
295 itemRect->top * (infoPtr->tabHeight - 2) +
296 ((lStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : 0);
300 * "scroll" it to make sure the item at the very left of the
301 * tab control is the leftmost visible tab.
303 if(lStyle & TCS_VERTICAL)
307 -(clientRect.bottom - infoPtr->items[infoPtr->leftmostVisible].rect.bottom));
310 * Move the rectangle so the first item is slightly offset from
311 * the bottom of the tab control.
315 -SELECTED_TAB_OFFSET);
320 -infoPtr->items[infoPtr->leftmostVisible].rect.left,
324 * Move the rectangle so the first item is slightly offset from
325 * the left of the tab control.
331 TRACE("item %d tab h=%d, rect=(%d,%d)-(%d,%d)\n",
332 itemIndex, infoPtr->tabHeight,
333 itemRect->left, itemRect->top, itemRect->right, itemRect->bottom);
335 /* Now, calculate the position of the item as if it were selected. */
336 if (selectedRect!=NULL)
338 CopyRect(selectedRect, itemRect);
340 /* The rectangle of a selected item is a bit wider. */
341 if(lStyle & TCS_VERTICAL)
342 InflateRect(selectedRect, 0, SELECTED_TAB_OFFSET);
344 InflateRect(selectedRect, SELECTED_TAB_OFFSET, 0);
346 /* If it also a bit higher. */
347 if ((lStyle & TCS_BOTTOM) && !(lStyle & TCS_VERTICAL))
349 selectedRect->top -= 2; /* the border is thicker on the bottom */
350 selectedRect->bottom += SELECTED_TAB_OFFSET;
352 else if((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
354 selectedRect->left -= 2; /* the border is thicker on the right */
355 selectedRect->right += SELECTED_TAB_OFFSET;
357 else if(lStyle & TCS_VERTICAL)
359 selectedRect->left -= SELECTED_TAB_OFFSET;
360 selectedRect->right += 1;
364 selectedRect->top -= SELECTED_TAB_OFFSET;
365 selectedRect->bottom += 1;
372 static BOOL TAB_GetItemRect(HWND hwnd, WPARAM wParam, LPARAM lParam)
374 return TAB_InternalGetItemRect(hwnd, TAB_GetInfoPtr(hwnd), (INT)wParam,
375 (LPRECT)lParam, (LPRECT)NULL);
378 /******************************************************************************
381 * This method is called to handle keyboard input
383 static LRESULT TAB_KeyUp(
387 TAB_INFO* infoPtr = TAB_GetInfoPtr(hwnd);
393 newItem = infoPtr->uFocus - 1;
396 newItem = infoPtr->uFocus + 1;
401 * If we changed to a valid item, change the selection
403 if ((newItem >= 0) &&
404 (newItem < infoPtr->uNumItem) &&
405 (infoPtr->uFocus != newItem))
407 if (!TAB_SendSimpleNotify(hwnd, TCN_SELCHANGING))
409 infoPtr->iSelected = newItem;
410 infoPtr->uFocus = newItem;
411 TAB_SendSimpleNotify(hwnd, TCN_SELCHANGE);
413 TAB_EnsureSelectionVisible(hwnd, infoPtr);
414 TAB_InvalidateTabArea(hwnd, infoPtr);
421 /******************************************************************************
424 * This method is called whenever the focus goes in or out of this control
425 * it is used to update the visual state of the control.
427 static LRESULT TAB_FocusChanging(
433 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
438 * Get the rectangle for the item.
440 isVisible = TAB_InternalGetItemRect(hwnd,
447 * If the rectangle is not completely invisible, invalidate that
448 * portion of the window.
452 TRACE("invalidate (%d,%d)-(%d,%d)\n",
453 selectedRect.left,selectedRect.top,
454 selectedRect.right,selectedRect.bottom);
455 InvalidateRect(hwnd, &selectedRect, TRUE);
459 * Don't otherwise disturb normal behavior.
461 return DefWindowProcA (hwnd, uMsg, wParam, lParam);
464 static HWND TAB_InternalHitTest (
474 for (iCount = 0; iCount < infoPtr->uNumItem; iCount++)
476 TAB_InternalGetItemRect(hwnd, infoPtr, iCount, &rect, NULL);
478 if (PtInRect(&rect, pt))
480 *flags = TCHT_ONITEM;
485 *flags = TCHT_NOWHERE;
490 TAB_HitTest (HWND hwnd, WPARAM wParam, LPARAM lParam)
492 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
493 LPTCHITTESTINFO lptest = (LPTCHITTESTINFO) lParam;
495 return TAB_InternalHitTest (hwnd, infoPtr, lptest->pt, &lptest->flags);
498 /******************************************************************************
501 * Napster v2b5 has a tab control for its main navigation which has a client
502 * area that covers the whole area of the dialog pages.
503 * That's why it receives all msgs for that area and the underlying dialog ctrls
505 * So I decided that we should handle WM_NCHITTEST here and return
506 * HTTRANSPARENT if we don't hit the tab control buttons.
507 * FIXME: WM_NCHITTEST handling correct ? Fix it if you know that Windows
508 * doesn't do it that way. Maybe depends on tab control styles ?
511 TAB_NCHitTest (HWND hwnd, LPARAM lParam)
513 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
517 pt.x = LOWORD(lParam);
518 pt.y = HIWORD(lParam);
519 ScreenToClient(hwnd, &pt);
521 if (TAB_InternalHitTest(hwnd, infoPtr, pt, &dummyflag) == -1)
522 return HTTRANSPARENT;
528 TAB_LButtonDown (HWND hwnd, WPARAM wParam, LPARAM lParam)
530 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
534 if (infoPtr->hwndToolTip)
535 TAB_RelayEvent (infoPtr->hwndToolTip, hwnd,
536 WM_LBUTTONDOWN, wParam, lParam);
538 if (GetWindowLongA(hwnd, GWL_STYLE) & TCS_FOCUSONBUTTONDOWN ) {
542 if (infoPtr->hwndToolTip)
543 TAB_RelayEvent (infoPtr->hwndToolTip, hwnd,
544 WM_LBUTTONDOWN, wParam, lParam);
546 pt.x = (INT)LOWORD(lParam);
547 pt.y = (INT)HIWORD(lParam);
549 newItem = TAB_InternalHitTest (hwnd, infoPtr, pt, &dummy);
551 TRACE("On Tab, item %d\n", newItem);
553 if ((newItem != -1) && (infoPtr->iSelected != newItem))
555 if (TAB_SendSimpleNotify(hwnd, TCN_SELCHANGING) != TRUE)
557 infoPtr->iSelected = newItem;
558 infoPtr->uFocus = newItem;
559 TAB_SendSimpleNotify(hwnd, TCN_SELCHANGE);
561 TAB_EnsureSelectionVisible(hwnd, infoPtr);
563 TAB_InvalidateTabArea(hwnd, infoPtr);
570 TAB_LButtonUp (HWND hwnd, WPARAM wParam, LPARAM lParam)
572 TAB_SendSimpleNotify(hwnd, NM_CLICK);
578 TAB_RButtonDown (HWND hwnd, WPARAM wParam, LPARAM lParam)
580 TAB_SendSimpleNotify(hwnd, NM_RCLICK);
584 /******************************************************************************
585 * TAB_DrawLoneItemInterior
587 * This calls TAB_DrawItemInterior. However, TAB_DrawItemInterior is normally
588 * called by TAB_DrawItem which is normally called by TAB_Refresh which sets
589 * up the device context and font. This routine does the same setup but
590 * only calls TAB_DrawItemInterior for the single specified item.
593 TAB_DrawLoneItemInterior(HWND hwnd, TAB_INFO* infoPtr, int iItem)
595 HDC hdc = GetDC(hwnd);
596 HFONT hOldFont = SelectObject(hdc, infoPtr->hFont);
597 TAB_DrawItemInterior(hwnd, hdc, iItem, NULL);
598 SelectObject(hdc, hOldFont);
599 ReleaseDC(hwnd, hdc);
602 /******************************************************************************
603 * TAB_HotTrackTimerProc
605 * When a mouse-move event causes a tab to be highlighted (hot-tracking), a
606 * timer is setup so we can check if the mouse is moved out of our window.
607 * (We don't get an event when the mouse leaves, the mouse-move events just
608 * stop being delivered to our window and just start being delivered to
609 * another window.) This function is called when the timer triggers so
610 * we can check if the mouse has left our window. If so, we un-highlight
611 * the hot-tracked tab.
614 TAB_HotTrackTimerProc
616 HWND hwnd, /* handle of window for timer messages */
617 UINT uMsg, /* WM_TIMER message */
618 UINT idEvent, /* timer identifier */
619 DWORD dwTime /* current system time */
622 TAB_INFO* infoPtr = TAB_GetInfoPtr(hwnd);
624 if (infoPtr != NULL && infoPtr->iHotTracked >= 0)
629 ** If we can't get the cursor position, or if the cursor is outside our
630 ** window, we un-highlight the hot-tracked tab. Note that the cursor is
631 ** "outside" even if it is within our bounding rect if another window
632 ** overlaps. Note also that the case where the cursor stayed within our
633 ** window but has moved off the hot-tracked tab will be handled by the
634 ** WM_MOUSEMOVE event.
636 if (!GetCursorPos(&pt) || WindowFromPoint(pt) != hwnd)
638 /* Redraw iHotTracked to look normal */
639 INT iRedraw = infoPtr->iHotTracked;
640 infoPtr->iHotTracked = -1;
641 TAB_DrawLoneItemInterior(hwnd, infoPtr, iRedraw);
643 /* Kill this timer */
644 KillTimer(hwnd, TAB_HOTTRACK_TIMER);
649 /******************************************************************************
652 * If a tab control has the TCS_HOTTRACK style, then the tab under the mouse
653 * should be highlighted. This function determines which tab in a tab control,
654 * if any, is under the mouse and records that information. The caller may
655 * supply output parameters to receive the item number of the tab item which
656 * was highlighted but isn't any longer and of the tab item which is now
657 * highlighted but wasn't previously. The caller can use this information to
658 * selectively redraw those tab items.
660 * If the caller has a mouse position, it can supply it through the pos
661 * parameter. For example, TAB_MouseMove does this. Otherwise, the caller
662 * supplies NULL and this function determines the current mouse position
670 int* out_redrawLeave,
674 TAB_INFO* infoPtr = TAB_GetInfoPtr(hwnd);
679 if (out_redrawLeave != NULL)
680 *out_redrawLeave = -1;
681 if (out_redrawEnter != NULL)
682 *out_redrawEnter = -1;
684 if (GetWindowLongA(hwnd, GWL_STYLE) & TCS_HOTTRACK)
692 ScreenToClient(hwnd, &pt);
700 item = TAB_InternalHitTest(hwnd, infoPtr, pt, &flags);
703 if (item != infoPtr->iHotTracked)
705 if (infoPtr->iHotTracked >= 0)
707 /* Mark currently hot-tracked to be redrawn to look normal */
708 if (out_redrawLeave != NULL)
709 *out_redrawLeave = infoPtr->iHotTracked;
713 /* Kill timer which forces recheck of mouse pos */
714 KillTimer(hwnd, TAB_HOTTRACK_TIMER);
719 /* Start timer so we recheck mouse pos */
720 UINT timerID = SetTimer
724 TAB_HOTTRACK_TIMER_INTERVAL,
725 TAB_HotTrackTimerProc
729 return; /* Hot tracking not available */
732 infoPtr->iHotTracked = item;
736 /* Mark new hot-tracked to be redrawn to look highlighted */
737 if (out_redrawEnter != NULL)
738 *out_redrawEnter = item;
743 /******************************************************************************
746 * Handles the mouse-move event. Updates tooltips. Updates hot-tracking.
749 TAB_MouseMove (HWND hwnd, WPARAM wParam, LPARAM lParam)
754 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
756 if (infoPtr->hwndToolTip)
757 TAB_RelayEvent (infoPtr->hwndToolTip, hwnd,
758 WM_LBUTTONDOWN, wParam, lParam);
760 /* Determine which tab to highlight. Redraw tabs which change highlight
762 TAB_RecalcHotTrack(hwnd, &lParam, &redrawLeave, &redrawEnter);
764 if (redrawLeave != -1)
765 TAB_DrawLoneItemInterior(hwnd, infoPtr, redrawLeave);
766 if (redrawEnter != -1)
767 TAB_DrawLoneItemInterior(hwnd, infoPtr, redrawEnter);
772 /******************************************************************************
775 * Calculates the tab control's display area given the window rectangle or
776 * the window rectangle given the requested display rectangle.
778 static LRESULT TAB_AdjustRect(
783 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
784 DWORD lStyle = GetWindowLongA(hwnd, GWL_STYLE);
786 if(lStyle & TCS_VERTICAL)
788 if (fLarger) /* Go from display rectangle */
790 /* Add the height of the tabs. */
791 if (lStyle & TCS_BOTTOM)
792 prc->right += (infoPtr->tabHeight - 2) * infoPtr->uNumRows + 2;
794 prc->left -= (infoPtr->tabHeight - 2) * infoPtr->uNumRows + 2;
796 /* FIXME: not sure if these InflateRect's need to have different values for TCS_VERTICAL */
797 /* Inflate the rectangle for the padding */
798 InflateRect(prc, DISPLAY_AREA_PADDINGX, DISPLAY_AREA_PADDINGY);
800 /* Inflate for the border */
801 InflateRect(prc, CONTROL_BORDER_SIZEX, CONTROL_BORDER_SIZEX);
803 else /* Go from window rectangle. */
805 /* FIXME: not sure if these InflateRect's need to have different values for TCS_VERTICAL */
806 /* Deflate the rectangle for the border */
807 InflateRect(prc, -CONTROL_BORDER_SIZEX, -CONTROL_BORDER_SIZEX);
809 /* Deflate the rectangle for the padding */
810 InflateRect(prc, -DISPLAY_AREA_PADDINGX, -DISPLAY_AREA_PADDINGY);
812 /* Remove the height of the tabs. */
813 if (lStyle & TCS_BOTTOM)
814 prc->right -= (infoPtr->tabHeight - 2) * infoPtr->uNumRows + 2;
816 prc->left += (infoPtr->tabHeight - 2) * infoPtr->uNumRows + 2;
820 if (fLarger) /* Go from display rectangle */
822 /* Add the height of the tabs. */
823 if (lStyle & TCS_BOTTOM)
824 prc->bottom += (infoPtr->tabHeight - 2) * infoPtr->uNumRows + 2;
826 prc->top -= (infoPtr->tabHeight - 2) * infoPtr->uNumRows + 2;
828 /* Inflate the rectangle for the padding */
829 InflateRect(prc, DISPLAY_AREA_PADDINGX, DISPLAY_AREA_PADDINGY);
831 /* Inflate for the border */
832 InflateRect(prc, CONTROL_BORDER_SIZEX, CONTROL_BORDER_SIZEX);
834 else /* Go from window rectangle. */
836 /* Deflate the rectangle for the border */
837 InflateRect(prc, -CONTROL_BORDER_SIZEX, -CONTROL_BORDER_SIZEX);
839 /* Deflate the rectangle for the padding */
840 InflateRect(prc, -DISPLAY_AREA_PADDINGX, -DISPLAY_AREA_PADDINGY);
842 /* Remove the height of the tabs. */
843 if (lStyle & TCS_BOTTOM)
844 prc->bottom -= (infoPtr->tabHeight - 2) * infoPtr->uNumRows + 2;
846 prc->top += (infoPtr->tabHeight - 2) * infoPtr->uNumRows + 2;
853 /******************************************************************************
856 * This method will handle the notification from the scroll control and
857 * perform the scrolling operation on the tab control.
859 static LRESULT TAB_OnHScroll(
865 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
867 if(nScrollCode == SB_THUMBPOSITION && nPos != infoPtr->leftmostVisible)
869 if(nPos < infoPtr->leftmostVisible)
870 infoPtr->leftmostVisible--;
872 infoPtr->leftmostVisible++;
874 TAB_RecalcHotTrack(hwnd, NULL, NULL, NULL);
875 TAB_InvalidateTabArea(hwnd, infoPtr);
876 SendMessageA(infoPtr->hwndUpDown, UDM_SETPOS, 0,
877 MAKELONG(infoPtr->leftmostVisible, 0));
883 /******************************************************************************
886 * This method will check the current scrolling state and make sure the
887 * scrolling control is displayed (or not).
889 static void TAB_SetupScrolling(
892 const RECT* clientRect)
895 DWORD lStyle = GetWindowLongA(hwnd, GWL_STYLE);
897 if (infoPtr->needsScrolling)
903 * Calculate the position of the scroll control.
905 if(lStyle & TCS_VERTICAL)
907 controlPos.right = clientRect->right;
908 controlPos.left = controlPos.right - 2 * GetSystemMetrics(SM_CXHSCROLL);
910 if (lStyle & TCS_BOTTOM)
912 controlPos.top = clientRect->bottom - infoPtr->tabHeight;
913 controlPos.bottom = controlPos.top + GetSystemMetrics(SM_CYHSCROLL);
917 controlPos.bottom = clientRect->top + infoPtr->tabHeight;
918 controlPos.top = controlPos.bottom - GetSystemMetrics(SM_CYHSCROLL);
923 controlPos.right = clientRect->right;
924 controlPos.left = controlPos.right - 2 * GetSystemMetrics(SM_CXHSCROLL);
926 if (lStyle & TCS_BOTTOM)
928 controlPos.top = clientRect->bottom - infoPtr->tabHeight;
929 controlPos.bottom = controlPos.top + GetSystemMetrics(SM_CYHSCROLL);
933 controlPos.bottom = clientRect->top + infoPtr->tabHeight;
934 controlPos.top = controlPos.bottom - GetSystemMetrics(SM_CYHSCROLL);
939 * If we don't have a scroll control yet, we want to create one.
940 * If we have one, we want to make sure it's positioned properly.
942 if (infoPtr->hwndUpDown==0)
944 infoPtr->hwndUpDown = CreateWindowA("msctls_updown32",
946 WS_VISIBLE | WS_CHILD | UDS_HORZ,
947 controlPos.left, controlPos.top,
948 controlPos.right - controlPos.left,
949 controlPos.bottom - controlPos.top,
957 SetWindowPos(infoPtr->hwndUpDown,
959 controlPos.left, controlPos.top,
960 controlPos.right - controlPos.left,
961 controlPos.bottom - controlPos.top,
962 SWP_SHOWWINDOW | SWP_NOZORDER);
965 /* Now calculate upper limit of the updown control range.
966 * We do this by calculating how many tabs will be offscreen when the
967 * last tab is visible.
969 if(infoPtr->uNumItem)
971 vsize = clientRect->right - (controlPos.right - controlPos.left + 1);
972 maxRange = infoPtr->uNumItem;
973 tabwidth = infoPtr->items[maxRange - 1].rect.right;
975 for(; maxRange > 0; maxRange--)
977 if(tabwidth - infoPtr->items[maxRange - 1].rect.left > vsize)
981 if(maxRange == infoPtr->uNumItem)
987 /* If we once had a scroll control... hide it */
988 if (infoPtr->hwndUpDown!=0)
989 ShowWindow(infoPtr->hwndUpDown, SW_HIDE);
991 if (infoPtr->hwndUpDown)
992 SendMessageA(infoPtr->hwndUpDown, UDM_SETRANGE32, 0, maxRange);
995 /******************************************************************************
998 * This method will calculate the position rectangles of all the items in the
999 * control. The rectangle calculated starts at 0 for the first item in the
1000 * list and ignores scrolling and selection.
1001 * It also uses the current font to determine the height of the tab row and
1002 * it checks if all the tabs fit in the client area of the window. If they
1003 * dont, a scrolling control is added.
1005 static void TAB_SetItemBounds (HWND hwnd)
1007 TAB_INFO* infoPtr = TAB_GetInfoPtr(hwnd);
1008 LONG lStyle = GetWindowLongA(hwnd, GWL_STYLE);
1009 TEXTMETRICA fontMetrics;
1012 INT curItemRowCount;
1013 HFONT hFont, hOldFont;
1022 * We need to get text information so we need a DC and we need to select
1027 hFont = infoPtr->hFont ? infoPtr->hFont : GetStockObject (SYSTEM_FONT);
1028 hOldFont = SelectObject (hdc, hFont);
1031 * We will base the rectangle calculations on the client rectangle
1034 GetClientRect(hwnd, &clientRect);
1036 /* if TCS_VERTICAL then swap the height and width so this code places the
1037 tabs along the top of the rectangle and we can just rotate them after
1038 rather than duplicate all of the below code */
1039 if(lStyle & TCS_VERTICAL)
1041 iTemp = clientRect.bottom;
1042 clientRect.bottom = clientRect.right;
1043 clientRect.right = iTemp;
1046 /* The leftmost item will be "0" aligned */
1048 curItemRowCount = infoPtr->uNumItem ? 1 : 0;
1050 if (!(lStyle & TCS_FIXEDWIDTH) && !((lStyle & TCS_OWNERDRAWFIXED) && infoPtr->fSizeSet) )
1053 int icon_height = 0;
1055 /* Use the current font to determine the height of a tab. */
1056 GetTextMetricsA(hdc, &fontMetrics);
1058 /* Get the icon height */
1060 ImageList_GetIconSize(infoPtr->himl, 0, &icon_height);
1062 /* Take the highest between font or icon */
1063 if (fontMetrics.tmHeight > icon_height)
1064 item_height = fontMetrics.tmHeight;
1066 item_height = icon_height;
1069 * Make sure there is enough space for the letters + icon + growing the
1070 * selected item + extra space for the selected item.
1072 infoPtr->tabHeight = item_height + SELECTED_TAB_OFFSET +
1073 ((lStyle & TCS_BUTTONS) ? 2 : 1) *
1074 VERTICAL_ITEM_PADDING;
1076 TRACE("tabH=%d, tmH=%ld, iconh=%d\n",
1077 infoPtr->tabHeight, fontMetrics.tmHeight, icon_height);
1080 for (curItem = 0; curItem < infoPtr->uNumItem; curItem++)
1082 /* Set the leftmost position of the tab. */
1083 infoPtr->items[curItem].rect.left = curItemLeftPos;
1085 if ( (lStyle & TCS_FIXEDWIDTH) || ((lStyle & TCS_OWNERDRAWFIXED) && infoPtr->fSizeSet))
1087 infoPtr->items[curItem].rect.right = infoPtr->items[curItem].rect.left +
1089 2 * HORIZONTAL_ITEM_PADDING;
1096 /* Calculate how wide the tab is depending on the text it contains */
1097 GetTextExtentPoint32W(hdc, infoPtr->items[curItem].pszText,
1098 lstrlenW(infoPtr->items[curItem].pszText), &size);
1100 /* under Windows, there seems to be a minimum width of 2x the height
1101 * for button style tabs */
1102 if (lStyle & TCS_BUTTONS)
1103 size.cx = max(size.cx, 2 * (infoPtr->tabHeight - 2));
1105 /* Add the icon width */
1108 ImageList_GetIconSize(infoPtr->himl, &icon_width, 0);
1112 infoPtr->items[curItem].rect.right = infoPtr->items[curItem].rect.left +
1113 size.cx + icon_width +
1114 num * HORIZONTAL_ITEM_PADDING;
1118 * Check if this is a multiline tab control and if so
1119 * check to see if we should wrap the tabs
1121 * Because we are going to arange all these tabs evenly
1122 * really we are basically just counting rows at this point
1126 if (((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)) &&
1127 (infoPtr->items[curItem].rect.right > clientRect.right))
1129 infoPtr->items[curItem].rect.right -=
1130 infoPtr->items[curItem].rect.left;
1132 infoPtr->items[curItem].rect.left = 0;
1136 infoPtr->items[curItem].rect.bottom = 0;
1137 infoPtr->items[curItem].rect.top = curItemRowCount - 1;
1139 TRACE("TextSize: %li\n", size.cx);
1140 TRACE("Rect: T %i, L %i, B %i, R %i\n",
1141 infoPtr->items[curItem].rect.top,
1142 infoPtr->items[curItem].rect.left,
1143 infoPtr->items[curItem].rect.bottom,
1144 infoPtr->items[curItem].rect.right);
1147 * The leftmost position of the next item is the rightmost position
1150 if (lStyle & TCS_BUTTONS)
1152 curItemLeftPos = infoPtr->items[curItem].rect.right + 1;
1153 if (lStyle & TCS_FLATBUTTONS)
1154 curItemLeftPos += FLAT_BTN_SPACINGX;
1157 curItemLeftPos = infoPtr->items[curItem].rect.right;
1160 if (!((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)))
1163 * Check if we need a scrolling control.
1165 infoPtr->needsScrolling = (curItemLeftPos + (2 * SELECTED_TAB_OFFSET) >
1168 /* Don't need scrolling, then update infoPtr->leftmostVisible */
1169 if(!infoPtr->needsScrolling)
1170 infoPtr->leftmostVisible = 0;
1172 TAB_SetupScrolling(hwnd, infoPtr, &clientRect);
1175 /* Set the number of rows */
1176 infoPtr->uNumRows = curItemRowCount;
1178 if (((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)) && (infoPtr->uNumItem > 0))
1180 INT widthDiff, remainder;
1181 INT tabPerRow,remTab;
1183 INT iIndexStart=0,iIndexEnd=0, iCount=0;
1186 * Ok windows tries to even out the rows. place the same
1187 * number of tabs in each row. So lets give that a shot
1190 tabPerRow = infoPtr->uNumItem / (infoPtr->uNumRows);
1191 remTab = infoPtr->uNumItem % (infoPtr->uNumRows);
1193 for (iItm=0,iRow=0,iCount=0,curItemLeftPos=0;
1194 iItm<infoPtr->uNumItem;
1197 /* if we have reached the maximum number of tabs on this row */
1198 /* move to the next row, reset our current item left position and */
1199 /* the count of items on this row */
1200 if (iCount >= ((iRow<remTab)?tabPerRow + 1:tabPerRow))
1207 /* normalize the current rect */
1209 /* shift the item to the left side of the clientRect */
1210 infoPtr->items[iItm].rect.right -=
1211 infoPtr->items[iItm].rect.left;
1212 infoPtr->items[iItm].rect.left = 0;
1214 /* shift the item to the right to place it as the next item in this row */
1215 infoPtr->items[iItm].rect.left += curItemLeftPos;
1216 infoPtr->items[iItm].rect.right += curItemLeftPos;
1217 infoPtr->items[iItm].rect.top = iRow;
1218 if (lStyle & TCS_BUTTONS)
1220 curItemLeftPos = infoPtr->items[iItm].rect.right + 1;
1221 if (lStyle & TCS_FLATBUTTONS)
1222 curItemLeftPos += FLAT_BTN_SPACINGX;
1225 curItemLeftPos = infoPtr->items[iItm].rect.right;
1232 while(iIndexStart < infoPtr->uNumItem)
1235 * find the indexs of the row
1237 /* find the first item on the next row */
1238 for (iIndexEnd=iIndexStart;
1239 (iIndexEnd < infoPtr->uNumItem) &&
1240 (infoPtr->items[iIndexEnd].rect.top ==
1241 infoPtr->items[iIndexStart].rect.top) ;
1243 /* intentionally blank */;
1246 * we need to justify these tabs so they fill the whole given
1250 /* find the amount of space remaining on this row */
1251 widthDiff = clientRect.right - (2 * SELECTED_TAB_OFFSET) -
1252 infoPtr->items[iIndexEnd - 1].rect.right;
1254 /* iCount is the number of tab items on this row */
1255 iCount = iIndexEnd - iIndexStart;
1260 remainder = widthDiff % iCount;
1261 widthDiff = widthDiff / iCount;
1262 /* add widthDiff/iCount, or extra space/items on row, to each item on this row */
1263 for (iIndex=iIndexStart,iCount=0; iIndex < iIndexEnd;
1266 infoPtr->items[iIndex].rect.left += iCount * widthDiff;
1267 infoPtr->items[iIndex].rect.right += (iCount + 1) * widthDiff;
1269 infoPtr->items[iIndex - 1].rect.right += remainder;
1271 else /* we have only one item on this row, make it take up the entire row */
1273 infoPtr->items[iIndexStart].rect.left = clientRect.left;
1274 infoPtr->items[iIndexStart].rect.right = clientRect.right - 4;
1278 iIndexStart = iIndexEnd;
1283 /* if TCS_VERTICAL rotate the tabs so they are along the side of the clientRect */
1284 if(lStyle & TCS_VERTICAL)
1287 for(iIndex = 0; iIndex < infoPtr->uNumItem; iIndex++)
1289 rcItem = &(infoPtr->items[iIndex].rect);
1291 rcOriginal = *rcItem;
1293 /* this is rotating the items by 90 degrees around the center of the control */
1294 rcItem->top = (clientRect.right - (rcOriginal.left - clientRect.left)) - (rcOriginal.right - rcOriginal.left);
1295 rcItem->bottom = rcItem->top + (rcOriginal.right - rcOriginal.left);
1296 rcItem->left = rcOriginal.top;
1297 rcItem->right = rcOriginal.bottom;
1301 TAB_EnsureSelectionVisible(hwnd,infoPtr);
1302 TAB_RecalcHotTrack(hwnd, NULL, NULL, NULL);
1305 SelectObject (hdc, hOldFont);
1306 ReleaseDC (hwnd, hdc);
1309 /******************************************************************************
1310 * TAB_DrawItemInterior
1312 * This method is used to draw the interior (text and icon) of a single tab
1313 * into the tab control.
1316 TAB_DrawItemInterior
1324 TAB_INFO* infoPtr = TAB_GetInfoPtr(hwnd);
1325 LONG lStyle = GetWindowLongA(hwnd, GWL_STYLE);
1333 if (drawRect == NULL)
1340 * Get the rectangle for the item.
1342 isVisible = TAB_InternalGetItemRect(hwnd, infoPtr, iItem, &itemRect, &selectedRect);
1347 * Make sure drawRect points to something valid; simplifies code.
1349 drawRect = &localRect;
1352 * This logic copied from the part of TAB_DrawItem which draws
1353 * the tab background. It's important to keep it in sync. I
1354 * would have liked to avoid code duplication, but couldn't figure
1355 * out how without making spaghetti of TAB_DrawItem.
1357 if (lStyle & TCS_BUTTONS)
1359 *drawRect = itemRect;
1360 if (iItem == infoPtr->iSelected)
1368 if (iItem == infoPtr->iSelected)
1369 *drawRect = selectedRect;
1371 *drawRect = itemRect;
1380 htextPen = CreatePen( PS_SOLID, 1, GetSysColor(COLOR_BTNTEXT) );
1381 holdPen = SelectObject(hdc, htextPen);
1383 oldBkMode = SetBkMode(hdc, TRANSPARENT);
1384 SetTextColor(hdc, (iItem == infoPtr->iHotTracked) ?
1385 comctl32_color.clrHighlight : comctl32_color.clrBtnText);
1388 * Deflate the rectangle to acount for the padding
1390 if(lStyle & TCS_VERTICAL)
1391 InflateRect(drawRect, -VERTICAL_ITEM_PADDING, -HORIZONTAL_ITEM_PADDING);
1393 InflateRect(drawRect, -HORIZONTAL_ITEM_PADDING, -VERTICAL_ITEM_PADDING);
1397 * if owner draw, tell the owner to draw
1399 if ((lStyle & TCS_OWNERDRAWFIXED) && GetParent(hwnd))
1405 * get the control id
1407 id = GetWindowLongA( hwnd, GWL_ID );
1410 * put together the DRAWITEMSTRUCT
1412 dis.CtlType = ODT_TAB;
1415 dis.itemAction = ODA_DRAWENTIRE;
1416 if ( iItem == infoPtr->iSelected )
1417 dis.itemState = ODS_SELECTED;
1420 dis.hwndItem = hwnd; /* */
1422 dis.rcItem = *drawRect; /* */
1423 dis.itemData = infoPtr->items[iItem].lParam;
1426 * send the draw message
1428 SendMessageA( GetParent(hwnd), WM_DRAWITEM, (WPARAM)id, (LPARAM)&dis );
1439 HFONT hOldFont = 0; /* stop uninitialized warning */
1441 INT nEscapement = 0; /* stop uninitialized warning */
1442 INT nOrientation = 0; /* stop uninitialized warning */
1445 /* used to center the icon and text in the tab */
1449 /* set rcImage to drawRect, we will use top & left in our ImageList_Draw call */
1450 rcImage = *drawRect;
1455 * Setup for text output
1457 oldBkMode = SetBkMode(hdc, TRANSPARENT);
1458 SetTextColor(hdc, (iItem == infoPtr->iHotTracked) ?
1459 comctl32_color.clrHighlight : comctl32_color.clrBtnText);
1461 /* get the rectangle that the text fits in */
1462 DrawTextW(hdc, infoPtr->items[iItem].pszText, -1,
1463 &rcText, DT_CALCRECT);
1466 * If not owner draw, then do the drawing ourselves.
1470 if (infoPtr->himl && (infoPtr->items[iItem].mask & TCIF_IMAGE))
1472 ImageList_GetIconSize(infoPtr->himl, &cx, &cy);
1474 if(lStyle & TCS_VERTICAL)
1475 center_offset = ((drawRect->bottom - drawRect->top) - (cy + VERTICAL_ITEM_PADDING + (rcText.right - rcText.left))) / 2;
1477 center_offset = ((drawRect->right - drawRect->left) - (cx + HORIZONTAL_ITEM_PADDING + (rcText.right - rcText.left))) / 2;
1479 if((lStyle & TCS_VERTICAL) && (lStyle & TCS_BOTTOM))
1481 /* rcImage.left = drawRect->left; */ /* explicit from above rcImage = *drawRect */
1482 rcImage.top = drawRect->top + center_offset;
1483 rcImage.left = drawRect->right - cx; /* if tab is TCS_VERTICAL and TCS_BOTTOM, the text is drawn from the */
1484 /* right side of the tab, but the image still uses the left as its x position */
1485 /* this keeps the image always drawn off of the same side of the tab */
1486 drawRect->top = rcImage.top + (cx + VERTICAL_ITEM_PADDING);
1488 else if(lStyle & TCS_VERTICAL)
1490 /* rcImage.left = drawRect->left; */ /* explicit from above rcImage = *drawRect */
1491 rcImage.top = drawRect->bottom - cy - center_offset;
1493 drawRect->bottom = rcImage.top - VERTICAL_ITEM_PADDING;
1495 else /* normal style, whether TCS_BOTTOM or not */
1497 rcImage.left = drawRect->left + center_offset;
1498 /* rcImage.top = drawRect->top; */ /* explicit from above rcImage = *drawRect */
1500 drawRect->left = rcImage.left + cx + HORIZONTAL_ITEM_PADDING;
1506 infoPtr->items[iItem].iImage,
1512 } else /* no image, so just shift the drawRect borders around */
1514 if(lStyle & TCS_VERTICAL)
1518 currently the rcText rect is flawed because the rotated font does not
1519 often match the horizontal font. So leave this as 0
1520 ((drawRect->bottom - drawRect->top) - (rcText.right - rcText.left)) / 2;
1522 if(lStyle & TCS_BOTTOM)
1523 drawRect->top+=center_offset;
1525 drawRect->bottom-=center_offset;
1529 center_offset = ((drawRect->right - drawRect->left) - (rcText.right - rcText.left)) / 2;
1530 drawRect->left+=center_offset;
1535 if (lStyle & TCS_RIGHTJUSTIFY)
1536 uHorizAlign = DT_CENTER;
1538 uHorizAlign = DT_LEFT;
1540 if(lStyle & TCS_VERTICAL) /* if we are vertical rotate the text and each character */
1542 if(lStyle & TCS_BOTTOM)
1545 nOrientation = -900;
1554 /* to get a font with the escapement and orientation we are looking for, we need to */
1555 /* call CreateFontIndirectA, which requires us to set the values of the logfont we pass in */
1556 if(lStyle & TCS_VERTICAL)
1558 if (!GetObjectA((infoPtr->hFont) ?
1559 infoPtr->hFont : GetStockObject(SYSTEM_FONT),
1560 sizeof(LOGFONTA),&logfont))
1564 lstrcpyA(logfont.lfFaceName, "Arial");
1565 logfont.lfHeight = -MulDiv(iPointSize, GetDeviceCaps(hdc, LOGPIXELSY),
1567 logfont.lfWeight = FW_NORMAL;
1568 logfont.lfItalic = 0;
1569 logfont.lfUnderline = 0;
1570 logfont.lfStrikeOut = 0;
1573 logfont.lfEscapement = nEscapement;
1574 logfont.lfOrientation = nOrientation;
1575 hFont = CreateFontIndirectA(&logfont);
1576 hOldFont = SelectObject(hdc, hFont);
1579 if (lStyle & TCS_VERTICAL)
1582 (lStyle & TCS_BOTTOM) ? drawRect->right : drawRect->left,
1583 (!(lStyle & TCS_BOTTOM)) ? drawRect->bottom : drawRect->top,
1586 infoPtr->items[iItem].pszText,
1587 lstrlenW(infoPtr->items[iItem].pszText),
1595 infoPtr->items[iItem].pszText,
1596 lstrlenW(infoPtr->items[iItem].pszText),
1598 uHorizAlign | DT_SINGLELINE
1602 /* clean things up */
1603 *drawRect = rcTemp; /* restore drawRect */
1605 if(lStyle & TCS_VERTICAL)
1607 SelectObject(hdc, hOldFont); /* restore the original font */
1609 DeleteObject(hFont);
1616 SetBkMode(hdc, oldBkMode);
1617 SelectObject(hdc, holdPen);
1618 DeleteObject( htextPen );
1621 /******************************************************************************
1624 * This method is used to draw a single tab into the tab control.
1626 static void TAB_DrawItem(
1631 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
1632 LONG lStyle = GetWindowLongA(hwnd, GWL_STYLE);
1636 RECT r, fillRect, r1;
1639 COLORREF bkgnd, corner;
1642 * Get the rectangle for the item.
1644 isVisible = TAB_InternalGetItemRect(hwnd,
1652 /* If you need to see what the control is doing,
1653 * then override these variables. They will change what
1654 * fill colors are used for filling the tabs, and the
1655 * corners when drawing the edge.
1657 bkgnd = comctl32_color.clrBtnFace;
1658 corner = comctl32_color.clrBtnFace;
1660 if (lStyle & TCS_BUTTONS)
1662 HBRUSH hbr = CreateSolidBrush (bkgnd);
1663 BOOL deleteBrush = TRUE;
1665 /* Get item rectangle */
1668 /* Separators between flat buttons */
1669 if (lStyle & TCS_FLATBUTTONS)
1672 r1.right += (FLAT_BTN_SPACINGX -2);
1673 DrawEdge(hdc, &r1, EDGE_ETCHED, BF_RIGHT);
1676 if (iItem == infoPtr->iSelected)
1678 /* Background color */
1679 if (!((lStyle & TCS_OWNERDRAWFIXED) && infoPtr->fSizeSet))
1682 hbr = GetSysColorBrush(COLOR_SCROLLBAR);
1684 SetTextColor(hdc, comctl32_color.clr3dFace);
1685 SetBkColor(hdc, comctl32_color.clr3dHilight);
1687 /* if COLOR_WINDOW happens to be the same as COLOR_3DHILIGHT
1688 * we better use 0x55aa bitmap brush to make scrollbar's background
1689 * look different from the window background.
1691 if (comctl32_color.clr3dHilight == comctl32_color.clrWindow)
1692 hbr = COMCTL32_hPattern55AABrush;
1694 deleteBrush = FALSE;
1697 /* Clear interior */
1698 FillRect(hdc, &r, hbr);
1700 DrawEdge(hdc, &r, EDGE_SUNKEN, BF_SOFT|BF_RECT);
1702 else /* ! selected */
1704 if (!(lStyle & TCS_FLATBUTTONS))
1706 /* Clear interior */
1707 FillRect(hdc, &r, hbr);
1709 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_RECT);
1714 if (deleteBrush) DeleteObject(hbr);
1716 else /* !TCS_BUTTONS */
1718 /* We draw a rectangle of different sizes depending on the selection
1720 if (iItem == infoPtr->iSelected) {
1722 GetClientRect (hwnd, &rect);
1723 clRight = rect.right;
1724 clBottom = rect.bottom;
1731 * Erase the background. (Delay it but setup rectangle.)
1732 * This is necessary when drawing the selected item since it is larger
1733 * than the others, it might overlap with stuff already drawn by the
1738 if(lStyle & TCS_VERTICAL)
1740 /* These are for adjusting the drawing of a Selected tab */
1741 /* The initial values are for the normal case of non-Selected */
1742 int ZZ = 1; /* Do not strech if selected */
1743 if (iItem == infoPtr->iSelected) {
1746 /* if leftmost draw the line longer */
1747 if(selectedRect.top == 0)
1749 /* if rightmost draw the line longer */
1750 if(selectedRect.bottom == clBottom)
1751 fillRect.bottom -= 2;
1754 if (lStyle & TCS_BOTTOM)
1756 /* Adjust both rectangles to match native */
1759 TRACE("<left> item=%d, fill=(%d,%d)-(%d,%d), edge=(%d,%d)-(%d,%d)\n",
1761 fillRect.left,fillRect.top,fillRect.right,fillRect.bottom,
1762 r.left,r.top,r.right,r.bottom);
1764 /* Clear interior */
1765 SetBkColor(hdc, bkgnd);
1766 ExtTextOutA(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
1768 /* Draw rectangular edge around tab */
1769 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_RIGHT|BF_TOP|BF_BOTTOM);
1771 /* Now erase the top corner and draw diagonal edge */
1772 SetBkColor(hdc, corner);
1773 r1.left = r.right - ROUND_CORNER_SIZE - 1;
1776 r1.bottom = r1.top + ROUND_CORNER_SIZE;
1777 ExtTextOutA(hdc, 0, 0, 2, &r1, NULL, 0, 0);
1779 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPLEFT);
1781 /* Now erase the bottom corner and draw diagonal edge */
1782 r1.left = r.right - ROUND_CORNER_SIZE - 1;
1783 r1.bottom = r.bottom;
1785 r1.top = r1.bottom - ROUND_CORNER_SIZE;
1786 ExtTextOutA(hdc, 0, 0, 2, &r1, NULL, 0, 0);
1788 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMLEFT);
1790 if ((iItem == infoPtr->iSelected) && (selectedRect.top == 0)) {
1794 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_TOP);
1800 /* Adjust both rectangles to match native */
1801 fillRect.right += (1-ZZ);
1803 TRACE("<left> item=%d, fill=(%d,%d)-(%d,%d), edge=(%d,%d)-(%d,%d)\n",
1805 fillRect.left,fillRect.top,fillRect.right,fillRect.bottom,
1806 r.left,r.top,r.right,r.bottom);
1808 /* Clear interior */
1809 SetBkColor(hdc, bkgnd);
1810 ExtTextOutA(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
1812 /* Draw rectangular edge around tab */
1813 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_TOP|BF_BOTTOM);
1815 /* Now erase the top corner and draw diagonal edge */
1816 SetBkColor(hdc, corner);
1819 r1.right = r1.left + ROUND_CORNER_SIZE + 1;
1820 r1.bottom = r1.top + ROUND_CORNER_SIZE;
1821 ExtTextOutA(hdc, 0, 0, 2, &r1, NULL, 0, 0);
1823 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPRIGHT);
1825 /* Now erase the bottom corner and draw diagonal edge */
1827 r1.bottom = r.bottom;
1828 r1.right = r1.left + ROUND_CORNER_SIZE + 1;
1829 r1.top = r1.bottom - ROUND_CORNER_SIZE;
1830 ExtTextOutA(hdc, 0, 0, 2, &r1, NULL, 0, 0);
1832 DrawEdge(hdc, &r1, EDGE_SUNKEN, BF_DIAGONAL_ENDTOPLEFT);
1835 else /* ! TCS_VERTICAL */
1837 /* These are for adjusting the drawing of a Selected tab */
1838 /* The initial values are for the normal case of non-Selected */
1839 int ZZ = 1; /* Do not strech if selected */
1840 if (iItem == infoPtr->iSelected) {
1843 /* if leftmost draw the line longer */
1844 if(selectedRect.left == 0)
1846 /* if rightmost draw the line longer */
1847 if(selectedRect.right == clRight)
1848 fillRect.right -= 2;
1851 if (lStyle & TCS_BOTTOM)
1854 /* Adjust both rectangles to match native */
1860 TRACE("<bottom> item=%d, fill=(%d,%d)-(%d,%d), edge=(%d,%d)-(%d,%d)\n",
1862 fillRect.left,fillRect.top,fillRect.right,fillRect.bottom,
1863 r.left,r.top,r.right,r.bottom);
1865 /* Clear interior */
1866 SetBkColor(hdc, bkgnd);
1867 ExtTextOutA(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
1869 /* Draw rectangular edge around tab */
1870 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_BOTTOM|BF_RIGHT);
1872 /* Now erase the righthand corner and draw diagonal edge */
1873 SetBkColor(hdc, corner);
1874 r1.left = r.right - ROUND_CORNER_SIZE;
1875 r1.bottom = r.bottom;
1877 r1.top = r1.bottom - ROUND_CORNER_SIZE - 1;
1878 ExtTextOutA(hdc, 0, 0, 2, &r1, NULL, 0, 0);
1880 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMLEFT);
1882 /* Now erase the lefthand corner and draw diagonal edge */
1884 r1.bottom = r.bottom;
1885 r1.right = r1.left + ROUND_CORNER_SIZE;
1886 r1.top = r1.bottom - ROUND_CORNER_SIZE - 1;
1887 ExtTextOutA(hdc, 0, 0, 2, &r1, NULL, 0, 0);
1889 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPLEFT);
1891 if ((iItem == infoPtr->iSelected) && (selectedRect.left == 0)) {
1895 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_LEFT);
1902 /* Adjust both rectangles to match native */
1903 fillRect.bottom += (1-ZZ);
1905 TRACE("<top> item=%d, fill=(%d,%d)-(%d,%d), edge=(%d,%d)-(%d,%d)\n",
1907 fillRect.left,fillRect.top,fillRect.right,fillRect.bottom,
1908 r.left,r.top,r.right,r.bottom);
1910 /* Clear interior */
1911 SetBkColor(hdc, bkgnd);
1912 ExtTextOutA(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
1914 /* Draw rectangular edge around tab */
1915 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_TOP|BF_RIGHT);
1917 /* Now erase the righthand corner and draw diagonal edge */
1918 SetBkColor(hdc, corner);
1919 r1.left = r.right - ROUND_CORNER_SIZE;
1922 r1.bottom = r1.top + ROUND_CORNER_SIZE + 1;
1923 ExtTextOutA(hdc, 0, 0, 2, &r1, NULL, 0, 0);
1925 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMRIGHT);
1927 /* Now erase the lefthand corner and draw diagonal edge */
1930 r1.right = r1.left + ROUND_CORNER_SIZE;
1931 r1.bottom = r1.top + ROUND_CORNER_SIZE + 1;
1932 ExtTextOutA(hdc, 0, 0, 2, &r1, NULL, 0, 0);
1934 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPRIGHT);
1940 /* This modifies r to be the text rectangle. */
1942 HFONT hOldFont = SelectObject(hdc, infoPtr->hFont);
1943 TAB_DrawItemInterior(hwnd, hdc, iItem, &r);
1944 SelectObject(hdc,hOldFont);
1947 /* Draw the focus rectangle */
1948 if (((lStyle & TCS_FOCUSNEVER) == 0) &&
1949 (GetFocus() == hwnd) &&
1950 (iItem == infoPtr->uFocus) )
1953 InflateRect(&r, -1, -1);
1955 DrawFocusRect(hdc, &r);
1960 /******************************************************************************
1963 * This method is used to draw the raised border around the tab control
1966 static void TAB_DrawBorder (HWND hwnd, HDC hdc)
1968 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
1970 DWORD lStyle = GetWindowLongA(hwnd, GWL_STYLE);
1972 GetClientRect (hwnd, &rect);
1975 * Adjust for the style
1978 if (infoPtr->uNumItem)
1980 if ((lStyle & TCS_BOTTOM) && !(lStyle & TCS_VERTICAL))
1982 rect.bottom -= (infoPtr->tabHeight - 2) * infoPtr->uNumRows + 3;
1984 else if((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
1986 rect.right -= (infoPtr->tabHeight - 2) * infoPtr->uNumRows + 2;
1988 else if(lStyle & TCS_VERTICAL)
1990 rect.left += (infoPtr->tabHeight - 2) * infoPtr->uNumRows + 2;
1992 else /* not TCS_VERTICAL and not TCS_BOTTOM */
1994 rect.top += (infoPtr->tabHeight - 2) * infoPtr->uNumRows + 2;
1998 TRACE("border=(%d,%d)-(%d,%d)\n",
1999 rect.left, rect.top, rect.right, rect.bottom);
2001 DrawEdge(hdc, &rect, EDGE_RAISED, BF_SOFT|BF_RECT);
2004 /******************************************************************************
2007 * This method repaints the tab control..
2009 static void TAB_Refresh (HWND hwnd, HDC hdc)
2011 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2015 if (!infoPtr->DoRedraw)
2018 hOldFont = SelectObject (hdc, infoPtr->hFont);
2020 if (GetWindowLongA(hwnd, GWL_STYLE) & TCS_BUTTONS)
2022 for (i = 0; i < infoPtr->uNumItem; i++)
2023 TAB_DrawItem (hwnd, hdc, i);
2027 /* Draw all the non selected item first */
2028 for (i = 0; i < infoPtr->uNumItem; i++)
2030 if (i != infoPtr->iSelected)
2031 TAB_DrawItem (hwnd, hdc, i);
2034 /* Now, draw the border, draw it before the selected item
2035 * since the selected item overwrites part of the border. */
2036 TAB_DrawBorder (hwnd, hdc);
2038 /* Then, draw the selected item */
2039 TAB_DrawItem (hwnd, hdc, infoPtr->iSelected);
2041 /* If we haven't set the current focus yet, set it now.
2042 * Only happens when we first paint the tab controls */
2043 if (infoPtr->uFocus == -1)
2044 TAB_SetCurFocus(hwnd, infoPtr->iSelected);
2047 SelectObject (hdc, hOldFont);
2051 TAB_GetRowCount (HWND hwnd )
2053 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2055 return infoPtr->uNumRows;
2059 TAB_SetRedraw (HWND hwnd, WPARAM wParam)
2061 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2063 infoPtr->DoRedraw=(BOOL) wParam;
2067 static LRESULT TAB_EraseBackground(
2074 HBRUSH brush = CreateSolidBrush(comctl32_color.clrBtnFace);
2076 hdc = givenDC ? givenDC : GetDC(hwnd);
2078 GetClientRect(hwnd, &clientRect);
2080 FillRect(hdc, &clientRect, brush);
2083 ReleaseDC(hwnd, hdc);
2085 DeleteObject(brush);
2090 /******************************************************************************
2091 * TAB_EnsureSelectionVisible
2093 * This method will make sure that the current selection is completely
2094 * visible by scrolling until it is.
2096 static void TAB_EnsureSelectionVisible(
2100 INT iSelected = infoPtr->iSelected;
2101 LONG lStyle = GetWindowLongA(hwnd, GWL_STYLE);
2102 INT iOrigLeftmostVisible = infoPtr->leftmostVisible;
2104 /* set the items row to the bottommost row or topmost row depending on
2106 if ((infoPtr->uNumRows > 1) && !(lStyle & TCS_BUTTONS))
2111 if(lStyle & TCS_VERTICAL)
2112 newselected = infoPtr->items[iSelected].rect.left;
2114 newselected = infoPtr->items[iSelected].rect.top;
2116 /* the target row is always (number of rows - 1)
2117 as row 0 is furthest from the clientRect */
2118 iTargetRow = infoPtr->uNumRows - 1;
2120 if (newselected != iTargetRow)
2123 if(lStyle & TCS_VERTICAL)
2125 for (i=0; i < infoPtr->uNumItem; i++)
2127 /* move everything in the row of the selected item to the iTargetRow */
2128 if (infoPtr->items[i].rect.left == newselected )
2129 infoPtr->items[i].rect.left = iTargetRow;
2132 if (infoPtr->items[i].rect.left > newselected)
2133 infoPtr->items[i].rect.left-=1;
2139 for (i=0; i < infoPtr->uNumItem; i++)
2141 if (infoPtr->items[i].rect.top == newselected )
2142 infoPtr->items[i].rect.top = iTargetRow;
2145 if (infoPtr->items[i].rect.top > newselected)
2146 infoPtr->items[i].rect.top-=1;
2150 TAB_RecalcHotTrack(hwnd, NULL, NULL, NULL);
2155 * Do the trivial cases first.
2157 if ( (!infoPtr->needsScrolling) ||
2158 (infoPtr->hwndUpDown==0) || (lStyle & TCS_VERTICAL))
2161 if (infoPtr->leftmostVisible >= iSelected)
2163 infoPtr->leftmostVisible = iSelected;
2170 /* Calculate the part of the client area that is visible */
2171 GetClientRect(hwnd, &r);
2174 GetClientRect(infoPtr->hwndUpDown, &r);
2177 if ((infoPtr->items[iSelected].rect.right -
2178 infoPtr->items[iSelected].rect.left) >= width )
2180 /* Special case: width of selected item is greater than visible
2183 infoPtr->leftmostVisible = iSelected;
2187 for (i = infoPtr->leftmostVisible; i < infoPtr->uNumItem; i++)
2189 if ((infoPtr->items[iSelected].rect.right -
2190 infoPtr->items[i].rect.left) < width)
2193 infoPtr->leftmostVisible = i;
2197 if (infoPtr->leftmostVisible != iOrigLeftmostVisible)
2198 TAB_RecalcHotTrack(hwnd, NULL, NULL, NULL);
2200 SendMessageA(infoPtr->hwndUpDown, UDM_SETPOS, 0,
2201 MAKELONG(infoPtr->leftmostVisible, 0));
2204 /******************************************************************************
2205 * TAB_InvalidateTabArea
2207 * This method will invalidate the portion of the control that contains the
2208 * tabs. It is called when the state of the control changes and needs
2211 static void TAB_InvalidateTabArea(
2216 DWORD lStyle = GetWindowLongA(hwnd, GWL_STYLE);
2217 INT lastRow = infoPtr->uNumRows - 1;
2219 if (lastRow < 0) return;
2221 GetClientRect(hwnd, &clientRect);
2223 if ((lStyle & TCS_BOTTOM) && !(lStyle & TCS_VERTICAL))
2225 clientRect.top = clientRect.bottom -
2226 infoPtr->tabHeight -
2227 lastRow * (infoPtr->tabHeight - 2) -
2228 ((lStyle & TCS_BUTTONS) ? lastRow * BUTTON_SPACINGY : 0) - 3;
2230 else if((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
2232 clientRect.left = clientRect.right - infoPtr->tabHeight -
2233 lastRow * (infoPtr->tabHeight - 2) -
2234 ((lStyle & TCS_BUTTONS) ? lastRow * BUTTON_SPACINGY : 0) - 2;
2236 else if(lStyle & TCS_VERTICAL)
2238 clientRect.right = clientRect.left + infoPtr->tabHeight +
2239 lastRow * (infoPtr->tabHeight - 2) -
2240 ((lStyle & TCS_BUTTONS) ? lastRow * BUTTON_SPACINGY : 0) + 2;
2245 clientRect.bottom = clientRect.top + infoPtr->tabHeight +
2246 lastRow * (infoPtr->tabHeight - 2) +
2247 ((lStyle & TCS_BUTTONS) ? lastRow * BUTTON_SPACINGY : 0) + 2;
2250 TRACE("invalidate (%d,%d)-(%d,%d)\n",
2251 clientRect.left,clientRect.top,
2252 clientRect.right,clientRect.bottom);
2253 InvalidateRect(hwnd, &clientRect, TRUE);
2257 TAB_Paint (HWND hwnd, WPARAM wParam)
2262 hdc = wParam== 0 ? BeginPaint (hwnd, &ps) : (HDC)wParam;
2264 TRACE("erase %d, rect=(%d,%d)-(%d,%d)\n",
2266 ps.rcPaint.left,ps.rcPaint.top,ps.rcPaint.right,ps.rcPaint.bottom);
2269 TAB_EraseBackground (hwnd, hdc);
2271 TAB_Refresh (hwnd, hdc);
2274 EndPaint (hwnd, &ps);
2280 TAB_InsertItemA (HWND hwnd, WPARAM wParam, LPARAM lParam)
2282 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2287 GetClientRect (hwnd, &rect);
2288 TRACE("Rect: %x T %i, L %i, B %i, R %i\n", hwnd,
2289 rect.top, rect.left, rect.bottom, rect.right);
2291 pti = (TCITEMA *)lParam;
2292 iItem = (INT)wParam;
2294 if (iItem < 0) return -1;
2295 if (iItem > infoPtr->uNumItem)
2296 iItem = infoPtr->uNumItem;
2298 if (infoPtr->uNumItem == 0) {
2299 infoPtr->items = COMCTL32_Alloc (sizeof (TAB_ITEM));
2300 infoPtr->uNumItem++;
2301 infoPtr->iSelected = 0;
2304 TAB_ITEM *oldItems = infoPtr->items;
2306 infoPtr->uNumItem++;
2307 infoPtr->items = COMCTL32_Alloc (sizeof (TAB_ITEM) * infoPtr->uNumItem);
2309 /* pre insert copy */
2311 memcpy (&infoPtr->items[0], &oldItems[0],
2312 iItem * sizeof(TAB_ITEM));
2315 /* post insert copy */
2316 if (iItem < infoPtr->uNumItem - 1) {
2317 memcpy (&infoPtr->items[iItem+1], &oldItems[iItem],
2318 (infoPtr->uNumItem - iItem - 1) * sizeof(TAB_ITEM));
2322 if (iItem <= infoPtr->iSelected)
2323 infoPtr->iSelected++;
2325 COMCTL32_Free (oldItems);
2328 infoPtr->items[iItem].mask = pti->mask;
2329 if (pti->mask & TCIF_TEXT)
2330 Str_SetPtrAtoW (&infoPtr->items[iItem].pszText, pti->pszText);
2332 if (pti->mask & TCIF_IMAGE)
2333 infoPtr->items[iItem].iImage = pti->iImage;
2335 if (pti->mask & TCIF_PARAM)
2336 infoPtr->items[iItem].lParam = pti->lParam;
2338 TAB_SetItemBounds(hwnd);
2339 if (infoPtr->uNumItem > 1)
2340 TAB_InvalidateTabArea(hwnd, infoPtr);
2342 InvalidateRect(hwnd, NULL, TRUE);
2344 TRACE("[%04x]: added item %d %s\n",
2345 hwnd, iItem, debugstr_w(infoPtr->items[iItem].pszText));
2352 TAB_InsertItemW (HWND hwnd, WPARAM wParam, LPARAM lParam)
2354 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2359 GetClientRect (hwnd, &rect);
2360 TRACE("Rect: %x T %i, L %i, B %i, R %i\n", hwnd,
2361 rect.top, rect.left, rect.bottom, rect.right);
2363 pti = (TCITEMW *)lParam;
2364 iItem = (INT)wParam;
2366 if (iItem < 0) return -1;
2367 if (iItem > infoPtr->uNumItem)
2368 iItem = infoPtr->uNumItem;
2370 if (infoPtr->uNumItem == 0) {
2371 infoPtr->items = COMCTL32_Alloc (sizeof (TAB_ITEM));
2372 infoPtr->uNumItem++;
2373 infoPtr->iSelected = 0;
2376 TAB_ITEM *oldItems = infoPtr->items;
2378 infoPtr->uNumItem++;
2379 infoPtr->items = COMCTL32_Alloc (sizeof (TAB_ITEM) * infoPtr->uNumItem);
2381 /* pre insert copy */
2383 memcpy (&infoPtr->items[0], &oldItems[0],
2384 iItem * sizeof(TAB_ITEM));
2387 /* post insert copy */
2388 if (iItem < infoPtr->uNumItem - 1) {
2389 memcpy (&infoPtr->items[iItem+1], &oldItems[iItem],
2390 (infoPtr->uNumItem - iItem - 1) * sizeof(TAB_ITEM));
2394 if (iItem <= infoPtr->iSelected)
2395 infoPtr->iSelected++;
2397 COMCTL32_Free (oldItems);
2400 infoPtr->items[iItem].mask = pti->mask;
2401 if (pti->mask & TCIF_TEXT)
2402 Str_SetPtrW (&infoPtr->items[iItem].pszText, pti->pszText);
2404 if (pti->mask & TCIF_IMAGE)
2405 infoPtr->items[iItem].iImage = pti->iImage;
2407 if (pti->mask & TCIF_PARAM)
2408 infoPtr->items[iItem].lParam = pti->lParam;
2410 TAB_SetItemBounds(hwnd);
2411 if (infoPtr->uNumItem > 1)
2412 TAB_InvalidateTabArea(hwnd, infoPtr);
2414 InvalidateRect(hwnd, NULL, TRUE);
2416 TRACE("[%04x]: added item %d %s\n",
2417 hwnd, iItem, debugstr_w(infoPtr->items[iItem].pszText));
2424 TAB_SetItemSize (HWND hwnd, WPARAM wParam, LPARAM lParam)
2426 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2427 LONG lStyle = GetWindowLongA(hwnd, GWL_STYLE);
2431 if ((lStyle & TCS_FIXEDWIDTH) || (lStyle & TCS_OWNERDRAWFIXED))
2433 lResult = MAKELONG(infoPtr->tabWidth, infoPtr->tabHeight);
2434 infoPtr->tabWidth = (INT)LOWORD(lParam);
2435 infoPtr->tabHeight = (INT)HIWORD(lParam);
2436 TRACE("was h=%d,w=%d, now h=%d,w=%d\n",
2437 HIWORD(lResult), LOWORD(lResult),
2438 infoPtr->tabHeight, infoPtr->tabWidth);
2440 infoPtr->fSizeSet = TRUE;
2446 TAB_SetItemA (HWND hwnd, WPARAM wParam, LPARAM lParam)
2448 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2453 iItem = (INT)wParam;
2454 tabItem = (LPTCITEMA)lParam;
2456 TRACE("%d %p\n", iItem, tabItem);
2457 if ((iItem<0) || (iItem>=infoPtr->uNumItem)) return FALSE;
2459 wineItem = &infoPtr->items[iItem];
2461 if (tabItem->mask & TCIF_IMAGE)
2462 wineItem->iImage = tabItem->iImage;
2464 if (tabItem->mask & TCIF_PARAM)
2465 wineItem->lParam = tabItem->lParam;
2467 if (tabItem->mask & TCIF_RTLREADING)
2468 FIXME("TCIF_RTLREADING\n");
2470 if (tabItem->mask & TCIF_STATE)
2471 wineItem->dwState = tabItem->dwState;
2473 if (tabItem->mask & TCIF_TEXT)
2474 Str_SetPtrAtoW(&wineItem->pszText, tabItem->pszText);
2476 /* Update and repaint tabs */
2477 TAB_SetItemBounds(hwnd);
2478 TAB_InvalidateTabArea(hwnd,infoPtr);
2485 TAB_SetItemW (HWND hwnd, WPARAM wParam, LPARAM lParam)
2487 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2492 iItem = (INT)wParam;
2493 tabItem = (LPTCITEMW)lParam;
2495 TRACE("%d %p\n", iItem, tabItem);
2496 if ((iItem<0) || (iItem>=infoPtr->uNumItem)) return FALSE;
2498 wineItem = &infoPtr->items[iItem];
2500 if (tabItem->mask & TCIF_IMAGE)
2501 wineItem->iImage = tabItem->iImage;
2503 if (tabItem->mask & TCIF_PARAM)
2504 wineItem->lParam = tabItem->lParam;
2506 if (tabItem->mask & TCIF_RTLREADING)
2507 FIXME("TCIF_RTLREADING\n");
2509 if (tabItem->mask & TCIF_STATE)
2510 wineItem->dwState = tabItem->dwState;
2512 if (tabItem->mask & TCIF_TEXT)
2513 Str_SetPtrW(&wineItem->pszText, tabItem->pszText);
2515 /* Update and repaint tabs */
2516 TAB_SetItemBounds(hwnd);
2517 TAB_InvalidateTabArea(hwnd,infoPtr);
2524 TAB_GetItemCount (HWND hwnd, WPARAM wParam, LPARAM lParam)
2526 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2528 return infoPtr->uNumItem;
2533 TAB_GetItemA (HWND hwnd, WPARAM wParam, LPARAM lParam)
2535 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2540 iItem = (INT)wParam;
2541 tabItem = (LPTCITEMA)lParam;
2543 if ((iItem<0) || (iItem>=infoPtr->uNumItem))
2546 wineItem = &infoPtr->items[iItem];
2548 if (tabItem->mask & TCIF_IMAGE)
2549 tabItem->iImage = wineItem->iImage;
2551 if (tabItem->mask & TCIF_PARAM)
2552 tabItem->lParam = wineItem->lParam;
2554 if (tabItem->mask & TCIF_RTLREADING)
2555 FIXME("TCIF_RTLREADING\n");
2557 if (tabItem->mask & TCIF_STATE)
2558 tabItem->dwState = wineItem->dwState;
2560 if (tabItem->mask & TCIF_TEXT)
2561 Str_GetPtrWtoA (wineItem->pszText, tabItem->pszText, tabItem->cchTextMax);
2568 TAB_GetItemW (HWND hwnd, WPARAM wParam, LPARAM lParam)
2570 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2575 iItem = (INT)wParam;
2576 tabItem = (LPTCITEMW)lParam;
2578 if ((iItem<0) || (iItem>=infoPtr->uNumItem))
2581 wineItem=& infoPtr->items[iItem];
2583 if (tabItem->mask & TCIF_IMAGE)
2584 tabItem->iImage = wineItem->iImage;
2586 if (tabItem->mask & TCIF_PARAM)
2587 tabItem->lParam = wineItem->lParam;
2589 if (tabItem->mask & TCIF_RTLREADING)
2590 FIXME("TCIF_RTLREADING\n");
2592 if (tabItem->mask & TCIF_STATE)
2593 tabItem->dwState = wineItem->dwState;
2595 if (tabItem->mask & TCIF_TEXT)
2596 Str_GetPtrW (wineItem->pszText, tabItem->pszText, tabItem->cchTextMax);
2603 TAB_DeleteItem (HWND hwnd, WPARAM wParam, LPARAM lParam)
2605 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2606 INT iItem = (INT) wParam;
2607 BOOL bResult = FALSE;
2609 if ((iItem >= 0) && (iItem < infoPtr->uNumItem))
2611 TAB_ITEM *oldItems = infoPtr->items;
2613 infoPtr->uNumItem--;
2614 infoPtr->items = COMCTL32_Alloc(sizeof (TAB_ITEM) * infoPtr->uNumItem);
2617 memcpy(&infoPtr->items[0], &oldItems[0], iItem * sizeof(TAB_ITEM));
2619 if (iItem < infoPtr->uNumItem)
2620 memcpy(&infoPtr->items[iItem], &oldItems[iItem + 1],
2621 (infoPtr->uNumItem - iItem) * sizeof(TAB_ITEM));
2623 COMCTL32_Free(oldItems);
2625 /* Readjust the selected index */
2626 if ((iItem == infoPtr->iSelected) && (iItem > 0))
2627 infoPtr->iSelected--;
2629 if (iItem < infoPtr->iSelected)
2630 infoPtr->iSelected--;
2632 if (infoPtr->uNumItem == 0)
2633 infoPtr->iSelected = -1;
2635 /* Reposition and repaint tabs */
2636 TAB_SetItemBounds(hwnd);
2637 TAB_InvalidateTabArea(hwnd,infoPtr);
2646 TAB_DeleteAllItems (HWND hwnd, WPARAM wParam, LPARAM lParam)
2648 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2650 COMCTL32_Free (infoPtr->items);
2651 infoPtr->uNumItem = 0;
2652 infoPtr->iSelected = -1;
2653 if (infoPtr->iHotTracked >= 0)
2654 KillTimer(hwnd, TAB_HOTTRACK_TIMER);
2655 infoPtr->iHotTracked = -1;
2657 TAB_SetItemBounds(hwnd);
2658 TAB_InvalidateTabArea(hwnd,infoPtr);
2664 TAB_GetFont (HWND hwnd, WPARAM wParam, LPARAM lParam)
2666 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2669 return (LRESULT)infoPtr->hFont;
2673 TAB_SetFont (HWND hwnd, WPARAM wParam, LPARAM lParam)
2676 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2678 TRACE("%x %lx\n",wParam, lParam);
2680 infoPtr->hFont = (HFONT)wParam;
2682 TAB_SetItemBounds(hwnd);
2684 TAB_InvalidateTabArea(hwnd, infoPtr);
2691 TAB_GetImageList (HWND hwnd, WPARAM wParam, LPARAM lParam)
2693 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2696 return (LRESULT)infoPtr->himl;
2700 TAB_SetImageList (HWND hwnd, WPARAM wParam, LPARAM lParam)
2702 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2703 HIMAGELIST himlPrev;
2706 himlPrev = infoPtr->himl;
2707 infoPtr->himl= (HIMAGELIST)lParam;
2708 return (LRESULT)himlPrev;
2712 TAB_GetUnicodeFormat (HWND hwnd)
2714 TAB_INFO *infoPtr = TAB_GetInfoPtr (hwnd);
2715 return infoPtr->bUnicode;
2719 TAB_SetUnicodeFormat (HWND hwnd, WPARAM wParam)
2721 TAB_INFO *infoPtr = TAB_GetInfoPtr (hwnd);
2722 BOOL bTemp = infoPtr->bUnicode;
2724 infoPtr->bUnicode = (BOOL)wParam;
2730 TAB_Size (HWND hwnd, WPARAM wParam, LPARAM lParam)
2733 /* I'm not really sure what the following code was meant to do.
2734 This is what it is doing:
2735 When WM_SIZE is sent with SIZE_RESTORED, the control
2736 gets positioned in the top left corner.
2740 UINT uPosFlags,cx,cy;
2744 parent = GetParent (hwnd);
2745 GetClientRect(parent, &parent_rect);
2748 if (GetWindowLongA(hwnd, GWL_STYLE) & CCS_NORESIZE)
2749 uPosFlags |= (SWP_NOSIZE | SWP_NOMOVE);
2751 SetWindowPos (hwnd, 0, parent_rect.left, parent_rect.top,
2752 cx, cy, uPosFlags | SWP_NOZORDER);
2754 FIXME("WM_SIZE flag %x %lx not handled\n", wParam, lParam);
2757 /* Recompute the size/position of the tabs. */
2758 TAB_SetItemBounds (hwnd);
2760 /* Force a repaint of the control. */
2761 InvalidateRect(hwnd, NULL, TRUE);
2768 TAB_Create (HWND hwnd, WPARAM wParam, LPARAM lParam)
2771 TEXTMETRICA fontMetrics;
2776 infoPtr = (TAB_INFO *)COMCTL32_Alloc (sizeof(TAB_INFO));
2778 SetWindowLongA(hwnd, 0, (DWORD)infoPtr);
2780 infoPtr->uNumItem = 0;
2781 infoPtr->uNumRows = 0;
2784 infoPtr->hcurArrow = LoadCursorA (0, IDC_ARROWA);
2785 infoPtr->iSelected = -1;
2786 infoPtr->iHotTracked = -1;
2787 infoPtr->uFocus = -1;
2788 infoPtr->hwndToolTip = 0;
2789 infoPtr->DoRedraw = TRUE;
2790 infoPtr->needsScrolling = FALSE;
2791 infoPtr->hwndUpDown = 0;
2792 infoPtr->leftmostVisible = 0;
2793 infoPtr->fSizeSet = FALSE;
2794 infoPtr->bUnicode = IsWindowUnicode (hwnd);
2796 TRACE("Created tab control, hwnd [%04x]\n", hwnd);
2798 /* The tab control always has the WS_CLIPSIBLINGS style. Even
2799 if you don't specify it in CreateWindow. This is necessary in
2800 order for paint to work correctly. This follows windows behaviour. */
2801 dwStyle = GetWindowLongA(hwnd, GWL_STYLE);
2802 SetWindowLongA(hwnd, GWL_STYLE, dwStyle|WS_CLIPSIBLINGS);
2804 if (dwStyle & TCS_TOOLTIPS) {
2805 /* Create tooltip control */
2806 infoPtr->hwndToolTip =
2807 CreateWindowExA (0, TOOLTIPS_CLASSA, NULL, 0,
2808 CW_USEDEFAULT, CW_USEDEFAULT,
2809 CW_USEDEFAULT, CW_USEDEFAULT,
2812 /* Send NM_TOOLTIPSCREATED notification */
2813 if (infoPtr->hwndToolTip) {
2814 NMTOOLTIPSCREATED nmttc;
2816 nmttc.hdr.hwndFrom = hwnd;
2817 nmttc.hdr.idFrom = GetWindowLongA(hwnd, GWL_ID);
2818 nmttc.hdr.code = NM_TOOLTIPSCREATED;
2819 nmttc.hwndToolTips = infoPtr->hwndToolTip;
2821 SendMessageA (GetParent (hwnd), WM_NOTIFY,
2822 (WPARAM)GetWindowLongA(hwnd, GWL_ID), (LPARAM)&nmttc);
2827 * We need to get text information so we need a DC and we need to select
2831 hOldFont = SelectObject (hdc, GetStockObject (SYSTEM_FONT));
2833 /* Use the system font to determine the initial height of a tab. */
2834 GetTextMetricsA(hdc, &fontMetrics);
2837 * Make sure there is enough space for the letters + growing the
2838 * selected item + extra space for the selected item.
2840 infoPtr->tabHeight = fontMetrics.tmHeight + SELECTED_TAB_OFFSET +
2841 ((dwStyle & TCS_BUTTONS) ? 2 : 1) *
2842 VERTICAL_ITEM_PADDING;
2844 /* Initialize the width of a tab. */
2845 infoPtr->tabWidth = DEFAULT_TAB_WIDTH;
2847 TRACE("tabH=%d, tabW=%d\n", infoPtr->tabHeight, infoPtr->tabWidth);
2849 SelectObject (hdc, hOldFont);
2850 ReleaseDC(hwnd, hdc);
2856 TAB_Destroy (HWND hwnd, WPARAM wParam, LPARAM lParam)
2858 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2864 if (infoPtr->items) {
2865 for (iItem = 0; iItem < infoPtr->uNumItem; iItem++) {
2866 if (infoPtr->items[iItem].pszText)
2867 COMCTL32_Free (infoPtr->items[iItem].pszText);
2869 COMCTL32_Free (infoPtr->items);
2872 if (infoPtr->hwndToolTip)
2873 DestroyWindow (infoPtr->hwndToolTip);
2875 if (infoPtr->hwndUpDown)
2876 DestroyWindow(infoPtr->hwndUpDown);
2878 if (infoPtr->iHotTracked >= 0)
2879 KillTimer(hwnd, TAB_HOTTRACK_TIMER);
2881 COMCTL32_Free (infoPtr);
2882 SetWindowLongA(hwnd, 0, 0);
2886 static LRESULT WINAPI
2887 TAB_WindowProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
2890 TRACE("hwnd=%x msg=%x wParam=%x lParam=%lx\n", hwnd, uMsg, wParam, lParam);
2891 if (!TAB_GetInfoPtr(hwnd) && (uMsg != WM_CREATE))
2892 return DefWindowProcA (hwnd, uMsg, wParam, lParam);
2896 case TCM_GETIMAGELIST:
2897 return TAB_GetImageList (hwnd, wParam, lParam);
2899 case TCM_SETIMAGELIST:
2900 return TAB_SetImageList (hwnd, wParam, lParam);
2902 case TCM_GETITEMCOUNT:
2903 return TAB_GetItemCount (hwnd, wParam, lParam);
2906 return TAB_GetItemA (hwnd, wParam, lParam);
2909 return TAB_GetItemW (hwnd, wParam, lParam);
2912 return TAB_SetItemA (hwnd, wParam, lParam);
2915 return TAB_SetItemW (hwnd, wParam, lParam);
2917 case TCM_DELETEITEM:
2918 return TAB_DeleteItem (hwnd, wParam, lParam);
2920 case TCM_DELETEALLITEMS:
2921 return TAB_DeleteAllItems (hwnd, wParam, lParam);
2923 case TCM_GETITEMRECT:
2924 return TAB_GetItemRect (hwnd, wParam, lParam);
2927 return TAB_GetCurSel (hwnd);
2930 return TAB_HitTest (hwnd, wParam, lParam);
2933 return TAB_SetCurSel (hwnd, wParam);
2935 case TCM_INSERTITEMA:
2936 return TAB_InsertItemA (hwnd, wParam, lParam);
2938 case TCM_INSERTITEMW:
2939 return TAB_InsertItemW (hwnd, wParam, lParam);
2941 case TCM_SETITEMEXTRA:
2942 FIXME("Unimplemented msg TCM_SETITEMEXTRA\n");
2945 case TCM_ADJUSTRECT:
2946 return TAB_AdjustRect (hwnd, (BOOL)wParam, (LPRECT)lParam);
2948 case TCM_SETITEMSIZE:
2949 return TAB_SetItemSize (hwnd, wParam, lParam);
2951 case TCM_REMOVEIMAGE:
2952 FIXME("Unimplemented msg TCM_REMOVEIMAGE\n");
2955 case TCM_SETPADDING:
2956 FIXME("Unimplemented msg TCM_SETPADDING\n");
2959 case TCM_GETROWCOUNT:
2960 return TAB_GetRowCount(hwnd);
2962 case TCM_GETUNICODEFORMAT:
2963 return TAB_GetUnicodeFormat (hwnd);
2965 case TCM_SETUNICODEFORMAT:
2966 return TAB_SetUnicodeFormat (hwnd, wParam);
2968 case TCM_HIGHLIGHTITEM:
2969 FIXME("Unimplemented msg TCM_HIGHLIGHTITEM\n");
2972 case TCM_GETTOOLTIPS:
2973 return TAB_GetToolTips (hwnd, wParam, lParam);
2975 case TCM_SETTOOLTIPS:
2976 return TAB_SetToolTips (hwnd, wParam, lParam);
2978 case TCM_GETCURFOCUS:
2979 return TAB_GetCurFocus (hwnd);
2981 case TCM_SETCURFOCUS:
2982 return TAB_SetCurFocus (hwnd, wParam);
2984 case TCM_SETMINTABWIDTH:
2985 FIXME("Unimplemented msg TCM_SETMINTABWIDTH\n");
2988 case TCM_DESELECTALL:
2989 FIXME("Unimplemented msg TCM_DESELECTALL\n");
2992 case TCM_GETEXTENDEDSTYLE:
2993 FIXME("Unimplemented msg TCM_GETEXTENDEDSTYLE\n");
2996 case TCM_SETEXTENDEDSTYLE:
2997 FIXME("Unimplemented msg TCM_SETEXTENDEDSTYLE\n");
3001 return TAB_GetFont (hwnd, wParam, lParam);
3004 return TAB_SetFont (hwnd, wParam, lParam);
3007 return TAB_Create (hwnd, wParam, lParam);
3010 return TAB_Destroy (hwnd, wParam, lParam);
3013 return DLGC_WANTARROWS | DLGC_WANTCHARS;
3015 case WM_LBUTTONDOWN:
3016 return TAB_LButtonDown (hwnd, wParam, lParam);
3019 return TAB_LButtonUp (hwnd, wParam, lParam);
3022 return SendMessageA(GetParent(hwnd), WM_NOTIFY, wParam, lParam);
3024 case WM_RBUTTONDOWN:
3025 return TAB_RButtonDown (hwnd, wParam, lParam);
3028 return TAB_MouseMove (hwnd, wParam, lParam);
3031 return TAB_EraseBackground (hwnd, (HDC)wParam);
3034 return TAB_Paint (hwnd, wParam);
3037 return TAB_Size (hwnd, wParam, lParam);
3040 return TAB_SetRedraw (hwnd, wParam);
3043 return TAB_OnHScroll(hwnd, (int)LOWORD(wParam), (int)HIWORD(wParam), (HWND)lParam);
3045 case WM_STYLECHANGED:
3046 TAB_SetItemBounds (hwnd);
3047 InvalidateRect(hwnd, NULL, TRUE);
3050 case WM_SYSCOLORCHANGE:
3051 COMCTL32_RefreshSysColors();
3056 return TAB_FocusChanging(hwnd, uMsg, wParam, lParam);
3059 return TAB_KeyUp(hwnd, wParam);
3061 return TAB_NCHitTest(hwnd, lParam);
3064 if (uMsg >= WM_USER)
3065 WARN("unknown msg %04x wp=%08x lp=%08lx\n",
3066 uMsg, wParam, lParam);
3067 return DefWindowProcA(hwnd, uMsg, wParam, lParam);
3079 ZeroMemory (&wndClass, sizeof(WNDCLASSA));
3080 wndClass.style = CS_GLOBALCLASS | CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
3081 wndClass.lpfnWndProc = (WNDPROC)TAB_WindowProc;
3082 wndClass.cbClsExtra = 0;
3083 wndClass.cbWndExtra = sizeof(TAB_INFO *);
3084 wndClass.hCursor = LoadCursorA (0, IDC_ARROWA);
3085 wndClass.hbrBackground = (HBRUSH)NULL;
3086 wndClass.lpszClassName = WC_TABCONTROLA;
3088 RegisterClassA (&wndClass);
3093 TAB_Unregister (void)
3095 UnregisterClassA (WC_TABCONTROLA, (HINSTANCE)NULL);