First implementation of the WriteIniValues action.
[wine/hacks.git] / dlls / comctl32 / tab.c
blob01654971018e049f3f0a095f11fc6af18138e566
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 /* RETURNS
205 * the index of the selected tab, or -1 if no tab is selected. */
206 static LRESULT
207 TAB_GetCurSel (HWND hwnd)
209 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
211 return infoPtr->iSelected;
214 /* RETURNS
215 * the index of the tab item that has the focus
216 * NOTE
217 * we have not to return negative value
218 * TODO
219 * test for windows */
220 static LRESULT
221 TAB_GetCurFocus (HWND hwnd)
223 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
224 if (infoPtr->uFocus<0)
226 FIXME("we have not to return negative value");
227 return 0;
229 return infoPtr->uFocus;
232 static LRESULT
233 TAB_GetToolTips (HWND hwnd, WPARAM wParam, LPARAM lParam)
235 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
237 if (infoPtr == NULL) return 0;
238 return (LRESULT)infoPtr->hwndToolTip;
241 static LRESULT
242 TAB_SetCurSel (HWND hwnd,WPARAM wParam)
244 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
245 INT iItem = (INT)wParam;
246 INT prevItem;
248 prevItem = -1;
249 if ((iItem >= 0) && (iItem < infoPtr->uNumItem)) {
250 prevItem=infoPtr->iSelected;
251 infoPtr->iSelected=iItem;
252 TAB_EnsureSelectionVisible(hwnd, infoPtr);
253 TAB_InvalidateTabArea(hwnd, infoPtr);
255 return prevItem;
258 static LRESULT
259 TAB_SetCurFocus (HWND hwnd,WPARAM wParam)
261 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
262 INT iItem=(INT) wParam;
264 if ((iItem < 0) || (iItem >= infoPtr->uNumItem)) return 0;
266 if (GetWindowLongA(hwnd, GWL_STYLE) & TCS_BUTTONS) {
267 FIXME("Should set input focus\n");
268 } else {
269 int oldFocus = infoPtr->uFocus;
270 if (infoPtr->iSelected != iItem || oldFocus == -1 ) {
271 infoPtr->uFocus = iItem;
272 if (oldFocus != -1) {
273 if (!TAB_SendSimpleNotify(hwnd, TCN_SELCHANGING)) {
274 infoPtr->iSelected = iItem;
275 TAB_SendSimpleNotify(hwnd, TCN_SELCHANGE);
277 else
278 infoPtr->iSelected = iItem;
279 TAB_EnsureSelectionVisible(hwnd, infoPtr);
280 TAB_InvalidateTabArea(hwnd, infoPtr);
284 return 0;
287 static LRESULT
288 TAB_SetToolTips (HWND hwnd, WPARAM wParam, LPARAM lParam)
290 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
292 if (infoPtr == NULL) return 0;
293 infoPtr->hwndToolTip = (HWND)wParam;
294 return 0;
297 static LRESULT
298 TAB_SetPadding (HWND hwnd, WPARAM wParam, LPARAM lParam)
300 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
302 if (infoPtr == NULL) return 0;
303 infoPtr->uHItemPadding_s=LOWORD(lParam);
304 infoPtr->uVItemPadding_s=HIWORD(lParam);
305 return 0;
308 /******************************************************************************
309 * TAB_InternalGetItemRect
311 * This method will calculate the rectangle representing a given tab item in
312 * client coordinates. This method takes scrolling into account.
314 * This method returns TRUE if the item is visible in the window and FALSE
315 * if it is completely outside the client area.
317 static BOOL TAB_InternalGetItemRect(
318 HWND hwnd,
319 TAB_INFO* infoPtr,
320 INT itemIndex,
321 RECT* itemRect,
322 RECT* selectedRect)
324 RECT tmpItemRect,clientRect;
325 LONG lStyle = GetWindowLongA(hwnd, GWL_STYLE);
327 /* Perform a sanity check and a trivial visibility check. */
328 if ( (infoPtr->uNumItem <= 0) ||
329 (itemIndex >= infoPtr->uNumItem) ||
330 (!((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)) && (itemIndex < infoPtr->leftmostVisible)) )
331 return FALSE;
334 * Avoid special cases in this procedure by assigning the "out"
335 * parameters if the caller didn't supply them
337 if (itemRect == NULL)
338 itemRect = &tmpItemRect;
340 /* Retrieve the unmodified item rect. */
341 *itemRect = TAB_GetItem(infoPtr,itemIndex)->rect;
343 /* calculate the times bottom and top based on the row */
344 GetClientRect(hwnd, &clientRect);
346 if ((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
348 itemRect->right = clientRect.right - SELECTED_TAB_OFFSET - itemRect->left * infoPtr->tabHeight -
349 ((lStyle & TCS_BUTTONS) ? itemRect->left * BUTTON_SPACINGX : 0);
350 itemRect->left = itemRect->right - infoPtr->tabHeight;
352 else if (lStyle & TCS_VERTICAL)
354 itemRect->left = clientRect.left + SELECTED_TAB_OFFSET + itemRect->left * infoPtr->tabHeight +
355 ((lStyle & TCS_BUTTONS) ? itemRect->left * BUTTON_SPACINGX : 0);
356 itemRect->right = itemRect->left + infoPtr->tabHeight;
358 else if (lStyle & TCS_BOTTOM)
360 itemRect->bottom = clientRect.bottom - itemRect->top * infoPtr->tabHeight -
361 ((lStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : SELECTED_TAB_OFFSET);
362 itemRect->top = itemRect->bottom - infoPtr->tabHeight;
364 else /* not TCS_BOTTOM and not TCS_VERTICAL */
366 itemRect->top = clientRect.top + itemRect->top * infoPtr->tabHeight +
367 ((lStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : SELECTED_TAB_OFFSET);
368 itemRect->bottom = itemRect->top + infoPtr->tabHeight;
372 * "scroll" it to make sure the item at the very left of the
373 * tab control is the leftmost visible tab.
375 if(lStyle & TCS_VERTICAL)
377 OffsetRect(itemRect,
379 -TAB_GetItem(infoPtr, infoPtr->leftmostVisible)->rect.top);
382 * Move the rectangle so the first item is slightly offset from
383 * the bottom of the tab control.
385 OffsetRect(itemRect,
387 SELECTED_TAB_OFFSET);
389 } else
391 OffsetRect(itemRect,
392 -TAB_GetItem(infoPtr, infoPtr->leftmostVisible)->rect.left,
396 * Move the rectangle so the first item is slightly offset from
397 * the left of the tab control.
399 OffsetRect(itemRect,
400 SELECTED_TAB_OFFSET,
403 TRACE("item %d tab h=%d, rect=(%ld,%ld)-(%ld,%ld)\n",
404 itemIndex, infoPtr->tabHeight,
405 itemRect->left, itemRect->top, itemRect->right, itemRect->bottom);
407 /* Now, calculate the position of the item as if it were selected. */
408 if (selectedRect!=NULL)
410 CopyRect(selectedRect, itemRect);
412 /* The rectangle of a selected item is a bit wider. */
413 if(lStyle & TCS_VERTICAL)
414 InflateRect(selectedRect, 0, SELECTED_TAB_OFFSET);
415 else
416 InflateRect(selectedRect, SELECTED_TAB_OFFSET, 0);
418 /* If it also a bit higher. */
419 if ((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
421 selectedRect->left -= 2; /* the border is thicker on the right */
422 selectedRect->right += SELECTED_TAB_OFFSET;
424 else if (lStyle & TCS_VERTICAL)
426 selectedRect->left -= SELECTED_TAB_OFFSET;
427 selectedRect->right += 1;
429 else if (lStyle & TCS_BOTTOM)
431 selectedRect->bottom += SELECTED_TAB_OFFSET;
433 else /* not TCS_BOTTOM and not TCS_VERTICAL */
435 selectedRect->top -= SELECTED_TAB_OFFSET;
436 selectedRect->bottom -= 1;
440 return TRUE;
443 static BOOL TAB_GetItemRect(HWND hwnd, WPARAM wParam, LPARAM lParam)
445 return TAB_InternalGetItemRect(hwnd, TAB_GetInfoPtr(hwnd), (INT)wParam,
446 (LPRECT)lParam, (LPRECT)NULL);
449 /******************************************************************************
450 * TAB_KeyUp
452 * This method is called to handle keyboard input
454 static LRESULT TAB_KeyUp(
455 HWND hwnd,
456 WPARAM keyCode)
458 TAB_INFO* infoPtr = TAB_GetInfoPtr(hwnd);
459 int newItem = -1;
461 switch (keyCode)
463 case VK_LEFT:
464 newItem = infoPtr->uFocus - 1;
465 break;
466 case VK_RIGHT:
467 newItem = infoPtr->uFocus + 1;
468 break;
472 * If we changed to a valid item, change the selection
474 if ((newItem >= 0) &&
475 (newItem < infoPtr->uNumItem) &&
476 (infoPtr->uFocus != newItem))
478 if (!TAB_SendSimpleNotify(hwnd, TCN_SELCHANGING))
480 infoPtr->iSelected = newItem;
481 infoPtr->uFocus = newItem;
482 TAB_SendSimpleNotify(hwnd, TCN_SELCHANGE);
484 TAB_EnsureSelectionVisible(hwnd, infoPtr);
485 TAB_InvalidateTabArea(hwnd, infoPtr);
489 return 0;
492 /******************************************************************************
493 * TAB_FocusChanging
495 * This method is called whenever the focus goes in or out of this control
496 * it is used to update the visual state of the control.
498 static LRESULT TAB_FocusChanging(
499 HWND hwnd,
500 UINT uMsg,
501 WPARAM wParam,
502 LPARAM lParam)
504 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
505 RECT selectedRect;
506 BOOL isVisible;
509 * Get the rectangle for the item.
511 isVisible = TAB_InternalGetItemRect(hwnd,
512 infoPtr,
513 infoPtr->uFocus,
514 NULL,
515 &selectedRect);
518 * If the rectangle is not completely invisible, invalidate that
519 * portion of the window.
521 if (isVisible)
523 TRACE("invalidate (%ld,%ld)-(%ld,%ld)\n",
524 selectedRect.left,selectedRect.top,
525 selectedRect.right,selectedRect.bottom);
526 InvalidateRect(hwnd, &selectedRect, TRUE);
530 * Don't otherwise disturb normal behavior.
532 return DefWindowProcW (hwnd, uMsg, wParam, lParam);
535 static INT TAB_InternalHitTest (
536 HWND hwnd,
537 TAB_INFO* infoPtr,
538 POINT pt,
539 UINT* flags)
542 RECT rect;
543 INT iCount;
545 for (iCount = 0; iCount < infoPtr->uNumItem; iCount++)
547 TAB_InternalGetItemRect(hwnd, infoPtr, iCount, &rect, NULL);
549 if (PtInRect(&rect, pt))
551 *flags = TCHT_ONITEM;
552 return iCount;
556 *flags = TCHT_NOWHERE;
557 return -1;
560 static LRESULT
561 TAB_HitTest (HWND hwnd, WPARAM wParam, LPARAM lParam)
563 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
564 LPTCHITTESTINFO lptest = (LPTCHITTESTINFO) lParam;
566 return TAB_InternalHitTest (hwnd, infoPtr, lptest->pt, &lptest->flags);
569 /******************************************************************************
570 * TAB_NCHitTest
572 * Napster v2b5 has a tab control for its main navigation which has a client
573 * area that covers the whole area of the dialog pages.
574 * That's why it receives all msgs for that area and the underlying dialog ctrls
575 * are dead.
576 * So I decided that we should handle WM_NCHITTEST here and return
577 * HTTRANSPARENT if we don't hit the tab control buttons.
578 * FIXME: WM_NCHITTEST handling correct ? Fix it if you know that Windows
579 * doesn't do it that way. Maybe depends on tab control styles ?
581 static LRESULT
582 TAB_NCHitTest (HWND hwnd, LPARAM lParam)
584 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
585 POINT pt;
586 UINT dummyflag;
588 pt.x = LOWORD(lParam);
589 pt.y = HIWORD(lParam);
590 ScreenToClient(hwnd, &pt);
592 if (TAB_InternalHitTest(hwnd, infoPtr, pt, &dummyflag) == -1)
593 return HTTRANSPARENT;
594 else
595 return HTCLIENT;
598 static LRESULT
599 TAB_LButtonDown (HWND hwnd, WPARAM wParam, LPARAM lParam)
601 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
602 POINT pt;
603 INT newItem, dummy;
605 if (infoPtr->hwndToolTip)
606 TAB_RelayEvent (infoPtr->hwndToolTip, hwnd,
607 WM_LBUTTONDOWN, wParam, lParam);
609 if (GetWindowLongA(hwnd, GWL_STYLE) & TCS_FOCUSONBUTTONDOWN ) {
610 SetFocus (hwnd);
613 if (infoPtr->hwndToolTip)
614 TAB_RelayEvent (infoPtr->hwndToolTip, hwnd,
615 WM_LBUTTONDOWN, wParam, lParam);
617 pt.x = (INT)LOWORD(lParam);
618 pt.y = (INT)HIWORD(lParam);
620 newItem = TAB_InternalHitTest (hwnd, infoPtr, pt, &dummy);
622 TRACE("On Tab, item %d\n", newItem);
624 if ((newItem != -1) && (infoPtr->iSelected != newItem))
626 if (!TAB_SendSimpleNotify(hwnd, TCN_SELCHANGING))
628 infoPtr->iSelected = newItem;
629 infoPtr->uFocus = newItem;
630 TAB_SendSimpleNotify(hwnd, TCN_SELCHANGE);
632 TAB_EnsureSelectionVisible(hwnd, infoPtr);
634 TAB_InvalidateTabArea(hwnd, infoPtr);
637 return 0;
640 static LRESULT
641 TAB_LButtonUp (HWND hwnd, WPARAM wParam, LPARAM lParam)
643 TAB_SendSimpleNotify(hwnd, NM_CLICK);
645 return 0;
648 static LRESULT
649 TAB_RButtonDown (HWND hwnd, WPARAM wParam, LPARAM lParam)
651 TAB_SendSimpleNotify(hwnd, NM_RCLICK);
652 return 0;
655 /******************************************************************************
656 * TAB_DrawLoneItemInterior
658 * This calls TAB_DrawItemInterior. However, TAB_DrawItemInterior is normally
659 * called by TAB_DrawItem which is normally called by TAB_Refresh which sets
660 * up the device context and font. This routine does the same setup but
661 * only calls TAB_DrawItemInterior for the single specified item.
663 static void
664 TAB_DrawLoneItemInterior(HWND hwnd, TAB_INFO* infoPtr, int iItem)
666 HDC hdc = GetDC(hwnd);
667 RECT r, rC;
669 /* Clip UpDown control to not draw over it */
670 if (infoPtr->needsScrolling)
672 GetWindowRect(hwnd, &rC);
673 GetWindowRect(infoPtr->hwndUpDown, &r);
674 ExcludeClipRect(hdc, r.left - rC.left, r.top - rC.top, r.right - rC.left, r.bottom - rC.top);
676 TAB_DrawItemInterior(hwnd, hdc, iItem, NULL);
677 ReleaseDC(hwnd, hdc);
680 /******************************************************************************
681 * TAB_HotTrackTimerProc
683 * When a mouse-move event causes a tab to be highlighted (hot-tracking), a
684 * timer is setup so we can check if the mouse is moved out of our window.
685 * (We don't get an event when the mouse leaves, the mouse-move events just
686 * stop being delivered to our window and just start being delivered to
687 * another window.) This function is called when the timer triggers so
688 * we can check if the mouse has left our window. If so, we un-highlight
689 * the hot-tracked tab.
691 static VOID CALLBACK
692 TAB_HotTrackTimerProc
694 HWND hwnd, /* handle of window for timer messages */
695 UINT uMsg, /* WM_TIMER message */
696 UINT idEvent, /* timer identifier */
697 DWORD dwTime /* current system time */
700 TAB_INFO* infoPtr = TAB_GetInfoPtr(hwnd);
702 if (infoPtr != NULL && infoPtr->iHotTracked >= 0)
704 POINT pt;
707 ** If we can't get the cursor position, or if the cursor is outside our
708 ** window, we un-highlight the hot-tracked tab. Note that the cursor is
709 ** "outside" even if it is within our bounding rect if another window
710 ** overlaps. Note also that the case where the cursor stayed within our
711 ** window but has moved off the hot-tracked tab will be handled by the
712 ** WM_MOUSEMOVE event.
714 if (!GetCursorPos(&pt) || WindowFromPoint(pt) != hwnd)
716 /* Redraw iHotTracked to look normal */
717 INT iRedraw = infoPtr->iHotTracked;
718 infoPtr->iHotTracked = -1;
719 TAB_DrawLoneItemInterior(hwnd, infoPtr, iRedraw);
721 /* Kill this timer */
722 KillTimer(hwnd, TAB_HOTTRACK_TIMER);
727 /******************************************************************************
728 * TAB_RecalcHotTrack
730 * If a tab control has the TCS_HOTTRACK style, then the tab under the mouse
731 * should be highlighted. This function determines which tab in a tab control,
732 * if any, is under the mouse and records that information. The caller may
733 * supply output parameters to receive the item number of the tab item which
734 * was highlighted but isn't any longer and of the tab item which is now
735 * highlighted but wasn't previously. The caller can use this information to
736 * selectively redraw those tab items.
738 * If the caller has a mouse position, it can supply it through the pos
739 * parameter. For example, TAB_MouseMove does this. Otherwise, the caller
740 * supplies NULL and this function determines the current mouse position
741 * itself.
743 static void
744 TAB_RecalcHotTrack
746 HWND hwnd,
747 const LPARAM* pos,
748 int* out_redrawLeave,
749 int* out_redrawEnter
752 TAB_INFO* infoPtr = TAB_GetInfoPtr(hwnd);
754 int item = -1;
757 if (out_redrawLeave != NULL)
758 *out_redrawLeave = -1;
759 if (out_redrawEnter != NULL)
760 *out_redrawEnter = -1;
762 if (GetWindowLongA(hwnd, GWL_STYLE) & TCS_HOTTRACK)
764 POINT pt;
765 UINT flags;
767 if (pos == NULL)
769 GetCursorPos(&pt);
770 ScreenToClient(hwnd, &pt);
772 else
774 pt.x = LOWORD(*pos);
775 pt.y = HIWORD(*pos);
778 item = TAB_InternalHitTest(hwnd, infoPtr, pt, &flags);
781 if (item != infoPtr->iHotTracked)
783 if (infoPtr->iHotTracked >= 0)
785 /* Mark currently hot-tracked to be redrawn to look normal */
786 if (out_redrawLeave != NULL)
787 *out_redrawLeave = infoPtr->iHotTracked;
789 if (item < 0)
791 /* Kill timer which forces recheck of mouse pos */
792 KillTimer(hwnd, TAB_HOTTRACK_TIMER);
795 else
797 /* Start timer so we recheck mouse pos */
798 UINT timerID = SetTimer
800 hwnd,
801 TAB_HOTTRACK_TIMER,
802 TAB_HOTTRACK_TIMER_INTERVAL,
803 TAB_HotTrackTimerProc
806 if (timerID == 0)
807 return; /* Hot tracking not available */
810 infoPtr->iHotTracked = item;
812 if (item >= 0)
814 /* Mark new hot-tracked to be redrawn to look highlighted */
815 if (out_redrawEnter != NULL)
816 *out_redrawEnter = item;
821 /******************************************************************************
822 * TAB_MouseMove
824 * Handles the mouse-move event. Updates tooltips. Updates hot-tracking.
826 static LRESULT
827 TAB_MouseMove (HWND hwnd, WPARAM wParam, LPARAM lParam)
829 int redrawLeave;
830 int redrawEnter;
832 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
834 if (infoPtr->hwndToolTip)
835 TAB_RelayEvent (infoPtr->hwndToolTip, hwnd,
836 WM_LBUTTONDOWN, wParam, lParam);
838 /* Determine which tab to highlight. Redraw tabs which change highlight
839 ** status. */
840 TAB_RecalcHotTrack(hwnd, &lParam, &redrawLeave, &redrawEnter);
842 if (redrawLeave != -1)
843 TAB_DrawLoneItemInterior(hwnd, infoPtr, redrawLeave);
844 if (redrawEnter != -1)
845 TAB_DrawLoneItemInterior(hwnd, infoPtr, redrawEnter);
847 return 0;
850 /******************************************************************************
851 * TAB_AdjustRect
853 * Calculates the tab control's display area given the window rectangle or
854 * the window rectangle given the requested display rectangle.
856 static LRESULT TAB_AdjustRect(
857 HWND hwnd,
858 WPARAM fLarger,
859 LPRECT prc)
861 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
862 DWORD lStyle = GetWindowLongA(hwnd, GWL_STYLE);
863 LONG *iRightBottom, *iLeftTop;
865 TRACE ("hwnd=%p fLarger=%d (%ld,%ld)-(%ld,%ld)\n", hwnd, fLarger, prc->left, prc->top, prc->right, prc->bottom);
867 if(lStyle & TCS_VERTICAL)
869 iRightBottom = &(prc->right);
870 iLeftTop = &(prc->left);
872 else
874 iRightBottom = &(prc->bottom);
875 iLeftTop = &(prc->top);
878 if (fLarger) /* Go from display rectangle */
880 /* Add the height of the tabs. */
881 if (lStyle & TCS_BOTTOM)
882 *iRightBottom += infoPtr->tabHeight * infoPtr->uNumRows;
883 else
884 *iLeftTop -= infoPtr->tabHeight * infoPtr->uNumRows +
885 ((lStyle & TCS_BUTTONS)? 3 * (infoPtr->uNumRows - 1) : 0);
887 /* Inflate the rectangle for the padding */
888 InflateRect(prc, DISPLAY_AREA_PADDINGX, DISPLAY_AREA_PADDINGY);
890 /* Inflate for the border */
891 InflateRect(prc, CONTROL_BORDER_SIZEX, CONTROL_BORDER_SIZEY);
893 else /* Go from window rectangle. */
895 /* Deflate the rectangle for the border */
896 InflateRect(prc, -CONTROL_BORDER_SIZEX, -CONTROL_BORDER_SIZEY);
898 /* Deflate the rectangle for the padding */
899 InflateRect(prc, -DISPLAY_AREA_PADDINGX, -DISPLAY_AREA_PADDINGY);
901 /* Remove the height of the tabs. */
902 if (lStyle & TCS_BOTTOM)
903 *iRightBottom -= infoPtr->tabHeight * infoPtr->uNumRows;
904 else
905 *iLeftTop += (infoPtr->tabHeight) * infoPtr->uNumRows +
906 ((lStyle & TCS_BUTTONS)? 3 * (infoPtr->uNumRows - 1) : 0);
909 return 0;
912 /******************************************************************************
913 * TAB_OnHScroll
915 * This method will handle the notification from the scroll control and
916 * perform the scrolling operation on the tab control.
918 static LRESULT TAB_OnHScroll(
919 HWND hwnd,
920 int nScrollCode,
921 int nPos,
922 HWND hwndScroll)
924 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
926 if(nScrollCode == SB_THUMBPOSITION && nPos != infoPtr->leftmostVisible)
928 if(nPos < infoPtr->leftmostVisible)
929 infoPtr->leftmostVisible--;
930 else
931 infoPtr->leftmostVisible++;
933 TAB_RecalcHotTrack(hwnd, NULL, NULL, NULL);
934 TAB_InvalidateTabArea(hwnd, infoPtr);
935 SendMessageW(infoPtr->hwndUpDown, UDM_SETPOS, 0,
936 MAKELONG(infoPtr->leftmostVisible, 0));
939 return 0;
942 /******************************************************************************
943 * TAB_SetupScrolling
945 * This method will check the current scrolling state and make sure the
946 * scrolling control is displayed (or not).
948 static void TAB_SetupScrolling(
949 HWND hwnd,
950 TAB_INFO* infoPtr,
951 const RECT* clientRect)
953 INT maxRange = 0;
954 DWORD lStyle = GetWindowLongA(hwnd, GWL_STYLE);
956 if (infoPtr->needsScrolling)
958 RECT controlPos;
959 INT vsize, tabwidth;
962 * Calculate the position of the scroll control.
964 if(lStyle & TCS_VERTICAL)
966 controlPos.right = clientRect->right;
967 controlPos.left = controlPos.right - 2 * GetSystemMetrics(SM_CXHSCROLL);
969 if (lStyle & TCS_BOTTOM)
971 controlPos.top = clientRect->bottom - infoPtr->tabHeight;
972 controlPos.bottom = controlPos.top + GetSystemMetrics(SM_CYHSCROLL);
974 else
976 controlPos.bottom = clientRect->top + infoPtr->tabHeight;
977 controlPos.top = controlPos.bottom - GetSystemMetrics(SM_CYHSCROLL);
980 else
982 controlPos.right = clientRect->right;
983 controlPos.left = controlPos.right - 2 * GetSystemMetrics(SM_CXHSCROLL);
985 if (lStyle & TCS_BOTTOM)
987 controlPos.top = clientRect->bottom - infoPtr->tabHeight;
988 controlPos.bottom = controlPos.top + GetSystemMetrics(SM_CYHSCROLL);
990 else
992 controlPos.bottom = clientRect->top + infoPtr->tabHeight;
993 controlPos.top = controlPos.bottom - GetSystemMetrics(SM_CYHSCROLL);
998 * If we don't have a scroll control yet, we want to create one.
999 * If we have one, we want to make sure it's positioned properly.
1001 if (infoPtr->hwndUpDown==0)
1003 infoPtr->hwndUpDown = CreateWindowA("msctls_updown32",
1005 WS_VISIBLE | WS_CHILD | UDS_HORZ,
1006 controlPos.left, controlPos.top,
1007 controlPos.right - controlPos.left,
1008 controlPos.bottom - controlPos.top,
1009 hwnd,
1010 NULL,
1011 NULL,
1012 NULL);
1014 else
1016 SetWindowPos(infoPtr->hwndUpDown,
1017 NULL,
1018 controlPos.left, controlPos.top,
1019 controlPos.right - controlPos.left,
1020 controlPos.bottom - controlPos.top,
1021 SWP_SHOWWINDOW | SWP_NOZORDER);
1024 /* Now calculate upper limit of the updown control range.
1025 * We do this by calculating how many tabs will be offscreen when the
1026 * last tab is visible.
1028 if(infoPtr->uNumItem)
1030 vsize = clientRect->right - (controlPos.right - controlPos.left + 1);
1031 maxRange = infoPtr->uNumItem;
1032 tabwidth = TAB_GetItem(infoPtr, infoPtr->uNumItem - 1)->rect.right;
1034 for(; maxRange > 0; maxRange--)
1036 if(tabwidth - TAB_GetItem(infoPtr,maxRange - 1)->rect.left > vsize)
1037 break;
1040 if(maxRange == infoPtr->uNumItem)
1041 maxRange--;
1044 else
1046 /* If we once had a scroll control... hide it */
1047 if (infoPtr->hwndUpDown!=0)
1048 ShowWindow(infoPtr->hwndUpDown, SW_HIDE);
1050 if (infoPtr->hwndUpDown)
1051 SendMessageW(infoPtr->hwndUpDown, UDM_SETRANGE32, 0, maxRange);
1054 /******************************************************************************
1055 * TAB_SetItemBounds
1057 * This method will calculate the position rectangles of all the items in the
1058 * control. The rectangle calculated starts at 0 for the first item in the
1059 * list and ignores scrolling and selection.
1060 * It also uses the current font to determine the height of the tab row and
1061 * it checks if all the tabs fit in the client area of the window. If they
1062 * don't, a scrolling control is added.
1064 static void TAB_SetItemBounds (HWND hwnd)
1066 TAB_INFO* infoPtr = TAB_GetInfoPtr(hwnd);
1067 LONG lStyle = GetWindowLongA(hwnd, GWL_STYLE);
1068 TEXTMETRICA fontMetrics;
1069 UINT curItem;
1070 INT curItemLeftPos;
1071 INT curItemRowCount;
1072 HFONT hFont, hOldFont;
1073 HDC hdc;
1074 RECT clientRect;
1075 SIZE size;
1076 INT iTemp;
1077 RECT* rcItem;
1078 INT iIndex;
1079 INT icon_width = 0;
1082 * We need to get text information so we need a DC and we need to select
1083 * a font.
1085 hdc = GetDC(hwnd);
1087 hFont = infoPtr->hFont ? infoPtr->hFont : GetStockObject (SYSTEM_FONT);
1088 hOldFont = SelectObject (hdc, hFont);
1091 * We will base the rectangle calculations on the client rectangle
1092 * of the control.
1094 GetClientRect(hwnd, &clientRect);
1096 /* if TCS_VERTICAL then swap the height and width so this code places the
1097 tabs along the top of the rectangle and we can just rotate them after
1098 rather than duplicate all of the below code */
1099 if(lStyle & TCS_VERTICAL)
1101 iTemp = clientRect.bottom;
1102 clientRect.bottom = clientRect.right;
1103 clientRect.right = iTemp;
1106 /* Now use hPadding and vPadding */
1107 infoPtr->uHItemPadding = infoPtr->uHItemPadding_s;
1108 infoPtr->uVItemPadding = infoPtr->uVItemPadding_s;
1110 /* The leftmost item will be "0" aligned */
1111 curItemLeftPos = 0;
1112 curItemRowCount = infoPtr->uNumItem ? 1 : 0;
1114 if (!(infoPtr->fHeightSet))
1116 int item_height;
1117 int icon_height = 0;
1119 /* Use the current font to determine the height of a tab. */
1120 GetTextMetricsA(hdc, &fontMetrics);
1122 /* Get the icon height */
1123 if (infoPtr->himl)
1124 ImageList_GetIconSize(infoPtr->himl, 0, &icon_height);
1126 /* Take the highest between font or icon */
1127 if (fontMetrics.tmHeight > icon_height)
1128 item_height = fontMetrics.tmHeight + 2;
1129 else
1130 item_height = icon_height;
1133 * Make sure there is enough space for the letters + icon + growing the
1134 * selected item + extra space for the selected item.
1136 infoPtr->tabHeight = item_height +
1137 ((lStyle & TCS_BUTTONS) ? 2 : 1) *
1138 infoPtr->uVItemPadding;
1140 TRACE("tabH=%d, tmH=%ld, iconh=%d\n",
1141 infoPtr->tabHeight, fontMetrics.tmHeight, icon_height);
1144 TRACE("client right=%ld\n", clientRect.right);
1146 /* Get the icon width */
1147 if (infoPtr->himl)
1149 ImageList_GetIconSize(infoPtr->himl, &icon_width, 0);
1151 if (lStyle & TCS_FIXEDWIDTH)
1152 icon_width += 4;
1153 else
1154 /* Add padding if icon is present */
1155 icon_width += infoPtr->uHItemPadding;
1158 for (curItem = 0; curItem < infoPtr->uNumItem; curItem++)
1160 TAB_ITEM *curr = TAB_GetItem(infoPtr, curItem);
1162 /* Set the leftmost position of the tab. */
1163 curr->rect.left = curItemLeftPos;
1165 if ((lStyle & TCS_FIXEDWIDTH) || !curr->pszText)
1167 curr->rect.right = curr->rect.left +
1168 max(infoPtr->tabWidth, icon_width);
1170 else
1172 int num = 2;
1174 /* Calculate how wide the tab is depending on the text it contains */
1175 GetTextExtentPoint32W(hdc, curr->pszText,
1176 lstrlenW(curr->pszText), &size);
1178 curr->rect.right = curr->rect.left + size.cx + icon_width +
1179 num * infoPtr->uHItemPadding;
1180 TRACE("for <%s>, l,r=%ld,%ld, num=%d\n",
1181 debugstr_w(curr->pszText), curr->rect.left, curr->rect.right, num);
1185 * Check if this is a multiline tab control and if so
1186 * check to see if we should wrap the tabs
1188 * Wrap all these tabs. We will arrange them evenly later.
1192 if (((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)) &&
1193 (curr->rect.right >
1194 (clientRect.right - CONTROL_BORDER_SIZEX - DISPLAY_AREA_PADDINGX)))
1196 curr->rect.right -= curr->rect.left;
1198 curr->rect.left = 0;
1199 curItemRowCount++;
1200 TRACE("wrapping <%s>, l,r=%ld,%ld\n", debugstr_w(curr->pszText),
1201 curr->rect.left, curr->rect.right);
1204 curr->rect.bottom = 0;
1205 curr->rect.top = curItemRowCount - 1;
1207 TRACE("TextSize: %li\n", size.cx);
1208 TRACE("Rect: T %li, L %li, B %li, R %li\n", curr->rect.top,
1209 curr->rect.left, curr->rect.bottom, curr->rect.right);
1212 * The leftmost position of the next item is the rightmost position
1213 * of this one.
1215 if (lStyle & TCS_BUTTONS)
1217 curItemLeftPos = curr->rect.right + BUTTON_SPACINGX;
1218 if (lStyle & TCS_FLATBUTTONS)
1219 curItemLeftPos += FLAT_BTN_SPACINGX;
1221 else
1222 curItemLeftPos = curr->rect.right;
1225 if (!((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)))
1228 * Check if we need a scrolling control.
1230 infoPtr->needsScrolling = (curItemLeftPos + (2 * SELECTED_TAB_OFFSET) >
1231 clientRect.right);
1233 /* Don't need scrolling, then update infoPtr->leftmostVisible */
1234 if(!infoPtr->needsScrolling)
1235 infoPtr->leftmostVisible = 0;
1237 else
1240 * No scrolling in Multiline or Vertical styles.
1242 infoPtr->needsScrolling = FALSE;
1243 infoPtr->leftmostVisible = 0;
1245 TAB_SetupScrolling(hwnd, infoPtr, &clientRect);
1247 /* Set the number of rows */
1248 infoPtr->uNumRows = curItemRowCount;
1250 /* Arrange all tabs evenly if style says so */
1251 if (!(lStyle & TCS_RAGGEDRIGHT) && ((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)) && (infoPtr->uNumItem > 0))
1253 INT tabPerRow,remTab,iRow;
1254 UINT iItm;
1255 INT iCount=0;
1258 * Ok windows tries to even out the rows. place the same
1259 * number of tabs in each row. So lets give that a shot
1262 tabPerRow = infoPtr->uNumItem / (infoPtr->uNumRows);
1263 remTab = infoPtr->uNumItem % (infoPtr->uNumRows);
1265 for (iItm=0,iRow=0,iCount=0,curItemLeftPos=0;
1266 iItm<infoPtr->uNumItem;
1267 iItm++,iCount++)
1269 /* normalize the current rect */
1270 TAB_ITEM *curr = TAB_GetItem(infoPtr, iItm);
1272 /* shift the item to the left side of the clientRect */
1273 curr->rect.right -= curr->rect.left;
1274 curr->rect.left = 0;
1276 TRACE("r=%ld, cl=%d, cl.r=%ld, iCount=%d, iRow=%d, uNumRows=%d, remTab=%d, tabPerRow=%d\n",
1277 curr->rect.right, curItemLeftPos, clientRect.right,
1278 iCount, iRow, infoPtr->uNumRows, remTab, tabPerRow);
1280 /* if we have reached the maximum number of tabs on this row */
1281 /* move to the next row, reset our current item left position and */
1282 /* the count of items on this row */
1284 if (lStyle & TCS_VERTICAL) {
1285 /* Vert: Add the remaining tabs in the *last* remainder rows */
1286 if (iCount >= ((iRow>=(INT)infoPtr->uNumRows - remTab)?tabPerRow + 1:tabPerRow)) {
1287 iRow++;
1288 curItemLeftPos = 0;
1289 iCount = 0;
1291 } else {
1292 /* Horz: Add the remaining tabs in the *first* remainder rows */
1293 if (iCount >= ((iRow<remTab)?tabPerRow + 1:tabPerRow)) {
1294 iRow++;
1295 curItemLeftPos = 0;
1296 iCount = 0;
1300 /* shift the item to the right to place it as the next item in this row */
1301 curr->rect.left += curItemLeftPos;
1302 curr->rect.right += curItemLeftPos;
1303 curr->rect.top = iRow;
1304 if (lStyle & TCS_BUTTONS)
1306 curItemLeftPos = curr->rect.right + 1;
1307 if (lStyle & TCS_FLATBUTTONS)
1308 curItemLeftPos += FLAT_BTN_SPACINGX;
1310 else
1311 curItemLeftPos = curr->rect.right;
1313 TRACE("arranging <%s>, l,r=%ld,%ld, row=%ld\n",
1314 debugstr_w(curr->pszText), curr->rect.left,
1315 curr->rect.right, curr->rect.top);
1319 * Justify the rows
1322 INT widthDiff, iIndexStart=0, iIndexEnd=0;
1323 INT remainder;
1324 INT iCount=0;
1326 while(iIndexStart < infoPtr->uNumItem)
1328 TAB_ITEM *start = TAB_GetItem(infoPtr, iIndexStart);
1331 * find the index of the row
1333 /* find the first item on the next row */
1334 for (iIndexEnd=iIndexStart;
1335 (iIndexEnd < infoPtr->uNumItem) &&
1336 (TAB_GetItem(infoPtr, iIndexEnd)->rect.top ==
1337 start->rect.top) ;
1338 iIndexEnd++)
1339 /* intentionally blank */;
1342 * we need to justify these tabs so they fill the whole given
1343 * client area
1346 /* find the amount of space remaining on this row */
1347 widthDiff = clientRect.right - (2 * SELECTED_TAB_OFFSET) -
1348 TAB_GetItem(infoPtr, iIndexEnd - 1)->rect.right;
1350 /* iCount is the number of tab items on this row */
1351 iCount = iIndexEnd - iIndexStart;
1353 if (iCount > 1)
1355 remainder = widthDiff % iCount;
1356 widthDiff = widthDiff / iCount;
1357 /* add widthDiff/iCount, or extra space/items on row, to each item on this row */
1358 for (iIndex=iIndexStart, iCount=0; iIndex < iIndexEnd; iIndex++, iCount++)
1360 TAB_ITEM *item = TAB_GetItem(infoPtr, iIndex);
1362 item->rect.left += iCount * widthDiff;
1363 item->rect.right += (iCount + 1) * widthDiff;
1365 TRACE("adjusting 1 <%s>, l,r=%ld,%ld\n",
1366 debugstr_w(item->pszText),
1367 item->rect.left, item->rect.right);
1370 TAB_GetItem(infoPtr, iIndex - 1)->rect.right += remainder;
1372 else /* we have only one item on this row, make it take up the entire row */
1374 start->rect.left = clientRect.left;
1375 start->rect.right = clientRect.right - 4;
1377 TRACE("adjusting 2 <%s>, l,r=%ld,%ld\n",
1378 debugstr_w(start->pszText),
1379 start->rect.left, start->rect.right);
1384 iIndexStart = iIndexEnd;
1389 /* if TCS_VERTICAL rotate the tabs so they are along the side of the clientRect */
1390 if(lStyle & TCS_VERTICAL)
1392 RECT rcOriginal;
1393 for(iIndex = 0; iIndex < infoPtr->uNumItem; iIndex++)
1395 rcItem = &TAB_GetItem(infoPtr, iIndex)->rect;
1397 rcOriginal = *rcItem;
1399 /* this is rotating the items by 90 degrees clockwise around the center of the control */
1400 rcItem->top = (rcOriginal.left - clientRect.left);
1401 rcItem->bottom = rcItem->top + (rcOriginal.right - rcOriginal.left);
1402 rcItem->left = rcOriginal.top;
1403 rcItem->right = rcOriginal.bottom;
1407 TAB_EnsureSelectionVisible(hwnd,infoPtr);
1408 TAB_RecalcHotTrack(hwnd, NULL, NULL, NULL);
1410 /* Cleanup */
1411 SelectObject (hdc, hOldFont);
1412 ReleaseDC (hwnd, hdc);
1416 static void
1417 TAB_EraseTabInterior
1419 HWND hwnd,
1420 HDC hdc,
1421 INT iItem,
1422 RECT* drawRect
1425 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
1426 LONG lStyle = GetWindowLongA(hwnd, GWL_STYLE);
1427 HBRUSH hbr = CreateSolidBrush (comctl32_color.clrBtnFace);
1428 BOOL deleteBrush = TRUE;
1429 RECT rTemp = *drawRect;
1431 InflateRect(&rTemp, -2, -2);
1432 if (lStyle & TCS_BUTTONS)
1434 if (iItem == infoPtr->iSelected)
1436 /* Background color */
1437 if (!(lStyle & TCS_OWNERDRAWFIXED))
1439 DeleteObject(hbr);
1440 hbr = GetSysColorBrush(COLOR_SCROLLBAR);
1442 SetTextColor(hdc, comctl32_color.clr3dFace);
1443 SetBkColor(hdc, comctl32_color.clr3dHilight);
1445 /* if COLOR_WINDOW happens to be the same as COLOR_3DHILIGHT
1446 * we better use 0x55aa bitmap brush to make scrollbar's background
1447 * look different from the window background.
1449 if (comctl32_color.clr3dHilight == comctl32_color.clrWindow)
1450 hbr = COMCTL32_hPattern55AABrush;
1452 deleteBrush = FALSE;
1454 FillRect(hdc, &rTemp, hbr);
1456 else /* ! selected */
1458 if (lStyle & TCS_FLATBUTTONS)
1460 FillRect(hdc, drawRect, hbr);
1461 if (iItem == infoPtr->iHotTracked)
1462 DrawEdge(hdc, drawRect, EDGE_RAISED, BF_SOFT|BF_RECT);
1464 else
1465 FillRect(hdc, &rTemp, hbr);
1469 else /* !TCS_BUTTONS */
1471 FillRect(hdc, &rTemp, hbr);
1474 /* Cleanup */
1475 if (deleteBrush) DeleteObject(hbr);
1478 /******************************************************************************
1479 * TAB_DrawItemInterior
1481 * This method is used to draw the interior (text and icon) of a single tab
1482 * into the tab control.
1484 static void
1485 TAB_DrawItemInterior
1487 HWND hwnd,
1488 HDC hdc,
1489 INT iItem,
1490 RECT* drawRect
1493 TAB_INFO* infoPtr = TAB_GetInfoPtr(hwnd);
1494 LONG lStyle = GetWindowLongA(hwnd, GWL_STYLE);
1496 RECT localRect;
1498 HPEN htextPen;
1499 HPEN holdPen;
1500 INT oldBkMode;
1501 HFONT hOldFont;
1503 /* if (drawRect == NULL) */
1505 BOOL isVisible;
1506 RECT itemRect;
1507 RECT selectedRect;
1510 * Get the rectangle for the item.
1512 isVisible = TAB_InternalGetItemRect(hwnd, infoPtr, iItem, &itemRect, &selectedRect);
1513 if (!isVisible)
1514 return;
1517 * Make sure drawRect points to something valid; simplifies code.
1519 drawRect = &localRect;
1522 * This logic copied from the part of TAB_DrawItem which draws
1523 * the tab background. It's important to keep it in sync. I
1524 * would have liked to avoid code duplication, but couldn't figure
1525 * out how without making spaghetti of TAB_DrawItem.
1527 if (iItem == infoPtr->iSelected)
1528 *drawRect = selectedRect;
1529 else
1530 *drawRect = itemRect;
1532 if (lStyle & TCS_BUTTONS)
1534 if (iItem == infoPtr->iSelected)
1536 drawRect->left += 4;
1537 drawRect->top += 4;
1538 drawRect->right -= 4;
1539 drawRect->bottom -= 1;
1541 else
1543 drawRect->left += 2;
1544 drawRect->top += 2;
1545 drawRect->right -= 2;
1546 drawRect->bottom -= 2;
1549 else
1551 if ((lStyle & TCS_VERTICAL) && (lStyle & TCS_BOTTOM))
1553 if (iItem != infoPtr->iSelected)
1555 drawRect->left += 2;
1556 drawRect->top += 2;
1557 drawRect->bottom -= 2;
1560 else if (lStyle & TCS_VERTICAL)
1562 if (iItem == infoPtr->iSelected)
1564 drawRect->right += 1;
1566 else
1568 drawRect->top += 2;
1569 drawRect->right -= 2;
1570 drawRect->bottom -= 2;
1573 else if (lStyle & TCS_BOTTOM)
1575 if (iItem == infoPtr->iSelected)
1577 drawRect->top -= 2;
1579 else
1581 InflateRect(drawRect, -2, -2);
1582 drawRect->bottom += 2;
1585 else
1587 if (iItem == infoPtr->iSelected)
1589 drawRect->bottom += 3;
1591 else
1593 drawRect->bottom -= 2;
1594 InflateRect(drawRect, -2, 0);
1599 TRACE("drawRect=(%ld,%ld)-(%ld,%ld)\n",
1600 drawRect->left, drawRect->top, drawRect->right, drawRect->bottom);
1602 /* Clear interior */
1603 TAB_EraseTabInterior (hwnd, hdc, iItem, drawRect);
1605 /* Draw the focus rectangle */
1606 if (!(lStyle & TCS_FOCUSNEVER) &&
1607 (GetFocus() == hwnd) &&
1608 (iItem == infoPtr->uFocus) )
1610 RECT rFocus = *drawRect;
1611 InflateRect(&rFocus, -3, -3);
1612 if (lStyle & TCS_BOTTOM && !(lStyle & TCS_VERTICAL))
1613 rFocus.top -= 3;
1614 if (lStyle & TCS_BUTTONS)
1616 rFocus.left -= 3;
1617 rFocus.top -= 3;
1620 DrawFocusRect(hdc, &rFocus);
1624 * Text pen
1626 htextPen = CreatePen( PS_SOLID, 1, GetSysColor(COLOR_BTNTEXT) );
1627 holdPen = SelectObject(hdc, htextPen);
1628 hOldFont = SelectObject(hdc, infoPtr->hFont);
1631 * Setup for text output
1633 oldBkMode = SetBkMode(hdc, TRANSPARENT);
1634 SetTextColor(hdc, (((iItem == infoPtr->iHotTracked) && !(lStyle & TCS_FLATBUTTONS)) |
1635 (TAB_GetItem(infoPtr, iItem)->dwState & TCIS_HIGHLIGHTED)) ?
1636 comctl32_color.clrHighlight : comctl32_color.clrBtnText);
1639 * if owner draw, tell the owner to draw
1641 if ((lStyle & TCS_OWNERDRAWFIXED) && GetParent(hwnd))
1643 DRAWITEMSTRUCT dis;
1644 UINT id;
1646 drawRect->top += 2;
1647 drawRect->right -= 1;
1648 if ( iItem == infoPtr->iSelected )
1650 drawRect->right -= 1;
1651 drawRect->left += 1;
1655 * get the control id
1657 id = (UINT)GetWindowLongPtrW( hwnd, GWLP_ID );
1660 * put together the DRAWITEMSTRUCT
1662 dis.CtlType = ODT_TAB;
1663 dis.CtlID = id;
1664 dis.itemID = iItem;
1665 dis.itemAction = ODA_DRAWENTIRE;
1666 dis.itemState = 0;
1667 if ( iItem == infoPtr->iSelected )
1668 dis.itemState |= ODS_SELECTED;
1669 if (infoPtr->uFocus == iItem)
1670 dis.itemState |= ODS_FOCUS;
1671 dis.hwndItem = hwnd; /* */
1672 dis.hDC = hdc;
1673 CopyRect(&dis.rcItem,drawRect);
1674 dis.itemData = (ULONG_PTR)TAB_GetItem(infoPtr, iItem)->extra;
1677 * send the draw message
1679 SendMessageW( infoPtr->hwndNotify, WM_DRAWITEM, (WPARAM)id, (LPARAM)&dis );
1681 else
1683 TAB_ITEM *item = TAB_GetItem(infoPtr, iItem);
1684 RECT rcTemp;
1685 RECT rcImage;
1687 /* used to center the icon and text in the tab */
1688 RECT rcText;
1689 INT center_offset_h, center_offset_v;
1691 /* set rcImage to drawRect, we will use top & left in our ImageList_Draw call */
1692 rcImage = *drawRect;
1694 rcTemp = *drawRect;
1696 rcText.left = rcText.top = rcText.right = rcText.bottom = 0;
1698 /* get the rectangle that the text fits in */
1699 if (item->pszText)
1701 DrawTextW(hdc, item->pszText, -1, &rcText, DT_CALCRECT);
1704 * If not owner draw, then do the drawing ourselves.
1706 * Draw the icon.
1708 if (infoPtr->himl && (item->mask & TCIF_IMAGE))
1710 INT cx;
1711 INT cy;
1713 ImageList_GetIconSize(infoPtr->himl, &cx, &cy);
1715 if(lStyle & TCS_VERTICAL)
1717 center_offset_h = ((drawRect->bottom - drawRect->top) - (cy + infoPtr->uHItemPadding + (rcText.right - rcText.left))) / 2;
1718 center_offset_v = ((drawRect->right - drawRect->left) - (cx + infoPtr->uVItemPadding)) / 2;
1720 else
1722 center_offset_h = ((drawRect->right - drawRect->left) - (cx + infoPtr->uHItemPadding + (rcText.right - rcText.left))) / 2;
1723 center_offset_v = ((drawRect->bottom - drawRect->top) - (cy + infoPtr->uVItemPadding)) / 2;
1726 if (lStyle & TCS_FIXEDWIDTH && lStyle & (TCS_FORCELABELLEFT | TCS_FORCEICONLEFT))
1727 center_offset_h = infoPtr->uHItemPadding;
1729 if (center_offset_h < 2)
1730 center_offset_h = 2;
1732 if (center_offset_v < 0)
1733 center_offset_v = 0;
1735 TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%ld,%ld)-(%ld,%ld), textlen=%ld\n",
1736 debugstr_w(item->pszText), center_offset_h, center_offset_v,
1737 drawRect->left, drawRect->top, drawRect->right, drawRect->bottom,
1738 (rcText.right-rcText.left));
1740 if((lStyle & TCS_VERTICAL) && (lStyle & TCS_BOTTOM))
1742 rcImage.top = drawRect->top + center_offset_h;
1743 /* if tab is TCS_VERTICAL and TCS_BOTTOM, the text is drawn from the */
1744 /* right side of the tab, but the image still uses the left as its x position */
1745 /* this keeps the image always drawn off of the same side of the tab */
1746 rcImage.left = drawRect->right - cx - center_offset_v;
1747 drawRect->top += cy + infoPtr->uHItemPadding;
1749 else if(lStyle & TCS_VERTICAL)
1751 rcImage.top = drawRect->bottom - cy - center_offset_h;
1752 rcImage.left = drawRect->left + center_offset_v;
1753 drawRect->bottom -= cy + infoPtr->uHItemPadding;
1755 else /* normal style, whether TCS_BOTTOM or not */
1757 rcImage.left = drawRect->left + center_offset_h;
1758 rcImage.top = drawRect->top + center_offset_v;
1759 drawRect->left += cx + infoPtr->uHItemPadding;
1762 TRACE("drawing image=%d, left=%ld, top=%ld\n",
1763 item->iImage, rcImage.left, rcImage.top-1);
1764 ImageList_Draw
1766 infoPtr->himl,
1767 item->iImage,
1768 hdc,
1769 rcImage.left,
1770 rcImage.top,
1771 ILD_NORMAL
1775 /* Now position text */
1776 if (lStyle & TCS_FIXEDWIDTH && lStyle & TCS_FORCELABELLEFT)
1777 center_offset_h = infoPtr->uHItemPadding;
1778 else
1779 if(lStyle & TCS_VERTICAL)
1780 center_offset_h = ((drawRect->bottom - drawRect->top) - (rcText.right - rcText.left)) / 2;
1781 else
1782 center_offset_h = ((drawRect->right - drawRect->left) - (rcText.right - rcText.left)) / 2;
1784 if(lStyle & TCS_VERTICAL)
1786 if(lStyle & TCS_BOTTOM)
1787 drawRect->top+=center_offset_h;
1788 else
1789 drawRect->bottom-=center_offset_h;
1791 center_offset_v = ((drawRect->right - drawRect->left) - ((rcText.bottom - rcText.top) + infoPtr->uVItemPadding)) / 2;
1793 else
1795 drawRect->left += center_offset_h;
1796 center_offset_v = ((drawRect->bottom - drawRect->top) - ((rcText.bottom - rcText.top) + infoPtr->uVItemPadding)) / 2;
1799 if (center_offset_v < 0)
1800 center_offset_v = 0;
1802 if(lStyle & TCS_VERTICAL)
1803 drawRect->left += center_offset_v;
1804 else
1805 drawRect->top += center_offset_v;
1807 /* Draw the text */
1808 if(lStyle & TCS_VERTICAL) /* if we are vertical rotate the text and each character */
1810 LOGFONTA logfont;
1811 HFONT hFont = 0;
1812 INT nEscapement = 900;
1813 INT nOrientation = 900;
1815 if(lStyle & TCS_BOTTOM)
1817 nEscapement = -900;
1818 nOrientation = -900;
1821 /* to get a font with the escapement and orientation we are looking for, we need to */
1822 /* call CreateFontIndirectA, which requires us to set the values of the logfont we pass in */
1823 if (!GetObjectA((infoPtr->hFont) ?
1824 infoPtr->hFont : GetStockObject(SYSTEM_FONT),
1825 sizeof(LOGFONTA),&logfont))
1827 INT iPointSize = 9;
1829 lstrcpyA(logfont.lfFaceName, "Arial");
1830 logfont.lfHeight = -MulDiv(iPointSize, GetDeviceCaps(hdc, LOGPIXELSY),
1831 72);
1832 logfont.lfWeight = FW_NORMAL;
1833 logfont.lfItalic = 0;
1834 logfont.lfUnderline = 0;
1835 logfont.lfStrikeOut = 0;
1838 logfont.lfEscapement = nEscapement;
1839 logfont.lfOrientation = nOrientation;
1840 hFont = CreateFontIndirectA(&logfont);
1841 SelectObject(hdc, hFont);
1843 if (item->pszText)
1845 ExtTextOutW(hdc,
1846 (lStyle & TCS_BOTTOM) ? drawRect->right : drawRect->left,
1847 (!(lStyle & TCS_BOTTOM)) ? drawRect->bottom : drawRect->top,
1848 ETO_CLIPPED,
1849 drawRect,
1850 item->pszText,
1851 lstrlenW(item->pszText),
1855 DeleteObject(hFont);
1857 else
1859 TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%ld,%ld)-(%ld,%ld), textlen=%ld\n",
1860 debugstr_w(item->pszText), center_offset_h, center_offset_v,
1861 drawRect->left, drawRect->top, drawRect->right, drawRect->bottom,
1862 (rcText.right-rcText.left));
1863 if (item->pszText)
1865 DrawTextW
1867 hdc,
1868 item->pszText,
1869 lstrlenW(item->pszText),
1870 drawRect,
1871 DT_LEFT | DT_SINGLELINE
1876 *drawRect = rcTemp; /* restore drawRect */
1880 * Cleanup
1882 SelectObject(hdc, hOldFont);
1883 SetBkMode(hdc, oldBkMode);
1884 SelectObject(hdc, holdPen);
1885 DeleteObject( htextPen );
1888 /******************************************************************************
1889 * TAB_DrawItem
1891 * This method is used to draw a single tab into the tab control.
1893 static void TAB_DrawItem(
1894 HWND hwnd,
1895 HDC hdc,
1896 INT iItem)
1898 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
1899 LONG lStyle = GetWindowLongA(hwnd, GWL_STYLE);
1900 RECT itemRect;
1901 RECT selectedRect;
1902 BOOL isVisible;
1903 RECT r, fillRect, r1;
1904 INT clRight = 0;
1905 INT clBottom = 0;
1906 COLORREF bkgnd, corner;
1909 * Get the rectangle for the item.
1911 isVisible = TAB_InternalGetItemRect(hwnd,
1912 infoPtr,
1913 iItem,
1914 &itemRect,
1915 &selectedRect);
1917 if (isVisible)
1919 RECT rUD, rC;
1921 /* Clip UpDown control to not draw over it */
1922 if (infoPtr->needsScrolling)
1924 GetWindowRect(hwnd, &rC);
1925 GetWindowRect(infoPtr->hwndUpDown, &rUD);
1926 ExcludeClipRect(hdc, rUD.left - rC.left, rUD.top - rC.top, rUD.right - rC.left, rUD.bottom - rC.top);
1929 /* If you need to see what the control is doing,
1930 * then override these variables. They will change what
1931 * fill colors are used for filling the tabs, and the
1932 * corners when drawing the edge.
1934 bkgnd = comctl32_color.clrBtnFace;
1935 corner = comctl32_color.clrBtnFace;
1937 if (lStyle & TCS_BUTTONS)
1939 /* Get item rectangle */
1940 r = itemRect;
1942 /* Separators between flat buttons */
1943 if (lStyle & TCS_FLATBUTTONS)
1945 r1 = r;
1946 r1.right += (FLAT_BTN_SPACINGX -2);
1947 DrawEdge(hdc, &r1, EDGE_ETCHED, BF_RIGHT);
1950 if (iItem == infoPtr->iSelected)
1952 DrawEdge(hdc, &r, EDGE_SUNKEN, BF_SOFT|BF_RECT);
1954 OffsetRect(&r, 1, 1);
1956 else /* ! selected */
1958 if (!(lStyle & TCS_FLATBUTTONS))
1959 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_RECT);
1962 else /* !TCS_BUTTONS */
1964 /* We draw a rectangle of different sizes depending on the selection
1965 * state. */
1966 if (iItem == infoPtr->iSelected) {
1967 RECT rect;
1968 GetClientRect (hwnd, &rect);
1969 clRight = rect.right;
1970 clBottom = rect.bottom;
1971 r = selectedRect;
1973 else
1974 r = itemRect;
1977 * Erase the background. (Delay it but setup rectangle.)
1978 * This is necessary when drawing the selected item since it is larger
1979 * than the others, it might overlap with stuff already drawn by the
1980 * other tabs
1982 fillRect = r;
1984 if(lStyle & TCS_VERTICAL)
1986 /* These are for adjusting the drawing of a Selected tab */
1987 /* The initial values are for the normal case of non-Selected */
1988 int ZZ = 1; /* Do not strech if selected */
1989 if (iItem == infoPtr->iSelected) {
1990 ZZ = 0;
1992 /* if leftmost draw the line longer */
1993 if(selectedRect.top == 0)
1994 fillRect.top += CONTROL_BORDER_SIZEY;
1995 /* if rightmost draw the line longer */
1996 if(selectedRect.bottom == clBottom)
1997 fillRect.bottom -= CONTROL_BORDER_SIZEY;
2000 if (lStyle & TCS_BOTTOM)
2002 /* Adjust both rectangles to match native */
2003 r.left += (1-ZZ);
2005 TRACE("<right> item=%d, fill=(%ld,%ld)-(%ld,%ld), edge=(%ld,%ld)-(%ld,%ld)\n",
2006 iItem,
2007 fillRect.left,fillRect.top,fillRect.right,fillRect.bottom,
2008 r.left,r.top,r.right,r.bottom);
2010 /* Clear interior */
2011 SetBkColor(hdc, bkgnd);
2012 ExtTextOutA(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2014 /* Draw rectangular edge around tab */
2015 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_RIGHT|BF_TOP|BF_BOTTOM);
2017 /* Now erase the top corner and draw diagonal edge */
2018 SetBkColor(hdc, corner);
2019 r1.left = r.right - ROUND_CORNER_SIZE - 1;
2020 r1.top = r.top;
2021 r1.right = r.right;
2022 r1.bottom = r1.top + ROUND_CORNER_SIZE;
2023 ExtTextOutA(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2024 r1.right--;
2025 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPLEFT);
2027 /* Now erase the bottom corner and draw diagonal edge */
2028 r1.left = r.right - ROUND_CORNER_SIZE - 1;
2029 r1.bottom = r.bottom;
2030 r1.right = r.right;
2031 r1.top = r1.bottom - ROUND_CORNER_SIZE;
2032 ExtTextOutA(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2033 r1.right--;
2034 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMLEFT);
2036 if ((iItem == infoPtr->iSelected) && (selectedRect.top == 0)) {
2037 r1 = r;
2038 r1.right = r1.left;
2039 r1.left--;
2040 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_TOP);
2044 else
2046 TRACE("<left> item=%d, fill=(%ld,%ld)-(%ld,%ld), edge=(%ld,%ld)-(%ld,%ld)\n",
2047 iItem,
2048 fillRect.left,fillRect.top,fillRect.right,fillRect.bottom,
2049 r.left,r.top,r.right,r.bottom);
2051 /* Clear interior */
2052 SetBkColor(hdc, bkgnd);
2053 ExtTextOutA(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2055 /* Draw rectangular edge around tab */
2056 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_TOP|BF_BOTTOM);
2058 /* Now erase the top corner and draw diagonal edge */
2059 SetBkColor(hdc, corner);
2060 r1.left = r.left;
2061 r1.top = r.top;
2062 r1.right = r1.left + ROUND_CORNER_SIZE + 1;
2063 r1.bottom = r1.top + ROUND_CORNER_SIZE;
2064 ExtTextOutA(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2065 r1.left++;
2066 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPRIGHT);
2068 /* Now erase the bottom corner and draw diagonal edge */
2069 r1.left = r.left;
2070 r1.bottom = r.bottom;
2071 r1.right = r1.left + ROUND_CORNER_SIZE + 1;
2072 r1.top = r1.bottom - ROUND_CORNER_SIZE;
2073 ExtTextOutA(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2074 r1.left++;
2075 DrawEdge(hdc, &r1, EDGE_SUNKEN, BF_DIAGONAL_ENDTOPLEFT);
2078 else /* ! TCS_VERTICAL */
2080 /* These are for adjusting the drawing of a Selected tab */
2081 /* The initial values are for the normal case of non-Selected */
2082 if (iItem == infoPtr->iSelected) {
2083 /* if leftmost draw the line longer */
2084 if(selectedRect.left == 0)
2085 fillRect.left += CONTROL_BORDER_SIZEX;
2086 /* if rightmost draw the line longer */
2087 if(selectedRect.right == clRight)
2088 fillRect.right -= CONTROL_BORDER_SIZEX;
2091 if (lStyle & TCS_BOTTOM)
2093 /* Adjust both rectangles for topmost row */
2094 if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2096 fillRect.top -= 2;
2097 r.top -= 1;
2100 TRACE("<bottom> item=%d, fill=(%ld,%ld)-(%ld,%ld), edge=(%ld,%ld)-(%ld,%ld)\n",
2101 iItem,
2102 fillRect.left,fillRect.top,fillRect.right,fillRect.bottom,
2103 r.left,r.top,r.right,r.bottom);
2105 /* Clear interior */
2106 SetBkColor(hdc, bkgnd);
2107 ExtTextOutA(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2109 /* Draw rectangular edge around tab */
2110 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_BOTTOM|BF_RIGHT);
2112 /* Now erase the righthand corner and draw diagonal edge */
2113 SetBkColor(hdc, corner);
2114 r1.left = r.right - ROUND_CORNER_SIZE;
2115 r1.bottom = r.bottom;
2116 r1.right = r.right;
2117 r1.top = r1.bottom - ROUND_CORNER_SIZE - 1;
2118 ExtTextOutA(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2119 r1.bottom--;
2120 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMLEFT);
2122 /* Now erase the lefthand corner and draw diagonal edge */
2123 r1.left = r.left;
2124 r1.bottom = r.bottom;
2125 r1.right = r1.left + ROUND_CORNER_SIZE;
2126 r1.top = r1.bottom - ROUND_CORNER_SIZE - 1;
2127 ExtTextOutA(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2128 r1.bottom--;
2129 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPLEFT);
2131 if (iItem == infoPtr->iSelected)
2133 r.top += 2;
2134 r.left += 1;
2135 if (selectedRect.left == 0)
2137 r1 = r;
2138 r1.bottom = r1.top;
2139 r1.top--;
2140 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_LEFT);
2145 else
2147 /* Adjust both rectangles for bottommost row */
2148 if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2150 fillRect.bottom += 3;
2151 r.bottom += 2;
2154 TRACE("<top> item=%d, fill=(%ld,%ld)-(%ld,%ld), edge=(%ld,%ld)-(%ld,%ld)\n",
2155 iItem,
2156 fillRect.left,fillRect.top,fillRect.right,fillRect.bottom,
2157 r.left,r.top,r.right,r.bottom);
2159 /* Clear interior */
2160 SetBkColor(hdc, bkgnd);
2161 ExtTextOutA(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2163 /* Draw rectangular edge around tab */
2164 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_TOP|BF_RIGHT);
2166 /* Now erase the righthand corner and draw diagonal edge */
2167 SetBkColor(hdc, corner);
2168 r1.left = r.right - ROUND_CORNER_SIZE;
2169 r1.top = r.top;
2170 r1.right = r.right;
2171 r1.bottom = r1.top + ROUND_CORNER_SIZE + 1;
2172 ExtTextOutA(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2173 r1.top++;
2174 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMRIGHT);
2176 /* Now erase the lefthand corner and draw diagonal edge */
2177 r1.left = r.left;
2178 r1.top = r.top;
2179 r1.right = r1.left + ROUND_CORNER_SIZE;
2180 r1.bottom = r1.top + ROUND_CORNER_SIZE + 1;
2181 ExtTextOutA(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2182 r1.top++;
2183 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPRIGHT);
2188 TAB_DumpItemInternal(infoPtr, iItem);
2190 /* This modifies r to be the text rectangle. */
2191 TAB_DrawItemInterior(hwnd, hdc, iItem, &r);
2195 /******************************************************************************
2196 * TAB_DrawBorder
2198 * This method is used to draw the raised border around the tab control
2199 * "content" area.
2201 static void TAB_DrawBorder (HWND hwnd, HDC hdc)
2203 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2204 RECT rect;
2205 DWORD lStyle = GetWindowLongA(hwnd, GWL_STYLE);
2207 GetClientRect (hwnd, &rect);
2210 * Adjust for the style
2213 if (infoPtr->uNumItem)
2215 if ((lStyle & TCS_BOTTOM) && !(lStyle & TCS_VERTICAL))
2216 rect.bottom -= infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2217 else if((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
2218 rect.right -= infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2219 else if(lStyle & TCS_VERTICAL)
2220 rect.left += infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2221 else /* not TCS_VERTICAL and not TCS_BOTTOM */
2222 rect.top += infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2225 TRACE("border=(%ld,%ld)-(%ld,%ld)\n",
2226 rect.left, rect.top, rect.right, rect.bottom);
2228 DrawEdge(hdc, &rect, EDGE_RAISED, BF_SOFT|BF_RECT);
2231 /******************************************************************************
2232 * TAB_Refresh
2234 * This method repaints the tab control..
2236 static void TAB_Refresh (HWND hwnd, HDC hdc)
2238 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2239 HFONT hOldFont;
2240 INT i;
2242 if (!infoPtr->DoRedraw)
2243 return;
2245 hOldFont = SelectObject (hdc, infoPtr->hFont);
2247 if (GetWindowLongA(hwnd, GWL_STYLE) & TCS_BUTTONS)
2249 for (i = 0; i < infoPtr->uNumItem; i++)
2250 TAB_DrawItem (hwnd, hdc, i);
2252 else
2254 /* Draw all the non selected item first */
2255 for (i = 0; i < infoPtr->uNumItem; i++)
2257 if (i != infoPtr->iSelected)
2258 TAB_DrawItem (hwnd, hdc, i);
2261 /* Now, draw the border, draw it before the selected item
2262 * since the selected item overwrites part of the border. */
2263 TAB_DrawBorder (hwnd, hdc);
2265 /* Then, draw the selected item */
2266 TAB_DrawItem (hwnd, hdc, infoPtr->iSelected);
2268 /* If we haven't set the current focus yet, set it now.
2269 * Only happens when we first paint the tab controls */
2270 if (infoPtr->uFocus == -1)
2271 TAB_SetCurFocus(hwnd, infoPtr->iSelected);
2274 SelectObject (hdc, hOldFont);
2277 static DWORD
2278 TAB_GetRowCount (HWND hwnd )
2280 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2282 return infoPtr->uNumRows;
2285 static LRESULT
2286 TAB_SetRedraw (HWND hwnd, WPARAM wParam)
2288 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2290 infoPtr->DoRedraw=(BOOL) wParam;
2291 return 0;
2294 /******************************************************************************
2295 * TAB_EnsureSelectionVisible
2297 * This method will make sure that the current selection is completely
2298 * visible by scrolling until it is.
2300 static void TAB_EnsureSelectionVisible(
2301 HWND hwnd,
2302 TAB_INFO* infoPtr)
2304 INT iSelected = infoPtr->iSelected;
2305 LONG lStyle = GetWindowLongA(hwnd, GWL_STYLE);
2306 INT iOrigLeftmostVisible = infoPtr->leftmostVisible;
2308 /* set the items row to the bottommost row or topmost row depending on
2309 * style */
2310 if ((infoPtr->uNumRows > 1) && !(lStyle & TCS_BUTTONS))
2312 TAB_ITEM *selected = TAB_GetItem(infoPtr, iSelected);
2313 INT newselected;
2314 INT iTargetRow;
2316 if(lStyle & TCS_VERTICAL)
2317 newselected = selected->rect.left;
2318 else
2319 newselected = selected->rect.top;
2321 /* the target row is always (number of rows - 1)
2322 as row 0 is furthest from the clientRect */
2323 iTargetRow = infoPtr->uNumRows - 1;
2325 if (newselected != iTargetRow)
2327 UINT i;
2328 if(lStyle & TCS_VERTICAL)
2330 for (i=0; i < infoPtr->uNumItem; i++)
2332 /* move everything in the row of the selected item to the iTargetRow */
2333 TAB_ITEM *item = TAB_GetItem(infoPtr, i);
2335 if (item->rect.left == newselected )
2336 item->rect.left = iTargetRow;
2337 else
2339 if (item->rect.left > newselected)
2340 item->rect.left-=1;
2344 else
2346 for (i=0; i < infoPtr->uNumItem; i++)
2348 TAB_ITEM *item = TAB_GetItem(infoPtr, i);
2350 if (item->rect.top == newselected )
2351 item->rect.top = iTargetRow;
2352 else
2354 if (item->rect.top > newselected)
2355 item->rect.top-=1;
2359 TAB_RecalcHotTrack(hwnd, NULL, NULL, NULL);
2364 * Do the trivial cases first.
2366 if ( (!infoPtr->needsScrolling) ||
2367 (infoPtr->hwndUpDown==0) || (lStyle & TCS_VERTICAL))
2368 return;
2370 if (infoPtr->leftmostVisible >= iSelected)
2372 infoPtr->leftmostVisible = iSelected;
2374 else
2376 TAB_ITEM *selected = TAB_GetItem(infoPtr, iSelected);
2377 RECT r;
2378 INT width;
2379 UINT i;
2381 /* Calculate the part of the client area that is visible */
2382 GetClientRect(hwnd, &r);
2383 width = r.right;
2385 GetClientRect(infoPtr->hwndUpDown, &r);
2386 width -= r.right;
2388 if ((selected->rect.right -
2389 selected->rect.left) >= width )
2391 /* Special case: width of selected item is greater than visible
2392 * part of control.
2394 infoPtr->leftmostVisible = iSelected;
2396 else
2398 for (i = infoPtr->leftmostVisible; i < infoPtr->uNumItem; i++)
2400 if ((selected->rect.right - TAB_GetItem(infoPtr, i)->rect.left) < width)
2401 break;
2403 infoPtr->leftmostVisible = i;
2407 if (infoPtr->leftmostVisible != iOrigLeftmostVisible)
2408 TAB_RecalcHotTrack(hwnd, NULL, NULL, NULL);
2410 SendMessageW(infoPtr->hwndUpDown, UDM_SETPOS, 0,
2411 MAKELONG(infoPtr->leftmostVisible, 0));
2414 /******************************************************************************
2415 * TAB_InvalidateTabArea
2417 * This method will invalidate the portion of the control that contains the
2418 * tabs. It is called when the state of the control changes and needs
2419 * to be redisplayed
2421 static void TAB_InvalidateTabArea(
2422 HWND hwnd,
2423 TAB_INFO* infoPtr)
2425 RECT clientRect, rInvalidate, rAdjClient;
2426 DWORD lStyle = GetWindowLongA(hwnd, GWL_STYLE);
2427 INT lastRow = infoPtr->uNumRows - 1;
2428 RECT rect;
2430 if (lastRow < 0) return;
2432 GetClientRect(hwnd, &clientRect);
2433 rInvalidate = clientRect;
2434 rAdjClient = clientRect;
2436 TAB_AdjustRect(hwnd, 0, &rAdjClient);
2438 TAB_InternalGetItemRect(hwnd, infoPtr, infoPtr->uNumItem-1 , &rect, NULL);
2439 if ((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
2441 rInvalidate.left = rAdjClient.right;
2442 if (infoPtr->uNumRows == 1)
2443 rInvalidate.bottom = clientRect.top + rect.bottom + 2 * SELECTED_TAB_OFFSET;
2445 else if(lStyle & TCS_VERTICAL)
2447 rInvalidate.right = rAdjClient.left;
2448 if (infoPtr->uNumRows == 1)
2449 rInvalidate.bottom = clientRect.top + rect.bottom + 2 * SELECTED_TAB_OFFSET;
2451 else if (lStyle & TCS_BOTTOM)
2453 rInvalidate.top = rAdjClient.bottom;
2454 if (infoPtr->uNumRows == 1)
2455 rInvalidate.right = clientRect.left + rect.right + 2 * SELECTED_TAB_OFFSET;
2457 else
2459 rInvalidate.bottom = rAdjClient.top;
2460 if (infoPtr->uNumRows == 1)
2461 rInvalidate.right = clientRect.left + rect.right + 2 * SELECTED_TAB_OFFSET;
2464 /* Punch out the updown control */
2465 if (infoPtr->needsScrolling && (rInvalidate.right > 0)) {
2466 RECT r;
2467 GetClientRect(infoPtr->hwndUpDown, &r);
2468 if (rInvalidate.right > clientRect.right - r.left)
2469 rInvalidate.right = rInvalidate.right - (r.right - r.left);
2470 else
2471 rInvalidate.right = clientRect.right - r.left;
2474 TRACE("invalidate (%ld,%ld)-(%ld,%ld)\n",
2475 rInvalidate.left, rInvalidate.top,
2476 rInvalidate.right, rInvalidate.bottom);
2478 InvalidateRect(hwnd, &rInvalidate, TRUE);
2481 static LRESULT
2482 TAB_Paint (HWND hwnd, WPARAM wParam)
2484 HDC hdc;
2485 PAINTSTRUCT ps;
2487 if (wParam == 0)
2489 hdc = BeginPaint (hwnd, &ps);
2490 TRACE("erase %d, rect=(%ld,%ld)-(%ld,%ld)\n",
2491 ps.fErase,
2492 ps.rcPaint.left,ps.rcPaint.top,ps.rcPaint.right,ps.rcPaint.bottom);
2493 } else {
2494 hdc = (HDC)wParam;
2497 TAB_Refresh (hwnd, hdc);
2499 if(!wParam)
2500 EndPaint (hwnd, &ps);
2502 return 0;
2505 static LRESULT
2506 TAB_InsertItemAW (HWND hwnd, WPARAM wParam, LPARAM lParam, BOOL bUnicode)
2508 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2509 TAB_ITEM *item;
2510 TCITEMA *pti;
2511 INT iItem;
2512 RECT rect;
2514 GetClientRect (hwnd, &rect);
2515 TRACE("Rect: %p T %li, L %li, B %li, R %li\n", hwnd,
2516 rect.top, rect.left, rect.bottom, rect.right);
2518 pti = (TCITEMA *)lParam;
2519 iItem = (INT)wParam;
2521 if (iItem < 0) return -1;
2522 if (iItem > infoPtr->uNumItem)
2523 iItem = infoPtr->uNumItem;
2525 if (bUnicode)
2526 TAB_DumpItemExternalW((TCITEMW*)pti, iItem);
2527 else
2528 TAB_DumpItemExternalA(pti, iItem);
2531 if (infoPtr->uNumItem == 0) {
2532 infoPtr->items = Alloc (TAB_ITEM_SIZE(infoPtr));
2533 infoPtr->uNumItem++;
2534 infoPtr->iSelected = 0;
2536 else {
2537 LPBYTE oldItems = (LPBYTE)infoPtr->items;
2539 infoPtr->uNumItem++;
2540 infoPtr->items = Alloc (TAB_ITEM_SIZE(infoPtr) * infoPtr->uNumItem);
2542 /* pre insert copy */
2543 if (iItem > 0) {
2544 memcpy (infoPtr->items, oldItems,
2545 iItem * TAB_ITEM_SIZE(infoPtr));
2548 /* post insert copy */
2549 if (iItem < infoPtr->uNumItem - 1) {
2550 memcpy (TAB_GetItem(infoPtr, iItem + 1),
2551 oldItems + iItem * TAB_ITEM_SIZE(infoPtr),
2552 (infoPtr->uNumItem - iItem - 1) * TAB_ITEM_SIZE(infoPtr));
2556 if (iItem <= infoPtr->iSelected)
2557 infoPtr->iSelected++;
2559 Free (oldItems);
2562 item = TAB_GetItem(infoPtr, iItem);
2564 item->mask = pti->mask;
2565 item->pszText = NULL;
2567 if (pti->mask & TCIF_TEXT)
2569 if (bUnicode)
2570 Str_SetPtrW (&item->pszText, (WCHAR*)pti->pszText);
2571 else
2572 Str_SetPtrAtoW (&item->pszText, pti->pszText);
2575 if (pti->mask & TCIF_IMAGE)
2576 item->iImage = pti->iImage;
2577 else
2578 item->iImage = -1;
2580 if (pti->mask & TCIF_PARAM)
2581 memcpy(item->extra, &pti->lParam, infoPtr->cbInfo);
2582 else
2583 memset(item->extra, 0, infoPtr->cbInfo);
2585 TAB_SetItemBounds(hwnd);
2586 if (infoPtr->uNumItem > 1)
2587 TAB_InvalidateTabArea(hwnd, infoPtr);
2588 else
2589 InvalidateRect(hwnd, NULL, TRUE);
2591 TRACE("[%p]: added item %d %s\n",
2592 hwnd, iItem, debugstr_w(item->pszText));
2594 return iItem;
2597 static LRESULT
2598 TAB_SetItemSize (HWND hwnd, WPARAM wParam, LPARAM lParam)
2600 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2601 LONG lStyle = GetWindowLongA(hwnd, GWL_STYLE);
2602 LONG lResult = 0;
2603 BOOL bNeedPaint = FALSE;
2605 lResult = MAKELONG(infoPtr->tabWidth, infoPtr->tabHeight);
2607 /* UNDOCUMENTED: If requested Width or Height is 0 this means that program wants to use auto size. */
2608 if (lStyle & TCS_FIXEDWIDTH && (infoPtr->tabWidth != (INT)LOWORD(lParam)))
2610 infoPtr->tabWidth = max((INT)LOWORD(lParam), infoPtr->tabMinWidth);
2611 bNeedPaint = TRUE;
2614 if (infoPtr->tabHeight != (INT)HIWORD(lParam))
2616 if ((infoPtr->fHeightSet = ((INT)HIWORD(lParam) != 0)))
2617 infoPtr->tabHeight = (INT)HIWORD(lParam);
2619 bNeedPaint = TRUE;
2621 TRACE("was h=%d,w=%d, now h=%d,w=%d\n",
2622 HIWORD(lResult), LOWORD(lResult),
2623 infoPtr->tabHeight, infoPtr->tabWidth);
2625 if (bNeedPaint)
2627 TAB_SetItemBounds(hwnd);
2628 RedrawWindow(hwnd, NULL, NULL, RDW_ERASE | RDW_INVALIDATE | RDW_UPDATENOW);
2631 return lResult;
2634 static LRESULT
2635 TAB_SetMinTabWidth (HWND hwnd, LPARAM lParam)
2637 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2638 INT cx = (INT)lParam;
2639 INT oldcx;
2641 if (infoPtr) {
2642 oldcx = infoPtr->tabMinWidth;
2643 infoPtr->tabMinWidth = (cx==-1)?DEFAULT_TAB_WIDTH:cx;
2644 } else
2645 return 0;
2647 return oldcx;
2650 static LRESULT
2651 TAB_HighlightItem (HWND hwnd, WPARAM wParam, LPARAM lParam)
2653 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2654 INT iItem = (INT)wParam;
2655 BOOL fHighlight = (BOOL)LOWORD(lParam);
2657 if ((infoPtr) && (iItem>=0) && (iItem<infoPtr->uNumItem)) {
2658 if (fHighlight)
2659 TAB_GetItem(infoPtr, iItem)->dwState |= TCIS_HIGHLIGHTED;
2660 else
2661 TAB_GetItem(infoPtr, iItem)->dwState &= ~TCIS_HIGHLIGHTED;
2662 } else
2663 return FALSE;
2665 return TRUE;
2668 static LRESULT
2669 TAB_SetItemAW (HWND hwnd, WPARAM wParam, LPARAM lParam, BOOL bUnicode)
2671 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2672 TCITEMA *tabItem;
2673 TAB_ITEM *wineItem;
2674 INT iItem;
2676 iItem = (INT)wParam;
2677 tabItem = (LPTCITEMA)lParam;
2679 TRACE("%d %p\n", iItem, tabItem);
2680 if ((iItem<0) || (iItem>=infoPtr->uNumItem)) return FALSE;
2682 if (bUnicode)
2683 TAB_DumpItemExternalW((TCITEMW *)tabItem, iItem);
2684 else
2685 TAB_DumpItemExternalA(tabItem, iItem);
2687 wineItem = TAB_GetItem(infoPtr, iItem);
2689 if (tabItem->mask & TCIF_IMAGE)
2690 wineItem->iImage = tabItem->iImage;
2692 if (tabItem->mask & TCIF_PARAM)
2693 memcpy(wineItem->extra, &tabItem->lParam, infoPtr->cbInfo);
2695 if (tabItem->mask & TCIF_RTLREADING)
2696 FIXME("TCIF_RTLREADING\n");
2698 if (tabItem->mask & TCIF_STATE)
2699 wineItem->dwState = tabItem->dwState;
2701 if (tabItem->mask & TCIF_TEXT)
2703 if (wineItem->pszText)
2705 Free(wineItem->pszText);
2706 wineItem->pszText = NULL;
2708 if (bUnicode)
2709 Str_SetPtrW(&wineItem->pszText, (WCHAR*)tabItem->pszText);
2710 else
2711 Str_SetPtrAtoW(&wineItem->pszText, tabItem->pszText);
2714 /* Update and repaint tabs */
2715 TAB_SetItemBounds(hwnd);
2716 TAB_InvalidateTabArea(hwnd,infoPtr);
2718 return TRUE;
2721 static LRESULT
2722 TAB_GetItemCount (HWND hwnd, WPARAM wParam, LPARAM lParam)
2724 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2726 return infoPtr->uNumItem;
2730 static LRESULT
2731 TAB_GetItemAW (HWND hwnd, WPARAM wParam, LPARAM lParam, BOOL bUnicode)
2733 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2734 TCITEMA *tabItem;
2735 TAB_ITEM *wineItem;
2736 INT iItem;
2738 iItem = (INT)wParam;
2739 tabItem = (LPTCITEMA)lParam;
2740 TRACE("\n");
2741 if ((iItem<0) || (iItem>=infoPtr->uNumItem))
2742 return FALSE;
2744 wineItem = TAB_GetItem(infoPtr, iItem);
2746 if (tabItem->mask & TCIF_IMAGE)
2747 tabItem->iImage = wineItem->iImage;
2749 if (tabItem->mask & TCIF_PARAM)
2750 memcpy(&tabItem->lParam, wineItem->extra, infoPtr->cbInfo);
2752 if (tabItem->mask & TCIF_RTLREADING)
2753 FIXME("TCIF_RTLREADING\n");
2755 if (tabItem->mask & TCIF_STATE)
2756 tabItem->dwState = wineItem->dwState;
2758 if (tabItem->mask & TCIF_TEXT)
2760 if (bUnicode)
2761 Str_GetPtrW (wineItem->pszText, (WCHAR*)tabItem->pszText, tabItem->cchTextMax);
2762 else
2763 Str_GetPtrWtoA (wineItem->pszText, tabItem->pszText, tabItem->cchTextMax);
2766 if (bUnicode)
2767 TAB_DumpItemExternalW((TCITEMW*)tabItem, iItem);
2768 else
2769 TAB_DumpItemExternalA(tabItem, iItem);
2771 return TRUE;
2775 static LRESULT
2776 TAB_DeleteItem (HWND hwnd, WPARAM wParam, LPARAM lParam)
2778 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2779 INT iItem = (INT) wParam;
2780 BOOL bResult = FALSE;
2782 if ((iItem >= 0) && (iItem < infoPtr->uNumItem))
2784 TAB_ITEM *item = TAB_GetItem(infoPtr, iItem);
2785 LPBYTE oldItems = (LPBYTE)infoPtr->items;
2787 TAB_InvalidateTabArea(hwnd, infoPtr);
2789 if ((item->mask & TCIF_TEXT) && item->pszText)
2790 Free(item->pszText);
2792 infoPtr->uNumItem--;
2794 if (!infoPtr->uNumItem)
2796 infoPtr->items = NULL;
2797 if (infoPtr->iHotTracked >= 0)
2799 KillTimer(hwnd, TAB_HOTTRACK_TIMER);
2800 infoPtr->iHotTracked = -1;
2803 else
2805 infoPtr->items = Alloc(TAB_ITEM_SIZE(infoPtr) * infoPtr->uNumItem);
2807 if (iItem > 0)
2808 memcpy(infoPtr->items, oldItems, iItem * TAB_ITEM_SIZE(infoPtr));
2810 if (iItem < infoPtr->uNumItem)
2811 memcpy(TAB_GetItem(infoPtr, iItem),
2812 oldItems + (iItem + 1) * TAB_ITEM_SIZE(infoPtr),
2813 (infoPtr->uNumItem - iItem) * TAB_ITEM_SIZE(infoPtr));
2815 if (iItem <= infoPtr->iHotTracked)
2817 /* When tabs move left/up, the hot track item may change */
2818 FIXME("Recalc hot track");
2821 Free(oldItems);
2823 /* Readjust the selected index */
2824 if ((iItem == infoPtr->iSelected) && (iItem > 0))
2825 infoPtr->iSelected--;
2827 if (iItem < infoPtr->iSelected)
2828 infoPtr->iSelected--;
2830 if (infoPtr->uNumItem == 0)
2831 infoPtr->iSelected = -1;
2833 /* Reposition and repaint tabs */
2834 TAB_SetItemBounds(hwnd);
2836 bResult = TRUE;
2839 return bResult;
2842 static LRESULT
2843 TAB_DeleteAllItems (HWND hwnd, WPARAM wParam, LPARAM lParam)
2845 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2847 while (infoPtr->uNumItem)
2848 TAB_DeleteItem (hwnd, 0, 0);
2849 return TRUE;
2853 static LRESULT
2854 TAB_GetFont (HWND hwnd, WPARAM wParam, LPARAM lParam)
2856 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2858 TRACE("\n");
2859 return (LRESULT)infoPtr->hFont;
2862 static LRESULT
2863 TAB_SetFont (HWND hwnd, WPARAM wParam, LPARAM lParam)
2866 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2868 TRACE("%x %lx\n",wParam, lParam);
2870 infoPtr->hFont = (HFONT)wParam;
2872 TAB_SetItemBounds(hwnd);
2874 TAB_InvalidateTabArea(hwnd, infoPtr);
2876 return 0;
2880 static LRESULT
2881 TAB_GetImageList (HWND hwnd, WPARAM wParam, LPARAM lParam)
2883 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2885 TRACE("\n");
2886 return (LRESULT)infoPtr->himl;
2889 static LRESULT
2890 TAB_SetImageList (HWND hwnd, WPARAM wParam, LPARAM lParam)
2892 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2893 HIMAGELIST himlPrev;
2895 TRACE("\n");
2896 himlPrev = infoPtr->himl;
2897 infoPtr->himl= (HIMAGELIST)lParam;
2898 return (LRESULT)himlPrev;
2901 static LRESULT
2902 TAB_GetUnicodeFormat (HWND hwnd)
2904 TAB_INFO *infoPtr = TAB_GetInfoPtr (hwnd);
2905 return infoPtr->bUnicode;
2908 static LRESULT
2909 TAB_SetUnicodeFormat (HWND hwnd, WPARAM wParam)
2911 TAB_INFO *infoPtr = TAB_GetInfoPtr (hwnd);
2912 BOOL bTemp = infoPtr->bUnicode;
2914 infoPtr->bUnicode = (BOOL)wParam;
2916 return bTemp;
2919 static LRESULT
2920 TAB_Size (HWND hwnd, WPARAM wParam, LPARAM lParam)
2923 /* I'm not really sure what the following code was meant to do.
2924 This is what it is doing:
2925 When WM_SIZE is sent with SIZE_RESTORED, the control
2926 gets positioned in the top left corner.
2928 RECT parent_rect;
2929 HWND parent;
2930 UINT uPosFlags,cx,cy;
2932 uPosFlags=0;
2933 if (!wParam) {
2934 parent = GetParent (hwnd);
2935 GetClientRect(parent, &parent_rect);
2936 cx=LOWORD (lParam);
2937 cy=HIWORD (lParam);
2938 if (GetWindowLongA(hwnd, GWL_STYLE) & CCS_NORESIZE)
2939 uPosFlags |= (SWP_NOSIZE | SWP_NOMOVE);
2941 SetWindowPos (hwnd, 0, parent_rect.left, parent_rect.top,
2942 cx, cy, uPosFlags | SWP_NOZORDER);
2943 } else {
2944 FIXME("WM_SIZE flag %x %lx not handled\n", wParam, lParam);
2945 } */
2947 /* Recompute the size/position of the tabs. */
2948 TAB_SetItemBounds (hwnd);
2950 /* Force a repaint of the control. */
2951 InvalidateRect(hwnd, NULL, TRUE);
2953 return 0;
2957 static LRESULT
2958 TAB_Create (HWND hwnd, WPARAM wParam, LPARAM lParam)
2960 TAB_INFO *infoPtr;
2961 TEXTMETRICA fontMetrics;
2962 HDC hdc;
2963 HFONT hOldFont;
2964 DWORD dwStyle;
2966 infoPtr = (TAB_INFO *)Alloc (sizeof(TAB_INFO));
2968 SetWindowLongA(hwnd, 0, (DWORD)infoPtr);
2970 infoPtr->hwndNotify = ((LPCREATESTRUCTW)lParam)->hwndParent;
2971 infoPtr->uNumItem = 0;
2972 infoPtr->uNumRows = 0;
2973 infoPtr->uHItemPadding = 6;
2974 infoPtr->uVItemPadding = 3;
2975 infoPtr->uHItemPadding_s = 6;
2976 infoPtr->uVItemPadding_s = 3;
2977 infoPtr->hFont = 0;
2978 infoPtr->items = 0;
2979 infoPtr->hcurArrow = LoadCursorA (0, (LPSTR)IDC_ARROW);
2980 infoPtr->iSelected = -1;
2981 infoPtr->iHotTracked = -1;
2982 infoPtr->uFocus = -1;
2983 infoPtr->hwndToolTip = 0;
2984 infoPtr->DoRedraw = TRUE;
2985 infoPtr->needsScrolling = FALSE;
2986 infoPtr->hwndUpDown = 0;
2987 infoPtr->leftmostVisible = 0;
2988 infoPtr->fHeightSet = FALSE;
2989 infoPtr->bUnicode = IsWindowUnicode (hwnd);
2990 infoPtr->cbInfo = sizeof(LPARAM);
2992 TRACE("Created tab control, hwnd [%p]\n", hwnd);
2994 /* The tab control always has the WS_CLIPSIBLINGS style. Even
2995 if you don't specify it in CreateWindow. This is necessary in
2996 order for paint to work correctly. This follows windows behaviour. */
2997 dwStyle = GetWindowLongA(hwnd, GWL_STYLE);
2998 SetWindowLongA(hwnd, GWL_STYLE, dwStyle|WS_CLIPSIBLINGS);
3000 if (dwStyle & TCS_TOOLTIPS) {
3001 /* Create tooltip control */
3002 infoPtr->hwndToolTip =
3003 CreateWindowExA (0, TOOLTIPS_CLASSA, NULL, 0,
3004 CW_USEDEFAULT, CW_USEDEFAULT,
3005 CW_USEDEFAULT, CW_USEDEFAULT,
3006 hwnd, 0, 0, 0);
3008 /* Send NM_TOOLTIPSCREATED notification */
3009 if (infoPtr->hwndToolTip) {
3010 NMTOOLTIPSCREATED nmttc;
3012 nmttc.hdr.hwndFrom = hwnd;
3013 nmttc.hdr.idFrom = GetWindowLongPtrW(hwnd, GWLP_ID);
3014 nmttc.hdr.code = NM_TOOLTIPSCREATED;
3015 nmttc.hwndToolTips = infoPtr->hwndToolTip;
3017 SendMessageW (infoPtr->hwndNotify, WM_NOTIFY,
3018 (WPARAM)GetWindowLongPtrW(hwnd, GWLP_ID), (LPARAM)&nmttc);
3023 * We need to get text information so we need a DC and we need to select
3024 * a font.
3026 hdc = GetDC(hwnd);
3027 hOldFont = SelectObject (hdc, GetStockObject (SYSTEM_FONT));
3029 /* Use the system font to determine the initial height of a tab. */
3030 GetTextMetricsA(hdc, &fontMetrics);
3033 * Make sure there is enough space for the letters + growing the
3034 * selected item + extra space for the selected item.
3036 infoPtr->tabHeight = fontMetrics.tmHeight + SELECTED_TAB_OFFSET +
3037 ((dwStyle & TCS_BUTTONS) ? 2 : 1) *
3038 infoPtr->uVItemPadding;
3040 /* Initialize the width of a tab. */
3041 infoPtr->tabWidth = DEFAULT_TAB_WIDTH;
3042 infoPtr->tabMinWidth = 0;
3044 TRACE("tabH=%d, tabW=%d\n", infoPtr->tabHeight, infoPtr->tabWidth);
3046 SelectObject (hdc, hOldFont);
3047 ReleaseDC(hwnd, hdc);
3049 return 0;
3052 static LRESULT
3053 TAB_Destroy (HWND hwnd, WPARAM wParam, LPARAM lParam)
3055 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
3056 UINT iItem;
3058 if (!infoPtr)
3059 return 0;
3061 if (infoPtr->items) {
3062 for (iItem = 0; iItem < infoPtr->uNumItem; iItem++) {
3063 if (TAB_GetItem(infoPtr, iItem)->pszText)
3064 Free (TAB_GetItem(infoPtr, iItem)->pszText);
3066 Free (infoPtr->items);
3069 if (infoPtr->hwndToolTip)
3070 DestroyWindow (infoPtr->hwndToolTip);
3072 if (infoPtr->hwndUpDown)
3073 DestroyWindow(infoPtr->hwndUpDown);
3075 if (infoPtr->iHotTracked >= 0)
3076 KillTimer(hwnd, TAB_HOTTRACK_TIMER);
3078 Free (infoPtr);
3079 SetWindowLongA(hwnd, 0, 0);
3080 return 0;
3083 static LRESULT
3084 TAB_SetItemExtra (HWND hwnd, WPARAM wParam, LPARAM lParam)
3086 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
3087 INT cbInfo = wParam;
3089 if (!infoPtr || cbInfo <= 0)
3090 return FALSE;
3092 if (infoPtr->uNumItem)
3094 /* FIXME: MSDN says this is not allowed, but this hasn't been verified */
3095 return FALSE;
3098 infoPtr->cbInfo = cbInfo;
3099 return TRUE;
3102 static LRESULT WINAPI
3103 TAB_WindowProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
3105 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
3107 TRACE("hwnd=%p msg=%x wParam=%x lParam=%lx\n", hwnd, uMsg, wParam, lParam);
3108 if (!TAB_GetInfoPtr(hwnd) && (uMsg != WM_CREATE))
3109 return DefWindowProcW (hwnd, uMsg, wParam, lParam);
3111 switch (uMsg)
3113 case TCM_GETIMAGELIST:
3114 return TAB_GetImageList (hwnd, wParam, lParam);
3116 case TCM_SETIMAGELIST:
3117 return TAB_SetImageList (hwnd, wParam, lParam);
3119 case TCM_GETITEMCOUNT:
3120 return TAB_GetItemCount (hwnd, wParam, lParam);
3122 case TCM_GETITEMA:
3123 case TCM_GETITEMW:
3124 return TAB_GetItemAW (hwnd, wParam, lParam, uMsg == TCM_GETITEMW);
3126 case TCM_SETITEMA:
3127 case TCM_SETITEMW:
3128 return TAB_SetItemAW (hwnd, wParam, lParam, uMsg == TCM_SETITEMW);
3130 case TCM_DELETEITEM:
3131 return TAB_DeleteItem (hwnd, wParam, lParam);
3133 case TCM_DELETEALLITEMS:
3134 return TAB_DeleteAllItems (hwnd, wParam, lParam);
3136 case TCM_GETITEMRECT:
3137 return TAB_GetItemRect (hwnd, wParam, lParam);
3139 case TCM_GETCURSEL:
3140 return TAB_GetCurSel (hwnd);
3142 case TCM_HITTEST:
3143 return TAB_HitTest (hwnd, wParam, lParam);
3145 case TCM_SETCURSEL:
3146 return TAB_SetCurSel (hwnd, wParam);
3148 case TCM_INSERTITEMA:
3149 case TCM_INSERTITEMW:
3150 return TAB_InsertItemAW (hwnd, wParam, lParam, uMsg == TCM_INSERTITEMW);
3152 case TCM_SETITEMEXTRA:
3153 return TAB_SetItemExtra (hwnd, wParam, lParam);
3155 case TCM_ADJUSTRECT:
3156 return TAB_AdjustRect (hwnd, (BOOL)wParam, (LPRECT)lParam);
3158 case TCM_SETITEMSIZE:
3159 return TAB_SetItemSize (hwnd, wParam, lParam);
3161 case TCM_REMOVEIMAGE:
3162 FIXME("Unimplemented msg TCM_REMOVEIMAGE\n");
3163 return 0;
3165 case TCM_SETPADDING:
3166 return TAB_SetPadding (hwnd, wParam, lParam);
3168 case TCM_GETROWCOUNT:
3169 return TAB_GetRowCount(hwnd);
3171 case TCM_GETUNICODEFORMAT:
3172 return TAB_GetUnicodeFormat (hwnd);
3174 case TCM_SETUNICODEFORMAT:
3175 return TAB_SetUnicodeFormat (hwnd, wParam);
3177 case TCM_HIGHLIGHTITEM:
3178 return TAB_HighlightItem (hwnd, wParam, lParam);
3180 case TCM_GETTOOLTIPS:
3181 return TAB_GetToolTips (hwnd, wParam, lParam);
3183 case TCM_SETTOOLTIPS:
3184 return TAB_SetToolTips (hwnd, wParam, lParam);
3186 case TCM_GETCURFOCUS:
3187 return TAB_GetCurFocus (hwnd);
3189 case TCM_SETCURFOCUS:
3190 return TAB_SetCurFocus (hwnd, wParam);
3192 case TCM_SETMINTABWIDTH:
3193 return TAB_SetMinTabWidth(hwnd, lParam);
3195 case TCM_DESELECTALL:
3196 FIXME("Unimplemented msg TCM_DESELECTALL\n");
3197 return 0;
3199 case TCM_GETEXTENDEDSTYLE:
3200 FIXME("Unimplemented msg TCM_GETEXTENDEDSTYLE\n");
3201 return 0;
3203 case TCM_SETEXTENDEDSTYLE:
3204 FIXME("Unimplemented msg TCM_SETEXTENDEDSTYLE\n");
3205 return 0;
3207 case WM_GETFONT:
3208 return TAB_GetFont (hwnd, wParam, lParam);
3210 case WM_SETFONT:
3211 return TAB_SetFont (hwnd, wParam, lParam);
3213 case WM_CREATE:
3214 return TAB_Create (hwnd, wParam, lParam);
3216 case WM_NCDESTROY:
3217 return TAB_Destroy (hwnd, wParam, lParam);
3219 case WM_GETDLGCODE:
3220 return DLGC_WANTARROWS | DLGC_WANTCHARS;
3222 case WM_LBUTTONDOWN:
3223 return TAB_LButtonDown (hwnd, wParam, lParam);
3225 case WM_LBUTTONUP:
3226 return TAB_LButtonUp (hwnd, wParam, lParam);
3228 case WM_NOTIFY:
3229 return SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, wParam, lParam);
3231 case WM_RBUTTONDOWN:
3232 return TAB_RButtonDown (hwnd, wParam, lParam);
3234 case WM_MOUSEMOVE:
3235 return TAB_MouseMove (hwnd, wParam, lParam);
3237 case WM_PAINT:
3238 return TAB_Paint (hwnd, wParam);
3240 case WM_SIZE:
3241 return TAB_Size (hwnd, wParam, lParam);
3243 case WM_SETREDRAW:
3244 return TAB_SetRedraw (hwnd, wParam);
3246 case WM_HSCROLL:
3247 return TAB_OnHScroll(hwnd, (int)LOWORD(wParam), (int)HIWORD(wParam), (HWND)lParam);
3249 case WM_STYLECHANGED:
3250 TAB_SetItemBounds (hwnd);
3251 InvalidateRect(hwnd, NULL, TRUE);
3252 return 0;
3254 case WM_SYSCOLORCHANGE:
3255 COMCTL32_RefreshSysColors();
3256 return 0;
3258 case WM_KILLFOCUS:
3259 case WM_SETFOCUS:
3260 return TAB_FocusChanging(hwnd, uMsg, wParam, lParam);
3262 case WM_KEYUP:
3263 return TAB_KeyUp(hwnd, wParam);
3264 case WM_NCHITTEST:
3265 return TAB_NCHitTest(hwnd, lParam);
3267 default:
3268 if ((uMsg >= WM_USER) && (uMsg < WM_APP))
3269 WARN("unknown msg %04x wp=%08x lp=%08lx\n",
3270 uMsg, wParam, lParam);
3271 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
3276 VOID
3277 TAB_Register (void)
3279 WNDCLASSW wndClass;
3281 ZeroMemory (&wndClass, sizeof(WNDCLASSW));
3282 wndClass.style = CS_GLOBALCLASS | CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
3283 wndClass.lpfnWndProc = TAB_WindowProc;
3284 wndClass.cbClsExtra = 0;
3285 wndClass.cbWndExtra = sizeof(TAB_INFO *);
3286 wndClass.hCursor = LoadCursorW (0, (LPWSTR)IDC_ARROW);
3287 wndClass.hbrBackground = (HBRUSH)(COLOR_BTNFACE+1);
3288 wndClass.lpszClassName = WC_TABCONTROLW;
3290 RegisterClassW (&wndClass);
3294 VOID
3295 TAB_Unregister (void)
3297 UnregisterClassW (WC_TABCONTROLW, NULL);