Tab unicodification.
[wine/hacks.git] / dlls / comctl32 / tab.c
blobc784185aa886d09497a7c403d909f3cddc02b344
1 /*
2 * Tab control
4 * Copyright 1998 Anders Carlsson
5 * Copyright 1999 Alex Priem <alexp@sci.kun.nl>
6 * Copyright 1999 Francis Beaudet
7 * Copyright 2003 Vitaliy Margolen
9 * This library is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Lesser General Public
11 * License as published by the Free Software Foundation; either
12 * version 2.1 of the License, or (at your option) any later version.
14 * This library is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Lesser General Public License for more details.
19 * You should have received a copy of the GNU Lesser General Public
20 * License along with this library; if not, write to the Free Software
21 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23 * TODO:
25 * Styles:
26 * TCIF_RTLREADING
28 * Messages:
29 * TCM_REMOVEIMAGE
30 * TCM_DESELECTALL
31 * TCM_GETEXTENDEDSTYLE
32 * TCM_SETEXTENDEDSTYLE
36 #include <stdarg.h>
37 #include <string.h>
39 #include "windef.h"
40 #include "winbase.h"
41 #include "wingdi.h"
42 #include "winuser.h"
43 #include "winnls.h"
44 #include "commctrl.h"
45 #include "comctl32.h"
46 #include "wine/debug.h"
47 #include <math.h>
49 WINE_DEFAULT_DEBUG_CHANNEL(tab);
51 typedef struct
53 UINT mask;
54 DWORD dwState;
55 LPWSTR pszText;
56 INT iImage;
57 RECT rect; /* bounding rectangle of the item relative to the
58 * leftmost item (the leftmost item, 0, would have a
59 * "left" member of 0 in this rectangle)
61 * additionally the top member holds the row number
62 * and bottom is unused and should be 0 */
63 BYTE extra[1]; /* Space for caller supplied info, variable size */
64 } TAB_ITEM;
66 /* The size of a tab item depends on how much extra data is requested */
67 #define TAB_ITEM_SIZE(infoPtr) (sizeof(TAB_ITEM) - sizeof(BYTE) + infoPtr->cbInfo)
69 typedef struct
71 HWND hwnd; /* Tab control window */
72 HWND hwndNotify; /* notification window (parent) */
73 UINT uNumItem; /* number of tab items */
74 UINT uNumRows; /* number of tab rows */
75 INT tabHeight; /* height of the tab row */
76 INT tabWidth; /* width of tabs */
77 INT tabMinWidth; /* minimum width of items */
78 USHORT uHItemPadding; /* amount of horizontal padding, in pixels */
79 USHORT uVItemPadding; /* amount of vertical padding, in pixels */
80 USHORT uHItemPadding_s; /* Set amount of horizontal padding, in pixels */
81 USHORT uVItemPadding_s; /* Set amount of vertical padding, in pixels */
82 HFONT hFont; /* handle to the current font */
83 HCURSOR hcurArrow; /* handle to the current cursor */
84 HIMAGELIST himl; /* handle to an image list (may be 0) */
85 HWND hwndToolTip; /* handle to tab's tooltip */
86 INT leftmostVisible; /* Used for scrolling, this member contains
87 * the index of the first visible item */
88 INT iSelected; /* the currently selected item */
89 INT iHotTracked; /* the highlighted item under the mouse */
90 INT uFocus; /* item which has the focus */
91 TAB_ITEM* items; /* pointer to an array of TAB_ITEM's */
92 BOOL DoRedraw; /* flag for redrawing when tab contents is changed*/
93 BOOL needsScrolling; /* TRUE if the size of the tabs is greater than
94 * the size of the control */
95 BOOL fHeightSet; /* was the height of the tabs explicitly set? */
96 BOOL bUnicode; /* Unicode control? */
97 HWND hwndUpDown; /* Updown control used for scrolling */
98 INT cbInfo; /* Number of bytes of caller supplied info per tab */
99 } TAB_INFO;
101 /******************************************************************************
102 * Positioning constants
104 #define SELECTED_TAB_OFFSET 2
105 #define ROUND_CORNER_SIZE 2
106 #define DISPLAY_AREA_PADDINGX 2
107 #define DISPLAY_AREA_PADDINGY 2
108 #define CONTROL_BORDER_SIZEX 2
109 #define CONTROL_BORDER_SIZEY 2
110 #define BUTTON_SPACINGX 3
111 #define BUTTON_SPACINGY 3
112 #define FLAT_BTN_SPACINGX 8
113 #define DEFAULT_TAB_WIDTH 96
115 #define TAB_GetInfoPtr(hwnd) ((TAB_INFO *)GetWindowLongPtrW(hwnd,0))
116 /* Since items are variable sized, cannot directly access them */
117 #define TAB_GetItem(info,i) \
118 ((TAB_ITEM*)((LPBYTE)info->items + (i) * TAB_ITEM_SIZE(info)))
120 /******************************************************************************
121 * Hot-tracking timer constants
123 #define TAB_HOTTRACK_TIMER 1
124 #define TAB_HOTTRACK_TIMER_INTERVAL 100 /* milliseconds */
126 /******************************************************************************
127 * Prototypes
129 static void TAB_InvalidateTabArea(TAB_INFO *);
130 static void TAB_EnsureSelectionVisible(TAB_INFO *);
131 static void TAB_DrawItemInterior(TAB_INFO *, HDC, INT, RECT*);
133 static BOOL
134 TAB_SendSimpleNotify (const TAB_INFO *infoPtr, UINT code)
136 NMHDR nmhdr;
138 nmhdr.hwndFrom = infoPtr->hwnd;
139 nmhdr.idFrom = GetWindowLongPtrW(infoPtr->hwnd, GWLP_ID);
140 nmhdr.code = code;
142 return (BOOL) SendMessageW (infoPtr->hwndNotify, WM_NOTIFY,
143 (WPARAM) nmhdr.idFrom, (LPARAM) &nmhdr);
146 static void
147 TAB_RelayEvent (HWND hwndTip, HWND hwndMsg, UINT uMsg,
148 WPARAM wParam, LPARAM lParam)
150 MSG msg;
152 msg.hwnd = hwndMsg;
153 msg.message = uMsg;
154 msg.wParam = wParam;
155 msg.lParam = lParam;
156 msg.time = GetMessageTime ();
157 msg.pt.x = LOWORD(GetMessagePos ());
158 msg.pt.y = HIWORD(GetMessagePos ());
160 SendMessageW (hwndTip, TTM_RELAYEVENT, 0, (LPARAM)&msg);
163 static void
164 TAB_DumpItemExternalT(TCITEMW *pti, UINT iItem, BOOL isW)
166 if (TRACE_ON(tab)) {
167 TRACE("external tab %d, mask=0x%08x, dwState=0x%08lx, dwStateMask=0x%08lx, cchTextMax=0x%08x\n",
168 iItem, pti->mask, pti->dwState, pti->dwStateMask, pti->cchTextMax);
169 TRACE("external tab %d, iImage=%d, lParam=0x%08lx, pszTextW=%s\n",
170 iItem, pti->iImage, pti->lParam, isW ? debugstr_w(pti->pszText) : debugstr_a((LPSTR)pti->pszText));
174 static void
175 TAB_DumpItemInternal(TAB_INFO *infoPtr, UINT iItem)
177 if (TRACE_ON(tab)) {
178 TAB_ITEM *ti;
180 ti = TAB_GetItem(infoPtr, iItem);
181 TRACE("tab %d, mask=0x%08x, dwState=0x%08lx, pszText=%s, iImage=%d\n",
182 iItem, ti->mask, ti->dwState, debugstr_w(ti->pszText),
183 ti->iImage);
184 TRACE("tab %d, rect.left=%ld, rect.top(row)=%ld\n",
185 iItem, ti->rect.left, ti->rect.top);
189 /* RETURNS
190 * the index of the selected tab, or -1 if no tab is selected. */
191 static inline LRESULT TAB_GetCurSel (const TAB_INFO *infoPtr)
193 return infoPtr->iSelected;
196 /* RETURNS
197 * the index of the tab item that has the focus
198 * NOTE
199 * we have not to return negative value
200 * TODO
201 * test for windows */
202 static inline LRESULT
203 TAB_GetCurFocus (const TAB_INFO *infoPtr)
205 if (infoPtr->uFocus<0)
207 FIXME("we have not to return negative value");
208 return 0;
210 return infoPtr->uFocus;
213 static inline LRESULT TAB_GetToolTips (const TAB_INFO *infoPtr)
215 if (infoPtr == NULL) return 0;
216 return (LRESULT)infoPtr->hwndToolTip;
219 static inline LRESULT TAB_SetCurSel (TAB_INFO *infoPtr, INT iItem)
221 INT prevItem = -1;
223 if (iItem >= 0 && iItem < infoPtr->uNumItem) {
224 prevItem=infoPtr->iSelected;
225 infoPtr->iSelected=iItem;
226 TAB_EnsureSelectionVisible(infoPtr);
227 TAB_InvalidateTabArea(infoPtr);
229 return prevItem;
232 static LRESULT TAB_SetCurFocus (TAB_INFO *infoPtr, INT iItem)
234 if (iItem < 0 || iItem >= infoPtr->uNumItem) return 0;
236 if (GetWindowLongW(infoPtr->hwnd, GWL_STYLE) & TCS_BUTTONS) {
237 FIXME("Should set input focus\n");
238 } else {
239 int oldFocus = infoPtr->uFocus;
240 if (infoPtr->iSelected != iItem || oldFocus == -1 ) {
241 infoPtr->uFocus = iItem;
242 if (oldFocus != -1) {
243 if (!TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGING)) {
244 infoPtr->iSelected = iItem;
245 TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGE);
247 else
248 infoPtr->iSelected = iItem;
249 TAB_EnsureSelectionVisible(infoPtr);
250 TAB_InvalidateTabArea(infoPtr);
254 return 0;
257 static inline LRESULT
258 TAB_SetToolTips (TAB_INFO *infoPtr, HWND hwndToolTip)
260 if (infoPtr)
261 infoPtr->hwndToolTip = hwndToolTip;
262 return 0;
265 static inline LRESULT
266 TAB_SetPadding (TAB_INFO *infoPtr, LPARAM lParam)
268 if (infoPtr)
270 infoPtr->uHItemPadding_s=LOWORD(lParam);
271 infoPtr->uVItemPadding_s=HIWORD(lParam);
273 return 0;
276 /******************************************************************************
277 * TAB_InternalGetItemRect
279 * This method will calculate the rectangle representing a given tab item in
280 * client coordinates. This method takes scrolling into account.
282 * This method returns TRUE if the item is visible in the window and FALSE
283 * if it is completely outside the client area.
285 static BOOL TAB_InternalGetItemRect(
286 const TAB_INFO* infoPtr,
287 INT itemIndex,
288 RECT* itemRect,
289 RECT* selectedRect)
291 RECT tmpItemRect,clientRect;
292 LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
294 /* Perform a sanity check and a trivial visibility check. */
295 if ( (infoPtr->uNumItem <= 0) ||
296 (itemIndex >= infoPtr->uNumItem) ||
297 (!((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)) && (itemIndex < infoPtr->leftmostVisible)) )
298 return FALSE;
301 * Avoid special cases in this procedure by assigning the "out"
302 * parameters if the caller didn't supply them
304 if (itemRect == NULL)
305 itemRect = &tmpItemRect;
307 /* Retrieve the unmodified item rect. */
308 *itemRect = TAB_GetItem(infoPtr,itemIndex)->rect;
310 /* calculate the times bottom and top based on the row */
311 GetClientRect(infoPtr->hwnd, &clientRect);
313 if ((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
315 itemRect->right = clientRect.right - SELECTED_TAB_OFFSET - itemRect->left * infoPtr->tabHeight -
316 ((lStyle & TCS_BUTTONS) ? itemRect->left * BUTTON_SPACINGX : 0);
317 itemRect->left = itemRect->right - infoPtr->tabHeight;
319 else if (lStyle & TCS_VERTICAL)
321 itemRect->left = clientRect.left + SELECTED_TAB_OFFSET + itemRect->left * infoPtr->tabHeight +
322 ((lStyle & TCS_BUTTONS) ? itemRect->left * BUTTON_SPACINGX : 0);
323 itemRect->right = itemRect->left + infoPtr->tabHeight;
325 else if (lStyle & TCS_BOTTOM)
327 itemRect->bottom = clientRect.bottom - itemRect->top * infoPtr->tabHeight -
328 ((lStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : SELECTED_TAB_OFFSET);
329 itemRect->top = itemRect->bottom - infoPtr->tabHeight;
331 else /* not TCS_BOTTOM and not TCS_VERTICAL */
333 itemRect->top = clientRect.top + itemRect->top * infoPtr->tabHeight +
334 ((lStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : SELECTED_TAB_OFFSET);
335 itemRect->bottom = itemRect->top + infoPtr->tabHeight;
339 * "scroll" it to make sure the item at the very left of the
340 * tab control is the leftmost visible tab.
342 if(lStyle & TCS_VERTICAL)
344 OffsetRect(itemRect,
346 -TAB_GetItem(infoPtr, infoPtr->leftmostVisible)->rect.top);
349 * Move the rectangle so the first item is slightly offset from
350 * the bottom of the tab control.
352 OffsetRect(itemRect,
354 SELECTED_TAB_OFFSET);
356 } else
358 OffsetRect(itemRect,
359 -TAB_GetItem(infoPtr, infoPtr->leftmostVisible)->rect.left,
363 * Move the rectangle so the first item is slightly offset from
364 * the left of the tab control.
366 OffsetRect(itemRect,
367 SELECTED_TAB_OFFSET,
370 TRACE("item %d tab h=%d, rect=(%ld,%ld)-(%ld,%ld)\n",
371 itemIndex, infoPtr->tabHeight,
372 itemRect->left, itemRect->top, itemRect->right, itemRect->bottom);
374 /* Now, calculate the position of the item as if it were selected. */
375 if (selectedRect!=NULL)
377 CopyRect(selectedRect, itemRect);
379 /* The rectangle of a selected item is a bit wider. */
380 if(lStyle & TCS_VERTICAL)
381 InflateRect(selectedRect, 0, SELECTED_TAB_OFFSET);
382 else
383 InflateRect(selectedRect, SELECTED_TAB_OFFSET, 0);
385 /* If it also a bit higher. */
386 if ((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
388 selectedRect->left -= 2; /* the border is thicker on the right */
389 selectedRect->right += SELECTED_TAB_OFFSET;
391 else if (lStyle & TCS_VERTICAL)
393 selectedRect->left -= SELECTED_TAB_OFFSET;
394 selectedRect->right += 1;
396 else if (lStyle & TCS_BOTTOM)
398 selectedRect->bottom += SELECTED_TAB_OFFSET;
400 else /* not TCS_BOTTOM and not TCS_VERTICAL */
402 selectedRect->top -= SELECTED_TAB_OFFSET;
403 selectedRect->bottom -= 1;
407 return TRUE;
410 static inline BOOL
411 TAB_GetItemRect(TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
413 return TAB_InternalGetItemRect(infoPtr, (INT)wParam, (LPRECT)lParam, (LPRECT)NULL);
416 /******************************************************************************
417 * TAB_KeyUp
419 * This method is called to handle keyboard input
421 static LRESULT TAB_KeyUp(TAB_INFO* infoPtr, WPARAM keyCode)
423 int newItem = -1;
425 switch (keyCode)
427 case VK_LEFT:
428 newItem = infoPtr->uFocus - 1;
429 break;
430 case VK_RIGHT:
431 newItem = infoPtr->uFocus + 1;
432 break;
436 * If we changed to a valid item, change the selection
438 if (newItem >= 0 &&
439 newItem < infoPtr->uNumItem &&
440 infoPtr->uFocus != newItem)
442 if (!TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGING))
444 infoPtr->iSelected = newItem;
445 infoPtr->uFocus = newItem;
446 TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGE);
448 TAB_EnsureSelectionVisible(infoPtr);
449 TAB_InvalidateTabArea(infoPtr);
453 return 0;
456 /******************************************************************************
457 * TAB_FocusChanging
459 * This method is called whenever the focus goes in or out of this control
460 * it is used to update the visual state of the control.
462 static void TAB_FocusChanging(const TAB_INFO *infoPtr)
464 RECT selectedRect;
465 BOOL isVisible;
468 * Get the rectangle for the item.
470 isVisible = TAB_InternalGetItemRect(infoPtr,
471 infoPtr->uFocus,
472 NULL,
473 &selectedRect);
476 * If the rectangle is not completely invisible, invalidate that
477 * portion of the window.
479 if (isVisible)
481 TRACE("invalidate (%ld,%ld)-(%ld,%ld)\n",
482 selectedRect.left,selectedRect.top,
483 selectedRect.right,selectedRect.bottom);
484 InvalidateRect(infoPtr->hwnd, &selectedRect, TRUE);
488 static INT TAB_InternalHitTest (
489 TAB_INFO* infoPtr,
490 POINT pt,
491 UINT* flags)
494 RECT rect;
495 INT iCount;
497 for (iCount = 0; iCount < infoPtr->uNumItem; iCount++)
499 TAB_InternalGetItemRect(infoPtr, iCount, &rect, NULL);
501 if (PtInRect(&rect, pt))
503 *flags = TCHT_ONITEM;
504 return iCount;
508 *flags = TCHT_NOWHERE;
509 return -1;
512 static inline LRESULT
513 TAB_HitTest (TAB_INFO *infoPtr, LPTCHITTESTINFO lptest)
515 return TAB_InternalHitTest (infoPtr, lptest->pt, &lptest->flags);
518 /******************************************************************************
519 * TAB_NCHitTest
521 * Napster v2b5 has a tab control for its main navigation which has a client
522 * area that covers the whole area of the dialog pages.
523 * That's why it receives all msgs for that area and the underlying dialog ctrls
524 * are dead.
525 * So I decided that we should handle WM_NCHITTEST here and return
526 * HTTRANSPARENT if we don't hit the tab control buttons.
527 * FIXME: WM_NCHITTEST handling correct ? Fix it if you know that Windows
528 * doesn't do it that way. Maybe depends on tab control styles ?
530 static inline LRESULT
531 TAB_NCHitTest (TAB_INFO *infoPtr, LPARAM lParam)
533 POINT pt;
534 UINT dummyflag;
536 pt.x = LOWORD(lParam);
537 pt.y = HIWORD(lParam);
538 ScreenToClient(infoPtr->hwnd, &pt);
540 if (TAB_InternalHitTest(infoPtr, pt, &dummyflag) == -1)
541 return HTTRANSPARENT;
542 else
543 return HTCLIENT;
546 static LRESULT
547 TAB_LButtonDown (TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
549 POINT pt;
550 INT newItem, dummy;
552 if (infoPtr->hwndToolTip)
553 TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
554 WM_LBUTTONDOWN, wParam, lParam);
556 if (GetWindowLongW(infoPtr->hwnd, GWL_STYLE) & TCS_FOCUSONBUTTONDOWN ) {
557 SetFocus (infoPtr->hwnd);
560 if (infoPtr->hwndToolTip)
561 TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
562 WM_LBUTTONDOWN, wParam, lParam);
564 pt.x = (INT)LOWORD(lParam);
565 pt.y = (INT)HIWORD(lParam);
567 newItem = TAB_InternalHitTest (infoPtr, pt, &dummy);
569 TRACE("On Tab, item %d\n", newItem);
571 if (newItem != -1 && infoPtr->iSelected != newItem)
573 if (!TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGING))
575 infoPtr->iSelected = newItem;
576 infoPtr->uFocus = newItem;
577 TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGE);
579 TAB_EnsureSelectionVisible(infoPtr);
581 TAB_InvalidateTabArea(infoPtr);
584 return 0;
587 static inline LRESULT
588 TAB_LButtonUp (const TAB_INFO *infoPtr)
590 TAB_SendSimpleNotify(infoPtr, NM_CLICK);
592 return 0;
595 static inline LRESULT
596 TAB_RButtonDown (const TAB_INFO *infoPtr)
598 TAB_SendSimpleNotify(infoPtr, NM_RCLICK);
599 return 0;
602 /******************************************************************************
603 * TAB_DrawLoneItemInterior
605 * This calls TAB_DrawItemInterior. However, TAB_DrawItemInterior is normally
606 * called by TAB_DrawItem which is normally called by TAB_Refresh which sets
607 * up the device context and font. This routine does the same setup but
608 * only calls TAB_DrawItemInterior for the single specified item.
610 static void
611 TAB_DrawLoneItemInterior(TAB_INFO* infoPtr, int iItem)
613 HDC hdc = GetDC(infoPtr->hwnd);
614 RECT r, rC;
616 /* Clip UpDown control to not draw over it */
617 if (infoPtr->needsScrolling)
619 GetWindowRect(infoPtr->hwnd, &rC);
620 GetWindowRect(infoPtr->hwndUpDown, &r);
621 ExcludeClipRect(hdc, r.left - rC.left, r.top - rC.top, r.right - rC.left, r.bottom - rC.top);
623 TAB_DrawItemInterior(infoPtr, hdc, iItem, NULL);
624 ReleaseDC(infoPtr->hwnd, hdc);
627 /******************************************************************************
628 * TAB_HotTrackTimerProc
630 * When a mouse-move event causes a tab to be highlighted (hot-tracking), a
631 * timer is setup so we can check if the mouse is moved out of our window.
632 * (We don't get an event when the mouse leaves, the mouse-move events just
633 * stop being delivered to our window and just start being delivered to
634 * another window.) This function is called when the timer triggers so
635 * we can check if the mouse has left our window. If so, we un-highlight
636 * the hot-tracked tab.
638 static void CALLBACK
639 TAB_HotTrackTimerProc
641 HWND hwnd, /* handle of window for timer messages */
642 UINT uMsg, /* WM_TIMER message */
643 UINT idEvent, /* timer identifier */
644 DWORD dwTime /* current system time */
647 TAB_INFO* infoPtr = TAB_GetInfoPtr(hwnd);
649 if (infoPtr != NULL && infoPtr->iHotTracked >= 0)
651 POINT pt;
654 ** If we can't get the cursor position, or if the cursor is outside our
655 ** window, we un-highlight the hot-tracked tab. Note that the cursor is
656 ** "outside" even if it is within our bounding rect if another window
657 ** overlaps. Note also that the case where the cursor stayed within our
658 ** window but has moved off the hot-tracked tab will be handled by the
659 ** WM_MOUSEMOVE event.
661 if (!GetCursorPos(&pt) || WindowFromPoint(pt) != hwnd)
663 /* Redraw iHotTracked to look normal */
664 INT iRedraw = infoPtr->iHotTracked;
665 infoPtr->iHotTracked = -1;
666 TAB_DrawLoneItemInterior(infoPtr, iRedraw);
668 /* Kill this timer */
669 KillTimer(hwnd, TAB_HOTTRACK_TIMER);
674 /******************************************************************************
675 * TAB_RecalcHotTrack
677 * If a tab control has the TCS_HOTTRACK style, then the tab under the mouse
678 * should be highlighted. This function determines which tab in a tab control,
679 * if any, is under the mouse and records that information. The caller may
680 * supply output parameters to receive the item number of the tab item which
681 * was highlighted but isn't any longer and of the tab item which is now
682 * highlighted but wasn't previously. The caller can use this information to
683 * selectively redraw those tab items.
685 * If the caller has a mouse position, it can supply it through the pos
686 * parameter. For example, TAB_MouseMove does this. Otherwise, the caller
687 * supplies NULL and this function determines the current mouse position
688 * itself.
690 static void
691 TAB_RecalcHotTrack
693 TAB_INFO* infoPtr,
694 const LPARAM* pos,
695 int* out_redrawLeave,
696 int* out_redrawEnter
699 int item = -1;
702 if (out_redrawLeave != NULL)
703 *out_redrawLeave = -1;
704 if (out_redrawEnter != NULL)
705 *out_redrawEnter = -1;
707 if (GetWindowLongW(infoPtr->hwnd, GWL_STYLE) & TCS_HOTTRACK)
709 POINT pt;
710 UINT flags;
712 if (pos == NULL)
714 GetCursorPos(&pt);
715 ScreenToClient(infoPtr->hwnd, &pt);
717 else
719 pt.x = LOWORD(*pos);
720 pt.y = HIWORD(*pos);
723 item = TAB_InternalHitTest(infoPtr, pt, &flags);
726 if (item != infoPtr->iHotTracked)
728 if (infoPtr->iHotTracked >= 0)
730 /* Mark currently hot-tracked to be redrawn to look normal */
731 if (out_redrawLeave != NULL)
732 *out_redrawLeave = infoPtr->iHotTracked;
734 if (item < 0)
736 /* Kill timer which forces recheck of mouse pos */
737 KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
740 else
742 /* Start timer so we recheck mouse pos */
743 UINT timerID = SetTimer
745 infoPtr->hwnd,
746 TAB_HOTTRACK_TIMER,
747 TAB_HOTTRACK_TIMER_INTERVAL,
748 TAB_HotTrackTimerProc
751 if (timerID == 0)
752 return; /* Hot tracking not available */
755 infoPtr->iHotTracked = item;
757 if (item >= 0)
759 /* Mark new hot-tracked to be redrawn to look highlighted */
760 if (out_redrawEnter != NULL)
761 *out_redrawEnter = item;
766 /******************************************************************************
767 * TAB_MouseMove
769 * Handles the mouse-move event. Updates tooltips. Updates hot-tracking.
771 static LRESULT
772 TAB_MouseMove (TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
774 int redrawLeave;
775 int redrawEnter;
777 if (infoPtr->hwndToolTip)
778 TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
779 WM_LBUTTONDOWN, wParam, lParam);
781 /* Determine which tab to highlight. Redraw tabs which change highlight
782 ** status. */
783 TAB_RecalcHotTrack(infoPtr, &lParam, &redrawLeave, &redrawEnter);
785 if (redrawLeave != -1)
786 TAB_DrawLoneItemInterior(infoPtr, redrawLeave);
787 if (redrawEnter != -1)
788 TAB_DrawLoneItemInterior(infoPtr, redrawEnter);
790 return 0;
793 /******************************************************************************
794 * TAB_AdjustRect
796 * Calculates the tab control's display area given the window rectangle or
797 * the window rectangle given the requested display rectangle.
799 static LRESULT TAB_AdjustRect(
800 TAB_INFO *infoPtr,
801 WPARAM fLarger,
802 LPRECT prc)
804 DWORD lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
805 LONG *iRightBottom, *iLeftTop;
807 TRACE ("hwnd=%p fLarger=%d (%ld,%ld)-(%ld,%ld)\n", infoPtr->hwnd, fLarger, prc->left, prc->top, prc->right, prc->bottom);
809 if(lStyle & TCS_VERTICAL)
811 iRightBottom = &(prc->right);
812 iLeftTop = &(prc->left);
814 else
816 iRightBottom = &(prc->bottom);
817 iLeftTop = &(prc->top);
820 if (fLarger) /* Go from display rectangle */
822 /* Add the height of the tabs. */
823 if (lStyle & TCS_BOTTOM)
824 *iRightBottom += infoPtr->tabHeight * infoPtr->uNumRows;
825 else
826 *iLeftTop -= infoPtr->tabHeight * infoPtr->uNumRows +
827 ((lStyle & TCS_BUTTONS)? 3 * (infoPtr->uNumRows - 1) : 0);
829 /* Inflate the rectangle for the padding */
830 InflateRect(prc, DISPLAY_AREA_PADDINGX, DISPLAY_AREA_PADDINGY);
832 /* Inflate for the border */
833 InflateRect(prc, CONTROL_BORDER_SIZEX, CONTROL_BORDER_SIZEY);
835 else /* Go from window rectangle. */
837 /* Deflate the rectangle for the border */
838 InflateRect(prc, -CONTROL_BORDER_SIZEX, -CONTROL_BORDER_SIZEY);
840 /* Deflate the rectangle for the padding */
841 InflateRect(prc, -DISPLAY_AREA_PADDINGX, -DISPLAY_AREA_PADDINGY);
843 /* Remove the height of the tabs. */
844 if (lStyle & TCS_BOTTOM)
845 *iRightBottom -= infoPtr->tabHeight * infoPtr->uNumRows;
846 else
847 *iLeftTop += (infoPtr->tabHeight) * infoPtr->uNumRows +
848 ((lStyle & TCS_BUTTONS)? 3 * (infoPtr->uNumRows - 1) : 0);
851 return 0;
854 /******************************************************************************
855 * TAB_OnHScroll
857 * This method will handle the notification from the scroll control and
858 * perform the scrolling operation on the tab control.
860 static LRESULT TAB_OnHScroll(
861 TAB_INFO *infoPtr,
862 int nScrollCode,
863 int nPos,
864 HWND hwndScroll)
866 if(nScrollCode == SB_THUMBPOSITION && nPos != infoPtr->leftmostVisible)
868 if(nPos < infoPtr->leftmostVisible)
869 infoPtr->leftmostVisible--;
870 else
871 infoPtr->leftmostVisible++;
873 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
874 TAB_InvalidateTabArea(infoPtr);
875 SendMessageW(infoPtr->hwndUpDown, UDM_SETPOS, 0,
876 MAKELONG(infoPtr->leftmostVisible, 0));
879 return 0;
882 /******************************************************************************
883 * TAB_SetupScrolling
885 * This method will check the current scrolling state and make sure the
886 * scrolling control is displayed (or not).
888 static void TAB_SetupScrolling(
889 HWND hwnd,
890 TAB_INFO* infoPtr,
891 const RECT* clientRect)
893 static const WCHAR msctls_updown32W[] = { 'm','s','c','t','l','s','_','u','p','d','o','w','n','3','2',0 };
894 static const WCHAR emptyW[] = { 0 };
895 INT maxRange = 0;
896 DWORD lStyle = GetWindowLongW(hwnd, GWL_STYLE);
898 if (infoPtr->needsScrolling)
900 RECT controlPos;
901 INT vsize, tabwidth;
904 * Calculate the position of the scroll control.
906 if(lStyle & TCS_VERTICAL)
908 controlPos.right = clientRect->right;
909 controlPos.left = controlPos.right - 2 * GetSystemMetrics(SM_CXHSCROLL);
911 if (lStyle & TCS_BOTTOM)
913 controlPos.top = clientRect->bottom - infoPtr->tabHeight;
914 controlPos.bottom = controlPos.top + GetSystemMetrics(SM_CYHSCROLL);
916 else
918 controlPos.bottom = clientRect->top + infoPtr->tabHeight;
919 controlPos.top = controlPos.bottom - GetSystemMetrics(SM_CYHSCROLL);
922 else
924 controlPos.right = clientRect->right;
925 controlPos.left = controlPos.right - 2 * GetSystemMetrics(SM_CXHSCROLL);
927 if (lStyle & TCS_BOTTOM)
929 controlPos.top = clientRect->bottom - infoPtr->tabHeight;
930 controlPos.bottom = controlPos.top + GetSystemMetrics(SM_CYHSCROLL);
932 else
934 controlPos.bottom = clientRect->top + infoPtr->tabHeight;
935 controlPos.top = controlPos.bottom - GetSystemMetrics(SM_CYHSCROLL);
940 * If we don't have a scroll control yet, we want to create one.
941 * If we have one, we want to make sure it's positioned properly.
943 if (infoPtr->hwndUpDown==0)
945 infoPtr->hwndUpDown = CreateWindowW(msctls_updown32W, emptyW,
946 WS_VISIBLE | WS_CHILD | UDS_HORZ,
947 controlPos.left, controlPos.top,
948 controlPos.right - controlPos.left,
949 controlPos.bottom - controlPos.top,
950 hwnd, NULL, NULL, NULL);
952 else
954 SetWindowPos(infoPtr->hwndUpDown,
955 NULL,
956 controlPos.left, controlPos.top,
957 controlPos.right - controlPos.left,
958 controlPos.bottom - controlPos.top,
959 SWP_SHOWWINDOW | SWP_NOZORDER);
962 /* Now calculate upper limit of the updown control range.
963 * We do this by calculating how many tabs will be offscreen when the
964 * last tab is visible.
966 if(infoPtr->uNumItem)
968 vsize = clientRect->right - (controlPos.right - controlPos.left + 1);
969 maxRange = infoPtr->uNumItem;
970 tabwidth = TAB_GetItem(infoPtr, infoPtr->uNumItem - 1)->rect.right;
972 for(; maxRange > 0; maxRange--)
974 if(tabwidth - TAB_GetItem(infoPtr,maxRange - 1)->rect.left > vsize)
975 break;
978 if(maxRange == infoPtr->uNumItem)
979 maxRange--;
982 else
984 /* If we once had a scroll control... hide it */
985 if (infoPtr->hwndUpDown!=0)
986 ShowWindow(infoPtr->hwndUpDown, SW_HIDE);
988 if (infoPtr->hwndUpDown)
989 SendMessageW(infoPtr->hwndUpDown, UDM_SETRANGE32, 0, maxRange);
992 /******************************************************************************
993 * TAB_SetItemBounds
995 * This method will calculate the position rectangles of all the items in the
996 * control. The rectangle calculated starts at 0 for the first item in the
997 * list and ignores scrolling and selection.
998 * It also uses the current font to determine the height of the tab row and
999 * it checks if all the tabs fit in the client area of the window. If they
1000 * don't, a scrolling control is added.
1002 static void TAB_SetItemBounds (TAB_INFO *infoPtr)
1004 LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1005 TEXTMETRICW fontMetrics;
1006 UINT curItem;
1007 INT curItemLeftPos;
1008 INT curItemRowCount;
1009 HFONT hFont, hOldFont;
1010 HDC hdc;
1011 RECT clientRect;
1012 SIZE size;
1013 INT iTemp;
1014 RECT* rcItem;
1015 INT iIndex;
1016 INT icon_width = 0;
1019 * We need to get text information so we need a DC and we need to select
1020 * a font.
1022 hdc = GetDC(infoPtr->hwnd);
1024 hFont = infoPtr->hFont ? infoPtr->hFont : GetStockObject (SYSTEM_FONT);
1025 hOldFont = SelectObject (hdc, hFont);
1028 * We will base the rectangle calculations on the client rectangle
1029 * of the control.
1031 GetClientRect(infoPtr->hwnd, &clientRect);
1033 /* if TCS_VERTICAL then swap the height and width so this code places the
1034 tabs along the top of the rectangle and we can just rotate them after
1035 rather than duplicate all of the below code */
1036 if(lStyle & TCS_VERTICAL)
1038 iTemp = clientRect.bottom;
1039 clientRect.bottom = clientRect.right;
1040 clientRect.right = iTemp;
1043 /* Now use hPadding and vPadding */
1044 infoPtr->uHItemPadding = infoPtr->uHItemPadding_s;
1045 infoPtr->uVItemPadding = infoPtr->uVItemPadding_s;
1047 /* The leftmost item will be "0" aligned */
1048 curItemLeftPos = 0;
1049 curItemRowCount = infoPtr->uNumItem ? 1 : 0;
1051 if (!(infoPtr->fHeightSet))
1053 int item_height;
1054 int icon_height = 0;
1056 /* Use the current font to determine the height of a tab. */
1057 GetTextMetricsW(hdc, &fontMetrics);
1059 /* Get the icon height */
1060 if (infoPtr->himl)
1061 ImageList_GetIconSize(infoPtr->himl, 0, &icon_height);
1063 /* Take the highest between font or icon */
1064 if (fontMetrics.tmHeight > icon_height)
1065 item_height = fontMetrics.tmHeight + 2;
1066 else
1067 item_height = icon_height;
1070 * Make sure there is enough space for the letters + icon + growing the
1071 * selected item + extra space for the selected item.
1073 infoPtr->tabHeight = item_height +
1074 ((lStyle & TCS_BUTTONS) ? 2 : 1) *
1075 infoPtr->uVItemPadding;
1077 TRACE("tabH=%d, tmH=%ld, iconh=%d\n",
1078 infoPtr->tabHeight, fontMetrics.tmHeight, icon_height);
1081 TRACE("client right=%ld\n", clientRect.right);
1083 /* Get the icon width */
1084 if (infoPtr->himl)
1086 ImageList_GetIconSize(infoPtr->himl, &icon_width, 0);
1088 if (lStyle & TCS_FIXEDWIDTH)
1089 icon_width += 4;
1090 else
1091 /* Add padding if icon is present */
1092 icon_width += infoPtr->uHItemPadding;
1095 for (curItem = 0; curItem < infoPtr->uNumItem; curItem++)
1097 TAB_ITEM *curr = TAB_GetItem(infoPtr, curItem);
1099 /* Set the leftmost position of the tab. */
1100 curr->rect.left = curItemLeftPos;
1102 if ((lStyle & TCS_FIXEDWIDTH) || !curr->pszText)
1104 curr->rect.right = curr->rect.left +
1105 max(infoPtr->tabWidth, icon_width);
1107 else
1109 int num = 2;
1111 /* Calculate how wide the tab is depending on the text it contains */
1112 GetTextExtentPoint32W(hdc, curr->pszText,
1113 lstrlenW(curr->pszText), &size);
1115 curr->rect.right = curr->rect.left + size.cx + icon_width +
1116 num * infoPtr->uHItemPadding;
1117 TRACE("for <%s>, l,r=%ld,%ld, num=%d\n",
1118 debugstr_w(curr->pszText), curr->rect.left, curr->rect.right, num);
1122 * Check if this is a multiline tab control and if so
1123 * check to see if we should wrap the tabs
1125 * Wrap all these tabs. We will arrange them evenly later.
1129 if (((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)) &&
1130 (curr->rect.right >
1131 (clientRect.right - CONTROL_BORDER_SIZEX - DISPLAY_AREA_PADDINGX)))
1133 curr->rect.right -= curr->rect.left;
1135 curr->rect.left = 0;
1136 curItemRowCount++;
1137 TRACE("wrapping <%s>, l,r=%ld,%ld\n", debugstr_w(curr->pszText),
1138 curr->rect.left, curr->rect.right);
1141 curr->rect.bottom = 0;
1142 curr->rect.top = curItemRowCount - 1;
1144 TRACE("TextSize: %li\n", size.cx);
1145 TRACE("Rect: T %li, L %li, B %li, R %li\n", curr->rect.top,
1146 curr->rect.left, curr->rect.bottom, curr->rect.right);
1149 * The leftmost position of the next item is the rightmost position
1150 * of this one.
1152 if (lStyle & TCS_BUTTONS)
1154 curItemLeftPos = curr->rect.right + BUTTON_SPACINGX;
1155 if (lStyle & TCS_FLATBUTTONS)
1156 curItemLeftPos += FLAT_BTN_SPACINGX;
1158 else
1159 curItemLeftPos = curr->rect.right;
1162 if (!((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)))
1165 * Check if we need a scrolling control.
1167 infoPtr->needsScrolling = (curItemLeftPos + (2 * SELECTED_TAB_OFFSET) >
1168 clientRect.right);
1170 /* Don't need scrolling, then update infoPtr->leftmostVisible */
1171 if(!infoPtr->needsScrolling)
1172 infoPtr->leftmostVisible = 0;
1174 else
1177 * No scrolling in Multiline or Vertical styles.
1179 infoPtr->needsScrolling = FALSE;
1180 infoPtr->leftmostVisible = 0;
1182 TAB_SetupScrolling(infoPtr->hwnd, infoPtr, &clientRect);
1184 /* Set the number of rows */
1185 infoPtr->uNumRows = curItemRowCount;
1187 /* Arrange all tabs evenly if style says so */
1188 if (!(lStyle & TCS_RAGGEDRIGHT) && ((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)) && (infoPtr->uNumItem > 0))
1190 INT tabPerRow,remTab,iRow;
1191 UINT iItm;
1192 INT iCount=0;
1195 * Ok windows tries to even out the rows. place the same
1196 * number of tabs in each row. So lets give that a shot
1199 tabPerRow = infoPtr->uNumItem / (infoPtr->uNumRows);
1200 remTab = infoPtr->uNumItem % (infoPtr->uNumRows);
1202 for (iItm=0,iRow=0,iCount=0,curItemLeftPos=0;
1203 iItm<infoPtr->uNumItem;
1204 iItm++,iCount++)
1206 /* normalize the current rect */
1207 TAB_ITEM *curr = TAB_GetItem(infoPtr, iItm);
1209 /* shift the item to the left side of the clientRect */
1210 curr->rect.right -= curr->rect.left;
1211 curr->rect.left = 0;
1213 TRACE("r=%ld, cl=%d, cl.r=%ld, iCount=%d, iRow=%d, uNumRows=%d, remTab=%d, tabPerRow=%d\n",
1214 curr->rect.right, curItemLeftPos, clientRect.right,
1215 iCount, iRow, infoPtr->uNumRows, remTab, tabPerRow);
1217 /* if we have reached the maximum number of tabs on this row */
1218 /* move to the next row, reset our current item left position and */
1219 /* the count of items on this row */
1221 if (lStyle & TCS_VERTICAL) {
1222 /* Vert: Add the remaining tabs in the *last* remainder rows */
1223 if (iCount >= ((iRow>=(INT)infoPtr->uNumRows - remTab)?tabPerRow + 1:tabPerRow)) {
1224 iRow++;
1225 curItemLeftPos = 0;
1226 iCount = 0;
1228 } else {
1229 /* Horz: Add the remaining tabs in the *first* remainder rows */
1230 if (iCount >= ((iRow<remTab)?tabPerRow + 1:tabPerRow)) {
1231 iRow++;
1232 curItemLeftPos = 0;
1233 iCount = 0;
1237 /* shift the item to the right to place it as the next item in this row */
1238 curr->rect.left += curItemLeftPos;
1239 curr->rect.right += curItemLeftPos;
1240 curr->rect.top = iRow;
1241 if (lStyle & TCS_BUTTONS)
1243 curItemLeftPos = curr->rect.right + 1;
1244 if (lStyle & TCS_FLATBUTTONS)
1245 curItemLeftPos += FLAT_BTN_SPACINGX;
1247 else
1248 curItemLeftPos = curr->rect.right;
1250 TRACE("arranging <%s>, l,r=%ld,%ld, row=%ld\n",
1251 debugstr_w(curr->pszText), curr->rect.left,
1252 curr->rect.right, curr->rect.top);
1256 * Justify the rows
1259 INT widthDiff, iIndexStart=0, iIndexEnd=0;
1260 INT remainder;
1261 INT iCount=0;
1263 while(iIndexStart < infoPtr->uNumItem)
1265 TAB_ITEM *start = TAB_GetItem(infoPtr, iIndexStart);
1268 * find the index of the row
1270 /* find the first item on the next row */
1271 for (iIndexEnd=iIndexStart;
1272 (iIndexEnd < infoPtr->uNumItem) &&
1273 (TAB_GetItem(infoPtr, iIndexEnd)->rect.top ==
1274 start->rect.top) ;
1275 iIndexEnd++)
1276 /* intentionally blank */;
1279 * we need to justify these tabs so they fill the whole given
1280 * client area
1283 /* find the amount of space remaining on this row */
1284 widthDiff = clientRect.right - (2 * SELECTED_TAB_OFFSET) -
1285 TAB_GetItem(infoPtr, iIndexEnd - 1)->rect.right;
1287 /* iCount is the number of tab items on this row */
1288 iCount = iIndexEnd - iIndexStart;
1290 if (iCount > 1)
1292 remainder = widthDiff % iCount;
1293 widthDiff = widthDiff / iCount;
1294 /* add widthDiff/iCount, or extra space/items on row, to each item on this row */
1295 for (iIndex=iIndexStart, iCount=0; iIndex < iIndexEnd; iIndex++, iCount++)
1297 TAB_ITEM *item = TAB_GetItem(infoPtr, iIndex);
1299 item->rect.left += iCount * widthDiff;
1300 item->rect.right += (iCount + 1) * widthDiff;
1302 TRACE("adjusting 1 <%s>, l,r=%ld,%ld\n",
1303 debugstr_w(item->pszText),
1304 item->rect.left, item->rect.right);
1307 TAB_GetItem(infoPtr, iIndex - 1)->rect.right += remainder;
1309 else /* we have only one item on this row, make it take up the entire row */
1311 start->rect.left = clientRect.left;
1312 start->rect.right = clientRect.right - 4;
1314 TRACE("adjusting 2 <%s>, l,r=%ld,%ld\n",
1315 debugstr_w(start->pszText),
1316 start->rect.left, start->rect.right);
1321 iIndexStart = iIndexEnd;
1326 /* if TCS_VERTICAL rotate the tabs so they are along the side of the clientRect */
1327 if(lStyle & TCS_VERTICAL)
1329 RECT rcOriginal;
1330 for(iIndex = 0; iIndex < infoPtr->uNumItem; iIndex++)
1332 rcItem = &TAB_GetItem(infoPtr, iIndex)->rect;
1334 rcOriginal = *rcItem;
1336 /* this is rotating the items by 90 degrees clockwise around the center of the control */
1337 rcItem->top = (rcOriginal.left - clientRect.left);
1338 rcItem->bottom = rcItem->top + (rcOriginal.right - rcOriginal.left);
1339 rcItem->left = rcOriginal.top;
1340 rcItem->right = rcOriginal.bottom;
1344 TAB_EnsureSelectionVisible(infoPtr);
1345 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
1347 /* Cleanup */
1348 SelectObject (hdc, hOldFont);
1349 ReleaseDC (infoPtr->hwnd, hdc);
1353 static void
1354 TAB_EraseTabInterior
1356 TAB_INFO* infoPtr,
1357 HDC hdc,
1358 INT iItem,
1359 RECT* drawRect
1362 LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1363 HBRUSH hbr = CreateSolidBrush (comctl32_color.clrBtnFace);
1364 BOOL deleteBrush = TRUE;
1365 RECT rTemp = *drawRect;
1367 InflateRect(&rTemp, -2, -2);
1368 if (lStyle & TCS_BUTTONS)
1370 if (iItem == infoPtr->iSelected)
1372 /* Background color */
1373 if (!(lStyle & TCS_OWNERDRAWFIXED))
1375 DeleteObject(hbr);
1376 hbr = GetSysColorBrush(COLOR_SCROLLBAR);
1378 SetTextColor(hdc, comctl32_color.clr3dFace);
1379 SetBkColor(hdc, comctl32_color.clr3dHilight);
1381 /* if COLOR_WINDOW happens to be the same as COLOR_3DHILIGHT
1382 * we better use 0x55aa bitmap brush to make scrollbar's background
1383 * look different from the window background.
1385 if (comctl32_color.clr3dHilight == comctl32_color.clrWindow)
1386 hbr = COMCTL32_hPattern55AABrush;
1388 deleteBrush = FALSE;
1390 FillRect(hdc, &rTemp, hbr);
1392 else /* ! selected */
1394 if (lStyle & TCS_FLATBUTTONS)
1396 FillRect(hdc, drawRect, hbr);
1397 if (iItem == infoPtr->iHotTracked)
1398 DrawEdge(hdc, drawRect, EDGE_RAISED, BF_SOFT|BF_RECT);
1400 else
1401 FillRect(hdc, &rTemp, hbr);
1405 else /* !TCS_BUTTONS */
1407 FillRect(hdc, &rTemp, hbr);
1410 /* Cleanup */
1411 if (deleteBrush) DeleteObject(hbr);
1414 /******************************************************************************
1415 * TAB_DrawItemInterior
1417 * This method is used to draw the interior (text and icon) of a single tab
1418 * into the tab control.
1420 static void
1421 TAB_DrawItemInterior
1423 TAB_INFO* infoPtr,
1424 HDC hdc,
1425 INT iItem,
1426 RECT* drawRect
1429 LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1431 RECT localRect;
1433 HPEN htextPen;
1434 HPEN holdPen;
1435 INT oldBkMode;
1436 HFONT hOldFont;
1438 /* if (drawRect == NULL) */
1440 BOOL isVisible;
1441 RECT itemRect;
1442 RECT selectedRect;
1445 * Get the rectangle for the item.
1447 isVisible = TAB_InternalGetItemRect(infoPtr, iItem, &itemRect, &selectedRect);
1448 if (!isVisible)
1449 return;
1452 * Make sure drawRect points to something valid; simplifies code.
1454 drawRect = &localRect;
1457 * This logic copied from the part of TAB_DrawItem which draws
1458 * the tab background. It's important to keep it in sync. I
1459 * would have liked to avoid code duplication, but couldn't figure
1460 * out how without making spaghetti of TAB_DrawItem.
1462 if (iItem == infoPtr->iSelected)
1463 *drawRect = selectedRect;
1464 else
1465 *drawRect = itemRect;
1467 if (lStyle & TCS_BUTTONS)
1469 if (iItem == infoPtr->iSelected)
1471 drawRect->left += 4;
1472 drawRect->top += 4;
1473 drawRect->right -= 4;
1474 drawRect->bottom -= 1;
1476 else
1478 drawRect->left += 2;
1479 drawRect->top += 2;
1480 drawRect->right -= 2;
1481 drawRect->bottom -= 2;
1484 else
1486 if ((lStyle & TCS_VERTICAL) && (lStyle & TCS_BOTTOM))
1488 if (iItem != infoPtr->iSelected)
1490 drawRect->left += 2;
1491 drawRect->top += 2;
1492 drawRect->bottom -= 2;
1495 else if (lStyle & TCS_VERTICAL)
1497 if (iItem == infoPtr->iSelected)
1499 drawRect->right += 1;
1501 else
1503 drawRect->top += 2;
1504 drawRect->right -= 2;
1505 drawRect->bottom -= 2;
1508 else if (lStyle & TCS_BOTTOM)
1510 if (iItem == infoPtr->iSelected)
1512 drawRect->top -= 2;
1514 else
1516 InflateRect(drawRect, -2, -2);
1517 drawRect->bottom += 2;
1520 else
1522 if (iItem == infoPtr->iSelected)
1524 drawRect->bottom += 3;
1526 else
1528 drawRect->bottom -= 2;
1529 InflateRect(drawRect, -2, 0);
1534 TRACE("drawRect=(%ld,%ld)-(%ld,%ld)\n",
1535 drawRect->left, drawRect->top, drawRect->right, drawRect->bottom);
1537 /* Clear interior */
1538 TAB_EraseTabInterior (infoPtr, hdc, iItem, drawRect);
1540 /* Draw the focus rectangle */
1541 if (!(lStyle & TCS_FOCUSNEVER) &&
1542 (GetFocus() == infoPtr->hwnd) &&
1543 (iItem == infoPtr->uFocus) )
1545 RECT rFocus = *drawRect;
1546 InflateRect(&rFocus, -3, -3);
1547 if (lStyle & TCS_BOTTOM && !(lStyle & TCS_VERTICAL))
1548 rFocus.top -= 3;
1549 if (lStyle & TCS_BUTTONS)
1551 rFocus.left -= 3;
1552 rFocus.top -= 3;
1555 DrawFocusRect(hdc, &rFocus);
1559 * Text pen
1561 htextPen = CreatePen( PS_SOLID, 1, GetSysColor(COLOR_BTNTEXT) );
1562 holdPen = SelectObject(hdc, htextPen);
1563 hOldFont = SelectObject(hdc, infoPtr->hFont);
1566 * Setup for text output
1568 oldBkMode = SetBkMode(hdc, TRANSPARENT);
1569 SetTextColor(hdc, (((iItem == infoPtr->iHotTracked) && !(lStyle & TCS_FLATBUTTONS)) |
1570 (TAB_GetItem(infoPtr, iItem)->dwState & TCIS_HIGHLIGHTED)) ?
1571 comctl32_color.clrHighlight : comctl32_color.clrBtnText);
1574 * if owner draw, tell the owner to draw
1576 if ((lStyle & TCS_OWNERDRAWFIXED) && GetParent(infoPtr->hwnd))
1578 DRAWITEMSTRUCT dis;
1579 UINT id;
1581 drawRect->top += 2;
1582 drawRect->right -= 1;
1583 if ( iItem == infoPtr->iSelected )
1585 drawRect->right -= 1;
1586 drawRect->left += 1;
1590 * get the control id
1592 id = (UINT)GetWindowLongPtrW( infoPtr->hwnd, GWLP_ID );
1595 * put together the DRAWITEMSTRUCT
1597 dis.CtlType = ODT_TAB;
1598 dis.CtlID = id;
1599 dis.itemID = iItem;
1600 dis.itemAction = ODA_DRAWENTIRE;
1601 dis.itemState = 0;
1602 if ( iItem == infoPtr->iSelected )
1603 dis.itemState |= ODS_SELECTED;
1604 if (infoPtr->uFocus == iItem)
1605 dis.itemState |= ODS_FOCUS;
1606 dis.hwndItem = infoPtr->hwnd;
1607 dis.hDC = hdc;
1608 CopyRect(&dis.rcItem,drawRect);
1609 dis.itemData = (ULONG_PTR)TAB_GetItem(infoPtr, iItem)->extra;
1612 * send the draw message
1614 SendMessageW( infoPtr->hwndNotify, WM_DRAWITEM, (WPARAM)id, (LPARAM)&dis );
1616 else
1618 TAB_ITEM *item = TAB_GetItem(infoPtr, iItem);
1619 RECT rcTemp;
1620 RECT rcImage;
1622 /* used to center the icon and text in the tab */
1623 RECT rcText;
1624 INT center_offset_h, center_offset_v;
1626 /* set rcImage to drawRect, we will use top & left in our ImageList_Draw call */
1627 rcImage = *drawRect;
1629 rcTemp = *drawRect;
1631 rcText.left = rcText.top = rcText.right = rcText.bottom = 0;
1633 /* get the rectangle that the text fits in */
1634 if (item->pszText)
1636 DrawTextW(hdc, item->pszText, -1, &rcText, DT_CALCRECT);
1639 * If not owner draw, then do the drawing ourselves.
1641 * Draw the icon.
1643 if (infoPtr->himl && (item->mask & TCIF_IMAGE))
1645 INT cx;
1646 INT cy;
1648 ImageList_GetIconSize(infoPtr->himl, &cx, &cy);
1650 if(lStyle & TCS_VERTICAL)
1652 center_offset_h = ((drawRect->bottom - drawRect->top) - (cy + infoPtr->uHItemPadding + (rcText.right - rcText.left))) / 2;
1653 center_offset_v = ((drawRect->right - drawRect->left) - (cx + infoPtr->uVItemPadding)) / 2;
1655 else
1657 center_offset_h = ((drawRect->right - drawRect->left) - (cx + infoPtr->uHItemPadding + (rcText.right - rcText.left))) / 2;
1658 center_offset_v = ((drawRect->bottom - drawRect->top) - (cy + infoPtr->uVItemPadding)) / 2;
1661 if (lStyle & TCS_FIXEDWIDTH && lStyle & (TCS_FORCELABELLEFT | TCS_FORCEICONLEFT))
1662 center_offset_h = infoPtr->uHItemPadding;
1664 if (center_offset_h < 2)
1665 center_offset_h = 2;
1667 if (center_offset_v < 0)
1668 center_offset_v = 0;
1670 TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%ld,%ld)-(%ld,%ld), textlen=%ld\n",
1671 debugstr_w(item->pszText), center_offset_h, center_offset_v,
1672 drawRect->left, drawRect->top, drawRect->right, drawRect->bottom,
1673 (rcText.right-rcText.left));
1675 if((lStyle & TCS_VERTICAL) && (lStyle & TCS_BOTTOM))
1677 rcImage.top = drawRect->top + center_offset_h;
1678 /* if tab is TCS_VERTICAL and TCS_BOTTOM, the text is drawn from the */
1679 /* right side of the tab, but the image still uses the left as its x position */
1680 /* this keeps the image always drawn off of the same side of the tab */
1681 rcImage.left = drawRect->right - cx - center_offset_v;
1682 drawRect->top += cy + infoPtr->uHItemPadding;
1684 else if(lStyle & TCS_VERTICAL)
1686 rcImage.top = drawRect->bottom - cy - center_offset_h;
1687 rcImage.left = drawRect->left + center_offset_v;
1688 drawRect->bottom -= cy + infoPtr->uHItemPadding;
1690 else /* normal style, whether TCS_BOTTOM or not */
1692 rcImage.left = drawRect->left + center_offset_h;
1693 rcImage.top = drawRect->top + center_offset_v;
1694 drawRect->left += cx + infoPtr->uHItemPadding;
1697 TRACE("drawing image=%d, left=%ld, top=%ld\n",
1698 item->iImage, rcImage.left, rcImage.top-1);
1699 ImageList_Draw
1701 infoPtr->himl,
1702 item->iImage,
1703 hdc,
1704 rcImage.left,
1705 rcImage.top,
1706 ILD_NORMAL
1710 /* Now position text */
1711 if (lStyle & TCS_FIXEDWIDTH && lStyle & TCS_FORCELABELLEFT)
1712 center_offset_h = infoPtr->uHItemPadding;
1713 else
1714 if(lStyle & TCS_VERTICAL)
1715 center_offset_h = ((drawRect->bottom - drawRect->top) - (rcText.right - rcText.left)) / 2;
1716 else
1717 center_offset_h = ((drawRect->right - drawRect->left) - (rcText.right - rcText.left)) / 2;
1719 if(lStyle & TCS_VERTICAL)
1721 if(lStyle & TCS_BOTTOM)
1722 drawRect->top+=center_offset_h;
1723 else
1724 drawRect->bottom-=center_offset_h;
1726 center_offset_v = ((drawRect->right - drawRect->left) - ((rcText.bottom - rcText.top) + infoPtr->uVItemPadding)) / 2;
1728 else
1730 drawRect->left += center_offset_h;
1731 center_offset_v = ((drawRect->bottom - drawRect->top) - ((rcText.bottom - rcText.top) + infoPtr->uVItemPadding)) / 2;
1734 if (center_offset_v < 0)
1735 center_offset_v = 0;
1737 if(lStyle & TCS_VERTICAL)
1738 drawRect->left += center_offset_v;
1739 else
1740 drawRect->top += center_offset_v;
1742 /* Draw the text */
1743 if(lStyle & TCS_VERTICAL) /* if we are vertical rotate the text and each character */
1745 static const WCHAR ArialW[] = { 'A','r','i','a','l',0 };
1746 LOGFONTW logfont;
1747 HFONT hFont = 0;
1748 INT nEscapement = 900;
1749 INT nOrientation = 900;
1751 if(lStyle & TCS_BOTTOM)
1753 nEscapement = -900;
1754 nOrientation = -900;
1757 /* to get a font with the escapement and orientation we are looking for, we need to */
1758 /* call CreateFontIndirectA, which requires us to set the values of the logfont we pass in */
1759 if (!GetObjectW((infoPtr->hFont) ?
1760 infoPtr->hFont : GetStockObject(SYSTEM_FONT),
1761 sizeof(LOGFONTW),&logfont))
1763 INT iPointSize = 9;
1765 lstrcpyW(logfont.lfFaceName, ArialW);
1766 logfont.lfHeight = -MulDiv(iPointSize, GetDeviceCaps(hdc, LOGPIXELSY),
1767 72);
1768 logfont.lfWeight = FW_NORMAL;
1769 logfont.lfItalic = 0;
1770 logfont.lfUnderline = 0;
1771 logfont.lfStrikeOut = 0;
1774 logfont.lfEscapement = nEscapement;
1775 logfont.lfOrientation = nOrientation;
1776 hFont = CreateFontIndirectW(&logfont);
1777 SelectObject(hdc, hFont);
1779 if (item->pszText)
1781 ExtTextOutW(hdc,
1782 (lStyle & TCS_BOTTOM) ? drawRect->right : drawRect->left,
1783 (!(lStyle & TCS_BOTTOM)) ? drawRect->bottom : drawRect->top,
1784 ETO_CLIPPED,
1785 drawRect,
1786 item->pszText,
1787 lstrlenW(item->pszText),
1791 DeleteObject(hFont);
1793 else
1795 TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%ld,%ld)-(%ld,%ld), textlen=%ld\n",
1796 debugstr_w(item->pszText), center_offset_h, center_offset_v,
1797 drawRect->left, drawRect->top, drawRect->right, drawRect->bottom,
1798 (rcText.right-rcText.left));
1799 if (item->pszText)
1801 DrawTextW
1803 hdc,
1804 item->pszText,
1805 lstrlenW(item->pszText),
1806 drawRect,
1807 DT_LEFT | DT_SINGLELINE
1812 *drawRect = rcTemp; /* restore drawRect */
1816 * Cleanup
1818 SelectObject(hdc, hOldFont);
1819 SetBkMode(hdc, oldBkMode);
1820 SelectObject(hdc, holdPen);
1821 DeleteObject( htextPen );
1824 /******************************************************************************
1825 * TAB_DrawItem
1827 * This method is used to draw a single tab into the tab control.
1829 static void TAB_DrawItem(
1830 TAB_INFO *infoPtr,
1831 HDC hdc,
1832 INT iItem)
1834 LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1835 RECT itemRect;
1836 RECT selectedRect;
1837 BOOL isVisible;
1838 RECT r, fillRect, r1;
1839 INT clRight = 0;
1840 INT clBottom = 0;
1841 COLORREF bkgnd, corner;
1844 * Get the rectangle for the item.
1846 isVisible = TAB_InternalGetItemRect(infoPtr,
1847 iItem,
1848 &itemRect,
1849 &selectedRect);
1851 if (isVisible)
1853 RECT rUD, rC;
1855 /* Clip UpDown control to not draw over it */
1856 if (infoPtr->needsScrolling)
1858 GetWindowRect(infoPtr->hwnd, &rC);
1859 GetWindowRect(infoPtr->hwndUpDown, &rUD);
1860 ExcludeClipRect(hdc, rUD.left - rC.left, rUD.top - rC.top, rUD.right - rC.left, rUD.bottom - rC.top);
1863 /* If you need to see what the control is doing,
1864 * then override these variables. They will change what
1865 * fill colors are used for filling the tabs, and the
1866 * corners when drawing the edge.
1868 bkgnd = comctl32_color.clrBtnFace;
1869 corner = comctl32_color.clrBtnFace;
1871 if (lStyle & TCS_BUTTONS)
1873 /* Get item rectangle */
1874 r = itemRect;
1876 /* Separators between flat buttons */
1877 if (lStyle & TCS_FLATBUTTONS)
1879 r1 = r;
1880 r1.right += (FLAT_BTN_SPACINGX -2);
1881 DrawEdge(hdc, &r1, EDGE_ETCHED, BF_RIGHT);
1884 if (iItem == infoPtr->iSelected)
1886 DrawEdge(hdc, &r, EDGE_SUNKEN, BF_SOFT|BF_RECT);
1888 OffsetRect(&r, 1, 1);
1890 else /* ! selected */
1892 if (!(lStyle & TCS_FLATBUTTONS))
1893 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_RECT);
1896 else /* !TCS_BUTTONS */
1898 /* We draw a rectangle of different sizes depending on the selection
1899 * state. */
1900 if (iItem == infoPtr->iSelected) {
1901 RECT rect;
1902 GetClientRect (infoPtr->hwnd, &rect);
1903 clRight = rect.right;
1904 clBottom = rect.bottom;
1905 r = selectedRect;
1907 else
1908 r = itemRect;
1911 * Erase the background. (Delay it but setup rectangle.)
1912 * This is necessary when drawing the selected item since it is larger
1913 * than the others, it might overlap with stuff already drawn by the
1914 * other tabs
1916 fillRect = r;
1918 if(lStyle & TCS_VERTICAL)
1920 /* These are for adjusting the drawing of a Selected tab */
1921 /* The initial values are for the normal case of non-Selected */
1922 int ZZ = 1; /* Do not strech if selected */
1923 if (iItem == infoPtr->iSelected) {
1924 ZZ = 0;
1926 /* if leftmost draw the line longer */
1927 if(selectedRect.top == 0)
1928 fillRect.top += CONTROL_BORDER_SIZEY;
1929 /* if rightmost draw the line longer */
1930 if(selectedRect.bottom == clBottom)
1931 fillRect.bottom -= CONTROL_BORDER_SIZEY;
1934 if (lStyle & TCS_BOTTOM)
1936 /* Adjust both rectangles to match native */
1937 r.left += (1-ZZ);
1939 TRACE("<right> item=%d, fill=(%ld,%ld)-(%ld,%ld), edge=(%ld,%ld)-(%ld,%ld)\n",
1940 iItem,
1941 fillRect.left,fillRect.top,fillRect.right,fillRect.bottom,
1942 r.left,r.top,r.right,r.bottom);
1944 /* Clear interior */
1945 SetBkColor(hdc, bkgnd);
1946 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
1948 /* Draw rectangular edge around tab */
1949 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_RIGHT|BF_TOP|BF_BOTTOM);
1951 /* Now erase the top corner and draw diagonal edge */
1952 SetBkColor(hdc, corner);
1953 r1.left = r.right - ROUND_CORNER_SIZE - 1;
1954 r1.top = r.top;
1955 r1.right = r.right;
1956 r1.bottom = r1.top + ROUND_CORNER_SIZE;
1957 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
1958 r1.right--;
1959 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPLEFT);
1961 /* Now erase the bottom corner and draw diagonal edge */
1962 r1.left = r.right - ROUND_CORNER_SIZE - 1;
1963 r1.bottom = r.bottom;
1964 r1.right = r.right;
1965 r1.top = r1.bottom - ROUND_CORNER_SIZE;
1966 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
1967 r1.right--;
1968 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMLEFT);
1970 if ((iItem == infoPtr->iSelected) && (selectedRect.top == 0)) {
1971 r1 = r;
1972 r1.right = r1.left;
1973 r1.left--;
1974 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_TOP);
1978 else
1980 TRACE("<left> item=%d, fill=(%ld,%ld)-(%ld,%ld), edge=(%ld,%ld)-(%ld,%ld)\n",
1981 iItem,
1982 fillRect.left,fillRect.top,fillRect.right,fillRect.bottom,
1983 r.left,r.top,r.right,r.bottom);
1985 /* Clear interior */
1986 SetBkColor(hdc, bkgnd);
1987 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
1989 /* Draw rectangular edge around tab */
1990 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_TOP|BF_BOTTOM);
1992 /* Now erase the top corner and draw diagonal edge */
1993 SetBkColor(hdc, corner);
1994 r1.left = r.left;
1995 r1.top = r.top;
1996 r1.right = r1.left + ROUND_CORNER_SIZE + 1;
1997 r1.bottom = r1.top + ROUND_CORNER_SIZE;
1998 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
1999 r1.left++;
2000 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPRIGHT);
2002 /* Now erase the bottom corner and draw diagonal edge */
2003 r1.left = r.left;
2004 r1.bottom = r.bottom;
2005 r1.right = r1.left + ROUND_CORNER_SIZE + 1;
2006 r1.top = r1.bottom - ROUND_CORNER_SIZE;
2007 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2008 r1.left++;
2009 DrawEdge(hdc, &r1, EDGE_SUNKEN, BF_DIAGONAL_ENDTOPLEFT);
2012 else /* ! TCS_VERTICAL */
2014 /* These are for adjusting the drawing of a Selected tab */
2015 /* The initial values are for the normal case of non-Selected */
2016 if (iItem == infoPtr->iSelected) {
2017 /* if leftmost draw the line longer */
2018 if(selectedRect.left == 0)
2019 fillRect.left += CONTROL_BORDER_SIZEX;
2020 /* if rightmost draw the line longer */
2021 if(selectedRect.right == clRight)
2022 fillRect.right -= CONTROL_BORDER_SIZEX;
2025 if (lStyle & TCS_BOTTOM)
2027 /* Adjust both rectangles for topmost row */
2028 if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2030 fillRect.top -= 2;
2031 r.top -= 1;
2034 TRACE("<bottom> item=%d, fill=(%ld,%ld)-(%ld,%ld), edge=(%ld,%ld)-(%ld,%ld)\n",
2035 iItem,
2036 fillRect.left,fillRect.top,fillRect.right,fillRect.bottom,
2037 r.left,r.top,r.right,r.bottom);
2039 /* Clear interior */
2040 SetBkColor(hdc, bkgnd);
2041 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2043 /* Draw rectangular edge around tab */
2044 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_BOTTOM|BF_RIGHT);
2046 /* Now erase the righthand corner and draw diagonal edge */
2047 SetBkColor(hdc, corner);
2048 r1.left = r.right - ROUND_CORNER_SIZE;
2049 r1.bottom = r.bottom;
2050 r1.right = r.right;
2051 r1.top = r1.bottom - ROUND_CORNER_SIZE - 1;
2052 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2053 r1.bottom--;
2054 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMLEFT);
2056 /* Now erase the lefthand corner and draw diagonal edge */
2057 r1.left = r.left;
2058 r1.bottom = r.bottom;
2059 r1.right = r1.left + ROUND_CORNER_SIZE;
2060 r1.top = r1.bottom - ROUND_CORNER_SIZE - 1;
2061 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2062 r1.bottom--;
2063 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPLEFT);
2065 if (iItem == infoPtr->iSelected)
2067 r.top += 2;
2068 r.left += 1;
2069 if (selectedRect.left == 0)
2071 r1 = r;
2072 r1.bottom = r1.top;
2073 r1.top--;
2074 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_LEFT);
2079 else
2081 /* Adjust both rectangles for bottommost row */
2082 if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2084 fillRect.bottom += 3;
2085 r.bottom += 2;
2088 TRACE("<top> item=%d, fill=(%ld,%ld)-(%ld,%ld), edge=(%ld,%ld)-(%ld,%ld)\n",
2089 iItem,
2090 fillRect.left,fillRect.top,fillRect.right,fillRect.bottom,
2091 r.left,r.top,r.right,r.bottom);
2093 /* Clear interior */
2094 SetBkColor(hdc, bkgnd);
2095 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2097 /* Draw rectangular edge around tab */
2098 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_TOP|BF_RIGHT);
2100 /* Now erase the righthand corner and draw diagonal edge */
2101 SetBkColor(hdc, corner);
2102 r1.left = r.right - ROUND_CORNER_SIZE;
2103 r1.top = r.top;
2104 r1.right = r.right;
2105 r1.bottom = r1.top + ROUND_CORNER_SIZE + 1;
2106 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2107 r1.top++;
2108 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMRIGHT);
2110 /* Now erase the lefthand corner and draw diagonal edge */
2111 r1.left = r.left;
2112 r1.top = r.top;
2113 r1.right = r1.left + ROUND_CORNER_SIZE;
2114 r1.bottom = r1.top + ROUND_CORNER_SIZE + 1;
2115 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2116 r1.top++;
2117 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPRIGHT);
2122 TAB_DumpItemInternal(infoPtr, iItem);
2124 /* This modifies r to be the text rectangle. */
2125 TAB_DrawItemInterior(infoPtr, hdc, iItem, &r);
2129 /******************************************************************************
2130 * TAB_DrawBorder
2132 * This method is used to draw the raised border around the tab control
2133 * "content" area.
2135 static void TAB_DrawBorder (TAB_INFO *infoPtr, HDC hdc)
2137 RECT rect;
2138 DWORD lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
2140 GetClientRect (infoPtr->hwnd, &rect);
2143 * Adjust for the style
2146 if (infoPtr->uNumItem)
2148 if ((lStyle & TCS_BOTTOM) && !(lStyle & TCS_VERTICAL))
2149 rect.bottom -= infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2150 else if((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
2151 rect.right -= infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2152 else if(lStyle & TCS_VERTICAL)
2153 rect.left += infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2154 else /* not TCS_VERTICAL and not TCS_BOTTOM */
2155 rect.top += infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2158 TRACE("border=(%ld,%ld)-(%ld,%ld)\n",
2159 rect.left, rect.top, rect.right, rect.bottom);
2161 DrawEdge(hdc, &rect, EDGE_RAISED, BF_SOFT|BF_RECT);
2164 /******************************************************************************
2165 * TAB_Refresh
2167 * This method repaints the tab control..
2169 static void TAB_Refresh (TAB_INFO *infoPtr, HDC hdc)
2171 HFONT hOldFont;
2172 INT i;
2174 if (!infoPtr->DoRedraw)
2175 return;
2177 hOldFont = SelectObject (hdc, infoPtr->hFont);
2179 if (GetWindowLongW(infoPtr->hwnd, GWL_STYLE) & TCS_BUTTONS)
2181 for (i = 0; i < infoPtr->uNumItem; i++)
2182 TAB_DrawItem (infoPtr, hdc, i);
2184 else
2186 /* Draw all the non selected item first */
2187 for (i = 0; i < infoPtr->uNumItem; i++)
2189 if (i != infoPtr->iSelected)
2190 TAB_DrawItem (infoPtr, hdc, i);
2193 /* Now, draw the border, draw it before the selected item
2194 * since the selected item overwrites part of the border. */
2195 TAB_DrawBorder (infoPtr, hdc);
2197 /* Then, draw the selected item */
2198 TAB_DrawItem (infoPtr, hdc, infoPtr->iSelected);
2200 /* If we haven't set the current focus yet, set it now.
2201 * Only happens when we first paint the tab controls */
2202 if (infoPtr->uFocus == -1)
2203 TAB_SetCurFocus(infoPtr, infoPtr->iSelected);
2206 SelectObject (hdc, hOldFont);
2209 static inline DWORD TAB_GetRowCount (const TAB_INFO *infoPtr)
2211 return infoPtr->uNumRows;
2214 static inline LRESULT TAB_SetRedraw (TAB_INFO *infoPtr, BOOL doRedraw)
2216 infoPtr->DoRedraw = doRedraw;
2217 return 0;
2220 /******************************************************************************
2221 * TAB_EnsureSelectionVisible
2223 * This method will make sure that the current selection is completely
2224 * visible by scrolling until it is.
2226 static void TAB_EnsureSelectionVisible(
2227 TAB_INFO* infoPtr)
2229 INT iSelected = infoPtr->iSelected;
2230 LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
2231 INT iOrigLeftmostVisible = infoPtr->leftmostVisible;
2233 /* set the items row to the bottommost row or topmost row depending on
2234 * style */
2235 if ((infoPtr->uNumRows > 1) && !(lStyle & TCS_BUTTONS))
2237 TAB_ITEM *selected = TAB_GetItem(infoPtr, iSelected);
2238 INT newselected;
2239 INT iTargetRow;
2241 if(lStyle & TCS_VERTICAL)
2242 newselected = selected->rect.left;
2243 else
2244 newselected = selected->rect.top;
2246 /* the target row is always (number of rows - 1)
2247 as row 0 is furthest from the clientRect */
2248 iTargetRow = infoPtr->uNumRows - 1;
2250 if (newselected != iTargetRow)
2252 UINT i;
2253 if(lStyle & TCS_VERTICAL)
2255 for (i=0; i < infoPtr->uNumItem; i++)
2257 /* move everything in the row of the selected item to the iTargetRow */
2258 TAB_ITEM *item = TAB_GetItem(infoPtr, i);
2260 if (item->rect.left == newselected )
2261 item->rect.left = iTargetRow;
2262 else
2264 if (item->rect.left > newselected)
2265 item->rect.left-=1;
2269 else
2271 for (i=0; i < infoPtr->uNumItem; i++)
2273 TAB_ITEM *item = TAB_GetItem(infoPtr, i);
2275 if (item->rect.top == newselected )
2276 item->rect.top = iTargetRow;
2277 else
2279 if (item->rect.top > newselected)
2280 item->rect.top-=1;
2284 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
2289 * Do the trivial cases first.
2291 if ( (!infoPtr->needsScrolling) ||
2292 (infoPtr->hwndUpDown==0) || (lStyle & TCS_VERTICAL))
2293 return;
2295 if (infoPtr->leftmostVisible >= iSelected)
2297 infoPtr->leftmostVisible = iSelected;
2299 else
2301 TAB_ITEM *selected = TAB_GetItem(infoPtr, iSelected);
2302 RECT r;
2303 INT width;
2304 UINT i;
2306 /* Calculate the part of the client area that is visible */
2307 GetClientRect(infoPtr->hwnd, &r);
2308 width = r.right;
2310 GetClientRect(infoPtr->hwndUpDown, &r);
2311 width -= r.right;
2313 if ((selected->rect.right -
2314 selected->rect.left) >= width )
2316 /* Special case: width of selected item is greater than visible
2317 * part of control.
2319 infoPtr->leftmostVisible = iSelected;
2321 else
2323 for (i = infoPtr->leftmostVisible; i < infoPtr->uNumItem; i++)
2325 if ((selected->rect.right - TAB_GetItem(infoPtr, i)->rect.left) < width)
2326 break;
2328 infoPtr->leftmostVisible = i;
2332 if (infoPtr->leftmostVisible != iOrigLeftmostVisible)
2333 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
2335 SendMessageW(infoPtr->hwndUpDown, UDM_SETPOS, 0,
2336 MAKELONG(infoPtr->leftmostVisible, 0));
2339 /******************************************************************************
2340 * TAB_InvalidateTabArea
2342 * This method will invalidate the portion of the control that contains the
2343 * tabs. It is called when the state of the control changes and needs
2344 * to be redisplayed
2346 static void TAB_InvalidateTabArea(TAB_INFO* infoPtr)
2348 RECT clientRect, rInvalidate, rAdjClient;
2349 DWORD lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
2350 INT lastRow = infoPtr->uNumRows - 1;
2351 RECT rect;
2353 if (lastRow < 0) return;
2355 GetClientRect(infoPtr->hwnd, &clientRect);
2356 rInvalidate = clientRect;
2357 rAdjClient = clientRect;
2359 TAB_AdjustRect(infoPtr, 0, &rAdjClient);
2361 TAB_InternalGetItemRect(infoPtr, infoPtr->uNumItem-1 , &rect, NULL);
2362 if ((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
2364 rInvalidate.left = rAdjClient.right;
2365 if (infoPtr->uNumRows == 1)
2366 rInvalidate.bottom = clientRect.top + rect.bottom + 2 * SELECTED_TAB_OFFSET;
2368 else if(lStyle & TCS_VERTICAL)
2370 rInvalidate.right = rAdjClient.left;
2371 if (infoPtr->uNumRows == 1)
2372 rInvalidate.bottom = clientRect.top + rect.bottom + 2 * SELECTED_TAB_OFFSET;
2374 else if (lStyle & TCS_BOTTOM)
2376 rInvalidate.top = rAdjClient.bottom;
2377 if (infoPtr->uNumRows == 1)
2378 rInvalidate.right = clientRect.left + rect.right + 2 * SELECTED_TAB_OFFSET;
2380 else
2382 rInvalidate.bottom = rAdjClient.top;
2383 if (infoPtr->uNumRows == 1)
2384 rInvalidate.right = clientRect.left + rect.right + 2 * SELECTED_TAB_OFFSET;
2387 /* Punch out the updown control */
2388 if (infoPtr->needsScrolling && (rInvalidate.right > 0)) {
2389 RECT r;
2390 GetClientRect(infoPtr->hwndUpDown, &r);
2391 if (rInvalidate.right > clientRect.right - r.left)
2392 rInvalidate.right = rInvalidate.right - (r.right - r.left);
2393 else
2394 rInvalidate.right = clientRect.right - r.left;
2397 TRACE("invalidate (%ld,%ld)-(%ld,%ld)\n",
2398 rInvalidate.left, rInvalidate.top,
2399 rInvalidate.right, rInvalidate.bottom);
2401 InvalidateRect(infoPtr->hwnd, &rInvalidate, TRUE);
2404 static inline LRESULT TAB_Paint (TAB_INFO *infoPtr, HDC hdcPaint)
2406 HDC hdc;
2407 PAINTSTRUCT ps;
2409 if (hdcPaint)
2410 hdc = hdcPaint;
2411 else
2413 hdc = BeginPaint (infoPtr->hwnd, &ps);
2414 TRACE("erase %d, rect=(%ld,%ld)-(%ld,%ld)\n",
2415 ps.fErase,
2416 ps.rcPaint.left,ps.rcPaint.top,ps.rcPaint.right,ps.rcPaint.bottom);
2419 TAB_Refresh (infoPtr, hdc);
2421 if (!hdcPaint)
2422 EndPaint (infoPtr->hwnd, &ps);
2424 return 0;
2427 static LRESULT
2428 TAB_InsertItemT (TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam, BOOL bUnicode)
2430 TAB_ITEM *item;
2431 TCITEMW *pti;
2432 INT iItem;
2433 RECT rect;
2435 GetClientRect (infoPtr->hwnd, &rect);
2436 TRACE("Rect: %p T %li, L %li, B %li, R %li\n", infoPtr->hwnd,
2437 rect.top, rect.left, rect.bottom, rect.right);
2439 pti = (TCITEMW *)lParam;
2440 iItem = (INT)wParam;
2442 if (iItem < 0) return -1;
2443 if (iItem > infoPtr->uNumItem)
2444 iItem = infoPtr->uNumItem;
2446 TAB_DumpItemExternalT(pti, iItem, bUnicode);
2449 if (infoPtr->uNumItem == 0) {
2450 infoPtr->items = Alloc (TAB_ITEM_SIZE(infoPtr));
2451 infoPtr->uNumItem++;
2452 infoPtr->iSelected = 0;
2454 else {
2455 LPBYTE oldItems = (LPBYTE)infoPtr->items;
2457 infoPtr->uNumItem++;
2458 infoPtr->items = Alloc (TAB_ITEM_SIZE(infoPtr) * infoPtr->uNumItem);
2460 /* pre insert copy */
2461 if (iItem > 0) {
2462 memcpy (infoPtr->items, oldItems,
2463 iItem * TAB_ITEM_SIZE(infoPtr));
2466 /* post insert copy */
2467 if (iItem < infoPtr->uNumItem - 1) {
2468 memcpy (TAB_GetItem(infoPtr, iItem + 1),
2469 oldItems + iItem * TAB_ITEM_SIZE(infoPtr),
2470 (infoPtr->uNumItem - iItem - 1) * TAB_ITEM_SIZE(infoPtr));
2474 if (iItem <= infoPtr->iSelected)
2475 infoPtr->iSelected++;
2477 Free (oldItems);
2480 item = TAB_GetItem(infoPtr, iItem);
2482 item->mask = pti->mask;
2483 item->pszText = NULL;
2485 if (pti->mask & TCIF_TEXT)
2487 if (bUnicode)
2488 Str_SetPtrW (&item->pszText, pti->pszText);
2489 else
2490 Str_SetPtrAtoW (&item->pszText, (LPSTR)pti->pszText);
2493 if (pti->mask & TCIF_IMAGE)
2494 item->iImage = pti->iImage;
2495 else
2496 item->iImage = -1;
2498 if (pti->mask & TCIF_PARAM)
2499 memcpy(item->extra, &pti->lParam, infoPtr->cbInfo);
2500 else
2501 memset(item->extra, 0, infoPtr->cbInfo);
2503 TAB_SetItemBounds(infoPtr);
2504 if (infoPtr->uNumItem > 1)
2505 TAB_InvalidateTabArea(infoPtr);
2506 else
2507 InvalidateRect(infoPtr->hwnd, NULL, TRUE);
2509 TRACE("[%p]: added item %d %s\n",
2510 infoPtr->hwnd, iItem, debugstr_w(item->pszText));
2512 return iItem;
2515 static LRESULT
2516 TAB_SetItemSize (TAB_INFO *infoPtr, LPARAM lParam)
2518 LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
2519 LONG lResult = 0;
2520 BOOL bNeedPaint = FALSE;
2522 lResult = MAKELONG(infoPtr->tabWidth, infoPtr->tabHeight);
2524 /* UNDOCUMENTED: If requested Width or Height is 0 this means that program wants to use auto size. */
2525 if (lStyle & TCS_FIXEDWIDTH && (infoPtr->tabWidth != (INT)LOWORD(lParam)))
2527 infoPtr->tabWidth = max((INT)LOWORD(lParam), infoPtr->tabMinWidth);
2528 bNeedPaint = TRUE;
2531 if (infoPtr->tabHeight != (INT)HIWORD(lParam))
2533 if ((infoPtr->fHeightSet = ((INT)HIWORD(lParam) != 0)))
2534 infoPtr->tabHeight = (INT)HIWORD(lParam);
2536 bNeedPaint = TRUE;
2538 TRACE("was h=%d,w=%d, now h=%d,w=%d\n",
2539 HIWORD(lResult), LOWORD(lResult),
2540 infoPtr->tabHeight, infoPtr->tabWidth);
2542 if (bNeedPaint)
2544 TAB_SetItemBounds(infoPtr);
2545 RedrawWindow(infoPtr->hwnd, NULL, NULL, RDW_ERASE | RDW_INVALIDATE | RDW_UPDATENOW);
2548 return lResult;
2551 static inline LRESULT TAB_SetMinTabWidth (TAB_INFO *infoPtr, INT cx)
2553 INT oldcx = 0;
2555 TRACE("(%p,%d)\n", infoPtr, cx);
2557 if (infoPtr) {
2558 oldcx = infoPtr->tabMinWidth;
2559 infoPtr->tabMinWidth = (cx==-1)?DEFAULT_TAB_WIDTH:cx;
2562 return oldcx;
2565 static inline LRESULT
2566 TAB_HighlightItem (TAB_INFO *infoPtr, INT iItem, BOOL fHighlight)
2568 LPDWORD lpState;
2570 TRACE("(%p,%d,%s)\n", infoPtr, iItem, fHighlight ? "true" : "false");
2572 if (!infoPtr || iItem < 0 || iItem >= infoPtr->uNumItem)
2573 return FALSE;
2575 lpState = &TAB_GetItem(infoPtr, iItem)->dwState;
2577 if (fHighlight)
2578 *lpState |= TCIS_HIGHLIGHTED;
2579 else
2580 *lpState &= ~TCIS_HIGHLIGHTED;
2582 return TRUE;
2585 static LRESULT
2586 TAB_SetItemT (TAB_INFO *infoPtr, INT iItem, LPTCITEMW tabItem, BOOL bUnicode)
2588 TAB_ITEM *wineItem;
2590 TRACE("(%p,%d,%p,%s)\n", infoPtr, iItem, tabItem, bUnicode ? "true" : "false");
2592 if (iItem < 0 || iItem >= infoPtr->uNumItem)
2593 return FALSE;
2595 TAB_DumpItemExternalT(tabItem, iItem, bUnicode);
2597 wineItem = TAB_GetItem(infoPtr, iItem);
2599 if (tabItem->mask & TCIF_IMAGE)
2600 wineItem->iImage = tabItem->iImage;
2602 if (tabItem->mask & TCIF_PARAM)
2603 memcpy(wineItem->extra, &tabItem->lParam, infoPtr->cbInfo);
2605 if (tabItem->mask & TCIF_RTLREADING)
2606 FIXME("TCIF_RTLREADING\n");
2608 if (tabItem->mask & TCIF_STATE)
2609 wineItem->dwState = tabItem->dwState;
2611 if (tabItem->mask & TCIF_TEXT)
2613 if (wineItem->pszText)
2615 Free(wineItem->pszText);
2616 wineItem->pszText = NULL;
2618 if (bUnicode)
2619 Str_SetPtrW(&wineItem->pszText, tabItem->pszText);
2620 else
2621 Str_SetPtrAtoW(&wineItem->pszText, (LPSTR)tabItem->pszText);
2624 /* Update and repaint tabs */
2625 TAB_SetItemBounds(infoPtr);
2626 TAB_InvalidateTabArea(infoPtr);
2628 return TRUE;
2631 static inline LRESULT TAB_GetItemCount (const TAB_INFO *infoPtr)
2633 return infoPtr->uNumItem;
2637 static LRESULT
2638 TAB_GetItemT (TAB_INFO *infoPtr, INT iItem, LPTCITEMW tabItem, BOOL bUnicode)
2640 TAB_ITEM *wineItem;
2642 TRACE("(%p,%d,%p,%s)\n", infoPtr, iItem, tabItem, bUnicode ? "true" : "false");
2644 if (iItem < 0 || iItem >= infoPtr->uNumItem)
2645 return FALSE;
2647 wineItem = TAB_GetItem(infoPtr, iItem);
2649 if (tabItem->mask & TCIF_IMAGE)
2650 tabItem->iImage = wineItem->iImage;
2652 if (tabItem->mask & TCIF_PARAM)
2653 memcpy(&tabItem->lParam, wineItem->extra, infoPtr->cbInfo);
2655 if (tabItem->mask & TCIF_RTLREADING)
2656 FIXME("TCIF_RTLREADING\n");
2658 if (tabItem->mask & TCIF_STATE)
2659 tabItem->dwState = wineItem->dwState;
2661 if (tabItem->mask & TCIF_TEXT)
2663 if (bUnicode)
2664 Str_GetPtrW (wineItem->pszText, tabItem->pszText, tabItem->cchTextMax);
2665 else
2666 Str_GetPtrWtoA (wineItem->pszText, (LPSTR)tabItem->pszText, tabItem->cchTextMax);
2669 TAB_DumpItemExternalT(tabItem, iItem, bUnicode);
2671 return TRUE;
2675 static LRESULT TAB_DeleteItem (TAB_INFO *infoPtr, INT iItem)
2677 BOOL bResult = FALSE;
2679 TRACE("(%p, %d)\n", infoPtr, iItem);
2681 if ((iItem >= 0) && (iItem < infoPtr->uNumItem))
2683 TAB_ITEM *item = TAB_GetItem(infoPtr, iItem);
2684 LPBYTE oldItems = (LPBYTE)infoPtr->items;
2686 TAB_InvalidateTabArea(infoPtr);
2688 if ((item->mask & TCIF_TEXT) && item->pszText)
2689 Free(item->pszText);
2691 infoPtr->uNumItem--;
2693 if (!infoPtr->uNumItem)
2695 infoPtr->items = NULL;
2696 if (infoPtr->iHotTracked >= 0)
2698 KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
2699 infoPtr->iHotTracked = -1;
2702 else
2704 infoPtr->items = Alloc(TAB_ITEM_SIZE(infoPtr) * infoPtr->uNumItem);
2706 if (iItem > 0)
2707 memcpy(infoPtr->items, oldItems, iItem * TAB_ITEM_SIZE(infoPtr));
2709 if (iItem < infoPtr->uNumItem)
2710 memcpy(TAB_GetItem(infoPtr, iItem),
2711 oldItems + (iItem + 1) * TAB_ITEM_SIZE(infoPtr),
2712 (infoPtr->uNumItem - iItem) * TAB_ITEM_SIZE(infoPtr));
2714 if (iItem <= infoPtr->iHotTracked)
2716 /* When tabs move left/up, the hot track item may change */
2717 FIXME("Recalc hot track");
2720 Free(oldItems);
2722 /* Readjust the selected index */
2723 if ((iItem == infoPtr->iSelected) && (iItem > 0))
2724 infoPtr->iSelected--;
2726 if (iItem < infoPtr->iSelected)
2727 infoPtr->iSelected--;
2729 if (infoPtr->uNumItem == 0)
2730 infoPtr->iSelected = -1;
2732 /* Reposition and repaint tabs */
2733 TAB_SetItemBounds(infoPtr);
2735 bResult = TRUE;
2738 return bResult;
2741 static inline LRESULT TAB_DeleteAllItems (TAB_INFO *infoPtr)
2743 TRACE("(%p)\n", infoPtr);
2744 while (infoPtr->uNumItem)
2745 TAB_DeleteItem (infoPtr, 0);
2746 return TRUE;
2750 static inline LRESULT TAB_GetFont (const TAB_INFO *infoPtr)
2752 TRACE("(%p) returning %p\n", infoPtr, infoPtr->hFont);
2753 return (LRESULT)infoPtr->hFont;
2756 static inline LRESULT TAB_SetFont (TAB_INFO *infoPtr, HFONT hNewFont)
2758 TRACE("(%p,%p)\n", infoPtr, hNewFont);
2760 infoPtr->hFont = hNewFont;
2762 TAB_SetItemBounds(infoPtr);
2764 TAB_InvalidateTabArea(infoPtr);
2766 return 0;
2770 static inline LRESULT TAB_GetImageList (const TAB_INFO *infoPtr)
2772 TRACE("\n");
2773 return (LRESULT)infoPtr->himl;
2776 static inline LRESULT TAB_SetImageList (TAB_INFO *infoPtr, HIMAGELIST himlNew)
2778 HIMAGELIST himlPrev = infoPtr->himl;
2779 TRACE("\n");
2780 infoPtr->himl = himlNew;
2781 return (LRESULT)himlPrev;
2784 static inline LRESULT TAB_GetUnicodeFormat (const TAB_INFO *infoPtr)
2786 return infoPtr->bUnicode;
2789 static inline LRESULT TAB_SetUnicodeFormat (TAB_INFO *infoPtr, BOOL bUnicode)
2791 BOOL bTemp = infoPtr->bUnicode;
2793 infoPtr->bUnicode = bUnicode;
2795 return bTemp;
2798 static inline LRESULT TAB_Size (TAB_INFO *infoPtr)
2800 /* I'm not really sure what the following code was meant to do.
2801 This is what it is doing:
2802 When WM_SIZE is sent with SIZE_RESTORED, the control
2803 gets positioned in the top left corner.
2805 RECT parent_rect;
2806 HWND parent;
2807 UINT uPosFlags,cx,cy;
2809 uPosFlags=0;
2810 if (!wParam) {
2811 parent = GetParent (hwnd);
2812 GetClientRect(parent, &parent_rect);
2813 cx=LOWORD (lParam);
2814 cy=HIWORD (lParam);
2815 if (GetWindowLongW(hwnd, GWL_STYLE) & CCS_NORESIZE)
2816 uPosFlags |= (SWP_NOSIZE | SWP_NOMOVE);
2818 SetWindowPos (hwnd, 0, parent_rect.left, parent_rect.top,
2819 cx, cy, uPosFlags | SWP_NOZORDER);
2820 } else {
2821 FIXME("WM_SIZE flag %x %lx not handled\n", wParam, lParam);
2822 } */
2824 /* Recompute the size/position of the tabs. */
2825 TAB_SetItemBounds (infoPtr);
2827 /* Force a repaint of the control. */
2828 InvalidateRect(infoPtr->hwnd, NULL, TRUE);
2830 return 0;
2834 static LRESULT TAB_Create (HWND hwnd, WPARAM wParam, LPARAM lParam)
2836 TAB_INFO *infoPtr;
2837 TEXTMETRICW fontMetrics;
2838 HDC hdc;
2839 HFONT hOldFont;
2840 DWORD dwStyle;
2842 infoPtr = (TAB_INFO *)Alloc (sizeof(TAB_INFO));
2844 SetWindowLongPtrW(hwnd, 0, (DWORD_PTR)infoPtr);
2846 infoPtr->hwnd = hwnd;
2847 infoPtr->hwndNotify = ((LPCREATESTRUCTW)lParam)->hwndParent;
2848 infoPtr->uNumItem = 0;
2849 infoPtr->uNumRows = 0;
2850 infoPtr->uHItemPadding = 6;
2851 infoPtr->uVItemPadding = 3;
2852 infoPtr->uHItemPadding_s = 6;
2853 infoPtr->uVItemPadding_s = 3;
2854 infoPtr->hFont = 0;
2855 infoPtr->items = 0;
2856 infoPtr->hcurArrow = LoadCursorW (0, (LPWSTR)IDC_ARROW);
2857 infoPtr->iSelected = -1;
2858 infoPtr->iHotTracked = -1;
2859 infoPtr->uFocus = -1;
2860 infoPtr->hwndToolTip = 0;
2861 infoPtr->DoRedraw = TRUE;
2862 infoPtr->needsScrolling = FALSE;
2863 infoPtr->hwndUpDown = 0;
2864 infoPtr->leftmostVisible = 0;
2865 infoPtr->fHeightSet = FALSE;
2866 infoPtr->bUnicode = IsWindowUnicode (hwnd);
2867 infoPtr->cbInfo = sizeof(LPARAM);
2869 TRACE("Created tab control, hwnd [%p]\n", hwnd);
2871 /* The tab control always has the WS_CLIPSIBLINGS style. Even
2872 if you don't specify it in CreateWindow. This is necessary in
2873 order for paint to work correctly. This follows windows behaviour. */
2874 dwStyle = GetWindowLongW(hwnd, GWL_STYLE);
2875 SetWindowLongW(hwnd, GWL_STYLE, dwStyle|WS_CLIPSIBLINGS);
2877 if (dwStyle & TCS_TOOLTIPS) {
2878 /* Create tooltip control */
2879 infoPtr->hwndToolTip =
2880 CreateWindowExW (0, TOOLTIPS_CLASSW, NULL, 0,
2881 CW_USEDEFAULT, CW_USEDEFAULT,
2882 CW_USEDEFAULT, CW_USEDEFAULT,
2883 hwnd, 0, 0, 0);
2885 /* Send NM_TOOLTIPSCREATED notification */
2886 if (infoPtr->hwndToolTip) {
2887 NMTOOLTIPSCREATED nmttc;
2889 nmttc.hdr.hwndFrom = hwnd;
2890 nmttc.hdr.idFrom = GetWindowLongPtrW(hwnd, GWLP_ID);
2891 nmttc.hdr.code = NM_TOOLTIPSCREATED;
2892 nmttc.hwndToolTips = infoPtr->hwndToolTip;
2894 SendMessageW (infoPtr->hwndNotify, WM_NOTIFY,
2895 (WPARAM)GetWindowLongPtrW(hwnd, GWLP_ID), (LPARAM)&nmttc);
2900 * We need to get text information so we need a DC and we need to select
2901 * a font.
2903 hdc = GetDC(hwnd);
2904 hOldFont = SelectObject (hdc, GetStockObject (SYSTEM_FONT));
2906 /* Use the system font to determine the initial height of a tab. */
2907 GetTextMetricsW(hdc, &fontMetrics);
2910 * Make sure there is enough space for the letters + growing the
2911 * selected item + extra space for the selected item.
2913 infoPtr->tabHeight = fontMetrics.tmHeight + SELECTED_TAB_OFFSET +
2914 ((dwStyle & TCS_BUTTONS) ? 2 : 1) *
2915 infoPtr->uVItemPadding;
2917 /* Initialize the width of a tab. */
2918 infoPtr->tabWidth = DEFAULT_TAB_WIDTH;
2919 infoPtr->tabMinWidth = 0;
2921 TRACE("tabH=%d, tabW=%d\n", infoPtr->tabHeight, infoPtr->tabWidth);
2923 SelectObject (hdc, hOldFont);
2924 ReleaseDC(hwnd, hdc);
2926 return 0;
2929 static LRESULT
2930 TAB_Destroy (TAB_INFO *infoPtr)
2932 UINT iItem;
2934 if (!infoPtr)
2935 return 0;
2937 SetWindowLongPtrW(infoPtr->hwnd, 0, 0);
2939 if (infoPtr->items) {
2940 for (iItem = 0; iItem < infoPtr->uNumItem; iItem++) {
2941 if (TAB_GetItem(infoPtr, iItem)->pszText)
2942 Free (TAB_GetItem(infoPtr, iItem)->pszText);
2944 Free (infoPtr->items);
2947 if (infoPtr->hwndToolTip)
2948 DestroyWindow (infoPtr->hwndToolTip);
2950 if (infoPtr->hwndUpDown)
2951 DestroyWindow(infoPtr->hwndUpDown);
2953 if (infoPtr->iHotTracked >= 0)
2954 KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
2956 Free (infoPtr);
2957 return 0;
2960 static inline LRESULT
2961 TAB_SetItemExtra (TAB_INFO *infoPtr, INT cbInfo)
2963 if (!infoPtr || cbInfo <= 0)
2964 return FALSE;
2966 if (infoPtr->uNumItem)
2968 /* FIXME: MSDN says this is not allowed, but this hasn't been verified */
2969 return FALSE;
2972 infoPtr->cbInfo = cbInfo;
2973 return TRUE;
2976 static LRESULT WINAPI
2977 TAB_WindowProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
2979 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2981 TRACE("hwnd=%p msg=%x wParam=%x lParam=%lx\n", hwnd, uMsg, wParam, lParam);
2982 if (!infoPtr && (uMsg != WM_CREATE))
2983 return DefWindowProcW (hwnd, uMsg, wParam, lParam);
2985 switch (uMsg)
2987 case TCM_GETIMAGELIST:
2988 return TAB_GetImageList (infoPtr);
2990 case TCM_SETIMAGELIST:
2991 return TAB_SetImageList (infoPtr, (HIMAGELIST)lParam);
2993 case TCM_GETITEMCOUNT:
2994 return TAB_GetItemCount (infoPtr);
2996 case TCM_GETITEMA:
2997 case TCM_GETITEMW:
2998 return TAB_GetItemT (infoPtr, (INT)wParam, (LPTCITEMW)lParam, uMsg == TCM_GETITEMW);
3000 case TCM_SETITEMA:
3001 case TCM_SETITEMW:
3002 return TAB_SetItemT (infoPtr, (INT)wParam, (LPTCITEMW)lParam, uMsg == TCM_SETITEMW);
3004 case TCM_DELETEITEM:
3005 return TAB_DeleteItem (infoPtr, (INT)wParam);
3007 case TCM_DELETEALLITEMS:
3008 return TAB_DeleteAllItems (infoPtr);
3010 case TCM_GETITEMRECT:
3011 return TAB_GetItemRect (infoPtr, wParam, lParam);
3013 case TCM_GETCURSEL:
3014 return TAB_GetCurSel (infoPtr);
3016 case TCM_HITTEST:
3017 return TAB_HitTest (infoPtr, (LPTCHITTESTINFO)lParam);
3019 case TCM_SETCURSEL:
3020 return TAB_SetCurSel (infoPtr, (INT)wParam);
3022 case TCM_INSERTITEMA:
3023 case TCM_INSERTITEMW:
3024 return TAB_InsertItemT (infoPtr, wParam, lParam, uMsg == TCM_INSERTITEMW);
3026 case TCM_SETITEMEXTRA:
3027 return TAB_SetItemExtra (infoPtr, (int)wParam);
3029 case TCM_ADJUSTRECT:
3030 return TAB_AdjustRect (infoPtr, (BOOL)wParam, (LPRECT)lParam);
3032 case TCM_SETITEMSIZE:
3033 return TAB_SetItemSize (infoPtr, lParam);
3035 case TCM_REMOVEIMAGE:
3036 FIXME("Unimplemented msg TCM_REMOVEIMAGE\n");
3037 return 0;
3039 case TCM_SETPADDING:
3040 return TAB_SetPadding (infoPtr, lParam);
3042 case TCM_GETROWCOUNT:
3043 return TAB_GetRowCount(infoPtr);
3045 case TCM_GETUNICODEFORMAT:
3046 return TAB_GetUnicodeFormat (infoPtr);
3048 case TCM_SETUNICODEFORMAT:
3049 return TAB_SetUnicodeFormat (infoPtr, (BOOL)wParam);
3051 case TCM_HIGHLIGHTITEM:
3052 return TAB_HighlightItem (infoPtr, (INT)wParam, (BOOL)LOWORD(lParam));
3054 case TCM_GETTOOLTIPS:
3055 return TAB_GetToolTips (infoPtr);
3057 case TCM_SETTOOLTIPS:
3058 return TAB_SetToolTips (infoPtr, (HWND)wParam);
3060 case TCM_GETCURFOCUS:
3061 return TAB_GetCurFocus (infoPtr);
3063 case TCM_SETCURFOCUS:
3064 return TAB_SetCurFocus (infoPtr, (INT)wParam);
3066 case TCM_SETMINTABWIDTH:
3067 return TAB_SetMinTabWidth(infoPtr, (INT)lParam);
3069 case TCM_DESELECTALL:
3070 FIXME("Unimplemented msg TCM_DESELECTALL\n");
3071 return 0;
3073 case TCM_GETEXTENDEDSTYLE:
3074 FIXME("Unimplemented msg TCM_GETEXTENDEDSTYLE\n");
3075 return 0;
3077 case TCM_SETEXTENDEDSTYLE:
3078 FIXME("Unimplemented msg TCM_SETEXTENDEDSTYLE\n");
3079 return 0;
3081 case WM_GETFONT:
3082 return TAB_GetFont (infoPtr);
3084 case WM_SETFONT:
3085 return TAB_SetFont (infoPtr, (HFONT)wParam);
3087 case WM_CREATE:
3088 return TAB_Create (hwnd, wParam, lParam);
3090 case WM_NCDESTROY:
3091 return TAB_Destroy (infoPtr);
3093 case WM_GETDLGCODE:
3094 return DLGC_WANTARROWS | DLGC_WANTCHARS;
3096 case WM_LBUTTONDOWN:
3097 return TAB_LButtonDown (infoPtr, wParam, lParam);
3099 case WM_LBUTTONUP:
3100 return TAB_LButtonUp (infoPtr);
3102 case WM_NOTIFY:
3103 return SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, wParam, lParam);
3105 case WM_RBUTTONDOWN:
3106 return TAB_RButtonDown (infoPtr);
3108 case WM_MOUSEMOVE:
3109 return TAB_MouseMove (infoPtr, wParam, lParam);
3111 case WM_PAINT:
3112 return TAB_Paint (infoPtr, (HDC)wParam);
3114 case WM_SIZE:
3115 return TAB_Size (infoPtr);
3117 case WM_SETREDRAW:
3118 return TAB_SetRedraw (infoPtr, (BOOL)wParam);
3120 case WM_HSCROLL:
3121 return TAB_OnHScroll(infoPtr, (int)LOWORD(wParam), (int)HIWORD(wParam), (HWND)lParam);
3123 case WM_STYLECHANGED:
3124 TAB_SetItemBounds (infoPtr);
3125 InvalidateRect(hwnd, NULL, TRUE);
3126 return 0;
3128 case WM_SYSCOLORCHANGE:
3129 COMCTL32_RefreshSysColors();
3130 return 0;
3132 case WM_KILLFOCUS:
3133 case WM_SETFOCUS:
3134 TAB_FocusChanging(infoPtr);
3135 break; /* Don't disturb normal focus behavior */
3137 case WM_KEYUP:
3138 return TAB_KeyUp(infoPtr, wParam);
3139 case WM_NCHITTEST:
3140 return TAB_NCHitTest(infoPtr, lParam);
3142 default:
3143 if (uMsg >= WM_USER && uMsg < WM_APP)
3144 WARN("unknown msg %04x wp=%08x lp=%08lx\n",
3145 uMsg, wParam, lParam);
3146 break;
3148 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
3152 void
3153 TAB_Register (void)
3155 WNDCLASSW wndClass;
3157 ZeroMemory (&wndClass, sizeof(WNDCLASSW));
3158 wndClass.style = CS_GLOBALCLASS | CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
3159 wndClass.lpfnWndProc = TAB_WindowProc;
3160 wndClass.cbClsExtra = 0;
3161 wndClass.cbWndExtra = sizeof(TAB_INFO *);
3162 wndClass.hCursor = LoadCursorW (0, (LPWSTR)IDC_ARROW);
3163 wndClass.hbrBackground = (HBRUSH)(COLOR_BTNFACE+1);
3164 wndClass.lpszClassName = WC_TABCONTROLW;
3166 RegisterClassW (&wndClass);
3170 void
3171 TAB_Unregister (void)
3173 UnregisterClassW (WC_TABCONTROLW, NULL);