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);
375 if (infoPtr->hwndToolTip)
376 TAB_RelayEvent (infoPtr->hwndToolTip, hwnd,
377 WM_LBUTTONDOWN, wParam, lParam);
379 if (GetWindowLongA(hwnd, GWL_STYLE) & TCS_FOCUSONBUTTONDOWN ) {
386 TAB_LButtonUp (HWND hwnd, WPARAM wParam, LPARAM lParam)
388 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
392 if (infoPtr->hwndToolTip)
393 TAB_RelayEvent (infoPtr->hwndToolTip, hwnd,
394 WM_LBUTTONDOWN, wParam, lParam);
396 pt.x = (INT)LOWORD(lParam);
397 pt.y = (INT)HIWORD(lParam);
399 newItem=TAB_InternalHitTest (hwnd, infoPtr,pt,&dummy);
401 TRACE("On Tab, item %d\n", newItem);
403 if ( (newItem!=-1) &&
404 (infoPtr->iSelected != newItem) )
406 if (TAB_SendSimpleNotify(hwnd, TCN_SELCHANGING)!=TRUE)
408 infoPtr->iSelected = newItem;
409 infoPtr->uFocus = newItem;
410 TAB_SendSimpleNotify(hwnd, TCN_SELCHANGE);
412 TAB_EnsureSelectionVisible(hwnd, infoPtr);
414 TAB_InvalidateTabArea(hwnd, infoPtr);
417 TAB_SendSimpleNotify(hwnd, NM_CLICK);
423 TAB_RButtonDown (HWND hwnd, WPARAM wParam, LPARAM lParam)
425 TAB_SendSimpleNotify(hwnd, NM_RCLICK);
430 TAB_MouseMove (HWND hwnd, WPARAM wParam, LPARAM lParam)
432 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
434 if (infoPtr->hwndToolTip)
435 TAB_RelayEvent (infoPtr->hwndToolTip, hwnd,
436 WM_LBUTTONDOWN, wParam, lParam);
440 /******************************************************************************
443 * Calculates the tab control's display area given the windows rectangle or
444 * the window rectangle given the requested display rectangle.
446 static LRESULT TAB_AdjustRect(
451 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
456 * Go from display rectangle
460 * Add the height of the tabs.
462 if (GetWindowLongA(hwnd, GWL_STYLE) & TCS_BOTTOM)
463 prc->bottom += infoPtr->tabHeight;
465 prc->top -= infoPtr->tabHeight;
468 * Inflate the rectangle for the padding
470 InflateRect(prc, DISPLAY_AREA_PADDINGX, DISPLAY_AREA_PADDINGY);
473 * Inflate for the border
475 InflateRect(prc, CONTROL_BORDER_SIZEX, CONTROL_BORDER_SIZEX);
480 * Go from window rectangle.
484 * Deflate the rectangle for the border
486 InflateRect(prc, -CONTROL_BORDER_SIZEX, -CONTROL_BORDER_SIZEX);
489 * Deflate the rectangle for the padding
491 InflateRect(prc, -DISPLAY_AREA_PADDINGX, -DISPLAY_AREA_PADDINGY);
494 * Remove the height of the tabs.
496 if (GetWindowLongA(hwnd, GWL_STYLE) & TCS_BOTTOM)
497 prc->bottom -= infoPtr->tabHeight;
499 prc->top += infoPtr->tabHeight;
506 /******************************************************************************
509 * This method will handle the notification from the scroll control and
510 * perform the scrolling operation on the tab control.
512 static LRESULT TAB_OnHScroll(
518 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
520 if (nScrollCode == SB_LINELEFT)
522 if (infoPtr->leftmostVisible>0)
524 infoPtr->leftmostVisible--;
526 TAB_InvalidateTabArea(hwnd, infoPtr);
529 else if (nScrollCode == SB_LINERIGHT)
531 if (infoPtr->leftmostVisible< (infoPtr->uNumItem-1))
533 infoPtr->leftmostVisible++;
535 TAB_InvalidateTabArea(hwnd, infoPtr);
542 /******************************************************************************
545 * This method will check the current scrolling state and make sure the
546 * scrolling control is displayed (or not).
548 static void TAB_SetupScrolling(
551 const RECT* clientRect)
553 if (infoPtr->needsScrolling)
558 * Calculate the position of the scroll control.
560 controlPos.right = clientRect->right;
561 controlPos.left = controlPos.right - 2*GetSystemMetrics(SM_CXHSCROLL);
563 if (GetWindowLongA(hwnd, GWL_STYLE) & TCS_BOTTOM)
565 controlPos.top = clientRect->bottom - infoPtr->tabHeight;
566 controlPos.bottom = controlPos.top + GetSystemMetrics(SM_CYHSCROLL);
570 controlPos.bottom = clientRect->top + infoPtr->tabHeight;
571 controlPos.top = controlPos.bottom - GetSystemMetrics(SM_CYHSCROLL);
575 * If we don't have a scroll control yet, we want to create one.
576 * If we have one, we want to make sure it's positioned right.
578 if (infoPtr->hwndUpDown==0)
581 * I use a scrollbar since it seems to be more stable than the Updown
584 infoPtr->hwndUpDown = CreateWindowA("ScrollBar",
586 WS_VISIBLE | WS_CHILD | WS_OVERLAPPED | SBS_HORZ,
587 controlPos.left, controlPos.top,
588 controlPos.right - controlPos.left,
589 controlPos.bottom - controlPos.top,
597 SetWindowPos(infoPtr->hwndUpDown,
599 controlPos.left, controlPos.top,
600 controlPos.right - controlPos.left,
601 controlPos.bottom - controlPos.top,
602 SWP_SHOWWINDOW | SWP_NOZORDER);
608 * If we once had a scroll control... hide it.
610 if (infoPtr->hwndUpDown!=0)
612 ShowWindow(infoPtr->hwndUpDown, SW_HIDE);
617 /******************************************************************************
620 * This method will calculate the position rectangles of all the items in the
621 * control. The rectangle calculated starts at 0 for the first item in the
622 * list and ignores scrolling and selection.
623 * It also uses the current font to determine the height of the tab row and
624 * it checks if all the tabs fit in the client area of the window. If they
625 * dont, a scrolling control is added.
627 static void TAB_SetItemBounds (HWND hwnd)
629 TAB_INFO* infoPtr = TAB_GetInfoPtr(hwnd);
630 LONG lStyle = GetWindowLongA(hwnd, GWL_STYLE);
631 TEXTMETRICA fontMetrics;
634 HFONT hFont, hOldFont;
640 * We need to get text information so we need a DC and we need to select
645 hFont = infoPtr->hFont ? infoPtr->hFont : GetStockObject (SYSTEM_FONT);
646 hOldFont = SelectObject (hdc, hFont);
649 * We will base the rectangle calculations on the client rectangle
652 GetClientRect(hwnd, &clientRect);
655 * The leftmost item will be "0" aligned
659 if (!((lStyle & TCS_FIXEDWIDTH) || (lStyle & TCS_OWNERDRAWFIXED)))
665 * Use the current font to determine the height of a tab.
667 GetTextMetricsA(hdc, &fontMetrics);
670 * Get the icon height
673 ImageList_GetIconSize(infoPtr->himl, 0, &icon_height);
676 * Take the highest between font or icon
678 if (fontMetrics.tmHeight > icon_height)
679 item_height = fontMetrics.tmHeight;
681 item_height = icon_height;
684 * Make sure there is enough space for the letters + icon + growing the
685 * selected item + extra space for the selected item.
687 infoPtr->tabHeight = item_height + 2*VERTICAL_ITEM_PADDING +
691 for (curItem = 0; curItem < infoPtr->uNumItem; curItem++)
694 * Calculate the vertical position of the tab
696 if (lStyle & TCS_BOTTOM)
698 infoPtr->items[curItem].rect.bottom = clientRect.bottom -
700 infoPtr->items[curItem].rect.top = clientRect.bottom -
705 infoPtr->items[curItem].rect.top = clientRect.top +
707 infoPtr->items[curItem].rect.bottom = clientRect.top +
712 * Set the leftmost position of the tab.
714 infoPtr->items[curItem].rect.left = curItemLeftPos;
716 if ((lStyle & TCS_FIXEDWIDTH) || (lStyle & TCS_OWNERDRAWFIXED))
718 infoPtr->items[curItem].rect.right = infoPtr->items[curItem].rect.left +
720 2*HORIZONTAL_ITEM_PADDING;
728 * Calculate how wide the tab is depending on the text it contains
730 GetTextExtentPoint32A(hdc, infoPtr->items[curItem].pszText,
731 lstrlenA(infoPtr->items[curItem].pszText), &size);
738 ImageList_GetIconSize(infoPtr->himl, &icon_width, 0);
742 infoPtr->items[curItem].rect.right = infoPtr->items[curItem].rect.left +
743 size.cx + icon_width +
744 num*HORIZONTAL_ITEM_PADDING;
747 TRACE("TextSize: %i\n ", size.cx);
748 TRACE("Rect: T %i, L %i, B %i, R %i\n",
749 infoPtr->items[curItem].rect.top,
750 infoPtr->items[curItem].rect.left,
751 infoPtr->items[curItem].rect.bottom,
752 infoPtr->items[curItem].rect.right);
755 * The leftmost position of the next item is the rightmost position
758 if (lStyle & TCS_BUTTONS)
759 curItemLeftPos = infoPtr->items[curItem].rect.right + BUTTON_SPACINGX;
761 curItemLeftPos = infoPtr->items[curItem].rect.right;
765 * Check if we need a scrolling control.
767 infoPtr->needsScrolling = (curItemLeftPos + (2*SELECTED_TAB_OFFSET) >
770 TAB_SetupScrolling(hwnd, infoPtr, &clientRect);
775 SelectObject (hdc, hOldFont);
776 ReleaseDC (hwnd, hdc);
779 /******************************************************************************
782 * This method is used to draw a single tab into the tab control.
784 static void TAB_DrawItem(
789 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
790 LONG lStyle = GetWindowLongA(hwnd, GWL_STYLE);
797 * Get the rectangle for the item.
799 isVisible = TAB_InternalGetItemRect(hwnd,
807 HBRUSH hbr = CreateSolidBrush (GetSysColor(COLOR_BTNFACE));
808 HPEN hwPen = GetSysColorPen (COLOR_3DHILIGHT);
809 HPEN hbPen = GetSysColorPen (COLOR_BTNSHADOW);
810 HPEN hsdPen = GetSysColorPen (COLOR_BTNTEXT);
811 HPEN hfocusPen = CreatePen(PS_DOT, 1, GetSysColor(COLOR_BTNTEXT));
816 if (lStyle & TCS_BUTTONS)
819 * Get item rectangle.
823 holdPen = SelectObject (hdc, hwPen);
825 if (iItem == infoPtr->iSelected)
830 if (!(lStyle & TCS_OWNERDRAWFIXED))
833 hbr = CreateSolidBrush(GetSysColor(COLOR_3DHILIGHT));
837 * Erase the background.
839 FillRect(hdc, &r, hbr);
843 * The rectangles calculated exclude the right and bottom
844 * borders of the rectangle. To simply the following code, those
845 * borders are shaved-off beforehand.
851 MoveToEx (hdc, r.left, r.bottom, NULL);
852 LineTo (hdc, r.right, r.bottom);
853 LineTo (hdc, r.right, r.top);
856 SelectObject(hdc, hbPen);
857 LineTo (hdc, r.left, r.top);
858 LineTo (hdc, r.left, r.bottom);
863 * Erase the background.
865 FillRect(hdc, &r, hbr);
868 MoveToEx (hdc, r.left, r.bottom, NULL);
869 LineTo (hdc, r.left, r.top);
870 LineTo (hdc, r.right, r.top);
873 SelectObject(hdc, hbPen);
874 LineTo (hdc, r.right, r.bottom);
875 LineTo (hdc, r.left, r.bottom);
884 hbr = CreateSolidBrush(GetSysColor(COLOR_BTNFACE));
887 * We draw a rectangle of different sizes depending on the selection
890 if (iItem == infoPtr->iSelected)
896 * Erase the background.
897 * This is necessary when drawing the selected item since it is larger
898 * than the others, it might overlap with stuff already drawn by the
901 FillRect(hdc, &r, hbr);
905 * The rectangles calculated exclude the right and bottom
906 * borders of the rectangle. To simply the following code, those
907 * borders are shaved-off beforehand.
912 holdPen = SelectObject (hdc, hwPen);
914 if (lStyle & TCS_BOTTOM)
917 MoveToEx (hdc, r.left, r.top, NULL);
918 LineTo (hdc, r.left, r.bottom - ROUND_CORNER_SIZE);
919 LineTo (hdc, r.left + ROUND_CORNER_SIZE, r.bottom);
922 SelectObject(hdc, hbPen);
923 LineTo (hdc, r.right - ROUND_CORNER_SIZE, r.bottom);
924 LineTo (hdc, r.right, r.bottom - ROUND_CORNER_SIZE);
925 LineTo (hdc, r.right, r.top);
930 MoveToEx (hdc, r.left, r.bottom, NULL);
931 LineTo (hdc, r.left, r.top + ROUND_CORNER_SIZE);
932 LineTo (hdc, r.left + ROUND_CORNER_SIZE, r.top);
933 LineTo (hdc, r.right - ROUND_CORNER_SIZE, r.top);
936 SelectObject(hdc, hbPen);
937 LineTo (hdc, r.right, r.top + ROUND_CORNER_SIZE);
938 LineTo (hdc, r.right, r.bottom);
945 SelectObject(hdc, hsdPen);
947 oldBkMode = SetBkMode(hdc, TRANSPARENT);
948 SetTextColor (hdc, COLOR_BTNTEXT);
951 * Deflate the rectangle to acount for the padding
953 InflateRect(&r, -HORIZONTAL_ITEM_PADDING, -VERTICAL_ITEM_PADDING);
960 ImageList_Draw (infoPtr->himl, iItem, hdc,
961 r.left, r.top+1, ILD_NORMAL);
962 ImageList_GetIconSize (infoPtr->himl, &cx, &cy);
963 r.left+=(cx + HORIZONTAL_ITEM_PADDING);
970 infoPtr->items[iItem].pszText,
971 lstrlenA(infoPtr->items[iItem].pszText),
973 DT_LEFT|DT_SINGLELINE|DT_VCENTER);
976 * Draw the focus rectangle
978 if (((lStyle & TCS_FOCUSNEVER) == 0) &&
979 (GetFocus() == hwnd) &&
980 (iItem == infoPtr->uFocus) )
982 InflateRect(&r, FOCUS_RECT_HOFFSET, FOCUS_RECT_VOFFSET);
984 SelectObject(hdc, hfocusPen);
986 MoveToEx (hdc, r.left, r.top, NULL);
987 LineTo (hdc, r.right-1, r.top);
988 LineTo (hdc, r.right-1, r.bottom -1);
989 LineTo (hdc, r.left, r.bottom -1);
990 LineTo (hdc, r.left, r.top);
996 SetBkMode(hdc, oldBkMode);
997 SelectObject(hdc, holdPen);
998 DeleteObject(hfocusPen);
1003 /******************************************************************************
1006 * This method is used to draw the raised border around the tab control
1009 static void TAB_DrawBorder (HWND hwnd, HDC hdc)
1011 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
1013 HPEN hwPen = GetSysColorPen (COLOR_3DHILIGHT);
1014 HPEN hbPen = GetSysColorPen (COLOR_3DDKSHADOW);
1015 HPEN hShade = GetSysColorPen (COLOR_BTNSHADOW);
1018 GetClientRect (hwnd, &rect);
1021 * Adjust for the style
1023 if (GetWindowLongA(hwnd, GWL_STYLE) & TCS_BOTTOM)
1025 rect.bottom -= infoPtr->tabHeight;
1029 rect.top += infoPtr->tabHeight;
1033 * Shave-off the right and bottom margins (exluded in the
1040 htmPen = SelectObject (hdc, hwPen);
1042 MoveToEx (hdc, rect.left, rect.bottom, NULL);
1043 LineTo (hdc, rect.left, rect.top);
1044 LineTo (hdc, rect.right, rect.top);
1047 SelectObject (hdc, hbPen);
1048 LineTo (hdc, rect.right, rect.bottom );
1049 LineTo (hdc, rect.left, rect.bottom);
1052 SelectObject (hdc, hShade );
1053 MoveToEx (hdc, rect.right-1, rect.top, NULL);
1054 LineTo (hdc, rect.right-1, rect.bottom-1);
1055 LineTo (hdc, rect.left, rect.bottom-1);
1057 SelectObject(hdc, htmPen);
1060 /******************************************************************************
1063 * This method repaints the tab control..
1065 static void TAB_Refresh (HWND hwnd, HDC hdc)
1067 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
1071 if (!infoPtr->DoRedraw)
1074 hOldFont = SelectObject (hdc, infoPtr->hFont);
1076 if (GetWindowLongA(hwnd, GWL_STYLE) & TCS_BUTTONS)
1078 for (i = 0; i < infoPtr->uNumItem; i++)
1080 TAB_DrawItem (hwnd, hdc, i);
1086 * Draw all the non selected item first.
1088 for (i = 0; i < infoPtr->uNumItem; i++)
1090 if (i != infoPtr->iSelected)
1091 TAB_DrawItem (hwnd, hdc, i);
1095 * Now, draw the border, draw it before the selected item
1096 * since the selected item overwrites part of the border.
1098 TAB_DrawBorder (hwnd, hdc);
1101 * Then, draw the selected item
1103 TAB_DrawItem (hwnd, hdc, infoPtr->iSelected);
1106 SelectObject (hdc, hOldFont);
1110 TAB_SetRedraw (HWND hwnd, WPARAM wParam)
1112 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
1114 infoPtr->DoRedraw=(BOOL) wParam;
1118 static LRESULT TAB_EraseBackground(
1125 HBRUSH brush = CreateSolidBrush(GetSysColor(COLOR_BTNFACE));
1127 hdc = givenDC ? givenDC : GetDC(hwnd);
1129 GetClientRect(hwnd, &clientRect);
1131 FillRect(hdc, &clientRect, brush);
1134 ReleaseDC(hwnd, hdc);
1136 DeleteObject(brush);
1141 /******************************************************************************
1142 * TAB_EnsureSelectionVisible
1144 * This method will make sure that the current selection is completely
1145 * visible by scrolling until it is.
1147 static void TAB_EnsureSelectionVisible(
1157 * Do the trivial cases first.
1159 if ( (!infoPtr->needsScrolling) ||
1160 (infoPtr->hwndUpDown==0) )
1163 if (infoPtr->leftmostVisible > infoPtr->iSelected)
1165 infoPtr->leftmostVisible = infoPtr->iSelected;
1170 * Calculate the part of the client area that is visible.
1172 GetClientRect(hwnd, &visibleRect);
1173 GetClientRect(infoPtr->hwndUpDown, &scrollerRect);
1174 visibleRect.right -= scrollerRect.right;
1177 * Get the rectangle for the item
1179 isVisible = TAB_InternalGetItemRect(hwnd,
1186 * If this function can't say it's completely invisible, maybe it
1187 * is partially visible. Let's check.
1194 pt1.x = selectedRect.left;
1195 pt1.y = selectedRect.top;
1196 pt2.x = selectedRect.right - 1;
1197 pt2.y = selectedRect.bottom - 1;
1199 isVisible = PtInRect(&visibleRect, pt1) && PtInRect(&visibleRect, pt2);
1202 while ( (infoPtr->leftmostVisible < infoPtr->iSelected) &&
1205 infoPtr->leftmostVisible++;
1208 * Get the rectangle for the item
1210 isVisible = TAB_InternalGetItemRect(hwnd,
1217 * If this function can't say it's completely invisible, maybe it
1218 * is partially visible. Let's check.
1225 pt1.x = selectedRect.left;
1226 pt1.y = selectedRect.top;
1227 pt2.x = selectedRect.right - 1;
1228 pt2.y = selectedRect.bottom - 1;
1230 isVisible = PtInRect(&visibleRect, pt1) && PtInRect(&visibleRect, pt2);
1235 /******************************************************************************
1236 * TAB_InvalidateTabArea
1238 * This method will invalidate the portion of the control that contains the
1239 * tabs. It is called when the state of the control changes and needs
1242 static void TAB_InvalidateTabArea(
1248 GetClientRect(hwnd, &clientRect);
1250 if (GetWindowLongA(hwnd, GWL_STYLE) & TCS_BOTTOM)
1252 clientRect.top = clientRect.bottom - (infoPtr->tabHeight + 1);
1256 clientRect.bottom = clientRect.top + (infoPtr->tabHeight + 1);
1259 InvalidateRect(hwnd, &clientRect, TRUE);
1263 TAB_Paint (HWND hwnd, WPARAM wParam)
1268 hdc = wParam== 0 ? BeginPaint (hwnd, &ps) : (HDC)wParam;
1269 TAB_Refresh (hwnd, hdc);
1272 EndPaint (hwnd, &ps);
1278 TAB_InsertItem (HWND hwnd, WPARAM wParam, LPARAM lParam)
1280 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
1285 GetClientRect (hwnd, &rect);
1286 TRACE("Rect: %x T %i, L %i, B %i, R %i\n", hwnd,
1287 rect.top, rect.left, rect.bottom, rect.right);
1289 pti = (TCITEMA *)lParam;
1290 iItem = (INT)wParam;
1292 if (iItem < 0) return -1;
1293 if (iItem > infoPtr->uNumItem)
1294 iItem = infoPtr->uNumItem;
1296 if (infoPtr->uNumItem == 0) {
1297 infoPtr->items = COMCTL32_Alloc (sizeof (TAB_ITEM));
1298 infoPtr->uNumItem++;
1299 infoPtr->iSelected = 0;
1302 TAB_ITEM *oldItems = infoPtr->items;
1304 infoPtr->uNumItem++;
1305 infoPtr->items = COMCTL32_Alloc (sizeof (TAB_ITEM) * infoPtr->uNumItem);
1307 /* pre insert copy */
1309 memcpy (&infoPtr->items[0], &oldItems[0],
1310 iItem * sizeof(TAB_ITEM));
1313 /* post insert copy */
1314 if (iItem < infoPtr->uNumItem - 1) {
1315 memcpy (&infoPtr->items[iItem+1], &oldItems[iItem],
1316 (infoPtr->uNumItem - iItem - 1) * sizeof(TAB_ITEM));
1320 if (iItem <= infoPtr->iSelected)
1321 infoPtr->iSelected++;
1323 COMCTL32_Free (oldItems);
1326 infoPtr->items[iItem].mask = pti->mask;
1327 if (pti->mask & TCIF_TEXT) {
1328 len = lstrlenA (pti->pszText);
1329 infoPtr->items[iItem].pszText = COMCTL32_Alloc (len+1);
1330 lstrcpyA (infoPtr->items[iItem].pszText, pti->pszText);
1331 infoPtr->items[iItem].cchTextMax = pti->cchTextMax;
1334 if (pti->mask & TCIF_IMAGE)
1335 infoPtr->items[iItem].iImage = pti->iImage;
1337 if (pti->mask & TCIF_PARAM)
1338 infoPtr->items[iItem].lParam = pti->lParam;
1340 TAB_InvalidateTabArea(hwnd, infoPtr);
1342 TRACE("[%04x]: added item %d '%s'\n",
1343 hwnd, iItem, infoPtr->items[iItem].pszText);
1345 TAB_SetItemBounds(hwnd);
1350 TAB_SetItemSize (HWND hwnd, WPARAM wParam, LPARAM lParam)
1352 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
1353 LONG lStyle = GetWindowLongA(hwnd, GWL_STYLE);
1356 if ((lStyle & TCS_FIXEDWIDTH) || (lStyle & TCS_OWNERDRAWFIXED))
1358 lResult = MAKELONG(infoPtr->tabWidth, infoPtr->tabHeight);
1359 infoPtr->tabWidth = (INT)LOWORD(lParam);
1360 infoPtr->tabHeight = (INT)HIWORD(lParam);
1367 TAB_SetItemA (HWND hwnd, WPARAM wParam, LPARAM lParam)
1369 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
1375 tabItem=(LPTCITEMA ) lParam;
1376 TRACE("%d %p\n",iItem, tabItem);
1377 if ((iItem<0) || (iItem>=infoPtr->uNumItem)) return FALSE;
1379 wineItem=& infoPtr->items[iItem];
1381 if (tabItem->mask & TCIF_IMAGE)
1382 wineItem->iImage=tabItem->iImage;
1384 if (tabItem->mask & TCIF_PARAM)
1385 wineItem->lParam=tabItem->lParam;
1387 if (tabItem->mask & TCIF_RTLREADING)
1388 FIXME("TCIF_RTLREADING\n");
1390 if (tabItem->mask & TCIF_STATE)
1391 wineItem->dwState=tabItem->dwState;
1393 if (tabItem->mask & TCIF_TEXT) {
1394 len=lstrlenA (tabItem->pszText);
1395 if (len>wineItem->cchTextMax)
1396 wineItem->pszText= COMCTL32_ReAlloc (wineItem->pszText, len+1);
1397 lstrcpyA (wineItem->pszText, tabItem->pszText);
1401 * Update and repaint tabs.
1403 TAB_SetItemBounds(hwnd);
1404 TAB_InvalidateTabArea(hwnd,infoPtr);
1410 TAB_GetItemCount (HWND hwnd, WPARAM wParam, LPARAM lParam)
1412 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
1414 return infoPtr->uNumItem;
1419 TAB_GetItemA (HWND hwnd, WPARAM wParam, LPARAM lParam)
1421 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
1427 tabItem=(LPTCITEMA) lParam;
1429 if ((iItem<0) || (iItem>=infoPtr->uNumItem)) return FALSE;
1431 wineItem=& infoPtr->items[iItem];
1433 if (tabItem->mask & TCIF_IMAGE)
1434 tabItem->iImage=wineItem->iImage;
1436 if (tabItem->mask & TCIF_PARAM)
1437 tabItem->lParam=wineItem->lParam;
1439 if (tabItem->mask & TCIF_RTLREADING)
1440 FIXME("TCIF_RTLREADING\n");
1442 if (tabItem->mask & TCIF_STATE)
1443 tabItem->dwState=wineItem->dwState;
1445 if (tabItem->mask & TCIF_TEXT)
1446 lstrcpynA (tabItem->pszText, wineItem->pszText, tabItem->cchTextMax);
1452 TAB_DeleteItem (HWND hwnd, WPARAM wParam, LPARAM lParam)
1454 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
1455 INT iItem = (INT) wParam;
1456 BOOL bResult = FALSE;
1458 if ((iItem >= 0) && (iItem < infoPtr->uNumItem))
1460 TAB_ITEM *oldItems = infoPtr->items;
1462 infoPtr->uNumItem--;
1463 infoPtr->items = COMCTL32_Alloc(sizeof (TAB_ITEM) * infoPtr->uNumItem);
1466 memcpy(&infoPtr->items[0], &oldItems[0], iItem * sizeof(TAB_ITEM));
1468 if (iItem < infoPtr->uNumItem)
1469 memcpy(&infoPtr->items[iItem], &oldItems[iItem + 1],
1470 (infoPtr->uNumItem - iItem) * sizeof(TAB_ITEM));
1472 COMCTL32_Free (oldItems);
1475 * Readjust the selected index.
1477 if ((iItem == infoPtr->iSelected) && (iItem > 0))
1478 infoPtr->iSelected--;
1480 if (iItem < infoPtr->iSelected)
1481 infoPtr->iSelected--;
1483 if (infoPtr->uNumItem == 0)
1484 infoPtr->iSelected = -1;
1487 * Reposition and repaint tabs.
1489 TAB_SetItemBounds(hwnd);
1490 TAB_InvalidateTabArea(hwnd,infoPtr);
1499 TAB_DeleteAllItems (HWND hwnd, WPARAM wParam, LPARAM lParam)
1501 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
1503 COMCTL32_Free (infoPtr->items);
1504 infoPtr->uNumItem = 0;
1505 infoPtr->iSelected = -1;
1512 TAB_GetFont (HWND hwnd, WPARAM wParam, LPARAM lParam)
1514 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
1517 return (LRESULT)infoPtr->hFont;
1521 TAB_SetFont (HWND hwnd, WPARAM wParam, LPARAM lParam)
1524 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
1526 TRACE("%x %lx\n",wParam, lParam);
1528 infoPtr->hFont = (HFONT)wParam;
1530 TAB_SetItemBounds(hwnd);
1532 TAB_InvalidateTabArea(hwnd, infoPtr);
1539 TAB_GetImageList (HWND hwnd, WPARAM wParam, LPARAM lParam)
1541 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
1544 return (LRESULT)infoPtr->himl;
1548 TAB_SetImageList (HWND hwnd, WPARAM wParam, LPARAM lParam)
1550 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
1551 HIMAGELIST himlPrev;
1554 himlPrev = infoPtr->himl;
1555 infoPtr->himl= (HIMAGELIST)lParam;
1556 return (LRESULT)himlPrev;
1561 TAB_Size (HWND hwnd, WPARAM wParam, LPARAM lParam)
1564 /* I'm not really sure what the following code was meant to do.
1565 This is what it is doing:
1566 When WM_SIZE is sent with SIZE_RESTORED, the control
1567 gets positioned in the top left corner.
1571 UINT uPosFlags,cx,cy;
1575 parent = GetParent (hwnd);
1576 GetClientRect(parent, &parent_rect);
1579 if (GetWindowLongA(hwnd, GWL_STYLE) & CCS_NORESIZE)
1580 uPosFlags |= (SWP_NOSIZE | SWP_NOMOVE);
1582 SetWindowPos (hwnd, 0, parent_rect.left, parent_rect.top,
1583 cx, cy, uPosFlags | SWP_NOZORDER);
1585 FIXME (tab,"WM_SIZE flag %x %lx not handled\n", wParam, lParam);
1589 * Recompute the size/position of the tabs.
1591 TAB_SetItemBounds (hwnd);
1594 * Force a repaint of the control.
1596 InvalidateRect(hwnd, NULL, TRUE);
1603 TAB_Create (HWND hwnd, WPARAM wParam, LPARAM lParam)
1606 TEXTMETRICA fontMetrics;
1610 infoPtr = (TAB_INFO *)COMCTL32_Alloc (sizeof(TAB_INFO));
1612 SetWindowLongA(hwnd, 0, (DWORD)infoPtr);
1614 infoPtr->uNumItem = 0;
1617 infoPtr->hcurArrow = LoadCursorA (0, IDC_ARROWA);
1618 infoPtr->iSelected = -1;
1619 infoPtr->uFocus = 0;
1620 infoPtr->hwndToolTip = 0;
1621 infoPtr->DoRedraw = TRUE;
1622 infoPtr->needsScrolling = FALSE;
1623 infoPtr->hwndUpDown = 0;
1624 infoPtr->leftmostVisible = 0;
1626 TRACE("Created tab control, hwnd [%04x]\n", hwnd);
1627 if (GetWindowLongA(hwnd, GWL_STYLE) & TCS_TOOLTIPS) {
1628 /* Create tooltip control */
1629 infoPtr->hwndToolTip =
1630 CreateWindowExA (0, TOOLTIPS_CLASSA, NULL, 0,
1631 CW_USEDEFAULT, CW_USEDEFAULT,
1632 CW_USEDEFAULT, CW_USEDEFAULT,
1635 /* Send NM_TOOLTIPSCREATED notification */
1636 if (infoPtr->hwndToolTip) {
1637 NMTOOLTIPSCREATED nmttc;
1639 nmttc.hdr.hwndFrom = hwnd;
1640 nmttc.hdr.idFrom = GetWindowLongA(hwnd, GWL_ID);
1641 nmttc.hdr.code = NM_TOOLTIPSCREATED;
1642 nmttc.hwndToolTips = infoPtr->hwndToolTip;
1644 SendMessageA (GetParent (hwnd), WM_NOTIFY,
1645 (WPARAM)GetWindowLongA(hwnd, GWL_ID), (LPARAM)&nmttc);
1650 * We need to get text information so we need a DC and we need to select
1654 hOldFont = SelectObject (hdc, GetStockObject (SYSTEM_FONT));
1657 * Use the system font to determine the initial height of a tab.
1659 GetTextMetricsA(hdc, &fontMetrics);
1662 * Make sure there is enough space for the letters + growing the
1663 * selected item + extra space for the selected item.
1665 infoPtr->tabHeight = fontMetrics.tmHeight + 2*VERTICAL_ITEM_PADDING +
1666 SELECTED_TAB_OFFSET;
1669 * Initialize the width of a tab.
1671 infoPtr->tabWidth = DEFAULT_TAB_WIDTH;
1673 SelectObject (hdc, hOldFont);
1674 ReleaseDC(hwnd, hdc);
1680 TAB_Destroy (HWND hwnd, WPARAM wParam, LPARAM lParam)
1682 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
1685 if (infoPtr->items) {
1686 for (iItem = 0; iItem < infoPtr->uNumItem; iItem++) {
1687 if (infoPtr->items[iItem].pszText)
1688 COMCTL32_Free (infoPtr->items[iItem].pszText);
1690 COMCTL32_Free (infoPtr->items);
1693 if (infoPtr->hwndToolTip)
1694 DestroyWindow (infoPtr->hwndToolTip);
1696 if (infoPtr->hwndUpDown)
1697 DestroyWindow(infoPtr->hwndUpDown);
1699 COMCTL32_Free (infoPtr);
1703 static LRESULT WINAPI
1704 TAB_WindowProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
1708 case TCM_GETIMAGELIST:
1709 return TAB_GetImageList (hwnd, wParam, lParam);
1711 case TCM_SETIMAGELIST:
1712 return TAB_SetImageList (hwnd, wParam, lParam);
1714 case TCM_GETITEMCOUNT:
1715 return TAB_GetItemCount (hwnd, wParam, lParam);
1718 return TAB_GetItemA (hwnd, wParam, lParam);
1721 FIXME("Unimplemented msg TCM_GETITEMW\n");
1725 return TAB_SetItemA (hwnd, wParam, lParam);
1728 FIXME("Unimplemented msg TCM_SETITEMW\n");
1731 case TCM_DELETEITEM:
1732 return TAB_DeleteItem (hwnd, wParam, lParam);
1734 case TCM_DELETEALLITEMS:
1735 return TAB_DeleteAllItems (hwnd, wParam, lParam);
1737 case TCM_GETITEMRECT:
1738 return TAB_GetItemRect (hwnd, wParam, lParam);
1741 return TAB_GetCurSel (hwnd);
1744 return TAB_HitTest (hwnd, wParam, lParam);
1747 return TAB_SetCurSel (hwnd, wParam);
1749 case TCM_INSERTITEMA:
1750 return TAB_InsertItem (hwnd, wParam, lParam);
1752 case TCM_INSERTITEMW:
1753 FIXME("Unimplemented msg TCM_INSERTITEM32W\n");
1756 case TCM_SETITEMEXTRA:
1757 FIXME("Unimplemented msg TCM_SETITEMEXTRA\n");
1760 case TCM_ADJUSTRECT:
1761 return TAB_AdjustRect (hwnd, (BOOL)wParam, (LPRECT)lParam);
1763 case TCM_SETITEMSIZE:
1764 return TAB_SetItemSize (hwnd, wParam, lParam);
1766 case TCM_REMOVEIMAGE:
1767 FIXME("Unimplemented msg TCM_REMOVEIMAGE\n");
1770 case TCM_SETPADDING:
1771 FIXME("Unimplemented msg TCM_SETPADDING\n");
1774 case TCM_GETROWCOUNT:
1775 FIXME("Unimplemented msg TCM_GETROWCOUNT\n");
1778 case TCM_GETUNICODEFORMAT:
1779 FIXME("Unimplemented msg TCM_GETUNICODEFORMAT\n");
1782 case TCM_SETUNICODEFORMAT:
1783 FIXME("Unimplemented msg TCM_SETUNICODEFORMAT\n");
1786 case TCM_HIGHLIGHTITEM:
1787 FIXME("Unimplemented msg TCM_HIGHLIGHTITEM\n");
1790 case TCM_GETTOOLTIPS:
1791 return TAB_GetToolTips (hwnd, wParam, lParam);
1793 case TCM_SETTOOLTIPS:
1794 return TAB_SetToolTips (hwnd, wParam, lParam);
1796 case TCM_GETCURFOCUS:
1797 return TAB_GetCurFocus (hwnd);
1799 case TCM_SETCURFOCUS:
1800 return TAB_SetCurFocus (hwnd, wParam);
1802 case TCM_SETMINTTABWIDTH:
1803 FIXME("Unimplemented msg TCM_SETMINTTABWIDTH\n");
1806 case TCM_DESELECTALL:
1807 FIXME("Unimplemented msg TCM_DESELECTALL\n");
1810 case TCM_GETEXTENDEDSTYLE:
1811 FIXME("Unimplemented msg TCM_GETEXTENDEDSTYLE\n");
1814 case TCM_SETEXTENDEDSTYLE:
1815 FIXME("Unimplemented msg TCM_SETEXTENDEDSTYLE\n");
1819 return TAB_GetFont (hwnd, wParam, lParam);
1822 return TAB_SetFont (hwnd, wParam, lParam);
1825 return TAB_Create (hwnd, wParam, lParam);
1828 return TAB_Destroy (hwnd, wParam, lParam);
1831 return DLGC_WANTARROWS | DLGC_WANTCHARS;
1833 case WM_LBUTTONDOWN:
1834 return TAB_LButtonDown (hwnd, wParam, lParam);
1837 return TAB_LButtonUp (hwnd, wParam, lParam);
1839 case WM_RBUTTONDOWN:
1840 return TAB_RButtonDown (hwnd, wParam, lParam);
1843 return TAB_MouseMove (hwnd, wParam, lParam);
1846 return TAB_EraseBackground (hwnd, (HDC)wParam);
1849 return TAB_Paint (hwnd, wParam);
1852 return TAB_Size (hwnd, wParam, lParam);
1855 return TAB_SetRedraw (hwnd, wParam);
1858 return TAB_OnHScroll(hwnd, (int)LOWORD(wParam), (int)HIWORD(wParam), (HWND)lParam);
1862 return TAB_FocusChanging(hwnd, uMsg, wParam, lParam);
1865 return TAB_KeyUp(hwnd, wParam);
1868 if (uMsg >= WM_USER)
1869 ERR("unknown msg %04x wp=%08x lp=%08lx\n",
1870 uMsg, wParam, lParam);
1871 return DefWindowProcA (hwnd, uMsg, wParam, lParam);
1883 if (GlobalFindAtomA (WC_TABCONTROLA)) return;
1885 ZeroMemory (&wndClass, sizeof(WNDCLASSA));
1886 wndClass.style = CS_GLOBALCLASS | CS_DBLCLKS | CS_SAVEBITS;
1887 wndClass.lpfnWndProc = (WNDPROC)TAB_WindowProc;
1888 wndClass.cbClsExtra = 0;
1889 wndClass.cbWndExtra = sizeof(TAB_INFO *);
1890 wndClass.hCursor = LoadCursorA (0, IDC_ARROWA);
1891 wndClass.hbrBackground = (HBRUSH)NULL;
1892 wndClass.lpszClassName = WC_TABCONTROLA;
1894 RegisterClassA (&wndClass);
1899 TAB_Unregister (void)
1901 if (GlobalFindAtomA (WC_TABCONTROLA))
1902 UnregisterClassA (WC_TABCONTROLA, (HINSTANCE)NULL);