Draw only visible tabs.
[wine/multimedia.git] / dlls / comctl32 / tab.c
blob14d426724f13626edbc30705760c0f80ef34d4f1
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 /* Check for visibility */
408 if (lStyle & TCS_VERTICAL)
409 return (itemRect->top < clientRect.bottom) && (itemRect->bottom > clientRect.top);
410 else
411 return (itemRect->left < clientRect.right) && (itemRect->right > clientRect.left);
414 static inline BOOL
415 TAB_GetItemRect(TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
417 return TAB_InternalGetItemRect(infoPtr, (INT)wParam, (LPRECT)lParam, (LPRECT)NULL);
420 /******************************************************************************
421 * TAB_KeyUp
423 * This method is called to handle keyboard input
425 static LRESULT TAB_KeyUp(TAB_INFO* infoPtr, WPARAM keyCode)
427 int newItem = -1;
429 switch (keyCode)
431 case VK_LEFT:
432 newItem = infoPtr->uFocus - 1;
433 break;
434 case VK_RIGHT:
435 newItem = infoPtr->uFocus + 1;
436 break;
440 * If we changed to a valid item, change the selection
442 if (newItem >= 0 &&
443 newItem < infoPtr->uNumItem &&
444 infoPtr->uFocus != newItem)
446 if (!TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGING))
448 infoPtr->iSelected = newItem;
449 infoPtr->uFocus = newItem;
450 TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGE);
452 TAB_EnsureSelectionVisible(infoPtr);
453 TAB_InvalidateTabArea(infoPtr);
457 return 0;
460 /******************************************************************************
461 * TAB_FocusChanging
463 * This method is called whenever the focus goes in or out of this control
464 * it is used to update the visual state of the control.
466 static void TAB_FocusChanging(const TAB_INFO *infoPtr)
468 RECT selectedRect;
469 BOOL isVisible;
472 * Get the rectangle for the item.
474 isVisible = TAB_InternalGetItemRect(infoPtr,
475 infoPtr->uFocus,
476 NULL,
477 &selectedRect);
480 * If the rectangle is not completely invisible, invalidate that
481 * portion of the window.
483 if (isVisible)
485 TRACE("invalidate (%ld,%ld)-(%ld,%ld)\n",
486 selectedRect.left,selectedRect.top,
487 selectedRect.right,selectedRect.bottom);
488 InvalidateRect(infoPtr->hwnd, &selectedRect, TRUE);
492 static INT TAB_InternalHitTest (
493 TAB_INFO* infoPtr,
494 POINT pt,
495 UINT* flags)
498 RECT rect;
499 INT iCount;
501 for (iCount = 0; iCount < infoPtr->uNumItem; iCount++)
503 TAB_InternalGetItemRect(infoPtr, iCount, &rect, NULL);
505 if (PtInRect(&rect, pt))
507 *flags = TCHT_ONITEM;
508 return iCount;
512 *flags = TCHT_NOWHERE;
513 return -1;
516 static inline LRESULT
517 TAB_HitTest (TAB_INFO *infoPtr, LPTCHITTESTINFO lptest)
519 return TAB_InternalHitTest (infoPtr, lptest->pt, &lptest->flags);
522 /******************************************************************************
523 * TAB_NCHitTest
525 * Napster v2b5 has a tab control for its main navigation which has a client
526 * area that covers the whole area of the dialog pages.
527 * That's why it receives all msgs for that area and the underlying dialog ctrls
528 * are dead.
529 * So I decided that we should handle WM_NCHITTEST here and return
530 * HTTRANSPARENT if we don't hit the tab control buttons.
531 * FIXME: WM_NCHITTEST handling correct ? Fix it if you know that Windows
532 * doesn't do it that way. Maybe depends on tab control styles ?
534 static inline LRESULT
535 TAB_NCHitTest (TAB_INFO *infoPtr, LPARAM lParam)
537 POINT pt;
538 UINT dummyflag;
540 pt.x = LOWORD(lParam);
541 pt.y = HIWORD(lParam);
542 ScreenToClient(infoPtr->hwnd, &pt);
544 if (TAB_InternalHitTest(infoPtr, pt, &dummyflag) == -1)
545 return HTTRANSPARENT;
546 else
547 return HTCLIENT;
550 static LRESULT
551 TAB_LButtonDown (TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
553 POINT pt;
554 INT newItem, dummy;
556 if (infoPtr->hwndToolTip)
557 TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
558 WM_LBUTTONDOWN, wParam, lParam);
560 if (GetWindowLongW(infoPtr->hwnd, GWL_STYLE) & TCS_FOCUSONBUTTONDOWN ) {
561 SetFocus (infoPtr->hwnd);
564 if (infoPtr->hwndToolTip)
565 TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
566 WM_LBUTTONDOWN, wParam, lParam);
568 pt.x = (INT)LOWORD(lParam);
569 pt.y = (INT)HIWORD(lParam);
571 newItem = TAB_InternalHitTest (infoPtr, pt, &dummy);
573 TRACE("On Tab, item %d\n", newItem);
575 if (newItem != -1 && infoPtr->iSelected != newItem)
577 if (!TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGING))
579 infoPtr->iSelected = newItem;
580 infoPtr->uFocus = newItem;
581 TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGE);
583 TAB_EnsureSelectionVisible(infoPtr);
585 TAB_InvalidateTabArea(infoPtr);
588 return 0;
591 static inline LRESULT
592 TAB_LButtonUp (const TAB_INFO *infoPtr)
594 TAB_SendSimpleNotify(infoPtr, NM_CLICK);
596 return 0;
599 static inline LRESULT
600 TAB_RButtonDown (const TAB_INFO *infoPtr)
602 TAB_SendSimpleNotify(infoPtr, NM_RCLICK);
603 return 0;
606 /******************************************************************************
607 * TAB_DrawLoneItemInterior
609 * This calls TAB_DrawItemInterior. However, TAB_DrawItemInterior is normally
610 * called by TAB_DrawItem which is normally called by TAB_Refresh which sets
611 * up the device context and font. This routine does the same setup but
612 * only calls TAB_DrawItemInterior for the single specified item.
614 static void
615 TAB_DrawLoneItemInterior(TAB_INFO* infoPtr, int iItem)
617 HDC hdc = GetDC(infoPtr->hwnd);
618 RECT r, rC;
620 /* Clip UpDown control to not draw over it */
621 if (infoPtr->needsScrolling)
623 GetWindowRect(infoPtr->hwnd, &rC);
624 GetWindowRect(infoPtr->hwndUpDown, &r);
625 ExcludeClipRect(hdc, r.left - rC.left, r.top - rC.top, r.right - rC.left, r.bottom - rC.top);
627 TAB_DrawItemInterior(infoPtr, hdc, iItem, NULL);
628 ReleaseDC(infoPtr->hwnd, hdc);
631 /******************************************************************************
632 * TAB_HotTrackTimerProc
634 * When a mouse-move event causes a tab to be highlighted (hot-tracking), a
635 * timer is setup so we can check if the mouse is moved out of our window.
636 * (We don't get an event when the mouse leaves, the mouse-move events just
637 * stop being delivered to our window and just start being delivered to
638 * another window.) This function is called when the timer triggers so
639 * we can check if the mouse has left our window. If so, we un-highlight
640 * the hot-tracked tab.
642 static void CALLBACK
643 TAB_HotTrackTimerProc
645 HWND hwnd, /* handle of window for timer messages */
646 UINT uMsg, /* WM_TIMER message */
647 UINT idEvent, /* timer identifier */
648 DWORD dwTime /* current system time */
651 TAB_INFO* infoPtr = TAB_GetInfoPtr(hwnd);
653 if (infoPtr != NULL && infoPtr->iHotTracked >= 0)
655 POINT pt;
658 ** If we can't get the cursor position, or if the cursor is outside our
659 ** window, we un-highlight the hot-tracked tab. Note that the cursor is
660 ** "outside" even if it is within our bounding rect if another window
661 ** overlaps. Note also that the case where the cursor stayed within our
662 ** window but has moved off the hot-tracked tab will be handled by the
663 ** WM_MOUSEMOVE event.
665 if (!GetCursorPos(&pt) || WindowFromPoint(pt) != hwnd)
667 /* Redraw iHotTracked to look normal */
668 INT iRedraw = infoPtr->iHotTracked;
669 infoPtr->iHotTracked = -1;
670 TAB_DrawLoneItemInterior(infoPtr, iRedraw);
672 /* Kill this timer */
673 KillTimer(hwnd, TAB_HOTTRACK_TIMER);
678 /******************************************************************************
679 * TAB_RecalcHotTrack
681 * If a tab control has the TCS_HOTTRACK style, then the tab under the mouse
682 * should be highlighted. This function determines which tab in a tab control,
683 * if any, is under the mouse and records that information. The caller may
684 * supply output parameters to receive the item number of the tab item which
685 * was highlighted but isn't any longer and of the tab item which is now
686 * highlighted but wasn't previously. The caller can use this information to
687 * selectively redraw those tab items.
689 * If the caller has a mouse position, it can supply it through the pos
690 * parameter. For example, TAB_MouseMove does this. Otherwise, the caller
691 * supplies NULL and this function determines the current mouse position
692 * itself.
694 static void
695 TAB_RecalcHotTrack
697 TAB_INFO* infoPtr,
698 const LPARAM* pos,
699 int* out_redrawLeave,
700 int* out_redrawEnter
703 int item = -1;
706 if (out_redrawLeave != NULL)
707 *out_redrawLeave = -1;
708 if (out_redrawEnter != NULL)
709 *out_redrawEnter = -1;
711 if (GetWindowLongW(infoPtr->hwnd, GWL_STYLE) & TCS_HOTTRACK)
713 POINT pt;
714 UINT flags;
716 if (pos == NULL)
718 GetCursorPos(&pt);
719 ScreenToClient(infoPtr->hwnd, &pt);
721 else
723 pt.x = LOWORD(*pos);
724 pt.y = HIWORD(*pos);
727 item = TAB_InternalHitTest(infoPtr, pt, &flags);
730 if (item != infoPtr->iHotTracked)
732 if (infoPtr->iHotTracked >= 0)
734 /* Mark currently hot-tracked to be redrawn to look normal */
735 if (out_redrawLeave != NULL)
736 *out_redrawLeave = infoPtr->iHotTracked;
738 if (item < 0)
740 /* Kill timer which forces recheck of mouse pos */
741 KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
744 else
746 /* Start timer so we recheck mouse pos */
747 UINT timerID = SetTimer
749 infoPtr->hwnd,
750 TAB_HOTTRACK_TIMER,
751 TAB_HOTTRACK_TIMER_INTERVAL,
752 TAB_HotTrackTimerProc
755 if (timerID == 0)
756 return; /* Hot tracking not available */
759 infoPtr->iHotTracked = item;
761 if (item >= 0)
763 /* Mark new hot-tracked to be redrawn to look highlighted */
764 if (out_redrawEnter != NULL)
765 *out_redrawEnter = item;
770 /******************************************************************************
771 * TAB_MouseMove
773 * Handles the mouse-move event. Updates tooltips. Updates hot-tracking.
775 static LRESULT
776 TAB_MouseMove (TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
778 int redrawLeave;
779 int redrawEnter;
781 if (infoPtr->hwndToolTip)
782 TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
783 WM_LBUTTONDOWN, wParam, lParam);
785 /* Determine which tab to highlight. Redraw tabs which change highlight
786 ** status. */
787 TAB_RecalcHotTrack(infoPtr, &lParam, &redrawLeave, &redrawEnter);
789 if (redrawLeave != -1)
790 TAB_DrawLoneItemInterior(infoPtr, redrawLeave);
791 if (redrawEnter != -1)
792 TAB_DrawLoneItemInterior(infoPtr, redrawEnter);
794 return 0;
797 /******************************************************************************
798 * TAB_AdjustRect
800 * Calculates the tab control's display area given the window rectangle or
801 * the window rectangle given the requested display rectangle.
803 static LRESULT TAB_AdjustRect(
804 TAB_INFO *infoPtr,
805 WPARAM fLarger,
806 LPRECT prc)
808 DWORD lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
809 LONG *iRightBottom, *iLeftTop;
811 TRACE ("hwnd=%p fLarger=%d (%ld,%ld)-(%ld,%ld)\n", infoPtr->hwnd, fLarger, prc->left, prc->top, prc->right, prc->bottom);
813 if(lStyle & TCS_VERTICAL)
815 iRightBottom = &(prc->right);
816 iLeftTop = &(prc->left);
818 else
820 iRightBottom = &(prc->bottom);
821 iLeftTop = &(prc->top);
824 if (fLarger) /* Go from display rectangle */
826 /* Add the height of the tabs. */
827 if (lStyle & TCS_BOTTOM)
828 *iRightBottom += infoPtr->tabHeight * infoPtr->uNumRows;
829 else
830 *iLeftTop -= infoPtr->tabHeight * infoPtr->uNumRows +
831 ((lStyle & TCS_BUTTONS)? 3 * (infoPtr->uNumRows - 1) : 0);
833 /* Inflate the rectangle for the padding */
834 InflateRect(prc, DISPLAY_AREA_PADDINGX, DISPLAY_AREA_PADDINGY);
836 /* Inflate for the border */
837 InflateRect(prc, CONTROL_BORDER_SIZEX, CONTROL_BORDER_SIZEY);
839 else /* Go from window rectangle. */
841 /* Deflate the rectangle for the border */
842 InflateRect(prc, -CONTROL_BORDER_SIZEX, -CONTROL_BORDER_SIZEY);
844 /* Deflate the rectangle for the padding */
845 InflateRect(prc, -DISPLAY_AREA_PADDINGX, -DISPLAY_AREA_PADDINGY);
847 /* Remove the height of the tabs. */
848 if (lStyle & TCS_BOTTOM)
849 *iRightBottom -= infoPtr->tabHeight * infoPtr->uNumRows;
850 else
851 *iLeftTop += (infoPtr->tabHeight) * infoPtr->uNumRows +
852 ((lStyle & TCS_BUTTONS)? 3 * (infoPtr->uNumRows - 1) : 0);
855 return 0;
858 /******************************************************************************
859 * TAB_OnHScroll
861 * This method will handle the notification from the scroll control and
862 * perform the scrolling operation on the tab control.
864 static LRESULT TAB_OnHScroll(
865 TAB_INFO *infoPtr,
866 int nScrollCode,
867 int nPos,
868 HWND hwndScroll)
870 if(nScrollCode == SB_THUMBPOSITION && nPos != infoPtr->leftmostVisible)
872 if(nPos < infoPtr->leftmostVisible)
873 infoPtr->leftmostVisible--;
874 else
875 infoPtr->leftmostVisible++;
877 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
878 TAB_InvalidateTabArea(infoPtr);
879 SendMessageW(infoPtr->hwndUpDown, UDM_SETPOS, 0,
880 MAKELONG(infoPtr->leftmostVisible, 0));
883 return 0;
886 /******************************************************************************
887 * TAB_SetupScrolling
889 * This method will check the current scrolling state and make sure the
890 * scrolling control is displayed (or not).
892 static void TAB_SetupScrolling(
893 HWND hwnd,
894 TAB_INFO* infoPtr,
895 const RECT* clientRect)
897 static const WCHAR msctls_updown32W[] = { 'm','s','c','t','l','s','_','u','p','d','o','w','n','3','2',0 };
898 static const WCHAR emptyW[] = { 0 };
899 INT maxRange = 0;
900 DWORD lStyle = GetWindowLongW(hwnd, GWL_STYLE);
902 if (infoPtr->needsScrolling)
904 RECT controlPos;
905 INT vsize, tabwidth;
908 * Calculate the position of the scroll control.
910 if(lStyle & TCS_VERTICAL)
912 controlPos.right = clientRect->right;
913 controlPos.left = controlPos.right - 2 * GetSystemMetrics(SM_CXHSCROLL);
915 if (lStyle & TCS_BOTTOM)
917 controlPos.top = clientRect->bottom - infoPtr->tabHeight;
918 controlPos.bottom = controlPos.top + GetSystemMetrics(SM_CYHSCROLL);
920 else
922 controlPos.bottom = clientRect->top + infoPtr->tabHeight;
923 controlPos.top = controlPos.bottom - GetSystemMetrics(SM_CYHSCROLL);
926 else
928 controlPos.right = clientRect->right;
929 controlPos.left = controlPos.right - 2 * GetSystemMetrics(SM_CXHSCROLL);
931 if (lStyle & TCS_BOTTOM)
933 controlPos.top = clientRect->bottom - infoPtr->tabHeight;
934 controlPos.bottom = controlPos.top + GetSystemMetrics(SM_CYHSCROLL);
936 else
938 controlPos.bottom = clientRect->top + infoPtr->tabHeight;
939 controlPos.top = controlPos.bottom - GetSystemMetrics(SM_CYHSCROLL);
944 * If we don't have a scroll control yet, we want to create one.
945 * If we have one, we want to make sure it's positioned properly.
947 if (infoPtr->hwndUpDown==0)
949 infoPtr->hwndUpDown = CreateWindowW(msctls_updown32W, emptyW,
950 WS_VISIBLE | WS_CHILD | UDS_HORZ,
951 controlPos.left, controlPos.top,
952 controlPos.right - controlPos.left,
953 controlPos.bottom - controlPos.top,
954 hwnd, NULL, NULL, NULL);
956 else
958 SetWindowPos(infoPtr->hwndUpDown,
959 NULL,
960 controlPos.left, controlPos.top,
961 controlPos.right - controlPos.left,
962 controlPos.bottom - controlPos.top,
963 SWP_SHOWWINDOW | SWP_NOZORDER);
966 /* Now calculate upper limit of the updown control range.
967 * We do this by calculating how many tabs will be offscreen when the
968 * last tab is visible.
970 if(infoPtr->uNumItem)
972 vsize = clientRect->right - (controlPos.right - controlPos.left + 1);
973 maxRange = infoPtr->uNumItem;
974 tabwidth = TAB_GetItem(infoPtr, infoPtr->uNumItem - 1)->rect.right;
976 for(; maxRange > 0; maxRange--)
978 if(tabwidth - TAB_GetItem(infoPtr,maxRange - 1)->rect.left > vsize)
979 break;
982 if(maxRange == infoPtr->uNumItem)
983 maxRange--;
986 else
988 /* If we once had a scroll control... hide it */
989 if (infoPtr->hwndUpDown!=0)
990 ShowWindow(infoPtr->hwndUpDown, SW_HIDE);
992 if (infoPtr->hwndUpDown)
993 SendMessageW(infoPtr->hwndUpDown, UDM_SETRANGE32, 0, maxRange);
996 /******************************************************************************
997 * TAB_SetItemBounds
999 * This method will calculate the position rectangles of all the items in the
1000 * control. The rectangle calculated starts at 0 for the first item in the
1001 * list and ignores scrolling and selection.
1002 * It also uses the current font to determine the height of the tab row and
1003 * it checks if all the tabs fit in the client area of the window. If they
1004 * don't, a scrolling control is added.
1006 static void TAB_SetItemBounds (TAB_INFO *infoPtr)
1008 LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1009 TEXTMETRICW fontMetrics;
1010 UINT curItem;
1011 INT curItemLeftPos;
1012 INT curItemRowCount;
1013 HFONT hFont, hOldFont;
1014 HDC hdc;
1015 RECT clientRect;
1016 SIZE size;
1017 INT iTemp;
1018 RECT* rcItem;
1019 INT iIndex;
1020 INT icon_width = 0;
1023 * We need to get text information so we need a DC and we need to select
1024 * a font.
1026 hdc = GetDC(infoPtr->hwnd);
1028 hFont = infoPtr->hFont ? infoPtr->hFont : GetStockObject (SYSTEM_FONT);
1029 hOldFont = SelectObject (hdc, hFont);
1032 * We will base the rectangle calculations on the client rectangle
1033 * of the control.
1035 GetClientRect(infoPtr->hwnd, &clientRect);
1037 /* if TCS_VERTICAL then swap the height and width so this code places the
1038 tabs along the top of the rectangle and we can just rotate them after
1039 rather than duplicate all of the below code */
1040 if(lStyle & TCS_VERTICAL)
1042 iTemp = clientRect.bottom;
1043 clientRect.bottom = clientRect.right;
1044 clientRect.right = iTemp;
1047 /* Now use hPadding and vPadding */
1048 infoPtr->uHItemPadding = infoPtr->uHItemPadding_s;
1049 infoPtr->uVItemPadding = infoPtr->uVItemPadding_s;
1051 /* The leftmost item will be "0" aligned */
1052 curItemLeftPos = 0;
1053 curItemRowCount = infoPtr->uNumItem ? 1 : 0;
1055 if (!(infoPtr->fHeightSet))
1057 int item_height;
1058 int icon_height = 0;
1060 /* Use the current font to determine the height of a tab. */
1061 GetTextMetricsW(hdc, &fontMetrics);
1063 /* Get the icon height */
1064 if (infoPtr->himl)
1065 ImageList_GetIconSize(infoPtr->himl, 0, &icon_height);
1067 /* Take the highest between font or icon */
1068 if (fontMetrics.tmHeight > icon_height)
1069 item_height = fontMetrics.tmHeight + 2;
1070 else
1071 item_height = icon_height;
1074 * Make sure there is enough space for the letters + icon + growing the
1075 * selected item + extra space for the selected item.
1077 infoPtr->tabHeight = item_height +
1078 ((lStyle & TCS_BUTTONS) ? 2 : 1) *
1079 infoPtr->uVItemPadding;
1081 TRACE("tabH=%d, tmH=%ld, iconh=%d\n",
1082 infoPtr->tabHeight, fontMetrics.tmHeight, icon_height);
1085 TRACE("client right=%ld\n", clientRect.right);
1087 /* Get the icon width */
1088 if (infoPtr->himl)
1090 ImageList_GetIconSize(infoPtr->himl, &icon_width, 0);
1092 if (lStyle & TCS_FIXEDWIDTH)
1093 icon_width += 4;
1094 else
1095 /* Add padding if icon is present */
1096 icon_width += infoPtr->uHItemPadding;
1099 for (curItem = 0; curItem < infoPtr->uNumItem; curItem++)
1101 TAB_ITEM *curr = TAB_GetItem(infoPtr, curItem);
1103 /* Set the leftmost position of the tab. */
1104 curr->rect.left = curItemLeftPos;
1106 if ((lStyle & TCS_FIXEDWIDTH) || !curr->pszText)
1108 curr->rect.right = curr->rect.left +
1109 max(infoPtr->tabWidth, icon_width);
1111 else
1113 int num = 2;
1115 /* Calculate how wide the tab is depending on the text it contains */
1116 GetTextExtentPoint32W(hdc, curr->pszText,
1117 lstrlenW(curr->pszText), &size);
1119 curr->rect.right = curr->rect.left + size.cx + icon_width +
1120 num * infoPtr->uHItemPadding;
1121 TRACE("for <%s>, l,r=%ld,%ld, num=%d\n",
1122 debugstr_w(curr->pszText), curr->rect.left, curr->rect.right, num);
1126 * Check if this is a multiline tab control and if so
1127 * check to see if we should wrap the tabs
1129 * Wrap all these tabs. We will arrange them evenly later.
1133 if (((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)) &&
1134 (curr->rect.right >
1135 (clientRect.right - CONTROL_BORDER_SIZEX - DISPLAY_AREA_PADDINGX)))
1137 curr->rect.right -= curr->rect.left;
1139 curr->rect.left = 0;
1140 curItemRowCount++;
1141 TRACE("wrapping <%s>, l,r=%ld,%ld\n", debugstr_w(curr->pszText),
1142 curr->rect.left, curr->rect.right);
1145 curr->rect.bottom = 0;
1146 curr->rect.top = curItemRowCount - 1;
1148 TRACE("TextSize: %li\n", size.cx);
1149 TRACE("Rect: T %li, L %li, B %li, R %li\n", curr->rect.top,
1150 curr->rect.left, curr->rect.bottom, curr->rect.right);
1153 * The leftmost position of the next item is the rightmost position
1154 * of this one.
1156 if (lStyle & TCS_BUTTONS)
1158 curItemLeftPos = curr->rect.right + BUTTON_SPACINGX;
1159 if (lStyle & TCS_FLATBUTTONS)
1160 curItemLeftPos += FLAT_BTN_SPACINGX;
1162 else
1163 curItemLeftPos = curr->rect.right;
1166 if (!((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)))
1169 * Check if we need a scrolling control.
1171 infoPtr->needsScrolling = (curItemLeftPos + (2 * SELECTED_TAB_OFFSET) >
1172 clientRect.right);
1174 /* Don't need scrolling, then update infoPtr->leftmostVisible */
1175 if(!infoPtr->needsScrolling)
1176 infoPtr->leftmostVisible = 0;
1178 else
1181 * No scrolling in Multiline or Vertical styles.
1183 infoPtr->needsScrolling = FALSE;
1184 infoPtr->leftmostVisible = 0;
1186 TAB_SetupScrolling(infoPtr->hwnd, infoPtr, &clientRect);
1188 /* Set the number of rows */
1189 infoPtr->uNumRows = curItemRowCount;
1191 /* Arrange all tabs evenly if style says so */
1192 if (!(lStyle & TCS_RAGGEDRIGHT) && ((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)) && (infoPtr->uNumItem > 0))
1194 INT tabPerRow,remTab,iRow;
1195 UINT iItm;
1196 INT iCount=0;
1199 * Ok windows tries to even out the rows. place the same
1200 * number of tabs in each row. So lets give that a shot
1203 tabPerRow = infoPtr->uNumItem / (infoPtr->uNumRows);
1204 remTab = infoPtr->uNumItem % (infoPtr->uNumRows);
1206 for (iItm=0,iRow=0,iCount=0,curItemLeftPos=0;
1207 iItm<infoPtr->uNumItem;
1208 iItm++,iCount++)
1210 /* normalize the current rect */
1211 TAB_ITEM *curr = TAB_GetItem(infoPtr, iItm);
1213 /* shift the item to the left side of the clientRect */
1214 curr->rect.right -= curr->rect.left;
1215 curr->rect.left = 0;
1217 TRACE("r=%ld, cl=%d, cl.r=%ld, iCount=%d, iRow=%d, uNumRows=%d, remTab=%d, tabPerRow=%d\n",
1218 curr->rect.right, curItemLeftPos, clientRect.right,
1219 iCount, iRow, infoPtr->uNumRows, remTab, tabPerRow);
1221 /* if we have reached the maximum number of tabs on this row */
1222 /* move to the next row, reset our current item left position and */
1223 /* the count of items on this row */
1225 if (lStyle & TCS_VERTICAL) {
1226 /* Vert: Add the remaining tabs in the *last* remainder rows */
1227 if (iCount >= ((iRow>=(INT)infoPtr->uNumRows - remTab)?tabPerRow + 1:tabPerRow)) {
1228 iRow++;
1229 curItemLeftPos = 0;
1230 iCount = 0;
1232 } else {
1233 /* Horz: Add the remaining tabs in the *first* remainder rows */
1234 if (iCount >= ((iRow<remTab)?tabPerRow + 1:tabPerRow)) {
1235 iRow++;
1236 curItemLeftPos = 0;
1237 iCount = 0;
1241 /* shift the item to the right to place it as the next item in this row */
1242 curr->rect.left += curItemLeftPos;
1243 curr->rect.right += curItemLeftPos;
1244 curr->rect.top = iRow;
1245 if (lStyle & TCS_BUTTONS)
1247 curItemLeftPos = curr->rect.right + 1;
1248 if (lStyle & TCS_FLATBUTTONS)
1249 curItemLeftPos += FLAT_BTN_SPACINGX;
1251 else
1252 curItemLeftPos = curr->rect.right;
1254 TRACE("arranging <%s>, l,r=%ld,%ld, row=%ld\n",
1255 debugstr_w(curr->pszText), curr->rect.left,
1256 curr->rect.right, curr->rect.top);
1260 * Justify the rows
1263 INT widthDiff, iIndexStart=0, iIndexEnd=0;
1264 INT remainder;
1265 INT iCount=0;
1267 while(iIndexStart < infoPtr->uNumItem)
1269 TAB_ITEM *start = TAB_GetItem(infoPtr, iIndexStart);
1272 * find the index of the row
1274 /* find the first item on the next row */
1275 for (iIndexEnd=iIndexStart;
1276 (iIndexEnd < infoPtr->uNumItem) &&
1277 (TAB_GetItem(infoPtr, iIndexEnd)->rect.top ==
1278 start->rect.top) ;
1279 iIndexEnd++)
1280 /* intentionally blank */;
1283 * we need to justify these tabs so they fill the whole given
1284 * client area
1287 /* find the amount of space remaining on this row */
1288 widthDiff = clientRect.right - (2 * SELECTED_TAB_OFFSET) -
1289 TAB_GetItem(infoPtr, iIndexEnd - 1)->rect.right;
1291 /* iCount is the number of tab items on this row */
1292 iCount = iIndexEnd - iIndexStart;
1294 if (iCount > 1)
1296 remainder = widthDiff % iCount;
1297 widthDiff = widthDiff / iCount;
1298 /* add widthDiff/iCount, or extra space/items on row, to each item on this row */
1299 for (iIndex=iIndexStart, iCount=0; iIndex < iIndexEnd; iIndex++, iCount++)
1301 TAB_ITEM *item = TAB_GetItem(infoPtr, iIndex);
1303 item->rect.left += iCount * widthDiff;
1304 item->rect.right += (iCount + 1) * widthDiff;
1306 TRACE("adjusting 1 <%s>, l,r=%ld,%ld\n",
1307 debugstr_w(item->pszText),
1308 item->rect.left, item->rect.right);
1311 TAB_GetItem(infoPtr, iIndex - 1)->rect.right += remainder;
1313 else /* we have only one item on this row, make it take up the entire row */
1315 start->rect.left = clientRect.left;
1316 start->rect.right = clientRect.right - 4;
1318 TRACE("adjusting 2 <%s>, l,r=%ld,%ld\n",
1319 debugstr_w(start->pszText),
1320 start->rect.left, start->rect.right);
1325 iIndexStart = iIndexEnd;
1330 /* if TCS_VERTICAL rotate the tabs so they are along the side of the clientRect */
1331 if(lStyle & TCS_VERTICAL)
1333 RECT rcOriginal;
1334 for(iIndex = 0; iIndex < infoPtr->uNumItem; iIndex++)
1336 rcItem = &TAB_GetItem(infoPtr, iIndex)->rect;
1338 rcOriginal = *rcItem;
1340 /* this is rotating the items by 90 degrees clockwise around the center of the control */
1341 rcItem->top = (rcOriginal.left - clientRect.left);
1342 rcItem->bottom = rcItem->top + (rcOriginal.right - rcOriginal.left);
1343 rcItem->left = rcOriginal.top;
1344 rcItem->right = rcOriginal.bottom;
1348 TAB_EnsureSelectionVisible(infoPtr);
1349 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
1351 /* Cleanup */
1352 SelectObject (hdc, hOldFont);
1353 ReleaseDC (infoPtr->hwnd, hdc);
1357 static void
1358 TAB_EraseTabInterior
1360 TAB_INFO* infoPtr,
1361 HDC hdc,
1362 INT iItem,
1363 RECT* drawRect
1366 LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1367 HBRUSH hbr = CreateSolidBrush (comctl32_color.clrBtnFace);
1368 BOOL deleteBrush = TRUE;
1369 RECT rTemp = *drawRect;
1371 InflateRect(&rTemp, -2, -2);
1372 if (lStyle & TCS_BUTTONS)
1374 if (iItem == infoPtr->iSelected)
1376 /* Background color */
1377 if (!(lStyle & TCS_OWNERDRAWFIXED))
1379 DeleteObject(hbr);
1380 hbr = GetSysColorBrush(COLOR_SCROLLBAR);
1382 SetTextColor(hdc, comctl32_color.clr3dFace);
1383 SetBkColor(hdc, comctl32_color.clr3dHilight);
1385 /* if COLOR_WINDOW happens to be the same as COLOR_3DHILIGHT
1386 * we better use 0x55aa bitmap brush to make scrollbar's background
1387 * look different from the window background.
1389 if (comctl32_color.clr3dHilight == comctl32_color.clrWindow)
1390 hbr = COMCTL32_hPattern55AABrush;
1392 deleteBrush = FALSE;
1394 FillRect(hdc, &rTemp, hbr);
1396 else /* ! selected */
1398 if (lStyle & TCS_FLATBUTTONS)
1400 FillRect(hdc, drawRect, hbr);
1401 if (iItem == infoPtr->iHotTracked)
1402 DrawEdge(hdc, drawRect, EDGE_RAISED, BF_SOFT|BF_RECT);
1404 else
1405 FillRect(hdc, &rTemp, hbr);
1409 else /* !TCS_BUTTONS */
1411 FillRect(hdc, &rTemp, hbr);
1414 /* Cleanup */
1415 if (deleteBrush) DeleteObject(hbr);
1418 /******************************************************************************
1419 * TAB_DrawItemInterior
1421 * This method is used to draw the interior (text and icon) of a single tab
1422 * into the tab control.
1424 static void
1425 TAB_DrawItemInterior
1427 TAB_INFO* infoPtr,
1428 HDC hdc,
1429 INT iItem,
1430 RECT* drawRect
1433 LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1435 RECT localRect;
1437 HPEN htextPen;
1438 HPEN holdPen;
1439 INT oldBkMode;
1440 HFONT hOldFont;
1442 /* if (drawRect == NULL) */
1444 BOOL isVisible;
1445 RECT itemRect;
1446 RECT selectedRect;
1449 * Get the rectangle for the item.
1451 isVisible = TAB_InternalGetItemRect(infoPtr, iItem, &itemRect, &selectedRect);
1452 if (!isVisible)
1453 return;
1456 * Make sure drawRect points to something valid; simplifies code.
1458 drawRect = &localRect;
1461 * This logic copied from the part of TAB_DrawItem which draws
1462 * the tab background. It's important to keep it in sync. I
1463 * would have liked to avoid code duplication, but couldn't figure
1464 * out how without making spaghetti of TAB_DrawItem.
1466 if (iItem == infoPtr->iSelected)
1467 *drawRect = selectedRect;
1468 else
1469 *drawRect = itemRect;
1471 if (lStyle & TCS_BUTTONS)
1473 if (iItem == infoPtr->iSelected)
1475 drawRect->left += 4;
1476 drawRect->top += 4;
1477 drawRect->right -= 4;
1478 drawRect->bottom -= 1;
1480 else
1482 drawRect->left += 2;
1483 drawRect->top += 2;
1484 drawRect->right -= 2;
1485 drawRect->bottom -= 2;
1488 else
1490 if ((lStyle & TCS_VERTICAL) && (lStyle & TCS_BOTTOM))
1492 if (iItem != infoPtr->iSelected)
1494 drawRect->left += 2;
1495 drawRect->top += 2;
1496 drawRect->bottom -= 2;
1499 else if (lStyle & TCS_VERTICAL)
1501 if (iItem == infoPtr->iSelected)
1503 drawRect->right += 1;
1505 else
1507 drawRect->top += 2;
1508 drawRect->right -= 2;
1509 drawRect->bottom -= 2;
1512 else if (lStyle & TCS_BOTTOM)
1514 if (iItem == infoPtr->iSelected)
1516 drawRect->top -= 2;
1518 else
1520 InflateRect(drawRect, -2, -2);
1521 drawRect->bottom += 2;
1524 else
1526 if (iItem == infoPtr->iSelected)
1528 drawRect->bottom += 3;
1530 else
1532 drawRect->bottom -= 2;
1533 InflateRect(drawRect, -2, 0);
1538 TRACE("drawRect=(%ld,%ld)-(%ld,%ld)\n",
1539 drawRect->left, drawRect->top, drawRect->right, drawRect->bottom);
1541 /* Clear interior */
1542 TAB_EraseTabInterior (infoPtr, hdc, iItem, drawRect);
1544 /* Draw the focus rectangle */
1545 if (!(lStyle & TCS_FOCUSNEVER) &&
1546 (GetFocus() == infoPtr->hwnd) &&
1547 (iItem == infoPtr->uFocus) )
1549 RECT rFocus = *drawRect;
1550 InflateRect(&rFocus, -3, -3);
1551 if (lStyle & TCS_BOTTOM && !(lStyle & TCS_VERTICAL))
1552 rFocus.top -= 3;
1553 if (lStyle & TCS_BUTTONS)
1555 rFocus.left -= 3;
1556 rFocus.top -= 3;
1559 DrawFocusRect(hdc, &rFocus);
1563 * Text pen
1565 htextPen = CreatePen( PS_SOLID, 1, GetSysColor(COLOR_BTNTEXT) );
1566 holdPen = SelectObject(hdc, htextPen);
1567 hOldFont = SelectObject(hdc, infoPtr->hFont);
1570 * Setup for text output
1572 oldBkMode = SetBkMode(hdc, TRANSPARENT);
1573 SetTextColor(hdc, (((iItem == infoPtr->iHotTracked) && !(lStyle & TCS_FLATBUTTONS)) |
1574 (TAB_GetItem(infoPtr, iItem)->dwState & TCIS_HIGHLIGHTED)) ?
1575 comctl32_color.clrHighlight : comctl32_color.clrBtnText);
1578 * if owner draw, tell the owner to draw
1580 if ((lStyle & TCS_OWNERDRAWFIXED) && GetParent(infoPtr->hwnd))
1582 DRAWITEMSTRUCT dis;
1583 UINT id;
1585 drawRect->top += 2;
1586 drawRect->right -= 1;
1587 if ( iItem == infoPtr->iSelected )
1589 drawRect->right -= 1;
1590 drawRect->left += 1;
1594 * get the control id
1596 id = (UINT)GetWindowLongPtrW( infoPtr->hwnd, GWLP_ID );
1599 * put together the DRAWITEMSTRUCT
1601 dis.CtlType = ODT_TAB;
1602 dis.CtlID = id;
1603 dis.itemID = iItem;
1604 dis.itemAction = ODA_DRAWENTIRE;
1605 dis.itemState = 0;
1606 if ( iItem == infoPtr->iSelected )
1607 dis.itemState |= ODS_SELECTED;
1608 if (infoPtr->uFocus == iItem)
1609 dis.itemState |= ODS_FOCUS;
1610 dis.hwndItem = infoPtr->hwnd;
1611 dis.hDC = hdc;
1612 CopyRect(&dis.rcItem,drawRect);
1613 dis.itemData = (ULONG_PTR)TAB_GetItem(infoPtr, iItem)->extra;
1616 * send the draw message
1618 SendMessageW( infoPtr->hwndNotify, WM_DRAWITEM, (WPARAM)id, (LPARAM)&dis );
1620 else
1622 TAB_ITEM *item = TAB_GetItem(infoPtr, iItem);
1623 RECT rcTemp;
1624 RECT rcImage;
1626 /* used to center the icon and text in the tab */
1627 RECT rcText;
1628 INT center_offset_h, center_offset_v;
1630 /* set rcImage to drawRect, we will use top & left in our ImageList_Draw call */
1631 rcImage = *drawRect;
1633 rcTemp = *drawRect;
1635 rcText.left = rcText.top = rcText.right = rcText.bottom = 0;
1637 /* get the rectangle that the text fits in */
1638 if (item->pszText)
1640 DrawTextW(hdc, item->pszText, -1, &rcText, DT_CALCRECT);
1643 * If not owner draw, then do the drawing ourselves.
1645 * Draw the icon.
1647 if (infoPtr->himl && (item->mask & TCIF_IMAGE))
1649 INT cx;
1650 INT cy;
1652 ImageList_GetIconSize(infoPtr->himl, &cx, &cy);
1654 if(lStyle & TCS_VERTICAL)
1656 center_offset_h = ((drawRect->bottom - drawRect->top) - (cy + infoPtr->uHItemPadding + (rcText.right - rcText.left))) / 2;
1657 center_offset_v = ((drawRect->right - drawRect->left) - (cx + infoPtr->uVItemPadding)) / 2;
1659 else
1661 center_offset_h = ((drawRect->right - drawRect->left) - (cx + infoPtr->uHItemPadding + (rcText.right - rcText.left))) / 2;
1662 center_offset_v = ((drawRect->bottom - drawRect->top) - (cy + infoPtr->uVItemPadding)) / 2;
1665 if (lStyle & TCS_FIXEDWIDTH && lStyle & (TCS_FORCELABELLEFT | TCS_FORCEICONLEFT))
1666 center_offset_h = infoPtr->uHItemPadding;
1668 if (center_offset_h < 2)
1669 center_offset_h = 2;
1671 if (center_offset_v < 0)
1672 center_offset_v = 0;
1674 TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%ld,%ld)-(%ld,%ld), textlen=%ld\n",
1675 debugstr_w(item->pszText), center_offset_h, center_offset_v,
1676 drawRect->left, drawRect->top, drawRect->right, drawRect->bottom,
1677 (rcText.right-rcText.left));
1679 if((lStyle & TCS_VERTICAL) && (lStyle & TCS_BOTTOM))
1681 rcImage.top = drawRect->top + center_offset_h;
1682 /* if tab is TCS_VERTICAL and TCS_BOTTOM, the text is drawn from the */
1683 /* right side of the tab, but the image still uses the left as its x position */
1684 /* this keeps the image always drawn off of the same side of the tab */
1685 rcImage.left = drawRect->right - cx - center_offset_v;
1686 drawRect->top += cy + infoPtr->uHItemPadding;
1688 else if(lStyle & TCS_VERTICAL)
1690 rcImage.top = drawRect->bottom - cy - center_offset_h;
1691 rcImage.left = drawRect->left + center_offset_v;
1692 drawRect->bottom -= cy + infoPtr->uHItemPadding;
1694 else /* normal style, whether TCS_BOTTOM or not */
1696 rcImage.left = drawRect->left + center_offset_h;
1697 rcImage.top = drawRect->top + center_offset_v;
1698 drawRect->left += cx + infoPtr->uHItemPadding;
1701 TRACE("drawing image=%d, left=%ld, top=%ld\n",
1702 item->iImage, rcImage.left, rcImage.top-1);
1703 ImageList_Draw
1705 infoPtr->himl,
1706 item->iImage,
1707 hdc,
1708 rcImage.left,
1709 rcImage.top,
1710 ILD_NORMAL
1714 /* Now position text */
1715 if (lStyle & TCS_FIXEDWIDTH && lStyle & TCS_FORCELABELLEFT)
1716 center_offset_h = infoPtr->uHItemPadding;
1717 else
1718 if(lStyle & TCS_VERTICAL)
1719 center_offset_h = ((drawRect->bottom - drawRect->top) - (rcText.right - rcText.left)) / 2;
1720 else
1721 center_offset_h = ((drawRect->right - drawRect->left) - (rcText.right - rcText.left)) / 2;
1723 if(lStyle & TCS_VERTICAL)
1725 if(lStyle & TCS_BOTTOM)
1726 drawRect->top+=center_offset_h;
1727 else
1728 drawRect->bottom-=center_offset_h;
1730 center_offset_v = ((drawRect->right - drawRect->left) - ((rcText.bottom - rcText.top) + infoPtr->uVItemPadding)) / 2;
1732 else
1734 drawRect->left += center_offset_h;
1735 center_offset_v = ((drawRect->bottom - drawRect->top) - ((rcText.bottom - rcText.top) + infoPtr->uVItemPadding)) / 2;
1738 if (center_offset_v < 0)
1739 center_offset_v = 0;
1741 if(lStyle & TCS_VERTICAL)
1742 drawRect->left += center_offset_v;
1743 else
1744 drawRect->top += center_offset_v;
1746 /* Draw the text */
1747 if(lStyle & TCS_VERTICAL) /* if we are vertical rotate the text and each character */
1749 static const WCHAR ArialW[] = { 'A','r','i','a','l',0 };
1750 LOGFONTW logfont;
1751 HFONT hFont = 0;
1752 INT nEscapement = 900;
1753 INT nOrientation = 900;
1755 if(lStyle & TCS_BOTTOM)
1757 nEscapement = -900;
1758 nOrientation = -900;
1761 /* to get a font with the escapement and orientation we are looking for, we need to */
1762 /* call CreateFontIndirectA, which requires us to set the values of the logfont we pass in */
1763 if (!GetObjectW((infoPtr->hFont) ?
1764 infoPtr->hFont : GetStockObject(SYSTEM_FONT),
1765 sizeof(LOGFONTW),&logfont))
1767 INT iPointSize = 9;
1769 lstrcpyW(logfont.lfFaceName, ArialW);
1770 logfont.lfHeight = -MulDiv(iPointSize, GetDeviceCaps(hdc, LOGPIXELSY),
1771 72);
1772 logfont.lfWeight = FW_NORMAL;
1773 logfont.lfItalic = 0;
1774 logfont.lfUnderline = 0;
1775 logfont.lfStrikeOut = 0;
1778 logfont.lfEscapement = nEscapement;
1779 logfont.lfOrientation = nOrientation;
1780 hFont = CreateFontIndirectW(&logfont);
1781 SelectObject(hdc, hFont);
1783 if (item->pszText)
1785 ExtTextOutW(hdc,
1786 (lStyle & TCS_BOTTOM) ? drawRect->right : drawRect->left,
1787 (!(lStyle & TCS_BOTTOM)) ? drawRect->bottom : drawRect->top,
1788 ETO_CLIPPED,
1789 drawRect,
1790 item->pszText,
1791 lstrlenW(item->pszText),
1795 DeleteObject(hFont);
1797 else
1799 TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%ld,%ld)-(%ld,%ld), textlen=%ld\n",
1800 debugstr_w(item->pszText), center_offset_h, center_offset_v,
1801 drawRect->left, drawRect->top, drawRect->right, drawRect->bottom,
1802 (rcText.right-rcText.left));
1803 if (item->pszText)
1805 DrawTextW
1807 hdc,
1808 item->pszText,
1809 lstrlenW(item->pszText),
1810 drawRect,
1811 DT_LEFT | DT_SINGLELINE
1816 *drawRect = rcTemp; /* restore drawRect */
1820 * Cleanup
1822 SelectObject(hdc, hOldFont);
1823 SetBkMode(hdc, oldBkMode);
1824 SelectObject(hdc, holdPen);
1825 DeleteObject( htextPen );
1828 /******************************************************************************
1829 * TAB_DrawItem
1831 * This method is used to draw a single tab into the tab control.
1833 static void TAB_DrawItem(
1834 TAB_INFO *infoPtr,
1835 HDC hdc,
1836 INT iItem)
1838 LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1839 RECT itemRect;
1840 RECT selectedRect;
1841 BOOL isVisible;
1842 RECT r, fillRect, r1;
1843 INT clRight = 0;
1844 INT clBottom = 0;
1845 COLORREF bkgnd, corner;
1848 * Get the rectangle for the item.
1850 isVisible = TAB_InternalGetItemRect(infoPtr,
1851 iItem,
1852 &itemRect,
1853 &selectedRect);
1855 if (isVisible)
1857 RECT rUD, rC;
1859 /* Clip UpDown control to not draw over it */
1860 if (infoPtr->needsScrolling)
1862 GetWindowRect(infoPtr->hwnd, &rC);
1863 GetWindowRect(infoPtr->hwndUpDown, &rUD);
1864 ExcludeClipRect(hdc, rUD.left - rC.left, rUD.top - rC.top, rUD.right - rC.left, rUD.bottom - rC.top);
1867 /* If you need to see what the control is doing,
1868 * then override these variables. They will change what
1869 * fill colors are used for filling the tabs, and the
1870 * corners when drawing the edge.
1872 bkgnd = comctl32_color.clrBtnFace;
1873 corner = comctl32_color.clrBtnFace;
1875 if (lStyle & TCS_BUTTONS)
1877 /* Get item rectangle */
1878 r = itemRect;
1880 /* Separators between flat buttons */
1881 if (lStyle & TCS_FLATBUTTONS)
1883 r1 = r;
1884 r1.right += (FLAT_BTN_SPACINGX -2);
1885 DrawEdge(hdc, &r1, EDGE_ETCHED, BF_RIGHT);
1888 if (iItem == infoPtr->iSelected)
1890 DrawEdge(hdc, &r, EDGE_SUNKEN, BF_SOFT|BF_RECT);
1892 OffsetRect(&r, 1, 1);
1894 else /* ! selected */
1896 if (!(lStyle & TCS_FLATBUTTONS))
1897 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_RECT);
1900 else /* !TCS_BUTTONS */
1902 /* We draw a rectangle of different sizes depending on the selection
1903 * state. */
1904 if (iItem == infoPtr->iSelected) {
1905 RECT rect;
1906 GetClientRect (infoPtr->hwnd, &rect);
1907 clRight = rect.right;
1908 clBottom = rect.bottom;
1909 r = selectedRect;
1911 else
1912 r = itemRect;
1915 * Erase the background. (Delay it but setup rectangle.)
1916 * This is necessary when drawing the selected item since it is larger
1917 * than the others, it might overlap with stuff already drawn by the
1918 * other tabs
1920 fillRect = r;
1922 if(lStyle & TCS_VERTICAL)
1924 /* These are for adjusting the drawing of a Selected tab */
1925 /* The initial values are for the normal case of non-Selected */
1926 int ZZ = 1; /* Do not strech if selected */
1927 if (iItem == infoPtr->iSelected) {
1928 ZZ = 0;
1930 /* if leftmost draw the line longer */
1931 if(selectedRect.top == 0)
1932 fillRect.top += CONTROL_BORDER_SIZEY;
1933 /* if rightmost draw the line longer */
1934 if(selectedRect.bottom == clBottom)
1935 fillRect.bottom -= CONTROL_BORDER_SIZEY;
1938 if (lStyle & TCS_BOTTOM)
1940 /* Adjust both rectangles to match native */
1941 r.left += (1-ZZ);
1943 TRACE("<right> item=%d, fill=(%ld,%ld)-(%ld,%ld), edge=(%ld,%ld)-(%ld,%ld)\n",
1944 iItem,
1945 fillRect.left,fillRect.top,fillRect.right,fillRect.bottom,
1946 r.left,r.top,r.right,r.bottom);
1948 /* Clear interior */
1949 SetBkColor(hdc, bkgnd);
1950 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
1952 /* Draw rectangular edge around tab */
1953 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_RIGHT|BF_TOP|BF_BOTTOM);
1955 /* Now erase the top corner and draw diagonal edge */
1956 SetBkColor(hdc, corner);
1957 r1.left = r.right - ROUND_CORNER_SIZE - 1;
1958 r1.top = r.top;
1959 r1.right = r.right;
1960 r1.bottom = r1.top + ROUND_CORNER_SIZE;
1961 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
1962 r1.right--;
1963 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPLEFT);
1965 /* Now erase the bottom corner and draw diagonal edge */
1966 r1.left = r.right - ROUND_CORNER_SIZE - 1;
1967 r1.bottom = r.bottom;
1968 r1.right = r.right;
1969 r1.top = r1.bottom - ROUND_CORNER_SIZE;
1970 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
1971 r1.right--;
1972 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMLEFT);
1974 if ((iItem == infoPtr->iSelected) && (selectedRect.top == 0)) {
1975 r1 = r;
1976 r1.right = r1.left;
1977 r1.left--;
1978 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_TOP);
1982 else
1984 TRACE("<left> item=%d, fill=(%ld,%ld)-(%ld,%ld), edge=(%ld,%ld)-(%ld,%ld)\n",
1985 iItem,
1986 fillRect.left,fillRect.top,fillRect.right,fillRect.bottom,
1987 r.left,r.top,r.right,r.bottom);
1989 /* Clear interior */
1990 SetBkColor(hdc, bkgnd);
1991 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
1993 /* Draw rectangular edge around tab */
1994 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_TOP|BF_BOTTOM);
1996 /* Now erase the top corner and draw diagonal edge */
1997 SetBkColor(hdc, corner);
1998 r1.left = r.left;
1999 r1.top = r.top;
2000 r1.right = r1.left + ROUND_CORNER_SIZE + 1;
2001 r1.bottom = r1.top + ROUND_CORNER_SIZE;
2002 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2003 r1.left++;
2004 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPRIGHT);
2006 /* Now erase the bottom corner and draw diagonal edge */
2007 r1.left = r.left;
2008 r1.bottom = r.bottom;
2009 r1.right = r1.left + ROUND_CORNER_SIZE + 1;
2010 r1.top = r1.bottom - ROUND_CORNER_SIZE;
2011 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2012 r1.left++;
2013 DrawEdge(hdc, &r1, EDGE_SUNKEN, BF_DIAGONAL_ENDTOPLEFT);
2016 else /* ! TCS_VERTICAL */
2018 /* These are for adjusting the drawing of a Selected tab */
2019 /* The initial values are for the normal case of non-Selected */
2020 if (iItem == infoPtr->iSelected) {
2021 /* if leftmost draw the line longer */
2022 if(selectedRect.left == 0)
2023 fillRect.left += CONTROL_BORDER_SIZEX;
2024 /* if rightmost draw the line longer */
2025 if(selectedRect.right == clRight)
2026 fillRect.right -= CONTROL_BORDER_SIZEX;
2029 if (lStyle & TCS_BOTTOM)
2031 /* Adjust both rectangles for topmost row */
2032 if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2034 fillRect.top -= 2;
2035 r.top -= 1;
2038 TRACE("<bottom> item=%d, fill=(%ld,%ld)-(%ld,%ld), edge=(%ld,%ld)-(%ld,%ld)\n",
2039 iItem,
2040 fillRect.left,fillRect.top,fillRect.right,fillRect.bottom,
2041 r.left,r.top,r.right,r.bottom);
2043 /* Clear interior */
2044 SetBkColor(hdc, bkgnd);
2045 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2047 /* Draw rectangular edge around tab */
2048 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_BOTTOM|BF_RIGHT);
2050 /* Now erase the righthand corner and draw diagonal edge */
2051 SetBkColor(hdc, corner);
2052 r1.left = r.right - ROUND_CORNER_SIZE;
2053 r1.bottom = r.bottom;
2054 r1.right = r.right;
2055 r1.top = r1.bottom - ROUND_CORNER_SIZE - 1;
2056 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2057 r1.bottom--;
2058 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMLEFT);
2060 /* Now erase the lefthand corner and draw diagonal edge */
2061 r1.left = r.left;
2062 r1.bottom = r.bottom;
2063 r1.right = r1.left + ROUND_CORNER_SIZE;
2064 r1.top = r1.bottom - ROUND_CORNER_SIZE - 1;
2065 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2066 r1.bottom--;
2067 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPLEFT);
2069 if (iItem == infoPtr->iSelected)
2071 r.top += 2;
2072 r.left += 1;
2073 if (selectedRect.left == 0)
2075 r1 = r;
2076 r1.bottom = r1.top;
2077 r1.top--;
2078 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_LEFT);
2083 else
2085 /* Adjust both rectangles for bottommost row */
2086 if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2088 fillRect.bottom += 3;
2089 r.bottom += 2;
2092 TRACE("<top> item=%d, fill=(%ld,%ld)-(%ld,%ld), edge=(%ld,%ld)-(%ld,%ld)\n",
2093 iItem,
2094 fillRect.left,fillRect.top,fillRect.right,fillRect.bottom,
2095 r.left,r.top,r.right,r.bottom);
2097 /* Clear interior */
2098 SetBkColor(hdc, bkgnd);
2099 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2101 /* Draw rectangular edge around tab */
2102 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_TOP|BF_RIGHT);
2104 /* Now erase the righthand corner and draw diagonal edge */
2105 SetBkColor(hdc, corner);
2106 r1.left = r.right - ROUND_CORNER_SIZE;
2107 r1.top = r.top;
2108 r1.right = r.right;
2109 r1.bottom = r1.top + ROUND_CORNER_SIZE + 1;
2110 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2111 r1.top++;
2112 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMRIGHT);
2114 /* Now erase the lefthand corner and draw diagonal edge */
2115 r1.left = r.left;
2116 r1.top = r.top;
2117 r1.right = r1.left + ROUND_CORNER_SIZE;
2118 r1.bottom = r1.top + ROUND_CORNER_SIZE + 1;
2119 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2120 r1.top++;
2121 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPRIGHT);
2126 TAB_DumpItemInternal(infoPtr, iItem);
2128 /* This modifies r to be the text rectangle. */
2129 TAB_DrawItemInterior(infoPtr, hdc, iItem, &r);
2133 /******************************************************************************
2134 * TAB_DrawBorder
2136 * This method is used to draw the raised border around the tab control
2137 * "content" area.
2139 static void TAB_DrawBorder (TAB_INFO *infoPtr, HDC hdc)
2141 RECT rect;
2142 DWORD lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
2144 GetClientRect (infoPtr->hwnd, &rect);
2147 * Adjust for the style
2150 if (infoPtr->uNumItem)
2152 if ((lStyle & TCS_BOTTOM) && !(lStyle & TCS_VERTICAL))
2153 rect.bottom -= infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2154 else if((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
2155 rect.right -= infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2156 else if(lStyle & TCS_VERTICAL)
2157 rect.left += infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2158 else /* not TCS_VERTICAL and not TCS_BOTTOM */
2159 rect.top += infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2162 TRACE("border=(%ld,%ld)-(%ld,%ld)\n",
2163 rect.left, rect.top, rect.right, rect.bottom);
2165 DrawEdge(hdc, &rect, EDGE_RAISED, BF_SOFT|BF_RECT);
2168 /******************************************************************************
2169 * TAB_Refresh
2171 * This method repaints the tab control..
2173 static void TAB_Refresh (TAB_INFO *infoPtr, HDC hdc)
2175 HFONT hOldFont;
2176 INT i;
2178 if (!infoPtr->DoRedraw)
2179 return;
2181 hOldFont = SelectObject (hdc, infoPtr->hFont);
2183 if (GetWindowLongW(infoPtr->hwnd, GWL_STYLE) & TCS_BUTTONS)
2185 for (i = 0; i < infoPtr->uNumItem; i++)
2186 TAB_DrawItem (infoPtr, hdc, i);
2188 else
2190 /* Draw all the non selected item first */
2191 for (i = 0; i < infoPtr->uNumItem; i++)
2193 if (i != infoPtr->iSelected)
2194 TAB_DrawItem (infoPtr, hdc, i);
2197 /* Now, draw the border, draw it before the selected item
2198 * since the selected item overwrites part of the border. */
2199 TAB_DrawBorder (infoPtr, hdc);
2201 /* Then, draw the selected item */
2202 TAB_DrawItem (infoPtr, hdc, infoPtr->iSelected);
2204 /* If we haven't set the current focus yet, set it now.
2205 * Only happens when we first paint the tab controls */
2206 if (infoPtr->uFocus == -1)
2207 TAB_SetCurFocus(infoPtr, infoPtr->iSelected);
2210 SelectObject (hdc, hOldFont);
2213 static inline DWORD TAB_GetRowCount (const TAB_INFO *infoPtr)
2215 return infoPtr->uNumRows;
2218 static inline LRESULT TAB_SetRedraw (TAB_INFO *infoPtr, BOOL doRedraw)
2220 infoPtr->DoRedraw = doRedraw;
2221 return 0;
2224 /******************************************************************************
2225 * TAB_EnsureSelectionVisible
2227 * This method will make sure that the current selection is completely
2228 * visible by scrolling until it is.
2230 static void TAB_EnsureSelectionVisible(
2231 TAB_INFO* infoPtr)
2233 INT iSelected = infoPtr->iSelected;
2234 LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
2235 INT iOrigLeftmostVisible = infoPtr->leftmostVisible;
2237 /* set the items row to the bottommost row or topmost row depending on
2238 * style */
2239 if ((infoPtr->uNumRows > 1) && !(lStyle & TCS_BUTTONS))
2241 TAB_ITEM *selected = TAB_GetItem(infoPtr, iSelected);
2242 INT newselected;
2243 INT iTargetRow;
2245 if(lStyle & TCS_VERTICAL)
2246 newselected = selected->rect.left;
2247 else
2248 newselected = selected->rect.top;
2250 /* the target row is always (number of rows - 1)
2251 as row 0 is furthest from the clientRect */
2252 iTargetRow = infoPtr->uNumRows - 1;
2254 if (newselected != iTargetRow)
2256 UINT i;
2257 if(lStyle & TCS_VERTICAL)
2259 for (i=0; i < infoPtr->uNumItem; i++)
2261 /* move everything in the row of the selected item to the iTargetRow */
2262 TAB_ITEM *item = TAB_GetItem(infoPtr, i);
2264 if (item->rect.left == newselected )
2265 item->rect.left = iTargetRow;
2266 else
2268 if (item->rect.left > newselected)
2269 item->rect.left-=1;
2273 else
2275 for (i=0; i < infoPtr->uNumItem; i++)
2277 TAB_ITEM *item = TAB_GetItem(infoPtr, i);
2279 if (item->rect.top == newselected )
2280 item->rect.top = iTargetRow;
2281 else
2283 if (item->rect.top > newselected)
2284 item->rect.top-=1;
2288 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
2293 * Do the trivial cases first.
2295 if ( (!infoPtr->needsScrolling) ||
2296 (infoPtr->hwndUpDown==0) || (lStyle & TCS_VERTICAL))
2297 return;
2299 if (infoPtr->leftmostVisible >= iSelected)
2301 infoPtr->leftmostVisible = iSelected;
2303 else
2305 TAB_ITEM *selected = TAB_GetItem(infoPtr, iSelected);
2306 RECT r;
2307 INT width;
2308 UINT i;
2310 /* Calculate the part of the client area that is visible */
2311 GetClientRect(infoPtr->hwnd, &r);
2312 width = r.right;
2314 GetClientRect(infoPtr->hwndUpDown, &r);
2315 width -= r.right;
2317 if ((selected->rect.right -
2318 selected->rect.left) >= width )
2320 /* Special case: width of selected item is greater than visible
2321 * part of control.
2323 infoPtr->leftmostVisible = iSelected;
2325 else
2327 for (i = infoPtr->leftmostVisible; i < infoPtr->uNumItem; i++)
2329 if ((selected->rect.right - TAB_GetItem(infoPtr, i)->rect.left) < width)
2330 break;
2332 infoPtr->leftmostVisible = i;
2336 if (infoPtr->leftmostVisible != iOrigLeftmostVisible)
2337 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
2339 SendMessageW(infoPtr->hwndUpDown, UDM_SETPOS, 0,
2340 MAKELONG(infoPtr->leftmostVisible, 0));
2343 /******************************************************************************
2344 * TAB_InvalidateTabArea
2346 * This method will invalidate the portion of the control that contains the
2347 * tabs. It is called when the state of the control changes and needs
2348 * to be redisplayed
2350 static void TAB_InvalidateTabArea(TAB_INFO* infoPtr)
2352 RECT clientRect, rInvalidate, rAdjClient;
2353 DWORD lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
2354 INT lastRow = infoPtr->uNumRows - 1;
2355 RECT rect;
2357 if (lastRow < 0) return;
2359 GetClientRect(infoPtr->hwnd, &clientRect);
2360 rInvalidate = clientRect;
2361 rAdjClient = clientRect;
2363 TAB_AdjustRect(infoPtr, 0, &rAdjClient);
2365 TAB_InternalGetItemRect(infoPtr, infoPtr->uNumItem-1 , &rect, NULL);
2366 if ((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
2368 rInvalidate.left = rAdjClient.right;
2369 if (infoPtr->uNumRows == 1)
2370 rInvalidate.bottom = clientRect.top + rect.bottom + 2 * SELECTED_TAB_OFFSET;
2372 else if(lStyle & TCS_VERTICAL)
2374 rInvalidate.right = rAdjClient.left;
2375 if (infoPtr->uNumRows == 1)
2376 rInvalidate.bottom = clientRect.top + rect.bottom + 2 * SELECTED_TAB_OFFSET;
2378 else if (lStyle & TCS_BOTTOM)
2380 rInvalidate.top = rAdjClient.bottom;
2381 if (infoPtr->uNumRows == 1)
2382 rInvalidate.right = clientRect.left + rect.right + 2 * SELECTED_TAB_OFFSET;
2384 else
2386 rInvalidate.bottom = rAdjClient.top;
2387 if (infoPtr->uNumRows == 1)
2388 rInvalidate.right = clientRect.left + rect.right + 2 * SELECTED_TAB_OFFSET;
2391 /* Punch out the updown control */
2392 if (infoPtr->needsScrolling && (rInvalidate.right > 0)) {
2393 RECT r;
2394 GetClientRect(infoPtr->hwndUpDown, &r);
2395 if (rInvalidate.right > clientRect.right - r.left)
2396 rInvalidate.right = rInvalidate.right - (r.right - r.left);
2397 else
2398 rInvalidate.right = clientRect.right - r.left;
2401 TRACE("invalidate (%ld,%ld)-(%ld,%ld)\n",
2402 rInvalidate.left, rInvalidate.top,
2403 rInvalidate.right, rInvalidate.bottom);
2405 InvalidateRect(infoPtr->hwnd, &rInvalidate, TRUE);
2408 static inline LRESULT TAB_Paint (TAB_INFO *infoPtr, HDC hdcPaint)
2410 HDC hdc;
2411 PAINTSTRUCT ps;
2413 if (hdcPaint)
2414 hdc = hdcPaint;
2415 else
2417 hdc = BeginPaint (infoPtr->hwnd, &ps);
2418 TRACE("erase %d, rect=(%ld,%ld)-(%ld,%ld)\n",
2419 ps.fErase,
2420 ps.rcPaint.left,ps.rcPaint.top,ps.rcPaint.right,ps.rcPaint.bottom);
2423 TAB_Refresh (infoPtr, hdc);
2425 if (!hdcPaint)
2426 EndPaint (infoPtr->hwnd, &ps);
2428 return 0;
2431 static LRESULT
2432 TAB_InsertItemT (TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam, BOOL bUnicode)
2434 TAB_ITEM *item;
2435 TCITEMW *pti;
2436 INT iItem;
2437 RECT rect;
2439 GetClientRect (infoPtr->hwnd, &rect);
2440 TRACE("Rect: %p T %li, L %li, B %li, R %li\n", infoPtr->hwnd,
2441 rect.top, rect.left, rect.bottom, rect.right);
2443 pti = (TCITEMW *)lParam;
2444 iItem = (INT)wParam;
2446 if (iItem < 0) return -1;
2447 if (iItem > infoPtr->uNumItem)
2448 iItem = infoPtr->uNumItem;
2450 TAB_DumpItemExternalT(pti, iItem, bUnicode);
2453 if (infoPtr->uNumItem == 0) {
2454 infoPtr->items = Alloc (TAB_ITEM_SIZE(infoPtr));
2455 infoPtr->uNumItem++;
2456 infoPtr->iSelected = 0;
2458 else {
2459 LPBYTE oldItems = (LPBYTE)infoPtr->items;
2461 infoPtr->uNumItem++;
2462 infoPtr->items = Alloc (TAB_ITEM_SIZE(infoPtr) * infoPtr->uNumItem);
2464 /* pre insert copy */
2465 if (iItem > 0) {
2466 memcpy (infoPtr->items, oldItems,
2467 iItem * TAB_ITEM_SIZE(infoPtr));
2470 /* post insert copy */
2471 if (iItem < infoPtr->uNumItem - 1) {
2472 memcpy (TAB_GetItem(infoPtr, iItem + 1),
2473 oldItems + iItem * TAB_ITEM_SIZE(infoPtr),
2474 (infoPtr->uNumItem - iItem - 1) * TAB_ITEM_SIZE(infoPtr));
2478 if (iItem <= infoPtr->iSelected)
2479 infoPtr->iSelected++;
2481 Free (oldItems);
2484 item = TAB_GetItem(infoPtr, iItem);
2486 item->mask = pti->mask;
2487 item->pszText = NULL;
2489 if (pti->mask & TCIF_TEXT)
2491 if (bUnicode)
2492 Str_SetPtrW (&item->pszText, pti->pszText);
2493 else
2494 Str_SetPtrAtoW (&item->pszText, (LPSTR)pti->pszText);
2497 if (pti->mask & TCIF_IMAGE)
2498 item->iImage = pti->iImage;
2499 else
2500 item->iImage = -1;
2502 if (pti->mask & TCIF_PARAM)
2503 memcpy(item->extra, &pti->lParam, infoPtr->cbInfo);
2504 else
2505 memset(item->extra, 0, infoPtr->cbInfo);
2507 TAB_SetItemBounds(infoPtr);
2508 if (infoPtr->uNumItem > 1)
2509 TAB_InvalidateTabArea(infoPtr);
2510 else
2511 InvalidateRect(infoPtr->hwnd, NULL, TRUE);
2513 TRACE("[%p]: added item %d %s\n",
2514 infoPtr->hwnd, iItem, debugstr_w(item->pszText));
2516 return iItem;
2519 static LRESULT
2520 TAB_SetItemSize (TAB_INFO *infoPtr, LPARAM lParam)
2522 LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
2523 LONG lResult = 0;
2524 BOOL bNeedPaint = FALSE;
2526 lResult = MAKELONG(infoPtr->tabWidth, infoPtr->tabHeight);
2528 /* UNDOCUMENTED: If requested Width or Height is 0 this means that program wants to use auto size. */
2529 if (lStyle & TCS_FIXEDWIDTH && (infoPtr->tabWidth != (INT)LOWORD(lParam)))
2531 infoPtr->tabWidth = max((INT)LOWORD(lParam), infoPtr->tabMinWidth);
2532 bNeedPaint = TRUE;
2535 if (infoPtr->tabHeight != (INT)HIWORD(lParam))
2537 if ((infoPtr->fHeightSet = ((INT)HIWORD(lParam) != 0)))
2538 infoPtr->tabHeight = (INT)HIWORD(lParam);
2540 bNeedPaint = TRUE;
2542 TRACE("was h=%d,w=%d, now h=%d,w=%d\n",
2543 HIWORD(lResult), LOWORD(lResult),
2544 infoPtr->tabHeight, infoPtr->tabWidth);
2546 if (bNeedPaint)
2548 TAB_SetItemBounds(infoPtr);
2549 RedrawWindow(infoPtr->hwnd, NULL, NULL, RDW_ERASE | RDW_INVALIDATE | RDW_UPDATENOW);
2552 return lResult;
2555 static inline LRESULT TAB_SetMinTabWidth (TAB_INFO *infoPtr, INT cx)
2557 INT oldcx = 0;
2559 TRACE("(%p,%d)\n", infoPtr, cx);
2561 if (infoPtr) {
2562 oldcx = infoPtr->tabMinWidth;
2563 infoPtr->tabMinWidth = (cx==-1)?DEFAULT_TAB_WIDTH:cx;
2566 return oldcx;
2569 static inline LRESULT
2570 TAB_HighlightItem (TAB_INFO *infoPtr, INT iItem, BOOL fHighlight)
2572 LPDWORD lpState;
2574 TRACE("(%p,%d,%s)\n", infoPtr, iItem, fHighlight ? "true" : "false");
2576 if (!infoPtr || iItem < 0 || iItem >= infoPtr->uNumItem)
2577 return FALSE;
2579 lpState = &TAB_GetItem(infoPtr, iItem)->dwState;
2581 if (fHighlight)
2582 *lpState |= TCIS_HIGHLIGHTED;
2583 else
2584 *lpState &= ~TCIS_HIGHLIGHTED;
2586 return TRUE;
2589 static LRESULT
2590 TAB_SetItemT (TAB_INFO *infoPtr, INT iItem, LPTCITEMW tabItem, BOOL bUnicode)
2592 TAB_ITEM *wineItem;
2594 TRACE("(%p,%d,%p,%s)\n", infoPtr, iItem, tabItem, bUnicode ? "true" : "false");
2596 if (iItem < 0 || iItem >= infoPtr->uNumItem)
2597 return FALSE;
2599 TAB_DumpItemExternalT(tabItem, iItem, bUnicode);
2601 wineItem = TAB_GetItem(infoPtr, iItem);
2603 if (tabItem->mask & TCIF_IMAGE)
2604 wineItem->iImage = tabItem->iImage;
2606 if (tabItem->mask & TCIF_PARAM)
2607 memcpy(wineItem->extra, &tabItem->lParam, infoPtr->cbInfo);
2609 if (tabItem->mask & TCIF_RTLREADING)
2610 FIXME("TCIF_RTLREADING\n");
2612 if (tabItem->mask & TCIF_STATE)
2613 wineItem->dwState = tabItem->dwState;
2615 if (tabItem->mask & TCIF_TEXT)
2617 if (wineItem->pszText)
2619 Free(wineItem->pszText);
2620 wineItem->pszText = NULL;
2622 if (bUnicode)
2623 Str_SetPtrW(&wineItem->pszText, tabItem->pszText);
2624 else
2625 Str_SetPtrAtoW(&wineItem->pszText, (LPSTR)tabItem->pszText);
2628 /* Update and repaint tabs */
2629 TAB_SetItemBounds(infoPtr);
2630 TAB_InvalidateTabArea(infoPtr);
2632 return TRUE;
2635 static inline LRESULT TAB_GetItemCount (const TAB_INFO *infoPtr)
2637 return infoPtr->uNumItem;
2641 static LRESULT
2642 TAB_GetItemT (TAB_INFO *infoPtr, INT iItem, LPTCITEMW tabItem, BOOL bUnicode)
2644 TAB_ITEM *wineItem;
2646 TRACE("(%p,%d,%p,%s)\n", infoPtr, iItem, tabItem, bUnicode ? "true" : "false");
2648 if (iItem < 0 || iItem >= infoPtr->uNumItem)
2649 return FALSE;
2651 wineItem = TAB_GetItem(infoPtr, iItem);
2653 if (tabItem->mask & TCIF_IMAGE)
2654 tabItem->iImage = wineItem->iImage;
2656 if (tabItem->mask & TCIF_PARAM)
2657 memcpy(&tabItem->lParam, wineItem->extra, infoPtr->cbInfo);
2659 if (tabItem->mask & TCIF_RTLREADING)
2660 FIXME("TCIF_RTLREADING\n");
2662 if (tabItem->mask & TCIF_STATE)
2663 tabItem->dwState = wineItem->dwState;
2665 if (tabItem->mask & TCIF_TEXT)
2667 if (bUnicode)
2668 Str_GetPtrW (wineItem->pszText, tabItem->pszText, tabItem->cchTextMax);
2669 else
2670 Str_GetPtrWtoA (wineItem->pszText, (LPSTR)tabItem->pszText, tabItem->cchTextMax);
2673 TAB_DumpItemExternalT(tabItem, iItem, bUnicode);
2675 return TRUE;
2679 static LRESULT TAB_DeleteItem (TAB_INFO *infoPtr, INT iItem)
2681 BOOL bResult = FALSE;
2683 TRACE("(%p, %d)\n", infoPtr, iItem);
2685 if ((iItem >= 0) && (iItem < infoPtr->uNumItem))
2687 TAB_ITEM *item = TAB_GetItem(infoPtr, iItem);
2688 LPBYTE oldItems = (LPBYTE)infoPtr->items;
2690 TAB_InvalidateTabArea(infoPtr);
2692 if ((item->mask & TCIF_TEXT) && item->pszText)
2693 Free(item->pszText);
2695 infoPtr->uNumItem--;
2697 if (!infoPtr->uNumItem)
2699 infoPtr->items = NULL;
2700 if (infoPtr->iHotTracked >= 0)
2702 KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
2703 infoPtr->iHotTracked = -1;
2706 else
2708 infoPtr->items = Alloc(TAB_ITEM_SIZE(infoPtr) * infoPtr->uNumItem);
2710 if (iItem > 0)
2711 memcpy(infoPtr->items, oldItems, iItem * TAB_ITEM_SIZE(infoPtr));
2713 if (iItem < infoPtr->uNumItem)
2714 memcpy(TAB_GetItem(infoPtr, iItem),
2715 oldItems + (iItem + 1) * TAB_ITEM_SIZE(infoPtr),
2716 (infoPtr->uNumItem - iItem) * TAB_ITEM_SIZE(infoPtr));
2718 if (iItem <= infoPtr->iHotTracked)
2720 /* When tabs move left/up, the hot track item may change */
2721 FIXME("Recalc hot track");
2724 Free(oldItems);
2726 /* Readjust the selected index */
2727 if ((iItem == infoPtr->iSelected) && (iItem > 0))
2728 infoPtr->iSelected--;
2730 if (iItem < infoPtr->iSelected)
2731 infoPtr->iSelected--;
2733 if (infoPtr->uNumItem == 0)
2734 infoPtr->iSelected = -1;
2736 /* Reposition and repaint tabs */
2737 TAB_SetItemBounds(infoPtr);
2739 bResult = TRUE;
2742 return bResult;
2745 static inline LRESULT TAB_DeleteAllItems (TAB_INFO *infoPtr)
2747 TRACE("(%p)\n", infoPtr);
2748 while (infoPtr->uNumItem)
2749 TAB_DeleteItem (infoPtr, 0);
2750 return TRUE;
2754 static inline LRESULT TAB_GetFont (const TAB_INFO *infoPtr)
2756 TRACE("(%p) returning %p\n", infoPtr, infoPtr->hFont);
2757 return (LRESULT)infoPtr->hFont;
2760 static inline LRESULT TAB_SetFont (TAB_INFO *infoPtr, HFONT hNewFont)
2762 TRACE("(%p,%p)\n", infoPtr, hNewFont);
2764 infoPtr->hFont = hNewFont;
2766 TAB_SetItemBounds(infoPtr);
2768 TAB_InvalidateTabArea(infoPtr);
2770 return 0;
2774 static inline LRESULT TAB_GetImageList (const TAB_INFO *infoPtr)
2776 TRACE("\n");
2777 return (LRESULT)infoPtr->himl;
2780 static inline LRESULT TAB_SetImageList (TAB_INFO *infoPtr, HIMAGELIST himlNew)
2782 HIMAGELIST himlPrev = infoPtr->himl;
2783 TRACE("\n");
2784 infoPtr->himl = himlNew;
2785 return (LRESULT)himlPrev;
2788 static inline LRESULT TAB_GetUnicodeFormat (const TAB_INFO *infoPtr)
2790 return infoPtr->bUnicode;
2793 static inline LRESULT TAB_SetUnicodeFormat (TAB_INFO *infoPtr, BOOL bUnicode)
2795 BOOL bTemp = infoPtr->bUnicode;
2797 infoPtr->bUnicode = bUnicode;
2799 return bTemp;
2802 static inline LRESULT TAB_Size (TAB_INFO *infoPtr)
2804 /* I'm not really sure what the following code was meant to do.
2805 This is what it is doing:
2806 When WM_SIZE is sent with SIZE_RESTORED, the control
2807 gets positioned in the top left corner.
2809 RECT parent_rect;
2810 HWND parent;
2811 UINT uPosFlags,cx,cy;
2813 uPosFlags=0;
2814 if (!wParam) {
2815 parent = GetParent (hwnd);
2816 GetClientRect(parent, &parent_rect);
2817 cx=LOWORD (lParam);
2818 cy=HIWORD (lParam);
2819 if (GetWindowLongW(hwnd, GWL_STYLE) & CCS_NORESIZE)
2820 uPosFlags |= (SWP_NOSIZE | SWP_NOMOVE);
2822 SetWindowPos (hwnd, 0, parent_rect.left, parent_rect.top,
2823 cx, cy, uPosFlags | SWP_NOZORDER);
2824 } else {
2825 FIXME("WM_SIZE flag %x %lx not handled\n", wParam, lParam);
2826 } */
2828 /* Recompute the size/position of the tabs. */
2829 TAB_SetItemBounds (infoPtr);
2831 /* Force a repaint of the control. */
2832 InvalidateRect(infoPtr->hwnd, NULL, TRUE);
2834 return 0;
2838 static LRESULT TAB_Create (HWND hwnd, WPARAM wParam, LPARAM lParam)
2840 TAB_INFO *infoPtr;
2841 TEXTMETRICW fontMetrics;
2842 HDC hdc;
2843 HFONT hOldFont;
2844 DWORD dwStyle;
2846 infoPtr = (TAB_INFO *)Alloc (sizeof(TAB_INFO));
2848 SetWindowLongPtrW(hwnd, 0, (DWORD_PTR)infoPtr);
2850 infoPtr->hwnd = hwnd;
2851 infoPtr->hwndNotify = ((LPCREATESTRUCTW)lParam)->hwndParent;
2852 infoPtr->uNumItem = 0;
2853 infoPtr->uNumRows = 0;
2854 infoPtr->uHItemPadding = 6;
2855 infoPtr->uVItemPadding = 3;
2856 infoPtr->uHItemPadding_s = 6;
2857 infoPtr->uVItemPadding_s = 3;
2858 infoPtr->hFont = 0;
2859 infoPtr->items = 0;
2860 infoPtr->hcurArrow = LoadCursorW (0, (LPWSTR)IDC_ARROW);
2861 infoPtr->iSelected = -1;
2862 infoPtr->iHotTracked = -1;
2863 infoPtr->uFocus = -1;
2864 infoPtr->hwndToolTip = 0;
2865 infoPtr->DoRedraw = TRUE;
2866 infoPtr->needsScrolling = FALSE;
2867 infoPtr->hwndUpDown = 0;
2868 infoPtr->leftmostVisible = 0;
2869 infoPtr->fHeightSet = FALSE;
2870 infoPtr->bUnicode = IsWindowUnicode (hwnd);
2871 infoPtr->cbInfo = sizeof(LPARAM);
2873 TRACE("Created tab control, hwnd [%p]\n", hwnd);
2875 /* The tab control always has the WS_CLIPSIBLINGS style. Even
2876 if you don't specify it in CreateWindow. This is necessary in
2877 order for paint to work correctly. This follows windows behaviour. */
2878 dwStyle = GetWindowLongW(hwnd, GWL_STYLE);
2879 SetWindowLongW(hwnd, GWL_STYLE, dwStyle|WS_CLIPSIBLINGS);
2881 if (dwStyle & TCS_TOOLTIPS) {
2882 /* Create tooltip control */
2883 infoPtr->hwndToolTip =
2884 CreateWindowExW (0, TOOLTIPS_CLASSW, NULL, 0,
2885 CW_USEDEFAULT, CW_USEDEFAULT,
2886 CW_USEDEFAULT, CW_USEDEFAULT,
2887 hwnd, 0, 0, 0);
2889 /* Send NM_TOOLTIPSCREATED notification */
2890 if (infoPtr->hwndToolTip) {
2891 NMTOOLTIPSCREATED nmttc;
2893 nmttc.hdr.hwndFrom = hwnd;
2894 nmttc.hdr.idFrom = GetWindowLongPtrW(hwnd, GWLP_ID);
2895 nmttc.hdr.code = NM_TOOLTIPSCREATED;
2896 nmttc.hwndToolTips = infoPtr->hwndToolTip;
2898 SendMessageW (infoPtr->hwndNotify, WM_NOTIFY,
2899 (WPARAM)GetWindowLongPtrW(hwnd, GWLP_ID), (LPARAM)&nmttc);
2904 * We need to get text information so we need a DC and we need to select
2905 * a font.
2907 hdc = GetDC(hwnd);
2908 hOldFont = SelectObject (hdc, GetStockObject (SYSTEM_FONT));
2910 /* Use the system font to determine the initial height of a tab. */
2911 GetTextMetricsW(hdc, &fontMetrics);
2914 * Make sure there is enough space for the letters + growing the
2915 * selected item + extra space for the selected item.
2917 infoPtr->tabHeight = fontMetrics.tmHeight + SELECTED_TAB_OFFSET +
2918 ((dwStyle & TCS_BUTTONS) ? 2 : 1) *
2919 infoPtr->uVItemPadding;
2921 /* Initialize the width of a tab. */
2922 infoPtr->tabWidth = DEFAULT_TAB_WIDTH;
2923 infoPtr->tabMinWidth = 0;
2925 TRACE("tabH=%d, tabW=%d\n", infoPtr->tabHeight, infoPtr->tabWidth);
2927 SelectObject (hdc, hOldFont);
2928 ReleaseDC(hwnd, hdc);
2930 return 0;
2933 static LRESULT
2934 TAB_Destroy (TAB_INFO *infoPtr)
2936 UINT iItem;
2938 if (!infoPtr)
2939 return 0;
2941 SetWindowLongPtrW(infoPtr->hwnd, 0, 0);
2943 if (infoPtr->items) {
2944 for (iItem = 0; iItem < infoPtr->uNumItem; iItem++) {
2945 if (TAB_GetItem(infoPtr, iItem)->pszText)
2946 Free (TAB_GetItem(infoPtr, iItem)->pszText);
2948 Free (infoPtr->items);
2951 if (infoPtr->hwndToolTip)
2952 DestroyWindow (infoPtr->hwndToolTip);
2954 if (infoPtr->hwndUpDown)
2955 DestroyWindow(infoPtr->hwndUpDown);
2957 if (infoPtr->iHotTracked >= 0)
2958 KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
2960 Free (infoPtr);
2961 return 0;
2964 static inline LRESULT
2965 TAB_SetItemExtra (TAB_INFO *infoPtr, INT cbInfo)
2967 if (!infoPtr || cbInfo <= 0)
2968 return FALSE;
2970 if (infoPtr->uNumItem)
2972 /* FIXME: MSDN says this is not allowed, but this hasn't been verified */
2973 return FALSE;
2976 infoPtr->cbInfo = cbInfo;
2977 return TRUE;
2980 static LRESULT WINAPI
2981 TAB_WindowProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
2983 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2985 TRACE("hwnd=%p msg=%x wParam=%x lParam=%lx\n", hwnd, uMsg, wParam, lParam);
2986 if (!infoPtr && (uMsg != WM_CREATE))
2987 return DefWindowProcW (hwnd, uMsg, wParam, lParam);
2989 switch (uMsg)
2991 case TCM_GETIMAGELIST:
2992 return TAB_GetImageList (infoPtr);
2994 case TCM_SETIMAGELIST:
2995 return TAB_SetImageList (infoPtr, (HIMAGELIST)lParam);
2997 case TCM_GETITEMCOUNT:
2998 return TAB_GetItemCount (infoPtr);
3000 case TCM_GETITEMA:
3001 case TCM_GETITEMW:
3002 return TAB_GetItemT (infoPtr, (INT)wParam, (LPTCITEMW)lParam, uMsg == TCM_GETITEMW);
3004 case TCM_SETITEMA:
3005 case TCM_SETITEMW:
3006 return TAB_SetItemT (infoPtr, (INT)wParam, (LPTCITEMW)lParam, uMsg == TCM_SETITEMW);
3008 case TCM_DELETEITEM:
3009 return TAB_DeleteItem (infoPtr, (INT)wParam);
3011 case TCM_DELETEALLITEMS:
3012 return TAB_DeleteAllItems (infoPtr);
3014 case TCM_GETITEMRECT:
3015 return TAB_GetItemRect (infoPtr, wParam, lParam);
3017 case TCM_GETCURSEL:
3018 return TAB_GetCurSel (infoPtr);
3020 case TCM_HITTEST:
3021 return TAB_HitTest (infoPtr, (LPTCHITTESTINFO)lParam);
3023 case TCM_SETCURSEL:
3024 return TAB_SetCurSel (infoPtr, (INT)wParam);
3026 case TCM_INSERTITEMA:
3027 case TCM_INSERTITEMW:
3028 return TAB_InsertItemT (infoPtr, wParam, lParam, uMsg == TCM_INSERTITEMW);
3030 case TCM_SETITEMEXTRA:
3031 return TAB_SetItemExtra (infoPtr, (int)wParam);
3033 case TCM_ADJUSTRECT:
3034 return TAB_AdjustRect (infoPtr, (BOOL)wParam, (LPRECT)lParam);
3036 case TCM_SETITEMSIZE:
3037 return TAB_SetItemSize (infoPtr, lParam);
3039 case TCM_REMOVEIMAGE:
3040 FIXME("Unimplemented msg TCM_REMOVEIMAGE\n");
3041 return 0;
3043 case TCM_SETPADDING:
3044 return TAB_SetPadding (infoPtr, lParam);
3046 case TCM_GETROWCOUNT:
3047 return TAB_GetRowCount(infoPtr);
3049 case TCM_GETUNICODEFORMAT:
3050 return TAB_GetUnicodeFormat (infoPtr);
3052 case TCM_SETUNICODEFORMAT:
3053 return TAB_SetUnicodeFormat (infoPtr, (BOOL)wParam);
3055 case TCM_HIGHLIGHTITEM:
3056 return TAB_HighlightItem (infoPtr, (INT)wParam, (BOOL)LOWORD(lParam));
3058 case TCM_GETTOOLTIPS:
3059 return TAB_GetToolTips (infoPtr);
3061 case TCM_SETTOOLTIPS:
3062 return TAB_SetToolTips (infoPtr, (HWND)wParam);
3064 case TCM_GETCURFOCUS:
3065 return TAB_GetCurFocus (infoPtr);
3067 case TCM_SETCURFOCUS:
3068 return TAB_SetCurFocus (infoPtr, (INT)wParam);
3070 case TCM_SETMINTABWIDTH:
3071 return TAB_SetMinTabWidth(infoPtr, (INT)lParam);
3073 case TCM_DESELECTALL:
3074 FIXME("Unimplemented msg TCM_DESELECTALL\n");
3075 return 0;
3077 case TCM_GETEXTENDEDSTYLE:
3078 FIXME("Unimplemented msg TCM_GETEXTENDEDSTYLE\n");
3079 return 0;
3081 case TCM_SETEXTENDEDSTYLE:
3082 FIXME("Unimplemented msg TCM_SETEXTENDEDSTYLE\n");
3083 return 0;
3085 case WM_GETFONT:
3086 return TAB_GetFont (infoPtr);
3088 case WM_SETFONT:
3089 return TAB_SetFont (infoPtr, (HFONT)wParam);
3091 case WM_CREATE:
3092 return TAB_Create (hwnd, wParam, lParam);
3094 case WM_NCDESTROY:
3095 return TAB_Destroy (infoPtr);
3097 case WM_GETDLGCODE:
3098 return DLGC_WANTARROWS | DLGC_WANTCHARS;
3100 case WM_LBUTTONDOWN:
3101 return TAB_LButtonDown (infoPtr, wParam, lParam);
3103 case WM_LBUTTONUP:
3104 return TAB_LButtonUp (infoPtr);
3106 case WM_NOTIFY:
3107 return SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, wParam, lParam);
3109 case WM_RBUTTONDOWN:
3110 return TAB_RButtonDown (infoPtr);
3112 case WM_MOUSEMOVE:
3113 return TAB_MouseMove (infoPtr, wParam, lParam);
3115 case WM_PAINT:
3116 return TAB_Paint (infoPtr, (HDC)wParam);
3118 case WM_SIZE:
3119 return TAB_Size (infoPtr);
3121 case WM_SETREDRAW:
3122 return TAB_SetRedraw (infoPtr, (BOOL)wParam);
3124 case WM_HSCROLL:
3125 return TAB_OnHScroll(infoPtr, (int)LOWORD(wParam), (int)HIWORD(wParam), (HWND)lParam);
3127 case WM_STYLECHANGED:
3128 TAB_SetItemBounds (infoPtr);
3129 InvalidateRect(hwnd, NULL, TRUE);
3130 return 0;
3132 case WM_SYSCOLORCHANGE:
3133 COMCTL32_RefreshSysColors();
3134 return 0;
3136 case WM_KILLFOCUS:
3137 case WM_SETFOCUS:
3138 TAB_FocusChanging(infoPtr);
3139 break; /* Don't disturb normal focus behavior */
3141 case WM_KEYUP:
3142 return TAB_KeyUp(infoPtr, wParam);
3143 case WM_NCHITTEST:
3144 return TAB_NCHitTest(infoPtr, lParam);
3146 default:
3147 if (uMsg >= WM_USER && uMsg < WM_APP)
3148 WARN("unknown msg %04x wp=%08x lp=%08lx\n",
3149 uMsg, wParam, lParam);
3150 break;
3152 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
3156 void
3157 TAB_Register (void)
3159 WNDCLASSW wndClass;
3161 ZeroMemory (&wndClass, sizeof(WNDCLASSW));
3162 wndClass.style = CS_GLOBALCLASS | CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
3163 wndClass.lpfnWndProc = TAB_WindowProc;
3164 wndClass.cbClsExtra = 0;
3165 wndClass.cbWndExtra = sizeof(TAB_INFO *);
3166 wndClass.hCursor = LoadCursorW (0, (LPWSTR)IDC_ARROW);
3167 wndClass.hbrBackground = (HBRUSH)(COLOR_BTNFACE+1);
3168 wndClass.lpszClassName = WC_TABCONTROLW;
3170 RegisterClassW (&wndClass);
3174 void
3175 TAB_Unregister (void)
3177 UnregisterClassW (WC_TABCONTROLW, NULL);