Items are variable sized, use an accessor to get at them.
[wine/hacks.git] / dlls / comctl32 / tab.c
blob44b42aa51882c5081d4819790e01ad2741b34a6a
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 hold 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 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 a 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_Refresh (HWND hwnd, HDC hdc);
130 static void TAB_InvalidateTabArea(HWND hwnd, TAB_INFO* infoPtr);
131 static void TAB_EnsureSelectionVisible(HWND hwnd, TAB_INFO* infoPtr);
132 static void TAB_DrawItem(HWND hwnd, HDC hdc, INT iItem);
133 static void TAB_DrawItemInterior(HWND hwnd, HDC hdc, INT iItem, RECT* drawRect);
135 static BOOL
136 TAB_SendSimpleNotify (HWND hwnd, UINT code)
138 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
139 NMHDR nmhdr;
141 nmhdr.hwndFrom = hwnd;
142 nmhdr.idFrom = GetWindowLongPtrW(hwnd, GWLP_ID);
143 nmhdr.code = code;
145 return (BOOL) SendMessageW (infoPtr->hwndNotify, WM_NOTIFY,
146 (WPARAM) nmhdr.idFrom, (LPARAM) &nmhdr);
149 static VOID
150 TAB_RelayEvent (HWND hwndTip, HWND hwndMsg, UINT uMsg,
151 WPARAM wParam, LPARAM lParam)
153 MSG msg;
155 msg.hwnd = hwndMsg;
156 msg.message = uMsg;
157 msg.wParam = wParam;
158 msg.lParam = lParam;
159 msg.time = GetMessageTime ();
160 msg.pt.x = LOWORD(GetMessagePos ());
161 msg.pt.y = HIWORD(GetMessagePos ());
163 SendMessageW (hwndTip, TTM_RELAYEVENT, 0, (LPARAM)&msg);
166 static void
167 TAB_DumpItemExternalA(TCITEMA *pti, UINT iItem)
169 if (TRACE_ON(tab)) {
170 TRACE("external tab %d, mask=0x%08x, dwState=0x%08x, dwStateMask=0x%08x, cchTextMax=0x%08x\n",
171 iItem, pti->mask, pti->dwState, pti->dwStateMask, pti->cchTextMax);
172 TRACE("external tab %d, iImage=%d, lParam=0x%08lx, pszTextA=%s\n",
173 iItem, pti->iImage, pti->lParam, debugstr_a(pti->pszText));
178 static void
179 TAB_DumpItemExternalW(TCITEMW *pti, UINT iItem)
181 if (TRACE_ON(tab)) {
182 TRACE("external tab %d, mask=0x%08x, dwState=0x%08lx, dwStateMask=0x%08lx, cchTextMax=0x%08x\n",
183 iItem, pti->mask, pti->dwState, pti->dwStateMask, pti->cchTextMax);
184 TRACE("external tab %d, iImage=%d, lParam=0x%08lx, pszTextW=%s\n",
185 iItem, pti->iImage, pti->lParam, debugstr_w(pti->pszText));
189 static void
190 TAB_DumpItemInternal(TAB_INFO *infoPtr, UINT iItem)
192 if (TRACE_ON(tab)) {
193 TAB_ITEM *ti;
195 ti = TAB_GetItem(infoPtr, iItem);
196 TRACE("tab %d, mask=0x%08x, dwState=0x%08lx, pszText=%s, iImage=%d\n",
197 iItem, ti->mask, ti->dwState, debugstr_w(ti->pszText),
198 ti->iImage);
199 TRACE("tab %d, rect.left=%ld, rect.top(row)=%ld\n",
200 iItem, ti->rect.left, ti->rect.top);
204 static LRESULT
205 TAB_GetCurSel (HWND hwnd)
207 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
209 return infoPtr->iSelected;
212 static LRESULT
213 TAB_GetCurFocus (HWND hwnd)
215 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
217 return infoPtr->uFocus;
220 static LRESULT
221 TAB_GetToolTips (HWND hwnd, WPARAM wParam, LPARAM lParam)
223 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
225 if (infoPtr == NULL) return 0;
226 return (LRESULT)infoPtr->hwndToolTip;
229 static LRESULT
230 TAB_SetCurSel (HWND hwnd,WPARAM wParam)
232 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
233 INT iItem = (INT)wParam;
234 INT prevItem;
236 prevItem = -1;
237 if ((iItem >= 0) && (iItem < infoPtr->uNumItem)) {
238 prevItem=infoPtr->iSelected;
239 infoPtr->iSelected=iItem;
240 TAB_EnsureSelectionVisible(hwnd, infoPtr);
241 TAB_InvalidateTabArea(hwnd, infoPtr);
243 return prevItem;
246 static LRESULT
247 TAB_SetCurFocus (HWND hwnd,WPARAM wParam)
249 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
250 INT iItem=(INT) wParam;
252 if ((iItem < 0) || (iItem >= infoPtr->uNumItem)) return 0;
254 if (GetWindowLongA(hwnd, GWL_STYLE) & TCS_BUTTONS) {
255 FIXME("Should set input focus\n");
256 } else {
257 int oldFocus = infoPtr->uFocus;
258 if (infoPtr->iSelected != iItem || infoPtr->uFocus == -1 ) {
259 infoPtr->uFocus = iItem;
260 if (oldFocus != -1) {
261 if (TAB_SendSimpleNotify(hwnd, TCN_SELCHANGING)!=TRUE) {
262 infoPtr->iSelected = iItem;
263 TAB_SendSimpleNotify(hwnd, TCN_SELCHANGE);
265 else
266 infoPtr->iSelected = iItem;
267 TAB_EnsureSelectionVisible(hwnd, infoPtr);
268 TAB_InvalidateTabArea(hwnd, infoPtr);
272 return 0;
275 static LRESULT
276 TAB_SetToolTips (HWND hwnd, WPARAM wParam, LPARAM lParam)
278 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
280 if (infoPtr == NULL) return 0;
281 infoPtr->hwndToolTip = (HWND)wParam;
282 return 0;
285 static LRESULT
286 TAB_SetPadding (HWND hwnd, WPARAM wParam, LPARAM lParam)
288 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
290 if (infoPtr == NULL) return 0;
291 infoPtr->uHItemPadding_s=LOWORD(lParam);
292 infoPtr->uVItemPadding_s=HIWORD(lParam);
293 return 0;
296 /******************************************************************************
297 * TAB_InternalGetItemRect
299 * This method will calculate the rectangle representing a given tab item in
300 * client coordinates. This method takes scrolling into account.
302 * This method returns TRUE if the item is visible in the window and FALSE
303 * if it is completely outside the client area.
305 static BOOL TAB_InternalGetItemRect(
306 HWND hwnd,
307 TAB_INFO* infoPtr,
308 INT itemIndex,
309 RECT* itemRect,
310 RECT* selectedRect)
312 RECT tmpItemRect,clientRect;
313 LONG lStyle = GetWindowLongA(hwnd, GWL_STYLE);
315 /* Perform a sanity check and a trivial visibility check. */
316 if ( (infoPtr->uNumItem <= 0) ||
317 (itemIndex >= infoPtr->uNumItem) ||
318 (!((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)) && (itemIndex < infoPtr->leftmostVisible)) )
319 return FALSE;
322 * Avoid special cases in this procedure by assigning the "out"
323 * parameters if the caller didn't supply them
325 if (itemRect == NULL)
326 itemRect = &tmpItemRect;
328 /* Retrieve the unmodified item rect. */
329 *itemRect = TAB_GetItem(infoPtr,itemIndex)->rect;
331 /* calculate the times bottom and top based on the row */
332 GetClientRect(hwnd, &clientRect);
334 if ((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
336 itemRect->right = clientRect.right - SELECTED_TAB_OFFSET - itemRect->left * infoPtr->tabHeight -
337 ((lStyle & TCS_BUTTONS) ? itemRect->left * BUTTON_SPACINGX : 0);
338 itemRect->left = itemRect->right - infoPtr->tabHeight;
340 else if (lStyle & TCS_VERTICAL)
342 itemRect->left = clientRect.left + SELECTED_TAB_OFFSET + itemRect->left * infoPtr->tabHeight +
343 ((lStyle & TCS_BUTTONS) ? itemRect->left * BUTTON_SPACINGX : 0);
344 itemRect->right = itemRect->left + infoPtr->tabHeight;
346 else if (lStyle & TCS_BOTTOM)
348 itemRect->bottom = clientRect.bottom - itemRect->top * infoPtr->tabHeight -
349 ((lStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : SELECTED_TAB_OFFSET);
350 itemRect->top = itemRect->bottom - infoPtr->tabHeight;
352 else /* not TCS_BOTTOM and not TCS_VERTICAL */
354 itemRect->top = clientRect.top + itemRect->top * infoPtr->tabHeight +
355 ((lStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : SELECTED_TAB_OFFSET);
356 itemRect->bottom = itemRect->top + infoPtr->tabHeight;
360 * "scroll" it to make sure the item at the very left of the
361 * tab control is the leftmost visible tab.
363 if(lStyle & TCS_VERTICAL)
365 OffsetRect(itemRect,
367 -TAB_GetItem(infoPtr, infoPtr->leftmostVisible)->rect.top);
370 * Move the rectangle so the first item is slightly offset from
371 * the bottom of the tab control.
373 OffsetRect(itemRect,
375 SELECTED_TAB_OFFSET);
377 } else
379 OffsetRect(itemRect,
380 -TAB_GetItem(infoPtr, infoPtr->leftmostVisible)->rect.left,
384 * Move the rectangle so the first item is slightly offset from
385 * the left of the tab control.
387 OffsetRect(itemRect,
388 SELECTED_TAB_OFFSET,
391 TRACE("item %d tab h=%d, rect=(%ld,%ld)-(%ld,%ld)\n",
392 itemIndex, infoPtr->tabHeight,
393 itemRect->left, itemRect->top, itemRect->right, itemRect->bottom);
395 /* Now, calculate the position of the item as if it were selected. */
396 if (selectedRect!=NULL)
398 CopyRect(selectedRect, itemRect);
400 /* The rectangle of a selected item is a bit wider. */
401 if(lStyle & TCS_VERTICAL)
402 InflateRect(selectedRect, 0, SELECTED_TAB_OFFSET);
403 else
404 InflateRect(selectedRect, SELECTED_TAB_OFFSET, 0);
406 /* If it also a bit higher. */
407 if ((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
409 selectedRect->left -= 2; /* the border is thicker on the right */
410 selectedRect->right += SELECTED_TAB_OFFSET;
412 else if (lStyle & TCS_VERTICAL)
414 selectedRect->left -= SELECTED_TAB_OFFSET;
415 selectedRect->right += 1;
417 else if (lStyle & TCS_BOTTOM)
419 selectedRect->bottom += SELECTED_TAB_OFFSET;
421 else /* not TCS_BOTTOM and not TCS_VERTICAL */
423 selectedRect->top -= SELECTED_TAB_OFFSET;
424 selectedRect->bottom -= 1;
428 return TRUE;
431 static BOOL TAB_GetItemRect(HWND hwnd, WPARAM wParam, LPARAM lParam)
433 return TAB_InternalGetItemRect(hwnd, TAB_GetInfoPtr(hwnd), (INT)wParam,
434 (LPRECT)lParam, (LPRECT)NULL);
437 /******************************************************************************
438 * TAB_KeyUp
440 * This method is called to handle keyboard input
442 static LRESULT TAB_KeyUp(
443 HWND hwnd,
444 WPARAM keyCode)
446 TAB_INFO* infoPtr = TAB_GetInfoPtr(hwnd);
447 int newItem = -1;
449 switch (keyCode)
451 case VK_LEFT:
452 newItem = infoPtr->uFocus - 1;
453 break;
454 case VK_RIGHT:
455 newItem = infoPtr->uFocus + 1;
456 break;
460 * If we changed to a valid item, change the selection
462 if ((newItem >= 0) &&
463 (newItem < infoPtr->uNumItem) &&
464 (infoPtr->uFocus != newItem))
466 if (!TAB_SendSimpleNotify(hwnd, TCN_SELCHANGING))
468 infoPtr->iSelected = newItem;
469 infoPtr->uFocus = newItem;
470 TAB_SendSimpleNotify(hwnd, TCN_SELCHANGE);
472 TAB_EnsureSelectionVisible(hwnd, infoPtr);
473 TAB_InvalidateTabArea(hwnd, infoPtr);
477 return 0;
480 /******************************************************************************
481 * TAB_FocusChanging
483 * This method is called whenever the focus goes in or out of this control
484 * it is used to update the visual state of the control.
486 static LRESULT TAB_FocusChanging(
487 HWND hwnd,
488 UINT uMsg,
489 WPARAM wParam,
490 LPARAM lParam)
492 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
493 RECT selectedRect;
494 BOOL isVisible;
497 * Get the rectangle for the item.
499 isVisible = TAB_InternalGetItemRect(hwnd,
500 infoPtr,
501 infoPtr->uFocus,
502 NULL,
503 &selectedRect);
506 * If the rectangle is not completely invisible, invalidate that
507 * portion of the window.
509 if (isVisible)
511 TRACE("invalidate (%ld,%ld)-(%ld,%ld)\n",
512 selectedRect.left,selectedRect.top,
513 selectedRect.right,selectedRect.bottom);
514 InvalidateRect(hwnd, &selectedRect, TRUE);
518 * Don't otherwise disturb normal behavior.
520 return DefWindowProcW (hwnd, uMsg, wParam, lParam);
523 static INT TAB_InternalHitTest (
524 HWND hwnd,
525 TAB_INFO* infoPtr,
526 POINT pt,
527 UINT* flags)
530 RECT rect;
531 INT iCount;
533 for (iCount = 0; iCount < infoPtr->uNumItem; iCount++)
535 TAB_InternalGetItemRect(hwnd, infoPtr, iCount, &rect, NULL);
537 if (PtInRect(&rect, pt))
539 *flags = TCHT_ONITEM;
540 return iCount;
544 *flags = TCHT_NOWHERE;
545 return -1;
548 static LRESULT
549 TAB_HitTest (HWND hwnd, WPARAM wParam, LPARAM lParam)
551 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
552 LPTCHITTESTINFO lptest = (LPTCHITTESTINFO) lParam;
554 return TAB_InternalHitTest (hwnd, infoPtr, lptest->pt, &lptest->flags);
557 /******************************************************************************
558 * TAB_NCHitTest
560 * Napster v2b5 has a tab control for its main navigation which has a client
561 * area that covers the whole area of the dialog pages.
562 * That's why it receives all msgs for that area and the underlying dialog ctrls
563 * are dead.
564 * So I decided that we should handle WM_NCHITTEST here and return
565 * HTTRANSPARENT if we don't hit the tab control buttons.
566 * FIXME: WM_NCHITTEST handling correct ? Fix it if you know that Windows
567 * doesn't do it that way. Maybe depends on tab control styles ?
569 static LRESULT
570 TAB_NCHitTest (HWND hwnd, LPARAM lParam)
572 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
573 POINT pt;
574 UINT dummyflag;
576 pt.x = LOWORD(lParam);
577 pt.y = HIWORD(lParam);
578 ScreenToClient(hwnd, &pt);
580 if (TAB_InternalHitTest(hwnd, infoPtr, pt, &dummyflag) == -1)
581 return HTTRANSPARENT;
582 else
583 return HTCLIENT;
586 static LRESULT
587 TAB_LButtonDown (HWND hwnd, WPARAM wParam, LPARAM lParam)
589 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
590 POINT pt;
591 INT newItem, dummy;
593 if (infoPtr->hwndToolTip)
594 TAB_RelayEvent (infoPtr->hwndToolTip, hwnd,
595 WM_LBUTTONDOWN, wParam, lParam);
597 if (GetWindowLongA(hwnd, GWL_STYLE) & TCS_FOCUSONBUTTONDOWN ) {
598 SetFocus (hwnd);
601 if (infoPtr->hwndToolTip)
602 TAB_RelayEvent (infoPtr->hwndToolTip, hwnd,
603 WM_LBUTTONDOWN, wParam, lParam);
605 pt.x = (INT)LOWORD(lParam);
606 pt.y = (INT)HIWORD(lParam);
608 newItem = TAB_InternalHitTest (hwnd, infoPtr, pt, &dummy);
610 TRACE("On Tab, item %d\n", newItem);
612 if ((newItem != -1) && (infoPtr->iSelected != newItem))
614 if (TAB_SendSimpleNotify(hwnd, TCN_SELCHANGING) != TRUE)
616 infoPtr->iSelected = newItem;
617 infoPtr->uFocus = newItem;
618 TAB_SendSimpleNotify(hwnd, TCN_SELCHANGE);
620 TAB_EnsureSelectionVisible(hwnd, infoPtr);
622 TAB_InvalidateTabArea(hwnd, infoPtr);
625 return 0;
628 static LRESULT
629 TAB_LButtonUp (HWND hwnd, WPARAM wParam, LPARAM lParam)
631 TAB_SendSimpleNotify(hwnd, NM_CLICK);
633 return 0;
636 static LRESULT
637 TAB_RButtonDown (HWND hwnd, WPARAM wParam, LPARAM lParam)
639 TAB_SendSimpleNotify(hwnd, NM_RCLICK);
640 return 0;
643 /******************************************************************************
644 * TAB_DrawLoneItemInterior
646 * This calls TAB_DrawItemInterior. However, TAB_DrawItemInterior is normally
647 * called by TAB_DrawItem which is normally called by TAB_Refresh which sets
648 * up the device context and font. This routine does the same setup but
649 * only calls TAB_DrawItemInterior for the single specified item.
651 static void
652 TAB_DrawLoneItemInterior(HWND hwnd, TAB_INFO* infoPtr, int iItem)
654 HDC hdc = GetDC(hwnd);
655 RECT r, rC;
657 /* Clip UpDown control to not draw over it */
658 if (infoPtr->needsScrolling)
660 GetWindowRect(hwnd, &rC);
661 GetWindowRect(infoPtr->hwndUpDown, &r);
662 ExcludeClipRect(hdc, r.left - rC.left, r.top - rC.top, r.right - rC.left, r.bottom - rC.top);
664 TAB_DrawItemInterior(hwnd, hdc, iItem, NULL);
665 ReleaseDC(hwnd, hdc);
668 /******************************************************************************
669 * TAB_HotTrackTimerProc
671 * When a mouse-move event causes a tab to be highlighted (hot-tracking), a
672 * timer is setup so we can check if the mouse is moved out of our window.
673 * (We don't get an event when the mouse leaves, the mouse-move events just
674 * stop being delivered to our window and just start being delivered to
675 * another window.) This function is called when the timer triggers so
676 * we can check if the mouse has left our window. If so, we un-highlight
677 * the hot-tracked tab.
679 static VOID CALLBACK
680 TAB_HotTrackTimerProc
682 HWND hwnd, /* handle of window for timer messages */
683 UINT uMsg, /* WM_TIMER message */
684 UINT idEvent, /* timer identifier */
685 DWORD dwTime /* current system time */
688 TAB_INFO* infoPtr = TAB_GetInfoPtr(hwnd);
690 if (infoPtr != NULL && infoPtr->iHotTracked >= 0)
692 POINT pt;
695 ** If we can't get the cursor position, or if the cursor is outside our
696 ** window, we un-highlight the hot-tracked tab. Note that the cursor is
697 ** "outside" even if it is within our bounding rect if another window
698 ** overlaps. Note also that the case where the cursor stayed within our
699 ** window but has moved off the hot-tracked tab will be handled by the
700 ** WM_MOUSEMOVE event.
702 if (!GetCursorPos(&pt) || WindowFromPoint(pt) != hwnd)
704 /* Redraw iHotTracked to look normal */
705 INT iRedraw = infoPtr->iHotTracked;
706 infoPtr->iHotTracked = -1;
707 TAB_DrawLoneItemInterior(hwnd, infoPtr, iRedraw);
709 /* Kill this timer */
710 KillTimer(hwnd, TAB_HOTTRACK_TIMER);
715 /******************************************************************************
716 * TAB_RecalcHotTrack
718 * If a tab control has the TCS_HOTTRACK style, then the tab under the mouse
719 * should be highlighted. This function determines which tab in a tab control,
720 * if any, is under the mouse and records that information. The caller may
721 * supply output parameters to receive the item number of the tab item which
722 * was highlighted but isn't any longer and of the tab item which is now
723 * highlighted but wasn't previously. The caller can use this information to
724 * selectively redraw those tab items.
726 * If the caller has a mouse position, it can supply it through the pos
727 * parameter. For example, TAB_MouseMove does this. Otherwise, the caller
728 * supplies NULL and this function determines the current mouse position
729 * itself.
731 static void
732 TAB_RecalcHotTrack
734 HWND hwnd,
735 const LPARAM* pos,
736 int* out_redrawLeave,
737 int* out_redrawEnter
740 TAB_INFO* infoPtr = TAB_GetInfoPtr(hwnd);
742 int item = -1;
745 if (out_redrawLeave != NULL)
746 *out_redrawLeave = -1;
747 if (out_redrawEnter != NULL)
748 *out_redrawEnter = -1;
750 if (GetWindowLongA(hwnd, GWL_STYLE) & TCS_HOTTRACK)
752 POINT pt;
753 UINT flags;
755 if (pos == NULL)
757 GetCursorPos(&pt);
758 ScreenToClient(hwnd, &pt);
760 else
762 pt.x = LOWORD(*pos);
763 pt.y = HIWORD(*pos);
766 item = TAB_InternalHitTest(hwnd, infoPtr, pt, &flags);
769 if (item != infoPtr->iHotTracked)
771 if (infoPtr->iHotTracked >= 0)
773 /* Mark currently hot-tracked to be redrawn to look normal */
774 if (out_redrawLeave != NULL)
775 *out_redrawLeave = infoPtr->iHotTracked;
777 if (item < 0)
779 /* Kill timer which forces recheck of mouse pos */
780 KillTimer(hwnd, TAB_HOTTRACK_TIMER);
783 else
785 /* Start timer so we recheck mouse pos */
786 UINT timerID = SetTimer
788 hwnd,
789 TAB_HOTTRACK_TIMER,
790 TAB_HOTTRACK_TIMER_INTERVAL,
791 TAB_HotTrackTimerProc
794 if (timerID == 0)
795 return; /* Hot tracking not available */
798 infoPtr->iHotTracked = item;
800 if (item >= 0)
802 /* Mark new hot-tracked to be redrawn to look highlighted */
803 if (out_redrawEnter != NULL)
804 *out_redrawEnter = item;
809 /******************************************************************************
810 * TAB_MouseMove
812 * Handles the mouse-move event. Updates tooltips. Updates hot-tracking.
814 static LRESULT
815 TAB_MouseMove (HWND hwnd, WPARAM wParam, LPARAM lParam)
817 int redrawLeave;
818 int redrawEnter;
820 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
822 if (infoPtr->hwndToolTip)
823 TAB_RelayEvent (infoPtr->hwndToolTip, hwnd,
824 WM_LBUTTONDOWN, wParam, lParam);
826 /* Determine which tab to highlight. Redraw tabs which change highlight
827 ** status. */
828 TAB_RecalcHotTrack(hwnd, &lParam, &redrawLeave, &redrawEnter);
830 if (redrawLeave != -1)
831 TAB_DrawLoneItemInterior(hwnd, infoPtr, redrawLeave);
832 if (redrawEnter != -1)
833 TAB_DrawLoneItemInterior(hwnd, infoPtr, redrawEnter);
835 return 0;
838 /******************************************************************************
839 * TAB_AdjustRect
841 * Calculates the tab control's display area given the window rectangle or
842 * the window rectangle given the requested display rectangle.
844 static LRESULT TAB_AdjustRect(
845 HWND hwnd,
846 WPARAM fLarger,
847 LPRECT prc)
849 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
850 DWORD lStyle = GetWindowLongA(hwnd, GWL_STYLE);
851 LONG *iRightBottom, *iLeftTop;
853 TRACE ("hwnd=%p fLarger=%d (%ld,%ld)-(%ld,%ld)\n", hwnd, fLarger, prc->left, prc->top, prc->right, prc->bottom);
855 if(lStyle & TCS_VERTICAL)
857 iRightBottom = &(prc->right);
858 iLeftTop = &(prc->left);
860 else
862 iRightBottom = &(prc->bottom);
863 iLeftTop = &(prc->top);
866 if (fLarger) /* Go from display rectangle */
868 /* Add the height of the tabs. */
869 if (lStyle & TCS_BOTTOM)
870 *iRightBottom += infoPtr->tabHeight * infoPtr->uNumRows;
871 else
872 *iLeftTop -= infoPtr->tabHeight * infoPtr->uNumRows +
873 ((lStyle & TCS_BUTTONS)? 3 * (infoPtr->uNumRows - 1) : 0);
875 /* Inflate the rectangle for the padding */
876 InflateRect(prc, DISPLAY_AREA_PADDINGX, DISPLAY_AREA_PADDINGY);
878 /* Inflate for the border */
879 InflateRect(prc, CONTROL_BORDER_SIZEX, CONTROL_BORDER_SIZEY);
881 else /* Go from window rectangle. */
883 /* Deflate the rectangle for the border */
884 InflateRect(prc, -CONTROL_BORDER_SIZEX, -CONTROL_BORDER_SIZEY);
886 /* Deflate the rectangle for the padding */
887 InflateRect(prc, -DISPLAY_AREA_PADDINGX, -DISPLAY_AREA_PADDINGY);
889 /* Remove the height of the tabs. */
890 if (lStyle & TCS_BOTTOM)
891 *iRightBottom -= infoPtr->tabHeight * infoPtr->uNumRows;
892 else
893 *iLeftTop += (infoPtr->tabHeight) * infoPtr->uNumRows +
894 ((lStyle & TCS_BUTTONS)? 3 * (infoPtr->uNumRows - 1) : 0);
897 return 0;
900 /******************************************************************************
901 * TAB_OnHScroll
903 * This method will handle the notification from the scroll control and
904 * perform the scrolling operation on the tab control.
906 static LRESULT TAB_OnHScroll(
907 HWND hwnd,
908 int nScrollCode,
909 int nPos,
910 HWND hwndScroll)
912 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
914 if(nScrollCode == SB_THUMBPOSITION && nPos != infoPtr->leftmostVisible)
916 if(nPos < infoPtr->leftmostVisible)
917 infoPtr->leftmostVisible--;
918 else
919 infoPtr->leftmostVisible++;
921 TAB_RecalcHotTrack(hwnd, NULL, NULL, NULL);
922 TAB_InvalidateTabArea(hwnd, infoPtr);
923 SendMessageW(infoPtr->hwndUpDown, UDM_SETPOS, 0,
924 MAKELONG(infoPtr->leftmostVisible, 0));
927 return 0;
930 /******************************************************************************
931 * TAB_SetupScrolling
933 * This method will check the current scrolling state and make sure the
934 * scrolling control is displayed (or not).
936 static void TAB_SetupScrolling(
937 HWND hwnd,
938 TAB_INFO* infoPtr,
939 const RECT* clientRect)
941 INT maxRange = 0;
942 DWORD lStyle = GetWindowLongA(hwnd, GWL_STYLE);
944 if (infoPtr->needsScrolling)
946 RECT controlPos;
947 INT vsize, tabwidth;
950 * Calculate the position of the scroll control.
952 if(lStyle & TCS_VERTICAL)
954 controlPos.right = clientRect->right;
955 controlPos.left = controlPos.right - 2 * GetSystemMetrics(SM_CXHSCROLL);
957 if (lStyle & TCS_BOTTOM)
959 controlPos.top = clientRect->bottom - infoPtr->tabHeight;
960 controlPos.bottom = controlPos.top + GetSystemMetrics(SM_CYHSCROLL);
962 else
964 controlPos.bottom = clientRect->top + infoPtr->tabHeight;
965 controlPos.top = controlPos.bottom - GetSystemMetrics(SM_CYHSCROLL);
968 else
970 controlPos.right = clientRect->right;
971 controlPos.left = controlPos.right - 2 * GetSystemMetrics(SM_CXHSCROLL);
973 if (lStyle & TCS_BOTTOM)
975 controlPos.top = clientRect->bottom - infoPtr->tabHeight;
976 controlPos.bottom = controlPos.top + GetSystemMetrics(SM_CYHSCROLL);
978 else
980 controlPos.bottom = clientRect->top + infoPtr->tabHeight;
981 controlPos.top = controlPos.bottom - GetSystemMetrics(SM_CYHSCROLL);
986 * If we don't have a scroll control yet, we want to create one.
987 * If we have one, we want to make sure it's positioned properly.
989 if (infoPtr->hwndUpDown==0)
991 infoPtr->hwndUpDown = CreateWindowA("msctls_updown32",
993 WS_VISIBLE | WS_CHILD | UDS_HORZ,
994 controlPos.left, controlPos.top,
995 controlPos.right - controlPos.left,
996 controlPos.bottom - controlPos.top,
997 hwnd,
998 NULL,
999 NULL,
1000 NULL);
1002 else
1004 SetWindowPos(infoPtr->hwndUpDown,
1005 NULL,
1006 controlPos.left, controlPos.top,
1007 controlPos.right - controlPos.left,
1008 controlPos.bottom - controlPos.top,
1009 SWP_SHOWWINDOW | SWP_NOZORDER);
1012 /* Now calculate upper limit of the updown control range.
1013 * We do this by calculating how many tabs will be offscreen when the
1014 * last tab is visible.
1016 if(infoPtr->uNumItem)
1018 vsize = clientRect->right - (controlPos.right - controlPos.left + 1);
1019 maxRange = infoPtr->uNumItem;
1020 tabwidth = TAB_GetItem(infoPtr, infoPtr->uNumItem - 1)->rect.right;
1022 for(; maxRange > 0; maxRange--)
1024 if(tabwidth - TAB_GetItem(infoPtr,maxRange - 1)->rect.left > vsize)
1025 break;
1028 if(maxRange == infoPtr->uNumItem)
1029 maxRange--;
1032 else
1034 /* If we once had a scroll control... hide it */
1035 if (infoPtr->hwndUpDown!=0)
1036 ShowWindow(infoPtr->hwndUpDown, SW_HIDE);
1038 if (infoPtr->hwndUpDown)
1039 SendMessageW(infoPtr->hwndUpDown, UDM_SETRANGE32, 0, maxRange);
1042 /******************************************************************************
1043 * TAB_SetItemBounds
1045 * This method will calculate the position rectangles of all the items in the
1046 * control. The rectangle calculated starts at 0 for the first item in the
1047 * list and ignores scrolling and selection.
1048 * It also uses the current font to determine the height of the tab row and
1049 * it checks if all the tabs fit in the client area of the window. If they
1050 * don't, a scrolling control is added.
1052 static void TAB_SetItemBounds (HWND hwnd)
1054 TAB_INFO* infoPtr = TAB_GetInfoPtr(hwnd);
1055 LONG lStyle = GetWindowLongA(hwnd, GWL_STYLE);
1056 TEXTMETRICA fontMetrics;
1057 UINT curItem;
1058 INT curItemLeftPos;
1059 INT curItemRowCount;
1060 HFONT hFont, hOldFont;
1061 HDC hdc;
1062 RECT clientRect;
1063 SIZE size;
1064 INT iTemp;
1065 RECT* rcItem;
1066 INT iIndex;
1067 INT icon_width = 0;
1070 * We need to get text information so we need a DC and we need to select
1071 * a font.
1073 hdc = GetDC(hwnd);
1075 hFont = infoPtr->hFont ? infoPtr->hFont : GetStockObject (SYSTEM_FONT);
1076 hOldFont = SelectObject (hdc, hFont);
1079 * We will base the rectangle calculations on the client rectangle
1080 * of the control.
1082 GetClientRect(hwnd, &clientRect);
1084 /* if TCS_VERTICAL then swap the height and width so this code places the
1085 tabs along the top of the rectangle and we can just rotate them after
1086 rather than duplicate all of the below code */
1087 if(lStyle & TCS_VERTICAL)
1089 iTemp = clientRect.bottom;
1090 clientRect.bottom = clientRect.right;
1091 clientRect.right = iTemp;
1094 /* Now use hPadding and vPadding */
1095 infoPtr->uHItemPadding = infoPtr->uHItemPadding_s;
1096 infoPtr->uVItemPadding = infoPtr->uVItemPadding_s;
1098 /* The leftmost item will be "0" aligned */
1099 curItemLeftPos = 0;
1100 curItemRowCount = infoPtr->uNumItem ? 1 : 0;
1102 if (!(infoPtr->fHeightSet))
1104 int item_height;
1105 int icon_height = 0;
1107 /* Use the current font to determine the height of a tab. */
1108 GetTextMetricsA(hdc, &fontMetrics);
1110 /* Get the icon height */
1111 if (infoPtr->himl)
1112 ImageList_GetIconSize(infoPtr->himl, 0, &icon_height);
1114 /* Take the highest between font or icon */
1115 if (fontMetrics.tmHeight > icon_height)
1116 item_height = fontMetrics.tmHeight + 2;
1117 else
1118 item_height = icon_height;
1121 * Make sure there is enough space for the letters + icon + growing the
1122 * selected item + extra space for the selected item.
1124 infoPtr->tabHeight = item_height +
1125 ((lStyle & TCS_BUTTONS) ? 2 : 1) *
1126 infoPtr->uVItemPadding;
1128 TRACE("tabH=%d, tmH=%ld, iconh=%d\n",
1129 infoPtr->tabHeight, fontMetrics.tmHeight, icon_height);
1132 TRACE("client right=%ld\n", clientRect.right);
1134 /* Get the icon width */
1135 if (infoPtr->himl)
1137 ImageList_GetIconSize(infoPtr->himl, &icon_width, 0);
1139 if (lStyle & TCS_FIXEDWIDTH)
1140 icon_width += 4;
1141 else
1142 /* Add padding if icon is present */
1143 icon_width += infoPtr->uHItemPadding;
1146 for (curItem = 0; curItem < infoPtr->uNumItem; curItem++)
1148 TAB_ITEM *curr = TAB_GetItem(infoPtr, curItem);
1150 /* Set the leftmost position of the tab. */
1151 curr->rect.left = curItemLeftPos;
1153 if ((lStyle & TCS_FIXEDWIDTH) || !curr->pszText)
1155 curr->rect.right = curr->rect.left +
1156 max(infoPtr->tabWidth, icon_width);
1158 else
1160 int num = 2;
1162 /* Calculate how wide the tab is depending on the text it contains */
1163 GetTextExtentPoint32W(hdc, curr->pszText,
1164 lstrlenW(curr->pszText), &size);
1166 curr->rect.right = curr->rect.left + size.cx + icon_width +
1167 num * infoPtr->uHItemPadding;
1168 TRACE("for <%s>, l,r=%ld,%ld, num=%d\n",
1169 debugstr_w(curr->pszText), curr->rect.left, curr->rect.right, num);
1173 * Check if this is a multiline tab control and if so
1174 * check to see if we should wrap the tabs
1176 * Wrap all these tabs. We will arrange them evenly later.
1180 if (((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)) &&
1181 (curr->rect.right >
1182 (clientRect.right - CONTROL_BORDER_SIZEX - DISPLAY_AREA_PADDINGX)))
1184 curr->rect.right -= curr->rect.left;
1186 curr->rect.left = 0;
1187 curItemRowCount++;
1188 TRACE("wrapping <%s>, l,r=%ld,%ld\n", debugstr_w(curr->pszText),
1189 curr->rect.left, curr->rect.right);
1192 curr->rect.bottom = 0;
1193 curr->rect.top = curItemRowCount - 1;
1195 TRACE("TextSize: %li\n", size.cx);
1196 TRACE("Rect: T %li, L %li, B %li, R %li\n", curr->rect.top,
1197 curr->rect.left, curr->rect.bottom, curr->rect.right);
1200 * The leftmost position of the next item is the rightmost position
1201 * of this one.
1203 if (lStyle & TCS_BUTTONS)
1205 curItemLeftPos = curr->rect.right + BUTTON_SPACINGX;
1206 if (lStyle & TCS_FLATBUTTONS)
1207 curItemLeftPos += FLAT_BTN_SPACINGX;
1209 else
1210 curItemLeftPos = curr->rect.right;
1213 if (!((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)))
1216 * Check if we need a scrolling control.
1218 infoPtr->needsScrolling = (curItemLeftPos + (2 * SELECTED_TAB_OFFSET) >
1219 clientRect.right);
1221 /* Don't need scrolling, then update infoPtr->leftmostVisible */
1222 if(!infoPtr->needsScrolling)
1223 infoPtr->leftmostVisible = 0;
1225 else
1228 * No scrolling in Multiline or Vertical styles.
1230 infoPtr->needsScrolling = FALSE;
1231 infoPtr->leftmostVisible = 0;
1233 TAB_SetupScrolling(hwnd, infoPtr, &clientRect);
1235 /* Set the number of rows */
1236 infoPtr->uNumRows = curItemRowCount;
1238 /* Arrange all tabs evenly if style says so */
1239 if (!(lStyle & TCS_RAGGEDRIGHT) && ((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)) && (infoPtr->uNumItem > 0))
1241 INT tabPerRow,remTab,iRow;
1242 UINT iItm;
1243 INT iCount=0;
1246 * Ok windows tries to even out the rows. place the same
1247 * number of tabs in each row. So lets give that a shot
1250 tabPerRow = infoPtr->uNumItem / (infoPtr->uNumRows);
1251 remTab = infoPtr->uNumItem % (infoPtr->uNumRows);
1253 for (iItm=0,iRow=0,iCount=0,curItemLeftPos=0;
1254 iItm<infoPtr->uNumItem;
1255 iItm++,iCount++)
1257 /* normalize the current rect */
1258 TAB_ITEM *curr = TAB_GetItem(infoPtr, iItm);
1260 /* shift the item to the left side of the clientRect */
1261 curr->rect.right -= curr->rect.left;
1262 curr->rect.left = 0;
1264 TRACE("r=%ld, cl=%d, cl.r=%ld, iCount=%d, iRow=%d, uNumRows=%d, remTab=%d, tabPerRow=%d\n",
1265 curr->rect.right, curItemLeftPos, clientRect.right,
1266 iCount, iRow, infoPtr->uNumRows, remTab, tabPerRow);
1268 /* if we have reached the maximum number of tabs on this row */
1269 /* move to the next row, reset our current item left position and */
1270 /* the count of items on this row */
1272 if (lStyle & TCS_VERTICAL) {
1273 /* Vert: Add the remaining tabs in the *last* remainder rows */
1274 if (iCount >= ((iRow>=(INT)infoPtr->uNumRows - remTab)?tabPerRow + 1:tabPerRow)) {
1275 iRow++;
1276 curItemLeftPos = 0;
1277 iCount = 0;
1279 } else {
1280 /* Horz: Add the remaining tabs in the *first* remainder rows */
1281 if (iCount >= ((iRow<remTab)?tabPerRow + 1:tabPerRow)) {
1282 iRow++;
1283 curItemLeftPos = 0;
1284 iCount = 0;
1288 /* shift the item to the right to place it as the next item in this row */
1289 curr->rect.left += curItemLeftPos;
1290 curr->rect.right += curItemLeftPos;
1291 curr->rect.top = iRow;
1292 if (lStyle & TCS_BUTTONS)
1294 curItemLeftPos = curr->rect.right + 1;
1295 if (lStyle & TCS_FLATBUTTONS)
1296 curItemLeftPos += FLAT_BTN_SPACINGX;
1298 else
1299 curItemLeftPos = curr->rect.right;
1301 TRACE("arranging <%s>, l,r=%ld,%ld, row=%ld\n",
1302 debugstr_w(curr->pszText), curr->rect.left,
1303 curr->rect.right, curr->rect.top);
1307 * Justify the rows
1310 INT widthDiff, iIndexStart=0, iIndexEnd=0;
1311 INT remainder;
1312 INT iCount=0;
1314 while(iIndexStart < infoPtr->uNumItem)
1316 TAB_ITEM *start = TAB_GetItem(infoPtr, iIndexStart);
1319 * find the index of the row
1321 /* find the first item on the next row */
1322 for (iIndexEnd=iIndexStart;
1323 (iIndexEnd < infoPtr->uNumItem) &&
1324 (TAB_GetItem(infoPtr, iIndexEnd)->rect.top ==
1325 start->rect.top) ;
1326 iIndexEnd++)
1327 /* intentionally blank */;
1330 * we need to justify these tabs so they fill the whole given
1331 * client area
1334 /* find the amount of space remaining on this row */
1335 widthDiff = clientRect.right - (2 * SELECTED_TAB_OFFSET) -
1336 TAB_GetItem(infoPtr, iIndexEnd - 1)->rect.right;
1338 /* iCount is the number of tab items on this row */
1339 iCount = iIndexEnd - iIndexStart;
1341 if (iCount > 1)
1343 remainder = widthDiff % iCount;
1344 widthDiff = widthDiff / iCount;
1345 /* add widthDiff/iCount, or extra space/items on row, to each item on this row */
1346 for (iIndex=iIndexStart, iCount=0; iIndex < iIndexEnd; iIndex++, iCount++)
1348 TAB_ITEM *item = TAB_GetItem(infoPtr, iIndex);
1350 item->rect.left += iCount * widthDiff;
1351 item->rect.right += (iCount + 1) * widthDiff;
1353 TRACE("adjusting 1 <%s>, l,r=%ld,%ld\n",
1354 debugstr_w(item->pszText),
1355 item->rect.left, item->rect.right);
1358 TAB_GetItem(infoPtr, iIndex - 1)->rect.right += remainder;
1360 else /* we have only one item on this row, make it take up the entire row */
1362 start->rect.left = clientRect.left;
1363 start->rect.right = clientRect.right - 4;
1365 TRACE("adjusting 2 <%s>, l,r=%ld,%ld\n",
1366 debugstr_w(start->pszText),
1367 start->rect.left, start->rect.right);
1372 iIndexStart = iIndexEnd;
1377 /* if TCS_VERTICAL rotate the tabs so they are along the side of the clientRect */
1378 if(lStyle & TCS_VERTICAL)
1380 RECT rcOriginal;
1381 for(iIndex = 0; iIndex < infoPtr->uNumItem; iIndex++)
1383 rcItem = &TAB_GetItem(infoPtr, iIndex)->rect;
1385 rcOriginal = *rcItem;
1387 /* this is rotating the items by 90 degrees clockwise around the center of the control */
1388 rcItem->top = (rcOriginal.left - clientRect.left);
1389 rcItem->bottom = rcItem->top + (rcOriginal.right - rcOriginal.left);
1390 rcItem->left = rcOriginal.top;
1391 rcItem->right = rcOriginal.bottom;
1395 TAB_EnsureSelectionVisible(hwnd,infoPtr);
1396 TAB_RecalcHotTrack(hwnd, NULL, NULL, NULL);
1398 /* Cleanup */
1399 SelectObject (hdc, hOldFont);
1400 ReleaseDC (hwnd, hdc);
1404 static void
1405 TAB_EraseTabInterior
1407 HWND hwnd,
1408 HDC hdc,
1409 INT iItem,
1410 RECT* drawRect
1413 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
1414 LONG lStyle = GetWindowLongA(hwnd, GWL_STYLE);
1415 HBRUSH hbr = CreateSolidBrush (comctl32_color.clrBtnFace);
1416 BOOL deleteBrush = TRUE;
1417 RECT rTemp = *drawRect;
1419 InflateRect(&rTemp, -2, -2);
1420 if (lStyle & TCS_BUTTONS)
1422 if (iItem == infoPtr->iSelected)
1424 /* Background color */
1425 if (!(lStyle & TCS_OWNERDRAWFIXED))
1427 DeleteObject(hbr);
1428 hbr = GetSysColorBrush(COLOR_SCROLLBAR);
1430 SetTextColor(hdc, comctl32_color.clr3dFace);
1431 SetBkColor(hdc, comctl32_color.clr3dHilight);
1433 /* if COLOR_WINDOW happens to be the same as COLOR_3DHILIGHT
1434 * we better use 0x55aa bitmap brush to make scrollbar's background
1435 * look different from the window background.
1437 if (comctl32_color.clr3dHilight == comctl32_color.clrWindow)
1438 hbr = COMCTL32_hPattern55AABrush;
1440 deleteBrush = FALSE;
1442 FillRect(hdc, &rTemp, hbr);
1444 else /* ! selected */
1446 if (lStyle & TCS_FLATBUTTONS)
1448 FillRect(hdc, drawRect, hbr);
1449 if (iItem == infoPtr->iHotTracked)
1450 DrawEdge(hdc, drawRect, EDGE_RAISED, BF_SOFT|BF_RECT);
1452 else
1453 FillRect(hdc, &rTemp, hbr);
1457 else /* !TCS_BUTTONS */
1459 FillRect(hdc, &rTemp, hbr);
1462 /* Cleanup */
1463 if (deleteBrush) DeleteObject(hbr);
1466 /******************************************************************************
1467 * TAB_DrawItemInterior
1469 * This method is used to draw the interior (text and icon) of a single tab
1470 * into the tab control.
1472 static void
1473 TAB_DrawItemInterior
1475 HWND hwnd,
1476 HDC hdc,
1477 INT iItem,
1478 RECT* drawRect
1481 TAB_INFO* infoPtr = TAB_GetInfoPtr(hwnd);
1482 LONG lStyle = GetWindowLongA(hwnd, GWL_STYLE);
1484 RECT localRect;
1486 HPEN htextPen;
1487 HPEN holdPen;
1488 INT oldBkMode;
1489 HFONT hOldFont;
1491 /* if (drawRect == NULL) */
1493 BOOL isVisible;
1494 RECT itemRect;
1495 RECT selectedRect;
1498 * Get the rectangle for the item.
1500 isVisible = TAB_InternalGetItemRect(hwnd, infoPtr, iItem, &itemRect, &selectedRect);
1501 if (!isVisible)
1502 return;
1505 * Make sure drawRect points to something valid; simplifies code.
1507 drawRect = &localRect;
1510 * This logic copied from the part of TAB_DrawItem which draws
1511 * the tab background. It's important to keep it in sync. I
1512 * would have liked to avoid code duplication, but couldn't figure
1513 * out how without making spaghetti of TAB_DrawItem.
1515 if (iItem == infoPtr->iSelected)
1516 *drawRect = selectedRect;
1517 else
1518 *drawRect = itemRect;
1520 if (lStyle & TCS_BUTTONS)
1522 if (iItem == infoPtr->iSelected)
1524 drawRect->left += 4;
1525 drawRect->top += 4;
1526 drawRect->right -= 4;
1527 drawRect->bottom -= 1;
1529 else
1531 drawRect->left += 2;
1532 drawRect->top += 2;
1533 drawRect->right -= 2;
1534 drawRect->bottom -= 2;
1537 else
1539 if ((lStyle & TCS_VERTICAL) && (lStyle & TCS_BOTTOM))
1541 if (iItem != infoPtr->iSelected)
1543 drawRect->left += 2;
1544 drawRect->top += 2;
1545 drawRect->bottom -= 2;
1548 else if (lStyle & TCS_VERTICAL)
1550 if (iItem == infoPtr->iSelected)
1552 drawRect->right += 1;
1554 else
1556 drawRect->top += 2;
1557 drawRect->right -= 2;
1558 drawRect->bottom -= 2;
1561 else if (lStyle & TCS_BOTTOM)
1563 if (iItem == infoPtr->iSelected)
1565 drawRect->top -= 2;
1567 else
1569 InflateRect(drawRect, -2, -2);
1570 drawRect->bottom += 2;
1573 else
1575 if (iItem == infoPtr->iSelected)
1577 drawRect->bottom += 3;
1579 else
1581 drawRect->bottom -= 2;
1582 InflateRect(drawRect, -2, 0);
1587 TRACE("drawRect=(%ld,%ld)-(%ld,%ld)\n",
1588 drawRect->left, drawRect->top, drawRect->right, drawRect->bottom);
1590 /* Clear interior */
1591 TAB_EraseTabInterior (hwnd, hdc, iItem, drawRect);
1593 /* Draw the focus rectangle */
1594 if (!(lStyle & TCS_FOCUSNEVER) &&
1595 (GetFocus() == hwnd) &&
1596 (iItem == infoPtr->uFocus) )
1598 RECT rFocus = *drawRect;
1599 InflateRect(&rFocus, -3, -3);
1600 if (lStyle & TCS_BOTTOM && !(lStyle & TCS_VERTICAL))
1601 rFocus.top -= 3;
1602 if (lStyle & TCS_BUTTONS)
1604 rFocus.left -= 3;
1605 rFocus.top -= 3;
1608 DrawFocusRect(hdc, &rFocus);
1612 * Text pen
1614 htextPen = CreatePen( PS_SOLID, 1, GetSysColor(COLOR_BTNTEXT) );
1615 holdPen = SelectObject(hdc, htextPen);
1616 hOldFont = SelectObject(hdc, infoPtr->hFont);
1619 * Setup for text output
1621 oldBkMode = SetBkMode(hdc, TRANSPARENT);
1622 SetTextColor(hdc, (((iItem == infoPtr->iHotTracked) && !(lStyle & TCS_FLATBUTTONS)) |
1623 (TAB_GetItem(infoPtr, iItem)->dwState & TCIS_HIGHLIGHTED)) ?
1624 comctl32_color.clrHighlight : comctl32_color.clrBtnText);
1627 * if owner draw, tell the owner to draw
1629 if ((lStyle & TCS_OWNERDRAWFIXED) && GetParent(hwnd))
1631 DRAWITEMSTRUCT dis;
1632 UINT id;
1634 drawRect->top += 2;
1635 drawRect->right -= 1;
1636 if ( iItem == infoPtr->iSelected )
1638 drawRect->right -= 1;
1639 drawRect->left += 1;
1643 * get the control id
1645 id = (UINT)GetWindowLongPtrW( hwnd, GWLP_ID );
1648 * put together the DRAWITEMSTRUCT
1650 dis.CtlType = ODT_TAB;
1651 dis.CtlID = id;
1652 dis.itemID = iItem;
1653 dis.itemAction = ODA_DRAWENTIRE;
1654 dis.itemState = 0;
1655 if ( iItem == infoPtr->iSelected )
1656 dis.itemState |= ODS_SELECTED;
1657 if (infoPtr->uFocus == iItem)
1658 dis.itemState |= ODS_FOCUS;
1659 dis.hwndItem = hwnd; /* */
1660 dis.hDC = hdc;
1661 CopyRect(&dis.rcItem,drawRect);
1662 dis.itemData = 0;
1663 memcpy( &dis.itemData, TAB_GetItem(infoPtr, iItem)->extra, min(sizeof(dis.itemData),infoPtr->cbInfo) );
1666 * send the draw message
1668 SendMessageW( infoPtr->hwndNotify, WM_DRAWITEM, (WPARAM)id, (LPARAM)&dis );
1670 else
1672 TAB_ITEM *item = TAB_GetItem(infoPtr, iItem);
1673 RECT rcTemp;
1674 RECT rcImage;
1676 /* used to center the icon and text in the tab */
1677 RECT rcText;
1678 INT center_offset_h, center_offset_v;
1680 /* set rcImage to drawRect, we will use top & left in our ImageList_Draw call */
1681 rcImage = *drawRect;
1683 rcTemp = *drawRect;
1685 rcText.left = rcText.top = rcText.right = rcText.bottom = 0;
1687 /* get the rectangle that the text fits in */
1688 if (item->pszText)
1690 DrawTextW(hdc, item->pszText, -1, &rcText, DT_CALCRECT);
1693 * If not owner draw, then do the drawing ourselves.
1695 * Draw the icon.
1697 if (infoPtr->himl && (item->mask & TCIF_IMAGE))
1699 INT cx;
1700 INT cy;
1702 ImageList_GetIconSize(infoPtr->himl, &cx, &cy);
1704 if(lStyle & TCS_VERTICAL)
1706 center_offset_h = ((drawRect->bottom - drawRect->top) - (cy + infoPtr->uHItemPadding + (rcText.right - rcText.left))) / 2;
1707 center_offset_v = ((drawRect->right - drawRect->left) - (cx + infoPtr->uVItemPadding)) / 2;
1709 else
1711 center_offset_h = ((drawRect->right - drawRect->left) - (cx + infoPtr->uHItemPadding + (rcText.right - rcText.left))) / 2;
1712 center_offset_v = ((drawRect->bottom - drawRect->top) - (cy + infoPtr->uVItemPadding)) / 2;
1715 if (lStyle & TCS_FIXEDWIDTH && lStyle & (TCS_FORCELABELLEFT | TCS_FORCEICONLEFT))
1716 center_offset_h = infoPtr->uHItemPadding;
1718 if (center_offset_h < 2)
1719 center_offset_h = 2;
1721 if (center_offset_v < 0)
1722 center_offset_v = 0;
1724 TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%ld,%ld)-(%ld,%ld), textlen=%ld\n",
1725 debugstr_w(item->pszText), center_offset_h, center_offset_v,
1726 drawRect->left, drawRect->top, drawRect->right, drawRect->bottom,
1727 (rcText.right-rcText.left));
1729 if((lStyle & TCS_VERTICAL) && (lStyle & TCS_BOTTOM))
1731 rcImage.top = drawRect->top + center_offset_h;
1732 /* if tab is TCS_VERTICAL and TCS_BOTTOM, the text is drawn from the */
1733 /* right side of the tab, but the image still uses the left as its x position */
1734 /* this keeps the image always drawn off of the same side of the tab */
1735 rcImage.left = drawRect->right - cx - center_offset_v;
1736 drawRect->top += cy + infoPtr->uHItemPadding;
1738 else if(lStyle & TCS_VERTICAL)
1740 rcImage.top = drawRect->bottom - cy - center_offset_h;
1741 rcImage.left = drawRect->left + center_offset_v;
1742 drawRect->bottom -= cy + infoPtr->uHItemPadding;
1744 else /* normal style, whether TCS_BOTTOM or not */
1746 rcImage.left = drawRect->left + center_offset_h;
1747 rcImage.top = drawRect->top + center_offset_v;
1748 drawRect->left += cx + infoPtr->uHItemPadding;
1751 TRACE("drawing image=%d, left=%ld, top=%ld\n",
1752 item->iImage, rcImage.left, rcImage.top-1);
1753 ImageList_Draw
1755 infoPtr->himl,
1756 item->iImage,
1757 hdc,
1758 rcImage.left,
1759 rcImage.top,
1760 ILD_NORMAL
1764 /* Now position text */
1765 if (lStyle & TCS_FIXEDWIDTH && lStyle & TCS_FORCELABELLEFT)
1766 center_offset_h = infoPtr->uHItemPadding;
1767 else
1768 if(lStyle & TCS_VERTICAL)
1769 center_offset_h = ((drawRect->bottom - drawRect->top) - (rcText.right - rcText.left)) / 2;
1770 else
1771 center_offset_h = ((drawRect->right - drawRect->left) - (rcText.right - rcText.left)) / 2;
1773 if(lStyle & TCS_VERTICAL)
1775 if(lStyle & TCS_BOTTOM)
1776 drawRect->top+=center_offset_h;
1777 else
1778 drawRect->bottom-=center_offset_h;
1780 center_offset_v = ((drawRect->right - drawRect->left) - ((rcText.bottom - rcText.top) + infoPtr->uVItemPadding)) / 2;
1782 else
1784 drawRect->left += center_offset_h;
1785 center_offset_v = ((drawRect->bottom - drawRect->top) - ((rcText.bottom - rcText.top) + infoPtr->uVItemPadding)) / 2;
1788 if (center_offset_v < 0)
1789 center_offset_v = 0;
1791 if(lStyle & TCS_VERTICAL)
1792 drawRect->left += center_offset_v;
1793 else
1794 drawRect->top += center_offset_v;
1796 /* Draw the text */
1797 if(lStyle & TCS_VERTICAL) /* if we are vertical rotate the text and each character */
1799 LOGFONTA logfont;
1800 HFONT hFont = 0;
1801 INT nEscapement = 900;
1802 INT nOrientation = 900;
1804 if(lStyle & TCS_BOTTOM)
1806 nEscapement = -900;
1807 nOrientation = -900;
1810 /* to get a font with the escapement and orientation we are looking for, we need to */
1811 /* call CreateFontIndirectA, which requires us to set the values of the logfont we pass in */
1812 if (!GetObjectA((infoPtr->hFont) ?
1813 infoPtr->hFont : GetStockObject(SYSTEM_FONT),
1814 sizeof(LOGFONTA),&logfont))
1816 INT iPointSize = 9;
1818 lstrcpyA(logfont.lfFaceName, "Arial");
1819 logfont.lfHeight = -MulDiv(iPointSize, GetDeviceCaps(hdc, LOGPIXELSY),
1820 72);
1821 logfont.lfWeight = FW_NORMAL;
1822 logfont.lfItalic = 0;
1823 logfont.lfUnderline = 0;
1824 logfont.lfStrikeOut = 0;
1827 logfont.lfEscapement = nEscapement;
1828 logfont.lfOrientation = nOrientation;
1829 hFont = CreateFontIndirectA(&logfont);
1830 SelectObject(hdc, hFont);
1832 if (item->pszText)
1834 ExtTextOutW(hdc,
1835 (lStyle & TCS_BOTTOM) ? drawRect->right : drawRect->left,
1836 (!(lStyle & TCS_BOTTOM)) ? drawRect->bottom : drawRect->top,
1837 ETO_CLIPPED,
1838 drawRect,
1839 item->pszText,
1840 lstrlenW(item->pszText),
1844 DeleteObject(hFont);
1846 else
1848 TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%ld,%ld)-(%ld,%ld), textlen=%ld\n",
1849 debugstr_w(item->pszText), center_offset_h, center_offset_v,
1850 drawRect->left, drawRect->top, drawRect->right, drawRect->bottom,
1851 (rcText.right-rcText.left));
1852 if (item->pszText)
1854 DrawTextW
1856 hdc,
1857 item->pszText,
1858 lstrlenW(item->pszText),
1859 drawRect,
1860 DT_LEFT | DT_SINGLELINE
1865 *drawRect = rcTemp; /* restore drawRect */
1869 * Cleanup
1871 SelectObject(hdc, hOldFont);
1872 SetBkMode(hdc, oldBkMode);
1873 SelectObject(hdc, holdPen);
1874 DeleteObject( htextPen );
1877 /******************************************************************************
1878 * TAB_DrawItem
1880 * This method is used to draw a single tab into the tab control.
1882 static void TAB_DrawItem(
1883 HWND hwnd,
1884 HDC hdc,
1885 INT iItem)
1887 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
1888 LONG lStyle = GetWindowLongA(hwnd, GWL_STYLE);
1889 RECT itemRect;
1890 RECT selectedRect;
1891 BOOL isVisible;
1892 RECT r, fillRect, r1;
1893 INT clRight = 0;
1894 INT clBottom = 0;
1895 COLORREF bkgnd, corner;
1898 * Get the rectangle for the item.
1900 isVisible = TAB_InternalGetItemRect(hwnd,
1901 infoPtr,
1902 iItem,
1903 &itemRect,
1904 &selectedRect);
1906 if (isVisible)
1908 RECT rUD, rC;
1910 /* Clip UpDown control to not draw over it */
1911 if (infoPtr->needsScrolling)
1913 GetWindowRect(hwnd, &rC);
1914 GetWindowRect(infoPtr->hwndUpDown, &rUD);
1915 ExcludeClipRect(hdc, rUD.left - rC.left, rUD.top - rC.top, rUD.right - rC.left, rUD.bottom - rC.top);
1918 /* If you need to see what the control is doing,
1919 * then override these variables. They will change what
1920 * fill colors are used for filling the tabs, and the
1921 * corners when drawing the edge.
1923 bkgnd = comctl32_color.clrBtnFace;
1924 corner = comctl32_color.clrBtnFace;
1926 if (lStyle & TCS_BUTTONS)
1928 /* Get item rectangle */
1929 r = itemRect;
1931 /* Separators between flat buttons */
1932 if (lStyle & TCS_FLATBUTTONS)
1934 r1 = r;
1935 r1.right += (FLAT_BTN_SPACINGX -2);
1936 DrawEdge(hdc, &r1, EDGE_ETCHED, BF_RIGHT);
1939 if (iItem == infoPtr->iSelected)
1941 DrawEdge(hdc, &r, EDGE_SUNKEN, BF_SOFT|BF_RECT);
1943 OffsetRect(&r, 1, 1);
1945 else /* ! selected */
1947 if (!(lStyle & TCS_FLATBUTTONS))
1948 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_RECT);
1951 else /* !TCS_BUTTONS */
1953 /* We draw a rectangle of different sizes depending on the selection
1954 * state. */
1955 if (iItem == infoPtr->iSelected) {
1956 RECT rect;
1957 GetClientRect (hwnd, &rect);
1958 clRight = rect.right;
1959 clBottom = rect.bottom;
1960 r = selectedRect;
1962 else
1963 r = itemRect;
1966 * Erase the background. (Delay it but setup rectangle.)
1967 * This is necessary when drawing the selected item since it is larger
1968 * than the others, it might overlap with stuff already drawn by the
1969 * other tabs
1971 fillRect = r;
1973 if(lStyle & TCS_VERTICAL)
1975 /* These are for adjusting the drawing of a Selected tab */
1976 /* The initial values are for the normal case of non-Selected */
1977 int ZZ = 1; /* Do not strech if selected */
1978 if (iItem == infoPtr->iSelected) {
1979 ZZ = 0;
1981 /* if leftmost draw the line longer */
1982 if(selectedRect.top == 0)
1983 fillRect.top += CONTROL_BORDER_SIZEY;
1984 /* if rightmost draw the line longer */
1985 if(selectedRect.bottom == clBottom)
1986 fillRect.bottom -= CONTROL_BORDER_SIZEY;
1989 if (lStyle & TCS_BOTTOM)
1991 /* Adjust both rectangles to match native */
1992 r.left += (1-ZZ);
1994 TRACE("<right> 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_RIGHT|BF_TOP|BF_BOTTOM);
2006 /* Now erase the top corner and draw diagonal edge */
2007 SetBkColor(hdc, corner);
2008 r1.left = r.right - ROUND_CORNER_SIZE - 1;
2009 r1.top = r.top;
2010 r1.right = r.right;
2011 r1.bottom = r1.top + ROUND_CORNER_SIZE;
2012 ExtTextOutA(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2013 r1.right--;
2014 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPLEFT);
2016 /* Now erase the bottom corner and draw diagonal edge */
2017 r1.left = r.right - ROUND_CORNER_SIZE - 1;
2018 r1.bottom = r.bottom;
2019 r1.right = r.right;
2020 r1.top = r1.bottom - ROUND_CORNER_SIZE;
2021 ExtTextOutA(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2022 r1.right--;
2023 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMLEFT);
2025 if ((iItem == infoPtr->iSelected) && (selectedRect.top == 0)) {
2026 r1 = r;
2027 r1.right = r1.left;
2028 r1.left--;
2029 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_TOP);
2033 else
2035 TRACE("<left> item=%d, fill=(%ld,%ld)-(%ld,%ld), edge=(%ld,%ld)-(%ld,%ld)\n",
2036 iItem,
2037 fillRect.left,fillRect.top,fillRect.right,fillRect.bottom,
2038 r.left,r.top,r.right,r.bottom);
2040 /* Clear interior */
2041 SetBkColor(hdc, bkgnd);
2042 ExtTextOutA(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2044 /* Draw rectangular edge around tab */
2045 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_TOP|BF_BOTTOM);
2047 /* Now erase the top corner and draw diagonal edge */
2048 SetBkColor(hdc, corner);
2049 r1.left = r.left;
2050 r1.top = r.top;
2051 r1.right = r1.left + ROUND_CORNER_SIZE + 1;
2052 r1.bottom = r1.top + ROUND_CORNER_SIZE;
2053 ExtTextOutA(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2054 r1.left++;
2055 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPRIGHT);
2057 /* Now erase the bottom corner and draw diagonal edge */
2058 r1.left = r.left;
2059 r1.bottom = r.bottom;
2060 r1.right = r1.left + ROUND_CORNER_SIZE + 1;
2061 r1.top = r1.bottom - ROUND_CORNER_SIZE;
2062 ExtTextOutA(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2063 r1.left++;
2064 DrawEdge(hdc, &r1, EDGE_SUNKEN, BF_DIAGONAL_ENDTOPLEFT);
2067 else /* ! TCS_VERTICAL */
2069 /* These are for adjusting the drawing of a Selected tab */
2070 /* The initial values are for the normal case of non-Selected */
2071 if (iItem == infoPtr->iSelected) {
2072 /* if leftmost draw the line longer */
2073 if(selectedRect.left == 0)
2074 fillRect.left += CONTROL_BORDER_SIZEX;
2075 /* if rightmost draw the line longer */
2076 if(selectedRect.right == clRight)
2077 fillRect.right -= CONTROL_BORDER_SIZEX;
2080 if (lStyle & TCS_BOTTOM)
2082 /* Adjust both rectangles for topmost row */
2083 if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2085 fillRect.top -= 2;
2086 r.top -= 1;
2089 TRACE("<bottom> item=%d, fill=(%ld,%ld)-(%ld,%ld), edge=(%ld,%ld)-(%ld,%ld)\n",
2090 iItem,
2091 fillRect.left,fillRect.top,fillRect.right,fillRect.bottom,
2092 r.left,r.top,r.right,r.bottom);
2094 /* Clear interior */
2095 SetBkColor(hdc, bkgnd);
2096 ExtTextOutA(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2098 /* Draw rectangular edge around tab */
2099 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_BOTTOM|BF_RIGHT);
2101 /* Now erase the righthand corner and draw diagonal edge */
2102 SetBkColor(hdc, corner);
2103 r1.left = r.right - ROUND_CORNER_SIZE;
2104 r1.bottom = r.bottom;
2105 r1.right = r.right;
2106 r1.top = r1.bottom - ROUND_CORNER_SIZE - 1;
2107 ExtTextOutA(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2108 r1.bottom--;
2109 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMLEFT);
2111 /* Now erase the lefthand corner and draw diagonal edge */
2112 r1.left = r.left;
2113 r1.bottom = r.bottom;
2114 r1.right = r1.left + ROUND_CORNER_SIZE;
2115 r1.top = r1.bottom - ROUND_CORNER_SIZE - 1;
2116 ExtTextOutA(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2117 r1.bottom--;
2118 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPLEFT);
2120 if (iItem == infoPtr->iSelected)
2122 r.top += 2;
2123 r.left += 1;
2124 if (selectedRect.left == 0)
2126 r1 = r;
2127 r1.bottom = r1.top;
2128 r1.top--;
2129 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_LEFT);
2134 else
2136 /* Adjust both rectangles for bottommost row */
2137 if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2139 fillRect.bottom += 3;
2140 r.bottom += 2;
2143 TRACE("<top> item=%d, fill=(%ld,%ld)-(%ld,%ld), edge=(%ld,%ld)-(%ld,%ld)\n",
2144 iItem,
2145 fillRect.left,fillRect.top,fillRect.right,fillRect.bottom,
2146 r.left,r.top,r.right,r.bottom);
2148 /* Clear interior */
2149 SetBkColor(hdc, bkgnd);
2150 ExtTextOutA(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2152 /* Draw rectangular edge around tab */
2153 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_TOP|BF_RIGHT);
2155 /* Now erase the righthand corner and draw diagonal edge */
2156 SetBkColor(hdc, corner);
2157 r1.left = r.right - ROUND_CORNER_SIZE;
2158 r1.top = r.top;
2159 r1.right = r.right;
2160 r1.bottom = r1.top + ROUND_CORNER_SIZE + 1;
2161 ExtTextOutA(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2162 r1.top++;
2163 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMRIGHT);
2165 /* Now erase the lefthand corner and draw diagonal edge */
2166 r1.left = r.left;
2167 r1.top = r.top;
2168 r1.right = r1.left + ROUND_CORNER_SIZE;
2169 r1.bottom = r1.top + ROUND_CORNER_SIZE + 1;
2170 ExtTextOutA(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2171 r1.top++;
2172 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPRIGHT);
2177 TAB_DumpItemInternal(infoPtr, iItem);
2179 /* This modifies r to be the text rectangle. */
2180 TAB_DrawItemInterior(hwnd, hdc, iItem, &r);
2184 /******************************************************************************
2185 * TAB_DrawBorder
2187 * This method is used to draw the raised border around the tab control
2188 * "content" area.
2190 static void TAB_DrawBorder (HWND hwnd, HDC hdc)
2192 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2193 RECT rect;
2194 DWORD lStyle = GetWindowLongA(hwnd, GWL_STYLE);
2196 GetClientRect (hwnd, &rect);
2199 * Adjust for the style
2202 if (infoPtr->uNumItem)
2204 if ((lStyle & TCS_BOTTOM) && !(lStyle & TCS_VERTICAL))
2205 rect.bottom -= infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2206 else if((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
2207 rect.right -= infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2208 else if(lStyle & TCS_VERTICAL)
2209 rect.left += infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2210 else /* not TCS_VERTICAL and not TCS_BOTTOM */
2211 rect.top += infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2214 TRACE("border=(%ld,%ld)-(%ld,%ld)\n",
2215 rect.left, rect.top, rect.right, rect.bottom);
2217 DrawEdge(hdc, &rect, EDGE_RAISED, BF_SOFT|BF_RECT);
2220 /******************************************************************************
2221 * TAB_Refresh
2223 * This method repaints the tab control..
2225 static void TAB_Refresh (HWND hwnd, HDC hdc)
2227 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2228 HFONT hOldFont;
2229 INT i;
2231 if (!infoPtr->DoRedraw)
2232 return;
2234 hOldFont = SelectObject (hdc, infoPtr->hFont);
2236 if (GetWindowLongA(hwnd, GWL_STYLE) & TCS_BUTTONS)
2238 for (i = 0; i < infoPtr->uNumItem; i++)
2239 TAB_DrawItem (hwnd, hdc, i);
2241 else
2243 /* Draw all the non selected item first */
2244 for (i = 0; i < infoPtr->uNumItem; i++)
2246 if (i != infoPtr->iSelected)
2247 TAB_DrawItem (hwnd, hdc, i);
2250 /* Now, draw the border, draw it before the selected item
2251 * since the selected item overwrites part of the border. */
2252 TAB_DrawBorder (hwnd, hdc);
2254 /* Then, draw the selected item */
2255 TAB_DrawItem (hwnd, hdc, infoPtr->iSelected);
2257 /* If we haven't set the current focus yet, set it now.
2258 * Only happens when we first paint the tab controls */
2259 if (infoPtr->uFocus == -1)
2260 TAB_SetCurFocus(hwnd, infoPtr->iSelected);
2263 SelectObject (hdc, hOldFont);
2266 static DWORD
2267 TAB_GetRowCount (HWND hwnd )
2269 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2271 return infoPtr->uNumRows;
2274 static LRESULT
2275 TAB_SetRedraw (HWND hwnd, WPARAM wParam)
2277 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2279 infoPtr->DoRedraw=(BOOL) wParam;
2280 return 0;
2283 /******************************************************************************
2284 * TAB_EnsureSelectionVisible
2286 * This method will make sure that the current selection is completely
2287 * visible by scrolling until it is.
2289 static void TAB_EnsureSelectionVisible(
2290 HWND hwnd,
2291 TAB_INFO* infoPtr)
2293 INT iSelected = infoPtr->iSelected;
2294 LONG lStyle = GetWindowLongA(hwnd, GWL_STYLE);
2295 INT iOrigLeftmostVisible = infoPtr->leftmostVisible;
2297 /* set the items row to the bottommost row or topmost row depending on
2298 * style */
2299 if ((infoPtr->uNumRows > 1) && !(lStyle & TCS_BUTTONS))
2301 TAB_ITEM *selected = TAB_GetItem(infoPtr, iSelected);
2302 INT newselected;
2303 INT iTargetRow;
2305 if(lStyle & TCS_VERTICAL)
2306 newselected = selected->rect.left;
2307 else
2308 newselected = selected->rect.top;
2310 /* the target row is always (number of rows - 1)
2311 as row 0 is furthest from the clientRect */
2312 iTargetRow = infoPtr->uNumRows - 1;
2314 if (newselected != iTargetRow)
2316 UINT i;
2317 if(lStyle & TCS_VERTICAL)
2319 for (i=0; i < infoPtr->uNumItem; i++)
2321 /* move everything in the row of the selected item to the iTargetRow */
2322 TAB_ITEM *item = TAB_GetItem(infoPtr, i);
2324 if (item->rect.left == newselected )
2325 item->rect.left = iTargetRow;
2326 else
2328 if (item->rect.left > newselected)
2329 item->rect.left-=1;
2333 else
2335 for (i=0; i < infoPtr->uNumItem; i++)
2337 TAB_ITEM *item = TAB_GetItem(infoPtr, i);
2339 if (item->rect.top == newselected )
2340 item->rect.top = iTargetRow;
2341 else
2343 if (item->rect.top > newselected)
2344 item->rect.top-=1;
2348 TAB_RecalcHotTrack(hwnd, NULL, NULL, NULL);
2353 * Do the trivial cases first.
2355 if ( (!infoPtr->needsScrolling) ||
2356 (infoPtr->hwndUpDown==0) || (lStyle & TCS_VERTICAL))
2357 return;
2359 if (infoPtr->leftmostVisible >= iSelected)
2361 infoPtr->leftmostVisible = iSelected;
2363 else
2365 TAB_ITEM *selected = TAB_GetItem(infoPtr, iSelected);
2366 RECT r;
2367 INT width;
2368 UINT i;
2370 /* Calculate the part of the client area that is visible */
2371 GetClientRect(hwnd, &r);
2372 width = r.right;
2374 GetClientRect(infoPtr->hwndUpDown, &r);
2375 width -= r.right;
2377 if ((selected->rect.right -
2378 selected->rect.left) >= width )
2380 /* Special case: width of selected item is greater than visible
2381 * part of control.
2383 infoPtr->leftmostVisible = iSelected;
2385 else
2387 for (i = infoPtr->leftmostVisible; i < infoPtr->uNumItem; i++)
2389 if ((selected->rect.right - TAB_GetItem(infoPtr, i)->rect.left) < width)
2390 break;
2392 infoPtr->leftmostVisible = i;
2396 if (infoPtr->leftmostVisible != iOrigLeftmostVisible)
2397 TAB_RecalcHotTrack(hwnd, NULL, NULL, NULL);
2399 SendMessageW(infoPtr->hwndUpDown, UDM_SETPOS, 0,
2400 MAKELONG(infoPtr->leftmostVisible, 0));
2403 /******************************************************************************
2404 * TAB_InvalidateTabArea
2406 * This method will invalidate the portion of the control that contains the
2407 * tabs. It is called when the state of the control changes and needs
2408 * to be redisplayed
2410 static void TAB_InvalidateTabArea(
2411 HWND hwnd,
2412 TAB_INFO* infoPtr)
2414 RECT clientRect, rInvalidate, rAdjClient;
2415 DWORD lStyle = GetWindowLongA(hwnd, GWL_STYLE);
2416 INT lastRow = infoPtr->uNumRows - 1;
2417 RECT rect;
2419 if (lastRow < 0) return;
2421 GetClientRect(hwnd, &clientRect);
2422 rInvalidate = clientRect;
2423 rAdjClient = clientRect;
2425 TAB_AdjustRect(hwnd, 0, &rAdjClient);
2427 TAB_InternalGetItemRect(hwnd, infoPtr, infoPtr->uNumItem-1 , &rect, NULL);
2428 if ((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
2430 rInvalidate.left = rAdjClient.right;
2431 if (infoPtr->uNumRows == 1)
2432 rInvalidate.bottom = clientRect.top + rect.bottom + 2 * SELECTED_TAB_OFFSET;
2434 else if(lStyle & TCS_VERTICAL)
2436 rInvalidate.right = rAdjClient.left;
2437 if (infoPtr->uNumRows == 1)
2438 rInvalidate.bottom = clientRect.top + rect.bottom + 2 * SELECTED_TAB_OFFSET;
2440 else if (lStyle & TCS_BOTTOM)
2442 rInvalidate.top = rAdjClient.bottom;
2443 if (infoPtr->uNumRows == 1)
2444 rInvalidate.right = clientRect.left + rect.right + 2 * SELECTED_TAB_OFFSET;
2446 else
2448 rInvalidate.bottom = rAdjClient.top;
2449 if (infoPtr->uNumRows == 1)
2450 rInvalidate.right = clientRect.left + rect.right + 2 * SELECTED_TAB_OFFSET;
2453 /* Punch out the updown control */
2454 if (infoPtr->needsScrolling && (rInvalidate.right > 0)) {
2455 RECT r;
2456 GetClientRect(infoPtr->hwndUpDown, &r);
2457 if (rInvalidate.right > clientRect.right - r.left)
2458 rInvalidate.right = rInvalidate.right - (r.right - r.left);
2459 else
2460 rInvalidate.right = clientRect.right - r.left;
2463 TRACE("invalidate (%ld,%ld)-(%ld,%ld)\n",
2464 rInvalidate.left, rInvalidate.top,
2465 rInvalidate.right, rInvalidate.bottom);
2467 InvalidateRect(hwnd, &rInvalidate, TRUE);
2470 static LRESULT
2471 TAB_Paint (HWND hwnd, WPARAM wParam)
2473 HDC hdc;
2474 PAINTSTRUCT ps;
2476 if (wParam == 0)
2478 hdc = BeginPaint (hwnd, &ps);
2479 TRACE("erase %d, rect=(%ld,%ld)-(%ld,%ld)\n",
2480 ps.fErase,
2481 ps.rcPaint.left,ps.rcPaint.top,ps.rcPaint.right,ps.rcPaint.bottom);
2482 } else {
2483 hdc = (HDC)wParam;
2486 TAB_Refresh (hwnd, hdc);
2488 if(!wParam)
2489 EndPaint (hwnd, &ps);
2491 return 0;
2494 static LRESULT
2495 TAB_InsertItemAW (HWND hwnd, WPARAM wParam, LPARAM lParam, BOOL bUnicode)
2497 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2498 TAB_ITEM *item;
2499 TCITEMA *pti;
2500 INT iItem;
2501 RECT rect;
2503 GetClientRect (hwnd, &rect);
2504 TRACE("Rect: %p T %li, L %li, B %li, R %li\n", hwnd,
2505 rect.top, rect.left, rect.bottom, rect.right);
2507 pti = (TCITEMA *)lParam;
2508 iItem = (INT)wParam;
2510 if (iItem < 0) return -1;
2511 if (iItem > infoPtr->uNumItem)
2512 iItem = infoPtr->uNumItem;
2514 if (bUnicode)
2515 TAB_DumpItemExternalW((TCITEMW*)pti, iItem);
2516 else
2517 TAB_DumpItemExternalA(pti, iItem);
2520 if (infoPtr->uNumItem == 0) {
2521 infoPtr->items = Alloc (TAB_ITEM_SIZE(infoPtr));
2522 infoPtr->uNumItem++;
2523 infoPtr->iSelected = 0;
2525 else {
2526 LPBYTE oldItems = (LPBYTE)infoPtr->items;
2528 infoPtr->uNumItem++;
2529 infoPtr->items = Alloc (TAB_ITEM_SIZE(infoPtr) * infoPtr->uNumItem);
2531 /* pre insert copy */
2532 if (iItem > 0) {
2533 memcpy (infoPtr->items, oldItems,
2534 iItem * TAB_ITEM_SIZE(infoPtr));
2537 /* post insert copy */
2538 if (iItem < infoPtr->uNumItem - 1) {
2539 memcpy (TAB_GetItem(infoPtr, iItem + 1),
2540 oldItems + iItem * TAB_ITEM_SIZE(infoPtr),
2541 (infoPtr->uNumItem - iItem - 1) * TAB_ITEM_SIZE(infoPtr));
2545 if (iItem <= infoPtr->iSelected)
2546 infoPtr->iSelected++;
2548 Free (oldItems);
2551 item = TAB_GetItem(infoPtr, iItem);
2553 item->mask = pti->mask;
2554 item->pszText = NULL;
2556 if (pti->mask & TCIF_TEXT)
2558 if (bUnicode)
2559 Str_SetPtrW (&item->pszText, (WCHAR*)pti->pszText);
2560 else
2561 Str_SetPtrAtoW (&item->pszText, pti->pszText);
2564 if (pti->mask & TCIF_IMAGE)
2565 item->iImage = pti->iImage;
2566 else
2567 item->iImage = -1;
2569 if (pti->mask & TCIF_PARAM)
2570 memcpy(item->extra, &pti->lParam, infoPtr->cbInfo);
2571 else
2572 memset(item->extra, 0, infoPtr->cbInfo);
2574 TAB_SetItemBounds(hwnd);
2575 if (infoPtr->uNumItem > 1)
2576 TAB_InvalidateTabArea(hwnd, infoPtr);
2577 else
2578 InvalidateRect(hwnd, NULL, TRUE);
2580 TRACE("[%p]: added item %d %s\n",
2581 hwnd, iItem, debugstr_w(item->pszText));
2583 return iItem;
2586 static LRESULT
2587 TAB_SetItemSize (HWND hwnd, WPARAM wParam, LPARAM lParam)
2589 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2590 LONG lStyle = GetWindowLongA(hwnd, GWL_STYLE);
2591 LONG lResult = 0;
2592 BOOL bNeedPaint = FALSE;
2594 lResult = MAKELONG(infoPtr->tabWidth, infoPtr->tabHeight);
2596 /* UNDOCUMENTED: If requested Width or Height is 0 this means that program wants to use auto size. */
2597 if (lStyle & TCS_FIXEDWIDTH && (infoPtr->tabWidth != (INT)LOWORD(lParam)))
2599 infoPtr->tabWidth = max((INT)LOWORD(lParam), infoPtr->tabMinWidth);
2600 bNeedPaint = TRUE;
2603 if (infoPtr->tabHeight != (INT)HIWORD(lParam))
2605 if ((infoPtr->fHeightSet = ((INT)HIWORD(lParam) != 0)))
2606 infoPtr->tabHeight = (INT)HIWORD(lParam);
2608 bNeedPaint = TRUE;
2610 TRACE("was h=%d,w=%d, now h=%d,w=%d\n",
2611 HIWORD(lResult), LOWORD(lResult),
2612 infoPtr->tabHeight, infoPtr->tabWidth);
2614 if (bNeedPaint)
2616 TAB_SetItemBounds(hwnd);
2617 RedrawWindow(hwnd, NULL, NULL, RDW_ERASE | RDW_INVALIDATE | RDW_UPDATENOW);
2620 return lResult;
2623 static LRESULT
2624 TAB_SetMinTabWidth (HWND hwnd, LPARAM lParam)
2626 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2627 INT cx = (INT)lParam;
2628 INT oldcx;
2630 if (infoPtr) {
2631 oldcx = infoPtr->tabMinWidth;
2632 infoPtr->tabMinWidth = (cx==-1)?DEFAULT_TAB_WIDTH:cx;
2633 } else
2634 return 0;
2636 return oldcx;
2639 static LRESULT
2640 TAB_HighlightItem (HWND hwnd, WPARAM wParam, LPARAM lParam)
2642 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2643 INT iItem = (INT)wParam;
2644 BOOL fHighlight = (BOOL)LOWORD(lParam);
2646 if ((infoPtr) && (iItem>=0) && (iItem<infoPtr->uNumItem)) {
2647 if (fHighlight)
2648 TAB_GetItem(infoPtr, iItem)->dwState |= TCIS_HIGHLIGHTED;
2649 else
2650 TAB_GetItem(infoPtr, iItem)->dwState &= ~TCIS_HIGHLIGHTED;
2651 } else
2652 return FALSE;
2654 return TRUE;
2657 static LRESULT
2658 TAB_SetItemAW (HWND hwnd, WPARAM wParam, LPARAM lParam, BOOL bUnicode)
2660 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2661 TCITEMA *tabItem;
2662 TAB_ITEM *wineItem;
2663 INT iItem;
2665 iItem = (INT)wParam;
2666 tabItem = (LPTCITEMA)lParam;
2668 TRACE("%d %p\n", iItem, tabItem);
2669 if ((iItem<0) || (iItem>=infoPtr->uNumItem)) return FALSE;
2671 if (bUnicode)
2672 TAB_DumpItemExternalW((TCITEMW *)tabItem, iItem);
2673 else
2674 TAB_DumpItemExternalA(tabItem, iItem);
2676 wineItem = TAB_GetItem(infoPtr, iItem);
2678 if (tabItem->mask & TCIF_IMAGE)
2679 wineItem->iImage = tabItem->iImage;
2681 if (tabItem->mask & TCIF_PARAM)
2682 memcpy(wineItem->extra, &tabItem->lParam, infoPtr->cbInfo);
2684 if (tabItem->mask & TCIF_RTLREADING)
2685 FIXME("TCIF_RTLREADING\n");
2687 if (tabItem->mask & TCIF_STATE)
2688 wineItem->dwState = tabItem->dwState;
2690 if (tabItem->mask & TCIF_TEXT)
2692 if (wineItem->pszText)
2694 Free(wineItem->pszText);
2695 wineItem->pszText = NULL;
2697 if (bUnicode)
2698 Str_SetPtrW(&wineItem->pszText, (WCHAR*)tabItem->pszText);
2699 else
2700 Str_SetPtrAtoW(&wineItem->pszText, tabItem->pszText);
2703 /* Update and repaint tabs */
2704 TAB_SetItemBounds(hwnd);
2705 TAB_InvalidateTabArea(hwnd,infoPtr);
2707 return TRUE;
2710 static LRESULT
2711 TAB_GetItemCount (HWND hwnd, WPARAM wParam, LPARAM lParam)
2713 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2715 return infoPtr->uNumItem;
2719 static LRESULT
2720 TAB_GetItemAW (HWND hwnd, WPARAM wParam, LPARAM lParam, BOOL bUnicode)
2722 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2723 TCITEMA *tabItem;
2724 TAB_ITEM *wineItem;
2725 INT iItem;
2727 iItem = (INT)wParam;
2728 tabItem = (LPTCITEMA)lParam;
2729 TRACE("\n");
2730 if ((iItem<0) || (iItem>=infoPtr->uNumItem))
2731 return FALSE;
2733 wineItem = TAB_GetItem(infoPtr, iItem);
2735 if (tabItem->mask & TCIF_IMAGE)
2736 tabItem->iImage = wineItem->iImage;
2738 if (tabItem->mask & TCIF_PARAM)
2739 memcpy(&tabItem->lParam, wineItem->extra, infoPtr->cbInfo);
2741 if (tabItem->mask & TCIF_RTLREADING)
2742 FIXME("TCIF_RTLREADING\n");
2744 if (tabItem->mask & TCIF_STATE)
2745 tabItem->dwState = wineItem->dwState;
2747 if (tabItem->mask & TCIF_TEXT)
2749 if (bUnicode)
2750 Str_GetPtrW (wineItem->pszText, (WCHAR*)tabItem->pszText, tabItem->cchTextMax);
2751 else
2752 Str_GetPtrWtoA (wineItem->pszText, tabItem->pszText, tabItem->cchTextMax);
2755 if (bUnicode)
2756 TAB_DumpItemExternalW((TCITEMW*)tabItem, iItem);
2757 else
2758 TAB_DumpItemExternalA(tabItem, iItem);
2760 return TRUE;
2764 static LRESULT
2765 TAB_DeleteItem (HWND hwnd, WPARAM wParam, LPARAM lParam)
2767 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2768 INT iItem = (INT) wParam;
2769 BOOL bResult = FALSE;
2771 if ((iItem >= 0) && (iItem < infoPtr->uNumItem))
2773 TAB_ITEM *item = TAB_GetItem(infoPtr, iItem);
2774 LPBYTE oldItems = (LPBYTE)infoPtr->items;
2776 TAB_InvalidateTabArea(hwnd, infoPtr);
2778 if ((item->mask & TCIF_TEXT) && item->pszText)
2779 Free(item->pszText);
2781 infoPtr->uNumItem--;
2783 if (!infoPtr->uNumItem)
2785 infoPtr->items = NULL;
2786 if (infoPtr->iHotTracked >= 0)
2788 KillTimer(hwnd, TAB_HOTTRACK_TIMER);
2789 infoPtr->iHotTracked = -1;
2792 else
2794 infoPtr->items = Alloc(TAB_ITEM_SIZE(infoPtr) * infoPtr->uNumItem);
2796 if (iItem > 0)
2797 memcpy(infoPtr->items, oldItems, iItem * TAB_ITEM_SIZE(infoPtr));
2799 if (iItem < infoPtr->uNumItem)
2800 memcpy(TAB_GetItem(infoPtr, iItem),
2801 oldItems + (iItem + 1) * TAB_ITEM_SIZE(infoPtr),
2802 (infoPtr->uNumItem - iItem) * TAB_ITEM_SIZE(infoPtr));
2804 if (iItem <= infoPtr->iHotTracked)
2806 /* When tabs move left/up, the hot track item may change */
2807 FIXME("Recalc hot track");
2810 Free(oldItems);
2812 /* Readjust the selected index */
2813 if ((iItem == infoPtr->iSelected) && (iItem > 0))
2814 infoPtr->iSelected--;
2816 if (iItem < infoPtr->iSelected)
2817 infoPtr->iSelected--;
2819 if (infoPtr->uNumItem == 0)
2820 infoPtr->iSelected = -1;
2822 /* Reposition and repaint tabs */
2823 TAB_SetItemBounds(hwnd);
2825 bResult = TRUE;
2828 return bResult;
2831 static LRESULT
2832 TAB_DeleteAllItems (HWND hwnd, WPARAM wParam, LPARAM lParam)
2834 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2836 while (infoPtr->uNumItem)
2837 TAB_DeleteItem (hwnd, 0, 0);
2838 return TRUE;
2842 static LRESULT
2843 TAB_GetFont (HWND hwnd, WPARAM wParam, LPARAM lParam)
2845 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2847 TRACE("\n");
2848 return (LRESULT)infoPtr->hFont;
2851 static LRESULT
2852 TAB_SetFont (HWND hwnd, WPARAM wParam, LPARAM lParam)
2855 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2857 TRACE("%x %lx\n",wParam, lParam);
2859 infoPtr->hFont = (HFONT)wParam;
2861 TAB_SetItemBounds(hwnd);
2863 TAB_InvalidateTabArea(hwnd, infoPtr);
2865 return 0;
2869 static LRESULT
2870 TAB_GetImageList (HWND hwnd, WPARAM wParam, LPARAM lParam)
2872 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2874 TRACE("\n");
2875 return (LRESULT)infoPtr->himl;
2878 static LRESULT
2879 TAB_SetImageList (HWND hwnd, WPARAM wParam, LPARAM lParam)
2881 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2882 HIMAGELIST himlPrev;
2884 TRACE("\n");
2885 himlPrev = infoPtr->himl;
2886 infoPtr->himl= (HIMAGELIST)lParam;
2887 return (LRESULT)himlPrev;
2890 static LRESULT
2891 TAB_GetUnicodeFormat (HWND hwnd)
2893 TAB_INFO *infoPtr = TAB_GetInfoPtr (hwnd);
2894 return infoPtr->bUnicode;
2897 static LRESULT
2898 TAB_SetUnicodeFormat (HWND hwnd, WPARAM wParam)
2900 TAB_INFO *infoPtr = TAB_GetInfoPtr (hwnd);
2901 BOOL bTemp = infoPtr->bUnicode;
2903 infoPtr->bUnicode = (BOOL)wParam;
2905 return bTemp;
2908 static LRESULT
2909 TAB_Size (HWND hwnd, WPARAM wParam, LPARAM lParam)
2912 /* I'm not really sure what the following code was meant to do.
2913 This is what it is doing:
2914 When WM_SIZE is sent with SIZE_RESTORED, the control
2915 gets positioned in the top left corner.
2917 RECT parent_rect;
2918 HWND parent;
2919 UINT uPosFlags,cx,cy;
2921 uPosFlags=0;
2922 if (!wParam) {
2923 parent = GetParent (hwnd);
2924 GetClientRect(parent, &parent_rect);
2925 cx=LOWORD (lParam);
2926 cy=HIWORD (lParam);
2927 if (GetWindowLongA(hwnd, GWL_STYLE) & CCS_NORESIZE)
2928 uPosFlags |= (SWP_NOSIZE | SWP_NOMOVE);
2930 SetWindowPos (hwnd, 0, parent_rect.left, parent_rect.top,
2931 cx, cy, uPosFlags | SWP_NOZORDER);
2932 } else {
2933 FIXME("WM_SIZE flag %x %lx not handled\n", wParam, lParam);
2934 } */
2936 /* Recompute the size/position of the tabs. */
2937 TAB_SetItemBounds (hwnd);
2939 /* Force a repaint of the control. */
2940 InvalidateRect(hwnd, NULL, TRUE);
2942 return 0;
2946 static LRESULT
2947 TAB_Create (HWND hwnd, WPARAM wParam, LPARAM lParam)
2949 TAB_INFO *infoPtr;
2950 TEXTMETRICA fontMetrics;
2951 HDC hdc;
2952 HFONT hOldFont;
2953 DWORD dwStyle;
2955 infoPtr = (TAB_INFO *)Alloc (sizeof(TAB_INFO));
2957 SetWindowLongA(hwnd, 0, (DWORD)infoPtr);
2959 infoPtr->hwndNotify = ((LPCREATESTRUCTW)lParam)->hwndParent;
2960 infoPtr->uNumItem = 0;
2961 infoPtr->uNumRows = 0;
2962 infoPtr->uHItemPadding = 6;
2963 infoPtr->uVItemPadding = 3;
2964 infoPtr->uHItemPadding_s = 6;
2965 infoPtr->uVItemPadding_s = 3;
2966 infoPtr->hFont = 0;
2967 infoPtr->items = 0;
2968 infoPtr->hcurArrow = LoadCursorA (0, (LPSTR)IDC_ARROW);
2969 infoPtr->iSelected = -1;
2970 infoPtr->iHotTracked = -1;
2971 infoPtr->uFocus = -1;
2972 infoPtr->hwndToolTip = 0;
2973 infoPtr->DoRedraw = TRUE;
2974 infoPtr->needsScrolling = FALSE;
2975 infoPtr->hwndUpDown = 0;
2976 infoPtr->leftmostVisible = 0;
2977 infoPtr->fHeightSet = FALSE;
2978 infoPtr->bUnicode = IsWindowUnicode (hwnd);
2979 infoPtr->cbInfo = sizeof(LPARAM);
2981 TRACE("Created tab control, hwnd [%p]\n", hwnd);
2983 /* The tab control always has the WS_CLIPSIBLINGS style. Even
2984 if you don't specify it in CreateWindow. This is necessary in
2985 order for paint to work correctly. This follows windows behaviour. */
2986 dwStyle = GetWindowLongA(hwnd, GWL_STYLE);
2987 SetWindowLongA(hwnd, GWL_STYLE, dwStyle|WS_CLIPSIBLINGS);
2989 if (dwStyle & TCS_TOOLTIPS) {
2990 /* Create tooltip control */
2991 infoPtr->hwndToolTip =
2992 CreateWindowExA (0, TOOLTIPS_CLASSA, NULL, 0,
2993 CW_USEDEFAULT, CW_USEDEFAULT,
2994 CW_USEDEFAULT, CW_USEDEFAULT,
2995 hwnd, 0, 0, 0);
2997 /* Send NM_TOOLTIPSCREATED notification */
2998 if (infoPtr->hwndToolTip) {
2999 NMTOOLTIPSCREATED nmttc;
3001 nmttc.hdr.hwndFrom = hwnd;
3002 nmttc.hdr.idFrom = GetWindowLongPtrW(hwnd, GWLP_ID);
3003 nmttc.hdr.code = NM_TOOLTIPSCREATED;
3004 nmttc.hwndToolTips = infoPtr->hwndToolTip;
3006 SendMessageW (infoPtr->hwndNotify, WM_NOTIFY,
3007 (WPARAM)GetWindowLongPtrW(hwnd, GWLP_ID), (LPARAM)&nmttc);
3012 * We need to get text information so we need a DC and we need to select
3013 * a font.
3015 hdc = GetDC(hwnd);
3016 hOldFont = SelectObject (hdc, GetStockObject (SYSTEM_FONT));
3018 /* Use the system font to determine the initial height of a tab. */
3019 GetTextMetricsA(hdc, &fontMetrics);
3022 * Make sure there is enough space for the letters + growing the
3023 * selected item + extra space for the selected item.
3025 infoPtr->tabHeight = fontMetrics.tmHeight + SELECTED_TAB_OFFSET +
3026 ((dwStyle & TCS_BUTTONS) ? 2 : 1) *
3027 infoPtr->uVItemPadding;
3029 /* Initialize the width of a tab. */
3030 infoPtr->tabWidth = DEFAULT_TAB_WIDTH;
3031 infoPtr->tabMinWidth = 0;
3033 TRACE("tabH=%d, tabW=%d\n", infoPtr->tabHeight, infoPtr->tabWidth);
3035 SelectObject (hdc, hOldFont);
3036 ReleaseDC(hwnd, hdc);
3038 return 0;
3041 static LRESULT
3042 TAB_Destroy (HWND hwnd, WPARAM wParam, LPARAM lParam)
3044 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
3045 UINT iItem;
3047 if (!infoPtr)
3048 return 0;
3050 if (infoPtr->items) {
3051 for (iItem = 0; iItem < infoPtr->uNumItem; iItem++) {
3052 if (TAB_GetItem(infoPtr, iItem)->pszText)
3053 Free (TAB_GetItem(infoPtr, iItem)->pszText);
3055 Free (infoPtr->items);
3058 if (infoPtr->hwndToolTip)
3059 DestroyWindow (infoPtr->hwndToolTip);
3061 if (infoPtr->hwndUpDown)
3062 DestroyWindow(infoPtr->hwndUpDown);
3064 if (infoPtr->iHotTracked >= 0)
3065 KillTimer(hwnd, TAB_HOTTRACK_TIMER);
3067 Free (infoPtr);
3068 SetWindowLongA(hwnd, 0, 0);
3069 return 0;
3072 static LRESULT
3073 TAB_SetItemExtra (HWND hwnd, WPARAM wParam, LPARAM lParam)
3075 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
3076 INT cbInfo = wParam;
3078 if (!infoPtr || cbInfo <= 0)
3079 return FALSE;
3081 if (infoPtr->uNumItem)
3083 /* FIXME: MSDN says this is not allowed, but this hasn't been verified */
3084 return FALSE;
3087 infoPtr->cbInfo = cbInfo;
3088 return TRUE;
3091 static LRESULT WINAPI
3092 TAB_WindowProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
3094 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
3096 TRACE("hwnd=%p msg=%x wParam=%x lParam=%lx\n", hwnd, uMsg, wParam, lParam);
3097 if (!TAB_GetInfoPtr(hwnd) && (uMsg != WM_CREATE))
3098 return DefWindowProcW (hwnd, uMsg, wParam, lParam);
3100 switch (uMsg)
3102 case TCM_GETIMAGELIST:
3103 return TAB_GetImageList (hwnd, wParam, lParam);
3105 case TCM_SETIMAGELIST:
3106 return TAB_SetImageList (hwnd, wParam, lParam);
3108 case TCM_GETITEMCOUNT:
3109 return TAB_GetItemCount (hwnd, wParam, lParam);
3111 case TCM_GETITEMA:
3112 case TCM_GETITEMW:
3113 return TAB_GetItemAW (hwnd, wParam, lParam, uMsg == TCM_GETITEMW);
3115 case TCM_SETITEMA:
3116 case TCM_SETITEMW:
3117 return TAB_SetItemAW (hwnd, wParam, lParam, uMsg == TCM_SETITEMW);
3119 case TCM_DELETEITEM:
3120 return TAB_DeleteItem (hwnd, wParam, lParam);
3122 case TCM_DELETEALLITEMS:
3123 return TAB_DeleteAllItems (hwnd, wParam, lParam);
3125 case TCM_GETITEMRECT:
3126 return TAB_GetItemRect (hwnd, wParam, lParam);
3128 case TCM_GETCURSEL:
3129 return TAB_GetCurSel (hwnd);
3131 case TCM_HITTEST:
3132 return TAB_HitTest (hwnd, wParam, lParam);
3134 case TCM_SETCURSEL:
3135 return TAB_SetCurSel (hwnd, wParam);
3137 case TCM_INSERTITEMA:
3138 case TCM_INSERTITEMW:
3139 return TAB_InsertItemAW (hwnd, wParam, lParam, uMsg == TCM_INSERTITEMW);
3141 case TCM_SETITEMEXTRA:
3142 return TAB_SetItemExtra (hwnd, wParam, lParam);
3144 case TCM_ADJUSTRECT:
3145 return TAB_AdjustRect (hwnd, (BOOL)wParam, (LPRECT)lParam);
3147 case TCM_SETITEMSIZE:
3148 return TAB_SetItemSize (hwnd, wParam, lParam);
3150 case TCM_REMOVEIMAGE:
3151 FIXME("Unimplemented msg TCM_REMOVEIMAGE\n");
3152 return 0;
3154 case TCM_SETPADDING:
3155 return TAB_SetPadding (hwnd, wParam, lParam);
3157 case TCM_GETROWCOUNT:
3158 return TAB_GetRowCount(hwnd);
3160 case TCM_GETUNICODEFORMAT:
3161 return TAB_GetUnicodeFormat (hwnd);
3163 case TCM_SETUNICODEFORMAT:
3164 return TAB_SetUnicodeFormat (hwnd, wParam);
3166 case TCM_HIGHLIGHTITEM:
3167 return TAB_HighlightItem (hwnd, wParam, lParam);
3169 case TCM_GETTOOLTIPS:
3170 return TAB_GetToolTips (hwnd, wParam, lParam);
3172 case TCM_SETTOOLTIPS:
3173 return TAB_SetToolTips (hwnd, wParam, lParam);
3175 case TCM_GETCURFOCUS:
3176 return TAB_GetCurFocus (hwnd);
3178 case TCM_SETCURFOCUS:
3179 return TAB_SetCurFocus (hwnd, wParam);
3181 case TCM_SETMINTABWIDTH:
3182 return TAB_SetMinTabWidth(hwnd, lParam);
3184 case TCM_DESELECTALL:
3185 FIXME("Unimplemented msg TCM_DESELECTALL\n");
3186 return 0;
3188 case TCM_GETEXTENDEDSTYLE:
3189 FIXME("Unimplemented msg TCM_GETEXTENDEDSTYLE\n");
3190 return 0;
3192 case TCM_SETEXTENDEDSTYLE:
3193 FIXME("Unimplemented msg TCM_SETEXTENDEDSTYLE\n");
3194 return 0;
3196 case WM_GETFONT:
3197 return TAB_GetFont (hwnd, wParam, lParam);
3199 case WM_SETFONT:
3200 return TAB_SetFont (hwnd, wParam, lParam);
3202 case WM_CREATE:
3203 return TAB_Create (hwnd, wParam, lParam);
3205 case WM_NCDESTROY:
3206 return TAB_Destroy (hwnd, wParam, lParam);
3208 case WM_GETDLGCODE:
3209 return DLGC_WANTARROWS | DLGC_WANTCHARS;
3211 case WM_LBUTTONDOWN:
3212 return TAB_LButtonDown (hwnd, wParam, lParam);
3214 case WM_LBUTTONUP:
3215 return TAB_LButtonUp (hwnd, wParam, lParam);
3217 case WM_NOTIFY:
3218 return SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, wParam, lParam);
3220 case WM_RBUTTONDOWN:
3221 return TAB_RButtonDown (hwnd, wParam, lParam);
3223 case WM_MOUSEMOVE:
3224 return TAB_MouseMove (hwnd, wParam, lParam);
3226 case WM_PAINT:
3227 return TAB_Paint (hwnd, wParam);
3229 case WM_SIZE:
3230 return TAB_Size (hwnd, wParam, lParam);
3232 case WM_SETREDRAW:
3233 return TAB_SetRedraw (hwnd, wParam);
3235 case WM_HSCROLL:
3236 return TAB_OnHScroll(hwnd, (int)LOWORD(wParam), (int)HIWORD(wParam), (HWND)lParam);
3238 case WM_STYLECHANGED:
3239 TAB_SetItemBounds (hwnd);
3240 InvalidateRect(hwnd, NULL, TRUE);
3241 return 0;
3243 case WM_SYSCOLORCHANGE:
3244 COMCTL32_RefreshSysColors();
3245 return 0;
3247 case WM_KILLFOCUS:
3248 case WM_SETFOCUS:
3249 return TAB_FocusChanging(hwnd, uMsg, wParam, lParam);
3251 case WM_KEYUP:
3252 return TAB_KeyUp(hwnd, wParam);
3253 case WM_NCHITTEST:
3254 return TAB_NCHitTest(hwnd, lParam);
3256 default:
3257 if ((uMsg >= WM_USER) && (uMsg < WM_APP))
3258 WARN("unknown msg %04x wp=%08x lp=%08lx\n",
3259 uMsg, wParam, lParam);
3260 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
3263 return 0;
3267 VOID
3268 TAB_Register (void)
3270 WNDCLASSW wndClass;
3272 ZeroMemory (&wndClass, sizeof(WNDCLASSW));
3273 wndClass.style = CS_GLOBALCLASS | CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
3274 wndClass.lpfnWndProc = TAB_WindowProc;
3275 wndClass.cbClsExtra = 0;
3276 wndClass.cbWndExtra = sizeof(TAB_INFO *);
3277 wndClass.hCursor = LoadCursorW (0, (LPWSTR)IDC_ARROW);
3278 wndClass.hbrBackground = (HBRUSH)(COLOR_BTNFACE+1);
3279 wndClass.lpszClassName = WC_TABCONTROLW;
3281 RegisterClassW (&wndClass);
3285 VOID
3286 TAB_Unregister (void)
3288 UnregisterClassW (WC_TABCONTROLW, NULL);