4 * Copyright 1998 Anders Carlsson
5 * Copyright 1999 Alex Priem <alexp@sci.kun.nl>
6 * Copyright 1999 Francis Beaudet
18 #include "debugtools.h"
21 DEFAULT_DEBUG_CHANNEL(tab);
31 RECT rect; /* bounding rectangle of the item relative to the
32 * leftmost item (the leftmost item, 0, would have a
33 * "left" member of 0 in this rectangle)
35 * additionally the top member hold the row number
36 * and bottom is unused and should be 0 */
41 UINT uNumItem; /* number of tab items */
42 UINT uNumRows; /* number of tab rows */
43 INT tabHeight; /* height of the tab row */
44 INT tabWidth; /* width of tabs */
45 HFONT hFont; /* handle to the current font */
46 HCURSOR hcurArrow; /* handle to the current cursor */
47 HIMAGELIST himl; /* handle to a image list (may be 0) */
48 HWND hwndToolTip; /* handle to tab's tooltip */
50 INT leftmostVisible; /* Used for scrolling, this member contains
51 * the index of the first visible item */
52 INT iSelected; /* the currently selected item */
53 INT iHotTracked; /* the highlighted item under the mouse */
54 INT uFocus; /* item which has the focus */
55 TAB_ITEM* items; /* pointer to an array of TAB_ITEM's */
56 BOOL DoRedraw; /* flag for redrawing when tab contents is changed*/
57 BOOL needsScrolling; /* TRUE if the size of the tabs is greater than
58 * the size of the control */
59 BOOL fSizeSet; /* was the size of the tabs explicitly set? */
60 HWND hwndUpDown; /* Updown control used for scrolling */
63 /******************************************************************************
64 * Positioning constants
66 #define SELECTED_TAB_OFFSET 2
67 #define HORIZONTAL_ITEM_PADDING 5
68 #define VERTICAL_ITEM_PADDING 3
69 #define ROUND_CORNER_SIZE 2
70 #define DISPLAY_AREA_PADDINGX 2
71 #define DISPLAY_AREA_PADDINGY 2
72 #define CONTROL_BORDER_SIZEX 2
73 #define CONTROL_BORDER_SIZEY 2
74 #define BUTTON_SPACINGX 4
75 #define FLAT_BTN_SPACINGX 8
76 #define DEFAULT_TAB_WIDTH 96
78 #define TAB_GetInfoPtr(hwnd) ((TAB_INFO *)GetWindowLongA(hwnd,0))
80 /******************************************************************************
81 * Hot-tracking timer constants
83 #define TAB_HOTTRACK_TIMER 1
84 #define TAB_HOTTRACK_TIMER_INTERVAL 100 /* milliseconds */
86 /******************************************************************************
89 static void TAB_Refresh (HWND hwnd, HDC hdc);
90 static void TAB_InvalidateTabArea(HWND hwnd, TAB_INFO* infoPtr);
91 static void TAB_EnsureSelectionVisible(HWND hwnd, TAB_INFO* infoPtr);
92 static void TAB_DrawItem(HWND hwnd, HDC hdc, INT iItem);
93 static void TAB_DrawItemInterior(HWND hwnd, HDC hdc, INT iItem, RECT* drawRect);
96 TAB_SendSimpleNotify (HWND hwnd, UINT code)
100 nmhdr.hwndFrom = hwnd;
101 nmhdr.idFrom = GetWindowLongA(hwnd, GWL_ID);
104 return (BOOL) SendMessageA (GetParent (hwnd), WM_NOTIFY,
105 (WPARAM) nmhdr.idFrom, (LPARAM) &nmhdr);
110 TAB_RelayEvent (HWND hwndTip, HWND hwndMsg, UINT uMsg,
111 WPARAM wParam, LPARAM lParam)
119 msg.time = GetMessageTime ();
120 msg.pt.x = LOWORD(GetMessagePos ());
121 msg.pt.y = HIWORD(GetMessagePos ());
123 SendMessageA (hwndTip, TTM_RELAYEVENT, 0, (LPARAM)&msg);
129 TAB_GetCurSel (HWND hwnd)
131 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
133 return infoPtr->iSelected;
137 TAB_GetCurFocus (HWND hwnd)
139 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
141 return infoPtr->uFocus;
145 TAB_GetToolTips (HWND hwnd, WPARAM wParam, LPARAM lParam)
147 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
149 if (infoPtr == NULL) return 0;
150 return infoPtr->hwndToolTip;
155 TAB_SetCurSel (HWND hwnd,WPARAM wParam)
157 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
158 INT iItem=(INT) wParam;
162 if ((iItem >= 0) && (iItem < infoPtr->uNumItem)) {
163 prevItem=infoPtr->iSelected;
164 infoPtr->iSelected=iItem;
165 TAB_EnsureSelectionVisible(hwnd, infoPtr);
166 TAB_InvalidateTabArea(hwnd, infoPtr);
172 TAB_SetCurFocus (HWND hwnd,WPARAM wParam)
174 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
175 INT iItem=(INT) wParam;
177 if ((iItem < 0) || (iItem >= infoPtr->uNumItem)) return 0;
179 if (GetWindowLongA(hwnd, GWL_STYLE) & TCS_BUTTONS) {
180 FIXME("Should set input focus\n");
182 if (infoPtr->iSelected != iItem || infoPtr->uFocus == -1 ) {
183 infoPtr->uFocus=iItem;
184 if (TAB_SendSimpleNotify(hwnd, TCN_SELCHANGING)!=TRUE) {
185 infoPtr->iSelected = iItem;
186 TAB_SendSimpleNotify(hwnd, TCN_SELCHANGE);
188 TAB_EnsureSelectionVisible(hwnd, infoPtr);
189 TAB_InvalidateTabArea(hwnd, infoPtr);
197 TAB_SetToolTips (HWND hwnd, WPARAM wParam, LPARAM lParam)
199 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
201 if (infoPtr == NULL) return 0;
202 infoPtr->hwndToolTip = (HWND)wParam;
206 /******************************************************************************
207 * TAB_InternalGetItemRect
209 * This method will calculate the rectangle representing a given tab item in
210 * client coordinates. This method takes scrolling into account.
212 * This method returns TRUE if the item is visible in the window and FALSE
213 * if it is completely outside the client area.
215 static BOOL TAB_InternalGetItemRect(
222 RECT tmpItemRect,clientRect;
223 LONG lStyle = GetWindowLongA(hwnd, GWL_STYLE);
226 * Perform a sanity check and a trivial visibility check.
228 if ( (infoPtr->uNumItem <=0) ||
229 (itemIndex >= infoPtr->uNumItem) ||
230 (!(lStyle &TCS_MULTILINE) && (itemIndex < infoPtr->leftmostVisible)) )
234 * Avoid special cases in this procedure by assigning the "out"
235 * parameters if the caller didn't supply them
238 itemRect = &tmpItemRect;
241 * Retrieve the unmodified item rect.
243 *itemRect = infoPtr->items[itemIndex].rect;
246 * calculate the times bottom and top based on the row
248 GetClientRect(hwnd, &clientRect);
250 if (lStyle & TCS_BOTTOM)
252 itemRect->bottom = clientRect.bottom -
253 SELECTED_TAB_OFFSET -
254 itemRect->top * (infoPtr->tabHeight - 2);
256 itemRect->top = clientRect.bottom -
258 itemRect->top * ( infoPtr->tabHeight - 2);
262 itemRect->bottom = clientRect.top +
264 itemRect->top * (infoPtr->tabHeight - 2);
265 itemRect->top = clientRect.top +
267 itemRect->top * (infoPtr->tabHeight - 2);
271 * "scroll" it to make sure the item at the very left of the
272 * tab control is the leftmost visible tab.
275 -infoPtr->items[infoPtr->leftmostVisible].rect.left,
279 * Move the rectangle so the first item is slightly offset from
280 * the left of the tab control.
288 * Now, calculate the position of the item as if it were selected.
290 if (selectedRect!=NULL)
292 CopyRect(selectedRect, itemRect);
295 * The rectangle of a selected item is a bit wider.
297 InflateRect(selectedRect, SELECTED_TAB_OFFSET, 0);
300 * If it also a bit higher.
302 if (GetWindowLongA(hwnd, GWL_STYLE) & TCS_BOTTOM)
304 selectedRect->top -=2; /* the border is thicker on the bottom */
305 selectedRect->bottom +=SELECTED_TAB_OFFSET;
309 selectedRect->top -=SELECTED_TAB_OFFSET;
310 selectedRect->bottom+=1;
317 static BOOL TAB_GetItemRect(HWND hwnd, WPARAM wParam, LPARAM lParam)
319 return TAB_InternalGetItemRect(hwnd, TAB_GetInfoPtr(hwnd), (INT)wParam,
320 (LPRECT)lParam, (LPRECT)NULL);
323 /******************************************************************************
326 * This method is called to handle keyboard input
328 static LRESULT TAB_KeyUp(
332 TAB_INFO* infoPtr = TAB_GetInfoPtr(hwnd);
338 newItem = infoPtr->uFocus-1;
341 newItem = infoPtr->uFocus+1;
346 * If we changed to a valid item, change the selection
348 if ( (newItem >= 0) &&
349 (newItem < infoPtr->uNumItem) &&
350 (infoPtr->uFocus != newItem) )
352 if (!TAB_SendSimpleNotify(hwnd, TCN_SELCHANGING))
354 infoPtr->iSelected = newItem;
355 infoPtr->uFocus = newItem;
356 TAB_SendSimpleNotify(hwnd, TCN_SELCHANGE);
358 TAB_EnsureSelectionVisible(hwnd, infoPtr);
359 TAB_InvalidateTabArea(hwnd, infoPtr);
366 /******************************************************************************
369 * This method is called whenever the focus goes in or out of this control
370 * it is used to update the visual state of the control.
372 static LRESULT TAB_FocusChanging(
378 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
383 * Get the rectangle for the item.
385 isVisible = TAB_InternalGetItemRect(hwnd,
392 * If the rectangle is not completely invisible, invalidate that
393 * portion of the window.
397 InvalidateRect(hwnd, &selectedRect, TRUE);
401 * Don't otherwise disturb normal behavior.
403 return DefWindowProcA (hwnd, uMsg, wParam, lParam);
406 static HWND TAB_InternalHitTest (
416 for (iCount = 0; iCount < infoPtr->uNumItem; iCount++)
418 TAB_InternalGetItemRect(hwnd, infoPtr, iCount, &rect, NULL);
420 if (PtInRect (&rect, pt))
422 *flags = TCHT_ONITEM;
432 TAB_HitTest (HWND hwnd, WPARAM wParam, LPARAM lParam)
434 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
435 LPTCHITTESTINFO lptest=(LPTCHITTESTINFO) lParam;
437 return TAB_InternalHitTest (hwnd, infoPtr,lptest->pt,&lptest->flags);
440 /******************************************************************************
443 * Napster v2b5 has a tab control for its main navigation which has a client
444 * area that covers the whole area of the dialog pages.
445 * That's why it receives all msgs for that area and the underlying dialog ctrls
447 * So I decided that we should handle WM_NCHITTEST here and return
448 * HTTRANSPARENT if we don't hit the tab control buttons.
449 * FIXME: WM_NCHITTEST handling correct ? Fix it if you know that Windows
450 * doesn't do it that way. Maybe depends on tab control styles ?
453 TAB_NCHitTest (HWND hwnd, LPARAM lParam)
455 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
459 pt.x = LOWORD(lParam);
460 pt.y = HIWORD(lParam);
461 ScreenToClient(hwnd, &pt);
463 if (TAB_InternalHitTest(hwnd, infoPtr, pt, &dummyflag) == -1)
464 return HTTRANSPARENT;
470 TAB_LButtonDown (HWND hwnd, WPARAM wParam, LPARAM lParam)
472 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
476 if (infoPtr->hwndToolTip)
477 TAB_RelayEvent (infoPtr->hwndToolTip, hwnd,
478 WM_LBUTTONDOWN, wParam, lParam);
480 if (GetWindowLongA(hwnd, GWL_STYLE) & TCS_FOCUSONBUTTONDOWN ) {
484 if (infoPtr->hwndToolTip)
485 TAB_RelayEvent (infoPtr->hwndToolTip, hwnd,
486 WM_LBUTTONDOWN, wParam, lParam);
488 pt.x = (INT)LOWORD(lParam);
489 pt.y = (INT)HIWORD(lParam);
491 newItem=TAB_InternalHitTest (hwnd, infoPtr,pt,&dummy);
493 TRACE("On Tab, item %d\n", newItem);
495 if ( (newItem!=-1) &&
496 (infoPtr->iSelected != newItem) )
498 if (TAB_SendSimpleNotify(hwnd, TCN_SELCHANGING)!=TRUE)
500 infoPtr->iSelected = newItem;
501 infoPtr->uFocus = newItem;
502 TAB_SendSimpleNotify(hwnd, TCN_SELCHANGE);
504 TAB_EnsureSelectionVisible(hwnd, infoPtr);
506 TAB_InvalidateTabArea(hwnd, infoPtr);
513 TAB_LButtonUp (HWND hwnd, WPARAM wParam, LPARAM lParam)
515 TAB_SendSimpleNotify(hwnd, NM_CLICK);
521 TAB_RButtonDown (HWND hwnd, WPARAM wParam, LPARAM lParam)
523 TAB_SendSimpleNotify(hwnd, NM_RCLICK);
527 /******************************************************************************
528 * TAB_DrawLoneItemInterior
530 * This calls TAB_DrawItemInterior. However, TAB_DrawItemInterior is normally
531 * called by TAB_DrawItem which is normally called by TAB_Refresh which sets
532 * up the device context and font. This routine does the same setup but
533 * only calls TAB_DrawItemInterior for the single specified item.
536 TAB_DrawLoneItemInterior(HWND hwnd, TAB_INFO* infoPtr, int iItem)
538 HDC hdc = GetDC(hwnd);
539 HFONT hOldFont = SelectObject(hdc, infoPtr->hFont);
540 TAB_DrawItemInterior(hwnd, hdc, iItem, NULL);
541 SelectObject(hdc, hOldFont);
542 ReleaseDC(hwnd, hdc);
545 /******************************************************************************
546 * TAB_HotTrackTimerProc
548 * When a mouse-move event causes a tab to be highlighted (hot-tracking), a
549 * timer is setup so we can check if the mouse is moved out of our window.
550 * (We don't get an event when the mouse leaves, the mouse-move events just
551 * stop being delivered to our window and just start being delivered to
552 * another window.) This function is called when the timer triggers so
553 * we can check if the mouse has left our window. If so, we un-highlight
554 * the hot-tracked tab.
557 TAB_HotTrackTimerProc
559 HWND hwnd, /* handle of window for timer messages */
560 UINT uMsg, /* WM_TIMER message */
561 UINT idEvent, /* timer identifier */
562 DWORD dwTime /* current system time */
565 TAB_INFO* infoPtr = TAB_GetInfoPtr(hwnd);
567 if (infoPtr != NULL && infoPtr->iHotTracked >= 0)
572 ** If we can't get the cursor position, or if the cursor is outside our
573 ** window, we un-highlight the hot-tracked tab. Note that the cursor is
574 ** "outside" even if it is within our bounding rect if another window
575 ** overlaps. Note also that the case where the cursor stayed within our
576 ** window but has moved off the hot-tracked tab will be handled by the
577 ** WM_MOUSEMOVE event.
579 if (!GetCursorPos(&pt) || WindowFromPoint(pt) != hwnd)
581 /* Redraw iHotTracked to look normal */
582 INT iRedraw = infoPtr->iHotTracked;
583 infoPtr->iHotTracked = -1;
584 TAB_DrawLoneItemInterior(hwnd, infoPtr, iRedraw);
586 /* Kill this timer */
587 KillTimer(hwnd, TAB_HOTTRACK_TIMER);
592 /******************************************************************************
595 * If a tab control has the TCS_HOTTRACK style, then the tab under the mouse
596 * should be highlighted. This function determines which tab in a tab control,
597 * if any, is under the mouse and records that information. The caller may
598 * supply output parameters to receive the item number of the tab item which
599 * was highlighted but isn't any longer and of the tab item which is now
600 * highlighted but wasn't previously. The caller can use this information to
601 * selectively redraw those tab items.
603 * If the caller has a mouse position, it can supply it through the pos
604 * parameter. For example, TAB_MouseMove does this. Otherwise, the caller
605 * supplies NULL and this function determines the current mouse position
613 int* out_redrawLeave,
617 TAB_INFO* infoPtr = TAB_GetInfoPtr(hwnd);
622 if (out_redrawLeave != NULL)
623 *out_redrawLeave = -1;
624 if (out_redrawEnter != NULL)
625 *out_redrawEnter = -1;
627 if (GetWindowLongA(hwnd, GWL_STYLE) & TCS_HOTTRACK)
635 ScreenToClient(hwnd, &pt);
643 item = TAB_InternalHitTest(hwnd, infoPtr, pt, &flags);
646 if (item != infoPtr->iHotTracked)
648 if (infoPtr->iHotTracked >= 0)
650 /* Mark currently hot-tracked to be redrawn to look normal */
651 if (out_redrawLeave != NULL)
652 *out_redrawLeave = infoPtr->iHotTracked;
656 /* Kill timer which forces recheck of mouse pos */
657 KillTimer(hwnd, TAB_HOTTRACK_TIMER);
662 /* Start timer so we recheck mouse pos */
663 UINT timerID = SetTimer
667 TAB_HOTTRACK_TIMER_INTERVAL,
668 TAB_HotTrackTimerProc
672 return; /* Hot tracking not available */
675 infoPtr->iHotTracked = item;
679 /* Mark new hot-tracked to be redrawn to look highlighted */
680 if (out_redrawEnter != NULL)
681 *out_redrawEnter = item;
686 /******************************************************************************
689 * Handles the mouse-move event. Updates tooltips. Updates hot-tracking.
692 TAB_MouseMove (HWND hwnd, WPARAM wParam, LPARAM lParam)
697 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
699 if (infoPtr->hwndToolTip)
700 TAB_RelayEvent (infoPtr->hwndToolTip, hwnd,
701 WM_LBUTTONDOWN, wParam, lParam);
703 /* Determine which tab to highlight. Redraw tabs which change highlight
705 TAB_RecalcHotTrack(hwnd, &lParam, &redrawLeave, &redrawEnter);
707 if (redrawLeave != -1)
708 TAB_DrawLoneItemInterior(hwnd, infoPtr, redrawLeave);
709 if (redrawEnter != -1)
710 TAB_DrawLoneItemInterior(hwnd, infoPtr, redrawEnter);
715 /******************************************************************************
718 * Calculates the tab control's display area given the window rectangle or
719 * the window rectangle given the requested display rectangle.
721 static LRESULT TAB_AdjustRect(
726 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
731 * Go from display rectangle
735 * Add the height of the tabs.
737 if (GetWindowLongA(hwnd, GWL_STYLE) & TCS_BOTTOM)
738 prc->bottom += (infoPtr->tabHeight - 2) * (infoPtr->uNumRows + 1) + 2;
740 prc->top -= (infoPtr->tabHeight - 2) * (infoPtr->uNumRows + 1) + 2;
743 * Inflate the rectangle for the padding
745 InflateRect(prc, DISPLAY_AREA_PADDINGX, DISPLAY_AREA_PADDINGY);
748 * Inflate for the border
750 InflateRect(prc, CONTROL_BORDER_SIZEX, CONTROL_BORDER_SIZEX);
755 * Go from window rectangle.
759 * Deflate the rectangle for the border
761 InflateRect(prc, -CONTROL_BORDER_SIZEX, -CONTROL_BORDER_SIZEX);
764 * Deflate the rectangle for the padding
766 InflateRect(prc, -DISPLAY_AREA_PADDINGX, -DISPLAY_AREA_PADDINGY);
769 * Remove the height of the tabs.
771 if (GetWindowLongA(hwnd, GWL_STYLE) & TCS_BOTTOM)
772 prc->bottom -= (infoPtr->tabHeight - 2) * (infoPtr->uNumRows + 1) + 2;
774 prc->top += (infoPtr->tabHeight - 2) * (infoPtr->uNumRows + 1) + 2;
781 /******************************************************************************
784 * This method will handle the notification from the scroll control and
785 * perform the scrolling operation on the tab control.
787 static LRESULT TAB_OnHScroll(
793 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
795 if(nScrollCode == SB_THUMBPOSITION && nPos != infoPtr->leftmostVisible)
797 if(nPos < infoPtr->leftmostVisible)
798 infoPtr->leftmostVisible--;
800 infoPtr->leftmostVisible++;
802 TAB_RecalcHotTrack(hwnd, NULL, NULL, NULL);
803 TAB_InvalidateTabArea(hwnd, infoPtr);
804 SendMessageA(infoPtr->hwndUpDown, UDM_SETPOS, 0,
805 MAKELONG(infoPtr->leftmostVisible, 0));
811 /******************************************************************************
814 * This method will check the current scrolling state and make sure the
815 * scrolling control is displayed (or not).
817 static void TAB_SetupScrolling(
820 const RECT* clientRect)
823 if (infoPtr->needsScrolling)
829 * Calculate the position of the scroll control.
831 controlPos.right = clientRect->right;
832 controlPos.left = controlPos.right - 2*GetSystemMetrics(SM_CXHSCROLL);
834 if (GetWindowLongA(hwnd, GWL_STYLE) & TCS_BOTTOM)
836 controlPos.top = clientRect->bottom - infoPtr->tabHeight;
837 controlPos.bottom = controlPos.top + GetSystemMetrics(SM_CYHSCROLL);
841 controlPos.bottom = clientRect->top + infoPtr->tabHeight;
842 controlPos.top = controlPos.bottom - GetSystemMetrics(SM_CYHSCROLL);
846 * If we don't have a scroll control yet, we want to create one.
847 * If we have one, we want to make sure it's positioned right.
849 if (infoPtr->hwndUpDown==0)
851 infoPtr->hwndUpDown = CreateWindowA("msctls_updown32",
853 WS_VISIBLE | WS_CHILD | UDS_HORZ,
854 controlPos.left, controlPos.top,
855 controlPos.right - controlPos.left,
856 controlPos.bottom - controlPos.top,
864 SetWindowPos(infoPtr->hwndUpDown,
866 controlPos.left, controlPos.top,
867 controlPos.right - controlPos.left,
868 controlPos.bottom - controlPos.top,
869 SWP_SHOWWINDOW | SWP_NOZORDER);
872 /* Now calculate upper limit of the updown control range.
873 * We do this by calculating how many tabs will be offscreen when the
874 * last tab is visible.
876 if(infoPtr->uNumItem)
878 vsize = clientRect->right - (controlPos.right - controlPos.left + 1);
879 maxRange = infoPtr->uNumItem;
880 tabwidth = infoPtr->items[maxRange-1].rect.right;
882 for(; maxRange > 0; maxRange--)
884 if(tabwidth - infoPtr->items[maxRange - 1].rect.left > vsize)
888 if(maxRange == infoPtr->uNumItem)
895 * If we once had a scroll control... hide it.
897 if (infoPtr->hwndUpDown!=0)
899 ShowWindow(infoPtr->hwndUpDown, SW_HIDE);
902 if (infoPtr->hwndUpDown)
903 SendMessageA(infoPtr->hwndUpDown, UDM_SETRANGE32, 0, maxRange);
906 /******************************************************************************
909 * This method will calculate the position rectangles of all the items in the
910 * control. The rectangle calculated starts at 0 for the first item in the
911 * list and ignores scrolling and selection.
912 * It also uses the current font to determine the height of the tab row and
913 * it checks if all the tabs fit in the client area of the window. If they
914 * dont, a scrolling control is added.
916 static void TAB_SetItemBounds (HWND hwnd)
918 TAB_INFO* infoPtr = TAB_GetInfoPtr(hwnd);
919 LONG lStyle = GetWindowLongA(hwnd, GWL_STYLE);
920 TEXTMETRICA fontMetrics;
924 HFONT hFont, hOldFont;
930 * We need to get text information so we need a DC and we need to select
935 hFont = infoPtr->hFont ? infoPtr->hFont : GetStockObject (SYSTEM_FONT);
936 hOldFont = SelectObject (hdc, hFont);
939 * We will base the rectangle calculations on the client rectangle
942 GetClientRect(hwnd, &clientRect);
945 * The leftmost item will be "0" aligned
950 if ( !(lStyle & TCS_FIXEDWIDTH) && !((lStyle & TCS_OWNERDRAWFIXED) && infoPtr->fSizeSet) )
956 * Use the current font to determine the height of a tab.
958 GetTextMetricsA(hdc, &fontMetrics);
961 * Get the icon height
964 ImageList_GetIconSize(infoPtr->himl, 0, &icon_height);
967 * Take the highest between font or icon
969 if (fontMetrics.tmHeight > icon_height)
970 item_height = fontMetrics.tmHeight;
972 item_height = icon_height;
975 * Make sure there is enough space for the letters + icon + growing the
976 * selected item + extra space for the selected item.
978 infoPtr->tabHeight = item_height + 2*VERTICAL_ITEM_PADDING +
982 for (curItem = 0; curItem < infoPtr->uNumItem; curItem++)
985 * Set the leftmost position of the tab.
987 infoPtr->items[curItem].rect.left = curItemLeftPos;
989 if ( (lStyle & TCS_FIXEDWIDTH) || ((lStyle & TCS_OWNERDRAWFIXED) && infoPtr->fSizeSet))
991 infoPtr->items[curItem].rect.right = infoPtr->items[curItem].rect.left +
993 2*HORIZONTAL_ITEM_PADDING;
1001 * Calculate how wide the tab is depending on the text it contains
1003 GetTextExtentPoint32A(hdc, infoPtr->items[curItem].pszText,
1004 lstrlenA(infoPtr->items[curItem].pszText), &size);
1006 * under Windows, there seems to be a minimum width of 2x the height
1007 * for button style tabs
1009 if (lStyle & TCS_BUTTONS)
1010 size.cx = max(size.cx, 2*infoPtr->tabHeight);
1013 * Add the icon width
1017 ImageList_GetIconSize(infoPtr->himl, &icon_width, 0);
1021 infoPtr->items[curItem].rect.right = infoPtr->items[curItem].rect.left +
1022 size.cx + icon_width +
1023 num*HORIZONTAL_ITEM_PADDING;
1027 * Check if this is a multiline tab control and if so
1028 * check to see if we should wrap the tabs
1030 * Because we are going to arange all these tabs evenly
1031 * really we are basically just counting rows at this point
1035 if ((lStyle & TCS_MULTILINE)&&
1036 (infoPtr->items[curItem].rect.right > clientRect.right))
1038 infoPtr->items[curItem].rect.right -=
1039 infoPtr->items[curItem].rect.left;
1040 infoPtr->items[curItem].rect.left = 0;
1044 infoPtr->items[curItem].rect.bottom = 0;
1045 infoPtr->items[curItem].rect.top = curItemRowCount;
1047 TRACE("TextSize: %li\n", size.cx);
1048 TRACE("Rect: T %i, L %i, B %i, R %i\n",
1049 infoPtr->items[curItem].rect.top,
1050 infoPtr->items[curItem].rect.left,
1051 infoPtr->items[curItem].rect.bottom,
1052 infoPtr->items[curItem].rect.right);
1055 * The leftmost position of the next item is the rightmost position
1058 if (lStyle & TCS_BUTTONS)
1060 curItemLeftPos = infoPtr->items[curItem].rect.right + BUTTON_SPACINGX;
1061 if (lStyle & TCS_FLATBUTTONS)
1062 curItemLeftPos += FLAT_BTN_SPACINGX;
1065 curItemLeftPos = infoPtr->items[curItem].rect.right;
1068 if (!(lStyle & TCS_MULTILINE))
1071 * Check if we need a scrolling control.
1073 infoPtr->needsScrolling = (curItemLeftPos + (2*SELECTED_TAB_OFFSET) >
1076 /* Don't need scrolling, then update infoPtr->leftmostVisible */
1077 if(!infoPtr->needsScrolling)
1078 infoPtr->leftmostVisible = 0;
1080 TAB_SetupScrolling(hwnd, infoPtr, &clientRect);
1084 * Set the number of rows
1087 infoPtr->uNumRows = curItemRowCount;
1089 if ((lStyle & TCS_MULTILINE)&&(infoPtr->uNumItem > 0))
1091 INT widthDiff,remainder;
1092 INT tabPerRow,remTab;
1094 INT iIndexStart=0,iIndexEnd=0, iCount=0;
1097 * Ok Microsoft trys to even out the rows. place the same
1098 * number of tabs in each row. So lets give that a shot
1102 tabPerRow = infoPtr->uNumItem / (infoPtr->uNumRows + 1);
1103 remTab = infoPtr->uNumItem % (infoPtr->uNumRows + 1);
1105 for (iItm=0,iRow=0,iCount=0,curItemLeftPos=0;
1106 iItm<infoPtr->uNumItem;
1109 if (iCount >= ((iRow<remTab)?tabPerRow+1:tabPerRow))
1116 * normalize the current rect
1118 infoPtr->items[iItm].rect.right -=
1119 infoPtr->items[iItm].rect.left;
1120 infoPtr->items[iItm].rect.left = 0;
1122 infoPtr->items[iItm].rect.top = iRow;
1123 infoPtr->items[iItm].rect.left +=curItemLeftPos;
1124 infoPtr->items[iItm].rect.right +=curItemLeftPos;
1125 if (lStyle & TCS_BUTTONS)
1127 curItemLeftPos = infoPtr->items[iItm].rect.right + BUTTON_SPACINGX;
1128 if (lStyle & TCS_FLATBUTTONS)
1129 curItemLeftPos += FLAT_BTN_SPACINGX;
1132 curItemLeftPos = infoPtr->items[iItm].rect.right;
1140 while(iIndexStart < infoPtr->uNumItem)
1143 * find the indexs of the row
1145 for (iIndexEnd=iIndexStart;
1146 (iIndexEnd < infoPtr->uNumItem) &&
1147 (infoPtr->items[iIndexEnd].rect.top ==
1148 infoPtr->items[iIndexStart].rect.top) ;
1150 /* intentionaly blank */;
1153 * we need to justify these tabs so they fill the whole given
1157 widthDiff = clientRect.right - (2*SELECTED_TAB_OFFSET) -
1158 infoPtr->items[iIndexEnd-1].rect.right;
1160 iCount = iIndexEnd-iIndexStart;
1165 remainder = widthDiff % iCount;
1166 widthDiff = widthDiff / iCount;
1167 for (iIndex=iIndexStart,iCount=0; iIndex < iIndexEnd;
1170 infoPtr->items[iIndex].rect.left +=iCount*widthDiff;
1171 infoPtr->items[iIndex].rect.right +=(iCount+1)*widthDiff;
1173 infoPtr->items[iIndex-1].rect.right += remainder;
1176 iIndexStart=iIndexEnd;
1181 TAB_EnsureSelectionVisible(hwnd,infoPtr);
1182 TAB_RecalcHotTrack(hwnd, NULL, NULL, NULL);
1186 SelectObject (hdc, hOldFont);
1187 ReleaseDC (hwnd, hdc);
1190 /******************************************************************************
1191 * TAB_DrawItemInterior
1193 * This method is used to draw the interior (text and icon) of a single tab
1194 * into the tab control.
1197 TAB_DrawItemInterior
1205 TAB_INFO* infoPtr = TAB_GetInfoPtr(hwnd);
1206 LONG lStyle = GetWindowLongA(hwnd, GWL_STYLE);
1210 HPEN htextPen = GetSysColorPen (COLOR_BTNTEXT);
1214 if (drawRect == NULL)
1221 * Get the rectangle for the item.
1223 isVisible = TAB_InternalGetItemRect
1235 * Make sure drawRect points to something valid; simplifies code.
1237 drawRect = &localRect;
1240 * This logic copied from the part of TAB_DrawItem which draws
1241 * the tab background. It's important to keep it in sync. I
1242 * would have liked to avoid code duplication, but couldn't figure
1243 * out how without making spaghetti of TAB_DrawItem.
1245 if (lStyle & TCS_BUTTONS)
1247 *drawRect = itemRect;
1248 if (iItem == infoPtr->iSelected)
1256 if (iItem == infoPtr->iSelected)
1257 *drawRect = selectedRect;
1259 *drawRect = itemRect;
1268 holdPen = SelectObject(hdc, htextPen);
1270 oldBkMode = SetBkMode(hdc, TRANSPARENT);
1276 (iItem == infoPtr->iHotTracked) ? COLOR_HIGHLIGHT : COLOR_BTNTEXT
1281 * Deflate the rectangle to acount for the padding
1283 InflateRect(drawRect, -HORIZONTAL_ITEM_PADDING, -VERTICAL_ITEM_PADDING);
1286 * if owner draw, tell the owner to draw
1288 if ( (lStyle & TCS_OWNERDRAWFIXED) && GetParent(hwnd) )
1294 * get the control id
1296 id = GetWindowLongA( hwnd, GWL_ID );
1299 * put together the DRAWITEMSTRUCT
1301 dis.CtlType = ODT_TAB;
1304 dis.itemAction = ODA_DRAWENTIRE;
1305 if ( iItem == infoPtr->iSelected )
1306 dis.itemState = ODS_SELECTED;
1309 dis.hwndItem = hwnd; /* */
1311 dis.rcItem = *drawRect; /* */
1312 dis.itemData = infoPtr->items[iItem].lParam;
1315 * send the draw message
1317 SendMessageA( GetParent(hwnd), WM_DRAWITEM, (WPARAM)id, (LPARAM)&dis );
1324 * If not owner draw, then do the drawing ourselves.
1328 if (infoPtr->himl && (infoPtr->items[iItem].mask & TCIF_IMAGE))
1336 infoPtr->items[iItem].iImage,
1342 ImageList_GetIconSize(infoPtr->himl, &cx, &cy);
1343 drawRect->left += (cx + HORIZONTAL_ITEM_PADDING);
1349 if (lStyle & TCS_RIGHTJUSTIFY)
1350 uHorizAlign = DT_CENTER;
1352 uHorizAlign = DT_LEFT;
1357 infoPtr->items[iItem].pszText,
1358 lstrlenA(infoPtr->items[iItem].pszText),
1360 uHorizAlign | DT_SINGLELINE | DT_VCENTER
1367 SetBkMode(hdc, oldBkMode);
1368 SelectObject(hdc, holdPen);
1371 /******************************************************************************
1374 * This method is used to draw a single tab into the tab control.
1376 static void TAB_DrawItem(
1381 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
1382 LONG lStyle = GetWindowLongA(hwnd, GWL_STYLE);
1389 * Get the rectangle for the item.
1391 isVisible = TAB_InternalGetItemRect(hwnd,
1399 HBRUSH hbr = CreateSolidBrush (GetSysColor(COLOR_BTNFACE));
1400 HPEN hwPen = GetSysColorPen (COLOR_3DHILIGHT);
1401 HPEN hbPen = GetSysColorPen (COLOR_BTNSHADOW);
1402 HPEN hfocusPen = CreatePen(PS_DOT, 1, GetSysColor(COLOR_BTNTEXT));
1405 BOOL deleteBrush = TRUE;
1407 if (lStyle & TCS_BUTTONS)
1410 * Get item rectangle.
1414 holdPen = SelectObject (hdc, hwPen);
1417 * Separators between flat buttons
1419 if (lStyle & TCS_FLATBUTTONS)
1422 int x = r.right+FLAT_BTN_SPACINGX-2;
1423 MoveToEx (hdc, x, r.bottom-1, NULL);
1424 LineTo (hdc, x, r.top-1);
1427 SelectObject(hdc, hbPen);
1428 MoveToEx (hdc, x, r.bottom-1, NULL);
1429 LineTo (hdc, x, r.top-1);
1432 if (iItem == infoPtr->iSelected)
1437 if (!((lStyle & TCS_OWNERDRAWFIXED) && infoPtr->fSizeSet))
1439 COLORREF bk = GetSysColor(COLOR_3DHILIGHT);
1441 hbr = GetSysColorBrush(COLOR_SCROLLBAR);
1442 SetTextColor(hdc, GetSysColor(COLOR_3DFACE));
1443 SetBkColor(hdc, bk);
1445 /* if COLOR_WINDOW happens to be the same as COLOR_3DHILIGHT
1446 * we better use 0x55aa bitmap brush to make scrollbar's background
1447 * look different from the window background.
1449 if (bk == GetSysColor(COLOR_WINDOW))
1450 hbr = CACHE_GetPattern55AABrush();
1452 deleteBrush = FALSE;
1456 * Erase the background.
1458 FillRect(hdc, &r, hbr);
1462 * The rectangles calculated exclude the right and bottom
1463 * borders of the rectangle. To simply the following code, those
1464 * borders are shaved-off beforehand.
1470 SelectObject(hdc, hwPen);
1471 MoveToEx (hdc, r.left, r.bottom, NULL);
1472 LineTo (hdc, r.right, r.bottom);
1473 LineTo (hdc, r.right, r.top);
1476 SelectObject(hdc, hbPen);
1477 LineTo (hdc, r.left, r.top);
1478 LineTo (hdc, r.left, r.bottom);
1483 * Erase the background.
1485 FillRect(hdc, &r, hbr);
1487 if (!(lStyle & TCS_FLATBUTTONS))
1490 MoveToEx (hdc, r.left, r.bottom, NULL);
1491 LineTo (hdc, r.left, r.top);
1492 LineTo (hdc, r.right, r.top);
1495 SelectObject(hdc, hbPen);
1496 LineTo (hdc, r.right, r.bottom);
1497 LineTo (hdc, r.left, r.bottom);
1507 hbr = CreateSolidBrush(GetSysColor(COLOR_BTNFACE));
1510 * We draw a rectangle of different sizes depending on the selection
1513 if (iItem == infoPtr->iSelected)
1519 * Erase the background.
1520 * This is necessary when drawing the selected item since it is larger
1521 * than the others, it might overlap with stuff already drawn by the
1524 FillRect(hdc, &r, hbr);
1528 * The rectangles calculated exclude the right and bottom
1529 * borders of the rectangle. To simply the following code, those
1530 * borders are shaved-off beforehand.
1535 holdPen = SelectObject (hdc, hwPen);
1537 if (lStyle & TCS_BOTTOM)
1540 MoveToEx (hdc, r.left, r.top, NULL);
1541 LineTo (hdc, r.left, r.bottom - ROUND_CORNER_SIZE);
1542 LineTo (hdc, r.left + ROUND_CORNER_SIZE, r.bottom);
1545 SelectObject(hdc, hbPen);
1546 LineTo (hdc, r.right - ROUND_CORNER_SIZE, r.bottom);
1547 LineTo (hdc, r.right, r.bottom - ROUND_CORNER_SIZE);
1548 LineTo (hdc, r.right, r.top);
1553 MoveToEx (hdc, r.left, r.bottom, NULL);
1554 LineTo (hdc, r.left, r.top + ROUND_CORNER_SIZE);
1555 LineTo (hdc, r.left + ROUND_CORNER_SIZE, r.top);
1556 LineTo (hdc, r.right - ROUND_CORNER_SIZE, r.top);
1559 SelectObject(hdc, hbPen);
1560 LineTo (hdc, r.right, r.top + ROUND_CORNER_SIZE);
1561 LineTo (hdc, r.right, r.bottom);
1565 oldBkMode = SetBkMode(hdc, TRANSPARENT);
1567 /* This modifies r to be the text rectangle. */
1568 TAB_DrawItemInterior(hwnd, hdc, iItem, &r);
1571 * Draw the focus rectangle
1573 if (((lStyle & TCS_FOCUSNEVER) == 0) &&
1574 (GetFocus() == hwnd) &&
1575 (iItem == infoPtr->uFocus) )
1578 InflateRect(&r, -1, -1);
1580 SelectObject(hdc, hfocusPen);
1582 MoveToEx (hdc, r.left, r.top, NULL);
1583 LineTo (hdc, r.right-1, r.top);
1584 LineTo (hdc, r.right-1, r.bottom -1);
1585 LineTo (hdc, r.left, r.bottom -1);
1586 LineTo (hdc, r.left, r.top);
1592 SetBkMode(hdc, oldBkMode);
1593 SelectObject(hdc, holdPen);
1594 DeleteObject(hfocusPen);
1595 if (deleteBrush) DeleteObject(hbr);
1599 /******************************************************************************
1602 * This method is used to draw the raised border around the tab control
1605 static void TAB_DrawBorder (HWND hwnd, HDC hdc)
1607 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
1609 HPEN hwPen = GetSysColorPen (COLOR_3DHILIGHT);
1610 HPEN hbPen = GetSysColorPen (COLOR_3DDKSHADOW);
1611 HPEN hShade = GetSysColorPen (COLOR_BTNSHADOW);
1614 GetClientRect (hwnd, &rect);
1617 * Adjust for the style
1619 if (GetWindowLongA(hwnd, GWL_STYLE) & TCS_BOTTOM)
1621 rect.bottom -= (infoPtr->tabHeight - 2) * (infoPtr->uNumRows + 1) + 2;
1625 rect.top += (infoPtr->tabHeight - 2) * (infoPtr->uNumRows + 1) + 2;
1629 * Shave-off the right and bottom margins (exluded in the
1636 htmPen = SelectObject (hdc, hwPen);
1638 MoveToEx (hdc, rect.left, rect.bottom, NULL);
1639 LineTo (hdc, rect.left, rect.top);
1640 LineTo (hdc, rect.right, rect.top);
1643 SelectObject (hdc, hbPen);
1644 LineTo (hdc, rect.right, rect.bottom );
1645 LineTo (hdc, rect.left, rect.bottom);
1648 SelectObject (hdc, hShade );
1649 MoveToEx (hdc, rect.right-1, rect.top, NULL);
1650 LineTo (hdc, rect.right-1, rect.bottom-1);
1651 LineTo (hdc, rect.left, rect.bottom-1);
1653 SelectObject(hdc, htmPen);
1656 /******************************************************************************
1659 * This method repaints the tab control..
1661 static void TAB_Refresh (HWND hwnd, HDC hdc)
1663 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
1667 if (!infoPtr->DoRedraw)
1670 hOldFont = SelectObject (hdc, infoPtr->hFont);
1672 if (GetWindowLongA(hwnd, GWL_STYLE) & TCS_BUTTONS)
1674 for (i = 0; i < infoPtr->uNumItem; i++)
1676 TAB_DrawItem (hwnd, hdc, i);
1682 * Draw all the non selected item first.
1684 for (i = 0; i < infoPtr->uNumItem; i++)
1686 if (i != infoPtr->iSelected)
1687 TAB_DrawItem (hwnd, hdc, i);
1691 * Now, draw the border, draw it before the selected item
1692 * since the selected item overwrites part of the border.
1694 TAB_DrawBorder (hwnd, hdc);
1697 * Then, draw the selected item
1699 TAB_DrawItem (hwnd, hdc, infoPtr->iSelected);
1702 * If we haven't set the current focus yet, set it now.
1703 * Only happens when we first paint the tab controls.
1705 if (infoPtr->uFocus == -1)
1706 TAB_SetCurFocus(hwnd, infoPtr->iSelected);
1709 SelectObject (hdc, hOldFont);
1713 TAB_GetRowCount (HWND hwnd )
1715 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
1717 return infoPtr->uNumRows;
1721 TAB_SetRedraw (HWND hwnd, WPARAM wParam)
1723 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
1725 infoPtr->DoRedraw=(BOOL) wParam;
1729 static LRESULT TAB_EraseBackground(
1736 HBRUSH brush = CreateSolidBrush(GetSysColor(COLOR_BTNFACE));
1738 hdc = givenDC ? givenDC : GetDC(hwnd);
1740 GetClientRect(hwnd, &clientRect);
1742 FillRect(hdc, &clientRect, brush);
1745 ReleaseDC(hwnd, hdc);
1747 DeleteObject(brush);
1752 /******************************************************************************
1753 * TAB_EnsureSelectionVisible
1755 * This method will make sure that the current selection is completely
1756 * visible by scrolling until it is.
1758 static void TAB_EnsureSelectionVisible(
1762 INT iSelected = infoPtr->iSelected;
1764 INT iOrigLeftmostVisible = infoPtr->leftmostVisible;
1767 * set the items row to the bottommost row or topmost row depending on
1771 if (infoPtr->uNumRows > 0)
1773 INT newselected=infoPtr->items[iSelected].rect.top;
1775 LONG lStyle = GetWindowLongA(hwnd, GWL_STYLE);
1777 if (lStyle & TCS_BOTTOM)
1780 iTargetRow = infoPtr->uNumRows;
1782 if (newselected != iTargetRow)
1785 for (i=0; i < infoPtr->uNumItem; i++)
1786 if (infoPtr->items[i].rect.top == newselected )
1787 infoPtr->items[i].rect.top = iTargetRow;
1788 else if (lStyle&TCS_BOTTOM)
1790 if (infoPtr->items[i].rect.top < newselected)
1791 infoPtr->items[i].rect.top+=1;
1795 if (infoPtr->items[i].rect.top > newselected)
1796 infoPtr->items[i].rect.top-=1;
1799 TAB_RecalcHotTrack(hwnd, NULL, NULL, NULL);
1804 * Do the trivial cases first.
1806 if ( (!infoPtr->needsScrolling) ||
1807 (infoPtr->hwndUpDown==0) )
1810 if (infoPtr->leftmostVisible >= iSelected)
1812 infoPtr->leftmostVisible = iSelected;
1819 * Calculate the part of the client area that is visible.
1821 GetClientRect(hwnd, &r);
1824 GetClientRect(infoPtr->hwndUpDown, &r);
1827 if ((infoPtr->items[iSelected].rect.right -
1828 infoPtr->items[iSelected].rect.left) >= width )
1830 /* Special case: width of selected item is greater than visible
1833 infoPtr->leftmostVisible = iSelected;
1837 for (i = infoPtr->leftmostVisible; i < infoPtr->uNumItem; i++)
1839 if ((infoPtr->items[iSelected].rect.right -
1840 infoPtr->items[i].rect.left) < width)
1843 infoPtr->leftmostVisible = i;
1847 if (infoPtr->leftmostVisible != iOrigLeftmostVisible)
1848 TAB_RecalcHotTrack(hwnd, NULL, NULL, NULL);
1850 SendMessageA(infoPtr->hwndUpDown, UDM_SETPOS, 0,
1851 MAKELONG(infoPtr->leftmostVisible, 0));
1854 /******************************************************************************
1855 * TAB_InvalidateTabArea
1857 * This method will invalidate the portion of the control that contains the
1858 * tabs. It is called when the state of the control changes and needs
1861 static void TAB_InvalidateTabArea(
1867 GetClientRect(hwnd, &clientRect);
1869 if (GetWindowLongA(hwnd, GWL_STYLE) & TCS_BOTTOM)
1871 clientRect.top = clientRect.bottom - (infoPtr->tabHeight *
1872 (infoPtr->uNumRows + 1) + 3);
1876 clientRect.bottom = clientRect.top + (infoPtr->tabHeight *
1877 (infoPtr->uNumRows + 1) + 1);
1880 InvalidateRect(hwnd, &clientRect, TRUE);
1884 TAB_Paint (HWND hwnd, WPARAM wParam)
1889 hdc = wParam== 0 ? BeginPaint (hwnd, &ps) : (HDC)wParam;
1890 TAB_Refresh (hwnd, hdc);
1893 EndPaint (hwnd, &ps);
1899 TAB_InsertItem (HWND hwnd, WPARAM wParam, LPARAM lParam)
1901 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
1906 GetClientRect (hwnd, &rect);
1907 TRACE("Rect: %x T %i, L %i, B %i, R %i\n", hwnd,
1908 rect.top, rect.left, rect.bottom, rect.right);
1910 pti = (TCITEMA *)lParam;
1911 iItem = (INT)wParam;
1913 if (iItem < 0) return -1;
1914 if (iItem > infoPtr->uNumItem)
1915 iItem = infoPtr->uNumItem;
1917 if (infoPtr->uNumItem == 0) {
1918 infoPtr->items = COMCTL32_Alloc (sizeof (TAB_ITEM));
1919 infoPtr->uNumItem++;
1920 infoPtr->iSelected = 0;
1923 TAB_ITEM *oldItems = infoPtr->items;
1925 infoPtr->uNumItem++;
1926 infoPtr->items = COMCTL32_Alloc (sizeof (TAB_ITEM) * infoPtr->uNumItem);
1928 /* pre insert copy */
1930 memcpy (&infoPtr->items[0], &oldItems[0],
1931 iItem * sizeof(TAB_ITEM));
1934 /* post insert copy */
1935 if (iItem < infoPtr->uNumItem - 1) {
1936 memcpy (&infoPtr->items[iItem+1], &oldItems[iItem],
1937 (infoPtr->uNumItem - iItem - 1) * sizeof(TAB_ITEM));
1941 if (iItem <= infoPtr->iSelected)
1942 infoPtr->iSelected++;
1944 COMCTL32_Free (oldItems);
1947 infoPtr->items[iItem].mask = pti->mask;
1948 if (pti->mask & TCIF_TEXT) {
1949 len = lstrlenA (pti->pszText);
1950 infoPtr->items[iItem].pszText = COMCTL32_Alloc (len+1);
1951 strcpy (infoPtr->items[iItem].pszText, pti->pszText);
1952 infoPtr->items[iItem].cchTextMax = pti->cchTextMax;
1955 if (pti->mask & TCIF_IMAGE)
1956 infoPtr->items[iItem].iImage = pti->iImage;
1958 if (pti->mask & TCIF_PARAM)
1959 infoPtr->items[iItem].lParam = pti->lParam;
1961 TAB_SetItemBounds(hwnd);
1962 TAB_InvalidateTabArea(hwnd, infoPtr);
1964 TRACE("[%04x]: added item %d '%s'\n",
1965 hwnd, iItem, infoPtr->items[iItem].pszText);
1971 TAB_SetItemSize (HWND hwnd, WPARAM wParam, LPARAM lParam)
1973 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
1974 LONG lStyle = GetWindowLongA(hwnd, GWL_STYLE);
1977 if ((lStyle & TCS_FIXEDWIDTH) || (lStyle & TCS_OWNERDRAWFIXED))
1979 lResult = MAKELONG(infoPtr->tabWidth, infoPtr->tabHeight);
1980 infoPtr->tabWidth = (INT)LOWORD(lParam);
1981 infoPtr->tabHeight = (INT)HIWORD(lParam);
1983 infoPtr->fSizeSet = TRUE;
1989 TAB_SetItemA (HWND hwnd, WPARAM wParam, LPARAM lParam)
1991 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
1997 tabItem=(LPTCITEMA ) lParam;
1998 TRACE("%d %p\n",iItem, tabItem);
1999 if ((iItem<0) || (iItem>=infoPtr->uNumItem)) return FALSE;
2001 wineItem=& infoPtr->items[iItem];
2003 if (tabItem->mask & TCIF_IMAGE)
2004 wineItem->iImage=tabItem->iImage;
2006 if (tabItem->mask & TCIF_PARAM)
2007 wineItem->lParam=tabItem->lParam;
2009 if (tabItem->mask & TCIF_RTLREADING)
2010 FIXME("TCIF_RTLREADING\n");
2012 if (tabItem->mask & TCIF_STATE)
2013 wineItem->dwState=tabItem->dwState;
2015 if (tabItem->mask & TCIF_TEXT) {
2016 len=lstrlenA (tabItem->pszText);
2017 if (len>wineItem->cchTextMax)
2018 wineItem->pszText= COMCTL32_ReAlloc (wineItem->pszText, len+1);
2019 strcpy (wineItem->pszText, tabItem->pszText);
2023 * Update and repaint tabs.
2025 TAB_SetItemBounds(hwnd);
2026 TAB_InvalidateTabArea(hwnd,infoPtr);
2032 TAB_GetItemCount (HWND hwnd, WPARAM wParam, LPARAM lParam)
2034 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2036 return infoPtr->uNumItem;
2041 TAB_GetItemA (HWND hwnd, WPARAM wParam, LPARAM lParam)
2043 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2049 tabItem=(LPTCITEMA) lParam;
2051 if ((iItem<0) || (iItem>=infoPtr->uNumItem)) return FALSE;
2053 wineItem=& infoPtr->items[iItem];
2055 if (tabItem->mask & TCIF_IMAGE)
2056 tabItem->iImage=wineItem->iImage;
2058 if (tabItem->mask & TCIF_PARAM)
2059 tabItem->lParam=wineItem->lParam;
2061 if (tabItem->mask & TCIF_RTLREADING)
2062 FIXME("TCIF_RTLREADING\n");
2064 if (tabItem->mask & TCIF_STATE)
2065 tabItem->dwState=wineItem->dwState;
2067 if (tabItem->mask & TCIF_TEXT)
2068 lstrcpynA (tabItem->pszText, wineItem->pszText, tabItem->cchTextMax);
2074 TAB_DeleteItem (HWND hwnd, WPARAM wParam, LPARAM lParam)
2076 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2077 INT iItem = (INT) wParam;
2078 BOOL bResult = FALSE;
2080 if ((iItem >= 0) && (iItem < infoPtr->uNumItem))
2082 TAB_ITEM *oldItems = infoPtr->items;
2084 infoPtr->uNumItem--;
2085 infoPtr->items = COMCTL32_Alloc(sizeof (TAB_ITEM) * infoPtr->uNumItem);
2088 memcpy(&infoPtr->items[0], &oldItems[0], iItem * sizeof(TAB_ITEM));
2090 if (iItem < infoPtr->uNumItem)
2091 memcpy(&infoPtr->items[iItem], &oldItems[iItem + 1],
2092 (infoPtr->uNumItem - iItem) * sizeof(TAB_ITEM));
2094 COMCTL32_Free (oldItems);
2097 * Readjust the selected index.
2099 if ((iItem == infoPtr->iSelected) && (iItem > 0))
2100 infoPtr->iSelected--;
2102 if (iItem < infoPtr->iSelected)
2103 infoPtr->iSelected--;
2105 if (infoPtr->uNumItem == 0)
2106 infoPtr->iSelected = -1;
2109 * Reposition and repaint tabs.
2111 TAB_SetItemBounds(hwnd);
2112 TAB_InvalidateTabArea(hwnd,infoPtr);
2121 TAB_DeleteAllItems (HWND hwnd, WPARAM wParam, LPARAM lParam)
2123 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2125 COMCTL32_Free (infoPtr->items);
2126 infoPtr->uNumItem = 0;
2127 infoPtr->iSelected = -1;
2128 if (infoPtr->iHotTracked >= 0)
2129 KillTimer(hwnd, TAB_HOTTRACK_TIMER);
2130 infoPtr->iHotTracked = -1;
2132 TAB_SetItemBounds(hwnd);
2133 TAB_InvalidateTabArea(hwnd,infoPtr);
2139 TAB_GetFont (HWND hwnd, WPARAM wParam, LPARAM lParam)
2141 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2144 return (LRESULT)infoPtr->hFont;
2148 TAB_SetFont (HWND hwnd, WPARAM wParam, LPARAM lParam)
2151 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2153 TRACE("%x %lx\n",wParam, lParam);
2155 infoPtr->hFont = (HFONT)wParam;
2157 TAB_SetItemBounds(hwnd);
2159 TAB_InvalidateTabArea(hwnd, infoPtr);
2166 TAB_GetImageList (HWND hwnd, WPARAM wParam, LPARAM lParam)
2168 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2171 return (LRESULT)infoPtr->himl;
2175 TAB_SetImageList (HWND hwnd, WPARAM wParam, LPARAM lParam)
2177 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2178 HIMAGELIST himlPrev;
2181 himlPrev = infoPtr->himl;
2182 infoPtr->himl= (HIMAGELIST)lParam;
2183 return (LRESULT)himlPrev;
2188 TAB_Size (HWND hwnd, WPARAM wParam, LPARAM lParam)
2191 /* I'm not really sure what the following code was meant to do.
2192 This is what it is doing:
2193 When WM_SIZE is sent with SIZE_RESTORED, the control
2194 gets positioned in the top left corner.
2198 UINT uPosFlags,cx,cy;
2202 parent = GetParent (hwnd);
2203 GetClientRect(parent, &parent_rect);
2206 if (GetWindowLongA(hwnd, GWL_STYLE) & CCS_NORESIZE)
2207 uPosFlags |= (SWP_NOSIZE | SWP_NOMOVE);
2209 SetWindowPos (hwnd, 0, parent_rect.left, parent_rect.top,
2210 cx, cy, uPosFlags | SWP_NOZORDER);
2212 FIXME("WM_SIZE flag %x %lx not handled\n", wParam, lParam);
2216 * Recompute the size/position of the tabs.
2218 TAB_SetItemBounds (hwnd);
2221 * Force a repaint of the control.
2223 InvalidateRect(hwnd, NULL, TRUE);
2230 TAB_Create (HWND hwnd, WPARAM wParam, LPARAM lParam)
2233 TEXTMETRICA fontMetrics;
2238 infoPtr = (TAB_INFO *)COMCTL32_Alloc (sizeof(TAB_INFO));
2240 SetWindowLongA(hwnd, 0, (DWORD)infoPtr);
2242 infoPtr->uNumItem = 0;
2243 infoPtr->uNumRows = 0;
2246 infoPtr->hcurArrow = LoadCursorA (0, IDC_ARROWA);
2247 infoPtr->iSelected = -1;
2248 infoPtr->iHotTracked = -1;
2249 infoPtr->uFocus = -1;
2250 infoPtr->hwndToolTip = 0;
2251 infoPtr->DoRedraw = TRUE;
2252 infoPtr->needsScrolling = FALSE;
2253 infoPtr->hwndUpDown = 0;
2254 infoPtr->leftmostVisible = 0;
2255 infoPtr->fSizeSet = FALSE;
2257 TRACE("Created tab control, hwnd [%04x]\n", hwnd);
2259 /* The tab control always has the WS_CLIPSIBLINGS style. Even
2260 if you don't specify it in CreateWindow. This is necessary in
2261 order for paint to work correctly. This follows windows behaviour. */
2262 dwStyle = GetWindowLongA(hwnd, GWL_STYLE);
2263 SetWindowLongA(hwnd, GWL_STYLE, dwStyle|WS_CLIPSIBLINGS);
2265 if (dwStyle & TCS_TOOLTIPS) {
2266 /* Create tooltip control */
2267 infoPtr->hwndToolTip =
2268 CreateWindowExA (0, TOOLTIPS_CLASSA, NULL, 0,
2269 CW_USEDEFAULT, CW_USEDEFAULT,
2270 CW_USEDEFAULT, CW_USEDEFAULT,
2273 /* Send NM_TOOLTIPSCREATED notification */
2274 if (infoPtr->hwndToolTip) {
2275 NMTOOLTIPSCREATED nmttc;
2277 nmttc.hdr.hwndFrom = hwnd;
2278 nmttc.hdr.idFrom = GetWindowLongA(hwnd, GWL_ID);
2279 nmttc.hdr.code = NM_TOOLTIPSCREATED;
2280 nmttc.hwndToolTips = infoPtr->hwndToolTip;
2282 SendMessageA (GetParent (hwnd), WM_NOTIFY,
2283 (WPARAM)GetWindowLongA(hwnd, GWL_ID), (LPARAM)&nmttc);
2288 * We need to get text information so we need a DC and we need to select
2292 hOldFont = SelectObject (hdc, GetStockObject (SYSTEM_FONT));
2295 * Use the system font to determine the initial height of a tab.
2297 GetTextMetricsA(hdc, &fontMetrics);
2300 * Make sure there is enough space for the letters + growing the
2301 * selected item + extra space for the selected item.
2303 infoPtr->tabHeight = fontMetrics.tmHeight + 2*VERTICAL_ITEM_PADDING +
2304 SELECTED_TAB_OFFSET;
2307 * Initialize the width of a tab.
2309 infoPtr->tabWidth = DEFAULT_TAB_WIDTH;
2311 SelectObject (hdc, hOldFont);
2312 ReleaseDC(hwnd, hdc);
2318 TAB_Destroy (HWND hwnd, WPARAM wParam, LPARAM lParam)
2320 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2326 if (infoPtr->items) {
2327 for (iItem = 0; iItem < infoPtr->uNumItem; iItem++) {
2328 if (infoPtr->items[iItem].pszText)
2329 COMCTL32_Free (infoPtr->items[iItem].pszText);
2331 COMCTL32_Free (infoPtr->items);
2334 if (infoPtr->hwndToolTip)
2335 DestroyWindow (infoPtr->hwndToolTip);
2337 if (infoPtr->hwndUpDown)
2338 DestroyWindow(infoPtr->hwndUpDown);
2340 if (infoPtr->iHotTracked >= 0)
2341 KillTimer(hwnd, TAB_HOTTRACK_TIMER);
2343 COMCTL32_Free (infoPtr);
2344 SetWindowLongA(hwnd, 0, 0);
2348 static LRESULT WINAPI
2349 TAB_WindowProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
2352 TRACE("hwnd=%x msg=%x wParam=%x lParam=%lx\n", hwnd, uMsg, wParam, lParam);
2353 if (!TAB_GetInfoPtr(hwnd) && (uMsg != WM_CREATE))
2354 return DefWindowProcA (hwnd, uMsg, wParam, lParam);
2358 case TCM_GETIMAGELIST:
2359 return TAB_GetImageList (hwnd, wParam, lParam);
2361 case TCM_SETIMAGELIST:
2362 return TAB_SetImageList (hwnd, wParam, lParam);
2364 case TCM_GETITEMCOUNT:
2365 return TAB_GetItemCount (hwnd, wParam, lParam);
2368 return TAB_GetItemA (hwnd, wParam, lParam);
2371 FIXME("Unimplemented msg TCM_GETITEMW\n");
2375 return TAB_SetItemA (hwnd, wParam, lParam);
2378 FIXME("Unimplemented msg TCM_SETITEMW\n");
2381 case TCM_DELETEITEM:
2382 return TAB_DeleteItem (hwnd, wParam, lParam);
2384 case TCM_DELETEALLITEMS:
2385 return TAB_DeleteAllItems (hwnd, wParam, lParam);
2387 case TCM_GETITEMRECT:
2388 return TAB_GetItemRect (hwnd, wParam, lParam);
2391 return TAB_GetCurSel (hwnd);
2394 return TAB_HitTest (hwnd, wParam, lParam);
2397 return TAB_SetCurSel (hwnd, wParam);
2399 case TCM_INSERTITEMA:
2400 return TAB_InsertItem (hwnd, wParam, lParam);
2402 case TCM_INSERTITEMW:
2403 FIXME("Unimplemented msg TCM_INSERTITEMW\n");
2406 case TCM_SETITEMEXTRA:
2407 FIXME("Unimplemented msg TCM_SETITEMEXTRA\n");
2410 case TCM_ADJUSTRECT:
2411 return TAB_AdjustRect (hwnd, (BOOL)wParam, (LPRECT)lParam);
2413 case TCM_SETITEMSIZE:
2414 return TAB_SetItemSize (hwnd, wParam, lParam);
2416 case TCM_REMOVEIMAGE:
2417 FIXME("Unimplemented msg TCM_REMOVEIMAGE\n");
2420 case TCM_SETPADDING:
2421 FIXME("Unimplemented msg TCM_SETPADDING\n");
2424 case TCM_GETROWCOUNT:
2425 return TAB_GetRowCount(hwnd);
2427 case TCM_GETUNICODEFORMAT:
2428 FIXME("Unimplemented msg TCM_GETUNICODEFORMAT\n");
2431 case TCM_SETUNICODEFORMAT:
2432 FIXME("Unimplemented msg TCM_SETUNICODEFORMAT\n");
2435 case TCM_HIGHLIGHTITEM:
2436 FIXME("Unimplemented msg TCM_HIGHLIGHTITEM\n");
2439 case TCM_GETTOOLTIPS:
2440 return TAB_GetToolTips (hwnd, wParam, lParam);
2442 case TCM_SETTOOLTIPS:
2443 return TAB_SetToolTips (hwnd, wParam, lParam);
2445 case TCM_GETCURFOCUS:
2446 return TAB_GetCurFocus (hwnd);
2448 case TCM_SETCURFOCUS:
2449 return TAB_SetCurFocus (hwnd, wParam);
2451 case TCM_SETMINTABWIDTH:
2452 FIXME("Unimplemented msg TCM_SETMINTABWIDTH\n");
2455 case TCM_DESELECTALL:
2456 FIXME("Unimplemented msg TCM_DESELECTALL\n");
2459 case TCM_GETEXTENDEDSTYLE:
2460 FIXME("Unimplemented msg TCM_GETEXTENDEDSTYLE\n");
2463 case TCM_SETEXTENDEDSTYLE:
2464 FIXME("Unimplemented msg TCM_SETEXTENDEDSTYLE\n");
2468 return TAB_GetFont (hwnd, wParam, lParam);
2471 return TAB_SetFont (hwnd, wParam, lParam);
2474 return TAB_Create (hwnd, wParam, lParam);
2477 return TAB_Destroy (hwnd, wParam, lParam);
2480 return DLGC_WANTARROWS | DLGC_WANTCHARS;
2482 case WM_LBUTTONDOWN:
2483 return TAB_LButtonDown (hwnd, wParam, lParam);
2486 return TAB_LButtonUp (hwnd, wParam, lParam);
2488 case WM_RBUTTONDOWN:
2489 return TAB_RButtonDown (hwnd, wParam, lParam);
2492 return TAB_MouseMove (hwnd, wParam, lParam);
2495 return TAB_EraseBackground (hwnd, (HDC)wParam);
2498 return TAB_Paint (hwnd, wParam);
2501 return TAB_Size (hwnd, wParam, lParam);
2504 return TAB_SetRedraw (hwnd, wParam);
2507 return TAB_OnHScroll(hwnd, (int)LOWORD(wParam), (int)HIWORD(wParam), (HWND)lParam);
2509 case WM_STYLECHANGED:
2510 TAB_SetItemBounds (hwnd);
2511 InvalidateRect(hwnd, NULL, TRUE);
2516 return TAB_FocusChanging(hwnd, uMsg, wParam, lParam);
2519 return TAB_KeyUp(hwnd, wParam);
2521 return TAB_NCHitTest(hwnd, lParam);
2524 if (uMsg >= WM_USER)
2525 WARN("unknown msg %04x wp=%08x lp=%08lx\n",
2526 uMsg, wParam, lParam);
2527 return DefWindowProcA (hwnd, uMsg, wParam, lParam);
2539 ZeroMemory (&wndClass, sizeof(WNDCLASSA));
2540 wndClass.style = CS_GLOBALCLASS | CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
2541 wndClass.lpfnWndProc = (WNDPROC)TAB_WindowProc;
2542 wndClass.cbClsExtra = 0;
2543 wndClass.cbWndExtra = sizeof(TAB_INFO *);
2544 wndClass.hCursor = LoadCursorA (0, IDC_ARROWA);
2545 wndClass.hbrBackground = (HBRUSH)NULL;
2546 wndClass.lpszClassName = WC_TABCONTROLA;
2548 RegisterClassA (&wndClass);
2553 TAB_Unregister (void)
2555 UnregisterClassA (WC_TABCONTROLA, (HINSTANCE)NULL);