4 * Copyright 1998 Anders Carlsson
5 * Copyright 1999 Alex Priem <alexp@sci.kun.nl>
6 * Copyright 1999 Francis Beaudet
19 #include "debugtools.h"
21 DEFAULT_DEBUG_CHANNEL(tab)
23 /******************************************************************************
24 * Positioning constants
26 #define SELECTED_TAB_OFFSET 2
27 #define HORIZONTAL_ITEM_PADDING 5
28 #define VERTICAL_ITEM_PADDING 3
29 #define ROUND_CORNER_SIZE 2
30 #define FOCUS_RECT_HOFFSET 2
31 #define FOCUS_RECT_VOFFSET 1
32 #define DISPLAY_AREA_PADDINGX 5
33 #define DISPLAY_AREA_PADDINGY 5
34 #define CONTROL_BORDER_SIZEX 2
35 #define CONTROL_BORDER_SIZEY 2
36 #define BUTTON_SPACINGX 10
37 #define DEFAULT_TAB_WIDTH 96
39 #define TAB_GetInfoPtr(hwnd) ((TAB_INFO *)GetWindowLongA(hwnd,0))
41 /******************************************************************************
44 static void TAB_Refresh (HWND hwnd, HDC hdc);
45 static void TAB_InvalidateTabArea(HWND hwnd, TAB_INFO* infoPtr);
46 static void TAB_EnsureSelectionVisible(HWND hwnd, TAB_INFO* infoPtr);
49 TAB_SendSimpleNotify (HWND hwnd, UINT code)
53 nmhdr.hwndFrom = hwnd;
54 nmhdr.idFrom = GetWindowLongA(hwnd, GWL_ID);
57 return (BOOL) SendMessageA (GetParent (hwnd), WM_NOTIFY,
58 (WPARAM) nmhdr.idFrom, (LPARAM) &nmhdr);
63 TAB_RelayEvent (HWND hwndTip, HWND hwndMsg, UINT uMsg,
64 WPARAM wParam, LPARAM lParam)
72 msg.time = GetMessageTime ();
73 msg.pt.x = LOWORD(GetMessagePos ());
74 msg.pt.y = HIWORD(GetMessagePos ());
76 SendMessageA (hwndTip, TTM_RELAYEVENT, 0, (LPARAM)&msg);
82 TAB_GetCurSel (HWND hwnd)
84 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
86 return infoPtr->iSelected;
90 TAB_GetCurFocus (HWND hwnd)
92 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
94 return infoPtr->uFocus;
98 TAB_GetToolTips (HWND hwnd, WPARAM wParam, LPARAM lParam)
100 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
102 if (infoPtr == NULL) return 0;
103 return infoPtr->hwndToolTip;
108 TAB_SetCurSel (HWND hwnd,WPARAM wParam)
110 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
111 INT iItem=(INT) wParam;
115 if ((iItem >= 0) && (iItem < infoPtr->uNumItem)) {
116 prevItem=infoPtr->iSelected;
117 infoPtr->iSelected=iItem;
123 TAB_SetCurFocus (HWND hwnd,WPARAM wParam)
125 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
126 INT iItem=(INT) wParam;
128 if ((iItem < 0) || (iItem >= infoPtr->uNumItem)) return 0;
130 infoPtr->uFocus=iItem;
131 if (GetWindowLongA(hwnd, GWL_STYLE) & TCS_BUTTONS) {
132 FIXME("Should set input focus\n");
134 if (infoPtr->iSelected != iItem) {
135 if (TAB_SendSimpleNotify(hwnd, TCN_SELCHANGING)!=TRUE) {
136 infoPtr->iSelected = iItem;
137 TAB_SendSimpleNotify(hwnd, TCN_SELCHANGE);
139 TAB_EnsureSelectionVisible(hwnd, infoPtr);
140 TAB_InvalidateTabArea(hwnd, infoPtr);
148 TAB_SetToolTips (HWND hwnd, WPARAM wParam, LPARAM lParam)
150 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
152 if (infoPtr == NULL) return 0;
153 infoPtr->hwndToolTip = (HWND)wParam;
157 /******************************************************************************
158 * TAB_InternalGetItemRect
160 * This method will calculate the rectangle representing a given tab item in
161 * client coordinates. This method takes scrolling into account.
163 * This method returns TRUE if the item is visible in the window and FALSE
164 * if it is completely outside the client area.
166 static BOOL TAB_InternalGetItemRect(
176 * Perform a sanity check and a trivial visibility check.
178 if ( (infoPtr->uNumItem <=0) ||
179 (itemIndex >= infoPtr->uNumItem) ||
180 (itemIndex < infoPtr->leftmostVisible) )
184 * Avoid special cases in this procedure by assigning the "out"
185 * parameters if the caller didn't supply them
188 itemRect = &tmpItemRect;
191 * Retrieve the unmodified item rect.
193 *itemRect = infoPtr->items[itemIndex].rect;
196 * "scroll" it to make sure the item at the very left of the
197 * tab control is the leftmost visible tab.
200 -infoPtr->items[infoPtr->leftmostVisible].rect.left,
204 * Move the rectangle so the first item is slightly offset from
205 * the left of the tab control.
213 * Now, calculate the position of the item as if it were selected.
215 if (selectedRect!=NULL)
217 CopyRect(selectedRect, itemRect);
220 * The rectangle of a selected item is a bit wider.
222 InflateRect(selectedRect, SELECTED_TAB_OFFSET, 0);
225 * If it also a bit higher.
227 if (GetWindowLongA(hwnd, GWL_STYLE) & TCS_BOTTOM)
229 selectedRect->top -=2; /* the border is thicker on the bottom */
230 selectedRect->bottom +=SELECTED_TAB_OFFSET;
234 selectedRect->top -=SELECTED_TAB_OFFSET;
235 selectedRect->bottom+=1;
242 static BOOL TAB_GetItemRect(HWND hwnd, WPARAM wParam, LPARAM lParam)
244 return TAB_InternalGetItemRect(hwnd, TAB_GetInfoPtr(hwnd), (INT)wParam,
245 (LPRECT)lParam, (LPRECT)NULL);
248 /******************************************************************************
251 * This method is called to handle keyboard input
253 static LRESULT TAB_KeyUp(
257 TAB_INFO* infoPtr = TAB_GetInfoPtr(hwnd);
263 newItem = infoPtr->uFocus-1;
266 newItem = infoPtr->uFocus+1;
271 * If we changed to a valid item, change the selection
273 if ( (newItem >= 0) &&
274 (newItem < infoPtr->uNumItem) &&
275 (infoPtr->uFocus != newItem) )
277 if (!TAB_SendSimpleNotify(hwnd, TCN_SELCHANGING))
279 infoPtr->iSelected = newItem;
280 infoPtr->uFocus = newItem;
281 TAB_SendSimpleNotify(hwnd, TCN_SELCHANGE);
283 TAB_EnsureSelectionVisible(hwnd, infoPtr);
284 TAB_InvalidateTabArea(hwnd, infoPtr);
291 /******************************************************************************
294 * This method is called whenever the focus goes in or out of this control
295 * it is used to update the visual state of the control.
297 static LRESULT TAB_FocusChanging(
303 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
308 * Get the rectangle for the item.
310 isVisible = TAB_InternalGetItemRect(hwnd,
317 * If the rectangle is not completely invisible, invalidate that
318 * portion of the window.
322 InvalidateRect(hwnd, &selectedRect, TRUE);
326 * Don't otherwise disturb normal behavior.
328 return DefWindowProcA (hwnd, uMsg, wParam, lParam);
331 static HWND TAB_InternalHitTest (
341 for (iCount = 0; iCount < infoPtr->uNumItem; iCount++)
343 TAB_InternalGetItemRect(hwnd,
349 if (PtInRect (&rect, pt))
351 *flags = TCHT_ONITEM;
361 TAB_HitTest (HWND hwnd, WPARAM wParam, LPARAM lParam)
363 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
364 LPTCHITTESTINFO lptest=(LPTCHITTESTINFO) lParam;
366 return TAB_InternalHitTest (hwnd, infoPtr,lptest->pt,&lptest->flags);
371 TAB_LButtonDown (HWND hwnd, WPARAM wParam, LPARAM lParam)
373 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
377 if (infoPtr->hwndToolTip)
378 TAB_RelayEvent (infoPtr->hwndToolTip, hwnd,
379 WM_LBUTTONDOWN, wParam, lParam);
381 if (GetWindowLongA(hwnd, GWL_STYLE) & TCS_FOCUSONBUTTONDOWN ) {
385 if (infoPtr->hwndToolTip)
386 TAB_RelayEvent (infoPtr->hwndToolTip, hwnd,
387 WM_LBUTTONDOWN, wParam, lParam);
389 pt.x = (INT)LOWORD(lParam);
390 pt.y = (INT)HIWORD(lParam);
392 newItem=TAB_InternalHitTest (hwnd, infoPtr,pt,&dummy);
394 TRACE("On Tab, item %d\n", newItem);
396 if ( (newItem!=-1) &&
397 (infoPtr->iSelected != newItem) )
399 if (TAB_SendSimpleNotify(hwnd, TCN_SELCHANGING)!=TRUE)
401 infoPtr->iSelected = newItem;
402 infoPtr->uFocus = newItem;
403 TAB_SendSimpleNotify(hwnd, TCN_SELCHANGE);
405 TAB_EnsureSelectionVisible(hwnd, infoPtr);
407 TAB_InvalidateTabArea(hwnd, infoPtr);
414 TAB_LButtonUp (HWND hwnd, WPARAM wParam, LPARAM lParam)
416 TAB_SendSimpleNotify(hwnd, NM_CLICK);
422 TAB_RButtonDown (HWND hwnd, WPARAM wParam, LPARAM lParam)
424 TAB_SendSimpleNotify(hwnd, NM_RCLICK);
429 TAB_MouseMove (HWND hwnd, WPARAM wParam, LPARAM lParam)
431 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
433 if (infoPtr->hwndToolTip)
434 TAB_RelayEvent (infoPtr->hwndToolTip, hwnd,
435 WM_LBUTTONDOWN, wParam, lParam);
439 /******************************************************************************
442 * Calculates the tab control's display area given the windows rectangle or
443 * the window rectangle given the requested display rectangle.
445 static LRESULT TAB_AdjustRect(
450 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
455 * Go from display rectangle
459 * Add the height of the tabs.
461 if (GetWindowLongA(hwnd, GWL_STYLE) & TCS_BOTTOM)
462 prc->bottom += infoPtr->tabHeight;
464 prc->top -= infoPtr->tabHeight;
467 * Inflate the rectangle for the padding
469 InflateRect(prc, DISPLAY_AREA_PADDINGX, DISPLAY_AREA_PADDINGY);
472 * Inflate for the border
474 InflateRect(prc, CONTROL_BORDER_SIZEX, CONTROL_BORDER_SIZEX);
479 * Go from window rectangle.
483 * Deflate the rectangle for the border
485 InflateRect(prc, -CONTROL_BORDER_SIZEX, -CONTROL_BORDER_SIZEX);
488 * Deflate the rectangle for the padding
490 InflateRect(prc, -DISPLAY_AREA_PADDINGX, -DISPLAY_AREA_PADDINGY);
493 * Remove the height of the tabs.
495 if (GetWindowLongA(hwnd, GWL_STYLE) & TCS_BOTTOM)
496 prc->bottom -= infoPtr->tabHeight;
498 prc->top += infoPtr->tabHeight;
505 /******************************************************************************
508 * This method will handle the notification from the scroll control and
509 * perform the scrolling operation on the tab control.
511 static LRESULT TAB_OnHScroll(
517 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
519 if (nScrollCode == SB_LINELEFT)
521 if (infoPtr->leftmostVisible>0)
523 infoPtr->leftmostVisible--;
525 TAB_InvalidateTabArea(hwnd, infoPtr);
528 else if (nScrollCode == SB_LINERIGHT)
530 if (infoPtr->leftmostVisible< (infoPtr->uNumItem-1))
532 infoPtr->leftmostVisible++;
534 TAB_InvalidateTabArea(hwnd, infoPtr);
541 /******************************************************************************
544 * This method will check the current scrolling state and make sure the
545 * scrolling control is displayed (or not).
547 static void TAB_SetupScrolling(
550 const RECT* clientRect)
552 if (infoPtr->needsScrolling)
557 * Calculate the position of the scroll control.
559 controlPos.right = clientRect->right;
560 controlPos.left = controlPos.right - 2*GetSystemMetrics(SM_CXHSCROLL);
562 if (GetWindowLongA(hwnd, GWL_STYLE) & TCS_BOTTOM)
564 controlPos.top = clientRect->bottom - infoPtr->tabHeight;
565 controlPos.bottom = controlPos.top + GetSystemMetrics(SM_CYHSCROLL);
569 controlPos.bottom = clientRect->top + infoPtr->tabHeight;
570 controlPos.top = controlPos.bottom - GetSystemMetrics(SM_CYHSCROLL);
574 * If we don't have a scroll control yet, we want to create one.
575 * If we have one, we want to make sure it's positioned right.
577 if (infoPtr->hwndUpDown==0)
580 * I use a scrollbar since it seems to be more stable than the Updown
583 infoPtr->hwndUpDown = CreateWindowA("ScrollBar",
585 WS_VISIBLE | WS_CHILD | WS_OVERLAPPED | SBS_HORZ,
586 controlPos.left, controlPos.top,
587 controlPos.right - controlPos.left,
588 controlPos.bottom - controlPos.top,
596 SetWindowPos(infoPtr->hwndUpDown,
598 controlPos.left, controlPos.top,
599 controlPos.right - controlPos.left,
600 controlPos.bottom - controlPos.top,
601 SWP_SHOWWINDOW | SWP_NOZORDER);
607 * If we once had a scroll control... hide it.
609 if (infoPtr->hwndUpDown!=0)
611 ShowWindow(infoPtr->hwndUpDown, SW_HIDE);
616 /******************************************************************************
619 * This method will calculate the position rectangles of all the items in the
620 * control. The rectangle calculated starts at 0 for the first item in the
621 * list and ignores scrolling and selection.
622 * It also uses the current font to determine the height of the tab row and
623 * it checks if all the tabs fit in the client area of the window. If they
624 * dont, a scrolling control is added.
626 static void TAB_SetItemBounds (HWND hwnd)
628 TAB_INFO* infoPtr = TAB_GetInfoPtr(hwnd);
629 LONG lStyle = GetWindowLongA(hwnd, GWL_STYLE);
630 TEXTMETRICA fontMetrics;
633 HFONT hFont, hOldFont;
639 * We need to get text information so we need a DC and we need to select
644 hFont = infoPtr->hFont ? infoPtr->hFont : GetStockObject (SYSTEM_FONT);
645 hOldFont = SelectObject (hdc, hFont);
648 * We will base the rectangle calculations on the client rectangle
651 GetClientRect(hwnd, &clientRect);
654 * The leftmost item will be "0" aligned
658 if (!((lStyle & TCS_FIXEDWIDTH) || (lStyle & TCS_OWNERDRAWFIXED)))
664 * Use the current font to determine the height of a tab.
666 GetTextMetricsA(hdc, &fontMetrics);
669 * Get the icon height
672 ImageList_GetIconSize(infoPtr->himl, 0, &icon_height);
675 * Take the highest between font or icon
677 if (fontMetrics.tmHeight > icon_height)
678 item_height = fontMetrics.tmHeight;
680 item_height = icon_height;
683 * Make sure there is enough space for the letters + icon + growing the
684 * selected item + extra space for the selected item.
686 infoPtr->tabHeight = item_height + 2*VERTICAL_ITEM_PADDING +
690 for (curItem = 0; curItem < infoPtr->uNumItem; curItem++)
693 * Calculate the vertical position of the tab
695 if (lStyle & TCS_BOTTOM)
697 infoPtr->items[curItem].rect.bottom = clientRect.bottom -
699 infoPtr->items[curItem].rect.top = clientRect.bottom -
704 infoPtr->items[curItem].rect.top = clientRect.top +
706 infoPtr->items[curItem].rect.bottom = clientRect.top +
711 * Set the leftmost position of the tab.
713 infoPtr->items[curItem].rect.left = curItemLeftPos;
715 if ((lStyle & TCS_FIXEDWIDTH) || (lStyle & TCS_OWNERDRAWFIXED))
717 infoPtr->items[curItem].rect.right = infoPtr->items[curItem].rect.left +
719 2*HORIZONTAL_ITEM_PADDING;
727 * Calculate how wide the tab is depending on the text it contains
729 GetTextExtentPoint32A(hdc, infoPtr->items[curItem].pszText,
730 lstrlenA(infoPtr->items[curItem].pszText), &size);
737 ImageList_GetIconSize(infoPtr->himl, &icon_width, 0);
741 infoPtr->items[curItem].rect.right = infoPtr->items[curItem].rect.left +
742 size.cx + icon_width +
743 num*HORIZONTAL_ITEM_PADDING;
746 TRACE("TextSize: %i\n ", size.cx);
747 TRACE("Rect: T %i, L %i, B %i, R %i\n",
748 infoPtr->items[curItem].rect.top,
749 infoPtr->items[curItem].rect.left,
750 infoPtr->items[curItem].rect.bottom,
751 infoPtr->items[curItem].rect.right);
754 * The leftmost position of the next item is the rightmost position
757 if (lStyle & TCS_BUTTONS)
758 curItemLeftPos = infoPtr->items[curItem].rect.right + BUTTON_SPACINGX;
760 curItemLeftPos = infoPtr->items[curItem].rect.right;
764 * Check if we need a scrolling control.
766 infoPtr->needsScrolling = (curItemLeftPos + (2*SELECTED_TAB_OFFSET) >
769 TAB_SetupScrolling(hwnd, infoPtr, &clientRect);
774 SelectObject (hdc, hOldFont);
775 ReleaseDC (hwnd, hdc);
778 /******************************************************************************
781 * This method is used to draw a single tab into the tab control.
783 static void TAB_DrawItem(
788 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
789 LONG lStyle = GetWindowLongA(hwnd, GWL_STYLE);
796 * Get the rectangle for the item.
798 isVisible = TAB_InternalGetItemRect(hwnd,
806 HBRUSH hbr = CreateSolidBrush (GetSysColor(COLOR_BTNFACE));
807 HPEN hwPen = GetSysColorPen (COLOR_3DHILIGHT);
808 HPEN hbPen = GetSysColorPen (COLOR_BTNSHADOW);
809 HPEN hsdPen = GetSysColorPen (COLOR_BTNTEXT);
810 HPEN hfocusPen = CreatePen(PS_DOT, 1, GetSysColor(COLOR_BTNTEXT));
815 if (lStyle & TCS_BUTTONS)
818 * Get item rectangle.
822 holdPen = SelectObject (hdc, hwPen);
824 if (iItem == infoPtr->iSelected)
829 if (!(lStyle & TCS_OWNERDRAWFIXED))
832 hbr = CreateSolidBrush(GetSysColor(COLOR_3DHILIGHT));
836 * Erase the background.
838 FillRect(hdc, &r, hbr);
842 * The rectangles calculated exclude the right and bottom
843 * borders of the rectangle. To simply the following code, those
844 * borders are shaved-off beforehand.
850 MoveToEx (hdc, r.left, r.bottom, NULL);
851 LineTo (hdc, r.right, r.bottom);
852 LineTo (hdc, r.right, r.top);
855 SelectObject(hdc, hbPen);
856 LineTo (hdc, r.left, r.top);
857 LineTo (hdc, r.left, r.bottom);
862 * Erase the background.
864 FillRect(hdc, &r, hbr);
867 MoveToEx (hdc, r.left, r.bottom, NULL);
868 LineTo (hdc, r.left, r.top);
869 LineTo (hdc, r.right, r.top);
872 SelectObject(hdc, hbPen);
873 LineTo (hdc, r.right, r.bottom);
874 LineTo (hdc, r.left, r.bottom);
883 hbr = CreateSolidBrush(GetSysColor(COLOR_BTNFACE));
886 * We draw a rectangle of different sizes depending on the selection
889 if (iItem == infoPtr->iSelected)
895 * Erase the background.
896 * This is necessary when drawing the selected item since it is larger
897 * than the others, it might overlap with stuff already drawn by the
900 FillRect(hdc, &r, hbr);
904 * The rectangles calculated exclude the right and bottom
905 * borders of the rectangle. To simply the following code, those
906 * borders are shaved-off beforehand.
911 holdPen = SelectObject (hdc, hwPen);
913 if (lStyle & TCS_BOTTOM)
916 MoveToEx (hdc, r.left, r.top, NULL);
917 LineTo (hdc, r.left, r.bottom - ROUND_CORNER_SIZE);
918 LineTo (hdc, r.left + ROUND_CORNER_SIZE, r.bottom);
921 SelectObject(hdc, hbPen);
922 LineTo (hdc, r.right - ROUND_CORNER_SIZE, r.bottom);
923 LineTo (hdc, r.right, r.bottom - ROUND_CORNER_SIZE);
924 LineTo (hdc, r.right, r.top);
929 MoveToEx (hdc, r.left, r.bottom, NULL);
930 LineTo (hdc, r.left, r.top + ROUND_CORNER_SIZE);
931 LineTo (hdc, r.left + ROUND_CORNER_SIZE, r.top);
932 LineTo (hdc, r.right - ROUND_CORNER_SIZE, r.top);
935 SelectObject(hdc, hbPen);
936 LineTo (hdc, r.right, r.top + ROUND_CORNER_SIZE);
937 LineTo (hdc, r.right, r.bottom);
944 SelectObject(hdc, hsdPen);
946 oldBkMode = SetBkMode(hdc, TRANSPARENT);
947 SetTextColor (hdc, COLOR_BTNTEXT);
950 * Deflate the rectangle to acount for the padding
952 InflateRect(&r, -HORIZONTAL_ITEM_PADDING, -VERTICAL_ITEM_PADDING);
959 ImageList_Draw (infoPtr->himl, iItem, hdc,
960 r.left, r.top+1, ILD_NORMAL);
961 ImageList_GetIconSize (infoPtr->himl, &cx, &cy);
962 r.left+=(cx + HORIZONTAL_ITEM_PADDING);
969 infoPtr->items[iItem].pszText,
970 lstrlenA(infoPtr->items[iItem].pszText),
972 DT_LEFT|DT_SINGLELINE|DT_VCENTER);
975 * Draw the focus rectangle
977 if (((lStyle & TCS_FOCUSNEVER) == 0) &&
978 (GetFocus() == hwnd) &&
979 (iItem == infoPtr->uFocus) )
981 InflateRect(&r, FOCUS_RECT_HOFFSET, FOCUS_RECT_VOFFSET);
983 SelectObject(hdc, hfocusPen);
985 MoveToEx (hdc, r.left, r.top, NULL);
986 LineTo (hdc, r.right-1, r.top);
987 LineTo (hdc, r.right-1, r.bottom -1);
988 LineTo (hdc, r.left, r.bottom -1);
989 LineTo (hdc, r.left, r.top);
995 SetBkMode(hdc, oldBkMode);
996 SelectObject(hdc, holdPen);
997 DeleteObject(hfocusPen);
1002 /******************************************************************************
1005 * This method is used to draw the raised border around the tab control
1008 static void TAB_DrawBorder (HWND hwnd, HDC hdc)
1010 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
1012 HPEN hwPen = GetSysColorPen (COLOR_3DHILIGHT);
1013 HPEN hbPen = GetSysColorPen (COLOR_3DDKSHADOW);
1014 HPEN hShade = GetSysColorPen (COLOR_BTNSHADOW);
1017 GetClientRect (hwnd, &rect);
1020 * Adjust for the style
1022 if (GetWindowLongA(hwnd, GWL_STYLE) & TCS_BOTTOM)
1024 rect.bottom -= infoPtr->tabHeight;
1028 rect.top += infoPtr->tabHeight;
1032 * Shave-off the right and bottom margins (exluded in the
1039 htmPen = SelectObject (hdc, hwPen);
1041 MoveToEx (hdc, rect.left, rect.bottom, NULL);
1042 LineTo (hdc, rect.left, rect.top);
1043 LineTo (hdc, rect.right, rect.top);
1046 SelectObject (hdc, hbPen);
1047 LineTo (hdc, rect.right, rect.bottom );
1048 LineTo (hdc, rect.left, rect.bottom);
1051 SelectObject (hdc, hShade );
1052 MoveToEx (hdc, rect.right-1, rect.top, NULL);
1053 LineTo (hdc, rect.right-1, rect.bottom-1);
1054 LineTo (hdc, rect.left, rect.bottom-1);
1056 SelectObject(hdc, htmPen);
1059 /******************************************************************************
1062 * This method repaints the tab control..
1064 static void TAB_Refresh (HWND hwnd, HDC hdc)
1066 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
1070 if (!infoPtr->DoRedraw)
1073 hOldFont = SelectObject (hdc, infoPtr->hFont);
1075 if (GetWindowLongA(hwnd, GWL_STYLE) & TCS_BUTTONS)
1077 for (i = 0; i < infoPtr->uNumItem; i++)
1079 TAB_DrawItem (hwnd, hdc, i);
1085 * Draw all the non selected item first.
1087 for (i = 0; i < infoPtr->uNumItem; i++)
1089 if (i != infoPtr->iSelected)
1090 TAB_DrawItem (hwnd, hdc, i);
1094 * Now, draw the border, draw it before the selected item
1095 * since the selected item overwrites part of the border.
1097 TAB_DrawBorder (hwnd, hdc);
1100 * Then, draw the selected item
1102 TAB_DrawItem (hwnd, hdc, infoPtr->iSelected);
1105 SelectObject (hdc, hOldFont);
1109 TAB_SetRedraw (HWND hwnd, WPARAM wParam)
1111 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
1113 infoPtr->DoRedraw=(BOOL) wParam;
1117 static LRESULT TAB_EraseBackground(
1124 HBRUSH brush = CreateSolidBrush(GetSysColor(COLOR_BTNFACE));
1126 hdc = givenDC ? givenDC : GetDC(hwnd);
1128 GetClientRect(hwnd, &clientRect);
1130 FillRect(hdc, &clientRect, brush);
1133 ReleaseDC(hwnd, hdc);
1135 DeleteObject(brush);
1140 /******************************************************************************
1141 * TAB_EnsureSelectionVisible
1143 * This method will make sure that the current selection is completely
1144 * visible by scrolling until it is.
1146 static void TAB_EnsureSelectionVisible(
1156 * Do the trivial cases first.
1158 if ( (!infoPtr->needsScrolling) ||
1159 (infoPtr->hwndUpDown==0) )
1162 if (infoPtr->leftmostVisible > infoPtr->iSelected)
1164 infoPtr->leftmostVisible = infoPtr->iSelected;
1169 * Calculate the part of the client area that is visible.
1171 GetClientRect(hwnd, &visibleRect);
1172 GetClientRect(infoPtr->hwndUpDown, &scrollerRect);
1173 visibleRect.right -= scrollerRect.right;
1176 * Get the rectangle for the item
1178 isVisible = TAB_InternalGetItemRect(hwnd,
1185 * If this function can't say it's completely invisible, maybe it
1186 * is partially visible. Let's check.
1193 pt1.x = selectedRect.left;
1194 pt1.y = selectedRect.top;
1195 pt2.x = selectedRect.right - 1;
1196 pt2.y = selectedRect.bottom - 1;
1198 isVisible = PtInRect(&visibleRect, pt1) && PtInRect(&visibleRect, pt2);
1201 while ( (infoPtr->leftmostVisible < infoPtr->iSelected) &&
1204 infoPtr->leftmostVisible++;
1207 * Get the rectangle for the item
1209 isVisible = TAB_InternalGetItemRect(hwnd,
1216 * If this function can't say it's completely invisible, maybe it
1217 * is partially visible. Let's check.
1224 pt1.x = selectedRect.left;
1225 pt1.y = selectedRect.top;
1226 pt2.x = selectedRect.right - 1;
1227 pt2.y = selectedRect.bottom - 1;
1229 isVisible = PtInRect(&visibleRect, pt1) && PtInRect(&visibleRect, pt2);
1234 /******************************************************************************
1235 * TAB_InvalidateTabArea
1237 * This method will invalidate the portion of the control that contains the
1238 * tabs. It is called when the state of the control changes and needs
1241 static void TAB_InvalidateTabArea(
1247 GetClientRect(hwnd, &clientRect);
1249 if (GetWindowLongA(hwnd, GWL_STYLE) & TCS_BOTTOM)
1251 clientRect.top = clientRect.bottom - (infoPtr->tabHeight + 1);
1255 clientRect.bottom = clientRect.top + (infoPtr->tabHeight + 1);
1258 InvalidateRect(hwnd, &clientRect, TRUE);
1262 TAB_Paint (HWND hwnd, WPARAM wParam)
1267 hdc = wParam== 0 ? BeginPaint (hwnd, &ps) : (HDC)wParam;
1268 TAB_Refresh (hwnd, hdc);
1271 EndPaint (hwnd, &ps);
1277 TAB_InsertItem (HWND hwnd, WPARAM wParam, LPARAM lParam)
1279 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
1284 GetClientRect (hwnd, &rect);
1285 TRACE("Rect: %x T %i, L %i, B %i, R %i\n", hwnd,
1286 rect.top, rect.left, rect.bottom, rect.right);
1288 pti = (TCITEMA *)lParam;
1289 iItem = (INT)wParam;
1291 if (iItem < 0) return -1;
1292 if (iItem > infoPtr->uNumItem)
1293 iItem = infoPtr->uNumItem;
1295 if (infoPtr->uNumItem == 0) {
1296 infoPtr->items = COMCTL32_Alloc (sizeof (TAB_ITEM));
1297 infoPtr->uNumItem++;
1298 infoPtr->iSelected = 0;
1301 TAB_ITEM *oldItems = infoPtr->items;
1303 infoPtr->uNumItem++;
1304 infoPtr->items = COMCTL32_Alloc (sizeof (TAB_ITEM) * infoPtr->uNumItem);
1306 /* pre insert copy */
1308 memcpy (&infoPtr->items[0], &oldItems[0],
1309 iItem * sizeof(TAB_ITEM));
1312 /* post insert copy */
1313 if (iItem < infoPtr->uNumItem - 1) {
1314 memcpy (&infoPtr->items[iItem+1], &oldItems[iItem],
1315 (infoPtr->uNumItem - iItem - 1) * sizeof(TAB_ITEM));
1319 if (iItem <= infoPtr->iSelected)
1320 infoPtr->iSelected++;
1322 COMCTL32_Free (oldItems);
1325 infoPtr->items[iItem].mask = pti->mask;
1326 if (pti->mask & TCIF_TEXT) {
1327 len = lstrlenA (pti->pszText);
1328 infoPtr->items[iItem].pszText = COMCTL32_Alloc (len+1);
1329 lstrcpyA (infoPtr->items[iItem].pszText, pti->pszText);
1330 infoPtr->items[iItem].cchTextMax = pti->cchTextMax;
1333 if (pti->mask & TCIF_IMAGE)
1334 infoPtr->items[iItem].iImage = pti->iImage;
1336 if (pti->mask & TCIF_PARAM)
1337 infoPtr->items[iItem].lParam = pti->lParam;
1339 TAB_InvalidateTabArea(hwnd, infoPtr);
1341 TRACE("[%04x]: added item %d '%s'\n",
1342 hwnd, iItem, infoPtr->items[iItem].pszText);
1344 TAB_SetItemBounds(hwnd);
1349 TAB_SetItemSize (HWND hwnd, WPARAM wParam, LPARAM lParam)
1351 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
1352 LONG lStyle = GetWindowLongA(hwnd, GWL_STYLE);
1355 if ((lStyle & TCS_FIXEDWIDTH) || (lStyle & TCS_OWNERDRAWFIXED))
1357 lResult = MAKELONG(infoPtr->tabWidth, infoPtr->tabHeight);
1358 infoPtr->tabWidth = (INT)LOWORD(lParam);
1359 infoPtr->tabHeight = (INT)HIWORD(lParam);
1366 TAB_SetItemA (HWND hwnd, WPARAM wParam, LPARAM lParam)
1368 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
1374 tabItem=(LPTCITEMA ) lParam;
1375 TRACE("%d %p\n",iItem, tabItem);
1376 if ((iItem<0) || (iItem>=infoPtr->uNumItem)) return FALSE;
1378 wineItem=& infoPtr->items[iItem];
1380 if (tabItem->mask & TCIF_IMAGE)
1381 wineItem->iImage=tabItem->iImage;
1383 if (tabItem->mask & TCIF_PARAM)
1384 wineItem->lParam=tabItem->lParam;
1386 if (tabItem->mask & TCIF_RTLREADING)
1387 FIXME("TCIF_RTLREADING\n");
1389 if (tabItem->mask & TCIF_STATE)
1390 wineItem->dwState=tabItem->dwState;
1392 if (tabItem->mask & TCIF_TEXT) {
1393 len=lstrlenA (tabItem->pszText);
1394 if (len>wineItem->cchTextMax)
1395 wineItem->pszText= COMCTL32_ReAlloc (wineItem->pszText, len+1);
1396 lstrcpyA (wineItem->pszText, tabItem->pszText);
1400 * Update and repaint tabs.
1402 TAB_SetItemBounds(hwnd);
1403 TAB_InvalidateTabArea(hwnd,infoPtr);
1409 TAB_GetItemCount (HWND hwnd, WPARAM wParam, LPARAM lParam)
1411 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
1413 return infoPtr->uNumItem;
1418 TAB_GetItemA (HWND hwnd, WPARAM wParam, LPARAM lParam)
1420 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
1426 tabItem=(LPTCITEMA) lParam;
1428 if ((iItem<0) || (iItem>=infoPtr->uNumItem)) return FALSE;
1430 wineItem=& infoPtr->items[iItem];
1432 if (tabItem->mask & TCIF_IMAGE)
1433 tabItem->iImage=wineItem->iImage;
1435 if (tabItem->mask & TCIF_PARAM)
1436 tabItem->lParam=wineItem->lParam;
1438 if (tabItem->mask & TCIF_RTLREADING)
1439 FIXME("TCIF_RTLREADING\n");
1441 if (tabItem->mask & TCIF_STATE)
1442 tabItem->dwState=wineItem->dwState;
1444 if (tabItem->mask & TCIF_TEXT)
1445 lstrcpynA (tabItem->pszText, wineItem->pszText, tabItem->cchTextMax);
1451 TAB_DeleteItem (HWND hwnd, WPARAM wParam, LPARAM lParam)
1453 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
1454 INT iItem = (INT) wParam;
1455 BOOL bResult = FALSE;
1457 if ((iItem >= 0) && (iItem < infoPtr->uNumItem))
1459 TAB_ITEM *oldItems = infoPtr->items;
1461 infoPtr->uNumItem--;
1462 infoPtr->items = COMCTL32_Alloc(sizeof (TAB_ITEM) * infoPtr->uNumItem);
1465 memcpy(&infoPtr->items[0], &oldItems[0], iItem * sizeof(TAB_ITEM));
1467 if (iItem < infoPtr->uNumItem)
1468 memcpy(&infoPtr->items[iItem], &oldItems[iItem + 1],
1469 (infoPtr->uNumItem - iItem) * sizeof(TAB_ITEM));
1471 COMCTL32_Free (oldItems);
1474 * Readjust the selected index.
1476 if ((iItem == infoPtr->iSelected) && (iItem > 0))
1477 infoPtr->iSelected--;
1479 if (iItem < infoPtr->iSelected)
1480 infoPtr->iSelected--;
1482 if (infoPtr->uNumItem == 0)
1483 infoPtr->iSelected = -1;
1486 * Reposition and repaint tabs.
1488 TAB_SetItemBounds(hwnd);
1489 TAB_InvalidateTabArea(hwnd,infoPtr);
1498 TAB_DeleteAllItems (HWND hwnd, WPARAM wParam, LPARAM lParam)
1500 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
1502 COMCTL32_Free (infoPtr->items);
1503 infoPtr->uNumItem = 0;
1504 infoPtr->iSelected = -1;
1511 TAB_GetFont (HWND hwnd, WPARAM wParam, LPARAM lParam)
1513 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
1516 return (LRESULT)infoPtr->hFont;
1520 TAB_SetFont (HWND hwnd, WPARAM wParam, LPARAM lParam)
1523 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
1525 TRACE("%x %lx\n",wParam, lParam);
1527 infoPtr->hFont = (HFONT)wParam;
1529 TAB_SetItemBounds(hwnd);
1531 TAB_InvalidateTabArea(hwnd, infoPtr);
1538 TAB_GetImageList (HWND hwnd, WPARAM wParam, LPARAM lParam)
1540 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
1543 return (LRESULT)infoPtr->himl;
1547 TAB_SetImageList (HWND hwnd, WPARAM wParam, LPARAM lParam)
1549 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
1550 HIMAGELIST himlPrev;
1553 himlPrev = infoPtr->himl;
1554 infoPtr->himl= (HIMAGELIST)lParam;
1555 return (LRESULT)himlPrev;
1560 TAB_Size (HWND hwnd, WPARAM wParam, LPARAM lParam)
1563 /* I'm not really sure what the following code was meant to do.
1564 This is what it is doing:
1565 When WM_SIZE is sent with SIZE_RESTORED, the control
1566 gets positioned in the top left corner.
1570 UINT uPosFlags,cx,cy;
1574 parent = GetParent (hwnd);
1575 GetClientRect(parent, &parent_rect);
1578 if (GetWindowLongA(hwnd, GWL_STYLE) & CCS_NORESIZE)
1579 uPosFlags |= (SWP_NOSIZE | SWP_NOMOVE);
1581 SetWindowPos (hwnd, 0, parent_rect.left, parent_rect.top,
1582 cx, cy, uPosFlags | SWP_NOZORDER);
1584 FIXME (tab,"WM_SIZE flag %x %lx not handled\n", wParam, lParam);
1588 * Recompute the size/position of the tabs.
1590 TAB_SetItemBounds (hwnd);
1593 * Force a repaint of the control.
1595 InvalidateRect(hwnd, NULL, TRUE);
1602 TAB_Create (HWND hwnd, WPARAM wParam, LPARAM lParam)
1605 TEXTMETRICA fontMetrics;
1609 infoPtr = (TAB_INFO *)COMCTL32_Alloc (sizeof(TAB_INFO));
1611 SetWindowLongA(hwnd, 0, (DWORD)infoPtr);
1613 infoPtr->uNumItem = 0;
1616 infoPtr->hcurArrow = LoadCursorA (0, IDC_ARROWA);
1617 infoPtr->iSelected = -1;
1618 infoPtr->uFocus = 0;
1619 infoPtr->hwndToolTip = 0;
1620 infoPtr->DoRedraw = TRUE;
1621 infoPtr->needsScrolling = FALSE;
1622 infoPtr->hwndUpDown = 0;
1623 infoPtr->leftmostVisible = 0;
1625 TRACE("Created tab control, hwnd [%04x]\n", hwnd);
1626 if (GetWindowLongA(hwnd, GWL_STYLE) & TCS_TOOLTIPS) {
1627 /* Create tooltip control */
1628 infoPtr->hwndToolTip =
1629 CreateWindowExA (0, TOOLTIPS_CLASSA, NULL, 0,
1630 CW_USEDEFAULT, CW_USEDEFAULT,
1631 CW_USEDEFAULT, CW_USEDEFAULT,
1634 /* Send NM_TOOLTIPSCREATED notification */
1635 if (infoPtr->hwndToolTip) {
1636 NMTOOLTIPSCREATED nmttc;
1638 nmttc.hdr.hwndFrom = hwnd;
1639 nmttc.hdr.idFrom = GetWindowLongA(hwnd, GWL_ID);
1640 nmttc.hdr.code = NM_TOOLTIPSCREATED;
1641 nmttc.hwndToolTips = infoPtr->hwndToolTip;
1643 SendMessageA (GetParent (hwnd), WM_NOTIFY,
1644 (WPARAM)GetWindowLongA(hwnd, GWL_ID), (LPARAM)&nmttc);
1649 * We need to get text information so we need a DC and we need to select
1653 hOldFont = SelectObject (hdc, GetStockObject (SYSTEM_FONT));
1656 * Use the system font to determine the initial height of a tab.
1658 GetTextMetricsA(hdc, &fontMetrics);
1661 * Make sure there is enough space for the letters + growing the
1662 * selected item + extra space for the selected item.
1664 infoPtr->tabHeight = fontMetrics.tmHeight + 2*VERTICAL_ITEM_PADDING +
1665 SELECTED_TAB_OFFSET;
1668 * Initialize the width of a tab.
1670 infoPtr->tabWidth = DEFAULT_TAB_WIDTH;
1672 SelectObject (hdc, hOldFont);
1673 ReleaseDC(hwnd, hdc);
1679 TAB_Destroy (HWND hwnd, WPARAM wParam, LPARAM lParam)
1681 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
1684 if (infoPtr->items) {
1685 for (iItem = 0; iItem < infoPtr->uNumItem; iItem++) {
1686 if (infoPtr->items[iItem].pszText)
1687 COMCTL32_Free (infoPtr->items[iItem].pszText);
1689 COMCTL32_Free (infoPtr->items);
1692 if (infoPtr->hwndToolTip)
1693 DestroyWindow (infoPtr->hwndToolTip);
1695 if (infoPtr->hwndUpDown)
1696 DestroyWindow(infoPtr->hwndUpDown);
1698 COMCTL32_Free (infoPtr);
1702 static LRESULT WINAPI
1703 TAB_WindowProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
1707 case TCM_GETIMAGELIST:
1708 return TAB_GetImageList (hwnd, wParam, lParam);
1710 case TCM_SETIMAGELIST:
1711 return TAB_SetImageList (hwnd, wParam, lParam);
1713 case TCM_GETITEMCOUNT:
1714 return TAB_GetItemCount (hwnd, wParam, lParam);
1717 return TAB_GetItemA (hwnd, wParam, lParam);
1720 FIXME("Unimplemented msg TCM_GETITEMW\n");
1724 return TAB_SetItemA (hwnd, wParam, lParam);
1727 FIXME("Unimplemented msg TCM_SETITEMW\n");
1730 case TCM_DELETEITEM:
1731 return TAB_DeleteItem (hwnd, wParam, lParam);
1733 case TCM_DELETEALLITEMS:
1734 return TAB_DeleteAllItems (hwnd, wParam, lParam);
1736 case TCM_GETITEMRECT:
1737 return TAB_GetItemRect (hwnd, wParam, lParam);
1740 return TAB_GetCurSel (hwnd);
1743 return TAB_HitTest (hwnd, wParam, lParam);
1746 return TAB_SetCurSel (hwnd, wParam);
1748 case TCM_INSERTITEMA:
1749 return TAB_InsertItem (hwnd, wParam, lParam);
1751 case TCM_INSERTITEMW:
1752 FIXME("Unimplemented msg TCM_INSERTITEM32W\n");
1755 case TCM_SETITEMEXTRA:
1756 FIXME("Unimplemented msg TCM_SETITEMEXTRA\n");
1759 case TCM_ADJUSTRECT:
1760 return TAB_AdjustRect (hwnd, (BOOL)wParam, (LPRECT)lParam);
1762 case TCM_SETITEMSIZE:
1763 return TAB_SetItemSize (hwnd, wParam, lParam);
1765 case TCM_REMOVEIMAGE:
1766 FIXME("Unimplemented msg TCM_REMOVEIMAGE\n");
1769 case TCM_SETPADDING:
1770 FIXME("Unimplemented msg TCM_SETPADDING\n");
1773 case TCM_GETROWCOUNT:
1774 FIXME("Unimplemented msg TCM_GETROWCOUNT\n");
1777 case TCM_GETUNICODEFORMAT:
1778 FIXME("Unimplemented msg TCM_GETUNICODEFORMAT\n");
1781 case TCM_SETUNICODEFORMAT:
1782 FIXME("Unimplemented msg TCM_SETUNICODEFORMAT\n");
1785 case TCM_HIGHLIGHTITEM:
1786 FIXME("Unimplemented msg TCM_HIGHLIGHTITEM\n");
1789 case TCM_GETTOOLTIPS:
1790 return TAB_GetToolTips (hwnd, wParam, lParam);
1792 case TCM_SETTOOLTIPS:
1793 return TAB_SetToolTips (hwnd, wParam, lParam);
1795 case TCM_GETCURFOCUS:
1796 return TAB_GetCurFocus (hwnd);
1798 case TCM_SETCURFOCUS:
1799 return TAB_SetCurFocus (hwnd, wParam);
1801 case TCM_SETMINTTABWIDTH:
1802 FIXME("Unimplemented msg TCM_SETMINTTABWIDTH\n");
1805 case TCM_DESELECTALL:
1806 FIXME("Unimplemented msg TCM_DESELECTALL\n");
1809 case TCM_GETEXTENDEDSTYLE:
1810 FIXME("Unimplemented msg TCM_GETEXTENDEDSTYLE\n");
1813 case TCM_SETEXTENDEDSTYLE:
1814 FIXME("Unimplemented msg TCM_SETEXTENDEDSTYLE\n");
1818 return TAB_GetFont (hwnd, wParam, lParam);
1821 return TAB_SetFont (hwnd, wParam, lParam);
1824 return TAB_Create (hwnd, wParam, lParam);
1827 return TAB_Destroy (hwnd, wParam, lParam);
1830 return DLGC_WANTARROWS | DLGC_WANTCHARS;
1832 case WM_LBUTTONDOWN:
1833 return TAB_LButtonDown (hwnd, wParam, lParam);
1836 return TAB_LButtonUp (hwnd, wParam, lParam);
1838 case WM_RBUTTONDOWN:
1839 return TAB_RButtonDown (hwnd, wParam, lParam);
1842 return TAB_MouseMove (hwnd, wParam, lParam);
1845 return TAB_EraseBackground (hwnd, (HDC)wParam);
1848 return TAB_Paint (hwnd, wParam);
1851 return TAB_Size (hwnd, wParam, lParam);
1854 return TAB_SetRedraw (hwnd, wParam);
1857 return TAB_OnHScroll(hwnd, (int)LOWORD(wParam), (int)HIWORD(wParam), (HWND)lParam);
1861 return TAB_FocusChanging(hwnd, uMsg, wParam, lParam);
1864 return TAB_KeyUp(hwnd, wParam);
1867 if (uMsg >= WM_USER)
1868 ERR("unknown msg %04x wp=%08x lp=%08lx\n",
1869 uMsg, wParam, lParam);
1870 return DefWindowProcA (hwnd, uMsg, wParam, lParam);
1882 if (GlobalFindAtomA (WC_TABCONTROLA)) return;
1884 ZeroMemory (&wndClass, sizeof(WNDCLASSA));
1885 wndClass.style = CS_GLOBALCLASS | CS_DBLCLKS | CS_SAVEBITS;
1886 wndClass.lpfnWndProc = (WNDPROC)TAB_WindowProc;
1887 wndClass.cbClsExtra = 0;
1888 wndClass.cbWndExtra = sizeof(TAB_INFO *);
1889 wndClass.hCursor = LoadCursorA (0, IDC_ARROWA);
1890 wndClass.hbrBackground = (HBRUSH)NULL;
1891 wndClass.lpszClassName = WC_TABCONTROLA;
1893 RegisterClassA (&wndClass);
1898 TAB_Unregister (void)
1900 if (GlobalFindAtomA (WC_TABCONTROLA))
1901 UnregisterClassA (WC_TABCONTROLA, (HINSTANCE)NULL);