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 Microsoft trys to even out the rows. place the same
1164 * number of tabs in each row. So lets give that a shot
1168 tabPerRow = infoPtr->uNumItem / (infoPtr->uNumRows);
1169 remTab = infoPtr->uNumItem % (infoPtr->uNumRows);
1171 for (iItm=0,iRow=0,iCount=0,curItemLeftPos=0;
1172 iItm<infoPtr->uNumItem;
1175 /* if we have reached the maximum number of tabs on this row */
1176 /* move to the next row, reset our current item left position and */
1177 /* the count of items on this row */
1178 if (iCount >= ((iRow<remTab)?tabPerRow + 1:tabPerRow))
1185 /* normalize the current rect */
1187 /* shift the item to the left side of the clientRect */
1188 infoPtr->items[iItm].rect.right -=
1189 infoPtr->items[iItm].rect.left;
1190 infoPtr->items[iItm].rect.left = 0;
1192 /* shift the item to the right to place it as the next item in this row */
1193 infoPtr->items[iItm].rect.left += curItemLeftPos;
1194 infoPtr->items[iItm].rect.right += curItemLeftPos;
1195 infoPtr->items[iItm].rect.top = iRow;
1196 if (lStyle & TCS_BUTTONS)
1198 curItemLeftPos = infoPtr->items[iItm].rect.right + 1;
1199 if (lStyle & TCS_FLATBUTTONS)
1200 curItemLeftPos += FLAT_BTN_SPACINGX;
1203 curItemLeftPos = infoPtr->items[iItm].rect.right;
1210 while(iIndexStart < infoPtr->uNumItem)
1213 * find the indexs of the row
1215 /* find the first item on the next row */
1216 for (iIndexEnd=iIndexStart;
1217 (iIndexEnd < infoPtr->uNumItem) &&
1218 (infoPtr->items[iIndexEnd].rect.top ==
1219 infoPtr->items[iIndexStart].rect.top) ;
1221 /* intentionaly blank */;
1224 * we need to justify these tabs so they fill the whole given
1228 /* find the amount of space remaining on this row */
1229 widthDiff = clientRect.right - (2 * SELECTED_TAB_OFFSET) -
1230 infoPtr->items[iIndexEnd - 1].rect.right;
1232 /* iCount is the number of tab items on this row */
1233 iCount = iIndexEnd - iIndexStart;
1238 remainder = widthDiff % iCount;
1239 widthDiff = widthDiff / iCount;
1240 /* add widthDiff/iCount, or extra space/items on row, to each item on this row */
1241 for (iIndex=iIndexStart,iCount=0; iIndex < iIndexEnd;
1244 infoPtr->items[iIndex].rect.left += iCount * widthDiff;
1245 infoPtr->items[iIndex].rect.right += (iCount + 1) * widthDiff;
1247 infoPtr->items[iIndex - 1].rect.right += remainder;
1249 else /* we have only one item on this row, make it take up the entire row */
1251 infoPtr->items[iIndexStart].rect.left = clientRect.left;
1252 infoPtr->items[iIndexStart].rect.right = clientRect.right - 4;
1256 iIndexStart = iIndexEnd;
1261 /* if TCS_VERTICAL rotate the tabs so they are along the side of the clientRect */
1262 if(lStyle & TCS_VERTICAL)
1265 for(iIndex = 0; iIndex < infoPtr->uNumItem; iIndex++)
1267 rcItem = &(infoPtr->items[iIndex].rect);
1269 rcOriginal = *rcItem;
1271 /* this is rotating the items by 90 degrees around the center of the control */
1272 rcItem->top = (clientRect.right - (rcOriginal.left - clientRect.left)) - (rcOriginal.right - rcOriginal.left);
1273 rcItem->bottom = rcItem->top + (rcOriginal.right - rcOriginal.left);
1274 rcItem->left = rcOriginal.top;
1275 rcItem->right = rcOriginal.bottom;
1279 TAB_EnsureSelectionVisible(hwnd,infoPtr);
1280 TAB_RecalcHotTrack(hwnd, NULL, NULL, NULL);
1283 SelectObject (hdc, hOldFont);
1284 ReleaseDC (hwnd, hdc);
1287 /******************************************************************************
1288 * TAB_DrawItemInterior
1290 * This method is used to draw the interior (text and icon) of a single tab
1291 * into the tab control.
1294 TAB_DrawItemInterior
1302 TAB_INFO* infoPtr = TAB_GetInfoPtr(hwnd);
1303 LONG lStyle = GetWindowLongA(hwnd, GWL_STYLE);
1307 HPEN htextPen = GetSysColorPen (COLOR_BTNTEXT);
1311 if (drawRect == NULL)
1318 * Get the rectangle for the item.
1320 isVisible = TAB_InternalGetItemRect(hwnd, infoPtr, iItem, &itemRect, &selectedRect);
1325 * Make sure drawRect points to something valid; simplifies code.
1327 drawRect = &localRect;
1330 * This logic copied from the part of TAB_DrawItem which draws
1331 * the tab background. It's important to keep it in sync. I
1332 * would have liked to avoid code duplication, but couldn't figure
1333 * out how without making spaghetti of TAB_DrawItem.
1335 if (lStyle & TCS_BUTTONS)
1337 *drawRect = itemRect;
1338 if (iItem == infoPtr->iSelected)
1346 if (iItem == infoPtr->iSelected)
1347 *drawRect = selectedRect;
1349 *drawRect = itemRect;
1358 holdPen = SelectObject(hdc, htextPen);
1360 oldBkMode = SetBkMode(hdc, TRANSPARENT);
1361 SetTextColor(hdc, GetSysColor((iItem == infoPtr->iHotTracked) ? COLOR_HIGHLIGHT : COLOR_BTNTEXT));
1364 * Deflate the rectangle to acount for the padding
1366 if(lStyle & TCS_VERTICAL)
1367 InflateRect(drawRect, -VERTICAL_ITEM_PADDING, -HORIZONTAL_ITEM_PADDING);
1369 InflateRect(drawRect, -HORIZONTAL_ITEM_PADDING, -VERTICAL_ITEM_PADDING);
1373 * if owner draw, tell the owner to draw
1375 if ((lStyle & TCS_OWNERDRAWFIXED) && GetParent(hwnd))
1381 * get the control id
1383 id = GetWindowLongA( hwnd, GWL_ID );
1386 * put together the DRAWITEMSTRUCT
1388 dis.CtlType = ODT_TAB;
1391 dis.itemAction = ODA_DRAWENTIRE;
1392 if ( iItem == infoPtr->iSelected )
1393 dis.itemState = ODS_SELECTED;
1396 dis.hwndItem = hwnd; /* */
1398 dis.rcItem = *drawRect; /* */
1399 dis.itemData = infoPtr->items[iItem].lParam;
1402 * send the draw message
1404 SendMessageA( GetParent(hwnd), WM_DRAWITEM, (WPARAM)id, (LPARAM)&dis );
1415 HFONT hOldFont = 0; /* stop uninitialized warning */
1417 INT nEscapement = 0; /* stop uninitialized warning */
1418 INT nOrientation = 0; /* stop uninitialized warning */
1421 /* used to center the icon and text in the tab */
1425 /* set rcImage to drawRect, we will use top & left in our ImageList_Draw call */
1426 rcImage = *drawRect;
1431 * Setup for text output
1433 oldBkMode = SetBkMode(hdc, TRANSPARENT);
1434 SetTextColor(hdc, GetSysColor((iItem == infoPtr->iHotTracked) ? COLOR_HIGHLIGHT : COLOR_BTNTEXT));
1436 /* get the rectangle that the text fits in */
1437 DrawTextW(hdc, infoPtr->items[iItem].pszText, -1,
1438 &rcText, DT_CALCRECT);
1441 * If not owner draw, then do the drawing ourselves.
1445 if (infoPtr->himl && (infoPtr->items[iItem].mask & TCIF_IMAGE))
1447 ImageList_GetIconSize(infoPtr->himl, &cx, &cy);
1449 if(lStyle & TCS_VERTICAL)
1450 center_offset = ((drawRect->bottom - drawRect->top) - (cy + VERTICAL_ITEM_PADDING + (rcText.right - rcText.left))) / 2;
1452 center_offset = ((drawRect->right - drawRect->left) - (cx + HORIZONTAL_ITEM_PADDING + (rcText.right - rcText.left))) / 2;
1454 if((lStyle & TCS_VERTICAL) && (lStyle & TCS_BOTTOM))
1456 /* rcImage.left = drawRect->left; */ /* explicit from above rcImage = *drawRect */
1457 rcImage.top = drawRect->top + center_offset;
1458 rcImage.left = drawRect->right - cx; /* if tab is TCS_VERTICAL and TCS_BOTTOM, the text is drawn from the */
1459 /* right side of the tab, but the image still uses the left as its x position */
1460 /* this keeps the image always drawn off of the same side of the tab */
1461 drawRect->top = rcImage.top + (cx + VERTICAL_ITEM_PADDING);
1463 else if(lStyle & TCS_VERTICAL)
1465 /* rcImage.left = drawRect->left; */ /* explicit from above rcImage = *drawRect */
1466 rcImage.top = drawRect->bottom - cy - center_offset;
1468 drawRect->bottom = rcImage.top - VERTICAL_ITEM_PADDING;
1470 else /* normal style, whether TCS_BOTTOM or not */
1472 rcImage.left = drawRect->left + center_offset;
1473 /* rcImage.top = drawRect->top; */ /* explicit from above rcImage = *drawRect */
1475 drawRect->left = rcImage.left + cx + HORIZONTAL_ITEM_PADDING;
1481 infoPtr->items[iItem].iImage,
1487 } else /* no image, so just shift the drawRect borders around */
1489 if(lStyle & TCS_VERTICAL)
1493 currently the rcText rect is flawed because the rotated font does not
1494 often match the horizontal font. So leave this as 0
1495 ((drawRect->bottom - drawRect->top) - (rcText.right - rcText.left)) / 2;
1497 if(lStyle & TCS_BOTTOM)
1498 drawRect->top+=center_offset;
1500 drawRect->bottom-=center_offset;
1504 center_offset = ((drawRect->right - drawRect->left) - (rcText.right - rcText.left)) / 2;
1505 drawRect->left+=center_offset;
1510 if (lStyle & TCS_RIGHTJUSTIFY)
1511 uHorizAlign = DT_CENTER;
1513 uHorizAlign = DT_LEFT;
1515 if(lStyle & TCS_VERTICAL) /* if we are vertical rotate the text and each character */
1517 if(lStyle & TCS_BOTTOM)
1520 nOrientation = -900;
1529 /* to get a font with the escapement and orientation we are looking for, we need to */
1530 /* call CreateFontIndirectA, which requires us to set the values of the logfont we pass in */
1531 if(lStyle & TCS_VERTICAL)
1533 if (!GetObjectA((infoPtr->hFont) ?
1534 infoPtr->hFont : GetStockObject(SYSTEM_FONT),
1535 sizeof(LOGFONTA),&logfont))
1539 lstrcpyA(logfont.lfFaceName, "Arial");
1540 logfont.lfHeight = -MulDiv(iPointSize, GetDeviceCaps(hdc, LOGPIXELSY),
1542 logfont.lfWeight = FW_NORMAL;
1543 logfont.lfItalic = 0;
1544 logfont.lfUnderline = 0;
1545 logfont.lfStrikeOut = 0;
1548 logfont.lfEscapement = nEscapement;
1549 logfont.lfOrientation = nOrientation;
1550 hFont = CreateFontIndirectA(&logfont);
1551 hOldFont = SelectObject(hdc, hFont);
1554 if (lStyle & TCS_VERTICAL)
1557 (lStyle & TCS_BOTTOM) ? drawRect->right : drawRect->left,
1558 (!(lStyle & TCS_BOTTOM)) ? drawRect->bottom : drawRect->top,
1561 infoPtr->items[iItem].pszText,
1562 lstrlenW(infoPtr->items[iItem].pszText),
1570 infoPtr->items[iItem].pszText,
1571 lstrlenW(infoPtr->items[iItem].pszText),
1573 uHorizAlign | DT_SINGLELINE
1577 /* clean things up */
1578 *drawRect = rcTemp; /* restore drawRect */
1580 if(lStyle & TCS_VERTICAL)
1582 SelectObject(hdc, hOldFont); /* restore the original font */
1584 DeleteObject(hFont);
1591 SetBkMode(hdc, oldBkMode);
1592 SelectObject(hdc, holdPen);
1595 /******************************************************************************
1598 * This method is used to draw a single tab into the tab control.
1600 static void TAB_DrawItem(
1605 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
1606 LONG lStyle = GetWindowLongA(hwnd, GWL_STYLE);
1613 * Get the rectangle for the item.
1615 isVisible = TAB_InternalGetItemRect(hwnd,
1623 HBRUSH hbr = CreateSolidBrush (GetSysColor(COLOR_BTNFACE));
1624 HPEN hwPen = GetSysColorPen (COLOR_3DHILIGHT);
1625 HPEN hbPen = GetSysColorPen (COLOR_3DDKSHADOW);
1626 HPEN hShade = GetSysColorPen (COLOR_BTNSHADOW);
1629 BOOL deleteBrush = TRUE;
1631 if (lStyle & TCS_BUTTONS)
1633 /* Get item rectangle */
1636 holdPen = SelectObject (hdc, hwPen);
1638 /* Separators between flat buttons */
1639 /* FIXME: test and correct this if necessary for TCS_FLATBUTTONS style */
1640 if (lStyle & TCS_FLATBUTTONS)
1642 int x = r.right + FLAT_BTN_SPACINGX - 2;
1645 MoveToEx (hdc, x, r.bottom - 1, NULL);
1646 LineTo (hdc, x, r.top - 1);
1650 SelectObject(hdc, hbPen);
1651 MoveToEx (hdc, x, r.bottom - 1, NULL);
1652 LineTo (hdc, x, r.top - 1);
1655 SelectObject (hdc, hShade );
1656 MoveToEx (hdc, x - 1, r.bottom - 1, NULL);
1657 LineTo (hdc, x - 1, r.top - 1);
1660 if (iItem == infoPtr->iSelected)
1662 /* Background color */
1663 if (!((lStyle & TCS_OWNERDRAWFIXED) && infoPtr->fSizeSet))
1665 COLORREF bk = GetSysColor(COLOR_3DHILIGHT);
1667 hbr = GetSysColorBrush(COLOR_SCROLLBAR);
1669 SetTextColor(hdc, GetSysColor(COLOR_3DFACE));
1670 SetBkColor(hdc, bk);
1672 /* if COLOR_WINDOW happens to be the same as COLOR_3DHILIGHT
1673 * we better use 0x55aa bitmap brush to make scrollbar's background
1674 * look different from the window background.
1676 if (bk == GetSysColor(COLOR_WINDOW))
1677 hbr = COMCTL32_hPattern55AABrush;
1679 deleteBrush = FALSE;
1682 /* Erase the background */
1683 FillRect(hdc, &r, hbr);
1687 * The rectangles calculated exclude the right and bottom
1688 * borders of the rectangle. To simplify the following code, those
1689 * borders are shaved-off beforehand.
1695 SelectObject(hdc, hwPen);
1696 MoveToEx (hdc, r.left, r.bottom, NULL);
1697 LineTo (hdc, r.right, r.bottom);
1698 LineTo (hdc, r.right, r.top + 1);
1701 SelectObject(hdc, hbPen);
1702 LineTo (hdc, r.left + 1, r.top + 1);
1703 LineTo (hdc, r.left + 1, r.bottom);
1706 SelectObject (hdc, hShade );
1707 MoveToEx (hdc, r.right, r.top, NULL);
1708 LineTo (hdc, r.left, r.top);
1709 LineTo (hdc, r.left, r.bottom);
1713 /* Erase the background */
1714 FillRect(hdc, &r, hbr);
1716 if (!(lStyle & TCS_FLATBUTTONS))
1719 MoveToEx (hdc, r.left, r.bottom, NULL);
1720 LineTo (hdc, r.left, r.top);
1721 LineTo (hdc, r.right, r.top);
1724 SelectObject(hdc, hbPen);
1725 LineTo (hdc, r.right, r.bottom);
1726 LineTo (hdc, r.left, r.bottom);
1729 SelectObject (hdc, hShade );
1730 MoveToEx (hdc, r.right - 1, r.top, NULL);
1731 LineTo (hdc, r.right - 1, r.bottom - 1);
1732 LineTo (hdc, r.left + 1, r.bottom - 1);
1736 else /* !TCS_BUTTONS */
1738 /* Background color */
1740 hbr = CreateSolidBrush(GetSysColor(COLOR_BTNFACE));
1742 /* We draw a rectangle of different sizes depending on the selection
1744 if (iItem == infoPtr->iSelected)
1750 * Erase the background.
1751 * This is necessary when drawing the selected item since it is larger
1752 * than the others, it might overlap with stuff already drawn by the
1755 FillRect(hdc, &r, hbr);
1759 * The rectangles calculated exclude the right and bottom
1760 * borders of the rectangle. To simplify the following code, those
1761 * borders are shaved-off beforehand.
1766 holdPen = SelectObject (hdc, hwPen);
1767 if(lStyle & TCS_VERTICAL)
1769 if (lStyle & TCS_BOTTOM)
1772 MoveToEx (hdc, r.left, r.top, NULL);
1773 LineTo (hdc, r.right - ROUND_CORNER_SIZE, r.top);
1774 LineTo (hdc, r.right, r.top + ROUND_CORNER_SIZE);
1777 SelectObject(hdc, hbPen);
1778 LineTo (hdc, r.right, r.bottom - ROUND_CORNER_SIZE);
1779 LineTo (hdc, r.right - ROUND_CORNER_SIZE, r.bottom);
1780 LineTo (hdc, r.left - 1, r.bottom);
1783 SelectObject (hdc, hShade );
1784 MoveToEx (hdc, r.right - 1, r.top, NULL);
1785 LineTo (hdc, r.right - 1, r.bottom - 1);
1786 LineTo (hdc, r.left - 1, r.bottom - 1);
1791 MoveToEx (hdc, r.right, r.top, NULL);
1792 LineTo (hdc, r.left + ROUND_CORNER_SIZE, r.top);
1793 LineTo (hdc, r.left, r.top + ROUND_CORNER_SIZE);
1794 LineTo (hdc, r.left, r.bottom - ROUND_CORNER_SIZE);
1797 SelectObject(hdc, hbPen);
1798 LineTo (hdc, r.left + ROUND_CORNER_SIZE, r.bottom);
1799 LineTo (hdc, r.right + 1, r.bottom);
1802 SelectObject (hdc, hShade );
1803 MoveToEx (hdc, r.left + ROUND_CORNER_SIZE - 1, r.bottom - 1, NULL);
1804 LineTo (hdc, r.right + 1, r.bottom - 1);
1809 if (lStyle & TCS_BOTTOM)
1812 MoveToEx (hdc, r.left, r.top, NULL);
1813 LineTo (hdc, r.left, r.bottom - ROUND_CORNER_SIZE);
1814 LineTo (hdc, r.left + ROUND_CORNER_SIZE, r.bottom);
1817 SelectObject(hdc, hbPen);
1818 LineTo (hdc, r.right - ROUND_CORNER_SIZE, r.bottom);
1819 LineTo (hdc, r.right, r.bottom - ROUND_CORNER_SIZE);
1820 LineTo (hdc, r.right, r.top - 1);
1823 SelectObject (hdc, hShade );
1824 MoveToEx (hdc, r.left, r.bottom - 1, NULL);
1825 LineTo (hdc, r.right - ROUND_CORNER_SIZE - 1, r.bottom - 1);
1826 LineTo (hdc, r.right - 1, r.bottom - ROUND_CORNER_SIZE - 1);
1827 LineTo (hdc, r.right - 1, r.top - 1);
1832 if(infoPtr->items[iItem].rect.left == 0) /* if leftmost draw the line longer */
1833 MoveToEx (hdc, r.left, r.bottom, NULL);
1835 MoveToEx (hdc, r.left, r.bottom - 1, NULL);
1837 LineTo (hdc, r.left, r.top + ROUND_CORNER_SIZE);
1838 LineTo (hdc, r.left + ROUND_CORNER_SIZE, r.top);
1839 LineTo (hdc, r.right - ROUND_CORNER_SIZE, r.top);
1842 SelectObject(hdc, hbPen);
1843 LineTo (hdc, r.right, r.top + ROUND_CORNER_SIZE);
1844 LineTo (hdc, r.right, r.bottom + 1);
1848 SelectObject (hdc, hShade );
1849 MoveToEx (hdc, r.right - 1, r.top + ROUND_CORNER_SIZE, NULL);
1850 LineTo (hdc, r.right - 1, r.bottom + 1);
1855 /* This modifies r to be the text rectangle. */
1857 HFONT hOldFont = SelectObject(hdc, infoPtr->hFont);
1858 TAB_DrawItemInterior(hwnd, hdc, iItem, &r);
1859 SelectObject(hdc,hOldFont);
1862 /* Draw the focus rectangle */
1863 if (((lStyle & TCS_FOCUSNEVER) == 0) &&
1864 (GetFocus() == hwnd) &&
1865 (iItem == infoPtr->uFocus) )
1868 InflateRect(&r, -1, -1);
1870 DrawFocusRect(hdc, &r);
1874 SelectObject(hdc, holdPen);
1875 if (deleteBrush) DeleteObject(hbr);
1879 /******************************************************************************
1882 * This method is used to draw the raised border around the tab control
1885 static void TAB_DrawBorder (HWND hwnd, HDC hdc)
1887 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
1889 HPEN hwPen = GetSysColorPen (COLOR_3DHILIGHT);
1890 HPEN hbPen = GetSysColorPen (COLOR_3DDKSHADOW);
1891 HPEN hShade = GetSysColorPen (COLOR_BTNSHADOW);
1893 DWORD lStyle = GetWindowLongA(hwnd, GWL_STYLE);
1895 GetClientRect (hwnd, &rect);
1898 * Adjust for the style
1901 if (infoPtr->uNumItem)
1903 if ((lStyle & TCS_BOTTOM) && !(lStyle & TCS_VERTICAL))
1905 rect.bottom -= (infoPtr->tabHeight - 2) * infoPtr->uNumRows + 2;
1907 else if((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
1909 rect.right -= (infoPtr->tabHeight - 2) * infoPtr->uNumRows + 2;
1911 else if(lStyle & TCS_VERTICAL)
1913 rect.left += (infoPtr->tabHeight - 2) * infoPtr->uNumRows + 2;
1915 else /* not TCS_VERTICAL and not TCS_BOTTOM */
1917 rect.top += (infoPtr->tabHeight - 2) * infoPtr->uNumRows + 1;
1922 * Shave-off the right and bottom margins (exluded in the
1929 htmPen = SelectObject (hdc, hwPen);
1931 MoveToEx (hdc, rect.left, rect.bottom, NULL);
1932 LineTo (hdc, rect.left, rect.top);
1933 LineTo (hdc, rect.right, rect.top);
1936 SelectObject (hdc, hbPen);
1937 LineTo (hdc, rect.right, rect.bottom );
1938 LineTo (hdc, rect.left, rect.bottom);
1941 SelectObject (hdc, hShade );
1942 MoveToEx (hdc, rect.right - 1, rect.top, NULL);
1943 LineTo (hdc, rect.right - 1, rect.bottom - 1);
1944 LineTo (hdc, rect.left, rect.bottom - 1);
1946 SelectObject(hdc, htmPen);
1949 /******************************************************************************
1952 * This method repaints the tab control..
1954 static void TAB_Refresh (HWND hwnd, HDC hdc)
1956 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
1960 if (!infoPtr->DoRedraw)
1963 hOldFont = SelectObject (hdc, infoPtr->hFont);
1965 if (GetWindowLongA(hwnd, GWL_STYLE) & TCS_BUTTONS)
1967 for (i = 0; i < infoPtr->uNumItem; i++)
1968 TAB_DrawItem (hwnd, hdc, i);
1972 /* Draw all the non selected item first */
1973 for (i = 0; i < infoPtr->uNumItem; i++)
1975 if (i != infoPtr->iSelected)
1976 TAB_DrawItem (hwnd, hdc, i);
1979 /* Now, draw the border, draw it before the selected item
1980 * since the selected item overwrites part of the border. */
1981 TAB_DrawBorder (hwnd, hdc);
1983 /* Then, draw the selected item */
1984 TAB_DrawItem (hwnd, hdc, infoPtr->iSelected);
1986 /* If we haven't set the current focus yet, set it now.
1987 * Only happens when we first paint the tab controls */
1988 if (infoPtr->uFocus == -1)
1989 TAB_SetCurFocus(hwnd, infoPtr->iSelected);
1992 SelectObject (hdc, hOldFont);
1996 TAB_GetRowCount (HWND hwnd )
1998 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2000 return infoPtr->uNumRows;
2004 TAB_SetRedraw (HWND hwnd, WPARAM wParam)
2006 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2008 infoPtr->DoRedraw=(BOOL) wParam;
2012 static LRESULT TAB_EraseBackground(
2019 HBRUSH brush = CreateSolidBrush(GetSysColor(COLOR_BTNFACE));
2021 hdc = givenDC ? givenDC : GetDC(hwnd);
2023 GetClientRect(hwnd, &clientRect);
2025 FillRect(hdc, &clientRect, brush);
2028 ReleaseDC(hwnd, hdc);
2030 DeleteObject(brush);
2035 /******************************************************************************
2036 * TAB_EnsureSelectionVisible
2038 * This method will make sure that the current selection is completely
2039 * visible by scrolling until it is.
2041 static void TAB_EnsureSelectionVisible(
2045 INT iSelected = infoPtr->iSelected;
2046 LONG lStyle = GetWindowLongA(hwnd, GWL_STYLE);
2047 INT iOrigLeftmostVisible = infoPtr->leftmostVisible;
2049 /* set the items row to the bottommost row or topmost row depending on
2051 if ((infoPtr->uNumRows > 1) && !(lStyle & TCS_BUTTONS))
2056 if(lStyle & TCS_VERTICAL)
2057 newselected = infoPtr->items[iSelected].rect.left;
2059 newselected = infoPtr->items[iSelected].rect.top;
2061 /* the target row is always (number of rows - 1)
2062 as row 0 is furthest from the clientRect */
2063 iTargetRow = infoPtr->uNumRows - 1;
2065 if (newselected != iTargetRow)
2068 if(lStyle & TCS_VERTICAL)
2070 for (i=0; i < infoPtr->uNumItem; i++)
2072 /* move everything in the row of the selected item to the iTargetRow */
2073 if (infoPtr->items[i].rect.left == newselected )
2074 infoPtr->items[i].rect.left = iTargetRow;
2077 if (infoPtr->items[i].rect.left > newselected)
2078 infoPtr->items[i].rect.left-=1;
2084 for (i=0; i < infoPtr->uNumItem; i++)
2086 if (infoPtr->items[i].rect.top == newselected )
2087 infoPtr->items[i].rect.top = iTargetRow;
2090 if (infoPtr->items[i].rect.top > newselected)
2091 infoPtr->items[i].rect.top-=1;
2095 TAB_RecalcHotTrack(hwnd, NULL, NULL, NULL);
2100 * Do the trivial cases first.
2102 if ( (!infoPtr->needsScrolling) ||
2103 (infoPtr->hwndUpDown==0) || (lStyle & TCS_VERTICAL))
2106 if (infoPtr->leftmostVisible >= iSelected)
2108 infoPtr->leftmostVisible = iSelected;
2115 /* Calculate the part of the client area that is visible */
2116 GetClientRect(hwnd, &r);
2119 GetClientRect(infoPtr->hwndUpDown, &r);
2122 if ((infoPtr->items[iSelected].rect.right -
2123 infoPtr->items[iSelected].rect.left) >= width )
2125 /* Special case: width of selected item is greater than visible
2128 infoPtr->leftmostVisible = iSelected;
2132 for (i = infoPtr->leftmostVisible; i < infoPtr->uNumItem; i++)
2134 if ((infoPtr->items[iSelected].rect.right -
2135 infoPtr->items[i].rect.left) < width)
2138 infoPtr->leftmostVisible = i;
2142 if (infoPtr->leftmostVisible != iOrigLeftmostVisible)
2143 TAB_RecalcHotTrack(hwnd, NULL, NULL, NULL);
2145 SendMessageA(infoPtr->hwndUpDown, UDM_SETPOS, 0,
2146 MAKELONG(infoPtr->leftmostVisible, 0));
2149 /******************************************************************************
2150 * TAB_InvalidateTabArea
2152 * This method will invalidate the portion of the control that contains the
2153 * tabs. It is called when the state of the control changes and needs
2156 static void TAB_InvalidateTabArea(
2161 DWORD lStyle = GetWindowLongA(hwnd, GWL_STYLE);
2162 INT lastRow = infoPtr->uNumRows - 1;
2164 if (lastRow < 0) return;
2166 GetClientRect(hwnd, &clientRect);
2168 if ((lStyle & TCS_BOTTOM) && !(lStyle & TCS_VERTICAL))
2170 clientRect.top = clientRect.bottom -
2171 infoPtr->tabHeight -
2172 lastRow * (infoPtr->tabHeight - 2) -
2173 ((lStyle & TCS_BUTTONS) ? lastRow * BUTTON_SPACINGY : 0) - 2;
2175 else if((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
2177 clientRect.left = clientRect.right - infoPtr->tabHeight -
2178 lastRow * (infoPtr->tabHeight - 2) -
2179 ((lStyle & TCS_BUTTONS) ? lastRow * BUTTON_SPACINGY : 0) - 2;
2181 else if(lStyle & TCS_VERTICAL)
2183 clientRect.right = clientRect.left + infoPtr->tabHeight +
2184 lastRow * (infoPtr->tabHeight - 2) -
2185 ((lStyle & TCS_BUTTONS) ? lastRow * BUTTON_SPACINGY : 0) + 1;
2190 clientRect.bottom = clientRect.top + infoPtr->tabHeight +
2191 lastRow * (infoPtr->tabHeight - 2) +
2192 ((lStyle & TCS_BUTTONS) ? lastRow * BUTTON_SPACINGY : 0) + 1;
2195 InvalidateRect(hwnd, &clientRect, TRUE);
2199 TAB_Paint (HWND hwnd, WPARAM wParam)
2204 hdc = wParam== 0 ? BeginPaint (hwnd, &ps) : (HDC)wParam;
2205 TAB_Refresh (hwnd, hdc);
2208 EndPaint (hwnd, &ps);
2214 TAB_InsertItemA (HWND hwnd, WPARAM wParam, LPARAM lParam)
2216 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2221 GetClientRect (hwnd, &rect);
2222 TRACE("Rect: %x T %i, L %i, B %i, R %i\n", hwnd,
2223 rect.top, rect.left, rect.bottom, rect.right);
2225 pti = (TCITEMA *)lParam;
2226 iItem = (INT)wParam;
2228 if (iItem < 0) return -1;
2229 if (iItem > infoPtr->uNumItem)
2230 iItem = infoPtr->uNumItem;
2232 if (infoPtr->uNumItem == 0) {
2233 infoPtr->items = COMCTL32_Alloc (sizeof (TAB_ITEM));
2234 infoPtr->uNumItem++;
2235 infoPtr->iSelected = 0;
2238 TAB_ITEM *oldItems = infoPtr->items;
2240 infoPtr->uNumItem++;
2241 infoPtr->items = COMCTL32_Alloc (sizeof (TAB_ITEM) * infoPtr->uNumItem);
2243 /* pre insert copy */
2245 memcpy (&infoPtr->items[0], &oldItems[0],
2246 iItem * sizeof(TAB_ITEM));
2249 /* post insert copy */
2250 if (iItem < infoPtr->uNumItem - 1) {
2251 memcpy (&infoPtr->items[iItem+1], &oldItems[iItem],
2252 (infoPtr->uNumItem - iItem - 1) * sizeof(TAB_ITEM));
2256 if (iItem <= infoPtr->iSelected)
2257 infoPtr->iSelected++;
2259 COMCTL32_Free (oldItems);
2262 infoPtr->items[iItem].mask = pti->mask;
2263 if (pti->mask & TCIF_TEXT)
2264 Str_SetPtrAtoW (&infoPtr->items[iItem].pszText, pti->pszText);
2266 if (pti->mask & TCIF_IMAGE)
2267 infoPtr->items[iItem].iImage = pti->iImage;
2269 if (pti->mask & TCIF_PARAM)
2270 infoPtr->items[iItem].lParam = pti->lParam;
2272 TAB_SetItemBounds(hwnd);
2273 TAB_InvalidateTabArea(hwnd, infoPtr);
2275 TRACE("[%04x]: added item %d %s\n",
2276 hwnd, iItem, debugstr_w(infoPtr->items[iItem].pszText));
2283 TAB_InsertItemW (HWND hwnd, WPARAM wParam, LPARAM lParam)
2285 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2290 GetClientRect (hwnd, &rect);
2291 TRACE("Rect: %x T %i, L %i, B %i, R %i\n", hwnd,
2292 rect.top, rect.left, rect.bottom, rect.right);
2294 pti = (TCITEMW *)lParam;
2295 iItem = (INT)wParam;
2297 if (iItem < 0) return -1;
2298 if (iItem > infoPtr->uNumItem)
2299 iItem = infoPtr->uNumItem;
2301 if (infoPtr->uNumItem == 0) {
2302 infoPtr->items = COMCTL32_Alloc (sizeof (TAB_ITEM));
2303 infoPtr->uNumItem++;
2304 infoPtr->iSelected = 0;
2307 TAB_ITEM *oldItems = infoPtr->items;
2309 infoPtr->uNumItem++;
2310 infoPtr->items = COMCTL32_Alloc (sizeof (TAB_ITEM) * infoPtr->uNumItem);
2312 /* pre insert copy */
2314 memcpy (&infoPtr->items[0], &oldItems[0],
2315 iItem * sizeof(TAB_ITEM));
2318 /* post insert copy */
2319 if (iItem < infoPtr->uNumItem - 1) {
2320 memcpy (&infoPtr->items[iItem+1], &oldItems[iItem],
2321 (infoPtr->uNumItem - iItem - 1) * sizeof(TAB_ITEM));
2325 if (iItem <= infoPtr->iSelected)
2326 infoPtr->iSelected++;
2328 COMCTL32_Free (oldItems);
2331 infoPtr->items[iItem].mask = pti->mask;
2332 if (pti->mask & TCIF_TEXT)
2333 Str_SetPtrW (&infoPtr->items[iItem].pszText, pti->pszText);
2335 if (pti->mask & TCIF_IMAGE)
2336 infoPtr->items[iItem].iImage = pti->iImage;
2338 if (pti->mask & TCIF_PARAM)
2339 infoPtr->items[iItem].lParam = pti->lParam;
2341 TAB_SetItemBounds(hwnd);
2342 TAB_InvalidateTabArea(hwnd, infoPtr);
2344 TRACE("[%04x]: added item %d %s\n",
2345 hwnd, iItem, debugstr_w(infoPtr->items[iItem].pszText));
2352 TAB_SetItemSize (HWND hwnd, WPARAM wParam, LPARAM lParam)
2354 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2355 LONG lStyle = GetWindowLongA(hwnd, GWL_STYLE);
2358 if ((lStyle & TCS_FIXEDWIDTH) || (lStyle & TCS_OWNERDRAWFIXED))
2360 lResult = MAKELONG(infoPtr->tabWidth, infoPtr->tabHeight);
2361 infoPtr->tabWidth = (INT)LOWORD(lParam);
2362 infoPtr->tabHeight = (INT)HIWORD(lParam);
2364 infoPtr->fSizeSet = TRUE;
2370 TAB_SetItemA (HWND hwnd, WPARAM wParam, LPARAM lParam)
2372 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2377 iItem = (INT)wParam;
2378 tabItem = (LPTCITEMA)lParam;
2380 TRACE("%d %p\n", iItem, tabItem);
2381 if ((iItem<0) || (iItem>=infoPtr->uNumItem)) return FALSE;
2383 wineItem = &infoPtr->items[iItem];
2385 if (tabItem->mask & TCIF_IMAGE)
2386 wineItem->iImage = tabItem->iImage;
2388 if (tabItem->mask & TCIF_PARAM)
2389 wineItem->lParam = tabItem->lParam;
2391 if (tabItem->mask & TCIF_RTLREADING)
2392 FIXME("TCIF_RTLREADING\n");
2394 if (tabItem->mask & TCIF_STATE)
2395 wineItem->dwState = tabItem->dwState;
2397 if (tabItem->mask & TCIF_TEXT)
2398 Str_SetPtrAtoW(&wineItem->pszText, tabItem->pszText);
2400 /* Update and repaint tabs */
2401 TAB_SetItemBounds(hwnd);
2402 TAB_InvalidateTabArea(hwnd,infoPtr);
2409 TAB_SetItemW (HWND hwnd, WPARAM wParam, LPARAM lParam)
2411 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2416 iItem = (INT)wParam;
2417 tabItem = (LPTCITEMW)lParam;
2419 TRACE("%d %p\n", iItem, tabItem);
2420 if ((iItem<0) || (iItem>=infoPtr->uNumItem)) return FALSE;
2422 wineItem = &infoPtr->items[iItem];
2424 if (tabItem->mask & TCIF_IMAGE)
2425 wineItem->iImage = tabItem->iImage;
2427 if (tabItem->mask & TCIF_PARAM)
2428 wineItem->lParam = tabItem->lParam;
2430 if (tabItem->mask & TCIF_RTLREADING)
2431 FIXME("TCIF_RTLREADING\n");
2433 if (tabItem->mask & TCIF_STATE)
2434 wineItem->dwState = tabItem->dwState;
2436 if (tabItem->mask & TCIF_TEXT)
2437 Str_SetPtrW(&wineItem->pszText, tabItem->pszText);
2439 /* Update and repaint tabs */
2440 TAB_SetItemBounds(hwnd);
2441 TAB_InvalidateTabArea(hwnd,infoPtr);
2448 TAB_GetItemCount (HWND hwnd, WPARAM wParam, LPARAM lParam)
2450 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2452 return infoPtr->uNumItem;
2457 TAB_GetItemA (HWND hwnd, WPARAM wParam, LPARAM lParam)
2459 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2464 iItem = (INT)wParam;
2465 tabItem = (LPTCITEMA)lParam;
2467 if ((iItem<0) || (iItem>=infoPtr->uNumItem))
2470 wineItem = &infoPtr->items[iItem];
2472 if (tabItem->mask & TCIF_IMAGE)
2473 tabItem->iImage = wineItem->iImage;
2475 if (tabItem->mask & TCIF_PARAM)
2476 tabItem->lParam = wineItem->lParam;
2478 if (tabItem->mask & TCIF_RTLREADING)
2479 FIXME("TCIF_RTLREADING\n");
2481 if (tabItem->mask & TCIF_STATE)
2482 tabItem->dwState = wineItem->dwState;
2484 if (tabItem->mask & TCIF_TEXT)
2485 Str_GetPtrWtoA (wineItem->pszText, tabItem->pszText, tabItem->cchTextMax);
2492 TAB_GetItemW (HWND hwnd, WPARAM wParam, LPARAM lParam)
2494 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2499 iItem = (INT)wParam;
2500 tabItem = (LPTCITEMW)lParam;
2502 if ((iItem<0) || (iItem>=infoPtr->uNumItem))
2505 wineItem=& infoPtr->items[iItem];
2507 if (tabItem->mask & TCIF_IMAGE)
2508 tabItem->iImage = wineItem->iImage;
2510 if (tabItem->mask & TCIF_PARAM)
2511 tabItem->lParam = wineItem->lParam;
2513 if (tabItem->mask & TCIF_RTLREADING)
2514 FIXME("TCIF_RTLREADING\n");
2516 if (tabItem->mask & TCIF_STATE)
2517 tabItem->dwState = wineItem->dwState;
2519 if (tabItem->mask & TCIF_TEXT)
2520 Str_GetPtrW (wineItem->pszText, tabItem->pszText, tabItem->cchTextMax);
2527 TAB_DeleteItem (HWND hwnd, WPARAM wParam, LPARAM lParam)
2529 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2530 INT iItem = (INT) wParam;
2531 BOOL bResult = FALSE;
2533 if ((iItem >= 0) && (iItem < infoPtr->uNumItem))
2535 TAB_ITEM *oldItems = infoPtr->items;
2537 infoPtr->uNumItem--;
2538 infoPtr->items = COMCTL32_Alloc(sizeof (TAB_ITEM) * infoPtr->uNumItem);
2541 memcpy(&infoPtr->items[0], &oldItems[0], iItem * sizeof(TAB_ITEM));
2543 if (iItem < infoPtr->uNumItem)
2544 memcpy(&infoPtr->items[iItem], &oldItems[iItem + 1],
2545 (infoPtr->uNumItem - iItem) * sizeof(TAB_ITEM));
2547 COMCTL32_Free(oldItems);
2549 /* Readjust the selected index */
2550 if ((iItem == infoPtr->iSelected) && (iItem > 0))
2551 infoPtr->iSelected--;
2553 if (iItem < infoPtr->iSelected)
2554 infoPtr->iSelected--;
2556 if (infoPtr->uNumItem == 0)
2557 infoPtr->iSelected = -1;
2559 /* Reposition and repaint tabs */
2560 TAB_SetItemBounds(hwnd);
2561 TAB_InvalidateTabArea(hwnd,infoPtr);
2570 TAB_DeleteAllItems (HWND hwnd, WPARAM wParam, LPARAM lParam)
2572 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2574 COMCTL32_Free (infoPtr->items);
2575 infoPtr->uNumItem = 0;
2576 infoPtr->iSelected = -1;
2577 if (infoPtr->iHotTracked >= 0)
2578 KillTimer(hwnd, TAB_HOTTRACK_TIMER);
2579 infoPtr->iHotTracked = -1;
2581 TAB_SetItemBounds(hwnd);
2582 TAB_InvalidateTabArea(hwnd,infoPtr);
2588 TAB_GetFont (HWND hwnd, WPARAM wParam, LPARAM lParam)
2590 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2593 return (LRESULT)infoPtr->hFont;
2597 TAB_SetFont (HWND hwnd, WPARAM wParam, LPARAM lParam)
2600 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2602 TRACE("%x %lx\n",wParam, lParam);
2604 infoPtr->hFont = (HFONT)wParam;
2606 TAB_SetItemBounds(hwnd);
2608 TAB_InvalidateTabArea(hwnd, infoPtr);
2615 TAB_GetImageList (HWND hwnd, WPARAM wParam, LPARAM lParam)
2617 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2620 return (LRESULT)infoPtr->himl;
2624 TAB_SetImageList (HWND hwnd, WPARAM wParam, LPARAM lParam)
2626 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2627 HIMAGELIST himlPrev;
2630 himlPrev = infoPtr->himl;
2631 infoPtr->himl= (HIMAGELIST)lParam;
2632 return (LRESULT)himlPrev;
2636 TAB_GetUnicodeFormat (HWND hwnd)
2638 TAB_INFO *infoPtr = TAB_GetInfoPtr (hwnd);
2639 return infoPtr->bUnicode;
2643 TAB_SetUnicodeFormat (HWND hwnd, WPARAM wParam)
2645 TAB_INFO *infoPtr = TAB_GetInfoPtr (hwnd);
2646 BOOL bTemp = infoPtr->bUnicode;
2648 infoPtr->bUnicode = (BOOL)wParam;
2654 TAB_Size (HWND hwnd, WPARAM wParam, LPARAM lParam)
2657 /* I'm not really sure what the following code was meant to do.
2658 This is what it is doing:
2659 When WM_SIZE is sent with SIZE_RESTORED, the control
2660 gets positioned in the top left corner.
2664 UINT uPosFlags,cx,cy;
2668 parent = GetParent (hwnd);
2669 GetClientRect(parent, &parent_rect);
2672 if (GetWindowLongA(hwnd, GWL_STYLE) & CCS_NORESIZE)
2673 uPosFlags |= (SWP_NOSIZE | SWP_NOMOVE);
2675 SetWindowPos (hwnd, 0, parent_rect.left, parent_rect.top,
2676 cx, cy, uPosFlags | SWP_NOZORDER);
2678 FIXME("WM_SIZE flag %x %lx not handled\n", wParam, lParam);
2681 /* Recompute the size/position of the tabs. */
2682 TAB_SetItemBounds (hwnd);
2684 /* Force a repaint of the control. */
2685 InvalidateRect(hwnd, NULL, TRUE);
2692 TAB_Create (HWND hwnd, WPARAM wParam, LPARAM lParam)
2695 TEXTMETRICA fontMetrics;
2700 infoPtr = (TAB_INFO *)COMCTL32_Alloc (sizeof(TAB_INFO));
2702 SetWindowLongA(hwnd, 0, (DWORD)infoPtr);
2704 infoPtr->uNumItem = 0;
2705 infoPtr->uNumRows = 0;
2708 infoPtr->hcurArrow = LoadCursorA (0, IDC_ARROWA);
2709 infoPtr->iSelected = -1;
2710 infoPtr->iHotTracked = -1;
2711 infoPtr->uFocus = -1;
2712 infoPtr->hwndToolTip = 0;
2713 infoPtr->DoRedraw = TRUE;
2714 infoPtr->needsScrolling = FALSE;
2715 infoPtr->hwndUpDown = 0;
2716 infoPtr->leftmostVisible = 0;
2717 infoPtr->fSizeSet = FALSE;
2718 infoPtr->bUnicode = IsWindowUnicode (hwnd);
2720 TRACE("Created tab control, hwnd [%04x]\n", hwnd);
2722 /* The tab control always has the WS_CLIPSIBLINGS style. Even
2723 if you don't specify it in CreateWindow. This is necessary in
2724 order for paint to work correctly. This follows windows behaviour. */
2725 dwStyle = GetWindowLongA(hwnd, GWL_STYLE);
2726 SetWindowLongA(hwnd, GWL_STYLE, dwStyle|WS_CLIPSIBLINGS);
2728 if (dwStyle & TCS_TOOLTIPS) {
2729 /* Create tooltip control */
2730 infoPtr->hwndToolTip =
2731 CreateWindowExA (0, TOOLTIPS_CLASSA, NULL, 0,
2732 CW_USEDEFAULT, CW_USEDEFAULT,
2733 CW_USEDEFAULT, CW_USEDEFAULT,
2736 /* Send NM_TOOLTIPSCREATED notification */
2737 if (infoPtr->hwndToolTip) {
2738 NMTOOLTIPSCREATED nmttc;
2740 nmttc.hdr.hwndFrom = hwnd;
2741 nmttc.hdr.idFrom = GetWindowLongA(hwnd, GWL_ID);
2742 nmttc.hdr.code = NM_TOOLTIPSCREATED;
2743 nmttc.hwndToolTips = infoPtr->hwndToolTip;
2745 SendMessageA (GetParent (hwnd), WM_NOTIFY,
2746 (WPARAM)GetWindowLongA(hwnd, GWL_ID), (LPARAM)&nmttc);
2751 * We need to get text information so we need a DC and we need to select
2755 hOldFont = SelectObject (hdc, GetStockObject (SYSTEM_FONT));
2757 /* Use the system font to determine the initial height of a tab. */
2758 GetTextMetricsA(hdc, &fontMetrics);
2761 * Make sure there is enough space for the letters + growing the
2762 * selected item + extra space for the selected item.
2764 infoPtr->tabHeight = fontMetrics.tmHeight + 2 * VERTICAL_ITEM_PADDING +
2765 SELECTED_TAB_OFFSET;
2767 /* Initialize the width of a tab. */
2768 infoPtr->tabWidth = DEFAULT_TAB_WIDTH;
2770 SelectObject (hdc, hOldFont);
2771 ReleaseDC(hwnd, hdc);
2777 TAB_Destroy (HWND hwnd, WPARAM wParam, LPARAM lParam)
2779 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2785 if (infoPtr->items) {
2786 for (iItem = 0; iItem < infoPtr->uNumItem; iItem++) {
2787 if (infoPtr->items[iItem].pszText)
2788 COMCTL32_Free (infoPtr->items[iItem].pszText);
2790 COMCTL32_Free (infoPtr->items);
2793 if (infoPtr->hwndToolTip)
2794 DestroyWindow (infoPtr->hwndToolTip);
2796 if (infoPtr->hwndUpDown)
2797 DestroyWindow(infoPtr->hwndUpDown);
2799 if (infoPtr->iHotTracked >= 0)
2800 KillTimer(hwnd, TAB_HOTTRACK_TIMER);
2802 COMCTL32_Free (infoPtr);
2803 SetWindowLongA(hwnd, 0, 0);
2807 static LRESULT WINAPI
2808 TAB_WindowProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
2811 TRACE("hwnd=%x msg=%x wParam=%x lParam=%lx\n", hwnd, uMsg, wParam, lParam);
2812 if (!TAB_GetInfoPtr(hwnd) && (uMsg != WM_CREATE))
2813 return DefWindowProcA (hwnd, uMsg, wParam, lParam);
2817 case TCM_GETIMAGELIST:
2818 return TAB_GetImageList (hwnd, wParam, lParam);
2820 case TCM_SETIMAGELIST:
2821 return TAB_SetImageList (hwnd, wParam, lParam);
2823 case TCM_GETITEMCOUNT:
2824 return TAB_GetItemCount (hwnd, wParam, lParam);
2827 return TAB_GetItemA (hwnd, wParam, lParam);
2830 return TAB_GetItemW (hwnd, wParam, lParam);
2833 return TAB_SetItemA (hwnd, wParam, lParam);
2836 return TAB_SetItemW (hwnd, wParam, lParam);
2838 case TCM_DELETEITEM:
2839 return TAB_DeleteItem (hwnd, wParam, lParam);
2841 case TCM_DELETEALLITEMS:
2842 return TAB_DeleteAllItems (hwnd, wParam, lParam);
2844 case TCM_GETITEMRECT:
2845 return TAB_GetItemRect (hwnd, wParam, lParam);
2848 return TAB_GetCurSel (hwnd);
2851 return TAB_HitTest (hwnd, wParam, lParam);
2854 return TAB_SetCurSel (hwnd, wParam);
2856 case TCM_INSERTITEMA:
2857 return TAB_InsertItemA (hwnd, wParam, lParam);
2859 case TCM_INSERTITEMW:
2860 return TAB_InsertItemW (hwnd, wParam, lParam);
2862 case TCM_SETITEMEXTRA:
2863 FIXME("Unimplemented msg TCM_SETITEMEXTRA\n");
2866 case TCM_ADJUSTRECT:
2867 return TAB_AdjustRect (hwnd, (BOOL)wParam, (LPRECT)lParam);
2869 case TCM_SETITEMSIZE:
2870 return TAB_SetItemSize (hwnd, wParam, lParam);
2872 case TCM_REMOVEIMAGE:
2873 FIXME("Unimplemented msg TCM_REMOVEIMAGE\n");
2876 case TCM_SETPADDING:
2877 FIXME("Unimplemented msg TCM_SETPADDING\n");
2880 case TCM_GETROWCOUNT:
2881 return TAB_GetRowCount(hwnd);
2883 case TCM_GETUNICODEFORMAT:
2884 return TAB_GetUnicodeFormat (hwnd);
2886 case TCM_SETUNICODEFORMAT:
2887 return TAB_SetUnicodeFormat (hwnd, wParam);
2889 case TCM_HIGHLIGHTITEM:
2890 FIXME("Unimplemented msg TCM_HIGHLIGHTITEM\n");
2893 case TCM_GETTOOLTIPS:
2894 return TAB_GetToolTips (hwnd, wParam, lParam);
2896 case TCM_SETTOOLTIPS:
2897 return TAB_SetToolTips (hwnd, wParam, lParam);
2899 case TCM_GETCURFOCUS:
2900 return TAB_GetCurFocus (hwnd);
2902 case TCM_SETCURFOCUS:
2903 return TAB_SetCurFocus (hwnd, wParam);
2905 case TCM_SETMINTABWIDTH:
2906 FIXME("Unimplemented msg TCM_SETMINTABWIDTH\n");
2909 case TCM_DESELECTALL:
2910 FIXME("Unimplemented msg TCM_DESELECTALL\n");
2913 case TCM_GETEXTENDEDSTYLE:
2914 FIXME("Unimplemented msg TCM_GETEXTENDEDSTYLE\n");
2917 case TCM_SETEXTENDEDSTYLE:
2918 FIXME("Unimplemented msg TCM_SETEXTENDEDSTYLE\n");
2922 return TAB_GetFont (hwnd, wParam, lParam);
2925 return TAB_SetFont (hwnd, wParam, lParam);
2928 return TAB_Create (hwnd, wParam, lParam);
2931 return TAB_Destroy (hwnd, wParam, lParam);
2934 return DLGC_WANTARROWS | DLGC_WANTCHARS;
2936 case WM_LBUTTONDOWN:
2937 return TAB_LButtonDown (hwnd, wParam, lParam);
2940 return TAB_LButtonUp (hwnd, wParam, lParam);
2942 case WM_RBUTTONDOWN:
2943 return TAB_RButtonDown (hwnd, wParam, lParam);
2946 return TAB_MouseMove (hwnd, wParam, lParam);
2949 return TAB_EraseBackground (hwnd, (HDC)wParam);
2952 return TAB_Paint (hwnd, wParam);
2955 return TAB_Size (hwnd, wParam, lParam);
2958 return TAB_SetRedraw (hwnd, wParam);
2961 return TAB_OnHScroll(hwnd, (int)LOWORD(wParam), (int)HIWORD(wParam), (HWND)lParam);
2963 case WM_STYLECHANGED:
2964 TAB_SetItemBounds (hwnd);
2965 InvalidateRect(hwnd, NULL, TRUE);
2970 return TAB_FocusChanging(hwnd, uMsg, wParam, lParam);
2973 return TAB_KeyUp(hwnd, wParam);
2975 return TAB_NCHitTest(hwnd, lParam);
2978 if (uMsg >= WM_USER)
2979 WARN("unknown msg %04x wp=%08x lp=%08lx\n",
2980 uMsg, wParam, lParam);
2981 return DefWindowProcA (hwnd, uMsg, wParam, lParam);
2993 ZeroMemory (&wndClass, sizeof(WNDCLASSA));
2994 wndClass.style = CS_GLOBALCLASS | CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
2995 wndClass.lpfnWndProc = (WNDPROC)TAB_WindowProc;
2996 wndClass.cbClsExtra = 0;
2997 wndClass.cbWndExtra = sizeof(TAB_INFO *);
2998 wndClass.hCursor = LoadCursorA (0, IDC_ARROWA);
2999 wndClass.hbrBackground = (HBRUSH)NULL;
3000 wndClass.lpszClassName = WC_TABCONTROLA;
3002 RegisterClassA (&wndClass);
3007 TAB_Unregister (void)
3009 UnregisterClassA (WC_TABCONTROLA, (HINSTANCE)NULL);