4 * Copyright 1998 Anders Carlsson
5 * Copyright 1999 Alex Priem <alexp@sci.kun.nl>
6 * Copyright 1999 Francis Beaudet
10 * Unicode support (under construction)
13 * UpDown control not displayed until after a tab is clicked on
21 #include "debugtools.h"
24 DEFAULT_DEBUG_CHANNEL(tab);
33 RECT rect; /* bounding rectangle of the item relative to the
34 * leftmost item (the leftmost item, 0, would have a
35 * "left" member of 0 in this rectangle)
37 * additionally the top member hold the row number
38 * and bottom is unused and should be 0 */
43 UINT uNumItem; /* number of tab items */
44 UINT uNumRows; /* number of tab rows */
45 INT tabHeight; /* height of the tab row */
46 INT tabWidth; /* width of tabs */
47 HFONT hFont; /* handle to the current font */
48 HCURSOR hcurArrow; /* handle to the current cursor */
49 HIMAGELIST himl; /* handle to a image list (may be 0) */
50 HWND hwndToolTip; /* handle to tab's tooltip */
51 INT leftmostVisible; /* Used for scrolling, this member contains
52 * the index of the first visible item */
53 INT iSelected; /* the currently selected item */
54 INT iHotTracked; /* the highlighted item under the mouse */
55 INT uFocus; /* item which has the focus */
56 TAB_ITEM* items; /* pointer to an array of TAB_ITEM's */
57 BOOL DoRedraw; /* flag for redrawing when tab contents is changed*/
58 BOOL needsScrolling; /* TRUE if the size of the tabs is greater than
59 * the size of the control */
60 BOOL fSizeSet; /* was the size of the tabs explicitly set? */
61 BOOL bUnicode; /* Unicode control? */
62 HWND hwndUpDown; /* Updown control used for scrolling */
65 /******************************************************************************
66 * Positioning constants
68 #define SELECTED_TAB_OFFSET 2
69 #define HORIZONTAL_ITEM_PADDING 5
70 #define VERTICAL_ITEM_PADDING 3
71 #define ROUND_CORNER_SIZE 2
72 #define DISPLAY_AREA_PADDINGX 2
73 #define DISPLAY_AREA_PADDINGY 2
74 #define CONTROL_BORDER_SIZEX 2
75 #define CONTROL_BORDER_SIZEY 2
76 #define BUTTON_SPACINGX 4
77 #define BUTTON_SPACINGY 4
78 #define FLAT_BTN_SPACINGX 8
79 #define DEFAULT_TAB_WIDTH 96
81 #define TAB_GetInfoPtr(hwnd) ((TAB_INFO *)GetWindowLongA(hwnd,0))
83 /******************************************************************************
84 * Hot-tracking timer constants
86 #define TAB_HOTTRACK_TIMER 1
87 #define TAB_HOTTRACK_TIMER_INTERVAL 100 /* milliseconds */
89 /******************************************************************************
92 static void TAB_Refresh (HWND hwnd, HDC hdc);
93 static void TAB_InvalidateTabArea(HWND hwnd, TAB_INFO* infoPtr);
94 static void TAB_EnsureSelectionVisible(HWND hwnd, TAB_INFO* infoPtr);
95 static void TAB_DrawItem(HWND hwnd, HDC hdc, INT iItem);
96 static void TAB_DrawItemInterior(HWND hwnd, HDC hdc, INT iItem, RECT* drawRect);
99 TAB_SendSimpleNotify (HWND hwnd, UINT code)
103 nmhdr.hwndFrom = hwnd;
104 nmhdr.idFrom = GetWindowLongA(hwnd, GWL_ID);
107 return (BOOL) SendMessageA (GetParent (hwnd), WM_NOTIFY,
108 (WPARAM) nmhdr.idFrom, (LPARAM) &nmhdr);
112 TAB_RelayEvent (HWND hwndTip, HWND hwndMsg, UINT uMsg,
113 WPARAM wParam, LPARAM lParam)
121 msg.time = GetMessageTime ();
122 msg.pt.x = LOWORD(GetMessagePos ());
123 msg.pt.y = HIWORD(GetMessagePos ());
125 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;
154 TAB_SetCurSel (HWND hwnd,WPARAM wParam)
156 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
157 INT iItem = (INT)wParam;
161 if ((iItem >= 0) && (iItem < infoPtr->uNumItem)) {
162 prevItem=infoPtr->iSelected;
163 infoPtr->iSelected=iItem;
164 TAB_EnsureSelectionVisible(hwnd, infoPtr);
165 TAB_InvalidateTabArea(hwnd, infoPtr);
171 TAB_SetCurFocus (HWND hwnd,WPARAM wParam)
173 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
174 INT iItem=(INT) wParam;
176 if ((iItem < 0) || (iItem >= infoPtr->uNumItem)) return 0;
178 if (GetWindowLongA(hwnd, GWL_STYLE) & TCS_BUTTONS) {
179 FIXME("Should set input focus\n");
181 int oldFocus = infoPtr->uFocus;
182 if (infoPtr->iSelected != iItem || infoPtr->uFocus == -1 ) {
183 infoPtr->uFocus = iItem;
184 if (oldFocus != -1) {
185 if (TAB_SendSimpleNotify(hwnd, TCN_SELCHANGING)!=TRUE) {
186 infoPtr->iSelected = iItem;
187 TAB_SendSimpleNotify(hwnd, TCN_SELCHANGE);
190 infoPtr->iSelected = iItem;
191 TAB_EnsureSelectionVisible(hwnd, infoPtr);
192 TAB_InvalidateTabArea(hwnd, infoPtr);
200 TAB_SetToolTips (HWND hwnd, WPARAM wParam, LPARAM lParam)
202 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
204 if (infoPtr == NULL) return 0;
205 infoPtr->hwndToolTip = (HWND)wParam;
209 /******************************************************************************
210 * TAB_InternalGetItemRect
212 * This method will calculate the rectangle representing a given tab item in
213 * client coordinates. This method takes scrolling into account.
215 * This method returns TRUE if the item is visible in the window and FALSE
216 * if it is completely outside the client area.
218 static BOOL TAB_InternalGetItemRect(
225 RECT tmpItemRect,clientRect;
226 LONG lStyle = GetWindowLongA(hwnd, GWL_STYLE);
228 /* Perform a sanity check and a trivial visibility check. */
229 if ( (infoPtr->uNumItem <= 0) ||
230 (itemIndex >= infoPtr->uNumItem) ||
231 (!((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)) && (itemIndex < infoPtr->leftmostVisible)) )
235 * Avoid special cases in this procedure by assigning the "out"
236 * parameters if the caller didn't supply them
238 if (itemRect == NULL)
239 itemRect = &tmpItemRect;
241 /* Retrieve the unmodified item rect. */
242 *itemRect = infoPtr->items[itemIndex].rect;
244 /* calculate the times bottom and top based on the row */
245 GetClientRect(hwnd, &clientRect);
247 if ((lStyle & TCS_BOTTOM) && !(lStyle & TCS_VERTICAL))
249 itemRect->bottom = clientRect.bottom -
250 SELECTED_TAB_OFFSET -
251 itemRect->top * (infoPtr->tabHeight - 2) -
252 ((lStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : 0);
254 itemRect->top = clientRect.bottom -
256 itemRect->top * (infoPtr->tabHeight - 2) -
257 ((lStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : 0);
259 else if((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
261 itemRect->right = clientRect.right - SELECTED_TAB_OFFSET - itemRect->left * (infoPtr->tabHeight - 2) -
262 ((lStyle & TCS_BUTTONS) ? itemRect->left * BUTTON_SPACINGY : 0);
263 itemRect->left = clientRect.right - infoPtr->tabHeight - itemRect->left * (infoPtr->tabHeight - 2) -
264 ((lStyle & TCS_BUTTONS) ? itemRect->left * BUTTON_SPACINGY : 0);
266 else if((lStyle & TCS_VERTICAL) && !(lStyle & TCS_BOTTOM))
268 itemRect->right = clientRect.left + infoPtr->tabHeight + itemRect->left * (infoPtr->tabHeight - 2) +
269 ((lStyle & TCS_BUTTONS) ? itemRect->left * BUTTON_SPACINGY : 0);
270 itemRect->left = clientRect.left + SELECTED_TAB_OFFSET + itemRect->left * (infoPtr->tabHeight - 2) +
271 ((lStyle & TCS_BUTTONS) ? itemRect->left * BUTTON_SPACINGY : 0);
273 else if(!(lStyle & TCS_VERTICAL) && !(lStyle & TCS_BOTTOM)) /* not TCS_BOTTOM and not TCS_VERTICAL */
275 itemRect->bottom = clientRect.top +
277 itemRect->top * (infoPtr->tabHeight - 2) +
278 ((lStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : 0);
279 itemRect->top = clientRect.top +
280 SELECTED_TAB_OFFSET +
281 itemRect->top * (infoPtr->tabHeight - 2) +
282 ((lStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : 0);
286 * "scroll" it to make sure the item at the very left of the
287 * tab control is the leftmost visible tab.
289 if(lStyle & TCS_VERTICAL)
293 -(clientRect.bottom - infoPtr->items[infoPtr->leftmostVisible].rect.bottom));
296 * Move the rectangle so the first item is slightly offset from
297 * the bottom of the tab control.
301 -SELECTED_TAB_OFFSET);
306 -infoPtr->items[infoPtr->leftmostVisible].rect.left,
310 * Move the rectangle so the first item is slightly offset from
311 * the left of the tab control.
318 /* Now, calculate the position of the item as if it were selected. */
319 if (selectedRect!=NULL)
321 CopyRect(selectedRect, itemRect);
323 /* The rectangle of a selected item is a bit wider. */
324 if(lStyle & TCS_VERTICAL)
325 InflateRect(selectedRect, 0, SELECTED_TAB_OFFSET);
327 InflateRect(selectedRect, SELECTED_TAB_OFFSET, 0);
329 /* If it also a bit higher. */
330 if ((lStyle & TCS_BOTTOM) && !(lStyle & TCS_VERTICAL))
332 selectedRect->top -= 2; /* the border is thicker on the bottom */
333 selectedRect->bottom += SELECTED_TAB_OFFSET;
335 else if((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
337 selectedRect->left -= 2; /* the border is thicker on the right */
338 selectedRect->right += SELECTED_TAB_OFFSET;
340 else if(lStyle & TCS_VERTICAL)
342 selectedRect->left -= SELECTED_TAB_OFFSET;
343 selectedRect->right += 1;
347 selectedRect->top -= SELECTED_TAB_OFFSET;
348 selectedRect->bottom += 1;
355 static BOOL TAB_GetItemRect(HWND hwnd, WPARAM wParam, LPARAM lParam)
357 return TAB_InternalGetItemRect(hwnd, TAB_GetInfoPtr(hwnd), (INT)wParam,
358 (LPRECT)lParam, (LPRECT)NULL);
361 /******************************************************************************
364 * This method is called to handle keyboard input
366 static LRESULT TAB_KeyUp(
370 TAB_INFO* infoPtr = TAB_GetInfoPtr(hwnd);
376 newItem = infoPtr->uFocus - 1;
379 newItem = infoPtr->uFocus + 1;
384 * If we changed to a valid item, change the selection
386 if ((newItem >= 0) &&
387 (newItem < infoPtr->uNumItem) &&
388 (infoPtr->uFocus != newItem))
390 if (!TAB_SendSimpleNotify(hwnd, TCN_SELCHANGING))
392 infoPtr->iSelected = newItem;
393 infoPtr->uFocus = newItem;
394 TAB_SendSimpleNotify(hwnd, TCN_SELCHANGE);
396 TAB_EnsureSelectionVisible(hwnd, infoPtr);
397 TAB_InvalidateTabArea(hwnd, infoPtr);
404 /******************************************************************************
407 * This method is called whenever the focus goes in or out of this control
408 * it is used to update the visual state of the control.
410 static LRESULT TAB_FocusChanging(
416 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
421 * Get the rectangle for the item.
423 isVisible = TAB_InternalGetItemRect(hwnd,
430 * If the rectangle is not completely invisible, invalidate that
431 * portion of the window.
435 InvalidateRect(hwnd, &selectedRect, TRUE);
439 * Don't otherwise disturb normal behavior.
441 return DefWindowProcA (hwnd, uMsg, wParam, lParam);
444 static HWND TAB_InternalHitTest (
454 for (iCount = 0; iCount < infoPtr->uNumItem; iCount++)
456 TAB_InternalGetItemRect(hwnd, infoPtr, iCount, &rect, NULL);
458 if (PtInRect(&rect, pt))
460 *flags = TCHT_ONITEM;
465 *flags = TCHT_NOWHERE;
470 TAB_HitTest (HWND hwnd, WPARAM wParam, LPARAM lParam)
472 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
473 LPTCHITTESTINFO lptest = (LPTCHITTESTINFO) lParam;
475 return TAB_InternalHitTest (hwnd, infoPtr, lptest->pt, &lptest->flags);
478 /******************************************************************************
481 * Napster v2b5 has a tab control for its main navigation which has a client
482 * area that covers the whole area of the dialog pages.
483 * That's why it receives all msgs for that area and the underlying dialog ctrls
485 * So I decided that we should handle WM_NCHITTEST here and return
486 * HTTRANSPARENT if we don't hit the tab control buttons.
487 * FIXME: WM_NCHITTEST handling correct ? Fix it if you know that Windows
488 * doesn't do it that way. Maybe depends on tab control styles ?
491 TAB_NCHitTest (HWND hwnd, LPARAM lParam)
493 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
497 pt.x = LOWORD(lParam);
498 pt.y = HIWORD(lParam);
499 ScreenToClient(hwnd, &pt);
501 if (TAB_InternalHitTest(hwnd, infoPtr, pt, &dummyflag) == -1)
502 return HTTRANSPARENT;
508 TAB_LButtonDown (HWND hwnd, WPARAM wParam, LPARAM lParam)
510 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
514 if (infoPtr->hwndToolTip)
515 TAB_RelayEvent (infoPtr->hwndToolTip, hwnd,
516 WM_LBUTTONDOWN, wParam, lParam);
518 if (GetWindowLongA(hwnd, GWL_STYLE) & TCS_FOCUSONBUTTONDOWN ) {
522 if (infoPtr->hwndToolTip)
523 TAB_RelayEvent (infoPtr->hwndToolTip, hwnd,
524 WM_LBUTTONDOWN, wParam, lParam);
526 pt.x = (INT)LOWORD(lParam);
527 pt.y = (INT)HIWORD(lParam);
529 newItem = TAB_InternalHitTest (hwnd, infoPtr, pt, &dummy);
531 TRACE("On Tab, item %d\n", newItem);
533 if ((newItem != -1) && (infoPtr->iSelected != newItem))
535 if (TAB_SendSimpleNotify(hwnd, TCN_SELCHANGING) != TRUE)
537 infoPtr->iSelected = newItem;
538 infoPtr->uFocus = newItem;
539 TAB_SendSimpleNotify(hwnd, TCN_SELCHANGE);
541 TAB_EnsureSelectionVisible(hwnd, infoPtr);
543 TAB_InvalidateTabArea(hwnd, infoPtr);
550 TAB_LButtonUp (HWND hwnd, WPARAM wParam, LPARAM lParam)
552 TAB_SendSimpleNotify(hwnd, NM_CLICK);
558 TAB_RButtonDown (HWND hwnd, WPARAM wParam, LPARAM lParam)
560 TAB_SendSimpleNotify(hwnd, NM_RCLICK);
564 /******************************************************************************
565 * TAB_DrawLoneItemInterior
567 * This calls TAB_DrawItemInterior. However, TAB_DrawItemInterior is normally
568 * called by TAB_DrawItem which is normally called by TAB_Refresh which sets
569 * up the device context and font. This routine does the same setup but
570 * only calls TAB_DrawItemInterior for the single specified item.
573 TAB_DrawLoneItemInterior(HWND hwnd, TAB_INFO* infoPtr, int iItem)
575 HDC hdc = GetDC(hwnd);
576 HFONT hOldFont = SelectObject(hdc, infoPtr->hFont);
577 TAB_DrawItemInterior(hwnd, hdc, iItem, NULL);
578 SelectObject(hdc, hOldFont);
579 ReleaseDC(hwnd, hdc);
582 /******************************************************************************
583 * TAB_HotTrackTimerProc
585 * When a mouse-move event causes a tab to be highlighted (hot-tracking), a
586 * timer is setup so we can check if the mouse is moved out of our window.
587 * (We don't get an event when the mouse leaves, the mouse-move events just
588 * stop being delivered to our window and just start being delivered to
589 * another window.) This function is called when the timer triggers so
590 * we can check if the mouse has left our window. If so, we un-highlight
591 * the hot-tracked tab.
594 TAB_HotTrackTimerProc
596 HWND hwnd, /* handle of window for timer messages */
597 UINT uMsg, /* WM_TIMER message */
598 UINT idEvent, /* timer identifier */
599 DWORD dwTime /* current system time */
602 TAB_INFO* infoPtr = TAB_GetInfoPtr(hwnd);
604 if (infoPtr != NULL && infoPtr->iHotTracked >= 0)
609 ** If we can't get the cursor position, or if the cursor is outside our
610 ** window, we un-highlight the hot-tracked tab. Note that the cursor is
611 ** "outside" even if it is within our bounding rect if another window
612 ** overlaps. Note also that the case where the cursor stayed within our
613 ** window but has moved off the hot-tracked tab will be handled by the
614 ** WM_MOUSEMOVE event.
616 if (!GetCursorPos(&pt) || WindowFromPoint(pt) != hwnd)
618 /* Redraw iHotTracked to look normal */
619 INT iRedraw = infoPtr->iHotTracked;
620 infoPtr->iHotTracked = -1;
621 TAB_DrawLoneItemInterior(hwnd, infoPtr, iRedraw);
623 /* Kill this timer */
624 KillTimer(hwnd, TAB_HOTTRACK_TIMER);
629 /******************************************************************************
632 * If a tab control has the TCS_HOTTRACK style, then the tab under the mouse
633 * should be highlighted. This function determines which tab in a tab control,
634 * if any, is under the mouse and records that information. The caller may
635 * supply output parameters to receive the item number of the tab item which
636 * was highlighted but isn't any longer and of the tab item which is now
637 * highlighted but wasn't previously. The caller can use this information to
638 * selectively redraw those tab items.
640 * If the caller has a mouse position, it can supply it through the pos
641 * parameter. For example, TAB_MouseMove does this. Otherwise, the caller
642 * supplies NULL and this function determines the current mouse position
650 int* out_redrawLeave,
654 TAB_INFO* infoPtr = TAB_GetInfoPtr(hwnd);
659 if (out_redrawLeave != NULL)
660 *out_redrawLeave = -1;
661 if (out_redrawEnter != NULL)
662 *out_redrawEnter = -1;
664 if (GetWindowLongA(hwnd, GWL_STYLE) & TCS_HOTTRACK)
672 ScreenToClient(hwnd, &pt);
680 item = TAB_InternalHitTest(hwnd, infoPtr, pt, &flags);
683 if (item != infoPtr->iHotTracked)
685 if (infoPtr->iHotTracked >= 0)
687 /* Mark currently hot-tracked to be redrawn to look normal */
688 if (out_redrawLeave != NULL)
689 *out_redrawLeave = infoPtr->iHotTracked;
693 /* Kill timer which forces recheck of mouse pos */
694 KillTimer(hwnd, TAB_HOTTRACK_TIMER);
699 /* Start timer so we recheck mouse pos */
700 UINT timerID = SetTimer
704 TAB_HOTTRACK_TIMER_INTERVAL,
705 TAB_HotTrackTimerProc
709 return; /* Hot tracking not available */
712 infoPtr->iHotTracked = item;
716 /* Mark new hot-tracked to be redrawn to look highlighted */
717 if (out_redrawEnter != NULL)
718 *out_redrawEnter = item;
723 /******************************************************************************
726 * Handles the mouse-move event. Updates tooltips. Updates hot-tracking.
729 TAB_MouseMove (HWND hwnd, WPARAM wParam, LPARAM lParam)
734 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
736 if (infoPtr->hwndToolTip)
737 TAB_RelayEvent (infoPtr->hwndToolTip, hwnd,
738 WM_LBUTTONDOWN, wParam, lParam);
740 /* Determine which tab to highlight. Redraw tabs which change highlight
742 TAB_RecalcHotTrack(hwnd, &lParam, &redrawLeave, &redrawEnter);
744 if (redrawLeave != -1)
745 TAB_DrawLoneItemInterior(hwnd, infoPtr, redrawLeave);
746 if (redrawEnter != -1)
747 TAB_DrawLoneItemInterior(hwnd, infoPtr, redrawEnter);
752 /******************************************************************************
755 * Calculates the tab control's display area given the window rectangle or
756 * the window rectangle given the requested display rectangle.
758 static LRESULT TAB_AdjustRect(
763 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
764 DWORD lStyle = GetWindowLongA(hwnd, GWL_STYLE);
766 if(lStyle & TCS_VERTICAL)
768 if (fLarger) /* Go from display rectangle */
770 /* Add the height of the tabs. */
771 if (lStyle & TCS_BOTTOM)
772 prc->right += (infoPtr->tabHeight - 2) * infoPtr->uNumRows + 2;
774 prc->left -= (infoPtr->tabHeight - 2) * infoPtr->uNumRows + 2;
776 /* FIXME: not sure if these InflateRect's need to have different values for TCS_VERTICAL */
777 /* Inflate the rectangle for the padding */
778 InflateRect(prc, DISPLAY_AREA_PADDINGX, DISPLAY_AREA_PADDINGY);
780 /* Inflate for the border */
781 InflateRect(prc, CONTROL_BORDER_SIZEX, CONTROL_BORDER_SIZEX);
783 else /* Go from window rectangle. */
785 /* FIXME: not sure if these InflateRect's need to have different values for TCS_VERTICAL */
786 /* Deflate the rectangle for the border */
787 InflateRect(prc, -CONTROL_BORDER_SIZEX, -CONTROL_BORDER_SIZEX);
789 /* Deflate the rectangle for the padding */
790 InflateRect(prc, -DISPLAY_AREA_PADDINGX, -DISPLAY_AREA_PADDINGY);
792 /* Remove the height of the tabs. */
793 if (lStyle & TCS_BOTTOM)
794 prc->right -= (infoPtr->tabHeight - 2) * infoPtr->uNumRows + 2;
796 prc->left += (infoPtr->tabHeight - 2) * infoPtr->uNumRows + 2;
800 if (fLarger) /* Go from display rectangle */
802 /* Add the height of the tabs. */
803 if (lStyle & TCS_BOTTOM)
804 prc->bottom += (infoPtr->tabHeight - 2) * infoPtr->uNumRows + 2;
806 prc->top -= (infoPtr->tabHeight - 2) * infoPtr->uNumRows + 2;
808 /* Inflate the rectangle for the padding */
809 InflateRect(prc, DISPLAY_AREA_PADDINGX, DISPLAY_AREA_PADDINGY);
811 /* Inflate for the border */
812 InflateRect(prc, CONTROL_BORDER_SIZEX, CONTROL_BORDER_SIZEX);
814 else /* Go from window rectangle. */
816 /* Deflate the rectangle for the border */
817 InflateRect(prc, -CONTROL_BORDER_SIZEX, -CONTROL_BORDER_SIZEX);
819 /* Deflate the rectangle for the padding */
820 InflateRect(prc, -DISPLAY_AREA_PADDINGX, -DISPLAY_AREA_PADDINGY);
822 /* Remove the height of the tabs. */
823 if (lStyle & TCS_BOTTOM)
824 prc->bottom -= (infoPtr->tabHeight - 2) * infoPtr->uNumRows + 2;
826 prc->top += (infoPtr->tabHeight - 2) * infoPtr->uNumRows + 2;
833 /******************************************************************************
836 * This method will handle the notification from the scroll control and
837 * perform the scrolling operation on the tab control.
839 static LRESULT TAB_OnHScroll(
845 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
847 if(nScrollCode == SB_THUMBPOSITION && nPos != infoPtr->leftmostVisible)
849 if(nPos < infoPtr->leftmostVisible)
850 infoPtr->leftmostVisible--;
852 infoPtr->leftmostVisible++;
854 TAB_RecalcHotTrack(hwnd, NULL, NULL, NULL);
855 TAB_InvalidateTabArea(hwnd, infoPtr);
856 SendMessageA(infoPtr->hwndUpDown, UDM_SETPOS, 0,
857 MAKELONG(infoPtr->leftmostVisible, 0));
863 /******************************************************************************
866 * This method will check the current scrolling state and make sure the
867 * scrolling control is displayed (or not).
869 static void TAB_SetupScrolling(
872 const RECT* clientRect)
875 DWORD lStyle = GetWindowLongA(hwnd, GWL_STYLE);
877 if (infoPtr->needsScrolling)
883 * Calculate the position of the scroll control.
885 if(lStyle & TCS_VERTICAL)
887 controlPos.right = clientRect->right;
888 controlPos.left = controlPos.right - 2 * GetSystemMetrics(SM_CXHSCROLL);
890 if (lStyle & TCS_BOTTOM)
892 controlPos.top = clientRect->bottom - infoPtr->tabHeight;
893 controlPos.bottom = controlPos.top + GetSystemMetrics(SM_CYHSCROLL);
897 controlPos.bottom = clientRect->top + infoPtr->tabHeight;
898 controlPos.top = controlPos.bottom - GetSystemMetrics(SM_CYHSCROLL);
903 controlPos.right = clientRect->right;
904 controlPos.left = controlPos.right - 2 * GetSystemMetrics(SM_CXHSCROLL);
906 if (lStyle & TCS_BOTTOM)
908 controlPos.top = clientRect->bottom - infoPtr->tabHeight;
909 controlPos.bottom = controlPos.top + GetSystemMetrics(SM_CYHSCROLL);
913 controlPos.bottom = clientRect->top + infoPtr->tabHeight;
914 controlPos.top = controlPos.bottom - GetSystemMetrics(SM_CYHSCROLL);
919 * If we don't have a scroll control yet, we want to create one.
920 * If we have one, we want to make sure it's positioned properly.
922 if (infoPtr->hwndUpDown==0)
924 infoPtr->hwndUpDown = CreateWindowA("msctls_updown32",
926 WS_VISIBLE | WS_CHILD | UDS_HORZ,
927 controlPos.left, controlPos.top,
928 controlPos.right - controlPos.left,
929 controlPos.bottom - controlPos.top,
937 SetWindowPos(infoPtr->hwndUpDown,
939 controlPos.left, controlPos.top,
940 controlPos.right - controlPos.left,
941 controlPos.bottom - controlPos.top,
942 SWP_SHOWWINDOW | SWP_NOZORDER);
945 /* Now calculate upper limit of the updown control range.
946 * We do this by calculating how many tabs will be offscreen when the
947 * last tab is visible.
949 if(infoPtr->uNumItem)
951 vsize = clientRect->right - (controlPos.right - controlPos.left + 1);
952 maxRange = infoPtr->uNumItem;
953 tabwidth = infoPtr->items[maxRange - 1].rect.right;
955 for(; maxRange > 0; maxRange--)
957 if(tabwidth - infoPtr->items[maxRange - 1].rect.left > vsize)
961 if(maxRange == infoPtr->uNumItem)
967 /* If we once had a scroll control... hide it */
968 if (infoPtr->hwndUpDown!=0)
969 ShowWindow(infoPtr->hwndUpDown, SW_HIDE);
971 if (infoPtr->hwndUpDown)
972 SendMessageA(infoPtr->hwndUpDown, UDM_SETRANGE32, 0, maxRange);
975 /******************************************************************************
978 * This method will calculate the position rectangles of all the items in the
979 * control. The rectangle calculated starts at 0 for the first item in the
980 * list and ignores scrolling and selection.
981 * It also uses the current font to determine the height of the tab row and
982 * it checks if all the tabs fit in the client area of the window. If they
983 * dont, a scrolling control is added.
985 static void TAB_SetItemBounds (HWND hwnd)
987 TAB_INFO* infoPtr = TAB_GetInfoPtr(hwnd);
988 LONG lStyle = GetWindowLongA(hwnd, GWL_STYLE);
989 TEXTMETRICA fontMetrics;
993 HFONT hFont, hOldFont;
1002 * We need to get text information so we need a DC and we need to select
1007 hFont = infoPtr->hFont ? infoPtr->hFont : GetStockObject (SYSTEM_FONT);
1008 hOldFont = SelectObject (hdc, hFont);
1011 * We will base the rectangle calculations on the client rectangle
1014 GetClientRect(hwnd, &clientRect);
1016 /* if TCS_VERTICAL then swap the height and width so this code places the
1017 tabs along the top of the rectangle and we can just rotate them after
1018 rather than duplicate all of the below code */
1019 if(lStyle & TCS_VERTICAL)
1021 iTemp = clientRect.bottom;
1022 clientRect.bottom = clientRect.right;
1023 clientRect.right = iTemp;
1026 /* The leftmost item will be "0" aligned */
1028 curItemRowCount = infoPtr->uNumItem ? 1 : 0;
1030 if (!(lStyle & TCS_FIXEDWIDTH) && !((lStyle & TCS_OWNERDRAWFIXED) && infoPtr->fSizeSet) )
1033 int icon_height = 0;
1035 /* Use the current font to determine the height of a tab. */
1036 GetTextMetricsA(hdc, &fontMetrics);
1038 /* Get the icon height */
1040 ImageList_GetIconSize(infoPtr->himl, 0, &icon_height);
1042 /* Take the highest between font or icon */
1043 if (fontMetrics.tmHeight > icon_height)
1044 item_height = fontMetrics.tmHeight;
1046 item_height = icon_height;
1049 * Make sure there is enough space for the letters + icon + growing the
1050 * selected item + extra space for the selected item.
1052 infoPtr->tabHeight = item_height + 2 * VERTICAL_ITEM_PADDING +
1053 SELECTED_TAB_OFFSET;
1057 for (curItem = 0; curItem < infoPtr->uNumItem; curItem++)
1059 /* Set the leftmost position of the tab. */
1060 infoPtr->items[curItem].rect.left = curItemLeftPos;
1062 if ( (lStyle & TCS_FIXEDWIDTH) || ((lStyle & TCS_OWNERDRAWFIXED) && infoPtr->fSizeSet))
1064 infoPtr->items[curItem].rect.right = infoPtr->items[curItem].rect.left +
1066 2 * HORIZONTAL_ITEM_PADDING;
1073 /* Calculate how wide the tab is depending on the text it contains */
1074 GetTextExtentPoint32W(hdc, infoPtr->items[curItem].pszText,
1075 lstrlenW(infoPtr->items[curItem].pszText), &size);
1077 /* under Windows, there seems to be a minimum width of 2x the height
1078 * for button style tabs */
1079 if (lStyle & TCS_BUTTONS)
1080 size.cx = max(size.cx, 2 * (infoPtr->tabHeight - 2));
1082 /* Add the icon width */
1085 ImageList_GetIconSize(infoPtr->himl, &icon_width, 0);
1089 infoPtr->items[curItem].rect.right = infoPtr->items[curItem].rect.left +
1090 size.cx + icon_width +
1091 num * HORIZONTAL_ITEM_PADDING;
1095 * Check if this is a multiline tab control and if so
1096 * check to see if we should wrap the tabs
1098 * Because we are going to arange all these tabs evenly
1099 * really we are basically just counting rows at this point
1103 if (((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)) &&
1104 (infoPtr->items[curItem].rect.right > clientRect.right))
1106 infoPtr->items[curItem].rect.right -=
1107 infoPtr->items[curItem].rect.left;
1109 infoPtr->items[curItem].rect.left = 0;
1113 infoPtr->items[curItem].rect.bottom = 0;
1114 infoPtr->items[curItem].rect.top = curItemRowCount - 1;
1116 TRACE("TextSize: %li\n", size.cx);
1117 TRACE("Rect: T %i, L %i, B %i, R %i\n",
1118 infoPtr->items[curItem].rect.top,
1119 infoPtr->items[curItem].rect.left,
1120 infoPtr->items[curItem].rect.bottom,
1121 infoPtr->items[curItem].rect.right);
1124 * The leftmost position of the next item is the rightmost position
1127 if (lStyle & TCS_BUTTONS)
1129 curItemLeftPos = infoPtr->items[curItem].rect.right + 1;
1130 if (lStyle & TCS_FLATBUTTONS)
1131 curItemLeftPos += FLAT_BTN_SPACINGX;
1134 curItemLeftPos = infoPtr->items[curItem].rect.right;
1137 if (!((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)))
1140 * Check if we need a scrolling control.
1142 infoPtr->needsScrolling = (curItemLeftPos + (2 * SELECTED_TAB_OFFSET) >
1145 /* Don't need scrolling, then update infoPtr->leftmostVisible */
1146 if(!infoPtr->needsScrolling)
1147 infoPtr->leftmostVisible = 0;
1149 TAB_SetupScrolling(hwnd, infoPtr, &clientRect);
1152 /* Set the number of rows */
1153 infoPtr->uNumRows = curItemRowCount;
1155 if (((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)) && (infoPtr->uNumItem > 0))
1157 INT widthDiff, remainder;
1158 INT tabPerRow,remTab;
1160 INT iIndexStart=0,iIndexEnd=0, iCount=0;
1163 * Ok windows tries to even out the rows. place the same
1164 * number of tabs in each row. So lets give that a shot
1167 tabPerRow = infoPtr->uNumItem / (infoPtr->uNumRows);
1168 remTab = infoPtr->uNumItem % (infoPtr->uNumRows);
1170 for (iItm=0,iRow=0,iCount=0,curItemLeftPos=0;
1171 iItm<infoPtr->uNumItem;
1174 /* if we have reached the maximum number of tabs on this row */
1175 /* move to the next row, reset our current item left position and */
1176 /* the count of items on this row */
1177 if (iCount >= ((iRow<remTab)?tabPerRow + 1:tabPerRow))
1184 /* normalize the current rect */
1186 /* shift the item to the left side of the clientRect */
1187 infoPtr->items[iItm].rect.right -=
1188 infoPtr->items[iItm].rect.left;
1189 infoPtr->items[iItm].rect.left = 0;
1191 /* shift the item to the right to place it as the next item in this row */
1192 infoPtr->items[iItm].rect.left += curItemLeftPos;
1193 infoPtr->items[iItm].rect.right += curItemLeftPos;
1194 infoPtr->items[iItm].rect.top = iRow;
1195 if (lStyle & TCS_BUTTONS)
1197 curItemLeftPos = infoPtr->items[iItm].rect.right + 1;
1198 if (lStyle & TCS_FLATBUTTONS)
1199 curItemLeftPos += FLAT_BTN_SPACINGX;
1202 curItemLeftPos = infoPtr->items[iItm].rect.right;
1209 while(iIndexStart < infoPtr->uNumItem)
1212 * find the indexs of the row
1214 /* find the first item on the next row */
1215 for (iIndexEnd=iIndexStart;
1216 (iIndexEnd < infoPtr->uNumItem) &&
1217 (infoPtr->items[iIndexEnd].rect.top ==
1218 infoPtr->items[iIndexStart].rect.top) ;
1220 /* intentionally blank */;
1223 * we need to justify these tabs so they fill the whole given
1227 /* find the amount of space remaining on this row */
1228 widthDiff = clientRect.right - (2 * SELECTED_TAB_OFFSET) -
1229 infoPtr->items[iIndexEnd - 1].rect.right;
1231 /* iCount is the number of tab items on this row */
1232 iCount = iIndexEnd - iIndexStart;
1237 remainder = widthDiff % iCount;
1238 widthDiff = widthDiff / iCount;
1239 /* add widthDiff/iCount, or extra space/items on row, to each item on this row */
1240 for (iIndex=iIndexStart,iCount=0; iIndex < iIndexEnd;
1243 infoPtr->items[iIndex].rect.left += iCount * widthDiff;
1244 infoPtr->items[iIndex].rect.right += (iCount + 1) * widthDiff;
1246 infoPtr->items[iIndex - 1].rect.right += remainder;
1248 else /* we have only one item on this row, make it take up the entire row */
1250 infoPtr->items[iIndexStart].rect.left = clientRect.left;
1251 infoPtr->items[iIndexStart].rect.right = clientRect.right - 4;
1255 iIndexStart = iIndexEnd;
1260 /* if TCS_VERTICAL rotate the tabs so they are along the side of the clientRect */
1261 if(lStyle & TCS_VERTICAL)
1264 for(iIndex = 0; iIndex < infoPtr->uNumItem; iIndex++)
1266 rcItem = &(infoPtr->items[iIndex].rect);
1268 rcOriginal = *rcItem;
1270 /* this is rotating the items by 90 degrees around the center of the control */
1271 rcItem->top = (clientRect.right - (rcOriginal.left - clientRect.left)) - (rcOriginal.right - rcOriginal.left);
1272 rcItem->bottom = rcItem->top + (rcOriginal.right - rcOriginal.left);
1273 rcItem->left = rcOriginal.top;
1274 rcItem->right = rcOriginal.bottom;
1278 TAB_EnsureSelectionVisible(hwnd,infoPtr);
1279 TAB_RecalcHotTrack(hwnd, NULL, NULL, NULL);
1282 SelectObject (hdc, hOldFont);
1283 ReleaseDC (hwnd, hdc);
1286 /******************************************************************************
1287 * TAB_DrawItemInterior
1289 * This method is used to draw the interior (text and icon) of a single tab
1290 * into the tab control.
1293 TAB_DrawItemInterior
1301 TAB_INFO* infoPtr = TAB_GetInfoPtr(hwnd);
1302 LONG lStyle = GetWindowLongA(hwnd, GWL_STYLE);
1306 HPEN htextPen = GetSysColorPen (COLOR_BTNTEXT);
1310 if (drawRect == NULL)
1317 * Get the rectangle for the item.
1319 isVisible = TAB_InternalGetItemRect(hwnd, infoPtr, iItem, &itemRect, &selectedRect);
1324 * Make sure drawRect points to something valid; simplifies code.
1326 drawRect = &localRect;
1329 * This logic copied from the part of TAB_DrawItem which draws
1330 * the tab background. It's important to keep it in sync. I
1331 * would have liked to avoid code duplication, but couldn't figure
1332 * out how without making spaghetti of TAB_DrawItem.
1334 if (lStyle & TCS_BUTTONS)
1336 *drawRect = itemRect;
1337 if (iItem == infoPtr->iSelected)
1345 if (iItem == infoPtr->iSelected)
1346 *drawRect = selectedRect;
1348 *drawRect = itemRect;
1357 holdPen = SelectObject(hdc, htextPen);
1359 oldBkMode = SetBkMode(hdc, TRANSPARENT);
1360 SetTextColor(hdc, GetSysColor((iItem == infoPtr->iHotTracked) ? COLOR_HIGHLIGHT : COLOR_BTNTEXT));
1363 * Deflate the rectangle to acount for the padding
1365 if(lStyle & TCS_VERTICAL)
1366 InflateRect(drawRect, -VERTICAL_ITEM_PADDING, -HORIZONTAL_ITEM_PADDING);
1368 InflateRect(drawRect, -HORIZONTAL_ITEM_PADDING, -VERTICAL_ITEM_PADDING);
1372 * if owner draw, tell the owner to draw
1374 if ((lStyle & TCS_OWNERDRAWFIXED) && GetParent(hwnd))
1380 * get the control id
1382 id = GetWindowLongA( hwnd, GWL_ID );
1385 * put together the DRAWITEMSTRUCT
1387 dis.CtlType = ODT_TAB;
1390 dis.itemAction = ODA_DRAWENTIRE;
1391 if ( iItem == infoPtr->iSelected )
1392 dis.itemState = ODS_SELECTED;
1395 dis.hwndItem = hwnd; /* */
1397 dis.rcItem = *drawRect; /* */
1398 dis.itemData = infoPtr->items[iItem].lParam;
1401 * send the draw message
1403 SendMessageA( GetParent(hwnd), WM_DRAWITEM, (WPARAM)id, (LPARAM)&dis );
1414 HFONT hOldFont = 0; /* stop uninitialized warning */
1416 INT nEscapement = 0; /* stop uninitialized warning */
1417 INT nOrientation = 0; /* stop uninitialized warning */
1420 /* used to center the icon and text in the tab */
1424 /* set rcImage to drawRect, we will use top & left in our ImageList_Draw call */
1425 rcImage = *drawRect;
1430 * Setup for text output
1432 oldBkMode = SetBkMode(hdc, TRANSPARENT);
1433 SetTextColor(hdc, GetSysColor((iItem == infoPtr->iHotTracked) ? COLOR_HIGHLIGHT : COLOR_BTNTEXT));
1435 /* get the rectangle that the text fits in */
1436 DrawTextW(hdc, infoPtr->items[iItem].pszText, -1,
1437 &rcText, DT_CALCRECT);
1440 * If not owner draw, then do the drawing ourselves.
1444 if (infoPtr->himl && (infoPtr->items[iItem].mask & TCIF_IMAGE))
1446 ImageList_GetIconSize(infoPtr->himl, &cx, &cy);
1448 if(lStyle & TCS_VERTICAL)
1449 center_offset = ((drawRect->bottom - drawRect->top) - (cy + VERTICAL_ITEM_PADDING + (rcText.right - rcText.left))) / 2;
1451 center_offset = ((drawRect->right - drawRect->left) - (cx + HORIZONTAL_ITEM_PADDING + (rcText.right - rcText.left))) / 2;
1453 if((lStyle & TCS_VERTICAL) && (lStyle & TCS_BOTTOM))
1455 /* rcImage.left = drawRect->left; */ /* explicit from above rcImage = *drawRect */
1456 rcImage.top = drawRect->top + center_offset;
1457 rcImage.left = drawRect->right - cx; /* if tab is TCS_VERTICAL and TCS_BOTTOM, the text is drawn from the */
1458 /* right side of the tab, but the image still uses the left as its x position */
1459 /* this keeps the image always drawn off of the same side of the tab */
1460 drawRect->top = rcImage.top + (cx + VERTICAL_ITEM_PADDING);
1462 else if(lStyle & TCS_VERTICAL)
1464 /* rcImage.left = drawRect->left; */ /* explicit from above rcImage = *drawRect */
1465 rcImage.top = drawRect->bottom - cy - center_offset;
1467 drawRect->bottom = rcImage.top - VERTICAL_ITEM_PADDING;
1469 else /* normal style, whether TCS_BOTTOM or not */
1471 rcImage.left = drawRect->left + center_offset;
1472 /* rcImage.top = drawRect->top; */ /* explicit from above rcImage = *drawRect */
1474 drawRect->left = rcImage.left + cx + HORIZONTAL_ITEM_PADDING;
1480 infoPtr->items[iItem].iImage,
1486 } else /* no image, so just shift the drawRect borders around */
1488 if(lStyle & TCS_VERTICAL)
1492 currently the rcText rect is flawed because the rotated font does not
1493 often match the horizontal font. So leave this as 0
1494 ((drawRect->bottom - drawRect->top) - (rcText.right - rcText.left)) / 2;
1496 if(lStyle & TCS_BOTTOM)
1497 drawRect->top+=center_offset;
1499 drawRect->bottom-=center_offset;
1503 center_offset = ((drawRect->right - drawRect->left) - (rcText.right - rcText.left)) / 2;
1504 drawRect->left+=center_offset;
1509 if (lStyle & TCS_RIGHTJUSTIFY)
1510 uHorizAlign = DT_CENTER;
1512 uHorizAlign = DT_LEFT;
1514 if(lStyle & TCS_VERTICAL) /* if we are vertical rotate the text and each character */
1516 if(lStyle & TCS_BOTTOM)
1519 nOrientation = -900;
1528 /* to get a font with the escapement and orientation we are looking for, we need to */
1529 /* call CreateFontIndirectA, which requires us to set the values of the logfont we pass in */
1530 if(lStyle & TCS_VERTICAL)
1532 if (!GetObjectA((infoPtr->hFont) ?
1533 infoPtr->hFont : GetStockObject(SYSTEM_FONT),
1534 sizeof(LOGFONTA),&logfont))
1538 lstrcpyA(logfont.lfFaceName, "Arial");
1539 logfont.lfHeight = -MulDiv(iPointSize, GetDeviceCaps(hdc, LOGPIXELSY),
1541 logfont.lfWeight = FW_NORMAL;
1542 logfont.lfItalic = 0;
1543 logfont.lfUnderline = 0;
1544 logfont.lfStrikeOut = 0;
1547 logfont.lfEscapement = nEscapement;
1548 logfont.lfOrientation = nOrientation;
1549 hFont = CreateFontIndirectA(&logfont);
1550 hOldFont = SelectObject(hdc, hFont);
1553 if (lStyle & TCS_VERTICAL)
1556 (lStyle & TCS_BOTTOM) ? drawRect->right : drawRect->left,
1557 (!(lStyle & TCS_BOTTOM)) ? drawRect->bottom : drawRect->top,
1560 infoPtr->items[iItem].pszText,
1561 lstrlenW(infoPtr->items[iItem].pszText),
1569 infoPtr->items[iItem].pszText,
1570 lstrlenW(infoPtr->items[iItem].pszText),
1572 uHorizAlign | DT_SINGLELINE
1576 /* clean things up */
1577 *drawRect = rcTemp; /* restore drawRect */
1579 if(lStyle & TCS_VERTICAL)
1581 SelectObject(hdc, hOldFont); /* restore the original font */
1583 DeleteObject(hFont);
1590 SetBkMode(hdc, oldBkMode);
1591 SelectObject(hdc, holdPen);
1594 /******************************************************************************
1597 * This method is used to draw a single tab into the tab control.
1599 static void TAB_DrawItem(
1604 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
1605 LONG lStyle = GetWindowLongA(hwnd, GWL_STYLE);
1612 * Get the rectangle for the item.
1614 isVisible = TAB_InternalGetItemRect(hwnd,
1622 HBRUSH hbr = CreateSolidBrush (GetSysColor(COLOR_BTNFACE));
1623 HPEN hwPen = GetSysColorPen (COLOR_3DHILIGHT);
1624 HPEN hbPen = GetSysColorPen (COLOR_3DDKSHADOW);
1625 HPEN hShade = GetSysColorPen (COLOR_BTNSHADOW);
1628 BOOL deleteBrush = TRUE;
1630 if (lStyle & TCS_BUTTONS)
1632 /* Get item rectangle */
1635 holdPen = SelectObject (hdc, hwPen);
1637 /* Separators between flat buttons */
1638 /* FIXME: test and correct this if necessary for TCS_FLATBUTTONS style */
1639 if (lStyle & TCS_FLATBUTTONS)
1641 int x = r.right + FLAT_BTN_SPACINGX - 2;
1644 MoveToEx (hdc, x, r.bottom - 1, NULL);
1645 LineTo (hdc, x, r.top - 1);
1649 SelectObject(hdc, hbPen);
1650 MoveToEx (hdc, x, r.bottom - 1, NULL);
1651 LineTo (hdc, x, r.top - 1);
1654 SelectObject (hdc, hShade );
1655 MoveToEx (hdc, x - 1, r.bottom - 1, NULL);
1656 LineTo (hdc, x - 1, r.top - 1);
1659 if (iItem == infoPtr->iSelected)
1661 /* Background color */
1662 if (!((lStyle & TCS_OWNERDRAWFIXED) && infoPtr->fSizeSet))
1664 COLORREF bk = GetSysColor(COLOR_3DHILIGHT);
1666 hbr = GetSysColorBrush(COLOR_SCROLLBAR);
1668 SetTextColor(hdc, GetSysColor(COLOR_3DFACE));
1669 SetBkColor(hdc, bk);
1671 /* if COLOR_WINDOW happens to be the same as COLOR_3DHILIGHT
1672 * we better use 0x55aa bitmap brush to make scrollbar's background
1673 * look different from the window background.
1675 if (bk == GetSysColor(COLOR_WINDOW))
1676 hbr = COMCTL32_hPattern55AABrush;
1678 deleteBrush = FALSE;
1681 /* Erase the background */
1682 FillRect(hdc, &r, hbr);
1686 * The rectangles calculated exclude the right and bottom
1687 * borders of the rectangle. To simplify the following code, those
1688 * borders are shaved-off beforehand.
1694 SelectObject(hdc, hwPen);
1695 MoveToEx (hdc, r.left, r.bottom, NULL);
1696 LineTo (hdc, r.right, r.bottom);
1697 LineTo (hdc, r.right, r.top + 1);
1700 SelectObject(hdc, hbPen);
1701 LineTo (hdc, r.left + 1, r.top + 1);
1702 LineTo (hdc, r.left + 1, r.bottom);
1705 SelectObject (hdc, hShade );
1706 MoveToEx (hdc, r.right, r.top, NULL);
1707 LineTo (hdc, r.left, r.top);
1708 LineTo (hdc, r.left, r.bottom);
1712 /* Erase the background */
1713 FillRect(hdc, &r, hbr);
1715 if (!(lStyle & TCS_FLATBUTTONS))
1718 MoveToEx (hdc, r.left, r.bottom, NULL);
1719 LineTo (hdc, r.left, r.top);
1720 LineTo (hdc, r.right, r.top);
1723 SelectObject(hdc, hbPen);
1724 LineTo (hdc, r.right, r.bottom);
1725 LineTo (hdc, r.left, r.bottom);
1728 SelectObject (hdc, hShade );
1729 MoveToEx (hdc, r.right - 1, r.top, NULL);
1730 LineTo (hdc, r.right - 1, r.bottom - 1);
1731 LineTo (hdc, r.left + 1, r.bottom - 1);
1735 else /* !TCS_BUTTONS */
1737 /* Background color */
1739 hbr = CreateSolidBrush(GetSysColor(COLOR_BTNFACE));
1741 /* We draw a rectangle of different sizes depending on the selection
1743 if (iItem == infoPtr->iSelected)
1749 * Erase the background.
1750 * This is necessary when drawing the selected item since it is larger
1751 * than the others, it might overlap with stuff already drawn by the
1754 FillRect(hdc, &r, hbr);
1758 * The rectangles calculated exclude the right and bottom
1759 * borders of the rectangle. To simplify the following code, those
1760 * borders are shaved-off beforehand.
1765 holdPen = SelectObject (hdc, hwPen);
1766 if(lStyle & TCS_VERTICAL)
1768 if (lStyle & TCS_BOTTOM)
1771 MoveToEx (hdc, r.left, r.top, NULL);
1772 LineTo (hdc, r.right - ROUND_CORNER_SIZE, r.top);
1773 LineTo (hdc, r.right, r.top + ROUND_CORNER_SIZE);
1776 SelectObject(hdc, hbPen);
1777 LineTo (hdc, r.right, r.bottom - ROUND_CORNER_SIZE);
1778 LineTo (hdc, r.right - ROUND_CORNER_SIZE, r.bottom);
1779 LineTo (hdc, r.left - 1, r.bottom);
1782 SelectObject (hdc, hShade );
1783 MoveToEx (hdc, r.right - 1, r.top, NULL);
1784 LineTo (hdc, r.right - 1, r.bottom - 1);
1785 LineTo (hdc, r.left - 1, r.bottom - 1);
1790 MoveToEx (hdc, r.right, r.top, NULL);
1791 LineTo (hdc, r.left + ROUND_CORNER_SIZE, r.top);
1792 LineTo (hdc, r.left, r.top + ROUND_CORNER_SIZE);
1793 LineTo (hdc, r.left, r.bottom - ROUND_CORNER_SIZE);
1796 SelectObject(hdc, hbPen);
1797 LineTo (hdc, r.left + ROUND_CORNER_SIZE, r.bottom);
1798 LineTo (hdc, r.right + 1, r.bottom);
1801 SelectObject (hdc, hShade );
1802 MoveToEx (hdc, r.left + ROUND_CORNER_SIZE - 1, r.bottom - 1, NULL);
1803 LineTo (hdc, r.right + 1, r.bottom - 1);
1808 if (lStyle & TCS_BOTTOM)
1811 MoveToEx (hdc, r.left, r.top, NULL);
1812 LineTo (hdc, r.left, r.bottom - ROUND_CORNER_SIZE);
1813 LineTo (hdc, r.left + ROUND_CORNER_SIZE, r.bottom);
1816 SelectObject(hdc, hbPen);
1817 LineTo (hdc, r.right - ROUND_CORNER_SIZE, r.bottom);
1818 LineTo (hdc, r.right, r.bottom - ROUND_CORNER_SIZE);
1819 LineTo (hdc, r.right, r.top - 1);
1822 SelectObject (hdc, hShade );
1823 MoveToEx (hdc, r.left, r.bottom - 1, NULL);
1824 LineTo (hdc, r.right - ROUND_CORNER_SIZE - 1, r.bottom - 1);
1825 LineTo (hdc, r.right - 1, r.bottom - ROUND_CORNER_SIZE - 1);
1826 LineTo (hdc, r.right - 1, r.top - 1);
1831 if(infoPtr->items[iItem].rect.left == 0) /* if leftmost draw the line longer */
1832 MoveToEx (hdc, r.left, r.bottom, NULL);
1834 MoveToEx (hdc, r.left, r.bottom - 1, NULL);
1836 LineTo (hdc, r.left, r.top + ROUND_CORNER_SIZE);
1837 LineTo (hdc, r.left + ROUND_CORNER_SIZE, r.top);
1838 LineTo (hdc, r.right - ROUND_CORNER_SIZE, r.top);
1841 SelectObject(hdc, hbPen);
1842 LineTo (hdc, r.right, r.top + ROUND_CORNER_SIZE);
1843 LineTo (hdc, r.right, r.bottom + 1);
1847 SelectObject (hdc, hShade );
1848 MoveToEx (hdc, r.right - 1, r.top + ROUND_CORNER_SIZE, NULL);
1849 LineTo (hdc, r.right - 1, r.bottom + 1);
1854 /* This modifies r to be the text rectangle. */
1856 HFONT hOldFont = SelectObject(hdc, infoPtr->hFont);
1857 TAB_DrawItemInterior(hwnd, hdc, iItem, &r);
1858 SelectObject(hdc,hOldFont);
1861 /* Draw the focus rectangle */
1862 if (((lStyle & TCS_FOCUSNEVER) == 0) &&
1863 (GetFocus() == hwnd) &&
1864 (iItem == infoPtr->uFocus) )
1867 InflateRect(&r, -1, -1);
1869 DrawFocusRect(hdc, &r);
1873 SelectObject(hdc, holdPen);
1874 if (deleteBrush) DeleteObject(hbr);
1878 /******************************************************************************
1881 * This method is used to draw the raised border around the tab control
1884 static void TAB_DrawBorder (HWND hwnd, HDC hdc)
1886 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
1888 HPEN hwPen = GetSysColorPen (COLOR_3DHILIGHT);
1889 HPEN hbPen = GetSysColorPen (COLOR_3DDKSHADOW);
1890 HPEN hShade = GetSysColorPen (COLOR_BTNSHADOW);
1892 DWORD lStyle = GetWindowLongA(hwnd, GWL_STYLE);
1894 GetClientRect (hwnd, &rect);
1897 * Adjust for the style
1900 if (infoPtr->uNumItem)
1902 if ((lStyle & TCS_BOTTOM) && !(lStyle & TCS_VERTICAL))
1904 rect.bottom -= (infoPtr->tabHeight - 2) * infoPtr->uNumRows + 2;
1906 else if((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
1908 rect.right -= (infoPtr->tabHeight - 2) * infoPtr->uNumRows + 2;
1910 else if(lStyle & TCS_VERTICAL)
1912 rect.left += (infoPtr->tabHeight - 2) * infoPtr->uNumRows + 2;
1914 else /* not TCS_VERTICAL and not TCS_BOTTOM */
1916 rect.top += (infoPtr->tabHeight - 2) * infoPtr->uNumRows + 1;
1921 * Shave-off the right and bottom margins (exluded in the
1928 htmPen = SelectObject (hdc, hwPen);
1930 MoveToEx (hdc, rect.left, rect.bottom, NULL);
1931 LineTo (hdc, rect.left, rect.top);
1932 LineTo (hdc, rect.right, rect.top);
1935 SelectObject (hdc, hbPen);
1936 LineTo (hdc, rect.right, rect.bottom );
1937 LineTo (hdc, rect.left, rect.bottom);
1940 SelectObject (hdc, hShade );
1941 MoveToEx (hdc, rect.right - 1, rect.top, NULL);
1942 LineTo (hdc, rect.right - 1, rect.bottom - 1);
1943 LineTo (hdc, rect.left, rect.bottom - 1);
1945 SelectObject(hdc, htmPen);
1948 /******************************************************************************
1951 * This method repaints the tab control..
1953 static void TAB_Refresh (HWND hwnd, HDC hdc)
1955 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
1959 if (!infoPtr->DoRedraw)
1962 hOldFont = SelectObject (hdc, infoPtr->hFont);
1964 if (GetWindowLongA(hwnd, GWL_STYLE) & TCS_BUTTONS)
1966 for (i = 0; i < infoPtr->uNumItem; i++)
1967 TAB_DrawItem (hwnd, hdc, i);
1971 /* Draw all the non selected item first */
1972 for (i = 0; i < infoPtr->uNumItem; i++)
1974 if (i != infoPtr->iSelected)
1975 TAB_DrawItem (hwnd, hdc, i);
1978 /* Now, draw the border, draw it before the selected item
1979 * since the selected item overwrites part of the border. */
1980 TAB_DrawBorder (hwnd, hdc);
1982 /* Then, draw the selected item */
1983 TAB_DrawItem (hwnd, hdc, infoPtr->iSelected);
1985 /* If we haven't set the current focus yet, set it now.
1986 * Only happens when we first paint the tab controls */
1987 if (infoPtr->uFocus == -1)
1988 TAB_SetCurFocus(hwnd, infoPtr->iSelected);
1991 SelectObject (hdc, hOldFont);
1995 TAB_GetRowCount (HWND hwnd )
1997 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
1999 return infoPtr->uNumRows;
2003 TAB_SetRedraw (HWND hwnd, WPARAM wParam)
2005 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2007 infoPtr->DoRedraw=(BOOL) wParam;
2011 static LRESULT TAB_EraseBackground(
2018 HBRUSH brush = CreateSolidBrush(GetSysColor(COLOR_BTNFACE));
2020 hdc = givenDC ? givenDC : GetDC(hwnd);
2022 GetClientRect(hwnd, &clientRect);
2024 FillRect(hdc, &clientRect, brush);
2027 ReleaseDC(hwnd, hdc);
2029 DeleteObject(brush);
2034 /******************************************************************************
2035 * TAB_EnsureSelectionVisible
2037 * This method will make sure that the current selection is completely
2038 * visible by scrolling until it is.
2040 static void TAB_EnsureSelectionVisible(
2044 INT iSelected = infoPtr->iSelected;
2045 LONG lStyle = GetWindowLongA(hwnd, GWL_STYLE);
2046 INT iOrigLeftmostVisible = infoPtr->leftmostVisible;
2048 /* set the items row to the bottommost row or topmost row depending on
2050 if ((infoPtr->uNumRows > 1) && !(lStyle & TCS_BUTTONS))
2055 if(lStyle & TCS_VERTICAL)
2056 newselected = infoPtr->items[iSelected].rect.left;
2058 newselected = infoPtr->items[iSelected].rect.top;
2060 /* the target row is always (number of rows - 1)
2061 as row 0 is furthest from the clientRect */
2062 iTargetRow = infoPtr->uNumRows - 1;
2064 if (newselected != iTargetRow)
2067 if(lStyle & TCS_VERTICAL)
2069 for (i=0; i < infoPtr->uNumItem; i++)
2071 /* move everything in the row of the selected item to the iTargetRow */
2072 if (infoPtr->items[i].rect.left == newselected )
2073 infoPtr->items[i].rect.left = iTargetRow;
2076 if (infoPtr->items[i].rect.left > newselected)
2077 infoPtr->items[i].rect.left-=1;
2083 for (i=0; i < infoPtr->uNumItem; i++)
2085 if (infoPtr->items[i].rect.top == newselected )
2086 infoPtr->items[i].rect.top = iTargetRow;
2089 if (infoPtr->items[i].rect.top > newselected)
2090 infoPtr->items[i].rect.top-=1;
2094 TAB_RecalcHotTrack(hwnd, NULL, NULL, NULL);
2099 * Do the trivial cases first.
2101 if ( (!infoPtr->needsScrolling) ||
2102 (infoPtr->hwndUpDown==0) || (lStyle & TCS_VERTICAL))
2105 if (infoPtr->leftmostVisible >= iSelected)
2107 infoPtr->leftmostVisible = iSelected;
2114 /* Calculate the part of the client area that is visible */
2115 GetClientRect(hwnd, &r);
2118 GetClientRect(infoPtr->hwndUpDown, &r);
2121 if ((infoPtr->items[iSelected].rect.right -
2122 infoPtr->items[iSelected].rect.left) >= width )
2124 /* Special case: width of selected item is greater than visible
2127 infoPtr->leftmostVisible = iSelected;
2131 for (i = infoPtr->leftmostVisible; i < infoPtr->uNumItem; i++)
2133 if ((infoPtr->items[iSelected].rect.right -
2134 infoPtr->items[i].rect.left) < width)
2137 infoPtr->leftmostVisible = i;
2141 if (infoPtr->leftmostVisible != iOrigLeftmostVisible)
2142 TAB_RecalcHotTrack(hwnd, NULL, NULL, NULL);
2144 SendMessageA(infoPtr->hwndUpDown, UDM_SETPOS, 0,
2145 MAKELONG(infoPtr->leftmostVisible, 0));
2148 /******************************************************************************
2149 * TAB_InvalidateTabArea
2151 * This method will invalidate the portion of the control that contains the
2152 * tabs. It is called when the state of the control changes and needs
2155 static void TAB_InvalidateTabArea(
2160 DWORD lStyle = GetWindowLongA(hwnd, GWL_STYLE);
2161 INT lastRow = infoPtr->uNumRows - 1;
2163 if (lastRow < 0) return;
2165 GetClientRect(hwnd, &clientRect);
2167 if ((lStyle & TCS_BOTTOM) && !(lStyle & TCS_VERTICAL))
2169 clientRect.top = clientRect.bottom -
2170 infoPtr->tabHeight -
2171 lastRow * (infoPtr->tabHeight - 2) -
2172 ((lStyle & TCS_BUTTONS) ? lastRow * BUTTON_SPACINGY : 0) - 2;
2174 else if((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
2176 clientRect.left = clientRect.right - infoPtr->tabHeight -
2177 lastRow * (infoPtr->tabHeight - 2) -
2178 ((lStyle & TCS_BUTTONS) ? lastRow * BUTTON_SPACINGY : 0) - 2;
2180 else if(lStyle & TCS_VERTICAL)
2182 clientRect.right = clientRect.left + infoPtr->tabHeight +
2183 lastRow * (infoPtr->tabHeight - 2) -
2184 ((lStyle & TCS_BUTTONS) ? lastRow * BUTTON_SPACINGY : 0) + 1;
2189 clientRect.bottom = clientRect.top + infoPtr->tabHeight +
2190 lastRow * (infoPtr->tabHeight - 2) +
2191 ((lStyle & TCS_BUTTONS) ? lastRow * BUTTON_SPACINGY : 0) + 1;
2194 InvalidateRect(hwnd, &clientRect, TRUE);
2198 TAB_Paint (HWND hwnd, WPARAM wParam)
2203 hdc = wParam== 0 ? BeginPaint (hwnd, &ps) : (HDC)wParam;
2204 TAB_Refresh (hwnd, hdc);
2207 EndPaint (hwnd, &ps);
2213 TAB_InsertItemA (HWND hwnd, WPARAM wParam, LPARAM lParam)
2215 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2220 GetClientRect (hwnd, &rect);
2221 TRACE("Rect: %x T %i, L %i, B %i, R %i\n", hwnd,
2222 rect.top, rect.left, rect.bottom, rect.right);
2224 pti = (TCITEMA *)lParam;
2225 iItem = (INT)wParam;
2227 if (iItem < 0) return -1;
2228 if (iItem > infoPtr->uNumItem)
2229 iItem = infoPtr->uNumItem;
2231 if (infoPtr->uNumItem == 0) {
2232 infoPtr->items = COMCTL32_Alloc (sizeof (TAB_ITEM));
2233 infoPtr->uNumItem++;
2234 infoPtr->iSelected = 0;
2237 TAB_ITEM *oldItems = infoPtr->items;
2239 infoPtr->uNumItem++;
2240 infoPtr->items = COMCTL32_Alloc (sizeof (TAB_ITEM) * infoPtr->uNumItem);
2242 /* pre insert copy */
2244 memcpy (&infoPtr->items[0], &oldItems[0],
2245 iItem * sizeof(TAB_ITEM));
2248 /* post insert copy */
2249 if (iItem < infoPtr->uNumItem - 1) {
2250 memcpy (&infoPtr->items[iItem+1], &oldItems[iItem],
2251 (infoPtr->uNumItem - iItem - 1) * sizeof(TAB_ITEM));
2255 if (iItem <= infoPtr->iSelected)
2256 infoPtr->iSelected++;
2258 COMCTL32_Free (oldItems);
2261 infoPtr->items[iItem].mask = pti->mask;
2262 if (pti->mask & TCIF_TEXT)
2263 Str_SetPtrAtoW (&infoPtr->items[iItem].pszText, pti->pszText);
2265 if (pti->mask & TCIF_IMAGE)
2266 infoPtr->items[iItem].iImage = pti->iImage;
2268 if (pti->mask & TCIF_PARAM)
2269 infoPtr->items[iItem].lParam = pti->lParam;
2271 TAB_SetItemBounds(hwnd);
2272 TAB_InvalidateTabArea(hwnd, infoPtr);
2274 TRACE("[%04x]: added item %d %s\n",
2275 hwnd, iItem, debugstr_w(infoPtr->items[iItem].pszText));
2282 TAB_InsertItemW (HWND hwnd, WPARAM wParam, LPARAM lParam)
2284 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2289 GetClientRect (hwnd, &rect);
2290 TRACE("Rect: %x T %i, L %i, B %i, R %i\n", hwnd,
2291 rect.top, rect.left, rect.bottom, rect.right);
2293 pti = (TCITEMW *)lParam;
2294 iItem = (INT)wParam;
2296 if (iItem < 0) return -1;
2297 if (iItem > infoPtr->uNumItem)
2298 iItem = infoPtr->uNumItem;
2300 if (infoPtr->uNumItem == 0) {
2301 infoPtr->items = COMCTL32_Alloc (sizeof (TAB_ITEM));
2302 infoPtr->uNumItem++;
2303 infoPtr->iSelected = 0;
2306 TAB_ITEM *oldItems = infoPtr->items;
2308 infoPtr->uNumItem++;
2309 infoPtr->items = COMCTL32_Alloc (sizeof (TAB_ITEM) * infoPtr->uNumItem);
2311 /* pre insert copy */
2313 memcpy (&infoPtr->items[0], &oldItems[0],
2314 iItem * sizeof(TAB_ITEM));
2317 /* post insert copy */
2318 if (iItem < infoPtr->uNumItem - 1) {
2319 memcpy (&infoPtr->items[iItem+1], &oldItems[iItem],
2320 (infoPtr->uNumItem - iItem - 1) * sizeof(TAB_ITEM));
2324 if (iItem <= infoPtr->iSelected)
2325 infoPtr->iSelected++;
2327 COMCTL32_Free (oldItems);
2330 infoPtr->items[iItem].mask = pti->mask;
2331 if (pti->mask & TCIF_TEXT)
2332 Str_SetPtrW (&infoPtr->items[iItem].pszText, pti->pszText);
2334 if (pti->mask & TCIF_IMAGE)
2335 infoPtr->items[iItem].iImage = pti->iImage;
2337 if (pti->mask & TCIF_PARAM)
2338 infoPtr->items[iItem].lParam = pti->lParam;
2340 TAB_SetItemBounds(hwnd);
2341 TAB_InvalidateTabArea(hwnd, infoPtr);
2343 TRACE("[%04x]: added item %d %s\n",
2344 hwnd, iItem, debugstr_w(infoPtr->items[iItem].pszText));
2351 TAB_SetItemSize (HWND hwnd, WPARAM wParam, LPARAM lParam)
2353 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2354 LONG lStyle = GetWindowLongA(hwnd, GWL_STYLE);
2357 if ((lStyle & TCS_FIXEDWIDTH) || (lStyle & TCS_OWNERDRAWFIXED))
2359 lResult = MAKELONG(infoPtr->tabWidth, infoPtr->tabHeight);
2360 infoPtr->tabWidth = (INT)LOWORD(lParam);
2361 infoPtr->tabHeight = (INT)HIWORD(lParam);
2363 infoPtr->fSizeSet = TRUE;
2369 TAB_SetItemA (HWND hwnd, WPARAM wParam, LPARAM lParam)
2371 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2376 iItem = (INT)wParam;
2377 tabItem = (LPTCITEMA)lParam;
2379 TRACE("%d %p\n", iItem, tabItem);
2380 if ((iItem<0) || (iItem>=infoPtr->uNumItem)) return FALSE;
2382 wineItem = &infoPtr->items[iItem];
2384 if (tabItem->mask & TCIF_IMAGE)
2385 wineItem->iImage = tabItem->iImage;
2387 if (tabItem->mask & TCIF_PARAM)
2388 wineItem->lParam = tabItem->lParam;
2390 if (tabItem->mask & TCIF_RTLREADING)
2391 FIXME("TCIF_RTLREADING\n");
2393 if (tabItem->mask & TCIF_STATE)
2394 wineItem->dwState = tabItem->dwState;
2396 if (tabItem->mask & TCIF_TEXT)
2397 Str_SetPtrAtoW(&wineItem->pszText, tabItem->pszText);
2399 /* Update and repaint tabs */
2400 TAB_SetItemBounds(hwnd);
2401 TAB_InvalidateTabArea(hwnd,infoPtr);
2408 TAB_SetItemW (HWND hwnd, WPARAM wParam, LPARAM lParam)
2410 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2415 iItem = (INT)wParam;
2416 tabItem = (LPTCITEMW)lParam;
2418 TRACE("%d %p\n", iItem, tabItem);
2419 if ((iItem<0) || (iItem>=infoPtr->uNumItem)) return FALSE;
2421 wineItem = &infoPtr->items[iItem];
2423 if (tabItem->mask & TCIF_IMAGE)
2424 wineItem->iImage = tabItem->iImage;
2426 if (tabItem->mask & TCIF_PARAM)
2427 wineItem->lParam = tabItem->lParam;
2429 if (tabItem->mask & TCIF_RTLREADING)
2430 FIXME("TCIF_RTLREADING\n");
2432 if (tabItem->mask & TCIF_STATE)
2433 wineItem->dwState = tabItem->dwState;
2435 if (tabItem->mask & TCIF_TEXT)
2436 Str_SetPtrW(&wineItem->pszText, tabItem->pszText);
2438 /* Update and repaint tabs */
2439 TAB_SetItemBounds(hwnd);
2440 TAB_InvalidateTabArea(hwnd,infoPtr);
2447 TAB_GetItemCount (HWND hwnd, WPARAM wParam, LPARAM lParam)
2449 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2451 return infoPtr->uNumItem;
2456 TAB_GetItemA (HWND hwnd, WPARAM wParam, LPARAM lParam)
2458 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2463 iItem = (INT)wParam;
2464 tabItem = (LPTCITEMA)lParam;
2466 if ((iItem<0) || (iItem>=infoPtr->uNumItem))
2469 wineItem = &infoPtr->items[iItem];
2471 if (tabItem->mask & TCIF_IMAGE)
2472 tabItem->iImage = wineItem->iImage;
2474 if (tabItem->mask & TCIF_PARAM)
2475 tabItem->lParam = wineItem->lParam;
2477 if (tabItem->mask & TCIF_RTLREADING)
2478 FIXME("TCIF_RTLREADING\n");
2480 if (tabItem->mask & TCIF_STATE)
2481 tabItem->dwState = wineItem->dwState;
2483 if (tabItem->mask & TCIF_TEXT)
2484 Str_GetPtrWtoA (wineItem->pszText, tabItem->pszText, tabItem->cchTextMax);
2491 TAB_GetItemW (HWND hwnd, WPARAM wParam, LPARAM lParam)
2493 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2498 iItem = (INT)wParam;
2499 tabItem = (LPTCITEMW)lParam;
2501 if ((iItem<0) || (iItem>=infoPtr->uNumItem))
2504 wineItem=& infoPtr->items[iItem];
2506 if (tabItem->mask & TCIF_IMAGE)
2507 tabItem->iImage = wineItem->iImage;
2509 if (tabItem->mask & TCIF_PARAM)
2510 tabItem->lParam = wineItem->lParam;
2512 if (tabItem->mask & TCIF_RTLREADING)
2513 FIXME("TCIF_RTLREADING\n");
2515 if (tabItem->mask & TCIF_STATE)
2516 tabItem->dwState = wineItem->dwState;
2518 if (tabItem->mask & TCIF_TEXT)
2519 Str_GetPtrW (wineItem->pszText, tabItem->pszText, tabItem->cchTextMax);
2526 TAB_DeleteItem (HWND hwnd, WPARAM wParam, LPARAM lParam)
2528 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2529 INT iItem = (INT) wParam;
2530 BOOL bResult = FALSE;
2532 if ((iItem >= 0) && (iItem < infoPtr->uNumItem))
2534 TAB_ITEM *oldItems = infoPtr->items;
2536 infoPtr->uNumItem--;
2537 infoPtr->items = COMCTL32_Alloc(sizeof (TAB_ITEM) * infoPtr->uNumItem);
2540 memcpy(&infoPtr->items[0], &oldItems[0], iItem * sizeof(TAB_ITEM));
2542 if (iItem < infoPtr->uNumItem)
2543 memcpy(&infoPtr->items[iItem], &oldItems[iItem + 1],
2544 (infoPtr->uNumItem - iItem) * sizeof(TAB_ITEM));
2546 COMCTL32_Free(oldItems);
2548 /* Readjust the selected index */
2549 if ((iItem == infoPtr->iSelected) && (iItem > 0))
2550 infoPtr->iSelected--;
2552 if (iItem < infoPtr->iSelected)
2553 infoPtr->iSelected--;
2555 if (infoPtr->uNumItem == 0)
2556 infoPtr->iSelected = -1;
2558 /* Reposition and repaint tabs */
2559 TAB_SetItemBounds(hwnd);
2560 TAB_InvalidateTabArea(hwnd,infoPtr);
2569 TAB_DeleteAllItems (HWND hwnd, WPARAM wParam, LPARAM lParam)
2571 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2573 COMCTL32_Free (infoPtr->items);
2574 infoPtr->uNumItem = 0;
2575 infoPtr->iSelected = -1;
2576 if (infoPtr->iHotTracked >= 0)
2577 KillTimer(hwnd, TAB_HOTTRACK_TIMER);
2578 infoPtr->iHotTracked = -1;
2580 TAB_SetItemBounds(hwnd);
2581 TAB_InvalidateTabArea(hwnd,infoPtr);
2587 TAB_GetFont (HWND hwnd, WPARAM wParam, LPARAM lParam)
2589 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2592 return (LRESULT)infoPtr->hFont;
2596 TAB_SetFont (HWND hwnd, WPARAM wParam, LPARAM lParam)
2599 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2601 TRACE("%x %lx\n",wParam, lParam);
2603 infoPtr->hFont = (HFONT)wParam;
2605 TAB_SetItemBounds(hwnd);
2607 TAB_InvalidateTabArea(hwnd, infoPtr);
2614 TAB_GetImageList (HWND hwnd, WPARAM wParam, LPARAM lParam)
2616 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2619 return (LRESULT)infoPtr->himl;
2623 TAB_SetImageList (HWND hwnd, WPARAM wParam, LPARAM lParam)
2625 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2626 HIMAGELIST himlPrev;
2629 himlPrev = infoPtr->himl;
2630 infoPtr->himl= (HIMAGELIST)lParam;
2631 return (LRESULT)himlPrev;
2635 TAB_GetUnicodeFormat (HWND hwnd)
2637 TAB_INFO *infoPtr = TAB_GetInfoPtr (hwnd);
2638 return infoPtr->bUnicode;
2642 TAB_SetUnicodeFormat (HWND hwnd, WPARAM wParam)
2644 TAB_INFO *infoPtr = TAB_GetInfoPtr (hwnd);
2645 BOOL bTemp = infoPtr->bUnicode;
2647 infoPtr->bUnicode = (BOOL)wParam;
2653 TAB_Size (HWND hwnd, WPARAM wParam, LPARAM lParam)
2656 /* I'm not really sure what the following code was meant to do.
2657 This is what it is doing:
2658 When WM_SIZE is sent with SIZE_RESTORED, the control
2659 gets positioned in the top left corner.
2663 UINT uPosFlags,cx,cy;
2667 parent = GetParent (hwnd);
2668 GetClientRect(parent, &parent_rect);
2671 if (GetWindowLongA(hwnd, GWL_STYLE) & CCS_NORESIZE)
2672 uPosFlags |= (SWP_NOSIZE | SWP_NOMOVE);
2674 SetWindowPos (hwnd, 0, parent_rect.left, parent_rect.top,
2675 cx, cy, uPosFlags | SWP_NOZORDER);
2677 FIXME("WM_SIZE flag %x %lx not handled\n", wParam, lParam);
2680 /* Recompute the size/position of the tabs. */
2681 TAB_SetItemBounds (hwnd);
2683 /* Force a repaint of the control. */
2684 InvalidateRect(hwnd, NULL, TRUE);
2691 TAB_Create (HWND hwnd, WPARAM wParam, LPARAM lParam)
2694 TEXTMETRICA fontMetrics;
2699 infoPtr = (TAB_INFO *)COMCTL32_Alloc (sizeof(TAB_INFO));
2701 SetWindowLongA(hwnd, 0, (DWORD)infoPtr);
2703 infoPtr->uNumItem = 0;
2704 infoPtr->uNumRows = 0;
2707 infoPtr->hcurArrow = LoadCursorA (0, IDC_ARROWA);
2708 infoPtr->iSelected = -1;
2709 infoPtr->iHotTracked = -1;
2710 infoPtr->uFocus = -1;
2711 infoPtr->hwndToolTip = 0;
2712 infoPtr->DoRedraw = TRUE;
2713 infoPtr->needsScrolling = FALSE;
2714 infoPtr->hwndUpDown = 0;
2715 infoPtr->leftmostVisible = 0;
2716 infoPtr->fSizeSet = FALSE;
2717 infoPtr->bUnicode = IsWindowUnicode (hwnd);
2719 TRACE("Created tab control, hwnd [%04x]\n", hwnd);
2721 /* The tab control always has the WS_CLIPSIBLINGS style. Even
2722 if you don't specify it in CreateWindow. This is necessary in
2723 order for paint to work correctly. This follows windows behaviour. */
2724 dwStyle = GetWindowLongA(hwnd, GWL_STYLE);
2725 SetWindowLongA(hwnd, GWL_STYLE, dwStyle|WS_CLIPSIBLINGS);
2727 if (dwStyle & TCS_TOOLTIPS) {
2728 /* Create tooltip control */
2729 infoPtr->hwndToolTip =
2730 CreateWindowExA (0, TOOLTIPS_CLASSA, NULL, 0,
2731 CW_USEDEFAULT, CW_USEDEFAULT,
2732 CW_USEDEFAULT, CW_USEDEFAULT,
2735 /* Send NM_TOOLTIPSCREATED notification */
2736 if (infoPtr->hwndToolTip) {
2737 NMTOOLTIPSCREATED nmttc;
2739 nmttc.hdr.hwndFrom = hwnd;
2740 nmttc.hdr.idFrom = GetWindowLongA(hwnd, GWL_ID);
2741 nmttc.hdr.code = NM_TOOLTIPSCREATED;
2742 nmttc.hwndToolTips = infoPtr->hwndToolTip;
2744 SendMessageA (GetParent (hwnd), WM_NOTIFY,
2745 (WPARAM)GetWindowLongA(hwnd, GWL_ID), (LPARAM)&nmttc);
2750 * We need to get text information so we need a DC and we need to select
2754 hOldFont = SelectObject (hdc, GetStockObject (SYSTEM_FONT));
2756 /* Use the system font to determine the initial height of a tab. */
2757 GetTextMetricsA(hdc, &fontMetrics);
2760 * Make sure there is enough space for the letters + growing the
2761 * selected item + extra space for the selected item.
2763 infoPtr->tabHeight = fontMetrics.tmHeight + 2 * VERTICAL_ITEM_PADDING +
2764 SELECTED_TAB_OFFSET;
2766 /* Initialize the width of a tab. */
2767 infoPtr->tabWidth = DEFAULT_TAB_WIDTH;
2769 SelectObject (hdc, hOldFont);
2770 ReleaseDC(hwnd, hdc);
2776 TAB_Destroy (HWND hwnd, WPARAM wParam, LPARAM lParam)
2778 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2784 if (infoPtr->items) {
2785 for (iItem = 0; iItem < infoPtr->uNumItem; iItem++) {
2786 if (infoPtr->items[iItem].pszText)
2787 COMCTL32_Free (infoPtr->items[iItem].pszText);
2789 COMCTL32_Free (infoPtr->items);
2792 if (infoPtr->hwndToolTip)
2793 DestroyWindow (infoPtr->hwndToolTip);
2795 if (infoPtr->hwndUpDown)
2796 DestroyWindow(infoPtr->hwndUpDown);
2798 if (infoPtr->iHotTracked >= 0)
2799 KillTimer(hwnd, TAB_HOTTRACK_TIMER);
2801 COMCTL32_Free (infoPtr);
2802 SetWindowLongA(hwnd, 0, 0);
2806 static LRESULT WINAPI
2807 TAB_WindowProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
2810 TRACE("hwnd=%x msg=%x wParam=%x lParam=%lx\n", hwnd, uMsg, wParam, lParam);
2811 if (!TAB_GetInfoPtr(hwnd) && (uMsg != WM_CREATE))
2812 return DefWindowProcA (hwnd, uMsg, wParam, lParam);
2816 case TCM_GETIMAGELIST:
2817 return TAB_GetImageList (hwnd, wParam, lParam);
2819 case TCM_SETIMAGELIST:
2820 return TAB_SetImageList (hwnd, wParam, lParam);
2822 case TCM_GETITEMCOUNT:
2823 return TAB_GetItemCount (hwnd, wParam, lParam);
2826 return TAB_GetItemA (hwnd, wParam, lParam);
2829 return TAB_GetItemW (hwnd, wParam, lParam);
2832 return TAB_SetItemA (hwnd, wParam, lParam);
2835 return TAB_SetItemW (hwnd, wParam, lParam);
2837 case TCM_DELETEITEM:
2838 return TAB_DeleteItem (hwnd, wParam, lParam);
2840 case TCM_DELETEALLITEMS:
2841 return TAB_DeleteAllItems (hwnd, wParam, lParam);
2843 case TCM_GETITEMRECT:
2844 return TAB_GetItemRect (hwnd, wParam, lParam);
2847 return TAB_GetCurSel (hwnd);
2850 return TAB_HitTest (hwnd, wParam, lParam);
2853 return TAB_SetCurSel (hwnd, wParam);
2855 case TCM_INSERTITEMA:
2856 return TAB_InsertItemA (hwnd, wParam, lParam);
2858 case TCM_INSERTITEMW:
2859 return TAB_InsertItemW (hwnd, wParam, lParam);
2861 case TCM_SETITEMEXTRA:
2862 FIXME("Unimplemented msg TCM_SETITEMEXTRA\n");
2865 case TCM_ADJUSTRECT:
2866 return TAB_AdjustRect (hwnd, (BOOL)wParam, (LPRECT)lParam);
2868 case TCM_SETITEMSIZE:
2869 return TAB_SetItemSize (hwnd, wParam, lParam);
2871 case TCM_REMOVEIMAGE:
2872 FIXME("Unimplemented msg TCM_REMOVEIMAGE\n");
2875 case TCM_SETPADDING:
2876 FIXME("Unimplemented msg TCM_SETPADDING\n");
2879 case TCM_GETROWCOUNT:
2880 return TAB_GetRowCount(hwnd);
2882 case TCM_GETUNICODEFORMAT:
2883 return TAB_GetUnicodeFormat (hwnd);
2885 case TCM_SETUNICODEFORMAT:
2886 return TAB_SetUnicodeFormat (hwnd, wParam);
2888 case TCM_HIGHLIGHTITEM:
2889 FIXME("Unimplemented msg TCM_HIGHLIGHTITEM\n");
2892 case TCM_GETTOOLTIPS:
2893 return TAB_GetToolTips (hwnd, wParam, lParam);
2895 case TCM_SETTOOLTIPS:
2896 return TAB_SetToolTips (hwnd, wParam, lParam);
2898 case TCM_GETCURFOCUS:
2899 return TAB_GetCurFocus (hwnd);
2901 case TCM_SETCURFOCUS:
2902 return TAB_SetCurFocus (hwnd, wParam);
2904 case TCM_SETMINTABWIDTH:
2905 FIXME("Unimplemented msg TCM_SETMINTABWIDTH\n");
2908 case TCM_DESELECTALL:
2909 FIXME("Unimplemented msg TCM_DESELECTALL\n");
2912 case TCM_GETEXTENDEDSTYLE:
2913 FIXME("Unimplemented msg TCM_GETEXTENDEDSTYLE\n");
2916 case TCM_SETEXTENDEDSTYLE:
2917 FIXME("Unimplemented msg TCM_SETEXTENDEDSTYLE\n");
2921 return TAB_GetFont (hwnd, wParam, lParam);
2924 return TAB_SetFont (hwnd, wParam, lParam);
2927 return TAB_Create (hwnd, wParam, lParam);
2930 return TAB_Destroy (hwnd, wParam, lParam);
2933 return DLGC_WANTARROWS | DLGC_WANTCHARS;
2935 case WM_LBUTTONDOWN:
2936 return TAB_LButtonDown (hwnd, wParam, lParam);
2939 return TAB_LButtonUp (hwnd, wParam, lParam);
2942 return SendMessageA(GetParent(hwnd), WM_NOTIFY, wParam, lParam);
2944 case WM_RBUTTONDOWN:
2945 return TAB_RButtonDown (hwnd, wParam, lParam);
2948 return TAB_MouseMove (hwnd, wParam, lParam);
2951 return TAB_EraseBackground (hwnd, (HDC)wParam);
2954 return TAB_Paint (hwnd, wParam);
2957 return TAB_Size (hwnd, wParam, lParam);
2960 return TAB_SetRedraw (hwnd, wParam);
2963 return TAB_OnHScroll(hwnd, (int)LOWORD(wParam), (int)HIWORD(wParam), (HWND)lParam);
2965 case WM_STYLECHANGED:
2966 TAB_SetItemBounds (hwnd);
2967 InvalidateRect(hwnd, NULL, TRUE);
2972 return TAB_FocusChanging(hwnd, uMsg, wParam, lParam);
2975 return TAB_KeyUp(hwnd, wParam);
2977 return TAB_NCHitTest(hwnd, lParam);
2980 if (uMsg >= WM_USER)
2981 WARN("unknown msg %04x wp=%08x lp=%08lx\n",
2982 uMsg, wParam, lParam);
2983 return DefWindowProcA(hwnd, uMsg, wParam, lParam);
2995 ZeroMemory (&wndClass, sizeof(WNDCLASSA));
2996 wndClass.style = CS_GLOBALCLASS | CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
2997 wndClass.lpfnWndProc = (WNDPROC)TAB_WindowProc;
2998 wndClass.cbClsExtra = 0;
2999 wndClass.cbWndExtra = sizeof(TAB_INFO *);
3000 wndClass.hCursor = LoadCursorA (0, IDC_ARROWA);
3001 wndClass.hbrBackground = (HBRUSH)NULL;
3002 wndClass.lpszClassName = WC_TABCONTROLA;
3004 RegisterClassA (&wndClass);
3009 TAB_Unregister (void)
3011 UnregisterClassA (WC_TABCONTROLA, (HINSTANCE)NULL);