Cleanup reference counting.
[wine/testsucceed.git] / dlls / comctl32 / tab.c
blobfd0e2c9d48c287b179a5a810a2850bacd5aa9693
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))
117 /******************************************************************************
118 * Hot-tracking timer constants
120 #define TAB_HOTTRACK_TIMER 1
121 #define TAB_HOTTRACK_TIMER_INTERVAL 100 /* milliseconds */
123 /******************************************************************************
124 * Prototypes
126 static void TAB_Refresh (HWND hwnd, HDC hdc);
127 static void TAB_InvalidateTabArea(HWND hwnd, TAB_INFO* infoPtr);
128 static void TAB_EnsureSelectionVisible(HWND hwnd, TAB_INFO* infoPtr);
129 static void TAB_DrawItem(HWND hwnd, HDC hdc, INT iItem);
130 static void TAB_DrawItemInterior(HWND hwnd, HDC hdc, INT iItem, RECT* drawRect);
132 static BOOL
133 TAB_SendSimpleNotify (HWND hwnd, UINT code)
135 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
136 NMHDR nmhdr;
138 nmhdr.hwndFrom = hwnd;
139 nmhdr.idFrom = GetWindowLongPtrW(hwnd, GWLP_ID);
140 nmhdr.code = code;
142 return (BOOL) SendMessageW (infoPtr->hwndNotify, WM_NOTIFY,
143 (WPARAM) nmhdr.idFrom, (LPARAM) &nmhdr);
146 static VOID
147 TAB_RelayEvent (HWND hwndTip, HWND hwndMsg, UINT uMsg,
148 WPARAM wParam, LPARAM lParam)
150 MSG msg;
152 msg.hwnd = hwndMsg;
153 msg.message = uMsg;
154 msg.wParam = wParam;
155 msg.lParam = lParam;
156 msg.time = GetMessageTime ();
157 msg.pt.x = LOWORD(GetMessagePos ());
158 msg.pt.y = HIWORD(GetMessagePos ());
160 SendMessageW (hwndTip, TTM_RELAYEVENT, 0, (LPARAM)&msg);
163 static void
164 TAB_DumpItemExternalA(TCITEMA *pti, UINT iItem)
166 if (TRACE_ON(tab)) {
167 TRACE("external tab %d, mask=0x%08x, dwState=0x%08x, dwStateMask=0x%08x, cchTextMax=0x%08x\n",
168 iItem, pti->mask, pti->dwState, pti->dwStateMask, pti->cchTextMax);
169 TRACE("external tab %d, iImage=%d, lParam=0x%08lx, pszTextA=%s\n",
170 iItem, pti->iImage, pti->lParam, debugstr_a(pti->pszText));
175 static void
176 TAB_DumpItemExternalW(TCITEMW *pti, UINT iItem)
178 if (TRACE_ON(tab)) {
179 TRACE("external tab %d, mask=0x%08x, dwState=0x%08lx, dwStateMask=0x%08lx, cchTextMax=0x%08x\n",
180 iItem, pti->mask, pti->dwState, pti->dwStateMask, pti->cchTextMax);
181 TRACE("external tab %d, iImage=%d, lParam=0x%08lx, pszTextW=%s\n",
182 iItem, pti->iImage, pti->lParam, debugstr_w(pti->pszText));
186 static void
187 TAB_DumpItemInternal(TAB_INFO *infoPtr, UINT iItem)
189 if (TRACE_ON(tab)) {
190 TAB_ITEM *ti;
192 ti = &infoPtr->items[iItem];
193 TRACE("tab %d, mask=0x%08x, dwState=0x%08lx, pszText=%s, iImage=%d\n",
194 iItem, ti->mask, ti->dwState, debugstr_w(ti->pszText),
195 ti->iImage);
196 TRACE("tab %d, rect.left=%ld, rect.top(row)=%ld\n",
197 iItem, ti->rect.left, ti->rect.top);
201 static LRESULT
202 TAB_GetCurSel (HWND hwnd)
204 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
206 return infoPtr->iSelected;
209 static LRESULT
210 TAB_GetCurFocus (HWND hwnd)
212 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
214 return infoPtr->uFocus;
217 static LRESULT
218 TAB_GetToolTips (HWND hwnd, WPARAM wParam, LPARAM lParam)
220 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
222 if (infoPtr == NULL) return 0;
223 return (LRESULT)infoPtr->hwndToolTip;
226 static LRESULT
227 TAB_SetCurSel (HWND hwnd,WPARAM wParam)
229 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
230 INT iItem = (INT)wParam;
231 INT prevItem;
233 prevItem = -1;
234 if ((iItem >= 0) && (iItem < infoPtr->uNumItem)) {
235 prevItem=infoPtr->iSelected;
236 infoPtr->iSelected=iItem;
237 TAB_EnsureSelectionVisible(hwnd, infoPtr);
238 TAB_InvalidateTabArea(hwnd, infoPtr);
240 return prevItem;
243 static LRESULT
244 TAB_SetCurFocus (HWND hwnd,WPARAM wParam)
246 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
247 INT iItem=(INT) wParam;
249 if ((iItem < 0) || (iItem >= infoPtr->uNumItem)) return 0;
251 if (GetWindowLongA(hwnd, GWL_STYLE) & TCS_BUTTONS) {
252 FIXME("Should set input focus\n");
253 } else {
254 int oldFocus = infoPtr->uFocus;
255 if (infoPtr->iSelected != iItem || infoPtr->uFocus == -1 ) {
256 infoPtr->uFocus = iItem;
257 if (oldFocus != -1) {
258 if (TAB_SendSimpleNotify(hwnd, TCN_SELCHANGING)!=TRUE) {
259 infoPtr->iSelected = iItem;
260 TAB_SendSimpleNotify(hwnd, TCN_SELCHANGE);
262 else
263 infoPtr->iSelected = iItem;
264 TAB_EnsureSelectionVisible(hwnd, infoPtr);
265 TAB_InvalidateTabArea(hwnd, infoPtr);
269 return 0;
272 static LRESULT
273 TAB_SetToolTips (HWND hwnd, WPARAM wParam, LPARAM lParam)
275 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
277 if (infoPtr == NULL) return 0;
278 infoPtr->hwndToolTip = (HWND)wParam;
279 return 0;
282 static LRESULT
283 TAB_SetPadding (HWND hwnd, WPARAM wParam, LPARAM lParam)
285 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
287 if (infoPtr == NULL) return 0;
288 infoPtr->uHItemPadding_s=LOWORD(lParam);
289 infoPtr->uVItemPadding_s=HIWORD(lParam);
290 return 0;
293 /******************************************************************************
294 * TAB_InternalGetItemRect
296 * This method will calculate the rectangle representing a given tab item in
297 * client coordinates. This method takes scrolling into account.
299 * This method returns TRUE if the item is visible in the window and FALSE
300 * if it is completely outside the client area.
302 static BOOL TAB_InternalGetItemRect(
303 HWND hwnd,
304 TAB_INFO* infoPtr,
305 INT itemIndex,
306 RECT* itemRect,
307 RECT* selectedRect)
309 RECT tmpItemRect,clientRect;
310 LONG lStyle = GetWindowLongA(hwnd, GWL_STYLE);
312 /* Perform a sanity check and a trivial visibility check. */
313 if ( (infoPtr->uNumItem <= 0) ||
314 (itemIndex >= infoPtr->uNumItem) ||
315 (!((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)) && (itemIndex < infoPtr->leftmostVisible)) )
316 return FALSE;
319 * Avoid special cases in this procedure by assigning the "out"
320 * parameters if the caller didn't supply them
322 if (itemRect == NULL)
323 itemRect = &tmpItemRect;
325 /* Retrieve the unmodified item rect. */
326 *itemRect = infoPtr->items[itemIndex].rect;
328 /* calculate the times bottom and top based on the row */
329 GetClientRect(hwnd, &clientRect);
331 if ((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
333 itemRect->right = clientRect.right - SELECTED_TAB_OFFSET - itemRect->left * infoPtr->tabHeight -
334 ((lStyle & TCS_BUTTONS) ? itemRect->left * BUTTON_SPACINGX : 0);
335 itemRect->left = itemRect->right - infoPtr->tabHeight;
337 else if (lStyle & TCS_VERTICAL)
339 itemRect->left = clientRect.left + SELECTED_TAB_OFFSET + itemRect->left * infoPtr->tabHeight +
340 ((lStyle & TCS_BUTTONS) ? itemRect->left * BUTTON_SPACINGX : 0);
341 itemRect->right = itemRect->left + infoPtr->tabHeight;
343 else if (lStyle & TCS_BOTTOM)
345 itemRect->bottom = clientRect.bottom - itemRect->top * infoPtr->tabHeight -
346 ((lStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : SELECTED_TAB_OFFSET);
347 itemRect->top = itemRect->bottom - infoPtr->tabHeight;
349 else /* not TCS_BOTTOM and not TCS_VERTICAL */
351 itemRect->top = clientRect.top + itemRect->top * infoPtr->tabHeight +
352 ((lStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : SELECTED_TAB_OFFSET);
353 itemRect->bottom = itemRect->top + infoPtr->tabHeight;
357 * "scroll" it to make sure the item at the very left of the
358 * tab control is the leftmost visible tab.
360 if(lStyle & TCS_VERTICAL)
362 OffsetRect(itemRect,
364 -infoPtr->items[infoPtr->leftmostVisible].rect.top);
367 * Move the rectangle so the first item is slightly offset from
368 * the bottom of the tab control.
370 OffsetRect(itemRect,
372 SELECTED_TAB_OFFSET);
374 } else
376 OffsetRect(itemRect,
377 -infoPtr->items[infoPtr->leftmostVisible].rect.left,
381 * Move the rectangle so the first item is slightly offset from
382 * the left of the tab control.
384 OffsetRect(itemRect,
385 SELECTED_TAB_OFFSET,
388 TRACE("item %d tab h=%d, rect=(%ld,%ld)-(%ld,%ld)\n",
389 itemIndex, infoPtr->tabHeight,
390 itemRect->left, itemRect->top, itemRect->right, itemRect->bottom);
392 /* Now, calculate the position of the item as if it were selected. */
393 if (selectedRect!=NULL)
395 CopyRect(selectedRect, itemRect);
397 /* The rectangle of a selected item is a bit wider. */
398 if(lStyle & TCS_VERTICAL)
399 InflateRect(selectedRect, 0, SELECTED_TAB_OFFSET);
400 else
401 InflateRect(selectedRect, SELECTED_TAB_OFFSET, 0);
403 /* If it also a bit higher. */
404 if ((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
406 selectedRect->left -= 2; /* the border is thicker on the right */
407 selectedRect->right += SELECTED_TAB_OFFSET;
409 else if (lStyle & TCS_VERTICAL)
411 selectedRect->left -= SELECTED_TAB_OFFSET;
412 selectedRect->right += 1;
414 else if (lStyle & TCS_BOTTOM)
416 selectedRect->bottom += SELECTED_TAB_OFFSET;
418 else /* not TCS_BOTTOM and not TCS_VERTICAL */
420 selectedRect->top -= SELECTED_TAB_OFFSET;
421 selectedRect->bottom -= 1;
425 return TRUE;
428 static BOOL TAB_GetItemRect(HWND hwnd, WPARAM wParam, LPARAM lParam)
430 return TAB_InternalGetItemRect(hwnd, TAB_GetInfoPtr(hwnd), (INT)wParam,
431 (LPRECT)lParam, (LPRECT)NULL);
434 /******************************************************************************
435 * TAB_KeyUp
437 * This method is called to handle keyboard input
439 static LRESULT TAB_KeyUp(
440 HWND hwnd,
441 WPARAM keyCode)
443 TAB_INFO* infoPtr = TAB_GetInfoPtr(hwnd);
444 int newItem = -1;
446 switch (keyCode)
448 case VK_LEFT:
449 newItem = infoPtr->uFocus - 1;
450 break;
451 case VK_RIGHT:
452 newItem = infoPtr->uFocus + 1;
453 break;
457 * If we changed to a valid item, change the selection
459 if ((newItem >= 0) &&
460 (newItem < infoPtr->uNumItem) &&
461 (infoPtr->uFocus != newItem))
463 if (!TAB_SendSimpleNotify(hwnd, TCN_SELCHANGING))
465 infoPtr->iSelected = newItem;
466 infoPtr->uFocus = newItem;
467 TAB_SendSimpleNotify(hwnd, TCN_SELCHANGE);
469 TAB_EnsureSelectionVisible(hwnd, infoPtr);
470 TAB_InvalidateTabArea(hwnd, infoPtr);
474 return 0;
477 /******************************************************************************
478 * TAB_FocusChanging
480 * This method is called whenever the focus goes in or out of this control
481 * it is used to update the visual state of the control.
483 static LRESULT TAB_FocusChanging(
484 HWND hwnd,
485 UINT uMsg,
486 WPARAM wParam,
487 LPARAM lParam)
489 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
490 RECT selectedRect;
491 BOOL isVisible;
494 * Get the rectangle for the item.
496 isVisible = TAB_InternalGetItemRect(hwnd,
497 infoPtr,
498 infoPtr->uFocus,
499 NULL,
500 &selectedRect);
503 * If the rectangle is not completely invisible, invalidate that
504 * portion of the window.
506 if (isVisible)
508 TRACE("invalidate (%ld,%ld)-(%ld,%ld)\n",
509 selectedRect.left,selectedRect.top,
510 selectedRect.right,selectedRect.bottom);
511 InvalidateRect(hwnd, &selectedRect, TRUE);
515 * Don't otherwise disturb normal behavior.
517 return DefWindowProcW (hwnd, uMsg, wParam, lParam);
520 static INT TAB_InternalHitTest (
521 HWND hwnd,
522 TAB_INFO* infoPtr,
523 POINT pt,
524 UINT* flags)
527 RECT rect;
528 INT iCount;
530 for (iCount = 0; iCount < infoPtr->uNumItem; iCount++)
532 TAB_InternalGetItemRect(hwnd, infoPtr, iCount, &rect, NULL);
534 if (PtInRect(&rect, pt))
536 *flags = TCHT_ONITEM;
537 return iCount;
541 *flags = TCHT_NOWHERE;
542 return -1;
545 static LRESULT
546 TAB_HitTest (HWND hwnd, WPARAM wParam, LPARAM lParam)
548 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
549 LPTCHITTESTINFO lptest = (LPTCHITTESTINFO) lParam;
551 return TAB_InternalHitTest (hwnd, infoPtr, lptest->pt, &lptest->flags);
554 /******************************************************************************
555 * TAB_NCHitTest
557 * Napster v2b5 has a tab control for its main navigation which has a client
558 * area that covers the whole area of the dialog pages.
559 * That's why it receives all msgs for that area and the underlying dialog ctrls
560 * are dead.
561 * So I decided that we should handle WM_NCHITTEST here and return
562 * HTTRANSPARENT if we don't hit the tab control buttons.
563 * FIXME: WM_NCHITTEST handling correct ? Fix it if you know that Windows
564 * doesn't do it that way. Maybe depends on tab control styles ?
566 static LRESULT
567 TAB_NCHitTest (HWND hwnd, LPARAM lParam)
569 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
570 POINT pt;
571 UINT dummyflag;
573 pt.x = LOWORD(lParam);
574 pt.y = HIWORD(lParam);
575 ScreenToClient(hwnd, &pt);
577 if (TAB_InternalHitTest(hwnd, infoPtr, pt, &dummyflag) == -1)
578 return HTTRANSPARENT;
579 else
580 return HTCLIENT;
583 static LRESULT
584 TAB_LButtonDown (HWND hwnd, WPARAM wParam, LPARAM lParam)
586 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
587 POINT pt;
588 INT newItem, dummy;
590 if (infoPtr->hwndToolTip)
591 TAB_RelayEvent (infoPtr->hwndToolTip, hwnd,
592 WM_LBUTTONDOWN, wParam, lParam);
594 if (GetWindowLongA(hwnd, GWL_STYLE) & TCS_FOCUSONBUTTONDOWN ) {
595 SetFocus (hwnd);
598 if (infoPtr->hwndToolTip)
599 TAB_RelayEvent (infoPtr->hwndToolTip, hwnd,
600 WM_LBUTTONDOWN, wParam, lParam);
602 pt.x = (INT)LOWORD(lParam);
603 pt.y = (INT)HIWORD(lParam);
605 newItem = TAB_InternalHitTest (hwnd, infoPtr, pt, &dummy);
607 TRACE("On Tab, item %d\n", newItem);
609 if ((newItem != -1) && (infoPtr->iSelected != newItem))
611 if (TAB_SendSimpleNotify(hwnd, TCN_SELCHANGING) != TRUE)
613 infoPtr->iSelected = newItem;
614 infoPtr->uFocus = newItem;
615 TAB_SendSimpleNotify(hwnd, TCN_SELCHANGE);
617 TAB_EnsureSelectionVisible(hwnd, infoPtr);
619 TAB_InvalidateTabArea(hwnd, infoPtr);
622 return 0;
625 static LRESULT
626 TAB_LButtonUp (HWND hwnd, WPARAM wParam, LPARAM lParam)
628 TAB_SendSimpleNotify(hwnd, NM_CLICK);
630 return 0;
633 static LRESULT
634 TAB_RButtonDown (HWND hwnd, WPARAM wParam, LPARAM lParam)
636 TAB_SendSimpleNotify(hwnd, NM_RCLICK);
637 return 0;
640 /******************************************************************************
641 * TAB_DrawLoneItemInterior
643 * This calls TAB_DrawItemInterior. However, TAB_DrawItemInterior is normally
644 * called by TAB_DrawItem which is normally called by TAB_Refresh which sets
645 * up the device context and font. This routine does the same setup but
646 * only calls TAB_DrawItemInterior for the single specified item.
648 static void
649 TAB_DrawLoneItemInterior(HWND hwnd, TAB_INFO* infoPtr, int iItem)
651 HDC hdc = GetDC(hwnd);
652 RECT r, rC;
654 /* Clip UpDown control to not draw over it */
655 if (infoPtr->needsScrolling)
657 GetWindowRect(hwnd, &rC);
658 GetWindowRect(infoPtr->hwndUpDown, &r);
659 ExcludeClipRect(hdc, r.left - rC.left, r.top - rC.top, r.right - rC.left, r.bottom - rC.top);
661 TAB_DrawItemInterior(hwnd, hdc, iItem, NULL);
662 ReleaseDC(hwnd, hdc);
665 /******************************************************************************
666 * TAB_HotTrackTimerProc
668 * When a mouse-move event causes a tab to be highlighted (hot-tracking), a
669 * timer is setup so we can check if the mouse is moved out of our window.
670 * (We don't get an event when the mouse leaves, the mouse-move events just
671 * stop being delivered to our window and just start being delivered to
672 * another window.) This function is called when the timer triggers so
673 * we can check if the mouse has left our window. If so, we un-highlight
674 * the hot-tracked tab.
676 static VOID CALLBACK
677 TAB_HotTrackTimerProc
679 HWND hwnd, /* handle of window for timer messages */
680 UINT uMsg, /* WM_TIMER message */
681 UINT idEvent, /* timer identifier */
682 DWORD dwTime /* current system time */
685 TAB_INFO* infoPtr = TAB_GetInfoPtr(hwnd);
687 if (infoPtr != NULL && infoPtr->iHotTracked >= 0)
689 POINT pt;
692 ** If we can't get the cursor position, or if the cursor is outside our
693 ** window, we un-highlight the hot-tracked tab. Note that the cursor is
694 ** "outside" even if it is within our bounding rect if another window
695 ** overlaps. Note also that the case where the cursor stayed within our
696 ** window but has moved off the hot-tracked tab will be handled by the
697 ** WM_MOUSEMOVE event.
699 if (!GetCursorPos(&pt) || WindowFromPoint(pt) != hwnd)
701 /* Redraw iHotTracked to look normal */
702 INT iRedraw = infoPtr->iHotTracked;
703 infoPtr->iHotTracked = -1;
704 TAB_DrawLoneItemInterior(hwnd, infoPtr, iRedraw);
706 /* Kill this timer */
707 KillTimer(hwnd, TAB_HOTTRACK_TIMER);
712 /******************************************************************************
713 * TAB_RecalcHotTrack
715 * If a tab control has the TCS_HOTTRACK style, then the tab under the mouse
716 * should be highlighted. This function determines which tab in a tab control,
717 * if any, is under the mouse and records that information. The caller may
718 * supply output parameters to receive the item number of the tab item which
719 * was highlighted but isn't any longer and of the tab item which is now
720 * highlighted but wasn't previously. The caller can use this information to
721 * selectively redraw those tab items.
723 * If the caller has a mouse position, it can supply it through the pos
724 * parameter. For example, TAB_MouseMove does this. Otherwise, the caller
725 * supplies NULL and this function determines the current mouse position
726 * itself.
728 static void
729 TAB_RecalcHotTrack
731 HWND hwnd,
732 const LPARAM* pos,
733 int* out_redrawLeave,
734 int* out_redrawEnter
737 TAB_INFO* infoPtr = TAB_GetInfoPtr(hwnd);
739 int item = -1;
742 if (out_redrawLeave != NULL)
743 *out_redrawLeave = -1;
744 if (out_redrawEnter != NULL)
745 *out_redrawEnter = -1;
747 if (GetWindowLongA(hwnd, GWL_STYLE) & TCS_HOTTRACK)
749 POINT pt;
750 UINT flags;
752 if (pos == NULL)
754 GetCursorPos(&pt);
755 ScreenToClient(hwnd, &pt);
757 else
759 pt.x = LOWORD(*pos);
760 pt.y = HIWORD(*pos);
763 item = TAB_InternalHitTest(hwnd, infoPtr, pt, &flags);
766 if (item != infoPtr->iHotTracked)
768 if (infoPtr->iHotTracked >= 0)
770 /* Mark currently hot-tracked to be redrawn to look normal */
771 if (out_redrawLeave != NULL)
772 *out_redrawLeave = infoPtr->iHotTracked;
774 if (item < 0)
776 /* Kill timer which forces recheck of mouse pos */
777 KillTimer(hwnd, TAB_HOTTRACK_TIMER);
780 else
782 /* Start timer so we recheck mouse pos */
783 UINT timerID = SetTimer
785 hwnd,
786 TAB_HOTTRACK_TIMER,
787 TAB_HOTTRACK_TIMER_INTERVAL,
788 TAB_HotTrackTimerProc
791 if (timerID == 0)
792 return; /* Hot tracking not available */
795 infoPtr->iHotTracked = item;
797 if (item >= 0)
799 /* Mark new hot-tracked to be redrawn to look highlighted */
800 if (out_redrawEnter != NULL)
801 *out_redrawEnter = item;
806 /******************************************************************************
807 * TAB_MouseMove
809 * Handles the mouse-move event. Updates tooltips. Updates hot-tracking.
811 static LRESULT
812 TAB_MouseMove (HWND hwnd, WPARAM wParam, LPARAM lParam)
814 int redrawLeave;
815 int redrawEnter;
817 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
819 if (infoPtr->hwndToolTip)
820 TAB_RelayEvent (infoPtr->hwndToolTip, hwnd,
821 WM_LBUTTONDOWN, wParam, lParam);
823 /* Determine which tab to highlight. Redraw tabs which change highlight
824 ** status. */
825 TAB_RecalcHotTrack(hwnd, &lParam, &redrawLeave, &redrawEnter);
827 if (redrawLeave != -1)
828 TAB_DrawLoneItemInterior(hwnd, infoPtr, redrawLeave);
829 if (redrawEnter != -1)
830 TAB_DrawLoneItemInterior(hwnd, infoPtr, redrawEnter);
832 return 0;
835 /******************************************************************************
836 * TAB_AdjustRect
838 * Calculates the tab control's display area given the window rectangle or
839 * the window rectangle given the requested display rectangle.
841 static LRESULT TAB_AdjustRect(
842 HWND hwnd,
843 WPARAM fLarger,
844 LPRECT prc)
846 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
847 DWORD lStyle = GetWindowLongA(hwnd, GWL_STYLE);
848 LONG *iRightBottom, *iLeftTop;
850 TRACE ("hwnd=%p fLarger=%d (%ld,%ld)-(%ld,%ld)\n", hwnd, fLarger, prc->left, prc->top, prc->right, prc->bottom);
852 if(lStyle & TCS_VERTICAL)
854 iRightBottom = &(prc->right);
855 iLeftTop = &(prc->left);
857 else
859 iRightBottom = &(prc->bottom);
860 iLeftTop = &(prc->top);
863 if (fLarger) /* Go from display rectangle */
865 /* Add the height of the tabs. */
866 if (lStyle & TCS_BOTTOM)
867 *iRightBottom += infoPtr->tabHeight * infoPtr->uNumRows;
868 else
869 *iLeftTop -= infoPtr->tabHeight * infoPtr->uNumRows +
870 ((lStyle & TCS_BUTTONS)? 3 * (infoPtr->uNumRows - 1) : 0);
872 /* Inflate the rectangle for the padding */
873 InflateRect(prc, DISPLAY_AREA_PADDINGX, DISPLAY_AREA_PADDINGY);
875 /* Inflate for the border */
876 InflateRect(prc, CONTROL_BORDER_SIZEX, CONTROL_BORDER_SIZEY);
878 else /* Go from window rectangle. */
880 /* Deflate the rectangle for the border */
881 InflateRect(prc, -CONTROL_BORDER_SIZEX, -CONTROL_BORDER_SIZEY);
883 /* Deflate the rectangle for the padding */
884 InflateRect(prc, -DISPLAY_AREA_PADDINGX, -DISPLAY_AREA_PADDINGY);
886 /* Remove the height of the tabs. */
887 if (lStyle & TCS_BOTTOM)
888 *iRightBottom -= infoPtr->tabHeight * infoPtr->uNumRows;
889 else
890 *iLeftTop += (infoPtr->tabHeight) * infoPtr->uNumRows +
891 ((lStyle & TCS_BUTTONS)? 3 * (infoPtr->uNumRows - 1) : 0);
894 return 0;
897 /******************************************************************************
898 * TAB_OnHScroll
900 * This method will handle the notification from the scroll control and
901 * perform the scrolling operation on the tab control.
903 static LRESULT TAB_OnHScroll(
904 HWND hwnd,
905 int nScrollCode,
906 int nPos,
907 HWND hwndScroll)
909 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
911 if(nScrollCode == SB_THUMBPOSITION && nPos != infoPtr->leftmostVisible)
913 if(nPos < infoPtr->leftmostVisible)
914 infoPtr->leftmostVisible--;
915 else
916 infoPtr->leftmostVisible++;
918 TAB_RecalcHotTrack(hwnd, NULL, NULL, NULL);
919 TAB_InvalidateTabArea(hwnd, infoPtr);
920 SendMessageW(infoPtr->hwndUpDown, UDM_SETPOS, 0,
921 MAKELONG(infoPtr->leftmostVisible, 0));
924 return 0;
927 /******************************************************************************
928 * TAB_SetupScrolling
930 * This method will check the current scrolling state and make sure the
931 * scrolling control is displayed (or not).
933 static void TAB_SetupScrolling(
934 HWND hwnd,
935 TAB_INFO* infoPtr,
936 const RECT* clientRect)
938 INT maxRange = 0;
939 DWORD lStyle = GetWindowLongA(hwnd, GWL_STYLE);
941 if (infoPtr->needsScrolling)
943 RECT controlPos;
944 INT vsize, tabwidth;
947 * Calculate the position of the scroll control.
949 if(lStyle & TCS_VERTICAL)
951 controlPos.right = clientRect->right;
952 controlPos.left = controlPos.right - 2 * GetSystemMetrics(SM_CXHSCROLL);
954 if (lStyle & TCS_BOTTOM)
956 controlPos.top = clientRect->bottom - infoPtr->tabHeight;
957 controlPos.bottom = controlPos.top + GetSystemMetrics(SM_CYHSCROLL);
959 else
961 controlPos.bottom = clientRect->top + infoPtr->tabHeight;
962 controlPos.top = controlPos.bottom - GetSystemMetrics(SM_CYHSCROLL);
965 else
967 controlPos.right = clientRect->right;
968 controlPos.left = controlPos.right - 2 * GetSystemMetrics(SM_CXHSCROLL);
970 if (lStyle & TCS_BOTTOM)
972 controlPos.top = clientRect->bottom - infoPtr->tabHeight;
973 controlPos.bottom = controlPos.top + GetSystemMetrics(SM_CYHSCROLL);
975 else
977 controlPos.bottom = clientRect->top + infoPtr->tabHeight;
978 controlPos.top = controlPos.bottom - GetSystemMetrics(SM_CYHSCROLL);
983 * If we don't have a scroll control yet, we want to create one.
984 * If we have one, we want to make sure it's positioned properly.
986 if (infoPtr->hwndUpDown==0)
988 infoPtr->hwndUpDown = CreateWindowA("msctls_updown32",
990 WS_VISIBLE | WS_CHILD | UDS_HORZ,
991 controlPos.left, controlPos.top,
992 controlPos.right - controlPos.left,
993 controlPos.bottom - controlPos.top,
994 hwnd,
995 NULL,
996 NULL,
997 NULL);
999 else
1001 SetWindowPos(infoPtr->hwndUpDown,
1002 NULL,
1003 controlPos.left, controlPos.top,
1004 controlPos.right - controlPos.left,
1005 controlPos.bottom - controlPos.top,
1006 SWP_SHOWWINDOW | SWP_NOZORDER);
1009 /* Now calculate upper limit of the updown control range.
1010 * We do this by calculating how many tabs will be offscreen when the
1011 * last tab is visible.
1013 if(infoPtr->uNumItem)
1015 vsize = clientRect->right - (controlPos.right - controlPos.left + 1);
1016 maxRange = infoPtr->uNumItem;
1017 tabwidth = infoPtr->items[maxRange - 1].rect.right;
1019 for(; maxRange > 0; maxRange--)
1021 if(tabwidth - infoPtr->items[maxRange - 1].rect.left > vsize)
1022 break;
1025 if(maxRange == infoPtr->uNumItem)
1026 maxRange--;
1029 else
1031 /* If we once had a scroll control... hide it */
1032 if (infoPtr->hwndUpDown!=0)
1033 ShowWindow(infoPtr->hwndUpDown, SW_HIDE);
1035 if (infoPtr->hwndUpDown)
1036 SendMessageW(infoPtr->hwndUpDown, UDM_SETRANGE32, 0, maxRange);
1039 /******************************************************************************
1040 * TAB_SetItemBounds
1042 * This method will calculate the position rectangles of all the items in the
1043 * control. The rectangle calculated starts at 0 for the first item in the
1044 * list and ignores scrolling and selection.
1045 * It also uses the current font to determine the height of the tab row and
1046 * it checks if all the tabs fit in the client area of the window. If they
1047 * don't, a scrolling control is added.
1049 static void TAB_SetItemBounds (HWND hwnd)
1051 TAB_INFO* infoPtr = TAB_GetInfoPtr(hwnd);
1052 LONG lStyle = GetWindowLongA(hwnd, GWL_STYLE);
1053 TEXTMETRICA fontMetrics;
1054 UINT curItem;
1055 INT curItemLeftPos;
1056 INT curItemRowCount;
1057 HFONT hFont, hOldFont;
1058 HDC hdc;
1059 RECT clientRect;
1060 SIZE size;
1061 INT iTemp;
1062 RECT* rcItem;
1063 INT iIndex;
1064 INT icon_width = 0;
1067 * We need to get text information so we need a DC and we need to select
1068 * a font.
1070 hdc = GetDC(hwnd);
1072 hFont = infoPtr->hFont ? infoPtr->hFont : GetStockObject (SYSTEM_FONT);
1073 hOldFont = SelectObject (hdc, hFont);
1076 * We will base the rectangle calculations on the client rectangle
1077 * of the control.
1079 GetClientRect(hwnd, &clientRect);
1081 /* if TCS_VERTICAL then swap the height and width so this code places the
1082 tabs along the top of the rectangle and we can just rotate them after
1083 rather than duplicate all of the below code */
1084 if(lStyle & TCS_VERTICAL)
1086 iTemp = clientRect.bottom;
1087 clientRect.bottom = clientRect.right;
1088 clientRect.right = iTemp;
1091 /* Now use hPadding and vPadding */
1092 infoPtr->uHItemPadding = infoPtr->uHItemPadding_s;
1093 infoPtr->uVItemPadding = infoPtr->uVItemPadding_s;
1095 /* The leftmost item will be "0" aligned */
1096 curItemLeftPos = 0;
1097 curItemRowCount = infoPtr->uNumItem ? 1 : 0;
1099 if (!(infoPtr->fHeightSet))
1101 int item_height;
1102 int icon_height = 0;
1104 /* Use the current font to determine the height of a tab. */
1105 GetTextMetricsA(hdc, &fontMetrics);
1107 /* Get the icon height */
1108 if (infoPtr->himl)
1109 ImageList_GetIconSize(infoPtr->himl, 0, &icon_height);
1111 /* Take the highest between font or icon */
1112 if (fontMetrics.tmHeight > icon_height)
1113 item_height = fontMetrics.tmHeight + 2;
1114 else
1115 item_height = icon_height;
1118 * Make sure there is enough space for the letters + icon + growing the
1119 * selected item + extra space for the selected item.
1121 infoPtr->tabHeight = item_height +
1122 ((lStyle & TCS_BUTTONS) ? 2 : 1) *
1123 infoPtr->uVItemPadding;
1125 TRACE("tabH=%d, tmH=%ld, iconh=%d\n",
1126 infoPtr->tabHeight, fontMetrics.tmHeight, icon_height);
1129 TRACE("client right=%ld\n", clientRect.right);
1131 /* Get the icon width */
1132 if (infoPtr->himl)
1134 ImageList_GetIconSize(infoPtr->himl, &icon_width, 0);
1136 if (lStyle & TCS_FIXEDWIDTH)
1137 icon_width += 4;
1138 else
1139 /* Add padding if icon is present */
1140 icon_width += infoPtr->uHItemPadding;
1143 for (curItem = 0; curItem < infoPtr->uNumItem; curItem++)
1145 /* Set the leftmost position of the tab. */
1146 infoPtr->items[curItem].rect.left = curItemLeftPos;
1148 if ((lStyle & TCS_FIXEDWIDTH) || !infoPtr->items[curItem].pszText)
1150 infoPtr->items[curItem].rect.right = infoPtr->items[curItem].rect.left +
1151 max(infoPtr->tabWidth, icon_width);
1153 else
1155 int num = 2;
1157 /* Calculate how wide the tab is depending on the text it contains */
1158 GetTextExtentPoint32W(hdc, infoPtr->items[curItem].pszText,
1159 lstrlenW(infoPtr->items[curItem].pszText), &size);
1161 infoPtr->items[curItem].rect.right = infoPtr->items[curItem].rect.left +
1162 size.cx + icon_width +
1163 num * infoPtr->uHItemPadding;
1164 TRACE("for <%s>, l,r=%ld,%ld, num=%d\n",
1165 debugstr_w(infoPtr->items[curItem].pszText),
1166 infoPtr->items[curItem].rect.left,
1167 infoPtr->items[curItem].rect.right,
1168 num);
1172 * Check if this is a multiline tab control and if so
1173 * check to see if we should wrap the tabs
1175 * Wrap all these tabs. We will arrange them evenly later.
1179 if (((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)) &&
1180 (infoPtr->items[curItem].rect.right >
1181 (clientRect.right - CONTROL_BORDER_SIZEX - DISPLAY_AREA_PADDINGX)))
1183 infoPtr->items[curItem].rect.right -=
1184 infoPtr->items[curItem].rect.left;
1186 infoPtr->items[curItem].rect.left = 0;
1187 curItemRowCount++;
1188 TRACE("wrapping <%s>, l,r=%ld,%ld\n",
1189 debugstr_w(infoPtr->items[curItem].pszText),
1190 infoPtr->items[curItem].rect.left,
1191 infoPtr->items[curItem].rect.right);
1194 infoPtr->items[curItem].rect.bottom = 0;
1195 infoPtr->items[curItem].rect.top = curItemRowCount - 1;
1197 TRACE("TextSize: %li\n", size.cx);
1198 TRACE("Rect: T %li, L %li, B %li, R %li\n",
1199 infoPtr->items[curItem].rect.top,
1200 infoPtr->items[curItem].rect.left,
1201 infoPtr->items[curItem].rect.bottom,
1202 infoPtr->items[curItem].rect.right);
1205 * The leftmost position of the next item is the rightmost position
1206 * of this one.
1208 if (lStyle & TCS_BUTTONS)
1210 curItemLeftPos = infoPtr->items[curItem].rect.right + BUTTON_SPACINGX;
1211 if (lStyle & TCS_FLATBUTTONS)
1212 curItemLeftPos += FLAT_BTN_SPACINGX;
1214 else
1215 curItemLeftPos = infoPtr->items[curItem].rect.right;
1218 if (!((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)))
1221 * Check if we need a scrolling control.
1223 infoPtr->needsScrolling = (curItemLeftPos + (2 * SELECTED_TAB_OFFSET) >
1224 clientRect.right);
1226 /* Don't need scrolling, then update infoPtr->leftmostVisible */
1227 if(!infoPtr->needsScrolling)
1228 infoPtr->leftmostVisible = 0;
1230 else
1233 * No scrolling in Multiline or Vertical styles.
1235 infoPtr->needsScrolling = FALSE;
1236 infoPtr->leftmostVisible = 0;
1238 TAB_SetupScrolling(hwnd, infoPtr, &clientRect);
1240 /* Set the number of rows */
1241 infoPtr->uNumRows = curItemRowCount;
1243 /* Arrange all tabs evenly if style says so */
1244 if (!(lStyle & TCS_RAGGEDRIGHT) && ((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)) && (infoPtr->uNumItem > 0))
1246 INT tabPerRow,remTab,iRow;
1247 UINT iItm;
1248 INT iCount=0;
1251 * Ok windows tries to even out the rows. place the same
1252 * number of tabs in each row. So lets give that a shot
1255 tabPerRow = infoPtr->uNumItem / (infoPtr->uNumRows);
1256 remTab = infoPtr->uNumItem % (infoPtr->uNumRows);
1258 for (iItm=0,iRow=0,iCount=0,curItemLeftPos=0;
1259 iItm<infoPtr->uNumItem;
1260 iItm++,iCount++)
1262 /* normalize the current rect */
1264 /* shift the item to the left side of the clientRect */
1265 infoPtr->items[iItm].rect.right -=
1266 infoPtr->items[iItm].rect.left;
1267 infoPtr->items[iItm].rect.left = 0;
1269 TRACE("r=%ld, cl=%d, cl.r=%ld, iCount=%d, iRow=%d, uNumRows=%d, remTab=%d, tabPerRow=%d\n",
1270 infoPtr->items[iItm].rect.right,
1271 curItemLeftPos, clientRect.right,
1272 iCount, iRow, infoPtr->uNumRows, remTab, tabPerRow);
1274 /* if we have reached the maximum number of tabs on this row */
1275 /* move to the next row, reset our current item left position and */
1276 /* the count of items on this row */
1278 if (lStyle & TCS_VERTICAL) {
1279 /* Vert: Add the remaining tabs in the *last* remainder rows */
1280 if (iCount >= ((iRow>=(INT)infoPtr->uNumRows - remTab)?tabPerRow + 1:tabPerRow)) {
1281 iRow++;
1282 curItemLeftPos = 0;
1283 iCount = 0;
1285 } else {
1286 /* Horz: Add the remaining tabs in the *first* remainder rows */
1287 if (iCount >= ((iRow<remTab)?tabPerRow + 1:tabPerRow)) {
1288 iRow++;
1289 curItemLeftPos = 0;
1290 iCount = 0;
1294 /* shift the item to the right to place it as the next item in this row */
1295 infoPtr->items[iItm].rect.left += curItemLeftPos;
1296 infoPtr->items[iItm].rect.right += curItemLeftPos;
1297 infoPtr->items[iItm].rect.top = iRow;
1298 if (lStyle & TCS_BUTTONS)
1300 curItemLeftPos = infoPtr->items[iItm].rect.right + 1;
1301 if (lStyle & TCS_FLATBUTTONS)
1302 curItemLeftPos += FLAT_BTN_SPACINGX;
1304 else
1305 curItemLeftPos = infoPtr->items[iItm].rect.right;
1307 TRACE("arranging <%s>, l,r=%ld,%ld, row=%ld\n",
1308 debugstr_w(infoPtr->items[iItm].pszText),
1309 infoPtr->items[iItm].rect.left,
1310 infoPtr->items[iItm].rect.right,
1311 infoPtr->items[iItm].rect.top);
1315 * Justify the rows
1318 INT widthDiff, iIndexStart=0, iIndexEnd=0;
1319 INT remainder;
1320 INT iCount=0;
1322 while(iIndexStart < infoPtr->uNumItem)
1325 * find the indexs of the row
1327 /* find the first item on the next row */
1328 for (iIndexEnd=iIndexStart;
1329 (iIndexEnd < infoPtr->uNumItem) &&
1330 (infoPtr->items[iIndexEnd].rect.top ==
1331 infoPtr->items[iIndexStart].rect.top) ;
1332 iIndexEnd++)
1333 /* intentionally blank */;
1336 * we need to justify these tabs so they fill the whole given
1337 * client area
1340 /* find the amount of space remaining on this row */
1341 widthDiff = clientRect.right - (2 * SELECTED_TAB_OFFSET) -
1342 infoPtr->items[iIndexEnd - 1].rect.right;
1344 /* iCount is the number of tab items on this row */
1345 iCount = iIndexEnd - iIndexStart;
1347 if (iCount > 1)
1349 remainder = widthDiff % iCount;
1350 widthDiff = widthDiff / iCount;
1351 /* add widthDiff/iCount, or extra space/items on row, to each item on this row */
1352 for (iIndex=iIndexStart, iCount=0; iIndex < iIndexEnd; iIndex++, iCount++)
1354 infoPtr->items[iIndex].rect.left += iCount * widthDiff;
1355 infoPtr->items[iIndex].rect.right += (iCount + 1) * widthDiff;
1357 TRACE("adjusting 1 <%s>, l,r=%ld,%ld\n",
1358 debugstr_w(infoPtr->items[iIndex].pszText),
1359 infoPtr->items[iIndex].rect.left,
1360 infoPtr->items[iIndex].rect.right);
1363 infoPtr->items[iIndex - 1].rect.right += remainder;
1365 else /* we have only one item on this row, make it take up the entire row */
1367 infoPtr->items[iIndexStart].rect.left = clientRect.left;
1368 infoPtr->items[iIndexStart].rect.right = clientRect.right - 4;
1370 TRACE("adjusting 2 <%s>, l,r=%ld,%ld\n",
1371 debugstr_w(infoPtr->items[iIndexStart].pszText),
1372 infoPtr->items[iIndexStart].rect.left,
1373 infoPtr->items[iIndexStart].rect.right);
1378 iIndexStart = iIndexEnd;
1383 /* if TCS_VERTICAL rotate the tabs so they are along the side of the clientRect */
1384 if(lStyle & TCS_VERTICAL)
1386 RECT rcOriginal;
1387 for(iIndex = 0; iIndex < infoPtr->uNumItem; iIndex++)
1389 rcItem = &(infoPtr->items[iIndex].rect);
1391 rcOriginal = *rcItem;
1393 /* this is rotating the items by 90 degrees clockwise around the center of the control */
1394 rcItem->top = (rcOriginal.left - clientRect.left);
1395 rcItem->bottom = rcItem->top + (rcOriginal.right - rcOriginal.left);
1396 rcItem->left = rcOriginal.top;
1397 rcItem->right = rcOriginal.bottom;
1401 TAB_EnsureSelectionVisible(hwnd,infoPtr);
1402 TAB_RecalcHotTrack(hwnd, NULL, NULL, NULL);
1404 /* Cleanup */
1405 SelectObject (hdc, hOldFont);
1406 ReleaseDC (hwnd, hdc);
1410 static void
1411 TAB_EraseTabInterior
1413 HWND hwnd,
1414 HDC hdc,
1415 INT iItem,
1416 RECT* drawRect
1419 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
1420 LONG lStyle = GetWindowLongA(hwnd, GWL_STYLE);
1421 HBRUSH hbr = CreateSolidBrush (comctl32_color.clrBtnFace);
1422 BOOL deleteBrush = TRUE;
1423 RECT rTemp = *drawRect;
1425 InflateRect(&rTemp, -2, -2);
1426 if (lStyle & TCS_BUTTONS)
1428 if (iItem == infoPtr->iSelected)
1430 /* Background color */
1431 if (!(lStyle & TCS_OWNERDRAWFIXED))
1433 DeleteObject(hbr);
1434 hbr = GetSysColorBrush(COLOR_SCROLLBAR);
1436 SetTextColor(hdc, comctl32_color.clr3dFace);
1437 SetBkColor(hdc, comctl32_color.clr3dHilight);
1439 /* if COLOR_WINDOW happens to be the same as COLOR_3DHILIGHT
1440 * we better use 0x55aa bitmap brush to make scrollbar's background
1441 * look different from the window background.
1443 if (comctl32_color.clr3dHilight == comctl32_color.clrWindow)
1444 hbr = COMCTL32_hPattern55AABrush;
1446 deleteBrush = FALSE;
1448 FillRect(hdc, &rTemp, hbr);
1450 else /* ! selected */
1452 if (lStyle & TCS_FLATBUTTONS)
1454 FillRect(hdc, drawRect, hbr);
1455 if (iItem == infoPtr->iHotTracked)
1456 DrawEdge(hdc, drawRect, EDGE_RAISED, BF_SOFT|BF_RECT);
1458 else
1459 FillRect(hdc, &rTemp, hbr);
1463 else /* !TCS_BUTTONS */
1465 FillRect(hdc, &rTemp, hbr);
1468 /* Cleanup */
1469 if (deleteBrush) DeleteObject(hbr);
1472 /******************************************************************************
1473 * TAB_DrawItemInterior
1475 * This method is used to draw the interior (text and icon) of a single tab
1476 * into the tab control.
1478 static void
1479 TAB_DrawItemInterior
1481 HWND hwnd,
1482 HDC hdc,
1483 INT iItem,
1484 RECT* drawRect
1487 TAB_INFO* infoPtr = TAB_GetInfoPtr(hwnd);
1488 LONG lStyle = GetWindowLongA(hwnd, GWL_STYLE);
1490 RECT localRect;
1492 HPEN htextPen;
1493 HPEN holdPen;
1494 INT oldBkMode;
1495 HFONT hOldFont;
1497 /* if (drawRect == NULL) */
1499 BOOL isVisible;
1500 RECT itemRect;
1501 RECT selectedRect;
1504 * Get the rectangle for the item.
1506 isVisible = TAB_InternalGetItemRect(hwnd, infoPtr, iItem, &itemRect, &selectedRect);
1507 if (!isVisible)
1508 return;
1511 * Make sure drawRect points to something valid; simplifies code.
1513 drawRect = &localRect;
1516 * This logic copied from the part of TAB_DrawItem which draws
1517 * the tab background. It's important to keep it in sync. I
1518 * would have liked to avoid code duplication, but couldn't figure
1519 * out how without making spaghetti of TAB_DrawItem.
1521 if (iItem == infoPtr->iSelected)
1522 *drawRect = selectedRect;
1523 else
1524 *drawRect = itemRect;
1526 if (lStyle & TCS_BUTTONS)
1528 if (iItem == infoPtr->iSelected)
1530 drawRect->left += 4;
1531 drawRect->top += 4;
1532 drawRect->right -= 4;
1533 drawRect->bottom -= 1;
1535 else
1537 drawRect->left += 2;
1538 drawRect->top += 2;
1539 drawRect->right -= 2;
1540 drawRect->bottom -= 2;
1543 else
1545 if ((lStyle & TCS_VERTICAL) && (lStyle & TCS_BOTTOM))
1547 if (iItem != infoPtr->iSelected)
1549 drawRect->left += 2;
1550 drawRect->top += 2;
1551 drawRect->bottom -= 2;
1554 else if (lStyle & TCS_VERTICAL)
1556 if (iItem == infoPtr->iSelected)
1558 drawRect->right += 1;
1560 else
1562 drawRect->top += 2;
1563 drawRect->right -= 2;
1564 drawRect->bottom -= 2;
1567 else if (lStyle & TCS_BOTTOM)
1569 if (iItem == infoPtr->iSelected)
1571 drawRect->top -= 2;
1573 else
1575 InflateRect(drawRect, -2, -2);
1576 drawRect->bottom += 2;
1579 else
1581 if (iItem == infoPtr->iSelected)
1583 drawRect->bottom += 3;
1585 else
1587 drawRect->bottom -= (infoPtr->items[iItem].rect.top != infoPtr->uNumRows-1)? 2:2;
1588 InflateRect(drawRect, -2, 0);
1593 TRACE("drawRect=(%ld,%ld)-(%ld,%ld)\n",
1594 drawRect->left, drawRect->top, drawRect->right, drawRect->bottom);
1596 /* Clear interior */
1597 TAB_EraseTabInterior (hwnd, hdc, iItem, drawRect);
1599 /* Draw the focus rectangle */
1600 if (!(lStyle & TCS_FOCUSNEVER) &&
1601 (GetFocus() == hwnd) &&
1602 (iItem == infoPtr->uFocus) )
1604 RECT rFocus = *drawRect;
1605 InflateRect(&rFocus, -3, -3);
1606 if (lStyle & TCS_BOTTOM && !(lStyle & TCS_VERTICAL))
1607 rFocus.top -= 3;
1608 if (lStyle & TCS_BUTTONS)
1610 rFocus.left -= 3;
1611 rFocus.top -= 3;
1614 DrawFocusRect(hdc, &rFocus);
1618 * Text pen
1620 htextPen = CreatePen( PS_SOLID, 1, GetSysColor(COLOR_BTNTEXT) );
1621 holdPen = SelectObject(hdc, htextPen);
1622 hOldFont = SelectObject(hdc, infoPtr->hFont);
1625 * Setup for text output
1627 oldBkMode = SetBkMode(hdc, TRANSPARENT);
1628 SetTextColor(hdc, (((iItem == infoPtr->iHotTracked) && !(lStyle & TCS_FLATBUTTONS)) |
1629 (infoPtr->items[iItem].dwState & TCIS_HIGHLIGHTED)) ?
1630 comctl32_color.clrHighlight : comctl32_color.clrBtnText);
1633 * if owner draw, tell the owner to draw
1635 if ((lStyle & TCS_OWNERDRAWFIXED) && GetParent(hwnd))
1637 DRAWITEMSTRUCT dis;
1638 UINT id;
1640 drawRect->top += 2;
1641 drawRect->right -= 1;
1642 if ( iItem == infoPtr->iSelected )
1644 drawRect->right -= 1;
1645 drawRect->left += 1;
1649 * get the control id
1651 id = (UINT)GetWindowLongPtrW( hwnd, GWLP_ID );
1654 * put together the DRAWITEMSTRUCT
1656 dis.CtlType = ODT_TAB;
1657 dis.CtlID = id;
1658 dis.itemID = iItem;
1659 dis.itemAction = ODA_DRAWENTIRE;
1660 dis.itemState = 0;
1661 if ( iItem == infoPtr->iSelected )
1662 dis.itemState |= ODS_SELECTED;
1663 if (infoPtr->uFocus == iItem)
1664 dis.itemState |= ODS_FOCUS;
1665 dis.hwndItem = hwnd; /* */
1666 dis.hDC = hdc;
1667 CopyRect(&dis.rcItem,drawRect);
1668 dis.itemData = 0;
1669 memcpy( &dis.itemData, infoPtr->items[iItem].extra, min(sizeof(dis.itemData),infoPtr->cbInfo) );
1672 * send the draw message
1674 SendMessageW( infoPtr->hwndNotify, WM_DRAWITEM, (WPARAM)id, (LPARAM)&dis );
1676 else
1678 RECT rcTemp;
1679 RECT rcImage;
1681 /* used to center the icon and text in the tab */
1682 RECT rcText;
1683 INT center_offset_h, center_offset_v;
1685 /* set rcImage to drawRect, we will use top & left in our ImageList_Draw call */
1686 rcImage = *drawRect;
1688 rcTemp = *drawRect;
1690 rcText.left = rcText.top = rcText.right = rcText.bottom = 0;
1692 /* get the rectangle that the text fits in */
1693 if (infoPtr->items[iItem].pszText)
1695 DrawTextW(hdc, infoPtr->items[iItem].pszText, -1,
1696 &rcText, DT_CALCRECT);
1699 * If not owner draw, then do the drawing ourselves.
1701 * Draw the icon.
1703 if (infoPtr->himl && (infoPtr->items[iItem].mask & TCIF_IMAGE))
1705 INT cx;
1706 INT cy;
1708 ImageList_GetIconSize(infoPtr->himl, &cx, &cy);
1710 if(lStyle & TCS_VERTICAL)
1712 center_offset_h = ((drawRect->bottom - drawRect->top) - (cy + infoPtr->uHItemPadding + (rcText.right - rcText.left))) / 2;
1713 center_offset_v = ((drawRect->right - drawRect->left) - (cx + infoPtr->uVItemPadding)) / 2;
1715 else
1717 center_offset_h = ((drawRect->right - drawRect->left) - (cx + infoPtr->uHItemPadding + (rcText.right - rcText.left))) / 2;
1718 center_offset_v = ((drawRect->bottom - drawRect->top) - (cy + infoPtr->uVItemPadding)) / 2;
1721 if (lStyle & TCS_FIXEDWIDTH && lStyle & (TCS_FORCELABELLEFT | TCS_FORCEICONLEFT))
1722 center_offset_h = infoPtr->uHItemPadding;
1724 if (center_offset_h < 2)
1725 center_offset_h = 2;
1727 if (center_offset_v < 0)
1728 center_offset_v = 0;
1730 TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%ld,%ld)-(%ld,%ld), textlen=%ld\n",
1731 debugstr_w(infoPtr->items[iItem].pszText), center_offset_h, center_offset_v,
1732 drawRect->left, drawRect->top, drawRect->right, drawRect->bottom,
1733 (rcText.right-rcText.left));
1735 if((lStyle & TCS_VERTICAL) && (lStyle & TCS_BOTTOM))
1737 rcImage.top = drawRect->top + center_offset_h;
1738 /* if tab is TCS_VERTICAL and TCS_BOTTOM, the text is drawn from the */
1739 /* right side of the tab, but the image still uses the left as its x position */
1740 /* this keeps the image always drawn off of the same side of the tab */
1741 rcImage.left = drawRect->right - cx - center_offset_v;
1742 drawRect->top += cy + infoPtr->uHItemPadding;
1744 else if(lStyle & TCS_VERTICAL)
1746 rcImage.top = drawRect->bottom - cy - center_offset_h;
1747 rcImage.left = drawRect->left + center_offset_v;
1748 drawRect->bottom -= cy + infoPtr->uHItemPadding;
1750 else /* normal style, whether TCS_BOTTOM or not */
1752 rcImage.left = drawRect->left + center_offset_h;
1753 rcImage.top = drawRect->top + center_offset_v;
1754 drawRect->left += cx + infoPtr->uHItemPadding;
1757 TRACE("drawing image=%d, left=%ld, top=%ld\n",
1758 infoPtr->items[iItem].iImage, rcImage.left, rcImage.top-1);
1759 ImageList_Draw
1761 infoPtr->himl,
1762 infoPtr->items[iItem].iImage,
1763 hdc,
1764 rcImage.left,
1765 rcImage.top,
1766 ILD_NORMAL
1770 /* Now position text */
1771 if (lStyle & TCS_FIXEDWIDTH && lStyle & TCS_FORCELABELLEFT)
1772 center_offset_h = infoPtr->uHItemPadding;
1773 else
1774 if(lStyle & TCS_VERTICAL)
1775 center_offset_h = ((drawRect->bottom - drawRect->top) - (rcText.right - rcText.left)) / 2;
1776 else
1777 center_offset_h = ((drawRect->right - drawRect->left) - (rcText.right - rcText.left)) / 2;
1779 if(lStyle & TCS_VERTICAL)
1781 if(lStyle & TCS_BOTTOM)
1782 drawRect->top+=center_offset_h;
1783 else
1784 drawRect->bottom-=center_offset_h;
1786 center_offset_v = ((drawRect->right - drawRect->left) - ((rcText.bottom - rcText.top) + infoPtr->uVItemPadding)) / 2;
1788 else
1790 drawRect->left += center_offset_h;
1791 center_offset_v = ((drawRect->bottom - drawRect->top) - ((rcText.bottom - rcText.top) + infoPtr->uVItemPadding)) / 2;
1794 if (center_offset_v < 0)
1795 center_offset_v = 0;
1797 if(lStyle & TCS_VERTICAL)
1798 drawRect->left += center_offset_v;
1799 else
1800 drawRect->top += center_offset_v;
1802 /* Draw the text */
1803 if(lStyle & TCS_VERTICAL) /* if we are vertical rotate the text and each character */
1805 LOGFONTA logfont;
1806 HFONT hFont = 0;
1807 INT nEscapement = 900;
1808 INT nOrientation = 900;
1810 if(lStyle & TCS_BOTTOM)
1812 nEscapement = -900;
1813 nOrientation = -900;
1816 /* to get a font with the escapement and orientation we are looking for, we need to */
1817 /* call CreateFontIndirectA, which requires us to set the values of the logfont we pass in */
1818 if (!GetObjectA((infoPtr->hFont) ?
1819 infoPtr->hFont : GetStockObject(SYSTEM_FONT),
1820 sizeof(LOGFONTA),&logfont))
1822 INT iPointSize = 9;
1824 lstrcpyA(logfont.lfFaceName, "Arial");
1825 logfont.lfHeight = -MulDiv(iPointSize, GetDeviceCaps(hdc, LOGPIXELSY),
1826 72);
1827 logfont.lfWeight = FW_NORMAL;
1828 logfont.lfItalic = 0;
1829 logfont.lfUnderline = 0;
1830 logfont.lfStrikeOut = 0;
1833 logfont.lfEscapement = nEscapement;
1834 logfont.lfOrientation = nOrientation;
1835 hFont = CreateFontIndirectA(&logfont);
1836 SelectObject(hdc, hFont);
1838 if (infoPtr->items[iItem].pszText)
1840 ExtTextOutW(hdc,
1841 (lStyle & TCS_BOTTOM) ? drawRect->right : drawRect->left,
1842 (!(lStyle & TCS_BOTTOM)) ? drawRect->bottom : drawRect->top,
1843 ETO_CLIPPED,
1844 drawRect,
1845 infoPtr->items[iItem].pszText,
1846 lstrlenW(infoPtr->items[iItem].pszText),
1850 DeleteObject(hFont);
1852 else
1854 TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%ld,%ld)-(%ld,%ld), textlen=%ld\n",
1855 debugstr_w(infoPtr->items[iItem].pszText), center_offset_h, center_offset_v,
1856 drawRect->left, drawRect->top, drawRect->right, drawRect->bottom,
1857 (rcText.right-rcText.left));
1858 if (infoPtr->items[iItem].pszText)
1860 DrawTextW
1862 hdc,
1863 infoPtr->items[iItem].pszText,
1864 lstrlenW(infoPtr->items[iItem].pszText),
1865 drawRect,
1866 DT_LEFT | DT_SINGLELINE
1871 *drawRect = rcTemp; /* restore drawRect */
1875 * Cleanup
1877 SelectObject(hdc, hOldFont);
1878 SetBkMode(hdc, oldBkMode);
1879 SelectObject(hdc, holdPen);
1880 DeleteObject( htextPen );
1883 /******************************************************************************
1884 * TAB_DrawItem
1886 * This method is used to draw a single tab into the tab control.
1888 static void TAB_DrawItem(
1889 HWND hwnd,
1890 HDC hdc,
1891 INT iItem)
1893 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
1894 LONG lStyle = GetWindowLongA(hwnd, GWL_STYLE);
1895 RECT itemRect;
1896 RECT selectedRect;
1897 BOOL isVisible;
1898 RECT r, fillRect, r1;
1899 INT clRight = 0;
1900 INT clBottom = 0;
1901 COLORREF bkgnd, corner;
1904 * Get the rectangle for the item.
1906 isVisible = TAB_InternalGetItemRect(hwnd,
1907 infoPtr,
1908 iItem,
1909 &itemRect,
1910 &selectedRect);
1912 if (isVisible)
1914 RECT rUD, rC;
1916 /* Clip UpDown control to not draw over it */
1917 if (infoPtr->needsScrolling)
1919 GetWindowRect(hwnd, &rC);
1920 GetWindowRect(infoPtr->hwndUpDown, &rUD);
1921 ExcludeClipRect(hdc, rUD.left - rC.left, rUD.top - rC.top, rUD.right - rC.left, rUD.bottom - rC.top);
1924 /* If you need to see what the control is doing,
1925 * then override these variables. They will change what
1926 * fill colors are used for filling the tabs, and the
1927 * corners when drawing the edge.
1929 bkgnd = comctl32_color.clrBtnFace;
1930 corner = comctl32_color.clrBtnFace;
1932 if (lStyle & TCS_BUTTONS)
1934 /* Get item rectangle */
1935 r = itemRect;
1937 /* Separators between flat buttons */
1938 if (lStyle & TCS_FLATBUTTONS)
1940 r1 = r;
1941 r1.right += (FLAT_BTN_SPACINGX -2);
1942 DrawEdge(hdc, &r1, EDGE_ETCHED, BF_RIGHT);
1945 if (iItem == infoPtr->iSelected)
1947 DrawEdge(hdc, &r, EDGE_SUNKEN, BF_SOFT|BF_RECT);
1949 OffsetRect(&r, 1, 1);
1951 else /* ! selected */
1953 if (!(lStyle & TCS_FLATBUTTONS))
1954 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_RECT);
1957 else /* !TCS_BUTTONS */
1959 /* We draw a rectangle of different sizes depending on the selection
1960 * state. */
1961 if (iItem == infoPtr->iSelected) {
1962 RECT rect;
1963 GetClientRect (hwnd, &rect);
1964 clRight = rect.right;
1965 clBottom = rect.bottom;
1966 r = selectedRect;
1968 else
1969 r = itemRect;
1972 * Erase the background. (Delay it but setup rectangle.)
1973 * This is necessary when drawing the selected item since it is larger
1974 * than the others, it might overlap with stuff already drawn by the
1975 * other tabs
1977 fillRect = r;
1979 if(lStyle & TCS_VERTICAL)
1981 /* These are for adjusting the drawing of a Selected tab */
1982 /* The initial values are for the normal case of non-Selected */
1983 int ZZ = 1; /* Do not strech if selected */
1984 if (iItem == infoPtr->iSelected) {
1985 ZZ = 0;
1987 /* if leftmost draw the line longer */
1988 if(selectedRect.top == 0)
1989 fillRect.top += CONTROL_BORDER_SIZEY;
1990 /* if rightmost draw the line longer */
1991 if(selectedRect.bottom == clBottom)
1992 fillRect.bottom -= CONTROL_BORDER_SIZEY;
1995 if (lStyle & TCS_BOTTOM)
1997 /* Adjust both rectangles to match native */
1998 r.left += (1-ZZ);
2000 TRACE("<right> item=%d, fill=(%ld,%ld)-(%ld,%ld), edge=(%ld,%ld)-(%ld,%ld)\n",
2001 iItem,
2002 fillRect.left,fillRect.top,fillRect.right,fillRect.bottom,
2003 r.left,r.top,r.right,r.bottom);
2005 /* Clear interior */
2006 SetBkColor(hdc, bkgnd);
2007 ExtTextOutA(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2009 /* Draw rectangular edge around tab */
2010 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_RIGHT|BF_TOP|BF_BOTTOM);
2012 /* Now erase the top corner and draw diagonal edge */
2013 SetBkColor(hdc, corner);
2014 r1.left = r.right - ROUND_CORNER_SIZE - 1;
2015 r1.top = r.top;
2016 r1.right = r.right;
2017 r1.bottom = r1.top + ROUND_CORNER_SIZE;
2018 ExtTextOutA(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2019 r1.right--;
2020 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPLEFT);
2022 /* Now erase the bottom corner and draw diagonal edge */
2023 r1.left = r.right - ROUND_CORNER_SIZE - 1;
2024 r1.bottom = r.bottom;
2025 r1.right = r.right;
2026 r1.top = r1.bottom - ROUND_CORNER_SIZE;
2027 ExtTextOutA(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2028 r1.right--;
2029 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMLEFT);
2031 if ((iItem == infoPtr->iSelected) && (selectedRect.top == 0)) {
2032 r1 = r;
2033 r1.right = r1.left;
2034 r1.left--;
2035 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_TOP);
2039 else
2041 TRACE("<left> item=%d, fill=(%ld,%ld)-(%ld,%ld), edge=(%ld,%ld)-(%ld,%ld)\n",
2042 iItem,
2043 fillRect.left,fillRect.top,fillRect.right,fillRect.bottom,
2044 r.left,r.top,r.right,r.bottom);
2046 /* Clear interior */
2047 SetBkColor(hdc, bkgnd);
2048 ExtTextOutA(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2050 /* Draw rectangular edge around tab */
2051 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_TOP|BF_BOTTOM);
2053 /* Now erase the top corner and draw diagonal edge */
2054 SetBkColor(hdc, corner);
2055 r1.left = r.left;
2056 r1.top = r.top;
2057 r1.right = r1.left + ROUND_CORNER_SIZE + 1;
2058 r1.bottom = r1.top + ROUND_CORNER_SIZE;
2059 ExtTextOutA(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2060 r1.left++;
2061 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPRIGHT);
2063 /* Now erase the bottom corner and draw diagonal edge */
2064 r1.left = r.left;
2065 r1.bottom = r.bottom;
2066 r1.right = r1.left + ROUND_CORNER_SIZE + 1;
2067 r1.top = r1.bottom - ROUND_CORNER_SIZE;
2068 ExtTextOutA(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2069 r1.left++;
2070 DrawEdge(hdc, &r1, EDGE_SUNKEN, BF_DIAGONAL_ENDTOPLEFT);
2073 else /* ! TCS_VERTICAL */
2075 /* These are for adjusting the drawing of a Selected tab */
2076 /* The initial values are for the normal case of non-Selected */
2077 if (iItem == infoPtr->iSelected) {
2078 /* if leftmost draw the line longer */
2079 if(selectedRect.left == 0)
2080 fillRect.left += CONTROL_BORDER_SIZEX;
2081 /* if rightmost draw the line longer */
2082 if(selectedRect.right == clRight)
2083 fillRect.right -= CONTROL_BORDER_SIZEX;
2086 if (lStyle & TCS_BOTTOM)
2088 /* Adjust both rectangles for topmost row */
2089 if (infoPtr->items[iItem].rect.top == infoPtr->uNumRows-1)
2091 fillRect.top -= 2;
2092 r.top -= 1;
2095 TRACE("<bottom> item=%d, fill=(%ld,%ld)-(%ld,%ld), edge=(%ld,%ld)-(%ld,%ld)\n",
2096 iItem,
2097 fillRect.left,fillRect.top,fillRect.right,fillRect.bottom,
2098 r.left,r.top,r.right,r.bottom);
2100 /* Clear interior */
2101 SetBkColor(hdc, bkgnd);
2102 ExtTextOutA(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2104 /* Draw rectangular edge around tab */
2105 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_BOTTOM|BF_RIGHT);
2107 /* Now erase the righthand corner and draw diagonal edge */
2108 SetBkColor(hdc, corner);
2109 r1.left = r.right - ROUND_CORNER_SIZE;
2110 r1.bottom = r.bottom;
2111 r1.right = r.right;
2112 r1.top = r1.bottom - ROUND_CORNER_SIZE - 1;
2113 ExtTextOutA(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2114 r1.bottom--;
2115 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMLEFT);
2117 /* Now erase the lefthand corner and draw diagonal edge */
2118 r1.left = r.left;
2119 r1.bottom = r.bottom;
2120 r1.right = r1.left + ROUND_CORNER_SIZE;
2121 r1.top = r1.bottom - ROUND_CORNER_SIZE - 1;
2122 ExtTextOutA(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2123 r1.bottom--;
2124 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPLEFT);
2126 if (iItem == infoPtr->iSelected)
2128 r.top += 2;
2129 r.left += 1;
2130 if (selectedRect.left == 0)
2132 r1 = r;
2133 r1.bottom = r1.top;
2134 r1.top--;
2135 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_LEFT);
2140 else
2142 /* Adjust both rectangles for bottommost row */
2143 if (infoPtr->items[iItem].rect.top == infoPtr->uNumRows-1)
2145 fillRect.bottom += 3;
2146 r.bottom += 2;
2149 TRACE("<top> item=%d, fill=(%ld,%ld)-(%ld,%ld), edge=(%ld,%ld)-(%ld,%ld)\n",
2150 iItem,
2151 fillRect.left,fillRect.top,fillRect.right,fillRect.bottom,
2152 r.left,r.top,r.right,r.bottom);
2154 /* Clear interior */
2155 SetBkColor(hdc, bkgnd);
2156 ExtTextOutA(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2158 /* Draw rectangular edge around tab */
2159 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_TOP|BF_RIGHT);
2161 /* Now erase the righthand corner and draw diagonal edge */
2162 SetBkColor(hdc, corner);
2163 r1.left = r.right - ROUND_CORNER_SIZE;
2164 r1.top = r.top;
2165 r1.right = r.right;
2166 r1.bottom = r1.top + ROUND_CORNER_SIZE + 1;
2167 ExtTextOutA(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2168 r1.top++;
2169 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMRIGHT);
2171 /* Now erase the lefthand corner and draw diagonal edge */
2172 r1.left = r.left;
2173 r1.top = r.top;
2174 r1.right = r1.left + ROUND_CORNER_SIZE;
2175 r1.bottom = r1.top + ROUND_CORNER_SIZE + 1;
2176 ExtTextOutA(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2177 r1.top++;
2178 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPRIGHT);
2183 TAB_DumpItemInternal(infoPtr, iItem);
2185 /* This modifies r to be the text rectangle. */
2186 TAB_DrawItemInterior(hwnd, hdc, iItem, &r);
2190 /******************************************************************************
2191 * TAB_DrawBorder
2193 * This method is used to draw the raised border around the tab control
2194 * "content" area.
2196 static void TAB_DrawBorder (HWND hwnd, HDC hdc)
2198 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2199 RECT rect;
2200 DWORD lStyle = GetWindowLongA(hwnd, GWL_STYLE);
2202 GetClientRect (hwnd, &rect);
2205 * Adjust for the style
2208 if (infoPtr->uNumItem)
2210 if ((lStyle & TCS_BOTTOM) && !(lStyle & TCS_VERTICAL))
2211 rect.bottom -= infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2212 else if((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
2213 rect.right -= infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2214 else if(lStyle & TCS_VERTICAL)
2215 rect.left += infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2216 else /* not TCS_VERTICAL and not TCS_BOTTOM */
2217 rect.top += infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2220 TRACE("border=(%ld,%ld)-(%ld,%ld)\n",
2221 rect.left, rect.top, rect.right, rect.bottom);
2223 DrawEdge(hdc, &rect, EDGE_RAISED, BF_SOFT|BF_RECT);
2226 /******************************************************************************
2227 * TAB_Refresh
2229 * This method repaints the tab control..
2231 static void TAB_Refresh (HWND hwnd, HDC hdc)
2233 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2234 HFONT hOldFont;
2235 INT i;
2237 if (!infoPtr->DoRedraw)
2238 return;
2240 hOldFont = SelectObject (hdc, infoPtr->hFont);
2242 if (GetWindowLongA(hwnd, GWL_STYLE) & TCS_BUTTONS)
2244 for (i = 0; i < infoPtr->uNumItem; i++)
2245 TAB_DrawItem (hwnd, hdc, i);
2247 else
2249 /* Draw all the non selected item first */
2250 for (i = 0; i < infoPtr->uNumItem; i++)
2252 if (i != infoPtr->iSelected)
2253 TAB_DrawItem (hwnd, hdc, i);
2256 /* Now, draw the border, draw it before the selected item
2257 * since the selected item overwrites part of the border. */
2258 TAB_DrawBorder (hwnd, hdc);
2260 /* Then, draw the selected item */
2261 TAB_DrawItem (hwnd, hdc, infoPtr->iSelected);
2263 /* If we haven't set the current focus yet, set it now.
2264 * Only happens when we first paint the tab controls */
2265 if (infoPtr->uFocus == -1)
2266 TAB_SetCurFocus(hwnd, infoPtr->iSelected);
2269 SelectObject (hdc, hOldFont);
2272 static DWORD
2273 TAB_GetRowCount (HWND hwnd )
2275 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2277 return infoPtr->uNumRows;
2280 static LRESULT
2281 TAB_SetRedraw (HWND hwnd, WPARAM wParam)
2283 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2285 infoPtr->DoRedraw=(BOOL) wParam;
2286 return 0;
2289 /******************************************************************************
2290 * TAB_EnsureSelectionVisible
2292 * This method will make sure that the current selection is completely
2293 * visible by scrolling until it is.
2295 static void TAB_EnsureSelectionVisible(
2296 HWND hwnd,
2297 TAB_INFO* infoPtr)
2299 INT iSelected = infoPtr->iSelected;
2300 LONG lStyle = GetWindowLongA(hwnd, GWL_STYLE);
2301 INT iOrigLeftmostVisible = infoPtr->leftmostVisible;
2303 /* set the items row to the bottommost row or topmost row depending on
2304 * style */
2305 if ((infoPtr->uNumRows > 1) && !(lStyle & TCS_BUTTONS))
2307 INT newselected;
2308 INT iTargetRow;
2310 if(lStyle & TCS_VERTICAL)
2311 newselected = infoPtr->items[iSelected].rect.left;
2312 else
2313 newselected = infoPtr->items[iSelected].rect.top;
2315 /* the target row is always (number of rows - 1)
2316 as row 0 is furthest from the clientRect */
2317 iTargetRow = infoPtr->uNumRows - 1;
2319 if (newselected != iTargetRow)
2321 UINT i;
2322 if(lStyle & TCS_VERTICAL)
2324 for (i=0; i < infoPtr->uNumItem; i++)
2326 /* move everything in the row of the selected item to the iTargetRow */
2327 if (infoPtr->items[i].rect.left == newselected )
2328 infoPtr->items[i].rect.left = iTargetRow;
2329 else
2331 if (infoPtr->items[i].rect.left > newselected)
2332 infoPtr->items[i].rect.left-=1;
2336 else
2338 for (i=0; i < infoPtr->uNumItem; i++)
2340 if (infoPtr->items[i].rect.top == newselected )
2341 infoPtr->items[i].rect.top = iTargetRow;
2342 else
2344 if (infoPtr->items[i].rect.top > newselected)
2345 infoPtr->items[i].rect.top-=1;
2349 TAB_RecalcHotTrack(hwnd, NULL, NULL, NULL);
2354 * Do the trivial cases first.
2356 if ( (!infoPtr->needsScrolling) ||
2357 (infoPtr->hwndUpDown==0) || (lStyle & TCS_VERTICAL))
2358 return;
2360 if (infoPtr->leftmostVisible >= iSelected)
2362 infoPtr->leftmostVisible = iSelected;
2364 else
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 ((infoPtr->items[iSelected].rect.right -
2378 infoPtr->items[iSelected].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 ((infoPtr->items[iSelected].rect.right -
2390 infoPtr->items[i].rect.left) < width)
2391 break;
2393 infoPtr->leftmostVisible = i;
2397 if (infoPtr->leftmostVisible != iOrigLeftmostVisible)
2398 TAB_RecalcHotTrack(hwnd, NULL, NULL, NULL);
2400 SendMessageW(infoPtr->hwndUpDown, UDM_SETPOS, 0,
2401 MAKELONG(infoPtr->leftmostVisible, 0));
2404 /******************************************************************************
2405 * TAB_InvalidateTabArea
2407 * This method will invalidate the portion of the control that contains the
2408 * tabs. It is called when the state of the control changes and needs
2409 * to be redisplayed
2411 static void TAB_InvalidateTabArea(
2412 HWND hwnd,
2413 TAB_INFO* infoPtr)
2415 RECT clientRect, rInvalidate, rAdjClient;
2416 DWORD lStyle = GetWindowLongA(hwnd, GWL_STYLE);
2417 INT lastRow = infoPtr->uNumRows - 1;
2418 RECT rect;
2420 if (lastRow < 0) return;
2422 GetClientRect(hwnd, &clientRect);
2423 rInvalidate = clientRect;
2424 rAdjClient = clientRect;
2426 TAB_AdjustRect(hwnd, 0, &rAdjClient);
2428 TAB_InternalGetItemRect(hwnd, infoPtr, infoPtr->uNumItem-1 , &rect, NULL);
2429 if ((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
2431 rInvalidate.left = rAdjClient.right;
2432 if (infoPtr->uNumRows == 1)
2433 rInvalidate.bottom = clientRect.top + rect.bottom + 2 * SELECTED_TAB_OFFSET;
2435 else if(lStyle & TCS_VERTICAL)
2437 rInvalidate.right = rAdjClient.left;
2438 if (infoPtr->uNumRows == 1)
2439 rInvalidate.bottom = clientRect.top + rect.bottom + 2 * SELECTED_TAB_OFFSET;
2441 else if (lStyle & TCS_BOTTOM)
2443 rInvalidate.top = rAdjClient.bottom;
2444 if (infoPtr->uNumRows == 1)
2445 rInvalidate.right = clientRect.left + rect.right + 2 * SELECTED_TAB_OFFSET;
2447 else
2449 rInvalidate.bottom = rAdjClient.top;
2450 if (infoPtr->uNumRows == 1)
2451 rInvalidate.right = clientRect.left + rect.right + 2 * SELECTED_TAB_OFFSET;
2454 /* Punch out the updown control */
2455 if (infoPtr->needsScrolling && (rInvalidate.right > 0)) {
2456 RECT r;
2457 GetClientRect(infoPtr->hwndUpDown, &r);
2458 if (rInvalidate.right > clientRect.right - r.left)
2459 rInvalidate.right = rInvalidate.right - (r.right - r.left);
2460 else
2461 rInvalidate.right = clientRect.right - r.left;
2464 TRACE("invalidate (%ld,%ld)-(%ld,%ld)\n",
2465 rInvalidate.left, rInvalidate.top,
2466 rInvalidate.right, rInvalidate.bottom);
2468 InvalidateRect(hwnd, &rInvalidate, TRUE);
2471 static LRESULT
2472 TAB_Paint (HWND hwnd, WPARAM wParam)
2474 HDC hdc;
2475 PAINTSTRUCT ps;
2477 if (wParam == 0)
2479 hdc = BeginPaint (hwnd, &ps);
2480 TRACE("erase %d, rect=(%ld,%ld)-(%ld,%ld)\n",
2481 ps.fErase,
2482 ps.rcPaint.left,ps.rcPaint.top,ps.rcPaint.right,ps.rcPaint.bottom);
2483 } else {
2484 hdc = (HDC)wParam;
2487 TAB_Refresh (hwnd, hdc);
2489 if(!wParam)
2490 EndPaint (hwnd, &ps);
2492 return 0;
2495 static LRESULT
2496 TAB_InsertItemA (HWND hwnd, WPARAM wParam, LPARAM lParam)
2498 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
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 TAB_DumpItemExternalA(pti, iItem);
2517 if (infoPtr->uNumItem == 0) {
2518 infoPtr->items = Alloc (TAB_ITEM_SIZE(infoPtr));
2519 infoPtr->uNumItem++;
2520 infoPtr->iSelected = 0;
2522 else {
2523 TAB_ITEM *oldItems = infoPtr->items;
2525 infoPtr->uNumItem++;
2526 infoPtr->items = Alloc (TAB_ITEM_SIZE(infoPtr) * infoPtr->uNumItem);
2528 /* pre insert copy */
2529 if (iItem > 0) {
2530 memcpy (&infoPtr->items[0], &oldItems[0],
2531 iItem * TAB_ITEM_SIZE(infoPtr));
2534 /* post insert copy */
2535 if (iItem < infoPtr->uNumItem - 1) {
2536 memcpy (&infoPtr->items[iItem+1], &oldItems[iItem],
2537 (infoPtr->uNumItem - iItem - 1) * TAB_ITEM_SIZE(infoPtr));
2541 if (iItem <= infoPtr->iSelected)
2542 infoPtr->iSelected++;
2544 Free (oldItems);
2547 infoPtr->items[iItem].mask = pti->mask;
2548 if (pti->mask & TCIF_TEXT)
2549 Str_SetPtrAtoW (&infoPtr->items[iItem].pszText, pti->pszText);
2551 if (pti->mask & TCIF_IMAGE)
2552 infoPtr->items[iItem].iImage = pti->iImage;
2554 if (pti->mask & TCIF_PARAM)
2555 memcpy(infoPtr->items[iItem].extra, &pti->lParam, infoPtr->cbInfo);
2556 else
2557 memset(infoPtr->items[iItem].extra, 0, infoPtr->cbInfo);
2559 TAB_SetItemBounds(hwnd);
2560 if (infoPtr->uNumItem > 1)
2561 TAB_InvalidateTabArea(hwnd, infoPtr);
2562 else
2563 InvalidateRect(hwnd, NULL, TRUE);
2565 TRACE("[%p]: added item %d %s\n",
2566 hwnd, iItem, debugstr_w(infoPtr->items[iItem].pszText));
2568 return iItem;
2572 static LRESULT
2573 TAB_InsertItemW (HWND hwnd, WPARAM wParam, LPARAM lParam)
2575 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2576 TCITEMW *pti;
2577 INT iItem;
2578 RECT rect;
2580 GetClientRect (hwnd, &rect);
2581 TRACE("Rect: %p T %li, L %li, B %li, R %li\n", hwnd,
2582 rect.top, rect.left, rect.bottom, rect.right);
2584 pti = (TCITEMW *)lParam;
2585 iItem = (INT)wParam;
2587 if (iItem < 0) return -1;
2588 if (iItem > infoPtr->uNumItem)
2589 iItem = infoPtr->uNumItem;
2591 TAB_DumpItemExternalW(pti, iItem);
2593 if (infoPtr->uNumItem == 0) {
2594 infoPtr->items = Alloc (TAB_ITEM_SIZE(infoPtr));
2595 infoPtr->uNumItem++;
2596 infoPtr->iSelected = 0;
2598 else {
2599 TAB_ITEM *oldItems = infoPtr->items;
2601 infoPtr->uNumItem++;
2602 infoPtr->items = Alloc (TAB_ITEM_SIZE(infoPtr) * infoPtr->uNumItem);
2604 /* pre insert copy */
2605 if (iItem > 0) {
2606 memcpy (&infoPtr->items[0], &oldItems[0],
2607 iItem * TAB_ITEM_SIZE(infoPtr));
2610 /* post insert copy */
2611 if (iItem < infoPtr->uNumItem - 1) {
2612 memcpy (&infoPtr->items[iItem+1], &oldItems[iItem],
2613 (infoPtr->uNumItem - iItem - 1) * TAB_ITEM_SIZE(infoPtr));
2617 if (iItem <= infoPtr->iSelected)
2618 infoPtr->iSelected++;
2620 Free (oldItems);
2623 infoPtr->items[iItem].mask = pti->mask;
2624 if (pti->mask & TCIF_TEXT)
2625 Str_SetPtrW (&infoPtr->items[iItem].pszText, pti->pszText);
2627 if (pti->mask & TCIF_IMAGE)
2628 infoPtr->items[iItem].iImage = pti->iImage;
2630 if (pti->mask & TCIF_PARAM)
2631 memcpy(infoPtr->items[iItem].extra, &pti->lParam, infoPtr->cbInfo);
2632 else
2633 memset(infoPtr->items[iItem].extra, 0, infoPtr->cbInfo);
2635 TAB_SetItemBounds(hwnd);
2636 if (infoPtr->uNumItem > 1)
2637 TAB_InvalidateTabArea(hwnd, infoPtr);
2638 else
2639 InvalidateRect(hwnd, NULL, TRUE);
2641 TRACE("[%p]: added item %d %s\n",
2642 hwnd, iItem, debugstr_w(infoPtr->items[iItem].pszText));
2644 return iItem;
2648 static LRESULT
2649 TAB_SetItemSize (HWND hwnd, WPARAM wParam, LPARAM lParam)
2651 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2652 LONG lStyle = GetWindowLongA(hwnd, GWL_STYLE);
2653 LONG lResult = 0;
2654 BOOL bNeedPaint = FALSE;
2656 lResult = MAKELONG(infoPtr->tabWidth, infoPtr->tabHeight);
2658 /* UNDOCUMENTED: If requested Width or Height is 0 this means that program wants to use auto size. */
2659 if (lStyle & TCS_FIXEDWIDTH && (infoPtr->tabWidth != (INT)LOWORD(lParam)))
2661 infoPtr->tabWidth = max((INT)LOWORD(lParam), infoPtr->tabMinWidth);
2662 bNeedPaint = TRUE;
2665 if (infoPtr->tabHeight != (INT)HIWORD(lParam))
2667 if ((infoPtr->fHeightSet = ((INT)HIWORD(lParam) != 0)))
2668 infoPtr->tabHeight = (INT)HIWORD(lParam);
2670 bNeedPaint = TRUE;
2672 TRACE("was h=%d,w=%d, now h=%d,w=%d\n",
2673 HIWORD(lResult), LOWORD(lResult),
2674 infoPtr->tabHeight, infoPtr->tabWidth);
2676 if (bNeedPaint)
2678 TAB_SetItemBounds(hwnd);
2679 RedrawWindow(hwnd, NULL, NULL, RDW_ERASE | RDW_INVALIDATE | RDW_UPDATENOW);
2682 return lResult;
2685 static LRESULT
2686 TAB_SetMinTabWidth (HWND hwnd, LPARAM lParam)
2688 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2689 INT cx = (INT)lParam;
2690 INT oldcx;
2692 if (infoPtr) {
2693 oldcx = infoPtr->tabMinWidth;
2694 infoPtr->tabMinWidth = (cx==-1)?DEFAULT_TAB_WIDTH:cx;
2695 } else
2696 return 0;
2698 return oldcx;
2701 static LRESULT
2702 TAB_HighlightItem (HWND hwnd, WPARAM wParam, LPARAM lParam)
2704 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2705 INT iItem = (INT)wParam;
2706 BOOL fHighlight = (BOOL)LOWORD(lParam);
2708 if ((infoPtr) && (iItem>=0) && (iItem<infoPtr->uNumItem)) {
2709 if (fHighlight)
2710 infoPtr->items[iItem].dwState |= TCIS_HIGHLIGHTED;
2711 else
2712 infoPtr->items[iItem].dwState &= ~TCIS_HIGHLIGHTED;
2713 } else
2714 return FALSE;
2716 return TRUE;
2719 static LRESULT
2720 TAB_SetItemA (HWND hwnd, WPARAM wParam, LPARAM lParam)
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;
2730 TRACE("%d %p\n", iItem, tabItem);
2731 if ((iItem<0) || (iItem>=infoPtr->uNumItem)) return FALSE;
2733 TAB_DumpItemExternalA(tabItem, iItem);
2735 wineItem = &infoPtr->items[iItem];
2737 if (tabItem->mask & TCIF_IMAGE)
2738 wineItem->iImage = tabItem->iImage;
2740 if (tabItem->mask & TCIF_PARAM)
2741 memcpy(wineItem->extra, &tabItem->lParam, infoPtr->cbInfo);
2743 if (tabItem->mask & TCIF_RTLREADING)
2744 FIXME("TCIF_RTLREADING\n");
2746 if (tabItem->mask & TCIF_STATE)
2747 wineItem->dwState = tabItem->dwState;
2749 if (tabItem->mask & TCIF_TEXT)
2750 Str_SetPtrAtoW(&wineItem->pszText, tabItem->pszText);
2752 /* Update and repaint tabs */
2753 TAB_SetItemBounds(hwnd);
2754 TAB_InvalidateTabArea(hwnd,infoPtr);
2756 return TRUE;
2760 static LRESULT
2761 TAB_SetItemW (HWND hwnd, WPARAM wParam, LPARAM lParam)
2763 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2764 TCITEMW *tabItem;
2765 TAB_ITEM *wineItem;
2766 INT iItem;
2768 iItem = (INT)wParam;
2769 tabItem = (LPTCITEMW)lParam;
2771 TRACE("%d %p\n", iItem, tabItem);
2772 if ((iItem<0) || (iItem>=infoPtr->uNumItem)) return FALSE;
2774 TAB_DumpItemExternalW(tabItem, iItem);
2776 wineItem = &infoPtr->items[iItem];
2778 if (tabItem->mask & TCIF_IMAGE)
2779 wineItem->iImage = tabItem->iImage;
2781 if (tabItem->mask & TCIF_PARAM)
2782 memcpy(wineItem->extra, &tabItem->lParam, infoPtr->cbInfo);
2784 if (tabItem->mask & TCIF_RTLREADING)
2785 FIXME("TCIF_RTLREADING\n");
2787 if (tabItem->mask & TCIF_STATE)
2788 wineItem->dwState = tabItem->dwState;
2790 if (tabItem->mask & TCIF_TEXT)
2791 Str_SetPtrW(&wineItem->pszText, tabItem->pszText);
2793 /* Update and repaint tabs */
2794 TAB_SetItemBounds(hwnd);
2795 TAB_InvalidateTabArea(hwnd,infoPtr);
2797 return TRUE;
2801 static LRESULT
2802 TAB_GetItemCount (HWND hwnd, WPARAM wParam, LPARAM lParam)
2804 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2806 return infoPtr->uNumItem;
2810 static LRESULT
2811 TAB_GetItemA (HWND hwnd, WPARAM wParam, LPARAM lParam)
2813 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2814 TCITEMA *tabItem;
2815 TAB_ITEM *wineItem;
2816 INT iItem;
2818 iItem = (INT)wParam;
2819 tabItem = (LPTCITEMA)lParam;
2820 TRACE("\n");
2821 if ((iItem<0) || (iItem>=infoPtr->uNumItem))
2822 return FALSE;
2824 wineItem = &infoPtr->items[iItem];
2826 if (tabItem->mask & TCIF_IMAGE)
2827 tabItem->iImage = wineItem->iImage;
2829 if (tabItem->mask & TCIF_PARAM)
2830 memcpy(&tabItem->lParam, wineItem->extra, infoPtr->cbInfo);
2832 if (tabItem->mask & TCIF_RTLREADING)
2833 FIXME("TCIF_RTLREADING\n");
2835 if (tabItem->mask & TCIF_STATE)
2836 tabItem->dwState = wineItem->dwState;
2838 if (tabItem->mask & TCIF_TEXT)
2839 Str_GetPtrWtoA (wineItem->pszText, tabItem->pszText, tabItem->cchTextMax);
2841 TAB_DumpItemExternalA(tabItem, iItem);
2843 return TRUE;
2847 static LRESULT
2848 TAB_GetItemW (HWND hwnd, WPARAM wParam, LPARAM lParam)
2850 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2851 TCITEMW *tabItem;
2852 TAB_ITEM *wineItem;
2853 INT iItem;
2855 iItem = (INT)wParam;
2856 tabItem = (LPTCITEMW)lParam;
2857 TRACE("\n");
2858 if ((iItem<0) || (iItem>=infoPtr->uNumItem))
2859 return FALSE;
2861 wineItem=& infoPtr->items[iItem];
2863 if (tabItem->mask & TCIF_IMAGE)
2864 tabItem->iImage = wineItem->iImage;
2866 if (tabItem->mask & TCIF_PARAM)
2867 memcpy(&tabItem->lParam, wineItem->extra, infoPtr->cbInfo);
2869 if (tabItem->mask & TCIF_RTLREADING)
2870 FIXME("TCIF_RTLREADING\n");
2872 if (tabItem->mask & TCIF_STATE)
2873 tabItem->dwState = wineItem->dwState;
2875 if (tabItem->mask & TCIF_TEXT)
2876 Str_GetPtrW (wineItem->pszText, tabItem->pszText, tabItem->cchTextMax);
2878 TAB_DumpItemExternalW(tabItem, iItem);
2880 return TRUE;
2884 static LRESULT
2885 TAB_DeleteItem (HWND hwnd, WPARAM wParam, LPARAM lParam)
2887 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2888 INT iItem = (INT) wParam;
2889 BOOL bResult = FALSE;
2891 if ((iItem >= 0) && (iItem < infoPtr->uNumItem))
2893 TAB_ITEM *oldItems = infoPtr->items;
2895 TAB_InvalidateTabArea(hwnd, infoPtr);
2897 infoPtr->uNumItem--;
2898 infoPtr->items = Alloc(TAB_ITEM_SIZE(infoPtr) * infoPtr->uNumItem);
2900 if (iItem > 0)
2901 memcpy(&infoPtr->items[0], &oldItems[0], iItem * TAB_ITEM_SIZE(infoPtr));
2903 if (iItem < infoPtr->uNumItem)
2904 memcpy(&infoPtr->items[iItem], &oldItems[iItem + 1],
2905 (infoPtr->uNumItem - iItem) * TAB_ITEM_SIZE(infoPtr));
2907 Free(oldItems);
2909 /* Readjust the selected index */
2910 if ((iItem == infoPtr->iSelected) && (iItem > 0))
2911 infoPtr->iSelected--;
2913 if (iItem < infoPtr->iSelected)
2914 infoPtr->iSelected--;
2916 if (infoPtr->uNumItem == 0)
2917 infoPtr->iSelected = -1;
2919 /* Reposition and repaint tabs */
2920 TAB_SetItemBounds(hwnd);
2922 bResult = TRUE;
2925 return bResult;
2928 static LRESULT
2929 TAB_DeleteAllItems (HWND hwnd, WPARAM wParam, LPARAM lParam)
2931 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2933 TAB_InvalidateTabArea(hwnd,infoPtr);
2935 Free (infoPtr->items);
2936 infoPtr->uNumItem = 0;
2937 infoPtr->iSelected = -1;
2938 if (infoPtr->iHotTracked >= 0)
2939 KillTimer(hwnd, TAB_HOTTRACK_TIMER);
2940 infoPtr->iHotTracked = -1;
2942 TAB_SetItemBounds(hwnd);
2943 return TRUE;
2947 static LRESULT
2948 TAB_GetFont (HWND hwnd, WPARAM wParam, LPARAM lParam)
2950 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2952 TRACE("\n");
2953 return (LRESULT)infoPtr->hFont;
2956 static LRESULT
2957 TAB_SetFont (HWND hwnd, WPARAM wParam, LPARAM lParam)
2960 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2962 TRACE("%x %lx\n",wParam, lParam);
2964 infoPtr->hFont = (HFONT)wParam;
2966 TAB_SetItemBounds(hwnd);
2968 TAB_InvalidateTabArea(hwnd, infoPtr);
2970 return 0;
2974 static LRESULT
2975 TAB_GetImageList (HWND hwnd, WPARAM wParam, LPARAM lParam)
2977 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2979 TRACE("\n");
2980 return (LRESULT)infoPtr->himl;
2983 static LRESULT
2984 TAB_SetImageList (HWND hwnd, WPARAM wParam, LPARAM lParam)
2986 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2987 HIMAGELIST himlPrev;
2989 TRACE("\n");
2990 himlPrev = infoPtr->himl;
2991 infoPtr->himl= (HIMAGELIST)lParam;
2992 return (LRESULT)himlPrev;
2995 static LRESULT
2996 TAB_GetUnicodeFormat (HWND hwnd)
2998 TAB_INFO *infoPtr = TAB_GetInfoPtr (hwnd);
2999 return infoPtr->bUnicode;
3002 static LRESULT
3003 TAB_SetUnicodeFormat (HWND hwnd, WPARAM wParam)
3005 TAB_INFO *infoPtr = TAB_GetInfoPtr (hwnd);
3006 BOOL bTemp = infoPtr->bUnicode;
3008 infoPtr->bUnicode = (BOOL)wParam;
3010 return bTemp;
3013 static LRESULT
3014 TAB_Size (HWND hwnd, WPARAM wParam, LPARAM lParam)
3017 /* I'm not really sure what the following code was meant to do.
3018 This is what it is doing:
3019 When WM_SIZE is sent with SIZE_RESTORED, the control
3020 gets positioned in the top left corner.
3022 RECT parent_rect;
3023 HWND parent;
3024 UINT uPosFlags,cx,cy;
3026 uPosFlags=0;
3027 if (!wParam) {
3028 parent = GetParent (hwnd);
3029 GetClientRect(parent, &parent_rect);
3030 cx=LOWORD (lParam);
3031 cy=HIWORD (lParam);
3032 if (GetWindowLongA(hwnd, GWL_STYLE) & CCS_NORESIZE)
3033 uPosFlags |= (SWP_NOSIZE | SWP_NOMOVE);
3035 SetWindowPos (hwnd, 0, parent_rect.left, parent_rect.top,
3036 cx, cy, uPosFlags | SWP_NOZORDER);
3037 } else {
3038 FIXME("WM_SIZE flag %x %lx not handled\n", wParam, lParam);
3039 } */
3041 /* Recompute the size/position of the tabs. */
3042 TAB_SetItemBounds (hwnd);
3044 /* Force a repaint of the control. */
3045 InvalidateRect(hwnd, NULL, TRUE);
3047 return 0;
3051 static LRESULT
3052 TAB_Create (HWND hwnd, WPARAM wParam, LPARAM lParam)
3054 TAB_INFO *infoPtr;
3055 TEXTMETRICA fontMetrics;
3056 HDC hdc;
3057 HFONT hOldFont;
3058 DWORD dwStyle;
3060 infoPtr = (TAB_INFO *)Alloc (sizeof(TAB_INFO));
3062 SetWindowLongA(hwnd, 0, (DWORD)infoPtr);
3064 infoPtr->hwndNotify = ((LPCREATESTRUCTW)lParam)->hwndParent;
3065 infoPtr->uNumItem = 0;
3066 infoPtr->uNumRows = 0;
3067 infoPtr->uHItemPadding = 6;
3068 infoPtr->uVItemPadding = 3;
3069 infoPtr->uHItemPadding_s = 6;
3070 infoPtr->uVItemPadding_s = 3;
3071 infoPtr->hFont = 0;
3072 infoPtr->items = 0;
3073 infoPtr->hcurArrow = LoadCursorA (0, (LPSTR)IDC_ARROW);
3074 infoPtr->iSelected = -1;
3075 infoPtr->iHotTracked = -1;
3076 infoPtr->uFocus = -1;
3077 infoPtr->hwndToolTip = 0;
3078 infoPtr->DoRedraw = TRUE;
3079 infoPtr->needsScrolling = FALSE;
3080 infoPtr->hwndUpDown = 0;
3081 infoPtr->leftmostVisible = 0;
3082 infoPtr->fHeightSet = FALSE;
3083 infoPtr->bUnicode = IsWindowUnicode (hwnd);
3084 infoPtr->cbInfo = sizeof(LPARAM);
3086 TRACE("Created tab control, hwnd [%p]\n", hwnd);
3088 /* The tab control always has the WS_CLIPSIBLINGS style. Even
3089 if you don't specify it in CreateWindow. This is necessary in
3090 order for paint to work correctly. This follows windows behaviour. */
3091 dwStyle = GetWindowLongA(hwnd, GWL_STYLE);
3092 SetWindowLongA(hwnd, GWL_STYLE, dwStyle|WS_CLIPSIBLINGS);
3094 if (dwStyle & TCS_TOOLTIPS) {
3095 /* Create tooltip control */
3096 infoPtr->hwndToolTip =
3097 CreateWindowExA (0, TOOLTIPS_CLASSA, NULL, 0,
3098 CW_USEDEFAULT, CW_USEDEFAULT,
3099 CW_USEDEFAULT, CW_USEDEFAULT,
3100 hwnd, 0, 0, 0);
3102 /* Send NM_TOOLTIPSCREATED notification */
3103 if (infoPtr->hwndToolTip) {
3104 NMTOOLTIPSCREATED nmttc;
3106 nmttc.hdr.hwndFrom = hwnd;
3107 nmttc.hdr.idFrom = GetWindowLongPtrW(hwnd, GWLP_ID);
3108 nmttc.hdr.code = NM_TOOLTIPSCREATED;
3109 nmttc.hwndToolTips = infoPtr->hwndToolTip;
3111 SendMessageW (infoPtr->hwndNotify, WM_NOTIFY,
3112 (WPARAM)GetWindowLongPtrW(hwnd, GWLP_ID), (LPARAM)&nmttc);
3117 * We need to get text information so we need a DC and we need to select
3118 * a font.
3120 hdc = GetDC(hwnd);
3121 hOldFont = SelectObject (hdc, GetStockObject (SYSTEM_FONT));
3123 /* Use the system font to determine the initial height of a tab. */
3124 GetTextMetricsA(hdc, &fontMetrics);
3127 * Make sure there is enough space for the letters + growing the
3128 * selected item + extra space for the selected item.
3130 infoPtr->tabHeight = fontMetrics.tmHeight + SELECTED_TAB_OFFSET +
3131 ((dwStyle & TCS_BUTTONS) ? 2 : 1) *
3132 infoPtr->uVItemPadding;
3134 /* Initialize the width of a tab. */
3135 infoPtr->tabWidth = DEFAULT_TAB_WIDTH;
3136 infoPtr->tabMinWidth = 0;
3138 TRACE("tabH=%d, tabW=%d\n", infoPtr->tabHeight, infoPtr->tabWidth);
3140 SelectObject (hdc, hOldFont);
3141 ReleaseDC(hwnd, hdc);
3143 return 0;
3146 static LRESULT
3147 TAB_Destroy (HWND hwnd, WPARAM wParam, LPARAM lParam)
3149 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
3150 UINT iItem;
3152 if (!infoPtr)
3153 return 0;
3155 if (infoPtr->items) {
3156 for (iItem = 0; iItem < infoPtr->uNumItem; iItem++) {
3157 if (infoPtr->items[iItem].pszText)
3158 Free (infoPtr->items[iItem].pszText);
3160 Free (infoPtr->items);
3163 if (infoPtr->hwndToolTip)
3164 DestroyWindow (infoPtr->hwndToolTip);
3166 if (infoPtr->hwndUpDown)
3167 DestroyWindow(infoPtr->hwndUpDown);
3169 if (infoPtr->iHotTracked >= 0)
3170 KillTimer(hwnd, TAB_HOTTRACK_TIMER);
3172 Free (infoPtr);
3173 SetWindowLongA(hwnd, 0, 0);
3174 return 0;
3177 static LRESULT
3178 TAB_SetItemExtra (HWND hwnd, WPARAM wParam, LPARAM lParam)
3180 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
3181 INT cbInfo = wParam;
3183 if (!infoPtr || cbInfo <= 0)
3184 return FALSE;
3186 if (infoPtr->uNumItem)
3188 /* FIXME: MSDN says this is not allowed, but this hasn't been verified */
3189 return FALSE;
3192 infoPtr->cbInfo = cbInfo;
3193 return TRUE;
3196 static LRESULT WINAPI
3197 TAB_WindowProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
3199 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
3201 TRACE("hwnd=%p msg=%x wParam=%x lParam=%lx\n", hwnd, uMsg, wParam, lParam);
3202 if (!TAB_GetInfoPtr(hwnd) && (uMsg != WM_CREATE))
3203 return DefWindowProcW (hwnd, uMsg, wParam, lParam);
3205 switch (uMsg)
3207 case TCM_GETIMAGELIST:
3208 return TAB_GetImageList (hwnd, wParam, lParam);
3210 case TCM_SETIMAGELIST:
3211 return TAB_SetImageList (hwnd, wParam, lParam);
3213 case TCM_GETITEMCOUNT:
3214 return TAB_GetItemCount (hwnd, wParam, lParam);
3216 case TCM_GETITEMA:
3217 return TAB_GetItemA (hwnd, wParam, lParam);
3219 case TCM_GETITEMW:
3220 return TAB_GetItemW (hwnd, wParam, lParam);
3222 case TCM_SETITEMA:
3223 return TAB_SetItemA (hwnd, wParam, lParam);
3225 case TCM_SETITEMW:
3226 return TAB_SetItemW (hwnd, wParam, lParam);
3228 case TCM_DELETEITEM:
3229 return TAB_DeleteItem (hwnd, wParam, lParam);
3231 case TCM_DELETEALLITEMS:
3232 return TAB_DeleteAllItems (hwnd, wParam, lParam);
3234 case TCM_GETITEMRECT:
3235 return TAB_GetItemRect (hwnd, wParam, lParam);
3237 case TCM_GETCURSEL:
3238 return TAB_GetCurSel (hwnd);
3240 case TCM_HITTEST:
3241 return TAB_HitTest (hwnd, wParam, lParam);
3243 case TCM_SETCURSEL:
3244 return TAB_SetCurSel (hwnd, wParam);
3246 case TCM_INSERTITEMA:
3247 return TAB_InsertItemA (hwnd, wParam, lParam);
3249 case TCM_INSERTITEMW:
3250 return TAB_InsertItemW (hwnd, wParam, lParam);
3252 case TCM_SETITEMEXTRA:
3253 return TAB_SetItemExtra (hwnd, wParam, lParam);
3255 case TCM_ADJUSTRECT:
3256 return TAB_AdjustRect (hwnd, (BOOL)wParam, (LPRECT)lParam);
3258 case TCM_SETITEMSIZE:
3259 return TAB_SetItemSize (hwnd, wParam, lParam);
3261 case TCM_REMOVEIMAGE:
3262 FIXME("Unimplemented msg TCM_REMOVEIMAGE\n");
3263 return 0;
3265 case TCM_SETPADDING:
3266 return TAB_SetPadding (hwnd, wParam, lParam);
3268 case TCM_GETROWCOUNT:
3269 return TAB_GetRowCount(hwnd);
3271 case TCM_GETUNICODEFORMAT:
3272 return TAB_GetUnicodeFormat (hwnd);
3274 case TCM_SETUNICODEFORMAT:
3275 return TAB_SetUnicodeFormat (hwnd, wParam);
3277 case TCM_HIGHLIGHTITEM:
3278 return TAB_HighlightItem (hwnd, wParam, lParam);
3280 case TCM_GETTOOLTIPS:
3281 return TAB_GetToolTips (hwnd, wParam, lParam);
3283 case TCM_SETTOOLTIPS:
3284 return TAB_SetToolTips (hwnd, wParam, lParam);
3286 case TCM_GETCURFOCUS:
3287 return TAB_GetCurFocus (hwnd);
3289 case TCM_SETCURFOCUS:
3290 return TAB_SetCurFocus (hwnd, wParam);
3292 case TCM_SETMINTABWIDTH:
3293 return TAB_SetMinTabWidth(hwnd, lParam);
3295 case TCM_DESELECTALL:
3296 FIXME("Unimplemented msg TCM_DESELECTALL\n");
3297 return 0;
3299 case TCM_GETEXTENDEDSTYLE:
3300 FIXME("Unimplemented msg TCM_GETEXTENDEDSTYLE\n");
3301 return 0;
3303 case TCM_SETEXTENDEDSTYLE:
3304 FIXME("Unimplemented msg TCM_SETEXTENDEDSTYLE\n");
3305 return 0;
3307 case WM_GETFONT:
3308 return TAB_GetFont (hwnd, wParam, lParam);
3310 case WM_SETFONT:
3311 return TAB_SetFont (hwnd, wParam, lParam);
3313 case WM_CREATE:
3314 return TAB_Create (hwnd, wParam, lParam);
3316 case WM_NCDESTROY:
3317 return TAB_Destroy (hwnd, wParam, lParam);
3319 case WM_GETDLGCODE:
3320 return DLGC_WANTARROWS | DLGC_WANTCHARS;
3322 case WM_LBUTTONDOWN:
3323 return TAB_LButtonDown (hwnd, wParam, lParam);
3325 case WM_LBUTTONUP:
3326 return TAB_LButtonUp (hwnd, wParam, lParam);
3328 case WM_NOTIFY:
3329 return SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, wParam, lParam);
3331 case WM_RBUTTONDOWN:
3332 return TAB_RButtonDown (hwnd, wParam, lParam);
3334 case WM_MOUSEMOVE:
3335 return TAB_MouseMove (hwnd, wParam, lParam);
3337 case WM_PAINT:
3338 return TAB_Paint (hwnd, wParam);
3340 case WM_SIZE:
3341 return TAB_Size (hwnd, wParam, lParam);
3343 case WM_SETREDRAW:
3344 return TAB_SetRedraw (hwnd, wParam);
3346 case WM_HSCROLL:
3347 return TAB_OnHScroll(hwnd, (int)LOWORD(wParam), (int)HIWORD(wParam), (HWND)lParam);
3349 case WM_STYLECHANGED:
3350 TAB_SetItemBounds (hwnd);
3351 InvalidateRect(hwnd, NULL, TRUE);
3352 return 0;
3354 case WM_SYSCOLORCHANGE:
3355 COMCTL32_RefreshSysColors();
3356 return 0;
3358 case WM_KILLFOCUS:
3359 case WM_SETFOCUS:
3360 return TAB_FocusChanging(hwnd, uMsg, wParam, lParam);
3362 case WM_KEYUP:
3363 return TAB_KeyUp(hwnd, wParam);
3364 case WM_NCHITTEST:
3365 return TAB_NCHitTest(hwnd, lParam);
3367 default:
3368 if ((uMsg >= WM_USER) && (uMsg < WM_APP))
3369 WARN("unknown msg %04x wp=%08x lp=%08lx\n",
3370 uMsg, wParam, lParam);
3371 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
3374 return 0;
3378 VOID
3379 TAB_Register (void)
3381 WNDCLASSW wndClass;
3383 ZeroMemory (&wndClass, sizeof(WNDCLASSW));
3384 wndClass.style = CS_GLOBALCLASS | CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
3385 wndClass.lpfnWndProc = TAB_WindowProc;
3386 wndClass.cbClsExtra = 0;
3387 wndClass.cbWndExtra = sizeof(TAB_INFO *);
3388 wndClass.hCursor = LoadCursorW (0, (LPWSTR)IDC_ARROW);
3389 wndClass.hbrBackground = (HBRUSH)(COLOR_BTNFACE+1);
3390 wndClass.lpszClassName = WC_TABCONTROLW;
3392 RegisterClassW (&wndClass);
3396 VOID
3397 TAB_Unregister (void)
3399 UnregisterClassW (WC_TABCONTROLW, NULL);