Dump HeapWalk entries.
[wine/multimedia.git] / dlls / comctl32 / tab.c
blob799f83745ae0834be01b53fd277a44cf55f0cd47
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:
24 * Unicode support (under construction)
26 * Styles:
27 * TCIF_RTLREADING
29 * Messages:
30 * TCM_REMOVEIMAGE
31 * TCM_DESELECTALL
32 * TCM_GETEXTENDEDSTYLE
33 * TCM_SETEXTENDEDSTYLE
37 #include <stdarg.h>
38 #include <string.h>
40 #include "windef.h"
41 #include "winbase.h"
42 #include "wingdi.h"
43 #include "winuser.h"
44 #include "winnls.h"
45 #include "commctrl.h"
46 #include "comctl32.h"
47 #include "wine/debug.h"
48 #include <math.h>
50 WINE_DEFAULT_DEBUG_CHANNEL(tab);
52 typedef struct
54 UINT mask;
55 DWORD dwState;
56 LPWSTR pszText;
57 INT iImage;
58 RECT rect; /* bounding rectangle of the item relative to the
59 * leftmost item (the leftmost item, 0, would have a
60 * "left" member of 0 in this rectangle)
62 * additionally the top member holds the row number
63 * and bottom is unused and should be 0 */
64 BYTE extra[1]; /* Space for caller supplied info, variable size */
65 } TAB_ITEM;
67 /* The size of a tab item depends on how much extra data is requested */
68 #define TAB_ITEM_SIZE(infoPtr) (sizeof(TAB_ITEM) - sizeof(BYTE) + infoPtr->cbInfo)
70 typedef struct
72 HWND hwnd; /* Tab control window */
73 HWND hwndNotify; /* notification window (parent) */
74 UINT uNumItem; /* number of tab items */
75 UINT uNumRows; /* number of tab rows */
76 INT tabHeight; /* height of the tab row */
77 INT tabWidth; /* width of tabs */
78 INT tabMinWidth; /* minimum width of items */
79 USHORT uHItemPadding; /* amount of horizontal padding, in pixels */
80 USHORT uVItemPadding; /* amount of vertical padding, in pixels */
81 USHORT uHItemPadding_s; /* Set amount of horizontal padding, in pixels */
82 USHORT uVItemPadding_s; /* Set amount of vertical padding, in pixels */
83 HFONT hFont; /* handle to the current font */
84 HCURSOR hcurArrow; /* handle to the current cursor */
85 HIMAGELIST himl; /* handle to a image list (may be 0) */
86 HWND hwndToolTip; /* handle to tab's tooltip */
87 INT leftmostVisible; /* Used for scrolling, this member contains
88 * the index of the first visible item */
89 INT iSelected; /* the currently selected item */
90 INT iHotTracked; /* the highlighted item under the mouse */
91 INT uFocus; /* item which has the focus */
92 TAB_ITEM* items; /* pointer to an array of TAB_ITEM's */
93 BOOL DoRedraw; /* flag for redrawing when tab contents is changed*/
94 BOOL needsScrolling; /* TRUE if the size of the tabs is greater than
95 * the size of the control */
96 BOOL fHeightSet; /* was the height of the tabs explicitly set? */
97 BOOL bUnicode; /* Unicode control? */
98 HWND hwndUpDown; /* Updown control used for scrolling */
99 INT cbInfo; /* Number of bytes of caller supplied info per tab */
100 } TAB_INFO;
102 /******************************************************************************
103 * Positioning constants
105 #define SELECTED_TAB_OFFSET 2
106 #define ROUND_CORNER_SIZE 2
107 #define DISPLAY_AREA_PADDINGX 2
108 #define DISPLAY_AREA_PADDINGY 2
109 #define CONTROL_BORDER_SIZEX 2
110 #define CONTROL_BORDER_SIZEY 2
111 #define BUTTON_SPACINGX 3
112 #define BUTTON_SPACINGY 3
113 #define FLAT_BTN_SPACINGX 8
114 #define DEFAULT_TAB_WIDTH 96
116 #define TAB_GetInfoPtr(hwnd) ((TAB_INFO *)GetWindowLongPtrW(hwnd,0))
117 /* Since items are variable sized, cannot directly access them */
118 #define TAB_GetItem(info,i) \
119 ((TAB_ITEM*)((LPBYTE)info->items + (i) * TAB_ITEM_SIZE(info)))
121 /******************************************************************************
122 * Hot-tracking timer constants
124 #define TAB_HOTTRACK_TIMER 1
125 #define TAB_HOTTRACK_TIMER_INTERVAL 100 /* milliseconds */
127 /******************************************************************************
128 * Prototypes
130 static void TAB_InvalidateTabArea(TAB_INFO *);
131 static void TAB_EnsureSelectionVisible(TAB_INFO *);
132 static void TAB_DrawItemInterior(TAB_INFO *, HDC, INT, RECT*);
134 static BOOL
135 TAB_SendSimpleNotify (const TAB_INFO *infoPtr, UINT code)
137 NMHDR nmhdr;
139 nmhdr.hwndFrom = infoPtr->hwnd;
140 nmhdr.idFrom = GetWindowLongPtrW(infoPtr->hwnd, GWLP_ID);
141 nmhdr.code = code;
143 return (BOOL) SendMessageW (infoPtr->hwndNotify, WM_NOTIFY,
144 (WPARAM) nmhdr.idFrom, (LPARAM) &nmhdr);
147 static VOID
148 TAB_RelayEvent (HWND hwndTip, HWND hwndMsg, UINT uMsg,
149 WPARAM wParam, LPARAM lParam)
151 MSG msg;
153 msg.hwnd = hwndMsg;
154 msg.message = uMsg;
155 msg.wParam = wParam;
156 msg.lParam = lParam;
157 msg.time = GetMessageTime ();
158 msg.pt.x = LOWORD(GetMessagePos ());
159 msg.pt.y = HIWORD(GetMessagePos ());
161 SendMessageW (hwndTip, TTM_RELAYEVENT, 0, (LPARAM)&msg);
164 static void
165 TAB_DumpItemExternalA(TCITEMA *pti, UINT iItem)
167 if (TRACE_ON(tab)) {
168 TRACE("external tab %d, mask=0x%08x, dwState=0x%08x, dwStateMask=0x%08x, cchTextMax=0x%08x\n",
169 iItem, pti->mask, pti->dwState, pti->dwStateMask, pti->cchTextMax);
170 TRACE("external tab %d, iImage=%d, lParam=0x%08lx, pszTextA=%s\n",
171 iItem, pti->iImage, pti->lParam, debugstr_a(pti->pszText));
176 static void
177 TAB_DumpItemExternalW(TCITEMW *pti, UINT iItem)
179 if (TRACE_ON(tab)) {
180 TRACE("external tab %d, mask=0x%08x, dwState=0x%08lx, dwStateMask=0x%08lx, cchTextMax=0x%08x\n",
181 iItem, pti->mask, pti->dwState, pti->dwStateMask, pti->cchTextMax);
182 TRACE("external tab %d, iImage=%d, lParam=0x%08lx, pszTextW=%s\n",
183 iItem, pti->iImage, pti->lParam, debugstr_w(pti->pszText));
187 static void
188 TAB_DumpItemInternal(TAB_INFO *infoPtr, UINT iItem)
190 if (TRACE_ON(tab)) {
191 TAB_ITEM *ti;
193 ti = TAB_GetItem(infoPtr, iItem);
194 TRACE("tab %d, mask=0x%08x, dwState=0x%08lx, pszText=%s, iImage=%d\n",
195 iItem, ti->mask, ti->dwState, debugstr_w(ti->pszText),
196 ti->iImage);
197 TRACE("tab %d, rect.left=%ld, rect.top(row)=%ld\n",
198 iItem, ti->rect.left, ti->rect.top);
202 /* RETURNS
203 * the index of the selected tab, or -1 if no tab is selected. */
204 static inline LRESULT TAB_GetCurSel (const TAB_INFO *infoPtr)
206 return infoPtr->iSelected;
209 /* RETURNS
210 * the index of the tab item that has the focus
211 * NOTE
212 * we have not to return negative value
213 * TODO
214 * test for windows */
215 static inline LRESULT
216 TAB_GetCurFocus (const TAB_INFO *infoPtr)
218 if (infoPtr->uFocus<0)
220 FIXME("we have not to return negative value");
221 return 0;
223 return infoPtr->uFocus;
226 static inline LRESULT TAB_GetToolTips (const TAB_INFO *infoPtr)
228 if (infoPtr == NULL) return 0;
229 return (LRESULT)infoPtr->hwndToolTip;
232 static inline LRESULT TAB_SetCurSel (TAB_INFO *infoPtr, INT iItem)
234 INT prevItem = -1;
236 if (iItem >= 0 && iItem < infoPtr->uNumItem) {
237 prevItem=infoPtr->iSelected;
238 infoPtr->iSelected=iItem;
239 TAB_EnsureSelectionVisible(infoPtr);
240 TAB_InvalidateTabArea(infoPtr);
242 return prevItem;
245 static LRESULT TAB_SetCurFocus (TAB_INFO *infoPtr, INT iItem)
247 if (iItem < 0 || iItem >= infoPtr->uNumItem) return 0;
249 if (GetWindowLongA(infoPtr->hwnd, GWL_STYLE) & TCS_BUTTONS) {
250 FIXME("Should set input focus\n");
251 } else {
252 int oldFocus = infoPtr->uFocus;
253 if (infoPtr->iSelected != iItem || oldFocus == -1 ) {
254 infoPtr->uFocus = iItem;
255 if (oldFocus != -1) {
256 if (!TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGING)) {
257 infoPtr->iSelected = iItem;
258 TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGE);
260 else
261 infoPtr->iSelected = iItem;
262 TAB_EnsureSelectionVisible(infoPtr);
263 TAB_InvalidateTabArea(infoPtr);
267 return 0;
270 static inline LRESULT
271 TAB_SetToolTips (TAB_INFO *infoPtr, HWND hwndToolTip)
273 if (infoPtr)
274 infoPtr->hwndToolTip = hwndToolTip;
275 return 0;
278 static inline LRESULT
279 TAB_SetPadding (TAB_INFO *infoPtr, LPARAM lParam)
281 if (infoPtr)
283 infoPtr->uHItemPadding_s=LOWORD(lParam);
284 infoPtr->uVItemPadding_s=HIWORD(lParam);
286 return 0;
289 /******************************************************************************
290 * TAB_InternalGetItemRect
292 * This method will calculate the rectangle representing a given tab item in
293 * client coordinates. This method takes scrolling into account.
295 * This method returns TRUE if the item is visible in the window and FALSE
296 * if it is completely outside the client area.
298 static BOOL TAB_InternalGetItemRect(
299 const TAB_INFO* infoPtr,
300 INT itemIndex,
301 RECT* itemRect,
302 RECT* selectedRect)
304 RECT tmpItemRect,clientRect;
305 LONG lStyle = GetWindowLongA(infoPtr->hwnd, GWL_STYLE);
307 /* Perform a sanity check and a trivial visibility check. */
308 if ( (infoPtr->uNumItem <= 0) ||
309 (itemIndex >= infoPtr->uNumItem) ||
310 (!((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)) && (itemIndex < infoPtr->leftmostVisible)) )
311 return FALSE;
314 * Avoid special cases in this procedure by assigning the "out"
315 * parameters if the caller didn't supply them
317 if (itemRect == NULL)
318 itemRect = &tmpItemRect;
320 /* Retrieve the unmodified item rect. */
321 *itemRect = TAB_GetItem(infoPtr,itemIndex)->rect;
323 /* calculate the times bottom and top based on the row */
324 GetClientRect(infoPtr->hwnd, &clientRect);
326 if ((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
328 itemRect->right = clientRect.right - SELECTED_TAB_OFFSET - itemRect->left * infoPtr->tabHeight -
329 ((lStyle & TCS_BUTTONS) ? itemRect->left * BUTTON_SPACINGX : 0);
330 itemRect->left = itemRect->right - infoPtr->tabHeight;
332 else if (lStyle & TCS_VERTICAL)
334 itemRect->left = clientRect.left + SELECTED_TAB_OFFSET + itemRect->left * infoPtr->tabHeight +
335 ((lStyle & TCS_BUTTONS) ? itemRect->left * BUTTON_SPACINGX : 0);
336 itemRect->right = itemRect->left + infoPtr->tabHeight;
338 else if (lStyle & TCS_BOTTOM)
340 itemRect->bottom = clientRect.bottom - itemRect->top * infoPtr->tabHeight -
341 ((lStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : SELECTED_TAB_OFFSET);
342 itemRect->top = itemRect->bottom - infoPtr->tabHeight;
344 else /* not TCS_BOTTOM and not TCS_VERTICAL */
346 itemRect->top = clientRect.top + itemRect->top * infoPtr->tabHeight +
347 ((lStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : SELECTED_TAB_OFFSET);
348 itemRect->bottom = itemRect->top + infoPtr->tabHeight;
352 * "scroll" it to make sure the item at the very left of the
353 * tab control is the leftmost visible tab.
355 if(lStyle & TCS_VERTICAL)
357 OffsetRect(itemRect,
359 -TAB_GetItem(infoPtr, infoPtr->leftmostVisible)->rect.top);
362 * Move the rectangle so the first item is slightly offset from
363 * the bottom of the tab control.
365 OffsetRect(itemRect,
367 SELECTED_TAB_OFFSET);
369 } else
371 OffsetRect(itemRect,
372 -TAB_GetItem(infoPtr, infoPtr->leftmostVisible)->rect.left,
376 * Move the rectangle so the first item is slightly offset from
377 * the left of the tab control.
379 OffsetRect(itemRect,
380 SELECTED_TAB_OFFSET,
383 TRACE("item %d tab h=%d, rect=(%ld,%ld)-(%ld,%ld)\n",
384 itemIndex, infoPtr->tabHeight,
385 itemRect->left, itemRect->top, itemRect->right, itemRect->bottom);
387 /* Now, calculate the position of the item as if it were selected. */
388 if (selectedRect!=NULL)
390 CopyRect(selectedRect, itemRect);
392 /* The rectangle of a selected item is a bit wider. */
393 if(lStyle & TCS_VERTICAL)
394 InflateRect(selectedRect, 0, SELECTED_TAB_OFFSET);
395 else
396 InflateRect(selectedRect, SELECTED_TAB_OFFSET, 0);
398 /* If it also a bit higher. */
399 if ((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
401 selectedRect->left -= 2; /* the border is thicker on the right */
402 selectedRect->right += SELECTED_TAB_OFFSET;
404 else if (lStyle & TCS_VERTICAL)
406 selectedRect->left -= SELECTED_TAB_OFFSET;
407 selectedRect->right += 1;
409 else if (lStyle & TCS_BOTTOM)
411 selectedRect->bottom += SELECTED_TAB_OFFSET;
413 else /* not TCS_BOTTOM and not TCS_VERTICAL */
415 selectedRect->top -= SELECTED_TAB_OFFSET;
416 selectedRect->bottom -= 1;
420 return TRUE;
423 static inline BOOL
424 TAB_GetItemRect(TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
426 return TAB_InternalGetItemRect(infoPtr, (INT)wParam, (LPRECT)lParam, (LPRECT)NULL);
429 /******************************************************************************
430 * TAB_KeyUp
432 * This method is called to handle keyboard input
434 static LRESULT TAB_KeyUp(TAB_INFO* infoPtr, WPARAM keyCode)
436 int newItem = -1;
438 switch (keyCode)
440 case VK_LEFT:
441 newItem = infoPtr->uFocus - 1;
442 break;
443 case VK_RIGHT:
444 newItem = infoPtr->uFocus + 1;
445 break;
449 * If we changed to a valid item, change the selection
451 if (newItem >= 0 &&
452 newItem < infoPtr->uNumItem &&
453 infoPtr->uFocus != newItem)
455 if (!TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGING))
457 infoPtr->iSelected = newItem;
458 infoPtr->uFocus = newItem;
459 TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGE);
461 TAB_EnsureSelectionVisible(infoPtr);
462 TAB_InvalidateTabArea(infoPtr);
466 return 0;
469 /******************************************************************************
470 * TAB_FocusChanging
472 * This method is called whenever the focus goes in or out of this control
473 * it is used to update the visual state of the control.
475 static VOID TAB_FocusChanging(const TAB_INFO *infoPtr)
477 RECT selectedRect;
478 BOOL isVisible;
481 * Get the rectangle for the item.
483 isVisible = TAB_InternalGetItemRect(infoPtr,
484 infoPtr->uFocus,
485 NULL,
486 &selectedRect);
489 * If the rectangle is not completely invisible, invalidate that
490 * portion of the window.
492 if (isVisible)
494 TRACE("invalidate (%ld,%ld)-(%ld,%ld)\n",
495 selectedRect.left,selectedRect.top,
496 selectedRect.right,selectedRect.bottom);
497 InvalidateRect(infoPtr->hwnd, &selectedRect, TRUE);
501 static INT TAB_InternalHitTest (
502 TAB_INFO* infoPtr,
503 POINT pt,
504 UINT* flags)
507 RECT rect;
508 INT iCount;
510 for (iCount = 0; iCount < infoPtr->uNumItem; iCount++)
512 TAB_InternalGetItemRect(infoPtr, iCount, &rect, NULL);
514 if (PtInRect(&rect, pt))
516 *flags = TCHT_ONITEM;
517 return iCount;
521 *flags = TCHT_NOWHERE;
522 return -1;
525 static inline LRESULT
526 TAB_HitTest (TAB_INFO *infoPtr, LPTCHITTESTINFO lptest)
528 return TAB_InternalHitTest (infoPtr, lptest->pt, &lptest->flags);
531 /******************************************************************************
532 * TAB_NCHitTest
534 * Napster v2b5 has a tab control for its main navigation which has a client
535 * area that covers the whole area of the dialog pages.
536 * That's why it receives all msgs for that area and the underlying dialog ctrls
537 * are dead.
538 * So I decided that we should handle WM_NCHITTEST here and return
539 * HTTRANSPARENT if we don't hit the tab control buttons.
540 * FIXME: WM_NCHITTEST handling correct ? Fix it if you know that Windows
541 * doesn't do it that way. Maybe depends on tab control styles ?
543 static inline LRESULT
544 TAB_NCHitTest (TAB_INFO *infoPtr, LPARAM lParam)
546 POINT pt;
547 UINT dummyflag;
549 pt.x = LOWORD(lParam);
550 pt.y = HIWORD(lParam);
551 ScreenToClient(infoPtr->hwnd, &pt);
553 if (TAB_InternalHitTest(infoPtr, pt, &dummyflag) == -1)
554 return HTTRANSPARENT;
555 else
556 return HTCLIENT;
559 static LRESULT
560 TAB_LButtonDown (TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
562 POINT pt;
563 INT newItem, dummy;
565 if (infoPtr->hwndToolTip)
566 TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
567 WM_LBUTTONDOWN, wParam, lParam);
569 if (GetWindowLongA(infoPtr->hwnd, GWL_STYLE) & TCS_FOCUSONBUTTONDOWN ) {
570 SetFocus (infoPtr->hwnd);
573 if (infoPtr->hwndToolTip)
574 TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
575 WM_LBUTTONDOWN, wParam, lParam);
577 pt.x = (INT)LOWORD(lParam);
578 pt.y = (INT)HIWORD(lParam);
580 newItem = TAB_InternalHitTest (infoPtr, pt, &dummy);
582 TRACE("On Tab, item %d\n", newItem);
584 if (newItem != -1 && infoPtr->iSelected != newItem)
586 if (!TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGING))
588 infoPtr->iSelected = newItem;
589 infoPtr->uFocus = newItem;
590 TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGE);
592 TAB_EnsureSelectionVisible(infoPtr);
594 TAB_InvalidateTabArea(infoPtr);
597 return 0;
600 static inline LRESULT
601 TAB_LButtonUp (const TAB_INFO *infoPtr)
603 TAB_SendSimpleNotify(infoPtr, NM_CLICK);
605 return 0;
608 static inline LRESULT
609 TAB_RButtonDown (const TAB_INFO *infoPtr)
611 TAB_SendSimpleNotify(infoPtr, NM_RCLICK);
612 return 0;
615 /******************************************************************************
616 * TAB_DrawLoneItemInterior
618 * This calls TAB_DrawItemInterior. However, TAB_DrawItemInterior is normally
619 * called by TAB_DrawItem which is normally called by TAB_Refresh which sets
620 * up the device context and font. This routine does the same setup but
621 * only calls TAB_DrawItemInterior for the single specified item.
623 static void
624 TAB_DrawLoneItemInterior(TAB_INFO* infoPtr, int iItem)
626 HDC hdc = GetDC(infoPtr->hwnd);
627 RECT r, rC;
629 /* Clip UpDown control to not draw over it */
630 if (infoPtr->needsScrolling)
632 GetWindowRect(infoPtr->hwnd, &rC);
633 GetWindowRect(infoPtr->hwndUpDown, &r);
634 ExcludeClipRect(hdc, r.left - rC.left, r.top - rC.top, r.right - rC.left, r.bottom - rC.top);
636 TAB_DrawItemInterior(infoPtr, hdc, iItem, NULL);
637 ReleaseDC(infoPtr->hwnd, hdc);
640 /******************************************************************************
641 * TAB_HotTrackTimerProc
643 * When a mouse-move event causes a tab to be highlighted (hot-tracking), a
644 * timer is setup so we can check if the mouse is moved out of our window.
645 * (We don't get an event when the mouse leaves, the mouse-move events just
646 * stop being delivered to our window and just start being delivered to
647 * another window.) This function is called when the timer triggers so
648 * we can check if the mouse has left our window. If so, we un-highlight
649 * the hot-tracked tab.
651 static VOID CALLBACK
652 TAB_HotTrackTimerProc
654 HWND hwnd, /* handle of window for timer messages */
655 UINT uMsg, /* WM_TIMER message */
656 UINT idEvent, /* timer identifier */
657 DWORD dwTime /* current system time */
660 TAB_INFO* infoPtr = TAB_GetInfoPtr(hwnd);
662 if (infoPtr != NULL && infoPtr->iHotTracked >= 0)
664 POINT pt;
667 ** If we can't get the cursor position, or if the cursor is outside our
668 ** window, we un-highlight the hot-tracked tab. Note that the cursor is
669 ** "outside" even if it is within our bounding rect if another window
670 ** overlaps. Note also that the case where the cursor stayed within our
671 ** window but has moved off the hot-tracked tab will be handled by the
672 ** WM_MOUSEMOVE event.
674 if (!GetCursorPos(&pt) || WindowFromPoint(pt) != hwnd)
676 /* Redraw iHotTracked to look normal */
677 INT iRedraw = infoPtr->iHotTracked;
678 infoPtr->iHotTracked = -1;
679 TAB_DrawLoneItemInterior(infoPtr, iRedraw);
681 /* Kill this timer */
682 KillTimer(hwnd, TAB_HOTTRACK_TIMER);
687 /******************************************************************************
688 * TAB_RecalcHotTrack
690 * If a tab control has the TCS_HOTTRACK style, then the tab under the mouse
691 * should be highlighted. This function determines which tab in a tab control,
692 * if any, is under the mouse and records that information. The caller may
693 * supply output parameters to receive the item number of the tab item which
694 * was highlighted but isn't any longer and of the tab item which is now
695 * highlighted but wasn't previously. The caller can use this information to
696 * selectively redraw those tab items.
698 * If the caller has a mouse position, it can supply it through the pos
699 * parameter. For example, TAB_MouseMove does this. Otherwise, the caller
700 * supplies NULL and this function determines the current mouse position
701 * itself.
703 static void
704 TAB_RecalcHotTrack
706 TAB_INFO* infoPtr,
707 const LPARAM* pos,
708 int* out_redrawLeave,
709 int* out_redrawEnter
712 int item = -1;
715 if (out_redrawLeave != NULL)
716 *out_redrawLeave = -1;
717 if (out_redrawEnter != NULL)
718 *out_redrawEnter = -1;
720 if (GetWindowLongA(infoPtr->hwnd, GWL_STYLE) & TCS_HOTTRACK)
722 POINT pt;
723 UINT flags;
725 if (pos == NULL)
727 GetCursorPos(&pt);
728 ScreenToClient(infoPtr->hwnd, &pt);
730 else
732 pt.x = LOWORD(*pos);
733 pt.y = HIWORD(*pos);
736 item = TAB_InternalHitTest(infoPtr, pt, &flags);
739 if (item != infoPtr->iHotTracked)
741 if (infoPtr->iHotTracked >= 0)
743 /* Mark currently hot-tracked to be redrawn to look normal */
744 if (out_redrawLeave != NULL)
745 *out_redrawLeave = infoPtr->iHotTracked;
747 if (item < 0)
749 /* Kill timer which forces recheck of mouse pos */
750 KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
753 else
755 /* Start timer so we recheck mouse pos */
756 UINT timerID = SetTimer
758 infoPtr->hwnd,
759 TAB_HOTTRACK_TIMER,
760 TAB_HOTTRACK_TIMER_INTERVAL,
761 TAB_HotTrackTimerProc
764 if (timerID == 0)
765 return; /* Hot tracking not available */
768 infoPtr->iHotTracked = item;
770 if (item >= 0)
772 /* Mark new hot-tracked to be redrawn to look highlighted */
773 if (out_redrawEnter != NULL)
774 *out_redrawEnter = item;
779 /******************************************************************************
780 * TAB_MouseMove
782 * Handles the mouse-move event. Updates tooltips. Updates hot-tracking.
784 static LRESULT
785 TAB_MouseMove (TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
787 int redrawLeave;
788 int redrawEnter;
790 if (infoPtr->hwndToolTip)
791 TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
792 WM_LBUTTONDOWN, wParam, lParam);
794 /* Determine which tab to highlight. Redraw tabs which change highlight
795 ** status. */
796 TAB_RecalcHotTrack(infoPtr, &lParam, &redrawLeave, &redrawEnter);
798 if (redrawLeave != -1)
799 TAB_DrawLoneItemInterior(infoPtr, redrawLeave);
800 if (redrawEnter != -1)
801 TAB_DrawLoneItemInterior(infoPtr, redrawEnter);
803 return 0;
806 /******************************************************************************
807 * TAB_AdjustRect
809 * Calculates the tab control's display area given the window rectangle or
810 * the window rectangle given the requested display rectangle.
812 static LRESULT TAB_AdjustRect(
813 TAB_INFO *infoPtr,
814 WPARAM fLarger,
815 LPRECT prc)
817 DWORD lStyle = GetWindowLongA(infoPtr->hwnd, GWL_STYLE);
818 LONG *iRightBottom, *iLeftTop;
820 TRACE ("hwnd=%p fLarger=%d (%ld,%ld)-(%ld,%ld)\n", infoPtr->hwnd, fLarger, prc->left, prc->top, prc->right, prc->bottom);
822 if(lStyle & TCS_VERTICAL)
824 iRightBottom = &(prc->right);
825 iLeftTop = &(prc->left);
827 else
829 iRightBottom = &(prc->bottom);
830 iLeftTop = &(prc->top);
833 if (fLarger) /* Go from display rectangle */
835 /* Add the height of the tabs. */
836 if (lStyle & TCS_BOTTOM)
837 *iRightBottom += infoPtr->tabHeight * infoPtr->uNumRows;
838 else
839 *iLeftTop -= infoPtr->tabHeight * infoPtr->uNumRows +
840 ((lStyle & TCS_BUTTONS)? 3 * (infoPtr->uNumRows - 1) : 0);
842 /* Inflate the rectangle for the padding */
843 InflateRect(prc, DISPLAY_AREA_PADDINGX, DISPLAY_AREA_PADDINGY);
845 /* Inflate for the border */
846 InflateRect(prc, CONTROL_BORDER_SIZEX, CONTROL_BORDER_SIZEY);
848 else /* Go from window rectangle. */
850 /* Deflate the rectangle for the border */
851 InflateRect(prc, -CONTROL_BORDER_SIZEX, -CONTROL_BORDER_SIZEY);
853 /* Deflate the rectangle for the padding */
854 InflateRect(prc, -DISPLAY_AREA_PADDINGX, -DISPLAY_AREA_PADDINGY);
856 /* Remove the height of the tabs. */
857 if (lStyle & TCS_BOTTOM)
858 *iRightBottom -= infoPtr->tabHeight * infoPtr->uNumRows;
859 else
860 *iLeftTop += (infoPtr->tabHeight) * infoPtr->uNumRows +
861 ((lStyle & TCS_BUTTONS)? 3 * (infoPtr->uNumRows - 1) : 0);
864 return 0;
867 /******************************************************************************
868 * TAB_OnHScroll
870 * This method will handle the notification from the scroll control and
871 * perform the scrolling operation on the tab control.
873 static LRESULT TAB_OnHScroll(
874 TAB_INFO *infoPtr,
875 int nScrollCode,
876 int nPos,
877 HWND hwndScroll)
879 if(nScrollCode == SB_THUMBPOSITION && nPos != infoPtr->leftmostVisible)
881 if(nPos < infoPtr->leftmostVisible)
882 infoPtr->leftmostVisible--;
883 else
884 infoPtr->leftmostVisible++;
886 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
887 TAB_InvalidateTabArea(infoPtr);
888 SendMessageW(infoPtr->hwndUpDown, UDM_SETPOS, 0,
889 MAKELONG(infoPtr->leftmostVisible, 0));
892 return 0;
895 /******************************************************************************
896 * TAB_SetupScrolling
898 * This method will check the current scrolling state and make sure the
899 * scrolling control is displayed (or not).
901 static void TAB_SetupScrolling(
902 HWND hwnd,
903 TAB_INFO* infoPtr,
904 const RECT* clientRect)
906 INT maxRange = 0;
907 DWORD lStyle = GetWindowLongA(hwnd, GWL_STYLE);
909 if (infoPtr->needsScrolling)
911 RECT controlPos;
912 INT vsize, tabwidth;
915 * Calculate the position of the scroll control.
917 if(lStyle & TCS_VERTICAL)
919 controlPos.right = clientRect->right;
920 controlPos.left = controlPos.right - 2 * GetSystemMetrics(SM_CXHSCROLL);
922 if (lStyle & TCS_BOTTOM)
924 controlPos.top = clientRect->bottom - infoPtr->tabHeight;
925 controlPos.bottom = controlPos.top + GetSystemMetrics(SM_CYHSCROLL);
927 else
929 controlPos.bottom = clientRect->top + infoPtr->tabHeight;
930 controlPos.top = controlPos.bottom - GetSystemMetrics(SM_CYHSCROLL);
933 else
935 controlPos.right = clientRect->right;
936 controlPos.left = controlPos.right - 2 * GetSystemMetrics(SM_CXHSCROLL);
938 if (lStyle & TCS_BOTTOM)
940 controlPos.top = clientRect->bottom - infoPtr->tabHeight;
941 controlPos.bottom = controlPos.top + GetSystemMetrics(SM_CYHSCROLL);
943 else
945 controlPos.bottom = clientRect->top + infoPtr->tabHeight;
946 controlPos.top = controlPos.bottom - GetSystemMetrics(SM_CYHSCROLL);
951 * If we don't have a scroll control yet, we want to create one.
952 * If we have one, we want to make sure it's positioned properly.
954 if (infoPtr->hwndUpDown==0)
956 infoPtr->hwndUpDown = CreateWindowA("msctls_updown32",
958 WS_VISIBLE | WS_CHILD | UDS_HORZ,
959 controlPos.left, controlPos.top,
960 controlPos.right - controlPos.left,
961 controlPos.bottom - controlPos.top,
962 hwnd,
963 NULL,
964 NULL,
965 NULL);
967 else
969 SetWindowPos(infoPtr->hwndUpDown,
970 NULL,
971 controlPos.left, controlPos.top,
972 controlPos.right - controlPos.left,
973 controlPos.bottom - controlPos.top,
974 SWP_SHOWWINDOW | SWP_NOZORDER);
977 /* Now calculate upper limit of the updown control range.
978 * We do this by calculating how many tabs will be offscreen when the
979 * last tab is visible.
981 if(infoPtr->uNumItem)
983 vsize = clientRect->right - (controlPos.right - controlPos.left + 1);
984 maxRange = infoPtr->uNumItem;
985 tabwidth = TAB_GetItem(infoPtr, infoPtr->uNumItem - 1)->rect.right;
987 for(; maxRange > 0; maxRange--)
989 if(tabwidth - TAB_GetItem(infoPtr,maxRange - 1)->rect.left > vsize)
990 break;
993 if(maxRange == infoPtr->uNumItem)
994 maxRange--;
997 else
999 /* If we once had a scroll control... hide it */
1000 if (infoPtr->hwndUpDown!=0)
1001 ShowWindow(infoPtr->hwndUpDown, SW_HIDE);
1003 if (infoPtr->hwndUpDown)
1004 SendMessageW(infoPtr->hwndUpDown, UDM_SETRANGE32, 0, maxRange);
1007 /******************************************************************************
1008 * TAB_SetItemBounds
1010 * This method will calculate the position rectangles of all the items in the
1011 * control. The rectangle calculated starts at 0 for the first item in the
1012 * list and ignores scrolling and selection.
1013 * It also uses the current font to determine the height of the tab row and
1014 * it checks if all the tabs fit in the client area of the window. If they
1015 * don't, a scrolling control is added.
1017 static void TAB_SetItemBounds (TAB_INFO *infoPtr)
1019 LONG lStyle = GetWindowLongA(infoPtr->hwnd, GWL_STYLE);
1020 TEXTMETRICA fontMetrics;
1021 UINT curItem;
1022 INT curItemLeftPos;
1023 INT curItemRowCount;
1024 HFONT hFont, hOldFont;
1025 HDC hdc;
1026 RECT clientRect;
1027 SIZE size;
1028 INT iTemp;
1029 RECT* rcItem;
1030 INT iIndex;
1031 INT icon_width = 0;
1034 * We need to get text information so we need a DC and we need to select
1035 * a font.
1037 hdc = GetDC(infoPtr->hwnd);
1039 hFont = infoPtr->hFont ? infoPtr->hFont : GetStockObject (SYSTEM_FONT);
1040 hOldFont = SelectObject (hdc, hFont);
1043 * We will base the rectangle calculations on the client rectangle
1044 * of the control.
1046 GetClientRect(infoPtr->hwnd, &clientRect);
1048 /* if TCS_VERTICAL then swap the height and width so this code places the
1049 tabs along the top of the rectangle and we can just rotate them after
1050 rather than duplicate all of the below code */
1051 if(lStyle & TCS_VERTICAL)
1053 iTemp = clientRect.bottom;
1054 clientRect.bottom = clientRect.right;
1055 clientRect.right = iTemp;
1058 /* Now use hPadding and vPadding */
1059 infoPtr->uHItemPadding = infoPtr->uHItemPadding_s;
1060 infoPtr->uVItemPadding = infoPtr->uVItemPadding_s;
1062 /* The leftmost item will be "0" aligned */
1063 curItemLeftPos = 0;
1064 curItemRowCount = infoPtr->uNumItem ? 1 : 0;
1066 if (!(infoPtr->fHeightSet))
1068 int item_height;
1069 int icon_height = 0;
1071 /* Use the current font to determine the height of a tab. */
1072 GetTextMetricsA(hdc, &fontMetrics);
1074 /* Get the icon height */
1075 if (infoPtr->himl)
1076 ImageList_GetIconSize(infoPtr->himl, 0, &icon_height);
1078 /* Take the highest between font or icon */
1079 if (fontMetrics.tmHeight > icon_height)
1080 item_height = fontMetrics.tmHeight + 2;
1081 else
1082 item_height = icon_height;
1085 * Make sure there is enough space for the letters + icon + growing the
1086 * selected item + extra space for the selected item.
1088 infoPtr->tabHeight = item_height +
1089 ((lStyle & TCS_BUTTONS) ? 2 : 1) *
1090 infoPtr->uVItemPadding;
1092 TRACE("tabH=%d, tmH=%ld, iconh=%d\n",
1093 infoPtr->tabHeight, fontMetrics.tmHeight, icon_height);
1096 TRACE("client right=%ld\n", clientRect.right);
1098 /* Get the icon width */
1099 if (infoPtr->himl)
1101 ImageList_GetIconSize(infoPtr->himl, &icon_width, 0);
1103 if (lStyle & TCS_FIXEDWIDTH)
1104 icon_width += 4;
1105 else
1106 /* Add padding if icon is present */
1107 icon_width += infoPtr->uHItemPadding;
1110 for (curItem = 0; curItem < infoPtr->uNumItem; curItem++)
1112 TAB_ITEM *curr = TAB_GetItem(infoPtr, curItem);
1114 /* Set the leftmost position of the tab. */
1115 curr->rect.left = curItemLeftPos;
1117 if ((lStyle & TCS_FIXEDWIDTH) || !curr->pszText)
1119 curr->rect.right = curr->rect.left +
1120 max(infoPtr->tabWidth, icon_width);
1122 else
1124 int num = 2;
1126 /* Calculate how wide the tab is depending on the text it contains */
1127 GetTextExtentPoint32W(hdc, curr->pszText,
1128 lstrlenW(curr->pszText), &size);
1130 curr->rect.right = curr->rect.left + size.cx + icon_width +
1131 num * infoPtr->uHItemPadding;
1132 TRACE("for <%s>, l,r=%ld,%ld, num=%d\n",
1133 debugstr_w(curr->pszText), curr->rect.left, curr->rect.right, num);
1137 * Check if this is a multiline tab control and if so
1138 * check to see if we should wrap the tabs
1140 * Wrap all these tabs. We will arrange them evenly later.
1144 if (((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)) &&
1145 (curr->rect.right >
1146 (clientRect.right - CONTROL_BORDER_SIZEX - DISPLAY_AREA_PADDINGX)))
1148 curr->rect.right -= curr->rect.left;
1150 curr->rect.left = 0;
1151 curItemRowCount++;
1152 TRACE("wrapping <%s>, l,r=%ld,%ld\n", debugstr_w(curr->pszText),
1153 curr->rect.left, curr->rect.right);
1156 curr->rect.bottom = 0;
1157 curr->rect.top = curItemRowCount - 1;
1159 TRACE("TextSize: %li\n", size.cx);
1160 TRACE("Rect: T %li, L %li, B %li, R %li\n", curr->rect.top,
1161 curr->rect.left, curr->rect.bottom, curr->rect.right);
1164 * The leftmost position of the next item is the rightmost position
1165 * of this one.
1167 if (lStyle & TCS_BUTTONS)
1169 curItemLeftPos = curr->rect.right + BUTTON_SPACINGX;
1170 if (lStyle & TCS_FLATBUTTONS)
1171 curItemLeftPos += FLAT_BTN_SPACINGX;
1173 else
1174 curItemLeftPos = curr->rect.right;
1177 if (!((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)))
1180 * Check if we need a scrolling control.
1182 infoPtr->needsScrolling = (curItemLeftPos + (2 * SELECTED_TAB_OFFSET) >
1183 clientRect.right);
1185 /* Don't need scrolling, then update infoPtr->leftmostVisible */
1186 if(!infoPtr->needsScrolling)
1187 infoPtr->leftmostVisible = 0;
1189 else
1192 * No scrolling in Multiline or Vertical styles.
1194 infoPtr->needsScrolling = FALSE;
1195 infoPtr->leftmostVisible = 0;
1197 TAB_SetupScrolling(infoPtr->hwnd, infoPtr, &clientRect);
1199 /* Set the number of rows */
1200 infoPtr->uNumRows = curItemRowCount;
1202 /* Arrange all tabs evenly if style says so */
1203 if (!(lStyle & TCS_RAGGEDRIGHT) && ((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)) && (infoPtr->uNumItem > 0))
1205 INT tabPerRow,remTab,iRow;
1206 UINT iItm;
1207 INT iCount=0;
1210 * Ok windows tries to even out the rows. place the same
1211 * number of tabs in each row. So lets give that a shot
1214 tabPerRow = infoPtr->uNumItem / (infoPtr->uNumRows);
1215 remTab = infoPtr->uNumItem % (infoPtr->uNumRows);
1217 for (iItm=0,iRow=0,iCount=0,curItemLeftPos=0;
1218 iItm<infoPtr->uNumItem;
1219 iItm++,iCount++)
1221 /* normalize the current rect */
1222 TAB_ITEM *curr = TAB_GetItem(infoPtr, iItm);
1224 /* shift the item to the left side of the clientRect */
1225 curr->rect.right -= curr->rect.left;
1226 curr->rect.left = 0;
1228 TRACE("r=%ld, cl=%d, cl.r=%ld, iCount=%d, iRow=%d, uNumRows=%d, remTab=%d, tabPerRow=%d\n",
1229 curr->rect.right, curItemLeftPos, clientRect.right,
1230 iCount, iRow, infoPtr->uNumRows, remTab, tabPerRow);
1232 /* if we have reached the maximum number of tabs on this row */
1233 /* move to the next row, reset our current item left position and */
1234 /* the count of items on this row */
1236 if (lStyle & TCS_VERTICAL) {
1237 /* Vert: Add the remaining tabs in the *last* remainder rows */
1238 if (iCount >= ((iRow>=(INT)infoPtr->uNumRows - remTab)?tabPerRow + 1:tabPerRow)) {
1239 iRow++;
1240 curItemLeftPos = 0;
1241 iCount = 0;
1243 } else {
1244 /* Horz: Add the remaining tabs in the *first* remainder rows */
1245 if (iCount >= ((iRow<remTab)?tabPerRow + 1:tabPerRow)) {
1246 iRow++;
1247 curItemLeftPos = 0;
1248 iCount = 0;
1252 /* shift the item to the right to place it as the next item in this row */
1253 curr->rect.left += curItemLeftPos;
1254 curr->rect.right += curItemLeftPos;
1255 curr->rect.top = iRow;
1256 if (lStyle & TCS_BUTTONS)
1258 curItemLeftPos = curr->rect.right + 1;
1259 if (lStyle & TCS_FLATBUTTONS)
1260 curItemLeftPos += FLAT_BTN_SPACINGX;
1262 else
1263 curItemLeftPos = curr->rect.right;
1265 TRACE("arranging <%s>, l,r=%ld,%ld, row=%ld\n",
1266 debugstr_w(curr->pszText), curr->rect.left,
1267 curr->rect.right, curr->rect.top);
1271 * Justify the rows
1274 INT widthDiff, iIndexStart=0, iIndexEnd=0;
1275 INT remainder;
1276 INT iCount=0;
1278 while(iIndexStart < infoPtr->uNumItem)
1280 TAB_ITEM *start = TAB_GetItem(infoPtr, iIndexStart);
1283 * find the index of the row
1285 /* find the first item on the next row */
1286 for (iIndexEnd=iIndexStart;
1287 (iIndexEnd < infoPtr->uNumItem) &&
1288 (TAB_GetItem(infoPtr, iIndexEnd)->rect.top ==
1289 start->rect.top) ;
1290 iIndexEnd++)
1291 /* intentionally blank */;
1294 * we need to justify these tabs so they fill the whole given
1295 * client area
1298 /* find the amount of space remaining on this row */
1299 widthDiff = clientRect.right - (2 * SELECTED_TAB_OFFSET) -
1300 TAB_GetItem(infoPtr, iIndexEnd - 1)->rect.right;
1302 /* iCount is the number of tab items on this row */
1303 iCount = iIndexEnd - iIndexStart;
1305 if (iCount > 1)
1307 remainder = widthDiff % iCount;
1308 widthDiff = widthDiff / iCount;
1309 /* add widthDiff/iCount, or extra space/items on row, to each item on this row */
1310 for (iIndex=iIndexStart, iCount=0; iIndex < iIndexEnd; iIndex++, iCount++)
1312 TAB_ITEM *item = TAB_GetItem(infoPtr, iIndex);
1314 item->rect.left += iCount * widthDiff;
1315 item->rect.right += (iCount + 1) * widthDiff;
1317 TRACE("adjusting 1 <%s>, l,r=%ld,%ld\n",
1318 debugstr_w(item->pszText),
1319 item->rect.left, item->rect.right);
1322 TAB_GetItem(infoPtr, iIndex - 1)->rect.right += remainder;
1324 else /* we have only one item on this row, make it take up the entire row */
1326 start->rect.left = clientRect.left;
1327 start->rect.right = clientRect.right - 4;
1329 TRACE("adjusting 2 <%s>, l,r=%ld,%ld\n",
1330 debugstr_w(start->pszText),
1331 start->rect.left, start->rect.right);
1336 iIndexStart = iIndexEnd;
1341 /* if TCS_VERTICAL rotate the tabs so they are along the side of the clientRect */
1342 if(lStyle & TCS_VERTICAL)
1344 RECT rcOriginal;
1345 for(iIndex = 0; iIndex < infoPtr->uNumItem; iIndex++)
1347 rcItem = &TAB_GetItem(infoPtr, iIndex)->rect;
1349 rcOriginal = *rcItem;
1351 /* this is rotating the items by 90 degrees clockwise around the center of the control */
1352 rcItem->top = (rcOriginal.left - clientRect.left);
1353 rcItem->bottom = rcItem->top + (rcOriginal.right - rcOriginal.left);
1354 rcItem->left = rcOriginal.top;
1355 rcItem->right = rcOriginal.bottom;
1359 TAB_EnsureSelectionVisible(infoPtr);
1360 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
1362 /* Cleanup */
1363 SelectObject (hdc, hOldFont);
1364 ReleaseDC (infoPtr->hwnd, hdc);
1368 static void
1369 TAB_EraseTabInterior
1371 TAB_INFO* infoPtr,
1372 HDC hdc,
1373 INT iItem,
1374 RECT* drawRect
1377 LONG lStyle = GetWindowLongA(infoPtr->hwnd, GWL_STYLE);
1378 HBRUSH hbr = CreateSolidBrush (comctl32_color.clrBtnFace);
1379 BOOL deleteBrush = TRUE;
1380 RECT rTemp = *drawRect;
1382 InflateRect(&rTemp, -2, -2);
1383 if (lStyle & TCS_BUTTONS)
1385 if (iItem == infoPtr->iSelected)
1387 /* Background color */
1388 if (!(lStyle & TCS_OWNERDRAWFIXED))
1390 DeleteObject(hbr);
1391 hbr = GetSysColorBrush(COLOR_SCROLLBAR);
1393 SetTextColor(hdc, comctl32_color.clr3dFace);
1394 SetBkColor(hdc, comctl32_color.clr3dHilight);
1396 /* if COLOR_WINDOW happens to be the same as COLOR_3DHILIGHT
1397 * we better use 0x55aa bitmap brush to make scrollbar's background
1398 * look different from the window background.
1400 if (comctl32_color.clr3dHilight == comctl32_color.clrWindow)
1401 hbr = COMCTL32_hPattern55AABrush;
1403 deleteBrush = FALSE;
1405 FillRect(hdc, &rTemp, hbr);
1407 else /* ! selected */
1409 if (lStyle & TCS_FLATBUTTONS)
1411 FillRect(hdc, drawRect, hbr);
1412 if (iItem == infoPtr->iHotTracked)
1413 DrawEdge(hdc, drawRect, EDGE_RAISED, BF_SOFT|BF_RECT);
1415 else
1416 FillRect(hdc, &rTemp, hbr);
1420 else /* !TCS_BUTTONS */
1422 FillRect(hdc, &rTemp, hbr);
1425 /* Cleanup */
1426 if (deleteBrush) DeleteObject(hbr);
1429 /******************************************************************************
1430 * TAB_DrawItemInterior
1432 * This method is used to draw the interior (text and icon) of a single tab
1433 * into the tab control.
1435 static void
1436 TAB_DrawItemInterior
1438 TAB_INFO* infoPtr,
1439 HDC hdc,
1440 INT iItem,
1441 RECT* drawRect
1444 LONG lStyle = GetWindowLongA(infoPtr->hwnd, GWL_STYLE);
1446 RECT localRect;
1448 HPEN htextPen;
1449 HPEN holdPen;
1450 INT oldBkMode;
1451 HFONT hOldFont;
1453 /* if (drawRect == NULL) */
1455 BOOL isVisible;
1456 RECT itemRect;
1457 RECT selectedRect;
1460 * Get the rectangle for the item.
1462 isVisible = TAB_InternalGetItemRect(infoPtr, iItem, &itemRect, &selectedRect);
1463 if (!isVisible)
1464 return;
1467 * Make sure drawRect points to something valid; simplifies code.
1469 drawRect = &localRect;
1472 * This logic copied from the part of TAB_DrawItem which draws
1473 * the tab background. It's important to keep it in sync. I
1474 * would have liked to avoid code duplication, but couldn't figure
1475 * out how without making spaghetti of TAB_DrawItem.
1477 if (iItem == infoPtr->iSelected)
1478 *drawRect = selectedRect;
1479 else
1480 *drawRect = itemRect;
1482 if (lStyle & TCS_BUTTONS)
1484 if (iItem == infoPtr->iSelected)
1486 drawRect->left += 4;
1487 drawRect->top += 4;
1488 drawRect->right -= 4;
1489 drawRect->bottom -= 1;
1491 else
1493 drawRect->left += 2;
1494 drawRect->top += 2;
1495 drawRect->right -= 2;
1496 drawRect->bottom -= 2;
1499 else
1501 if ((lStyle & TCS_VERTICAL) && (lStyle & TCS_BOTTOM))
1503 if (iItem != infoPtr->iSelected)
1505 drawRect->left += 2;
1506 drawRect->top += 2;
1507 drawRect->bottom -= 2;
1510 else if (lStyle & TCS_VERTICAL)
1512 if (iItem == infoPtr->iSelected)
1514 drawRect->right += 1;
1516 else
1518 drawRect->top += 2;
1519 drawRect->right -= 2;
1520 drawRect->bottom -= 2;
1523 else if (lStyle & TCS_BOTTOM)
1525 if (iItem == infoPtr->iSelected)
1527 drawRect->top -= 2;
1529 else
1531 InflateRect(drawRect, -2, -2);
1532 drawRect->bottom += 2;
1535 else
1537 if (iItem == infoPtr->iSelected)
1539 drawRect->bottom += 3;
1541 else
1543 drawRect->bottom -= 2;
1544 InflateRect(drawRect, -2, 0);
1549 TRACE("drawRect=(%ld,%ld)-(%ld,%ld)\n",
1550 drawRect->left, drawRect->top, drawRect->right, drawRect->bottom);
1552 /* Clear interior */
1553 TAB_EraseTabInterior (infoPtr, hdc, iItem, drawRect);
1555 /* Draw the focus rectangle */
1556 if (!(lStyle & TCS_FOCUSNEVER) &&
1557 (GetFocus() == infoPtr->hwnd) &&
1558 (iItem == infoPtr->uFocus) )
1560 RECT rFocus = *drawRect;
1561 InflateRect(&rFocus, -3, -3);
1562 if (lStyle & TCS_BOTTOM && !(lStyle & TCS_VERTICAL))
1563 rFocus.top -= 3;
1564 if (lStyle & TCS_BUTTONS)
1566 rFocus.left -= 3;
1567 rFocus.top -= 3;
1570 DrawFocusRect(hdc, &rFocus);
1574 * Text pen
1576 htextPen = CreatePen( PS_SOLID, 1, GetSysColor(COLOR_BTNTEXT) );
1577 holdPen = SelectObject(hdc, htextPen);
1578 hOldFont = SelectObject(hdc, infoPtr->hFont);
1581 * Setup for text output
1583 oldBkMode = SetBkMode(hdc, TRANSPARENT);
1584 SetTextColor(hdc, (((iItem == infoPtr->iHotTracked) && !(lStyle & TCS_FLATBUTTONS)) |
1585 (TAB_GetItem(infoPtr, iItem)->dwState & TCIS_HIGHLIGHTED)) ?
1586 comctl32_color.clrHighlight : comctl32_color.clrBtnText);
1589 * if owner draw, tell the owner to draw
1591 if ((lStyle & TCS_OWNERDRAWFIXED) && GetParent(infoPtr->hwnd))
1593 DRAWITEMSTRUCT dis;
1594 UINT id;
1596 drawRect->top += 2;
1597 drawRect->right -= 1;
1598 if ( iItem == infoPtr->iSelected )
1600 drawRect->right -= 1;
1601 drawRect->left += 1;
1605 * get the control id
1607 id = (UINT)GetWindowLongPtrW( infoPtr->hwnd, GWLP_ID );
1610 * put together the DRAWITEMSTRUCT
1612 dis.CtlType = ODT_TAB;
1613 dis.CtlID = id;
1614 dis.itemID = iItem;
1615 dis.itemAction = ODA_DRAWENTIRE;
1616 dis.itemState = 0;
1617 if ( iItem == infoPtr->iSelected )
1618 dis.itemState |= ODS_SELECTED;
1619 if (infoPtr->uFocus == iItem)
1620 dis.itemState |= ODS_FOCUS;
1621 dis.hwndItem = infoPtr->hwnd;
1622 dis.hDC = hdc;
1623 CopyRect(&dis.rcItem,drawRect);
1624 dis.itemData = (ULONG_PTR)TAB_GetItem(infoPtr, iItem)->extra;
1627 * send the draw message
1629 SendMessageW( infoPtr->hwndNotify, WM_DRAWITEM, (WPARAM)id, (LPARAM)&dis );
1631 else
1633 TAB_ITEM *item = TAB_GetItem(infoPtr, iItem);
1634 RECT rcTemp;
1635 RECT rcImage;
1637 /* used to center the icon and text in the tab */
1638 RECT rcText;
1639 INT center_offset_h, center_offset_v;
1641 /* set rcImage to drawRect, we will use top & left in our ImageList_Draw call */
1642 rcImage = *drawRect;
1644 rcTemp = *drawRect;
1646 rcText.left = rcText.top = rcText.right = rcText.bottom = 0;
1648 /* get the rectangle that the text fits in */
1649 if (item->pszText)
1651 DrawTextW(hdc, item->pszText, -1, &rcText, DT_CALCRECT);
1654 * If not owner draw, then do the drawing ourselves.
1656 * Draw the icon.
1658 if (infoPtr->himl && (item->mask & TCIF_IMAGE))
1660 INT cx;
1661 INT cy;
1663 ImageList_GetIconSize(infoPtr->himl, &cx, &cy);
1665 if(lStyle & TCS_VERTICAL)
1667 center_offset_h = ((drawRect->bottom - drawRect->top) - (cy + infoPtr->uHItemPadding + (rcText.right - rcText.left))) / 2;
1668 center_offset_v = ((drawRect->right - drawRect->left) - (cx + infoPtr->uVItemPadding)) / 2;
1670 else
1672 center_offset_h = ((drawRect->right - drawRect->left) - (cx + infoPtr->uHItemPadding + (rcText.right - rcText.left))) / 2;
1673 center_offset_v = ((drawRect->bottom - drawRect->top) - (cy + infoPtr->uVItemPadding)) / 2;
1676 if (lStyle & TCS_FIXEDWIDTH && lStyle & (TCS_FORCELABELLEFT | TCS_FORCEICONLEFT))
1677 center_offset_h = infoPtr->uHItemPadding;
1679 if (center_offset_h < 2)
1680 center_offset_h = 2;
1682 if (center_offset_v < 0)
1683 center_offset_v = 0;
1685 TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%ld,%ld)-(%ld,%ld), textlen=%ld\n",
1686 debugstr_w(item->pszText), center_offset_h, center_offset_v,
1687 drawRect->left, drawRect->top, drawRect->right, drawRect->bottom,
1688 (rcText.right-rcText.left));
1690 if((lStyle & TCS_VERTICAL) && (lStyle & TCS_BOTTOM))
1692 rcImage.top = drawRect->top + center_offset_h;
1693 /* if tab is TCS_VERTICAL and TCS_BOTTOM, the text is drawn from the */
1694 /* right side of the tab, but the image still uses the left as its x position */
1695 /* this keeps the image always drawn off of the same side of the tab */
1696 rcImage.left = drawRect->right - cx - center_offset_v;
1697 drawRect->top += cy + infoPtr->uHItemPadding;
1699 else if(lStyle & TCS_VERTICAL)
1701 rcImage.top = drawRect->bottom - cy - center_offset_h;
1702 rcImage.left = drawRect->left + center_offset_v;
1703 drawRect->bottom -= cy + infoPtr->uHItemPadding;
1705 else /* normal style, whether TCS_BOTTOM or not */
1707 rcImage.left = drawRect->left + center_offset_h;
1708 rcImage.top = drawRect->top + center_offset_v;
1709 drawRect->left += cx + infoPtr->uHItemPadding;
1712 TRACE("drawing image=%d, left=%ld, top=%ld\n",
1713 item->iImage, rcImage.left, rcImage.top-1);
1714 ImageList_Draw
1716 infoPtr->himl,
1717 item->iImage,
1718 hdc,
1719 rcImage.left,
1720 rcImage.top,
1721 ILD_NORMAL
1725 /* Now position text */
1726 if (lStyle & TCS_FIXEDWIDTH && lStyle & TCS_FORCELABELLEFT)
1727 center_offset_h = infoPtr->uHItemPadding;
1728 else
1729 if(lStyle & TCS_VERTICAL)
1730 center_offset_h = ((drawRect->bottom - drawRect->top) - (rcText.right - rcText.left)) / 2;
1731 else
1732 center_offset_h = ((drawRect->right - drawRect->left) - (rcText.right - rcText.left)) / 2;
1734 if(lStyle & TCS_VERTICAL)
1736 if(lStyle & TCS_BOTTOM)
1737 drawRect->top+=center_offset_h;
1738 else
1739 drawRect->bottom-=center_offset_h;
1741 center_offset_v = ((drawRect->right - drawRect->left) - ((rcText.bottom - rcText.top) + infoPtr->uVItemPadding)) / 2;
1743 else
1745 drawRect->left += center_offset_h;
1746 center_offset_v = ((drawRect->bottom - drawRect->top) - ((rcText.bottom - rcText.top) + infoPtr->uVItemPadding)) / 2;
1749 if (center_offset_v < 0)
1750 center_offset_v = 0;
1752 if(lStyle & TCS_VERTICAL)
1753 drawRect->left += center_offset_v;
1754 else
1755 drawRect->top += center_offset_v;
1757 /* Draw the text */
1758 if(lStyle & TCS_VERTICAL) /* if we are vertical rotate the text and each character */
1760 LOGFONTA logfont;
1761 HFONT hFont = 0;
1762 INT nEscapement = 900;
1763 INT nOrientation = 900;
1765 if(lStyle & TCS_BOTTOM)
1767 nEscapement = -900;
1768 nOrientation = -900;
1771 /* to get a font with the escapement and orientation we are looking for, we need to */
1772 /* call CreateFontIndirectA, which requires us to set the values of the logfont we pass in */
1773 if (!GetObjectA((infoPtr->hFont) ?
1774 infoPtr->hFont : GetStockObject(SYSTEM_FONT),
1775 sizeof(LOGFONTA),&logfont))
1777 INT iPointSize = 9;
1779 lstrcpyA(logfont.lfFaceName, "Arial");
1780 logfont.lfHeight = -MulDiv(iPointSize, GetDeviceCaps(hdc, LOGPIXELSY),
1781 72);
1782 logfont.lfWeight = FW_NORMAL;
1783 logfont.lfItalic = 0;
1784 logfont.lfUnderline = 0;
1785 logfont.lfStrikeOut = 0;
1788 logfont.lfEscapement = nEscapement;
1789 logfont.lfOrientation = nOrientation;
1790 hFont = CreateFontIndirectA(&logfont);
1791 SelectObject(hdc, hFont);
1793 if (item->pszText)
1795 ExtTextOutW(hdc,
1796 (lStyle & TCS_BOTTOM) ? drawRect->right : drawRect->left,
1797 (!(lStyle & TCS_BOTTOM)) ? drawRect->bottom : drawRect->top,
1798 ETO_CLIPPED,
1799 drawRect,
1800 item->pszText,
1801 lstrlenW(item->pszText),
1805 DeleteObject(hFont);
1807 else
1809 TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%ld,%ld)-(%ld,%ld), textlen=%ld\n",
1810 debugstr_w(item->pszText), center_offset_h, center_offset_v,
1811 drawRect->left, drawRect->top, drawRect->right, drawRect->bottom,
1812 (rcText.right-rcText.left));
1813 if (item->pszText)
1815 DrawTextW
1817 hdc,
1818 item->pszText,
1819 lstrlenW(item->pszText),
1820 drawRect,
1821 DT_LEFT | DT_SINGLELINE
1826 *drawRect = rcTemp; /* restore drawRect */
1830 * Cleanup
1832 SelectObject(hdc, hOldFont);
1833 SetBkMode(hdc, oldBkMode);
1834 SelectObject(hdc, holdPen);
1835 DeleteObject( htextPen );
1838 /******************************************************************************
1839 * TAB_DrawItem
1841 * This method is used to draw a single tab into the tab control.
1843 static void TAB_DrawItem(
1844 TAB_INFO *infoPtr,
1845 HDC hdc,
1846 INT iItem)
1848 LONG lStyle = GetWindowLongA(infoPtr->hwnd, GWL_STYLE);
1849 RECT itemRect;
1850 RECT selectedRect;
1851 BOOL isVisible;
1852 RECT r, fillRect, r1;
1853 INT clRight = 0;
1854 INT clBottom = 0;
1855 COLORREF bkgnd, corner;
1858 * Get the rectangle for the item.
1860 isVisible = TAB_InternalGetItemRect(infoPtr,
1861 iItem,
1862 &itemRect,
1863 &selectedRect);
1865 if (isVisible)
1867 RECT rUD, rC;
1869 /* Clip UpDown control to not draw over it */
1870 if (infoPtr->needsScrolling)
1872 GetWindowRect(infoPtr->hwnd, &rC);
1873 GetWindowRect(infoPtr->hwndUpDown, &rUD);
1874 ExcludeClipRect(hdc, rUD.left - rC.left, rUD.top - rC.top, rUD.right - rC.left, rUD.bottom - rC.top);
1877 /* If you need to see what the control is doing,
1878 * then override these variables. They will change what
1879 * fill colors are used for filling the tabs, and the
1880 * corners when drawing the edge.
1882 bkgnd = comctl32_color.clrBtnFace;
1883 corner = comctl32_color.clrBtnFace;
1885 if (lStyle & TCS_BUTTONS)
1887 /* Get item rectangle */
1888 r = itemRect;
1890 /* Separators between flat buttons */
1891 if (lStyle & TCS_FLATBUTTONS)
1893 r1 = r;
1894 r1.right += (FLAT_BTN_SPACINGX -2);
1895 DrawEdge(hdc, &r1, EDGE_ETCHED, BF_RIGHT);
1898 if (iItem == infoPtr->iSelected)
1900 DrawEdge(hdc, &r, EDGE_SUNKEN, BF_SOFT|BF_RECT);
1902 OffsetRect(&r, 1, 1);
1904 else /* ! selected */
1906 if (!(lStyle & TCS_FLATBUTTONS))
1907 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_RECT);
1910 else /* !TCS_BUTTONS */
1912 /* We draw a rectangle of different sizes depending on the selection
1913 * state. */
1914 if (iItem == infoPtr->iSelected) {
1915 RECT rect;
1916 GetClientRect (infoPtr->hwnd, &rect);
1917 clRight = rect.right;
1918 clBottom = rect.bottom;
1919 r = selectedRect;
1921 else
1922 r = itemRect;
1925 * Erase the background. (Delay it but setup rectangle.)
1926 * This is necessary when drawing the selected item since it is larger
1927 * than the others, it might overlap with stuff already drawn by the
1928 * other tabs
1930 fillRect = r;
1932 if(lStyle & TCS_VERTICAL)
1934 /* These are for adjusting the drawing of a Selected tab */
1935 /* The initial values are for the normal case of non-Selected */
1936 int ZZ = 1; /* Do not strech if selected */
1937 if (iItem == infoPtr->iSelected) {
1938 ZZ = 0;
1940 /* if leftmost draw the line longer */
1941 if(selectedRect.top == 0)
1942 fillRect.top += CONTROL_BORDER_SIZEY;
1943 /* if rightmost draw the line longer */
1944 if(selectedRect.bottom == clBottom)
1945 fillRect.bottom -= CONTROL_BORDER_SIZEY;
1948 if (lStyle & TCS_BOTTOM)
1950 /* Adjust both rectangles to match native */
1951 r.left += (1-ZZ);
1953 TRACE("<right> item=%d, fill=(%ld,%ld)-(%ld,%ld), edge=(%ld,%ld)-(%ld,%ld)\n",
1954 iItem,
1955 fillRect.left,fillRect.top,fillRect.right,fillRect.bottom,
1956 r.left,r.top,r.right,r.bottom);
1958 /* Clear interior */
1959 SetBkColor(hdc, bkgnd);
1960 ExtTextOutA(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
1962 /* Draw rectangular edge around tab */
1963 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_RIGHT|BF_TOP|BF_BOTTOM);
1965 /* Now erase the top corner and draw diagonal edge */
1966 SetBkColor(hdc, corner);
1967 r1.left = r.right - ROUND_CORNER_SIZE - 1;
1968 r1.top = r.top;
1969 r1.right = r.right;
1970 r1.bottom = r1.top + ROUND_CORNER_SIZE;
1971 ExtTextOutA(hdc, 0, 0, 2, &r1, NULL, 0, 0);
1972 r1.right--;
1973 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPLEFT);
1975 /* Now erase the bottom corner and draw diagonal edge */
1976 r1.left = r.right - ROUND_CORNER_SIZE - 1;
1977 r1.bottom = r.bottom;
1978 r1.right = r.right;
1979 r1.top = r1.bottom - ROUND_CORNER_SIZE;
1980 ExtTextOutA(hdc, 0, 0, 2, &r1, NULL, 0, 0);
1981 r1.right--;
1982 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMLEFT);
1984 if ((iItem == infoPtr->iSelected) && (selectedRect.top == 0)) {
1985 r1 = r;
1986 r1.right = r1.left;
1987 r1.left--;
1988 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_TOP);
1992 else
1994 TRACE("<left> item=%d, fill=(%ld,%ld)-(%ld,%ld), edge=(%ld,%ld)-(%ld,%ld)\n",
1995 iItem,
1996 fillRect.left,fillRect.top,fillRect.right,fillRect.bottom,
1997 r.left,r.top,r.right,r.bottom);
1999 /* Clear interior */
2000 SetBkColor(hdc, bkgnd);
2001 ExtTextOutA(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2003 /* Draw rectangular edge around tab */
2004 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_TOP|BF_BOTTOM);
2006 /* Now erase the top corner and draw diagonal edge */
2007 SetBkColor(hdc, corner);
2008 r1.left = r.left;
2009 r1.top = r.top;
2010 r1.right = r1.left + ROUND_CORNER_SIZE + 1;
2011 r1.bottom = r1.top + ROUND_CORNER_SIZE;
2012 ExtTextOutA(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2013 r1.left++;
2014 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPRIGHT);
2016 /* Now erase the bottom corner and draw diagonal edge */
2017 r1.left = r.left;
2018 r1.bottom = r.bottom;
2019 r1.right = r1.left + ROUND_CORNER_SIZE + 1;
2020 r1.top = r1.bottom - ROUND_CORNER_SIZE;
2021 ExtTextOutA(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2022 r1.left++;
2023 DrawEdge(hdc, &r1, EDGE_SUNKEN, BF_DIAGONAL_ENDTOPLEFT);
2026 else /* ! TCS_VERTICAL */
2028 /* These are for adjusting the drawing of a Selected tab */
2029 /* The initial values are for the normal case of non-Selected */
2030 if (iItem == infoPtr->iSelected) {
2031 /* if leftmost draw the line longer */
2032 if(selectedRect.left == 0)
2033 fillRect.left += CONTROL_BORDER_SIZEX;
2034 /* if rightmost draw the line longer */
2035 if(selectedRect.right == clRight)
2036 fillRect.right -= CONTROL_BORDER_SIZEX;
2039 if (lStyle & TCS_BOTTOM)
2041 /* Adjust both rectangles for topmost row */
2042 if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2044 fillRect.top -= 2;
2045 r.top -= 1;
2048 TRACE("<bottom> item=%d, fill=(%ld,%ld)-(%ld,%ld), edge=(%ld,%ld)-(%ld,%ld)\n",
2049 iItem,
2050 fillRect.left,fillRect.top,fillRect.right,fillRect.bottom,
2051 r.left,r.top,r.right,r.bottom);
2053 /* Clear interior */
2054 SetBkColor(hdc, bkgnd);
2055 ExtTextOutA(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2057 /* Draw rectangular edge around tab */
2058 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_BOTTOM|BF_RIGHT);
2060 /* Now erase the righthand corner and draw diagonal edge */
2061 SetBkColor(hdc, corner);
2062 r1.left = r.right - ROUND_CORNER_SIZE;
2063 r1.bottom = r.bottom;
2064 r1.right = r.right;
2065 r1.top = r1.bottom - ROUND_CORNER_SIZE - 1;
2066 ExtTextOutA(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2067 r1.bottom--;
2068 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMLEFT);
2070 /* Now erase the lefthand corner and draw diagonal edge */
2071 r1.left = r.left;
2072 r1.bottom = r.bottom;
2073 r1.right = r1.left + ROUND_CORNER_SIZE;
2074 r1.top = r1.bottom - ROUND_CORNER_SIZE - 1;
2075 ExtTextOutA(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2076 r1.bottom--;
2077 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPLEFT);
2079 if (iItem == infoPtr->iSelected)
2081 r.top += 2;
2082 r.left += 1;
2083 if (selectedRect.left == 0)
2085 r1 = r;
2086 r1.bottom = r1.top;
2087 r1.top--;
2088 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_LEFT);
2093 else
2095 /* Adjust both rectangles for bottommost row */
2096 if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2098 fillRect.bottom += 3;
2099 r.bottom += 2;
2102 TRACE("<top> item=%d, fill=(%ld,%ld)-(%ld,%ld), edge=(%ld,%ld)-(%ld,%ld)\n",
2103 iItem,
2104 fillRect.left,fillRect.top,fillRect.right,fillRect.bottom,
2105 r.left,r.top,r.right,r.bottom);
2107 /* Clear interior */
2108 SetBkColor(hdc, bkgnd);
2109 ExtTextOutA(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2111 /* Draw rectangular edge around tab */
2112 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_TOP|BF_RIGHT);
2114 /* Now erase the righthand corner and draw diagonal edge */
2115 SetBkColor(hdc, corner);
2116 r1.left = r.right - ROUND_CORNER_SIZE;
2117 r1.top = r.top;
2118 r1.right = r.right;
2119 r1.bottom = r1.top + ROUND_CORNER_SIZE + 1;
2120 ExtTextOutA(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2121 r1.top++;
2122 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMRIGHT);
2124 /* Now erase the lefthand corner and draw diagonal edge */
2125 r1.left = r.left;
2126 r1.top = r.top;
2127 r1.right = r1.left + ROUND_CORNER_SIZE;
2128 r1.bottom = r1.top + ROUND_CORNER_SIZE + 1;
2129 ExtTextOutA(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2130 r1.top++;
2131 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPRIGHT);
2136 TAB_DumpItemInternal(infoPtr, iItem);
2138 /* This modifies r to be the text rectangle. */
2139 TAB_DrawItemInterior(infoPtr, hdc, iItem, &r);
2143 /******************************************************************************
2144 * TAB_DrawBorder
2146 * This method is used to draw the raised border around the tab control
2147 * "content" area.
2149 static void TAB_DrawBorder (TAB_INFO *infoPtr, HDC hdc)
2151 RECT rect;
2152 DWORD lStyle = GetWindowLongA(infoPtr->hwnd, GWL_STYLE);
2154 GetClientRect (infoPtr->hwnd, &rect);
2157 * Adjust for the style
2160 if (infoPtr->uNumItem)
2162 if ((lStyle & TCS_BOTTOM) && !(lStyle & TCS_VERTICAL))
2163 rect.bottom -= infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2164 else if((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
2165 rect.right -= infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2166 else if(lStyle & TCS_VERTICAL)
2167 rect.left += infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2168 else /* not TCS_VERTICAL and not TCS_BOTTOM */
2169 rect.top += infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2172 TRACE("border=(%ld,%ld)-(%ld,%ld)\n",
2173 rect.left, rect.top, rect.right, rect.bottom);
2175 DrawEdge(hdc, &rect, EDGE_RAISED, BF_SOFT|BF_RECT);
2178 /******************************************************************************
2179 * TAB_Refresh
2181 * This method repaints the tab control..
2183 static void TAB_Refresh (TAB_INFO *infoPtr, HDC hdc)
2185 HFONT hOldFont;
2186 INT i;
2188 if (!infoPtr->DoRedraw)
2189 return;
2191 hOldFont = SelectObject (hdc, infoPtr->hFont);
2193 if (GetWindowLongA(infoPtr->hwnd, GWL_STYLE) & TCS_BUTTONS)
2195 for (i = 0; i < infoPtr->uNumItem; i++)
2196 TAB_DrawItem (infoPtr, hdc, i);
2198 else
2200 /* Draw all the non selected item first */
2201 for (i = 0; i < infoPtr->uNumItem; i++)
2203 if (i != infoPtr->iSelected)
2204 TAB_DrawItem (infoPtr, hdc, i);
2207 /* Now, draw the border, draw it before the selected item
2208 * since the selected item overwrites part of the border. */
2209 TAB_DrawBorder (infoPtr, hdc);
2211 /* Then, draw the selected item */
2212 TAB_DrawItem (infoPtr, hdc, infoPtr->iSelected);
2214 /* If we haven't set the current focus yet, set it now.
2215 * Only happens when we first paint the tab controls */
2216 if (infoPtr->uFocus == -1)
2217 TAB_SetCurFocus(infoPtr, infoPtr->iSelected);
2220 SelectObject (hdc, hOldFont);
2223 static inline DWORD TAB_GetRowCount (const TAB_INFO *infoPtr)
2225 return infoPtr->uNumRows;
2228 static inline LRESULT TAB_SetRedraw (TAB_INFO *infoPtr, BOOL doRedraw)
2230 infoPtr->DoRedraw = doRedraw;
2231 return 0;
2234 /******************************************************************************
2235 * TAB_EnsureSelectionVisible
2237 * This method will make sure that the current selection is completely
2238 * visible by scrolling until it is.
2240 static void TAB_EnsureSelectionVisible(
2241 TAB_INFO* infoPtr)
2243 INT iSelected = infoPtr->iSelected;
2244 LONG lStyle = GetWindowLongA(infoPtr->hwnd, GWL_STYLE);
2245 INT iOrigLeftmostVisible = infoPtr->leftmostVisible;
2247 /* set the items row to the bottommost row or topmost row depending on
2248 * style */
2249 if ((infoPtr->uNumRows > 1) && !(lStyle & TCS_BUTTONS))
2251 TAB_ITEM *selected = TAB_GetItem(infoPtr, iSelected);
2252 INT newselected;
2253 INT iTargetRow;
2255 if(lStyle & TCS_VERTICAL)
2256 newselected = selected->rect.left;
2257 else
2258 newselected = selected->rect.top;
2260 /* the target row is always (number of rows - 1)
2261 as row 0 is furthest from the clientRect */
2262 iTargetRow = infoPtr->uNumRows - 1;
2264 if (newselected != iTargetRow)
2266 UINT i;
2267 if(lStyle & TCS_VERTICAL)
2269 for (i=0; i < infoPtr->uNumItem; i++)
2271 /* move everything in the row of the selected item to the iTargetRow */
2272 TAB_ITEM *item = TAB_GetItem(infoPtr, i);
2274 if (item->rect.left == newselected )
2275 item->rect.left = iTargetRow;
2276 else
2278 if (item->rect.left > newselected)
2279 item->rect.left-=1;
2283 else
2285 for (i=0; i < infoPtr->uNumItem; i++)
2287 TAB_ITEM *item = TAB_GetItem(infoPtr, i);
2289 if (item->rect.top == newselected )
2290 item->rect.top = iTargetRow;
2291 else
2293 if (item->rect.top > newselected)
2294 item->rect.top-=1;
2298 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
2303 * Do the trivial cases first.
2305 if ( (!infoPtr->needsScrolling) ||
2306 (infoPtr->hwndUpDown==0) || (lStyle & TCS_VERTICAL))
2307 return;
2309 if (infoPtr->leftmostVisible >= iSelected)
2311 infoPtr->leftmostVisible = iSelected;
2313 else
2315 TAB_ITEM *selected = TAB_GetItem(infoPtr, iSelected);
2316 RECT r;
2317 INT width;
2318 UINT i;
2320 /* Calculate the part of the client area that is visible */
2321 GetClientRect(infoPtr->hwnd, &r);
2322 width = r.right;
2324 GetClientRect(infoPtr->hwndUpDown, &r);
2325 width -= r.right;
2327 if ((selected->rect.right -
2328 selected->rect.left) >= width )
2330 /* Special case: width of selected item is greater than visible
2331 * part of control.
2333 infoPtr->leftmostVisible = iSelected;
2335 else
2337 for (i = infoPtr->leftmostVisible; i < infoPtr->uNumItem; i++)
2339 if ((selected->rect.right - TAB_GetItem(infoPtr, i)->rect.left) < width)
2340 break;
2342 infoPtr->leftmostVisible = i;
2346 if (infoPtr->leftmostVisible != iOrigLeftmostVisible)
2347 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
2349 SendMessageW(infoPtr->hwndUpDown, UDM_SETPOS, 0,
2350 MAKELONG(infoPtr->leftmostVisible, 0));
2353 /******************************************************************************
2354 * TAB_InvalidateTabArea
2356 * This method will invalidate the portion of the control that contains the
2357 * tabs. It is called when the state of the control changes and needs
2358 * to be redisplayed
2360 static void TAB_InvalidateTabArea(TAB_INFO* infoPtr)
2362 RECT clientRect, rInvalidate, rAdjClient;
2363 DWORD lStyle = GetWindowLongA(infoPtr->hwnd, GWL_STYLE);
2364 INT lastRow = infoPtr->uNumRows - 1;
2365 RECT rect;
2367 if (lastRow < 0) return;
2369 GetClientRect(infoPtr->hwnd, &clientRect);
2370 rInvalidate = clientRect;
2371 rAdjClient = clientRect;
2373 TAB_AdjustRect(infoPtr, 0, &rAdjClient);
2375 TAB_InternalGetItemRect(infoPtr, infoPtr->uNumItem-1 , &rect, NULL);
2376 if ((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
2378 rInvalidate.left = rAdjClient.right;
2379 if (infoPtr->uNumRows == 1)
2380 rInvalidate.bottom = clientRect.top + rect.bottom + 2 * SELECTED_TAB_OFFSET;
2382 else if(lStyle & TCS_VERTICAL)
2384 rInvalidate.right = rAdjClient.left;
2385 if (infoPtr->uNumRows == 1)
2386 rInvalidate.bottom = clientRect.top + rect.bottom + 2 * SELECTED_TAB_OFFSET;
2388 else if (lStyle & TCS_BOTTOM)
2390 rInvalidate.top = rAdjClient.bottom;
2391 if (infoPtr->uNumRows == 1)
2392 rInvalidate.right = clientRect.left + rect.right + 2 * SELECTED_TAB_OFFSET;
2394 else
2396 rInvalidate.bottom = rAdjClient.top;
2397 if (infoPtr->uNumRows == 1)
2398 rInvalidate.right = clientRect.left + rect.right + 2 * SELECTED_TAB_OFFSET;
2401 /* Punch out the updown control */
2402 if (infoPtr->needsScrolling && (rInvalidate.right > 0)) {
2403 RECT r;
2404 GetClientRect(infoPtr->hwndUpDown, &r);
2405 if (rInvalidate.right > clientRect.right - r.left)
2406 rInvalidate.right = rInvalidate.right - (r.right - r.left);
2407 else
2408 rInvalidate.right = clientRect.right - r.left;
2411 TRACE("invalidate (%ld,%ld)-(%ld,%ld)\n",
2412 rInvalidate.left, rInvalidate.top,
2413 rInvalidate.right, rInvalidate.bottom);
2415 InvalidateRect(infoPtr->hwnd, &rInvalidate, TRUE);
2418 static inline LRESULT TAB_Paint (TAB_INFO *infoPtr, HDC hdcPaint)
2420 HDC hdc;
2421 PAINTSTRUCT ps;
2423 if (hdcPaint)
2424 hdc = hdcPaint;
2425 else
2427 hdc = BeginPaint (infoPtr->hwnd, &ps);
2428 TRACE("erase %d, rect=(%ld,%ld)-(%ld,%ld)\n",
2429 ps.fErase,
2430 ps.rcPaint.left,ps.rcPaint.top,ps.rcPaint.right,ps.rcPaint.bottom);
2433 TAB_Refresh (infoPtr, hdc);
2435 if (!hdcPaint)
2436 EndPaint (infoPtr->hwnd, &ps);
2438 return 0;
2441 static LRESULT
2442 TAB_InsertItemAW (TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam, BOOL bUnicode)
2444 TAB_ITEM *item;
2445 TCITEMA *pti;
2446 INT iItem;
2447 RECT rect;
2449 GetClientRect (infoPtr->hwnd, &rect);
2450 TRACE("Rect: %p T %li, L %li, B %li, R %li\n", infoPtr->hwnd,
2451 rect.top, rect.left, rect.bottom, rect.right);
2453 pti = (TCITEMA *)lParam;
2454 iItem = (INT)wParam;
2456 if (iItem < 0) return -1;
2457 if (iItem > infoPtr->uNumItem)
2458 iItem = infoPtr->uNumItem;
2460 if (bUnicode)
2461 TAB_DumpItemExternalW((TCITEMW*)pti, iItem);
2462 else
2463 TAB_DumpItemExternalA(pti, iItem);
2466 if (infoPtr->uNumItem == 0) {
2467 infoPtr->items = Alloc (TAB_ITEM_SIZE(infoPtr));
2468 infoPtr->uNumItem++;
2469 infoPtr->iSelected = 0;
2471 else {
2472 LPBYTE oldItems = (LPBYTE)infoPtr->items;
2474 infoPtr->uNumItem++;
2475 infoPtr->items = Alloc (TAB_ITEM_SIZE(infoPtr) * infoPtr->uNumItem);
2477 /* pre insert copy */
2478 if (iItem > 0) {
2479 memcpy (infoPtr->items, oldItems,
2480 iItem * TAB_ITEM_SIZE(infoPtr));
2483 /* post insert copy */
2484 if (iItem < infoPtr->uNumItem - 1) {
2485 memcpy (TAB_GetItem(infoPtr, iItem + 1),
2486 oldItems + iItem * TAB_ITEM_SIZE(infoPtr),
2487 (infoPtr->uNumItem - iItem - 1) * TAB_ITEM_SIZE(infoPtr));
2491 if (iItem <= infoPtr->iSelected)
2492 infoPtr->iSelected++;
2494 Free (oldItems);
2497 item = TAB_GetItem(infoPtr, iItem);
2499 item->mask = pti->mask;
2500 item->pszText = NULL;
2502 if (pti->mask & TCIF_TEXT)
2504 if (bUnicode)
2505 Str_SetPtrW (&item->pszText, (WCHAR*)pti->pszText);
2506 else
2507 Str_SetPtrAtoW (&item->pszText, pti->pszText);
2510 if (pti->mask & TCIF_IMAGE)
2511 item->iImage = pti->iImage;
2512 else
2513 item->iImage = -1;
2515 if (pti->mask & TCIF_PARAM)
2516 memcpy(item->extra, &pti->lParam, infoPtr->cbInfo);
2517 else
2518 memset(item->extra, 0, infoPtr->cbInfo);
2520 TAB_SetItemBounds(infoPtr);
2521 if (infoPtr->uNumItem > 1)
2522 TAB_InvalidateTabArea(infoPtr);
2523 else
2524 InvalidateRect(infoPtr->hwnd, NULL, TRUE);
2526 TRACE("[%p]: added item %d %s\n",
2527 infoPtr->hwnd, iItem, debugstr_w(item->pszText));
2529 return iItem;
2532 static LRESULT
2533 TAB_SetItemSize (TAB_INFO *infoPtr, LPARAM lParam)
2535 LONG lStyle = GetWindowLongA(infoPtr->hwnd, GWL_STYLE);
2536 LONG lResult = 0;
2537 BOOL bNeedPaint = FALSE;
2539 lResult = MAKELONG(infoPtr->tabWidth, infoPtr->tabHeight);
2541 /* UNDOCUMENTED: If requested Width or Height is 0 this means that program wants to use auto size. */
2542 if (lStyle & TCS_FIXEDWIDTH && (infoPtr->tabWidth != (INT)LOWORD(lParam)))
2544 infoPtr->tabWidth = max((INT)LOWORD(lParam), infoPtr->tabMinWidth);
2545 bNeedPaint = TRUE;
2548 if (infoPtr->tabHeight != (INT)HIWORD(lParam))
2550 if ((infoPtr->fHeightSet = ((INT)HIWORD(lParam) != 0)))
2551 infoPtr->tabHeight = (INT)HIWORD(lParam);
2553 bNeedPaint = TRUE;
2555 TRACE("was h=%d,w=%d, now h=%d,w=%d\n",
2556 HIWORD(lResult), LOWORD(lResult),
2557 infoPtr->tabHeight, infoPtr->tabWidth);
2559 if (bNeedPaint)
2561 TAB_SetItemBounds(infoPtr);
2562 RedrawWindow(infoPtr->hwnd, NULL, NULL, RDW_ERASE | RDW_INVALIDATE | RDW_UPDATENOW);
2565 return lResult;
2568 static inline LRESULT TAB_SetMinTabWidth (TAB_INFO *infoPtr, INT cx)
2570 INT oldcx = 0;
2572 TRACE("(%p,%d)\n", infoPtr, cx);
2574 if (infoPtr) {
2575 oldcx = infoPtr->tabMinWidth;
2576 infoPtr->tabMinWidth = (cx==-1)?DEFAULT_TAB_WIDTH:cx;
2579 return oldcx;
2582 static inline LRESULT
2583 TAB_HighlightItem (TAB_INFO *infoPtr, INT iItem, BOOL fHighlight)
2585 LPDWORD lpState;
2587 TRACE("(%p,%d,%s)\n", infoPtr, iItem, fHighlight ? "true" : "false");
2589 if (!infoPtr || iItem < 0 || iItem >= infoPtr->uNumItem)
2590 return FALSE;
2592 lpState = &TAB_GetItem(infoPtr, iItem)->dwState;
2594 if (fHighlight)
2595 *lpState |= TCIS_HIGHLIGHTED;
2596 else
2597 *lpState &= ~TCIS_HIGHLIGHTED;
2599 return TRUE;
2602 static LRESULT
2603 TAB_SetItemAW (TAB_INFO *infoPtr, INT iItem, LPTCITEMA tabItem, BOOL bUnicode)
2605 TAB_ITEM *wineItem;
2607 TRACE("(%p,%d,%p,%s)\n", infoPtr, iItem, tabItem, bUnicode ? "true" : "false");
2609 if (iItem < 0 || iItem >= infoPtr->uNumItem)
2610 return FALSE;
2612 if (bUnicode)
2613 TAB_DumpItemExternalW((TCITEMW *)tabItem, iItem);
2614 else
2615 TAB_DumpItemExternalA(tabItem, iItem);
2617 wineItem = TAB_GetItem(infoPtr, iItem);
2619 if (tabItem->mask & TCIF_IMAGE)
2620 wineItem->iImage = tabItem->iImage;
2622 if (tabItem->mask & TCIF_PARAM)
2623 memcpy(wineItem->extra, &tabItem->lParam, infoPtr->cbInfo);
2625 if (tabItem->mask & TCIF_RTLREADING)
2626 FIXME("TCIF_RTLREADING\n");
2628 if (tabItem->mask & TCIF_STATE)
2629 wineItem->dwState = tabItem->dwState;
2631 if (tabItem->mask & TCIF_TEXT)
2633 if (wineItem->pszText)
2635 Free(wineItem->pszText);
2636 wineItem->pszText = NULL;
2638 if (bUnicode)
2639 Str_SetPtrW(&wineItem->pszText, (WCHAR*)tabItem->pszText);
2640 else
2641 Str_SetPtrAtoW(&wineItem->pszText, tabItem->pszText);
2644 /* Update and repaint tabs */
2645 TAB_SetItemBounds(infoPtr);
2646 TAB_InvalidateTabArea(infoPtr);
2648 return TRUE;
2651 static inline LRESULT TAB_GetItemCount (const TAB_INFO *infoPtr)
2653 return infoPtr->uNumItem;
2657 static LRESULT
2658 TAB_GetItemAW (TAB_INFO *infoPtr, INT iItem, LPTCITEMA tabItem, BOOL bUnicode)
2660 TAB_ITEM *wineItem;
2662 TRACE("(%p,%d,%p,%s)\n", infoPtr, iItem, tabItem, bUnicode ? "true" : "false");
2664 if (iItem < 0 || iItem >= infoPtr->uNumItem)
2665 return FALSE;
2667 wineItem = TAB_GetItem(infoPtr, iItem);
2669 if (tabItem->mask & TCIF_IMAGE)
2670 tabItem->iImage = wineItem->iImage;
2672 if (tabItem->mask & TCIF_PARAM)
2673 memcpy(&tabItem->lParam, wineItem->extra, infoPtr->cbInfo);
2675 if (tabItem->mask & TCIF_RTLREADING)
2676 FIXME("TCIF_RTLREADING\n");
2678 if (tabItem->mask & TCIF_STATE)
2679 tabItem->dwState = wineItem->dwState;
2681 if (tabItem->mask & TCIF_TEXT)
2683 if (bUnicode)
2684 Str_GetPtrW (wineItem->pszText, (WCHAR*)tabItem->pszText, tabItem->cchTextMax);
2685 else
2686 Str_GetPtrWtoA (wineItem->pszText, tabItem->pszText, tabItem->cchTextMax);
2689 if (bUnicode)
2690 TAB_DumpItemExternalW((TCITEMW*)tabItem, iItem);
2691 else
2692 TAB_DumpItemExternalA(tabItem, iItem);
2694 return TRUE;
2698 static LRESULT TAB_DeleteItem (TAB_INFO *infoPtr, INT iItem)
2700 BOOL bResult = FALSE;
2702 TRACE("(%p, %d)\n", infoPtr, iItem);
2704 if ((iItem >= 0) && (iItem < infoPtr->uNumItem))
2706 TAB_ITEM *item = TAB_GetItem(infoPtr, iItem);
2707 LPBYTE oldItems = (LPBYTE)infoPtr->items;
2709 TAB_InvalidateTabArea(infoPtr);
2711 if ((item->mask & TCIF_TEXT) && item->pszText)
2712 Free(item->pszText);
2714 infoPtr->uNumItem--;
2716 if (!infoPtr->uNumItem)
2718 infoPtr->items = NULL;
2719 if (infoPtr->iHotTracked >= 0)
2721 KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
2722 infoPtr->iHotTracked = -1;
2725 else
2727 infoPtr->items = Alloc(TAB_ITEM_SIZE(infoPtr) * infoPtr->uNumItem);
2729 if (iItem > 0)
2730 memcpy(infoPtr->items, oldItems, iItem * TAB_ITEM_SIZE(infoPtr));
2732 if (iItem < infoPtr->uNumItem)
2733 memcpy(TAB_GetItem(infoPtr, iItem),
2734 oldItems + (iItem + 1) * TAB_ITEM_SIZE(infoPtr),
2735 (infoPtr->uNumItem - iItem) * TAB_ITEM_SIZE(infoPtr));
2737 if (iItem <= infoPtr->iHotTracked)
2739 /* When tabs move left/up, the hot track item may change */
2740 FIXME("Recalc hot track");
2743 Free(oldItems);
2745 /* Readjust the selected index */
2746 if ((iItem == infoPtr->iSelected) && (iItem > 0))
2747 infoPtr->iSelected--;
2749 if (iItem < infoPtr->iSelected)
2750 infoPtr->iSelected--;
2752 if (infoPtr->uNumItem == 0)
2753 infoPtr->iSelected = -1;
2755 /* Reposition and repaint tabs */
2756 TAB_SetItemBounds(infoPtr);
2758 bResult = TRUE;
2761 return bResult;
2764 static inline LRESULT TAB_DeleteAllItems (TAB_INFO *infoPtr)
2766 TRACE("(%p)\n", infoPtr);
2767 while (infoPtr->uNumItem)
2768 TAB_DeleteItem (infoPtr, 0);
2769 return TRUE;
2773 static inline LRESULT TAB_GetFont (const TAB_INFO *infoPtr)
2775 TRACE("(%p) returning %p\n", infoPtr, infoPtr->hFont);
2776 return (LRESULT)infoPtr->hFont;
2779 static inline LRESULT TAB_SetFont (TAB_INFO *infoPtr, HFONT hNewFont)
2781 TRACE("(%p,%p)\n", infoPtr, hNewFont);
2783 infoPtr->hFont = hNewFont;
2785 TAB_SetItemBounds(infoPtr);
2787 TAB_InvalidateTabArea(infoPtr);
2789 return 0;
2793 static inline LRESULT TAB_GetImageList (const TAB_INFO *infoPtr)
2795 TRACE("\n");
2796 return (LRESULT)infoPtr->himl;
2799 static inline LRESULT TAB_SetImageList (TAB_INFO *infoPtr, HIMAGELIST himlNew)
2801 HIMAGELIST himlPrev = infoPtr->himl;
2802 TRACE("\n");
2803 infoPtr->himl = himlNew;
2804 return (LRESULT)himlPrev;
2807 static inline LRESULT TAB_GetUnicodeFormat (const TAB_INFO *infoPtr)
2809 return infoPtr->bUnicode;
2812 static inline LRESULT TAB_SetUnicodeFormat (TAB_INFO *infoPtr, BOOL bUnicode)
2814 BOOL bTemp = infoPtr->bUnicode;
2816 infoPtr->bUnicode = bUnicode;
2818 return bTemp;
2821 static inline LRESULT TAB_Size (TAB_INFO *infoPtr)
2823 /* I'm not really sure what the following code was meant to do.
2824 This is what it is doing:
2825 When WM_SIZE is sent with SIZE_RESTORED, the control
2826 gets positioned in the top left corner.
2828 RECT parent_rect;
2829 HWND parent;
2830 UINT uPosFlags,cx,cy;
2832 uPosFlags=0;
2833 if (!wParam) {
2834 parent = GetParent (hwnd);
2835 GetClientRect(parent, &parent_rect);
2836 cx=LOWORD (lParam);
2837 cy=HIWORD (lParam);
2838 if (GetWindowLongA(hwnd, GWL_STYLE) & CCS_NORESIZE)
2839 uPosFlags |= (SWP_NOSIZE | SWP_NOMOVE);
2841 SetWindowPos (hwnd, 0, parent_rect.left, parent_rect.top,
2842 cx, cy, uPosFlags | SWP_NOZORDER);
2843 } else {
2844 FIXME("WM_SIZE flag %x %lx not handled\n", wParam, lParam);
2845 } */
2847 /* Recompute the size/position of the tabs. */
2848 TAB_SetItemBounds (infoPtr);
2850 /* Force a repaint of the control. */
2851 InvalidateRect(infoPtr->hwnd, NULL, TRUE);
2853 return 0;
2857 static LRESULT TAB_Create (HWND hwnd, WPARAM wParam, LPARAM lParam)
2859 TAB_INFO *infoPtr;
2860 TEXTMETRICA fontMetrics;
2861 HDC hdc;
2862 HFONT hOldFont;
2863 DWORD dwStyle;
2865 infoPtr = (TAB_INFO *)Alloc (sizeof(TAB_INFO));
2867 SetWindowLongA(hwnd, 0, (DWORD)infoPtr);
2869 infoPtr->hwnd = hwnd;
2870 infoPtr->hwndNotify = ((LPCREATESTRUCTW)lParam)->hwndParent;
2871 infoPtr->uNumItem = 0;
2872 infoPtr->uNumRows = 0;
2873 infoPtr->uHItemPadding = 6;
2874 infoPtr->uVItemPadding = 3;
2875 infoPtr->uHItemPadding_s = 6;
2876 infoPtr->uVItemPadding_s = 3;
2877 infoPtr->hFont = 0;
2878 infoPtr->items = 0;
2879 infoPtr->hcurArrow = LoadCursorA (0, (LPSTR)IDC_ARROW);
2880 infoPtr->iSelected = -1;
2881 infoPtr->iHotTracked = -1;
2882 infoPtr->uFocus = -1;
2883 infoPtr->hwndToolTip = 0;
2884 infoPtr->DoRedraw = TRUE;
2885 infoPtr->needsScrolling = FALSE;
2886 infoPtr->hwndUpDown = 0;
2887 infoPtr->leftmostVisible = 0;
2888 infoPtr->fHeightSet = FALSE;
2889 infoPtr->bUnicode = IsWindowUnicode (hwnd);
2890 infoPtr->cbInfo = sizeof(LPARAM);
2892 TRACE("Created tab control, hwnd [%p]\n", hwnd);
2894 /* The tab control always has the WS_CLIPSIBLINGS style. Even
2895 if you don't specify it in CreateWindow. This is necessary in
2896 order for paint to work correctly. This follows windows behaviour. */
2897 dwStyle = GetWindowLongA(hwnd, GWL_STYLE);
2898 SetWindowLongA(hwnd, GWL_STYLE, dwStyle|WS_CLIPSIBLINGS);
2900 if (dwStyle & TCS_TOOLTIPS) {
2901 /* Create tooltip control */
2902 infoPtr->hwndToolTip =
2903 CreateWindowExA (0, TOOLTIPS_CLASSA, NULL, 0,
2904 CW_USEDEFAULT, CW_USEDEFAULT,
2905 CW_USEDEFAULT, CW_USEDEFAULT,
2906 hwnd, 0, 0, 0);
2908 /* Send NM_TOOLTIPSCREATED notification */
2909 if (infoPtr->hwndToolTip) {
2910 NMTOOLTIPSCREATED nmttc;
2912 nmttc.hdr.hwndFrom = hwnd;
2913 nmttc.hdr.idFrom = GetWindowLongPtrW(hwnd, GWLP_ID);
2914 nmttc.hdr.code = NM_TOOLTIPSCREATED;
2915 nmttc.hwndToolTips = infoPtr->hwndToolTip;
2917 SendMessageW (infoPtr->hwndNotify, WM_NOTIFY,
2918 (WPARAM)GetWindowLongPtrW(hwnd, GWLP_ID), (LPARAM)&nmttc);
2923 * We need to get text information so we need a DC and we need to select
2924 * a font.
2926 hdc = GetDC(hwnd);
2927 hOldFont = SelectObject (hdc, GetStockObject (SYSTEM_FONT));
2929 /* Use the system font to determine the initial height of a tab. */
2930 GetTextMetricsA(hdc, &fontMetrics);
2933 * Make sure there is enough space for the letters + growing the
2934 * selected item + extra space for the selected item.
2936 infoPtr->tabHeight = fontMetrics.tmHeight + SELECTED_TAB_OFFSET +
2937 ((dwStyle & TCS_BUTTONS) ? 2 : 1) *
2938 infoPtr->uVItemPadding;
2940 /* Initialize the width of a tab. */
2941 infoPtr->tabWidth = DEFAULT_TAB_WIDTH;
2942 infoPtr->tabMinWidth = 0;
2944 TRACE("tabH=%d, tabW=%d\n", infoPtr->tabHeight, infoPtr->tabWidth);
2946 SelectObject (hdc, hOldFont);
2947 ReleaseDC(hwnd, hdc);
2949 return 0;
2952 static LRESULT
2953 TAB_Destroy (TAB_INFO *infoPtr)
2955 UINT iItem;
2957 if (!infoPtr)
2958 return 0;
2960 if (infoPtr->items) {
2961 for (iItem = 0; iItem < infoPtr->uNumItem; iItem++) {
2962 if (TAB_GetItem(infoPtr, iItem)->pszText)
2963 Free (TAB_GetItem(infoPtr, iItem)->pszText);
2965 Free (infoPtr->items);
2968 if (infoPtr->hwndToolTip)
2969 DestroyWindow (infoPtr->hwndToolTip);
2971 if (infoPtr->hwndUpDown)
2972 DestroyWindow(infoPtr->hwndUpDown);
2974 if (infoPtr->iHotTracked >= 0)
2975 KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
2977 Free (infoPtr);
2978 SetWindowLongA(infoPtr->hwnd, 0, 0);
2979 return 0;
2982 static inline LRESULT
2983 TAB_SetItemExtra (TAB_INFO *infoPtr, INT cbInfo)
2985 if (!infoPtr || cbInfo <= 0)
2986 return FALSE;
2988 if (infoPtr->uNumItem)
2990 /* FIXME: MSDN says this is not allowed, but this hasn't been verified */
2991 return FALSE;
2994 infoPtr->cbInfo = cbInfo;
2995 return TRUE;
2998 static LRESULT WINAPI
2999 TAB_WindowProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
3001 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
3003 TRACE("hwnd=%p msg=%x wParam=%x lParam=%lx\n", hwnd, uMsg, wParam, lParam);
3004 if (!infoPtr && (uMsg != WM_CREATE))
3005 return DefWindowProcW (hwnd, uMsg, wParam, lParam);
3007 switch (uMsg)
3009 case TCM_GETIMAGELIST:
3010 return TAB_GetImageList (infoPtr);
3012 case TCM_SETIMAGELIST:
3013 return TAB_SetImageList (infoPtr, (HIMAGELIST)lParam);
3015 case TCM_GETITEMCOUNT:
3016 return TAB_GetItemCount (infoPtr);
3018 case TCM_GETITEMA:
3019 case TCM_GETITEMW:
3020 return TAB_GetItemAW (infoPtr, (INT)wParam, (LPTCITEMA)lParam, uMsg == TCM_GETITEMW);
3022 case TCM_SETITEMA:
3023 case TCM_SETITEMW:
3024 return TAB_SetItemAW (infoPtr, (INT)wParam, (LPTCITEMA)lParam, uMsg == TCM_SETITEMW);
3026 case TCM_DELETEITEM:
3027 return TAB_DeleteItem (infoPtr, (INT)wParam);
3029 case TCM_DELETEALLITEMS:
3030 return TAB_DeleteAllItems (infoPtr);
3032 case TCM_GETITEMRECT:
3033 return TAB_GetItemRect (infoPtr, wParam, lParam);
3035 case TCM_GETCURSEL:
3036 return TAB_GetCurSel (infoPtr);
3038 case TCM_HITTEST:
3039 return TAB_HitTest (infoPtr, (LPTCHITTESTINFO)lParam);
3041 case TCM_SETCURSEL:
3042 return TAB_SetCurSel (infoPtr, (INT)wParam);
3044 case TCM_INSERTITEMA:
3045 case TCM_INSERTITEMW:
3046 return TAB_InsertItemAW (infoPtr, wParam, lParam, uMsg == TCM_INSERTITEMW);
3048 case TCM_SETITEMEXTRA:
3049 return TAB_SetItemExtra (infoPtr, (int)wParam);
3051 case TCM_ADJUSTRECT:
3052 return TAB_AdjustRect (infoPtr, (BOOL)wParam, (LPRECT)lParam);
3054 case TCM_SETITEMSIZE:
3055 return TAB_SetItemSize (infoPtr, lParam);
3057 case TCM_REMOVEIMAGE:
3058 FIXME("Unimplemented msg TCM_REMOVEIMAGE\n");
3059 return 0;
3061 case TCM_SETPADDING:
3062 return TAB_SetPadding (infoPtr, lParam);
3064 case TCM_GETROWCOUNT:
3065 return TAB_GetRowCount(infoPtr);
3067 case TCM_GETUNICODEFORMAT:
3068 return TAB_GetUnicodeFormat (infoPtr);
3070 case TCM_SETUNICODEFORMAT:
3071 return TAB_SetUnicodeFormat (infoPtr, (BOOL)wParam);
3073 case TCM_HIGHLIGHTITEM:
3074 return TAB_HighlightItem (infoPtr, (INT)wParam, (BOOL)LOWORD(lParam));
3076 case TCM_GETTOOLTIPS:
3077 return TAB_GetToolTips (infoPtr);
3079 case TCM_SETTOOLTIPS:
3080 return TAB_SetToolTips (infoPtr, (HWND)wParam);
3082 case TCM_GETCURFOCUS:
3083 return TAB_GetCurFocus (infoPtr);
3085 case TCM_SETCURFOCUS:
3086 return TAB_SetCurFocus (infoPtr, (INT)wParam);
3088 case TCM_SETMINTABWIDTH:
3089 return TAB_SetMinTabWidth(infoPtr, (INT)lParam);
3091 case TCM_DESELECTALL:
3092 FIXME("Unimplemented msg TCM_DESELECTALL\n");
3093 return 0;
3095 case TCM_GETEXTENDEDSTYLE:
3096 FIXME("Unimplemented msg TCM_GETEXTENDEDSTYLE\n");
3097 return 0;
3099 case TCM_SETEXTENDEDSTYLE:
3100 FIXME("Unimplemented msg TCM_SETEXTENDEDSTYLE\n");
3101 return 0;
3103 case WM_GETFONT:
3104 return TAB_GetFont (infoPtr);
3106 case WM_SETFONT:
3107 return TAB_SetFont (infoPtr, (HFONT)wParam);
3109 case WM_CREATE:
3110 return TAB_Create (hwnd, wParam, lParam);
3112 case WM_NCDESTROY:
3113 return TAB_Destroy (infoPtr);
3115 case WM_GETDLGCODE:
3116 return DLGC_WANTARROWS | DLGC_WANTCHARS;
3118 case WM_LBUTTONDOWN:
3119 return TAB_LButtonDown (infoPtr, wParam, lParam);
3121 case WM_LBUTTONUP:
3122 return TAB_LButtonUp (infoPtr);
3124 case WM_NOTIFY:
3125 return SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, wParam, lParam);
3127 case WM_RBUTTONDOWN:
3128 return TAB_RButtonDown (infoPtr);
3130 case WM_MOUSEMOVE:
3131 return TAB_MouseMove (infoPtr, wParam, lParam);
3133 case WM_PAINT:
3134 return TAB_Paint (infoPtr, (HDC)wParam);
3136 case WM_SIZE:
3137 return TAB_Size (infoPtr);
3139 case WM_SETREDRAW:
3140 return TAB_SetRedraw (infoPtr, (BOOL)wParam);
3142 case WM_HSCROLL:
3143 return TAB_OnHScroll(infoPtr, (int)LOWORD(wParam), (int)HIWORD(wParam), (HWND)lParam);
3145 case WM_STYLECHANGED:
3146 TAB_SetItemBounds (infoPtr);
3147 InvalidateRect(hwnd, NULL, TRUE);
3148 return 0;
3150 case WM_SYSCOLORCHANGE:
3151 COMCTL32_RefreshSysColors();
3152 return 0;
3154 case WM_KILLFOCUS:
3155 case WM_SETFOCUS:
3156 TAB_FocusChanging(infoPtr);
3157 break; /* Don't disturb normal focus behavior */
3159 case WM_KEYUP:
3160 return TAB_KeyUp(infoPtr, wParam);
3161 case WM_NCHITTEST:
3162 return TAB_NCHitTest(infoPtr, lParam);
3164 default:
3165 if (uMsg >= WM_USER && uMsg < WM_APP)
3166 WARN("unknown msg %04x wp=%08x lp=%08lx\n",
3167 uMsg, wParam, lParam);
3168 break;
3170 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
3174 VOID
3175 TAB_Register (void)
3177 WNDCLASSW wndClass;
3179 ZeroMemory (&wndClass, sizeof(WNDCLASSW));
3180 wndClass.style = CS_GLOBALCLASS | CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
3181 wndClass.lpfnWndProc = TAB_WindowProc;
3182 wndClass.cbClsExtra = 0;
3183 wndClass.cbWndExtra = sizeof(TAB_INFO *);
3184 wndClass.hCursor = LoadCursorW (0, (LPWSTR)IDC_ARROW);
3185 wndClass.hbrBackground = (HBRUSH)(COLOR_BTNFACE+1);
3186 wndClass.lpszClassName = WC_TABCONTROLW;
3188 RegisterClassW (&wndClass);
3192 VOID
3193 TAB_Unregister (void)
3195 UnregisterClassW (WC_TABCONTROLW, NULL);