- Fix tab size for TCS_OWNERDRAWFIXED style.
[wine/wine-kai.git] / dlls / comctl32 / tab.c
blobeacef5f985f2c84f7ad2e89715f8f15ec6e619f4
1 /*
2 * Tab control
4 * Copyright 1998 Anders Carlsson
5 * Copyright 1999 Alex Priem <alexp@sci.kun.nl>
6 * Copyright 1999 Francis Beaudet
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public
10 * License as published by the Free Software Foundation; either
11 * version 2.1 of the License, or (at your option) any later version.
13 * This library is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Lesser General Public License for more details.
18 * You should have received a copy of the GNU Lesser General Public
19 * License along with this library; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22 * TODO:
23 * Image list support
24 * Unicode support (under construction)
26 * FIXME:
27 * UpDown control not displayed until after a tab is clicked on
30 #include <stdarg.h>
31 #include <string.h>
33 #include "windef.h"
34 #include "winbase.h"
35 #include "wingdi.h"
36 #include "winuser.h"
37 #include "winnls.h"
38 #include "commctrl.h"
39 #include "comctl32.h"
40 #include "wine/debug.h"
41 #include <math.h>
43 WINE_DEFAULT_DEBUG_CHANNEL(tab);
45 typedef struct
47 UINT mask;
48 DWORD dwState;
49 LPWSTR pszText;
50 INT iImage;
51 LPARAM lParam;
52 RECT rect; /* bounding rectangle of the item relative to the
53 * leftmost item (the leftmost item, 0, would have a
54 * "left" member of 0 in this rectangle)
56 * additionally the top member hold the row number
57 * and bottom is unused and should be 0 */
58 } TAB_ITEM;
60 typedef struct
62 UINT uNumItem; /* number of tab items */
63 UINT uNumRows; /* number of tab rows */
64 INT tabHeight; /* height of the tab row */
65 INT tabWidth; /* width of tabs */
66 INT tabMinWidth; /* minimum width of items */
67 USHORT uHItemPadding; /* amount of horizontal padding, in pixels */
68 USHORT uVItemPadding; /* amount of vertical padding, in pixels */
69 HFONT hFont; /* handle to the current font */
70 HCURSOR hcurArrow; /* handle to the current cursor */
71 HIMAGELIST himl; /* handle to a image list (may be 0) */
72 HWND hwndToolTip; /* handle to tab's tooltip */
73 INT leftmostVisible; /* Used for scrolling, this member contains
74 * the index of the first visible item */
75 INT iSelected; /* the currently selected item */
76 INT iHotTracked; /* the highlighted item under the mouse */
77 INT uFocus; /* item which has the focus */
78 TAB_ITEM* items; /* pointer to an array of TAB_ITEM's */
79 BOOL DoRedraw; /* flag for redrawing when tab contents is changed*/
80 BOOL needsScrolling; /* TRUE if the size of the tabs is greater than
81 * the size of the control */
82 BOOL fHeightSet; /* was the height of the tabs explicitly set? */
83 BOOL bUnicode; /* Unicode control? */
84 HWND hwndUpDown; /* Updown control used for scrolling */
85 } TAB_INFO;
87 /******************************************************************************
88 * Positioning constants
90 #define SELECTED_TAB_OFFSET 2
91 #define ROUND_CORNER_SIZE 2
92 #define DISPLAY_AREA_PADDINGX 2
93 #define DISPLAY_AREA_PADDINGY 2
94 #define CONTROL_BORDER_SIZEX 2
95 #define CONTROL_BORDER_SIZEY 2
96 #define BUTTON_SPACINGX 3
97 #define BUTTON_SPACINGY 4
98 #define FLAT_BTN_SPACINGX 8
99 #define DEFAULT_TAB_WIDTH 96
101 #define TAB_GetInfoPtr(hwnd) ((TAB_INFO *)GetWindowLongA(hwnd,0))
103 /******************************************************************************
104 * Hot-tracking timer constants
106 #define TAB_HOTTRACK_TIMER 1
107 #define TAB_HOTTRACK_TIMER_INTERVAL 100 /* milliseconds */
109 /******************************************************************************
110 * Prototypes
112 static void TAB_Refresh (HWND hwnd, HDC hdc);
113 static void TAB_InvalidateTabArea(HWND hwnd, TAB_INFO* infoPtr);
114 static void TAB_EnsureSelectionVisible(HWND hwnd, TAB_INFO* infoPtr);
115 static void TAB_DrawItem(HWND hwnd, HDC hdc, INT iItem);
116 static void TAB_DrawItemInterior(HWND hwnd, HDC hdc, INT iItem, RECT* drawRect);
118 static BOOL
119 TAB_SendSimpleNotify (HWND hwnd, UINT code)
121 NMHDR nmhdr;
123 nmhdr.hwndFrom = hwnd;
124 nmhdr.idFrom = GetWindowLongA(hwnd, GWL_ID);
125 nmhdr.code = code;
127 return (BOOL) SendMessageA (GetParent (hwnd), WM_NOTIFY,
128 (WPARAM) nmhdr.idFrom, (LPARAM) &nmhdr);
131 static VOID
132 TAB_RelayEvent (HWND hwndTip, HWND hwndMsg, UINT uMsg,
133 WPARAM wParam, LPARAM lParam)
135 MSG msg;
137 msg.hwnd = hwndMsg;
138 msg.message = uMsg;
139 msg.wParam = wParam;
140 msg.lParam = lParam;
141 msg.time = GetMessageTime ();
142 msg.pt.x = LOWORD(GetMessagePos ());
143 msg.pt.y = HIWORD(GetMessagePos ());
145 SendMessageA (hwndTip, TTM_RELAYEVENT, 0, (LPARAM)&msg);
148 static void
149 TAB_DumpItemExternalA(TCITEMA *pti, UINT iItem)
151 if (TRACE_ON(tab)) {
152 TRACE("external tab %d, mask=0x%08x, dwState=0x%08x, dwStateMask=0x%08x, cchTextMax=0x%08x\n",
153 iItem, pti->mask, pti->dwState, pti->dwStateMask, pti->cchTextMax);
154 TRACE("external tab %d, iImage=%d, lParam=0x%08lx, pszTextA=%s\n",
155 iItem, pti->iImage, pti->lParam, debugstr_a(pti->pszText));
160 static void
161 TAB_DumpItemExternalW(TCITEMW *pti, UINT iItem)
163 if (TRACE_ON(tab)) {
164 TRACE("external tab %d, mask=0x%08x, dwState=0x%08lx, dwStateMask=0x%08lx, cchTextMax=0x%08x\n",
165 iItem, pti->mask, pti->dwState, pti->dwStateMask, pti->cchTextMax);
166 TRACE("external tab %d, iImage=%d, lParam=0x%08lx, pszTextW=%s\n",
167 iItem, pti->iImage, pti->lParam, debugstr_w(pti->pszText));
171 static void
172 TAB_DumpItemInternal(TAB_INFO *infoPtr, UINT iItem)
174 if (TRACE_ON(tab)) {
175 TAB_ITEM *ti;
177 ti = &infoPtr->items[iItem];
178 TRACE("tab %d, mask=0x%08x, dwState=0x%08lx, pszText=%s, iImage=%d\n",
179 iItem, ti->mask, ti->dwState, debugstr_w(ti->pszText),
180 ti->iImage);
181 TRACE("tab %d, lParam=0x%08lx, rect.left=%ld, rect.top(row)=%ld\n",
182 iItem, ti->lParam, ti->rect.left, ti->rect.top);
186 static LRESULT
187 TAB_GetCurSel (HWND hwnd)
189 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
191 return infoPtr->iSelected;
194 static LRESULT
195 TAB_GetCurFocus (HWND hwnd)
197 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
199 return infoPtr->uFocus;
202 static LRESULT
203 TAB_GetToolTips (HWND hwnd, WPARAM wParam, LPARAM lParam)
205 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
207 if (infoPtr == NULL) return 0;
208 return (LRESULT)infoPtr->hwndToolTip;
211 static LRESULT
212 TAB_SetCurSel (HWND hwnd,WPARAM wParam)
214 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
215 INT iItem = (INT)wParam;
216 INT prevItem;
218 prevItem = -1;
219 if ((iItem >= 0) && (iItem < infoPtr->uNumItem)) {
220 prevItem=infoPtr->iSelected;
221 infoPtr->iSelected=iItem;
222 TAB_EnsureSelectionVisible(hwnd, infoPtr);
223 TAB_InvalidateTabArea(hwnd, infoPtr);
225 return prevItem;
228 static LRESULT
229 TAB_SetCurFocus (HWND hwnd,WPARAM wParam)
231 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
232 INT iItem=(INT) wParam;
234 if ((iItem < 0) || (iItem >= infoPtr->uNumItem)) return 0;
236 if (GetWindowLongA(hwnd, GWL_STYLE) & TCS_BUTTONS) {
237 FIXME("Should set input focus\n");
238 } else {
239 int oldFocus = infoPtr->uFocus;
240 if (infoPtr->iSelected != iItem || infoPtr->uFocus == -1 ) {
241 infoPtr->uFocus = iItem;
242 if (oldFocus != -1) {
243 if (TAB_SendSimpleNotify(hwnd, TCN_SELCHANGING)!=TRUE) {
244 infoPtr->iSelected = iItem;
245 TAB_SendSimpleNotify(hwnd, TCN_SELCHANGE);
247 else
248 infoPtr->iSelected = iItem;
249 TAB_EnsureSelectionVisible(hwnd, infoPtr);
250 TAB_InvalidateTabArea(hwnd, infoPtr);
254 return 0;
257 static LRESULT
258 TAB_SetToolTips (HWND hwnd, WPARAM wParam, LPARAM lParam)
260 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
262 if (infoPtr == NULL) return 0;
263 infoPtr->hwndToolTip = (HWND)wParam;
264 return 0;
267 static LRESULT
268 TAB_SetPadding (HWND hwnd, WPARAM wParam, LPARAM lParam)
270 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
272 if (infoPtr == NULL) return 0;
273 infoPtr->uHItemPadding=LOWORD(lParam);
274 infoPtr->uVItemPadding=HIWORD(lParam);
275 return 0;
278 /******************************************************************************
279 * TAB_InternalGetItemRect
281 * This method will calculate the rectangle representing a given tab item in
282 * client coordinates. This method takes scrolling into account.
284 * This method returns TRUE if the item is visible in the window and FALSE
285 * if it is completely outside the client area.
287 static BOOL TAB_InternalGetItemRect(
288 HWND hwnd,
289 TAB_INFO* infoPtr,
290 INT itemIndex,
291 RECT* itemRect,
292 RECT* selectedRect)
294 RECT tmpItemRect,clientRect;
295 LONG lStyle = GetWindowLongA(hwnd, GWL_STYLE);
297 /* Perform a sanity check and a trivial visibility check. */
298 if ( (infoPtr->uNumItem <= 0) ||
299 (itemIndex >= infoPtr->uNumItem) ||
300 (!((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)) && (itemIndex < infoPtr->leftmostVisible)) )
301 return FALSE;
304 * Avoid special cases in this procedure by assigning the "out"
305 * parameters if the caller didn't supply them
307 if (itemRect == NULL)
308 itemRect = &tmpItemRect;
310 /* Retrieve the unmodified item rect. */
311 *itemRect = infoPtr->items[itemIndex].rect;
313 /* calculate the times bottom and top based on the row */
314 GetClientRect(hwnd, &clientRect);
316 if ((lStyle & TCS_BOTTOM) && !(lStyle & TCS_VERTICAL))
318 itemRect->bottom = clientRect.bottom -
319 SELECTED_TAB_OFFSET -
320 itemRect->top * (infoPtr->tabHeight - 2) -
321 ((lStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : 0);
323 itemRect->top = clientRect.bottom -
324 infoPtr->tabHeight -
325 itemRect->top * (infoPtr->tabHeight - 2) -
326 ((lStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : 0);
328 else if((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
330 itemRect->right = clientRect.right - SELECTED_TAB_OFFSET - itemRect->left * (infoPtr->tabHeight - 2) -
331 ((lStyle & TCS_BUTTONS) ? itemRect->left * BUTTON_SPACINGY : 0);
332 itemRect->left = clientRect.right - infoPtr->tabHeight - itemRect->left * (infoPtr->tabHeight - 2) -
333 ((lStyle & TCS_BUTTONS) ? itemRect->left * BUTTON_SPACINGY : 0);
335 else if((lStyle & TCS_VERTICAL) && !(lStyle & TCS_BOTTOM))
337 itemRect->right = clientRect.left + infoPtr->tabHeight + itemRect->left * (infoPtr->tabHeight - 2) +
338 ((lStyle & TCS_BUTTONS) ? itemRect->left * BUTTON_SPACINGY : 0);
339 itemRect->left = clientRect.left + SELECTED_TAB_OFFSET + itemRect->left * (infoPtr->tabHeight - 2) +
340 ((lStyle & TCS_BUTTONS) ? itemRect->left * BUTTON_SPACINGY : 0);
342 else if(!(lStyle & TCS_VERTICAL) && !(lStyle & TCS_BOTTOM)) /* not TCS_BOTTOM and not TCS_VERTICAL */
344 itemRect->bottom = clientRect.top +
345 infoPtr->tabHeight +
346 itemRect->top * (infoPtr->tabHeight - 2) +
347 ((lStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : SELECTED_TAB_OFFSET);
348 itemRect->top = clientRect.top +
349 itemRect->top * (infoPtr->tabHeight - 2) +
350 ((lStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : SELECTED_TAB_OFFSET);
354 * "scroll" it to make sure the item at the very left of the
355 * tab control is the leftmost visible tab.
357 if(lStyle & TCS_VERTICAL)
359 OffsetRect(itemRect,
361 -(clientRect.bottom - infoPtr->items[infoPtr->leftmostVisible].rect.bottom));
364 * Move the rectangle so the first item is slightly offset from
365 * the bottom of the tab control.
367 OffsetRect(itemRect,
369 -SELECTED_TAB_OFFSET);
371 } else
373 OffsetRect(itemRect,
374 -infoPtr->items[infoPtr->leftmostVisible].rect.left,
378 * Move the rectangle so the first item is slightly offset from
379 * the left of the tab control.
381 OffsetRect(itemRect,
382 SELECTED_TAB_OFFSET,
385 TRACE("item %d tab h=%d, rect=(%ld,%ld)-(%ld,%ld)\n",
386 itemIndex, infoPtr->tabHeight,
387 itemRect->left, itemRect->top, itemRect->right, itemRect->bottom);
389 /* Now, calculate the position of the item as if it were selected. */
390 if (selectedRect!=NULL)
392 CopyRect(selectedRect, itemRect);
394 /* The rectangle of a selected item is a bit wider. */
395 if(lStyle & TCS_VERTICAL)
396 InflateRect(selectedRect, 0, SELECTED_TAB_OFFSET);
397 else
398 InflateRect(selectedRect, SELECTED_TAB_OFFSET, 0);
400 /* If it also a bit higher. */
401 if ((lStyle & TCS_BOTTOM) && !(lStyle & TCS_VERTICAL))
403 selectedRect->top -= 2; /* the border is thicker on the bottom */
404 selectedRect->bottom += SELECTED_TAB_OFFSET;
406 else if((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
408 selectedRect->left -= 2; /* the border is thicker on the right */
409 selectedRect->right += SELECTED_TAB_OFFSET;
411 else if(lStyle & TCS_VERTICAL)
413 selectedRect->left -= SELECTED_TAB_OFFSET;
414 selectedRect->right += 1;
416 else
418 selectedRect->top -= SELECTED_TAB_OFFSET;
419 selectedRect->bottom += 1;
423 return TRUE;
426 static BOOL TAB_GetItemRect(HWND hwnd, WPARAM wParam, LPARAM lParam)
428 return TAB_InternalGetItemRect(hwnd, TAB_GetInfoPtr(hwnd), (INT)wParam,
429 (LPRECT)lParam, (LPRECT)NULL);
432 /******************************************************************************
433 * TAB_KeyUp
435 * This method is called to handle keyboard input
437 static LRESULT TAB_KeyUp(
438 HWND hwnd,
439 WPARAM keyCode)
441 TAB_INFO* infoPtr = TAB_GetInfoPtr(hwnd);
442 int newItem = -1;
444 switch (keyCode)
446 case VK_LEFT:
447 newItem = infoPtr->uFocus - 1;
448 break;
449 case VK_RIGHT:
450 newItem = infoPtr->uFocus + 1;
451 break;
455 * If we changed to a valid item, change the selection
457 if ((newItem >= 0) &&
458 (newItem < infoPtr->uNumItem) &&
459 (infoPtr->uFocus != newItem))
461 if (!TAB_SendSimpleNotify(hwnd, TCN_SELCHANGING))
463 infoPtr->iSelected = newItem;
464 infoPtr->uFocus = newItem;
465 TAB_SendSimpleNotify(hwnd, TCN_SELCHANGE);
467 TAB_EnsureSelectionVisible(hwnd, infoPtr);
468 TAB_InvalidateTabArea(hwnd, infoPtr);
472 return 0;
475 /******************************************************************************
476 * TAB_FocusChanging
478 * This method is called whenever the focus goes in or out of this control
479 * it is used to update the visual state of the control.
481 static LRESULT TAB_FocusChanging(
482 HWND hwnd,
483 UINT uMsg,
484 WPARAM wParam,
485 LPARAM lParam)
487 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
488 RECT selectedRect;
489 BOOL isVisible;
492 * Get the rectangle for the item.
494 isVisible = TAB_InternalGetItemRect(hwnd,
495 infoPtr,
496 infoPtr->uFocus,
497 NULL,
498 &selectedRect);
501 * If the rectangle is not completely invisible, invalidate that
502 * portion of the window.
504 if (isVisible)
506 TRACE("invalidate (%ld,%ld)-(%ld,%ld)\n",
507 selectedRect.left,selectedRect.top,
508 selectedRect.right,selectedRect.bottom);
509 InvalidateRect(hwnd, &selectedRect, TRUE);
513 * Don't otherwise disturb normal behavior.
515 return DefWindowProcA (hwnd, uMsg, wParam, lParam);
518 static INT TAB_InternalHitTest (
519 HWND hwnd,
520 TAB_INFO* infoPtr,
521 POINT pt,
522 UINT* flags)
525 RECT rect;
526 INT iCount;
528 for (iCount = 0; iCount < infoPtr->uNumItem; iCount++)
530 TAB_InternalGetItemRect(hwnd, infoPtr, iCount, &rect, NULL);
532 if (PtInRect(&rect, pt))
534 *flags = TCHT_ONITEM;
535 return iCount;
539 *flags = TCHT_NOWHERE;
540 return -1;
543 static LRESULT
544 TAB_HitTest (HWND hwnd, WPARAM wParam, LPARAM lParam)
546 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
547 LPTCHITTESTINFO lptest = (LPTCHITTESTINFO) lParam;
549 return TAB_InternalHitTest (hwnd, infoPtr, lptest->pt, &lptest->flags);
552 /******************************************************************************
553 * TAB_NCHitTest
555 * Napster v2b5 has a tab control for its main navigation which has a client
556 * area that covers the whole area of the dialog pages.
557 * That's why it receives all msgs for that area and the underlying dialog ctrls
558 * are dead.
559 * So I decided that we should handle WM_NCHITTEST here and return
560 * HTTRANSPARENT if we don't hit the tab control buttons.
561 * FIXME: WM_NCHITTEST handling correct ? Fix it if you know that Windows
562 * doesn't do it that way. Maybe depends on tab control styles ?
564 static LRESULT
565 TAB_NCHitTest (HWND hwnd, LPARAM lParam)
567 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
568 POINT pt;
569 UINT dummyflag;
571 pt.x = LOWORD(lParam);
572 pt.y = HIWORD(lParam);
573 ScreenToClient(hwnd, &pt);
575 if (TAB_InternalHitTest(hwnd, infoPtr, pt, &dummyflag) == -1)
576 return HTTRANSPARENT;
577 else
578 return HTCLIENT;
581 static LRESULT
582 TAB_LButtonDown (HWND hwnd, WPARAM wParam, LPARAM lParam)
584 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
585 POINT pt;
586 INT newItem, dummy;
588 if (infoPtr->hwndToolTip)
589 TAB_RelayEvent (infoPtr->hwndToolTip, hwnd,
590 WM_LBUTTONDOWN, wParam, lParam);
592 if (GetWindowLongA(hwnd, GWL_STYLE) & TCS_FOCUSONBUTTONDOWN ) {
593 SetFocus (hwnd);
596 if (infoPtr->hwndToolTip)
597 TAB_RelayEvent (infoPtr->hwndToolTip, hwnd,
598 WM_LBUTTONDOWN, wParam, lParam);
600 pt.x = (INT)LOWORD(lParam);
601 pt.y = (INT)HIWORD(lParam);
603 newItem = TAB_InternalHitTest (hwnd, infoPtr, pt, &dummy);
605 TRACE("On Tab, item %d\n", newItem);
607 if ((newItem != -1) && (infoPtr->iSelected != newItem))
609 if (TAB_SendSimpleNotify(hwnd, TCN_SELCHANGING) != TRUE)
611 infoPtr->iSelected = newItem;
612 infoPtr->uFocus = newItem;
613 TAB_SendSimpleNotify(hwnd, TCN_SELCHANGE);
615 TAB_EnsureSelectionVisible(hwnd, infoPtr);
617 TAB_InvalidateTabArea(hwnd, infoPtr);
620 return 0;
623 static LRESULT
624 TAB_LButtonUp (HWND hwnd, WPARAM wParam, LPARAM lParam)
626 TAB_SendSimpleNotify(hwnd, NM_CLICK);
628 return 0;
631 static LRESULT
632 TAB_RButtonDown (HWND hwnd, WPARAM wParam, LPARAM lParam)
634 TAB_SendSimpleNotify(hwnd, NM_RCLICK);
635 return 0;
638 /******************************************************************************
639 * TAB_DrawLoneItemInterior
641 * This calls TAB_DrawItemInterior. However, TAB_DrawItemInterior is normally
642 * called by TAB_DrawItem which is normally called by TAB_Refresh which sets
643 * up the device context and font. This routine does the same setup but
644 * only calls TAB_DrawItemInterior for the single specified item.
646 static void
647 TAB_DrawLoneItemInterior(HWND hwnd, TAB_INFO* infoPtr, int iItem)
649 HDC hdc = GetDC(hwnd);
650 HFONT hOldFont = SelectObject(hdc, infoPtr->hFont);
651 TAB_DrawItemInterior(hwnd, hdc, iItem, NULL);
652 SelectObject(hdc, hOldFont);
653 ReleaseDC(hwnd, hdc);
656 /******************************************************************************
657 * TAB_HotTrackTimerProc
659 * When a mouse-move event causes a tab to be highlighted (hot-tracking), a
660 * timer is setup so we can check if the mouse is moved out of our window.
661 * (We don't get an event when the mouse leaves, the mouse-move events just
662 * stop being delivered to our window and just start being delivered to
663 * another window.) This function is called when the timer triggers so
664 * we can check if the mouse has left our window. If so, we un-highlight
665 * the hot-tracked tab.
667 static VOID CALLBACK
668 TAB_HotTrackTimerProc
670 HWND hwnd, /* handle of window for timer messages */
671 UINT uMsg, /* WM_TIMER message */
672 UINT idEvent, /* timer identifier */
673 DWORD dwTime /* current system time */
676 TAB_INFO* infoPtr = TAB_GetInfoPtr(hwnd);
678 if (infoPtr != NULL && infoPtr->iHotTracked >= 0)
680 POINT pt;
683 ** If we can't get the cursor position, or if the cursor is outside our
684 ** window, we un-highlight the hot-tracked tab. Note that the cursor is
685 ** "outside" even if it is within our bounding rect if another window
686 ** overlaps. Note also that the case where the cursor stayed within our
687 ** window but has moved off the hot-tracked tab will be handled by the
688 ** WM_MOUSEMOVE event.
690 if (!GetCursorPos(&pt) || WindowFromPoint(pt) != hwnd)
692 /* Redraw iHotTracked to look normal */
693 INT iRedraw = infoPtr->iHotTracked;
694 infoPtr->iHotTracked = -1;
695 TAB_DrawLoneItemInterior(hwnd, infoPtr, iRedraw);
697 /* Kill this timer */
698 KillTimer(hwnd, TAB_HOTTRACK_TIMER);
703 /******************************************************************************
704 * TAB_RecalcHotTrack
706 * If a tab control has the TCS_HOTTRACK style, then the tab under the mouse
707 * should be highlighted. This function determines which tab in a tab control,
708 * if any, is under the mouse and records that information. The caller may
709 * supply output parameters to receive the item number of the tab item which
710 * was highlighted but isn't any longer and of the tab item which is now
711 * highlighted but wasn't previously. The caller can use this information to
712 * selectively redraw those tab items.
714 * If the caller has a mouse position, it can supply it through the pos
715 * parameter. For example, TAB_MouseMove does this. Otherwise, the caller
716 * supplies NULL and this function determines the current mouse position
717 * itself.
719 static void
720 TAB_RecalcHotTrack
722 HWND hwnd,
723 const LPARAM* pos,
724 int* out_redrawLeave,
725 int* out_redrawEnter
728 TAB_INFO* infoPtr = TAB_GetInfoPtr(hwnd);
730 int item = -1;
733 if (out_redrawLeave != NULL)
734 *out_redrawLeave = -1;
735 if (out_redrawEnter != NULL)
736 *out_redrawEnter = -1;
738 if (GetWindowLongA(hwnd, GWL_STYLE) & TCS_HOTTRACK)
740 POINT pt;
741 UINT flags;
743 if (pos == NULL)
745 GetCursorPos(&pt);
746 ScreenToClient(hwnd, &pt);
748 else
750 pt.x = LOWORD(*pos);
751 pt.y = HIWORD(*pos);
754 item = TAB_InternalHitTest(hwnd, infoPtr, pt, &flags);
757 if (item != infoPtr->iHotTracked)
759 if (infoPtr->iHotTracked >= 0)
761 /* Mark currently hot-tracked to be redrawn to look normal */
762 if (out_redrawLeave != NULL)
763 *out_redrawLeave = infoPtr->iHotTracked;
765 if (item < 0)
767 /* Kill timer which forces recheck of mouse pos */
768 KillTimer(hwnd, TAB_HOTTRACK_TIMER);
771 else
773 /* Start timer so we recheck mouse pos */
774 UINT timerID = SetTimer
776 hwnd,
777 TAB_HOTTRACK_TIMER,
778 TAB_HOTTRACK_TIMER_INTERVAL,
779 TAB_HotTrackTimerProc
782 if (timerID == 0)
783 return; /* Hot tracking not available */
786 infoPtr->iHotTracked = item;
788 if (item >= 0)
790 /* Mark new hot-tracked to be redrawn to look highlighted */
791 if (out_redrawEnter != NULL)
792 *out_redrawEnter = item;
797 /******************************************************************************
798 * TAB_MouseMove
800 * Handles the mouse-move event. Updates tooltips. Updates hot-tracking.
802 static LRESULT
803 TAB_MouseMove (HWND hwnd, WPARAM wParam, LPARAM lParam)
805 int redrawLeave;
806 int redrawEnter;
808 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
810 if (infoPtr->hwndToolTip)
811 TAB_RelayEvent (infoPtr->hwndToolTip, hwnd,
812 WM_LBUTTONDOWN, wParam, lParam);
814 /* Determine which tab to highlight. Redraw tabs which change highlight
815 ** status. */
816 TAB_RecalcHotTrack(hwnd, &lParam, &redrawLeave, &redrawEnter);
818 if (redrawLeave != -1)
819 TAB_DrawLoneItemInterior(hwnd, infoPtr, redrawLeave);
820 if (redrawEnter != -1)
821 TAB_DrawLoneItemInterior(hwnd, infoPtr, redrawEnter);
823 return 0;
826 /******************************************************************************
827 * TAB_AdjustRect
829 * Calculates the tab control's display area given the window rectangle or
830 * the window rectangle given the requested display rectangle.
832 static LRESULT TAB_AdjustRect(
833 HWND hwnd,
834 WPARAM fLarger,
835 LPRECT prc)
837 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
838 DWORD lStyle = GetWindowLongA(hwnd, GWL_STYLE);
840 if(lStyle & TCS_VERTICAL)
842 if (fLarger) /* Go from display rectangle */
844 /* Add the height of the tabs. */
845 if (lStyle & TCS_BOTTOM)
846 prc->right += (infoPtr->tabHeight - 2) * infoPtr->uNumRows + 2;
847 else
848 prc->left -= (infoPtr->tabHeight - 2) * infoPtr->uNumRows + 2;
850 /* FIXME: not sure if these InflateRect's need to have different values for TCS_VERTICAL */
851 /* Inflate the rectangle for the padding */
852 InflateRect(prc, DISPLAY_AREA_PADDINGX, DISPLAY_AREA_PADDINGY);
854 /* Inflate for the border */
855 InflateRect(prc, CONTROL_BORDER_SIZEX, CONTROL_BORDER_SIZEX);
857 else /* Go from window rectangle. */
859 /* FIXME: not sure if these InflateRect's need to have different values for TCS_VERTICAL */
860 /* Deflate the rectangle for the border */
861 InflateRect(prc, -CONTROL_BORDER_SIZEX, -CONTROL_BORDER_SIZEX);
863 /* Deflate the rectangle for the padding */
864 InflateRect(prc, -DISPLAY_AREA_PADDINGX, -DISPLAY_AREA_PADDINGY);
866 /* Remove the height of the tabs. */
867 if (lStyle & TCS_BOTTOM)
868 prc->right -= (infoPtr->tabHeight - 2) * infoPtr->uNumRows + 2;
869 else
870 prc->left += (infoPtr->tabHeight - 2) * infoPtr->uNumRows + 2;
873 else {
874 if (fLarger) /* Go from display rectangle */
876 /* Add the height of the tabs. */
877 if (lStyle & TCS_BOTTOM)
878 prc->bottom += (infoPtr->tabHeight - 2) * infoPtr->uNumRows + 2;
879 else
880 prc->top -= (infoPtr->tabHeight - 2) * infoPtr->uNumRows + 2;
882 /* Inflate the rectangle for the padding */
883 InflateRect(prc, DISPLAY_AREA_PADDINGX, DISPLAY_AREA_PADDINGY);
885 /* Inflate for the border */
886 InflateRect(prc, CONTROL_BORDER_SIZEX, CONTROL_BORDER_SIZEX);
888 else /* Go from window rectangle. */
890 /* Deflate the rectangle for the border */
891 InflateRect(prc, -CONTROL_BORDER_SIZEX, -CONTROL_BORDER_SIZEX);
893 /* Deflate the rectangle for the padding */
894 InflateRect(prc, -DISPLAY_AREA_PADDINGX, -DISPLAY_AREA_PADDINGY);
896 /* Remove the height of the tabs. */
897 if (lStyle & TCS_BOTTOM)
898 prc->bottom -= (infoPtr->tabHeight - 2) * infoPtr->uNumRows + 2;
899 else
900 prc->top += (infoPtr->tabHeight - 2) * infoPtr->uNumRows + 2;
904 return 0;
907 /******************************************************************************
908 * TAB_OnHScroll
910 * This method will handle the notification from the scroll control and
911 * perform the scrolling operation on the tab control.
913 static LRESULT TAB_OnHScroll(
914 HWND hwnd,
915 int nScrollCode,
916 int nPos,
917 HWND hwndScroll)
919 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
921 if(nScrollCode == SB_THUMBPOSITION && nPos != infoPtr->leftmostVisible)
923 if(nPos < infoPtr->leftmostVisible)
924 infoPtr->leftmostVisible--;
925 else
926 infoPtr->leftmostVisible++;
928 TAB_RecalcHotTrack(hwnd, NULL, NULL, NULL);
929 TAB_InvalidateTabArea(hwnd, infoPtr);
930 SendMessageA(infoPtr->hwndUpDown, UDM_SETPOS, 0,
931 MAKELONG(infoPtr->leftmostVisible, 0));
934 return 0;
937 /******************************************************************************
938 * TAB_SetupScrolling
940 * This method will check the current scrolling state and make sure the
941 * scrolling control is displayed (or not).
943 static void TAB_SetupScrolling(
944 HWND hwnd,
945 TAB_INFO* infoPtr,
946 const RECT* clientRect)
948 INT maxRange = 0;
949 DWORD lStyle = GetWindowLongA(hwnd, GWL_STYLE);
951 if (infoPtr->needsScrolling)
953 RECT controlPos;
954 INT vsize, tabwidth;
957 * Calculate the position of the scroll control.
959 if(lStyle & TCS_VERTICAL)
961 controlPos.right = clientRect->right;
962 controlPos.left = controlPos.right - 2 * GetSystemMetrics(SM_CXHSCROLL);
964 if (lStyle & TCS_BOTTOM)
966 controlPos.top = clientRect->bottom - infoPtr->tabHeight;
967 controlPos.bottom = controlPos.top + GetSystemMetrics(SM_CYHSCROLL);
969 else
971 controlPos.bottom = clientRect->top + infoPtr->tabHeight;
972 controlPos.top = controlPos.bottom - GetSystemMetrics(SM_CYHSCROLL);
975 else
977 controlPos.right = clientRect->right;
978 controlPos.left = controlPos.right - 2 * GetSystemMetrics(SM_CXHSCROLL);
980 if (lStyle & TCS_BOTTOM)
982 controlPos.top = clientRect->bottom - infoPtr->tabHeight;
983 controlPos.bottom = controlPos.top + GetSystemMetrics(SM_CYHSCROLL);
985 else
987 controlPos.bottom = clientRect->top + infoPtr->tabHeight;
988 controlPos.top = controlPos.bottom - GetSystemMetrics(SM_CYHSCROLL);
993 * If we don't have a scroll control yet, we want to create one.
994 * If we have one, we want to make sure it's positioned properly.
996 if (infoPtr->hwndUpDown==0)
998 infoPtr->hwndUpDown = CreateWindowA("msctls_updown32",
1000 WS_VISIBLE | WS_CHILD | UDS_HORZ,
1001 controlPos.left, controlPos.top,
1002 controlPos.right - controlPos.left,
1003 controlPos.bottom - controlPos.top,
1004 hwnd,
1005 NULL,
1006 NULL,
1007 NULL);
1009 else
1011 SetWindowPos(infoPtr->hwndUpDown,
1012 NULL,
1013 controlPos.left, controlPos.top,
1014 controlPos.right - controlPos.left,
1015 controlPos.bottom - controlPos.top,
1016 SWP_SHOWWINDOW | SWP_NOZORDER);
1019 /* Now calculate upper limit of the updown control range.
1020 * We do this by calculating how many tabs will be offscreen when the
1021 * last tab is visible.
1023 if(infoPtr->uNumItem)
1025 vsize = clientRect->right - (controlPos.right - controlPos.left + 1);
1026 maxRange = infoPtr->uNumItem;
1027 tabwidth = infoPtr->items[maxRange - 1].rect.right;
1029 for(; maxRange > 0; maxRange--)
1031 if(tabwidth - infoPtr->items[maxRange - 1].rect.left > vsize)
1032 break;
1035 if(maxRange == infoPtr->uNumItem)
1036 maxRange--;
1039 else
1041 /* If we once had a scroll control... hide it */
1042 if (infoPtr->hwndUpDown!=0)
1043 ShowWindow(infoPtr->hwndUpDown, SW_HIDE);
1045 if (infoPtr->hwndUpDown)
1046 SendMessageA(infoPtr->hwndUpDown, UDM_SETRANGE32, 0, maxRange);
1049 /******************************************************************************
1050 * TAB_SetItemBounds
1052 * This method will calculate the position rectangles of all the items in the
1053 * control. The rectangle calculated starts at 0 for the first item in the
1054 * list and ignores scrolling and selection.
1055 * It also uses the current font to determine the height of the tab row and
1056 * it checks if all the tabs fit in the client area of the window. If they
1057 * dont, a scrolling control is added.
1059 static void TAB_SetItemBounds (HWND hwnd)
1061 TAB_INFO* infoPtr = TAB_GetInfoPtr(hwnd);
1062 LONG lStyle = GetWindowLongA(hwnd, GWL_STYLE);
1063 TEXTMETRICA fontMetrics;
1064 INT curItem;
1065 INT curItemLeftPos;
1066 INT curItemRowCount;
1067 HFONT hFont, hOldFont;
1068 HDC hdc;
1069 RECT clientRect;
1070 SIZE size;
1071 INT iTemp;
1072 RECT* rcItem;
1073 INT iIndex;
1074 INT icon_width = 0;
1077 * We need to get text information so we need a DC and we need to select
1078 * a font.
1080 hdc = GetDC(hwnd);
1082 hFont = infoPtr->hFont ? infoPtr->hFont : GetStockObject (SYSTEM_FONT);
1083 hOldFont = SelectObject (hdc, hFont);
1086 * We will base the rectangle calculations on the client rectangle
1087 * of the control.
1089 GetClientRect(hwnd, &clientRect);
1091 /* if TCS_VERTICAL then swap the height and width so this code places the
1092 tabs along the top of the rectangle and we can just rotate them after
1093 rather than duplicate all of the below code */
1094 if(lStyle & TCS_VERTICAL)
1096 iTemp = clientRect.bottom;
1097 clientRect.bottom = clientRect.right;
1098 clientRect.right = iTemp;
1101 /* The leftmost item will be "0" aligned */
1102 curItemLeftPos = 0;
1103 curItemRowCount = infoPtr->uNumItem ? 1 : 0;
1105 if (!(infoPtr->fHeightSet))
1107 int item_height;
1108 int icon_height = 0;
1110 /* Use the current font to determine the height of a tab. */
1111 GetTextMetricsA(hdc, &fontMetrics);
1113 /* Get the icon height */
1114 if (infoPtr->himl)
1115 ImageList_GetIconSize(infoPtr->himl, 0, &icon_height);
1117 /* Take the highest between font or icon */
1118 if (fontMetrics.tmHeight > icon_height)
1119 item_height = fontMetrics.tmHeight + 2;
1120 else
1121 item_height = icon_height;
1124 * Make sure there is enough space for the letters + icon + growing the
1125 * selected item + extra space for the selected item.
1127 infoPtr->tabHeight = item_height + SELECTED_TAB_OFFSET +
1128 ((lStyle & TCS_BUTTONS) ? 2 : 1) *
1129 infoPtr->uVItemPadding;
1131 TRACE("tabH=%d, tmH=%ld, iconh=%d\n",
1132 infoPtr->tabHeight, fontMetrics.tmHeight, icon_height);
1135 TRACE("client right=%ld\n", clientRect.right);
1137 /* Get the icon width */
1138 if (infoPtr->himl)
1140 ImageList_GetIconSize(infoPtr->himl, &icon_width, 0);
1142 if (lStyle & TCS_FIXEDWIDTH)
1143 icon_width += 4;
1144 else
1145 /* Add padding if icon is present */
1146 icon_width += infoPtr->uHItemPadding;
1149 for (curItem = 0; curItem < infoPtr->uNumItem; curItem++)
1151 /* Set the leftmost position of the tab. */
1152 infoPtr->items[curItem].rect.left = curItemLeftPos;
1154 if (lStyle & TCS_FIXEDWIDTH)
1156 infoPtr->items[curItem].rect.right = infoPtr->items[curItem].rect.left +
1157 max(infoPtr->tabWidth, icon_width);
1159 else
1161 int num = 2;
1163 /* Calculate how wide the tab is depending on the text it contains */
1164 GetTextExtentPoint32W(hdc, infoPtr->items[curItem].pszText,
1165 lstrlenW(infoPtr->items[curItem].pszText), &size);
1167 infoPtr->items[curItem].rect.right = infoPtr->items[curItem].rect.left +
1168 size.cx + icon_width +
1169 num * infoPtr->uHItemPadding;
1170 TRACE("for <%s>, l,r=%ld,%ld, num=%d\n",
1171 debugstr_w(infoPtr->items[curItem].pszText),
1172 infoPtr->items[curItem].rect.left,
1173 infoPtr->items[curItem].rect.right,
1174 num);
1178 * Check if this is a multiline tab control and if so
1179 * check to see if we should wrap the tabs
1181 * Because we are going to arange all these tabs evenly
1182 * really we are basically just counting rows at this point
1186 if (((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)) &&
1187 (infoPtr->items[curItem].rect.right > clientRect.right))
1189 infoPtr->items[curItem].rect.right -=
1190 infoPtr->items[curItem].rect.left;
1192 infoPtr->items[curItem].rect.left = 0;
1193 curItemRowCount++;
1194 TRACE("wrapping <%s>, l,r=%ld,%ld\n",
1195 debugstr_w(infoPtr->items[curItem].pszText),
1196 infoPtr->items[curItem].rect.left,
1197 infoPtr->items[curItem].rect.right);
1200 infoPtr->items[curItem].rect.bottom = 0;
1201 infoPtr->items[curItem].rect.top = curItemRowCount - 1;
1203 TRACE("TextSize: %li\n", size.cx);
1204 TRACE("Rect: T %li, L %li, B %li, R %li\n",
1205 infoPtr->items[curItem].rect.top,
1206 infoPtr->items[curItem].rect.left,
1207 infoPtr->items[curItem].rect.bottom,
1208 infoPtr->items[curItem].rect.right);
1211 * The leftmost position of the next item is the rightmost position
1212 * of this one.
1214 if (lStyle & TCS_BUTTONS)
1216 curItemLeftPos = infoPtr->items[curItem].rect.right + BUTTON_SPACINGX;
1217 if (lStyle & TCS_FLATBUTTONS)
1218 curItemLeftPos += FLAT_BTN_SPACINGX;
1220 else
1221 curItemLeftPos = infoPtr->items[curItem].rect.right;
1224 if (!((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)))
1227 * Check if we need a scrolling control.
1229 infoPtr->needsScrolling = (curItemLeftPos + (2 * SELECTED_TAB_OFFSET) >
1230 clientRect.right);
1232 /* Don't need scrolling, then update infoPtr->leftmostVisible */
1233 if(!infoPtr->needsScrolling)
1234 infoPtr->leftmostVisible = 0;
1236 TAB_SetupScrolling(hwnd, infoPtr, &clientRect);
1239 /* Set the number of rows */
1240 infoPtr->uNumRows = curItemRowCount;
1242 if (((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)) && (infoPtr->uNumItem > 0))
1244 INT widthDiff, remainder;
1245 INT tabPerRow,remTab;
1246 INT iRow,iItm;
1247 INT iIndexStart=0,iIndexEnd=0, iCount=0;
1250 * Ok windows tries to even out the rows. place the same
1251 * number of tabs in each row. So lets give that a shot
1254 tabPerRow = infoPtr->uNumItem / (infoPtr->uNumRows);
1255 remTab = infoPtr->uNumItem % (infoPtr->uNumRows);
1257 for (iItm=0,iRow=0,iCount=0,curItemLeftPos=0;
1258 iItm<infoPtr->uNumItem;
1259 iItm++,iCount++)
1261 /* normalize the current rect */
1263 /* shift the item to the left side of the clientRect */
1264 infoPtr->items[iItm].rect.right -=
1265 infoPtr->items[iItm].rect.left;
1266 infoPtr->items[iItm].rect.left = 0;
1268 TRACE("r=%ld, cl=%d, cl.r=%ld, iCount=%d, iRow=%d, uNumRows=%d, remTab=%d, tabPerRow=%d\n",
1269 infoPtr->items[iItm].rect.right,
1270 curItemLeftPos, clientRect.right,
1271 iCount, iRow, infoPtr->uNumRows, remTab, tabPerRow);
1273 /* if we have reached the maximum number of tabs on this row */
1274 /* move to the next row, reset our current item left position and */
1275 /* the count of items on this row */
1277 /* ************ FIXME FIXME FIXME *************** */
1278 /* */
1279 /* FIXME: */
1280 /* if vertical, */
1281 /* if item n and n+1 are in the same row, */
1282 /* then the display has n+1 lower (toward the */
1283 /* bottom) than n. We do it just the */
1284 /* opposite!!! */
1285 /* */
1286 /* ************ FIXME FIXME FIXME *************** */
1288 if (lStyle & TCS_VERTICAL) {
1289 /* Vert: Add the remaining tabs in the *last* remainder rows */
1290 if (iCount >= ((iRow>=(INT)infoPtr->uNumRows - remTab)?tabPerRow + 1:tabPerRow)) {
1291 iRow++;
1292 curItemLeftPos = 0;
1293 iCount = 0;
1295 } else {
1296 /* Horz: Add the remaining tabs in the *first* remainder rows */
1297 if (iCount >= ((iRow<remTab)?tabPerRow + 1:tabPerRow)) {
1298 iRow++;
1299 curItemLeftPos = 0;
1300 iCount = 0;
1304 /* shift the item to the right to place it as the next item in this row */
1305 infoPtr->items[iItm].rect.left += curItemLeftPos;
1306 infoPtr->items[iItm].rect.right += curItemLeftPos;
1307 infoPtr->items[iItm].rect.top = iRow;
1308 if (lStyle & TCS_BUTTONS)
1310 curItemLeftPos = infoPtr->items[iItm].rect.right + 1;
1311 if (lStyle & TCS_FLATBUTTONS)
1312 curItemLeftPos += FLAT_BTN_SPACINGX;
1314 else
1315 curItemLeftPos = infoPtr->items[iItm].rect.right;
1317 TRACE("arranging <%s>, l,r=%ld,%ld, row=%ld\n",
1318 debugstr_w(infoPtr->items[iItm].pszText),
1319 infoPtr->items[iItm].rect.left,
1320 infoPtr->items[iItm].rect.right,
1321 infoPtr->items[iItm].rect.top);
1325 * Justify the rows
1328 while(iIndexStart < infoPtr->uNumItem)
1331 * find the indexs of the row
1333 /* find the first item on the next row */
1334 for (iIndexEnd=iIndexStart;
1335 (iIndexEnd < infoPtr->uNumItem) &&
1336 (infoPtr->items[iIndexEnd].rect.top ==
1337 infoPtr->items[iIndexStart].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 infoPtr->items[iIndexEnd - 1].rect.right;
1350 /* iCount is the number of tab items on this row */
1351 iCount = iIndexEnd - iIndexStart;
1354 if (iCount > 1)
1356 remainder = widthDiff % iCount;
1357 widthDiff = widthDiff / iCount;
1358 /* add widthDiff/iCount, or extra space/items on row, to each item on this row */
1359 for (iIndex=iIndexStart,iCount=0; iIndex < iIndexEnd;
1360 iIndex++,iCount++)
1362 infoPtr->items[iIndex].rect.left += iCount * widthDiff;
1363 infoPtr->items[iIndex].rect.right += (iCount + 1) * widthDiff;
1365 TRACE("adjusting 1 <%s>, l,r=%ld,%ld\n",
1366 debugstr_w(infoPtr->items[iIndex].pszText),
1367 infoPtr->items[iIndex].rect.left,
1368 infoPtr->items[iIndex].rect.right);
1371 infoPtr->items[iIndex - 1].rect.right += remainder;
1373 else /* we have only one item on this row, make it take up the entire row */
1375 infoPtr->items[iIndexStart].rect.left = clientRect.left;
1376 infoPtr->items[iIndexStart].rect.right = clientRect.right - 4;
1378 TRACE("adjusting 2 <%s>, l,r=%ld,%ld\n",
1379 debugstr_w(infoPtr->items[iIndexStart].pszText),
1380 infoPtr->items[iIndexStart].rect.left,
1381 infoPtr->items[iIndexStart].rect.right);
1386 iIndexStart = iIndexEnd;
1391 /* if TCS_VERTICAL rotate the tabs so they are along the side of the clientRect */
1392 if(lStyle & TCS_VERTICAL)
1394 RECT rcOriginal;
1395 for(iIndex = 0; iIndex < infoPtr->uNumItem; iIndex++)
1397 rcItem = &(infoPtr->items[iIndex].rect);
1399 rcOriginal = *rcItem;
1401 /* this is rotating the items by 90 degrees around the center of the control */
1402 rcItem->top = (clientRect.right - (rcOriginal.left - clientRect.left)) - (rcOriginal.right - rcOriginal.left);
1403 rcItem->bottom = rcItem->top + (rcOriginal.right - rcOriginal.left);
1404 rcItem->left = rcOriginal.top;
1405 rcItem->right = rcOriginal.bottom;
1409 TAB_EnsureSelectionVisible(hwnd,infoPtr);
1410 TAB_RecalcHotTrack(hwnd, NULL, NULL, NULL);
1412 /* Cleanup */
1413 SelectObject (hdc, hOldFont);
1414 ReleaseDC (hwnd, hdc);
1417 /******************************************************************************
1418 * TAB_DrawItemInterior
1420 * This method is used to draw the interior (text and icon) of a single tab
1421 * into the tab control.
1423 static void
1424 TAB_DrawItemInterior
1426 HWND hwnd,
1427 HDC hdc,
1428 INT iItem,
1429 RECT* drawRect
1432 TAB_INFO* infoPtr = TAB_GetInfoPtr(hwnd);
1433 LONG lStyle = GetWindowLongA(hwnd, GWL_STYLE);
1435 RECT localRect;
1437 HPEN htextPen;
1438 HPEN holdPen;
1439 INT oldBkMode;
1441 if (drawRect == NULL)
1443 BOOL isVisible;
1444 RECT itemRect;
1445 RECT selectedRect;
1448 * Get the rectangle for the item.
1450 isVisible = TAB_InternalGetItemRect(hwnd, infoPtr, iItem, &itemRect, &selectedRect);
1451 if (!isVisible)
1452 return;
1455 * Make sure drawRect points to something valid; simplifies code.
1457 drawRect = &localRect;
1460 * This logic copied from the part of TAB_DrawItem which draws
1461 * the tab background. It's important to keep it in sync. I
1462 * would have liked to avoid code duplication, but couldn't figure
1463 * out how without making spaghetti of TAB_DrawItem.
1465 if (lStyle & TCS_BUTTONS)
1467 *drawRect = itemRect;
1468 if (iItem == infoPtr->iSelected)
1470 drawRect->right--;
1471 drawRect->bottom--;
1474 else
1476 if (iItem == infoPtr->iSelected)
1477 *drawRect = selectedRect;
1478 else
1479 *drawRect = itemRect;
1480 drawRect->right--;
1481 drawRect->bottom--;
1486 * Text pen
1488 htextPen = CreatePen( PS_SOLID, 1, GetSysColor(COLOR_BTNTEXT) );
1489 holdPen = SelectObject(hdc, htextPen);
1491 oldBkMode = SetBkMode(hdc, TRANSPARENT);
1492 SetTextColor(hdc, ( (iItem == infoPtr->iHotTracked) | (infoPtr->items[iItem].dwState & TCIS_HIGHLIGHTED)) ?
1493 comctl32_color.clrHighlight : comctl32_color.clrBtnText);
1497 * if owner draw, tell the owner to draw
1499 if ((lStyle & TCS_OWNERDRAWFIXED) && GetParent(hwnd))
1501 DRAWITEMSTRUCT dis;
1502 UINT id;
1505 * get the control id
1507 id = GetWindowLongA( hwnd, GWL_ID );
1510 * put together the DRAWITEMSTRUCT
1512 dis.CtlType = ODT_TAB;
1513 dis.CtlID = id;
1514 dis.itemID = iItem;
1515 dis.itemAction = ODA_DRAWENTIRE;
1516 dis.itemState = 0;
1517 if ( iItem == infoPtr->iSelected )
1518 dis.itemState |= ODS_SELECTED;
1519 if (infoPtr->uFocus == iItem)
1520 dis.itemState |= ODS_FOCUS;
1521 dis.hwndItem = hwnd; /* */
1522 dis.hDC = hdc;
1523 CopyRect(&dis.rcItem,drawRect);
1524 dis.itemData = infoPtr->items[iItem].lParam;
1527 * send the draw message
1529 SendMessageA( GetParent(hwnd), WM_DRAWITEM, (WPARAM)id, (LPARAM)&dis );
1531 else
1533 INT cx;
1534 INT cy;
1535 UINT uHorizAlign;
1536 RECT rcTemp;
1537 RECT rcImage;
1538 LOGFONTA logfont;
1539 HFONT hFont = 0;
1540 HFONT hOldFont = 0; /* stop uninitialized warning */
1542 INT nEscapement = 0; /* stop uninitialized warning */
1543 INT nOrientation = 0; /* stop uninitialized warning */
1544 INT iPointSize;
1546 /* used to center the icon and text in the tab */
1547 RECT rcText;
1548 INT center_offset_h, center_offset_v;
1551 * Deflate the rectangle to acount for the padding
1553 if(lStyle & TCS_VERTICAL)
1554 InflateRect(drawRect, -infoPtr->uVItemPadding, -infoPtr->uHItemPadding);
1555 else
1556 InflateRect(drawRect, -infoPtr->uHItemPadding, -infoPtr->uVItemPadding);
1558 /* set rcImage to drawRect, we will use top & left in our ImageList_Draw call */
1559 rcImage = *drawRect;
1561 rcTemp = *drawRect;
1563 rcText.left = rcText.top = rcText.right = rcText.bottom = 0;
1566 * Setup for text output
1568 oldBkMode = SetBkMode(hdc, TRANSPARENT);
1569 SetTextColor(hdc, ((iItem == infoPtr->iHotTracked) | (infoPtr->items[iItem].dwState & TCIS_HIGHLIGHTED))?
1570 comctl32_color.clrHighlight : comctl32_color.clrBtnText);
1572 /* get the rectangle that the text fits in */
1573 DrawTextW(hdc, infoPtr->items[iItem].pszText, -1,
1574 &rcText, DT_CALCRECT);
1575 rcText.right += 4;
1577 * If not owner draw, then do the drawing ourselves.
1579 * Draw the icon.
1581 if (infoPtr->himl && (infoPtr->items[iItem].mask & TCIF_IMAGE))
1583 ImageList_GetIconSize(infoPtr->himl, &cx, &cy);
1585 if(lStyle & TCS_VERTICAL)
1587 center_offset_h = ((drawRect->bottom - drawRect->top) - (cy + infoPtr->uHItemPadding + (rcText.right - rcText.left))) / 2;
1588 center_offset_v = ((drawRect->right - drawRect->left) - (cx + infoPtr->uVItemPadding)) / 2;
1590 else
1592 center_offset_h = ((drawRect->right - drawRect->left) - (cx + infoPtr->uHItemPadding + (rcText.right - rcText.left))) / 2;
1593 center_offset_v = ((drawRect->bottom - drawRect->top) - (cy + infoPtr->uVItemPadding)) / 2;
1596 if ((lStyle & TCS_FIXEDWIDTH &&
1597 lStyle & (TCS_FORCELABELLEFT | TCS_FORCEICONLEFT)) ||
1598 (center_offset_h < 0))
1599 center_offset_h = 0;
1601 TRACE("for <%s>, c_o=%d, draw=(%ld,%ld)-(%ld,%ld), textlen=%ld\n",
1602 debugstr_w(infoPtr->items[iItem].pszText), center_offset_h,
1603 drawRect->left, drawRect->top, drawRect->right, drawRect->bottom,
1604 (rcText.right-rcText.left));
1606 if((lStyle & TCS_VERTICAL) && (lStyle & TCS_BOTTOM))
1608 rcImage.top = drawRect->top + center_offset_h;
1609 /* if tab is TCS_VERTICAL and TCS_BOTTOM, the text is drawn from the */
1610 /* right side of the tab, but the image still uses the left as its x position */
1611 /* this keeps the image always drawn off of the same side of the tab */
1612 rcImage.left = drawRect->right - cx - center_offset_v;
1613 drawRect->top = rcImage.top + (cx + infoPtr->uHItemPadding);
1615 else if(lStyle & TCS_VERTICAL)
1617 rcImage.top = drawRect->bottom - cy - center_offset_h;
1618 rcImage.left = drawRect->left + center_offset_v;
1619 drawRect->bottom = rcImage.top - infoPtr->uHItemPadding;
1621 else /* normal style, whether TCS_BOTTOM or not */
1623 rcImage.left = drawRect->left + center_offset_h + 3;
1624 drawRect->left = rcImage.left + cx + infoPtr->uHItemPadding;
1625 rcImage.top = drawRect->top + center_offset_v;
1628 TRACE("drawing image=%d, left=%ld, top=%ld\n",
1629 infoPtr->items[iItem].iImage, rcImage.left, rcImage.top-1);
1630 ImageList_Draw
1632 infoPtr->himl,
1633 infoPtr->items[iItem].iImage,
1634 hdc,
1635 rcImage.left,
1636 rcImage.top,
1637 ILD_NORMAL
1640 else /* no image, so just shift the drawRect borders around */
1642 if(lStyle & TCS_VERTICAL)
1644 center_offset_h = 0;
1646 currently the rcText rect is flawed because the rotated font does not
1647 often match the horizontal font. So leave this as 0
1648 ((drawRect->bottom - drawRect->top) - (rcText.right - rcText.left)) / 2;
1650 if(lStyle & TCS_BOTTOM)
1651 drawRect->top+=center_offset_h;
1652 else
1653 drawRect->bottom-=center_offset_h;
1655 else
1657 center_offset_h = ((drawRect->right - drawRect->left) - (rcText.right - rcText.left)) / 2;
1658 drawRect->left+=center_offset_h;
1662 if(lStyle & TCS_VERTICAL)
1664 center_offset_v = ((drawRect->right - drawRect->left) - ((rcText.bottom - rcText.top) + infoPtr->uVItemPadding)) / 2;
1665 drawRect->left += center_offset_v;
1667 else
1669 center_offset_v = ((drawRect->bottom - drawRect->top) - ((rcText.bottom - rcText.top) + infoPtr->uVItemPadding)) / 2;
1670 drawRect->top += center_offset_v;
1674 /* Draw the text */
1675 if ((lStyle & TCS_FIXEDWIDTH && lStyle & TCS_FORCELABELLEFT) ||
1676 !center_offset_h)
1677 uHorizAlign = DT_LEFT;
1678 else
1679 uHorizAlign = DT_CENTER;
1681 if(lStyle & TCS_VERTICAL) /* if we are vertical rotate the text and each character */
1683 if(lStyle & TCS_BOTTOM)
1685 nEscapement = -900;
1686 nOrientation = -900;
1688 else
1690 nEscapement = 900;
1691 nOrientation = 900;
1695 /* to get a font with the escapement and orientation we are looking for, we need to */
1696 /* call CreateFontIndirectA, which requires us to set the values of the logfont we pass in */
1697 if(lStyle & TCS_VERTICAL)
1699 if (!GetObjectA((infoPtr->hFont) ?
1700 infoPtr->hFont : GetStockObject(SYSTEM_FONT),
1701 sizeof(LOGFONTA),&logfont))
1703 iPointSize = 9;
1705 lstrcpyA(logfont.lfFaceName, "Arial");
1706 logfont.lfHeight = -MulDiv(iPointSize, GetDeviceCaps(hdc, LOGPIXELSY),
1707 72);
1708 logfont.lfWeight = FW_NORMAL;
1709 logfont.lfItalic = 0;
1710 logfont.lfUnderline = 0;
1711 logfont.lfStrikeOut = 0;
1714 logfont.lfEscapement = nEscapement;
1715 logfont.lfOrientation = nOrientation;
1716 hFont = CreateFontIndirectA(&logfont);
1717 hOldFont = SelectObject(hdc, hFont);
1720 if (lStyle & TCS_VERTICAL)
1722 ExtTextOutW(hdc,
1723 (lStyle & TCS_BOTTOM) ? drawRect->right : drawRect->left,
1724 (!(lStyle & TCS_BOTTOM)) ? drawRect->bottom : drawRect->top,
1725 ETO_CLIPPED,
1726 drawRect,
1727 infoPtr->items[iItem].pszText,
1728 lstrlenW(infoPtr->items[iItem].pszText),
1731 else
1733 DrawTextW
1735 hdc,
1736 infoPtr->items[iItem].pszText,
1737 lstrlenW(infoPtr->items[iItem].pszText),
1738 drawRect,
1739 uHorizAlign | DT_SINGLELINE
1743 /* clean things up */
1744 *drawRect = rcTemp; /* restore drawRect */
1746 if(lStyle & TCS_VERTICAL)
1748 SelectObject(hdc, hOldFont); /* restore the original font */
1749 if (hFont)
1750 DeleteObject(hFont);
1755 * Cleanup
1757 SetBkMode(hdc, oldBkMode);
1758 SelectObject(hdc, holdPen);
1759 DeleteObject( htextPen );
1762 /******************************************************************************
1763 * TAB_DrawItem
1765 * This method is used to draw a single tab into the tab control.
1767 static void TAB_DrawItem(
1768 HWND hwnd,
1769 HDC hdc,
1770 INT iItem)
1772 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
1773 LONG lStyle = GetWindowLongA(hwnd, GWL_STYLE);
1774 RECT itemRect;
1775 RECT selectedRect;
1776 BOOL isVisible;
1777 RECT r, fillRect, r1;
1778 INT clRight = 0;
1779 INT clBottom = 0;
1780 COLORREF bkgnd, corner;
1783 * Get the rectangle for the item.
1785 isVisible = TAB_InternalGetItemRect(hwnd,
1786 infoPtr,
1787 iItem,
1788 &itemRect,
1789 &selectedRect);
1791 if (isVisible)
1793 /* If you need to see what the control is doing,
1794 * then override these variables. They will change what
1795 * fill colors are used for filling the tabs, and the
1796 * corners when drawing the edge.
1798 bkgnd = comctl32_color.clrBtnFace;
1799 corner = comctl32_color.clrBtnFace;
1801 if (lStyle & TCS_BUTTONS)
1803 HBRUSH hbr = CreateSolidBrush (bkgnd);
1804 BOOL deleteBrush = TRUE;
1806 /* Get item rectangle */
1807 r = itemRect;
1809 /* Separators between flat buttons */
1810 if (lStyle & TCS_FLATBUTTONS)
1812 r1 = r;
1813 r1.right += (FLAT_BTN_SPACINGX -2);
1814 DrawEdge(hdc, &r1, EDGE_ETCHED, BF_RIGHT);
1817 if (iItem == infoPtr->iSelected)
1819 /* Background color */
1820 if (!(lStyle & TCS_OWNERDRAWFIXED))
1822 DeleteObject(hbr);
1823 hbr = GetSysColorBrush(COLOR_SCROLLBAR);
1825 SetTextColor(hdc, comctl32_color.clr3dFace);
1826 SetBkColor(hdc, comctl32_color.clr3dHilight);
1828 /* if COLOR_WINDOW happens to be the same as COLOR_3DHILIGHT
1829 * we better use 0x55aa bitmap brush to make scrollbar's background
1830 * look different from the window background.
1832 if (comctl32_color.clr3dHilight == comctl32_color.clrWindow)
1833 hbr = COMCTL32_hPattern55AABrush;
1835 deleteBrush = FALSE;
1838 /* Clear interior */
1839 FillRect(hdc, &r, hbr);
1841 DrawEdge(hdc, &r, EDGE_SUNKEN, BF_SOFT|BF_RECT);
1843 else /* ! selected */
1845 if (!(lStyle & TCS_FLATBUTTONS))
1847 /* Clear interior */
1848 FillRect(hdc, &r, hbr);
1850 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_RECT);
1854 /* Cleanup */
1855 if (deleteBrush) DeleteObject(hbr);
1857 else /* !TCS_BUTTONS */
1859 /* We draw a rectangle of different sizes depending on the selection
1860 * state. */
1861 if (iItem == infoPtr->iSelected) {
1862 RECT rect;
1863 GetClientRect (hwnd, &rect);
1864 clRight = rect.right;
1865 clBottom = rect.bottom;
1866 r = selectedRect;
1868 else
1869 r = itemRect;
1872 * Erase the background. (Delay it but setup rectangle.)
1873 * This is necessary when drawing the selected item since it is larger
1874 * than the others, it might overlap with stuff already drawn by the
1875 * other tabs
1877 fillRect = r;
1879 if(lStyle & TCS_VERTICAL)
1881 /* These are for adjusting the drawing of a Selected tab */
1882 /* The initial values are for the normal case of non-Selected */
1883 int ZZ = 1; /* Do not strech if selected */
1884 if (iItem == infoPtr->iSelected) {
1885 ZZ = 0;
1887 /* if leftmost draw the line longer */
1888 if(selectedRect.top == 0)
1889 fillRect.top += 2;
1890 /* if rightmost draw the line longer */
1891 if(selectedRect.bottom == clBottom)
1892 fillRect.bottom -= 2;
1895 if (lStyle & TCS_BOTTOM)
1897 /* Adjust both rectangles to match native */
1898 r.left += (1-ZZ);
1900 TRACE("<left> item=%d, fill=(%ld,%ld)-(%ld,%ld), edge=(%ld,%ld)-(%ld,%ld)\n",
1901 iItem,
1902 fillRect.left,fillRect.top,fillRect.right,fillRect.bottom,
1903 r.left,r.top,r.right,r.bottom);
1905 /* Clear interior */
1906 SetBkColor(hdc, bkgnd);
1907 ExtTextOutA(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
1909 /* Draw rectangular edge around tab */
1910 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_RIGHT|BF_TOP|BF_BOTTOM);
1912 /* Now erase the top corner and draw diagonal edge */
1913 SetBkColor(hdc, corner);
1914 r1.left = r.right - ROUND_CORNER_SIZE - 1;
1915 r1.top = r.top;
1916 r1.right = r.right;
1917 r1.bottom = r1.top + ROUND_CORNER_SIZE;
1918 ExtTextOutA(hdc, 0, 0, 2, &r1, NULL, 0, 0);
1919 r1.right--;
1920 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPLEFT);
1922 /* Now erase the bottom corner and draw diagonal edge */
1923 r1.left = r.right - ROUND_CORNER_SIZE - 1;
1924 r1.bottom = r.bottom;
1925 r1.right = r.right;
1926 r1.top = r1.bottom - ROUND_CORNER_SIZE;
1927 ExtTextOutA(hdc, 0, 0, 2, &r1, NULL, 0, 0);
1928 r1.right--;
1929 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMLEFT);
1931 if ((iItem == infoPtr->iSelected) && (selectedRect.top == 0)) {
1932 r1 = r;
1933 r1.right = r1.left;
1934 r1.left--;
1935 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_TOP);
1939 else
1941 /* Adjust both rectangles to match native */
1942 fillRect.right += (1-ZZ);
1944 TRACE("<left> item=%d, fill=(%ld,%ld)-(%ld,%ld), edge=(%ld,%ld)-(%ld,%ld)\n",
1945 iItem,
1946 fillRect.left,fillRect.top,fillRect.right,fillRect.bottom,
1947 r.left,r.top,r.right,r.bottom);
1949 /* Clear interior */
1950 SetBkColor(hdc, bkgnd);
1951 ExtTextOutA(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
1953 /* Draw rectangular edge around tab */
1954 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_TOP|BF_BOTTOM);
1956 /* Now erase the top corner and draw diagonal edge */
1957 SetBkColor(hdc, corner);
1958 r1.left = r.left;
1959 r1.top = r.top;
1960 r1.right = r1.left + ROUND_CORNER_SIZE + 1;
1961 r1.bottom = r1.top + ROUND_CORNER_SIZE;
1962 ExtTextOutA(hdc, 0, 0, 2, &r1, NULL, 0, 0);
1963 r1.left++;
1964 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPRIGHT);
1966 /* Now erase the bottom corner and draw diagonal edge */
1967 r1.left = r.left;
1968 r1.bottom = r.bottom;
1969 r1.right = r1.left + ROUND_CORNER_SIZE + 1;
1970 r1.top = r1.bottom - ROUND_CORNER_SIZE;
1971 ExtTextOutA(hdc, 0, 0, 2, &r1, NULL, 0, 0);
1972 r1.left++;
1973 DrawEdge(hdc, &r1, EDGE_SUNKEN, BF_DIAGONAL_ENDTOPLEFT);
1976 else /* ! TCS_VERTICAL */
1978 /* These are for adjusting the drawing of a Selected tab */
1979 /* The initial values are for the normal case of non-Selected */
1980 int ZZ = 1; /* Do not strech if selected */
1981 if (iItem == infoPtr->iSelected) {
1982 ZZ = 0;
1984 /* if leftmost draw the line longer */
1985 if(selectedRect.left == 0)
1986 fillRect.left += 2;
1987 /* if rightmost draw the line longer */
1988 if(selectedRect.right == clRight)
1989 fillRect.right -= 2;
1992 if (lStyle & TCS_BOTTOM)
1995 /* Adjust both rectangles to match native */
1996 fillRect.top--;
1997 fillRect.bottom--;
1998 r.bottom--;
1999 r.top -= ZZ;
2001 TRACE("<bottom> item=%d, fill=(%ld,%ld)-(%ld,%ld), edge=(%ld,%ld)-(%ld,%ld)\n",
2002 iItem,
2003 fillRect.left,fillRect.top,fillRect.right,fillRect.bottom,
2004 r.left,r.top,r.right,r.bottom);
2006 /* Clear interior */
2007 SetBkColor(hdc, bkgnd);
2008 ExtTextOutA(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2010 /* Draw rectangular edge around tab */
2011 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_BOTTOM|BF_RIGHT);
2013 /* Now erase the righthand corner and draw diagonal edge */
2014 SetBkColor(hdc, corner);
2015 r1.left = r.right - ROUND_CORNER_SIZE;
2016 r1.bottom = r.bottom;
2017 r1.right = r.right;
2018 r1.top = r1.bottom - ROUND_CORNER_SIZE - 1;
2019 ExtTextOutA(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2020 r1.bottom--;
2021 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMLEFT);
2023 /* Now erase the lefthand corner and draw diagonal edge */
2024 r1.left = r.left;
2025 r1.bottom = r.bottom;
2026 r1.right = r1.left + ROUND_CORNER_SIZE;
2027 r1.top = r1.bottom - ROUND_CORNER_SIZE - 1;
2028 ExtTextOutA(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2029 r1.bottom--;
2030 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPLEFT);
2032 if ((iItem == infoPtr->iSelected) && (selectedRect.left == 0)) {
2033 r1 = r;
2034 r1.bottom = r1.top;
2035 r1.top--;
2036 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_LEFT);
2040 else
2043 /* Adjust both rectangles to match native */
2044 fillRect.bottom += (1-ZZ);
2046 TRACE("<top> 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_RIGHT);
2058 /* Now erase the righthand corner and draw diagonal edge */
2059 SetBkColor(hdc, corner);
2060 r1.left = r.right - ROUND_CORNER_SIZE;
2061 r1.top = r.top;
2062 r1.right = r.right;
2063 r1.bottom = r1.top + ROUND_CORNER_SIZE + 1;
2064 ExtTextOutA(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2065 r1.top++;
2066 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMRIGHT);
2068 /* Now erase the lefthand corner and draw diagonal edge */
2069 r1.left = r.left;
2070 r1.top = r.top;
2071 r1.right = r1.left + ROUND_CORNER_SIZE;
2072 r1.bottom = r1.top + ROUND_CORNER_SIZE + 1;
2073 ExtTextOutA(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2074 r1.top++;
2075 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPRIGHT);
2081 TAB_DumpItemInternal(infoPtr, iItem);
2083 /* This modifies r to be the text rectangle. */
2085 HFONT hOldFont = SelectObject(hdc, infoPtr->hFont);
2086 TAB_DrawItemInterior(hwnd, hdc, iItem, &r);
2087 SelectObject(hdc,hOldFont);
2090 /* Draw the focus rectangle */
2091 if (((lStyle & TCS_FOCUSNEVER) == 0) &&
2092 (GetFocus() == hwnd) &&
2093 (iItem == infoPtr->uFocus) )
2095 r = itemRect;
2096 InflateRect(&r, -1, -1);
2098 DrawFocusRect(hdc, &r);
2103 /******************************************************************************
2104 * TAB_DrawBorder
2106 * This method is used to draw the raised border around the tab control
2107 * "content" area.
2109 static void TAB_DrawBorder (HWND hwnd, HDC hdc)
2111 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2112 RECT rect;
2113 DWORD lStyle = GetWindowLongA(hwnd, GWL_STYLE);
2115 GetClientRect (hwnd, &rect);
2118 * Adjust for the style
2121 if (infoPtr->uNumItem)
2123 if ((lStyle & TCS_BOTTOM) && !(lStyle & TCS_VERTICAL))
2125 rect.bottom -= (infoPtr->tabHeight - 2) * infoPtr->uNumRows + 3;
2127 else if((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
2129 rect.right -= (infoPtr->tabHeight - 2) * infoPtr->uNumRows + 2;
2131 else if(lStyle & TCS_VERTICAL)
2133 rect.left += (infoPtr->tabHeight - 2) * infoPtr->uNumRows + 2;
2135 else /* not TCS_VERTICAL and not TCS_BOTTOM */
2137 rect.top += (infoPtr->tabHeight - 2) * infoPtr->uNumRows + 2;
2141 TRACE("border=(%ld,%ld)-(%ld,%ld)\n",
2142 rect.left, rect.top, rect.right, rect.bottom);
2144 DrawEdge(hdc, &rect, EDGE_RAISED, BF_SOFT|BF_RECT);
2147 /******************************************************************************
2148 * TAB_Refresh
2150 * This method repaints the tab control..
2152 static void TAB_Refresh (HWND hwnd, HDC hdc)
2154 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2155 HFONT hOldFont;
2156 INT i;
2158 if (!infoPtr->DoRedraw)
2159 return;
2161 hOldFont = SelectObject (hdc, infoPtr->hFont);
2163 if (GetWindowLongA(hwnd, GWL_STYLE) & TCS_BUTTONS)
2165 for (i = 0; i < infoPtr->uNumItem; i++)
2166 TAB_DrawItem (hwnd, hdc, i);
2168 else
2170 /* Draw all the non selected item first */
2171 for (i = 0; i < infoPtr->uNumItem; i++)
2173 if (i != infoPtr->iSelected)
2174 TAB_DrawItem (hwnd, hdc, i);
2177 /* Now, draw the border, draw it before the selected item
2178 * since the selected item overwrites part of the border. */
2179 TAB_DrawBorder (hwnd, hdc);
2181 /* Then, draw the selected item */
2182 TAB_DrawItem (hwnd, hdc, infoPtr->iSelected);
2184 /* If we haven't set the current focus yet, set it now.
2185 * Only happens when we first paint the tab controls */
2186 if (infoPtr->uFocus == -1)
2187 TAB_SetCurFocus(hwnd, infoPtr->iSelected);
2190 SelectObject (hdc, hOldFont);
2193 static DWORD
2194 TAB_GetRowCount (HWND hwnd )
2196 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2198 return infoPtr->uNumRows;
2201 static LRESULT
2202 TAB_SetRedraw (HWND hwnd, WPARAM wParam)
2204 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2206 infoPtr->DoRedraw=(BOOL) wParam;
2207 return 0;
2210 static LRESULT TAB_EraseBackground(
2211 HWND hwnd,
2212 HDC givenDC)
2214 HDC hdc;
2215 RECT clientRect;
2217 HBRUSH brush = CreateSolidBrush(comctl32_color.clrBtnFace);
2219 hdc = givenDC ? givenDC : GetDC(hwnd);
2221 GetClientRect(hwnd, &clientRect);
2223 FillRect(hdc, &clientRect, brush);
2225 if (givenDC==0)
2226 ReleaseDC(hwnd, hdc);
2228 DeleteObject(brush);
2230 return 0;
2233 /******************************************************************************
2234 * TAB_EnsureSelectionVisible
2236 * This method will make sure that the current selection is completely
2237 * visible by scrolling until it is.
2239 static void TAB_EnsureSelectionVisible(
2240 HWND hwnd,
2241 TAB_INFO* infoPtr)
2243 INT iSelected = infoPtr->iSelected;
2244 LONG lStyle = GetWindowLongA(hwnd, GWL_STYLE);
2245 INT iOrigLeftmostVisible = infoPtr->leftmostVisible;
2247 /* set the items row to the bottommost row or topmost row depending on
2248 * style */
2249 if ((infoPtr->uNumRows > 1) && !(lStyle & TCS_BUTTONS))
2251 INT newselected;
2252 INT iTargetRow;
2254 if(lStyle & TCS_VERTICAL)
2255 newselected = infoPtr->items[iSelected].rect.left;
2256 else
2257 newselected = infoPtr->items[iSelected].rect.top;
2259 /* the target row is always (number of rows - 1)
2260 as row 0 is furthest from the clientRect */
2261 iTargetRow = infoPtr->uNumRows - 1;
2263 if (newselected != iTargetRow)
2265 INT i;
2266 if(lStyle & TCS_VERTICAL)
2268 for (i=0; i < infoPtr->uNumItem; i++)
2270 /* move everything in the row of the selected item to the iTargetRow */
2271 if (infoPtr->items[i].rect.left == newselected )
2272 infoPtr->items[i].rect.left = iTargetRow;
2273 else
2275 if (infoPtr->items[i].rect.left > newselected)
2276 infoPtr->items[i].rect.left-=1;
2280 else
2282 for (i=0; i < infoPtr->uNumItem; i++)
2284 if (infoPtr->items[i].rect.top == newselected )
2285 infoPtr->items[i].rect.top = iTargetRow;
2286 else
2288 if (infoPtr->items[i].rect.top > newselected)
2289 infoPtr->items[i].rect.top-=1;
2293 TAB_RecalcHotTrack(hwnd, NULL, NULL, NULL);
2298 * Do the trivial cases first.
2300 if ( (!infoPtr->needsScrolling) ||
2301 (infoPtr->hwndUpDown==0) || (lStyle & TCS_VERTICAL))
2302 return;
2304 if (infoPtr->leftmostVisible >= iSelected)
2306 infoPtr->leftmostVisible = iSelected;
2308 else
2310 RECT r;
2311 INT width, i;
2313 /* Calculate the part of the client area that is visible */
2314 GetClientRect(hwnd, &r);
2315 width = r.right;
2317 GetClientRect(infoPtr->hwndUpDown, &r);
2318 width -= r.right;
2320 if ((infoPtr->items[iSelected].rect.right -
2321 infoPtr->items[iSelected].rect.left) >= width )
2323 /* Special case: width of selected item is greater than visible
2324 * part of control.
2326 infoPtr->leftmostVisible = iSelected;
2328 else
2330 for (i = infoPtr->leftmostVisible; i < infoPtr->uNumItem; i++)
2332 if ((infoPtr->items[iSelected].rect.right -
2333 infoPtr->items[i].rect.left) < width)
2334 break;
2336 infoPtr->leftmostVisible = i;
2340 if (infoPtr->leftmostVisible != iOrigLeftmostVisible)
2341 TAB_RecalcHotTrack(hwnd, NULL, NULL, NULL);
2343 SendMessageA(infoPtr->hwndUpDown, UDM_SETPOS, 0,
2344 MAKELONG(infoPtr->leftmostVisible, 0));
2347 /******************************************************************************
2348 * TAB_InvalidateTabArea
2350 * This method will invalidate the portion of the control that contains the
2351 * tabs. It is called when the state of the control changes and needs
2352 * to be redisplayed
2354 static void TAB_InvalidateTabArea(
2355 HWND hwnd,
2356 TAB_INFO* infoPtr)
2358 RECT clientRect, r;
2359 DWORD lStyle = GetWindowLongA(hwnd, GWL_STYLE);
2360 INT lastRow = infoPtr->uNumRows - 1;
2361 RECT rect;
2363 if (lastRow < 0) return;
2365 GetClientRect(hwnd, &clientRect);
2367 TAB_InternalGetItemRect(hwnd, infoPtr, infoPtr->uNumItem-1 , &rect, NULL);
2368 if ((lStyle & TCS_BOTTOM) && !(lStyle & TCS_VERTICAL))
2370 clientRect.top = clientRect.bottom -
2371 infoPtr->tabHeight -
2372 lastRow * (infoPtr->tabHeight - 2) -
2373 ((lStyle & TCS_BUTTONS) ? lastRow * BUTTON_SPACINGY : 0) - 3;
2374 clientRect.right = clientRect.left + rect.right + 2 * SELECTED_TAB_OFFSET;
2376 else if((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
2378 clientRect.left = clientRect.right - infoPtr->tabHeight -
2379 lastRow * (infoPtr->tabHeight - 2) -
2380 ((lStyle & TCS_BUTTONS) ? lastRow * BUTTON_SPACINGY : 0) - 2;
2381 clientRect.bottom = clientRect.top + rect.bottom + 2 * SELECTED_TAB_OFFSET;
2383 else if(lStyle & TCS_VERTICAL)
2385 clientRect.right = clientRect.left + infoPtr->tabHeight +
2386 lastRow * (infoPtr->tabHeight - 2) -
2387 ((lStyle & TCS_BUTTONS) ? lastRow * BUTTON_SPACINGY : 0) + 2;
2388 clientRect.bottom = clientRect.top + rect.bottom + 2 * SELECTED_TAB_OFFSET;
2390 else
2392 clientRect.bottom = clientRect.top + infoPtr->tabHeight +
2393 lastRow * (infoPtr->tabHeight - 2) +
2394 ((lStyle & TCS_BUTTONS) ? lastRow * BUTTON_SPACINGY : 0) + 2;
2395 clientRect.right = clientRect.left + rect.right + 2 * SELECTED_TAB_OFFSET;
2398 /* Punch out the updown control */
2399 if (infoPtr->needsScrolling && (clientRect.right > 0)) {
2400 GetClientRect(infoPtr->hwndUpDown, &r);
2401 clientRect.right = clientRect.right - (r.right - r.left);
2404 TRACE("invalidate (%ld,%ld)-(%ld,%ld)\n",
2405 clientRect.left,clientRect.top,
2406 clientRect.right,clientRect.bottom);
2408 InvalidateRect(hwnd, &clientRect, TRUE);
2411 static LRESULT
2412 TAB_Paint (HWND hwnd, WPARAM wParam)
2414 HDC hdc;
2415 PAINTSTRUCT ps;
2417 if (wParam == 0)
2419 hdc = BeginPaint (hwnd, &ps);
2420 TRACE("erase %d, rect=(%ld,%ld)-(%ld,%ld)\n",
2421 ps.fErase,
2422 ps.rcPaint.left,ps.rcPaint.top,ps.rcPaint.right,ps.rcPaint.bottom);
2424 if (ps.fErase)
2425 TAB_EraseBackground (hwnd, hdc);
2427 } else {
2428 hdc = (HDC)wParam;
2431 TAB_Refresh (hwnd, hdc);
2433 if(!wParam)
2434 EndPaint (hwnd, &ps);
2436 return 0;
2439 static LRESULT
2440 TAB_InsertItemA (HWND hwnd, WPARAM wParam, LPARAM lParam)
2442 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2443 TCITEMA *pti;
2444 INT iItem;
2445 RECT rect;
2447 GetClientRect (hwnd, &rect);
2448 TRACE("Rect: %p T %li, L %li, B %li, R %li\n", hwnd,
2449 rect.top, rect.left, rect.bottom, rect.right);
2451 pti = (TCITEMA *)lParam;
2452 iItem = (INT)wParam;
2454 if (iItem < 0) return -1;
2455 if (iItem > infoPtr->uNumItem)
2456 iItem = infoPtr->uNumItem;
2458 TAB_DumpItemExternalA(pti, iItem);
2461 if (infoPtr->uNumItem == 0) {
2462 infoPtr->items = Alloc (sizeof (TAB_ITEM));
2463 infoPtr->uNumItem++;
2464 infoPtr->iSelected = 0;
2466 else {
2467 TAB_ITEM *oldItems = infoPtr->items;
2469 infoPtr->uNumItem++;
2470 infoPtr->items = Alloc (sizeof (TAB_ITEM) * infoPtr->uNumItem);
2472 /* pre insert copy */
2473 if (iItem > 0) {
2474 memcpy (&infoPtr->items[0], &oldItems[0],
2475 iItem * sizeof(TAB_ITEM));
2478 /* post insert copy */
2479 if (iItem < infoPtr->uNumItem - 1) {
2480 memcpy (&infoPtr->items[iItem+1], &oldItems[iItem],
2481 (infoPtr->uNumItem - iItem - 1) * sizeof(TAB_ITEM));
2485 if (iItem <= infoPtr->iSelected)
2486 infoPtr->iSelected++;
2488 Free (oldItems);
2491 infoPtr->items[iItem].mask = pti->mask;
2492 if (pti->mask & TCIF_TEXT)
2493 Str_SetPtrAtoW (&infoPtr->items[iItem].pszText, pti->pszText);
2495 if (pti->mask & TCIF_IMAGE)
2496 infoPtr->items[iItem].iImage = pti->iImage;
2498 if (pti->mask & TCIF_PARAM)
2499 infoPtr->items[iItem].lParam = pti->lParam;
2501 TAB_SetItemBounds(hwnd);
2502 if (infoPtr->uNumItem > 1)
2503 TAB_InvalidateTabArea(hwnd, infoPtr);
2504 else
2505 InvalidateRect(hwnd, NULL, TRUE);
2507 TRACE("[%p]: added item %d %s\n",
2508 hwnd, iItem, debugstr_w(infoPtr->items[iItem].pszText));
2510 return iItem;
2514 static LRESULT
2515 TAB_InsertItemW (HWND hwnd, WPARAM wParam, LPARAM lParam)
2517 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2518 TCITEMW *pti;
2519 INT iItem;
2520 RECT rect;
2522 GetClientRect (hwnd, &rect);
2523 TRACE("Rect: %p T %li, L %li, B %li, R %li\n", hwnd,
2524 rect.top, rect.left, rect.bottom, rect.right);
2526 pti = (TCITEMW *)lParam;
2527 iItem = (INT)wParam;
2529 if (iItem < 0) return -1;
2530 if (iItem > infoPtr->uNumItem)
2531 iItem = infoPtr->uNumItem;
2533 TAB_DumpItemExternalW(pti, iItem);
2535 if (infoPtr->uNumItem == 0) {
2536 infoPtr->items = Alloc (sizeof (TAB_ITEM));
2537 infoPtr->uNumItem++;
2538 infoPtr->iSelected = 0;
2540 else {
2541 TAB_ITEM *oldItems = infoPtr->items;
2543 infoPtr->uNumItem++;
2544 infoPtr->items = Alloc (sizeof (TAB_ITEM) * infoPtr->uNumItem);
2546 /* pre insert copy */
2547 if (iItem > 0) {
2548 memcpy (&infoPtr->items[0], &oldItems[0],
2549 iItem * sizeof(TAB_ITEM));
2552 /* post insert copy */
2553 if (iItem < infoPtr->uNumItem - 1) {
2554 memcpy (&infoPtr->items[iItem+1], &oldItems[iItem],
2555 (infoPtr->uNumItem - iItem - 1) * sizeof(TAB_ITEM));
2559 if (iItem <= infoPtr->iSelected)
2560 infoPtr->iSelected++;
2562 Free (oldItems);
2565 infoPtr->items[iItem].mask = pti->mask;
2566 if (pti->mask & TCIF_TEXT)
2567 Str_SetPtrW (&infoPtr->items[iItem].pszText, pti->pszText);
2569 if (pti->mask & TCIF_IMAGE)
2570 infoPtr->items[iItem].iImage = pti->iImage;
2572 if (pti->mask & TCIF_PARAM)
2573 infoPtr->items[iItem].lParam = pti->lParam;
2575 TAB_SetItemBounds(hwnd);
2576 if (infoPtr->uNumItem > 1)
2577 TAB_InvalidateTabArea(hwnd, infoPtr);
2578 else
2579 InvalidateRect(hwnd, NULL, TRUE);
2581 TRACE("[%p]: added item %d %s\n",
2582 hwnd, iItem, debugstr_w(infoPtr->items[iItem].pszText));
2584 return iItem;
2588 static LRESULT
2589 TAB_SetItemSize (HWND hwnd, WPARAM wParam, LPARAM lParam)
2591 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2592 LONG lStyle = GetWindowLongA(hwnd, GWL_STYLE);
2593 LONG lResult = 0;
2594 BOOL bNeedPaint = FALSE;
2596 lResult = MAKELONG(infoPtr->tabWidth, infoPtr->tabHeight);
2598 /* UNDOCUMENTED: If requested Width or Height is 0 this means that program wants to use auto size. */
2599 if (lStyle & TCS_FIXEDWIDTH && (infoPtr->tabWidth != (INT)LOWORD(lParam)))
2601 infoPtr->tabWidth = max((INT)LOWORD(lParam), infoPtr->tabMinWidth);
2602 bNeedPaint = TRUE;
2605 if (infoPtr->tabHeight != (INT)HIWORD(lParam))
2607 if ((infoPtr->fHeightSet = ((INT)HIWORD(lParam) != 0)))
2608 infoPtr->tabHeight = (INT)HIWORD(lParam);
2610 bNeedPaint = TRUE;
2612 TRACE("was h=%d,w=%d, now h=%d,w=%d\n",
2613 HIWORD(lResult), LOWORD(lResult),
2614 infoPtr->tabHeight, infoPtr->tabWidth);
2616 if (bNeedPaint)
2618 TAB_SetItemBounds(hwnd);
2619 RedrawWindow(hwnd, NULL, NULL, RDW_ERASE | RDW_INVALIDATE | RDW_UPDATENOW);
2622 return lResult;
2625 static LRESULT
2626 TAB_SetMinTabWidth (HWND hwnd, LPARAM lParam)
2628 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2629 INT cx = (INT)lParam;
2630 INT oldcx;
2632 if (infoPtr) {
2633 oldcx = infoPtr->tabMinWidth;
2634 infoPtr->tabMinWidth = (cx==-1)?DEFAULT_TAB_WIDTH:cx;
2635 } else
2636 return 0;
2638 return oldcx;
2641 static LRESULT
2642 TAB_HighlightItem (HWND hwnd, WPARAM wParam, LPARAM lParam)
2644 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2645 INT iItem = (INT)wParam;
2646 BOOL fHighlight = (BOOL)LOWORD(lParam);
2648 if ((infoPtr) && (iItem>=0) && (iItem<infoPtr->uNumItem)) {
2649 if (fHighlight)
2650 infoPtr->items[iItem].dwState |= TCIS_HIGHLIGHTED;
2651 else
2652 infoPtr->items[iItem].dwState &= ~TCIS_HIGHLIGHTED;
2653 } else
2654 return FALSE;
2656 return TRUE;
2659 static LRESULT
2660 TAB_SetItemA (HWND hwnd, WPARAM wParam, LPARAM lParam)
2662 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2663 TCITEMA *tabItem;
2664 TAB_ITEM *wineItem;
2665 INT iItem;
2667 iItem = (INT)wParam;
2668 tabItem = (LPTCITEMA)lParam;
2670 TRACE("%d %p\n", iItem, tabItem);
2671 if ((iItem<0) || (iItem>=infoPtr->uNumItem)) return FALSE;
2673 TAB_DumpItemExternalA(tabItem, iItem);
2675 wineItem = &infoPtr->items[iItem];
2677 if (tabItem->mask & TCIF_IMAGE)
2678 wineItem->iImage = tabItem->iImage;
2680 if (tabItem->mask & TCIF_PARAM)
2681 wineItem->lParam = tabItem->lParam;
2683 if (tabItem->mask & TCIF_RTLREADING)
2684 FIXME("TCIF_RTLREADING\n");
2686 if (tabItem->mask & TCIF_STATE)
2687 wineItem->dwState = tabItem->dwState;
2689 if (tabItem->mask & TCIF_TEXT)
2690 Str_SetPtrAtoW(&wineItem->pszText, tabItem->pszText);
2692 /* Update and repaint tabs */
2693 TAB_SetItemBounds(hwnd);
2694 TAB_InvalidateTabArea(hwnd,infoPtr);
2696 return TRUE;
2700 static LRESULT
2701 TAB_SetItemW (HWND hwnd, WPARAM wParam, LPARAM lParam)
2703 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2704 TCITEMW *tabItem;
2705 TAB_ITEM *wineItem;
2706 INT iItem;
2708 iItem = (INT)wParam;
2709 tabItem = (LPTCITEMW)lParam;
2711 TRACE("%d %p\n", iItem, tabItem);
2712 if ((iItem<0) || (iItem>=infoPtr->uNumItem)) return FALSE;
2714 TAB_DumpItemExternalW(tabItem, iItem);
2716 wineItem = &infoPtr->items[iItem];
2718 if (tabItem->mask & TCIF_IMAGE)
2719 wineItem->iImage = tabItem->iImage;
2721 if (tabItem->mask & TCIF_PARAM)
2722 wineItem->lParam = tabItem->lParam;
2724 if (tabItem->mask & TCIF_RTLREADING)
2725 FIXME("TCIF_RTLREADING\n");
2727 if (tabItem->mask & TCIF_STATE)
2728 wineItem->dwState = tabItem->dwState;
2730 if (tabItem->mask & TCIF_TEXT)
2731 Str_SetPtrW(&wineItem->pszText, tabItem->pszText);
2733 /* Update and repaint tabs */
2734 TAB_SetItemBounds(hwnd);
2735 TAB_InvalidateTabArea(hwnd,infoPtr);
2737 return TRUE;
2741 static LRESULT
2742 TAB_GetItemCount (HWND hwnd, WPARAM wParam, LPARAM lParam)
2744 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2746 return infoPtr->uNumItem;
2750 static LRESULT
2751 TAB_GetItemA (HWND hwnd, WPARAM wParam, LPARAM lParam)
2753 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2754 TCITEMA *tabItem;
2755 TAB_ITEM *wineItem;
2756 INT iItem;
2758 iItem = (INT)wParam;
2759 tabItem = (LPTCITEMA)lParam;
2760 TRACE("\n");
2761 if ((iItem<0) || (iItem>=infoPtr->uNumItem))
2762 return FALSE;
2764 wineItem = &infoPtr->items[iItem];
2766 if (tabItem->mask & TCIF_IMAGE)
2767 tabItem->iImage = wineItem->iImage;
2769 if (tabItem->mask & TCIF_PARAM)
2770 tabItem->lParam = wineItem->lParam;
2772 if (tabItem->mask & TCIF_RTLREADING)
2773 FIXME("TCIF_RTLREADING\n");
2775 if (tabItem->mask & TCIF_STATE)
2776 tabItem->dwState = wineItem->dwState;
2778 if (tabItem->mask & TCIF_TEXT)
2779 Str_GetPtrWtoA (wineItem->pszText, tabItem->pszText, tabItem->cchTextMax);
2781 TAB_DumpItemExternalA(tabItem, iItem);
2783 return TRUE;
2787 static LRESULT
2788 TAB_GetItemW (HWND hwnd, WPARAM wParam, LPARAM lParam)
2790 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2791 TCITEMW *tabItem;
2792 TAB_ITEM *wineItem;
2793 INT iItem;
2795 iItem = (INT)wParam;
2796 tabItem = (LPTCITEMW)lParam;
2797 TRACE("\n");
2798 if ((iItem<0) || (iItem>=infoPtr->uNumItem))
2799 return FALSE;
2801 wineItem=& infoPtr->items[iItem];
2803 if (tabItem->mask & TCIF_IMAGE)
2804 tabItem->iImage = wineItem->iImage;
2806 if (tabItem->mask & TCIF_PARAM)
2807 tabItem->lParam = wineItem->lParam;
2809 if (tabItem->mask & TCIF_RTLREADING)
2810 FIXME("TCIF_RTLREADING\n");
2812 if (tabItem->mask & TCIF_STATE)
2813 tabItem->dwState = wineItem->dwState;
2815 if (tabItem->mask & TCIF_TEXT)
2816 Str_GetPtrW (wineItem->pszText, tabItem->pszText, tabItem->cchTextMax);
2818 TAB_DumpItemExternalW(tabItem, iItem);
2820 return TRUE;
2824 static LRESULT
2825 TAB_DeleteItem (HWND hwnd, WPARAM wParam, LPARAM lParam)
2827 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2828 INT iItem = (INT) wParam;
2829 BOOL bResult = FALSE;
2831 if ((iItem >= 0) && (iItem < infoPtr->uNumItem))
2833 TAB_ITEM *oldItems = infoPtr->items;
2835 TAB_InvalidateTabArea(hwnd, infoPtr);
2837 infoPtr->uNumItem--;
2838 infoPtr->items = Alloc(sizeof (TAB_ITEM) * infoPtr->uNumItem);
2840 if (iItem > 0)
2841 memcpy(&infoPtr->items[0], &oldItems[0], iItem * sizeof(TAB_ITEM));
2843 if (iItem < infoPtr->uNumItem)
2844 memcpy(&infoPtr->items[iItem], &oldItems[iItem + 1],
2845 (infoPtr->uNumItem - iItem) * sizeof(TAB_ITEM));
2847 Free(oldItems);
2849 /* Readjust the selected index */
2850 if ((iItem == infoPtr->iSelected) && (iItem > 0))
2851 infoPtr->iSelected--;
2853 if (iItem < infoPtr->iSelected)
2854 infoPtr->iSelected--;
2856 if (infoPtr->uNumItem == 0)
2857 infoPtr->iSelected = -1;
2859 /* Reposition and repaint tabs */
2860 TAB_SetItemBounds(hwnd);
2862 bResult = TRUE;
2865 return bResult;
2868 static LRESULT
2869 TAB_DeleteAllItems (HWND hwnd, WPARAM wParam, LPARAM lParam)
2871 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2873 TAB_InvalidateTabArea(hwnd,infoPtr);
2875 Free (infoPtr->items);
2876 infoPtr->uNumItem = 0;
2877 infoPtr->iSelected = -1;
2878 if (infoPtr->iHotTracked >= 0)
2879 KillTimer(hwnd, TAB_HOTTRACK_TIMER);
2880 infoPtr->iHotTracked = -1;
2882 TAB_SetItemBounds(hwnd);
2883 return TRUE;
2887 static LRESULT
2888 TAB_GetFont (HWND hwnd, WPARAM wParam, LPARAM lParam)
2890 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2892 TRACE("\n");
2893 return (LRESULT)infoPtr->hFont;
2896 static LRESULT
2897 TAB_SetFont (HWND hwnd, WPARAM wParam, LPARAM lParam)
2900 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2902 TRACE("%x %lx\n",wParam, lParam);
2904 infoPtr->hFont = (HFONT)wParam;
2906 TAB_SetItemBounds(hwnd);
2908 TAB_InvalidateTabArea(hwnd, infoPtr);
2910 return 0;
2914 static LRESULT
2915 TAB_GetImageList (HWND hwnd, WPARAM wParam, LPARAM lParam)
2917 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2919 TRACE("\n");
2920 return (LRESULT)infoPtr->himl;
2923 static LRESULT
2924 TAB_SetImageList (HWND hwnd, WPARAM wParam, LPARAM lParam)
2926 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2927 HIMAGELIST himlPrev;
2929 TRACE("\n");
2930 himlPrev = infoPtr->himl;
2931 infoPtr->himl= (HIMAGELIST)lParam;
2932 return (LRESULT)himlPrev;
2935 static LRESULT
2936 TAB_GetUnicodeFormat (HWND hwnd)
2938 TAB_INFO *infoPtr = TAB_GetInfoPtr (hwnd);
2939 return infoPtr->bUnicode;
2942 static LRESULT
2943 TAB_SetUnicodeFormat (HWND hwnd, WPARAM wParam)
2945 TAB_INFO *infoPtr = TAB_GetInfoPtr (hwnd);
2946 BOOL bTemp = infoPtr->bUnicode;
2948 infoPtr->bUnicode = (BOOL)wParam;
2950 return bTemp;
2953 static LRESULT
2954 TAB_Size (HWND hwnd, WPARAM wParam, LPARAM lParam)
2957 /* I'm not really sure what the following code was meant to do.
2958 This is what it is doing:
2959 When WM_SIZE is sent with SIZE_RESTORED, the control
2960 gets positioned in the top left corner.
2962 RECT parent_rect;
2963 HWND parent;
2964 UINT uPosFlags,cx,cy;
2966 uPosFlags=0;
2967 if (!wParam) {
2968 parent = GetParent (hwnd);
2969 GetClientRect(parent, &parent_rect);
2970 cx=LOWORD (lParam);
2971 cy=HIWORD (lParam);
2972 if (GetWindowLongA(hwnd, GWL_STYLE) & CCS_NORESIZE)
2973 uPosFlags |= (SWP_NOSIZE | SWP_NOMOVE);
2975 SetWindowPos (hwnd, 0, parent_rect.left, parent_rect.top,
2976 cx, cy, uPosFlags | SWP_NOZORDER);
2977 } else {
2978 FIXME("WM_SIZE flag %x %lx not handled\n", wParam, lParam);
2979 } */
2981 /* Recompute the size/position of the tabs. */
2982 TAB_SetItemBounds (hwnd);
2984 /* Force a repaint of the control. */
2985 InvalidateRect(hwnd, NULL, TRUE);
2987 return 0;
2991 static LRESULT
2992 TAB_Create (HWND hwnd, WPARAM wParam, LPARAM lParam)
2994 TAB_INFO *infoPtr;
2995 TEXTMETRICA fontMetrics;
2996 HDC hdc;
2997 HFONT hOldFont;
2998 DWORD dwStyle;
3000 infoPtr = (TAB_INFO *)Alloc (sizeof(TAB_INFO));
3002 SetWindowLongA(hwnd, 0, (DWORD)infoPtr);
3004 infoPtr->uNumItem = 0;
3005 infoPtr->uNumRows = 0;
3006 infoPtr->uHItemPadding = 6;
3007 infoPtr->uVItemPadding = 3;
3008 infoPtr->hFont = 0;
3009 infoPtr->items = 0;
3010 infoPtr->hcurArrow = LoadCursorA (0, (LPSTR)IDC_ARROW);
3011 infoPtr->iSelected = -1;
3012 infoPtr->iHotTracked = -1;
3013 infoPtr->uFocus = -1;
3014 infoPtr->hwndToolTip = 0;
3015 infoPtr->DoRedraw = TRUE;
3016 infoPtr->needsScrolling = FALSE;
3017 infoPtr->hwndUpDown = 0;
3018 infoPtr->leftmostVisible = 0;
3019 infoPtr->fHeightSet = FALSE;
3020 infoPtr->bUnicode = IsWindowUnicode (hwnd);
3022 TRACE("Created tab control, hwnd [%p]\n", hwnd);
3024 /* The tab control always has the WS_CLIPSIBLINGS style. Even
3025 if you don't specify it in CreateWindow. This is necessary in
3026 order for paint to work correctly. This follows windows behaviour. */
3027 dwStyle = GetWindowLongA(hwnd, GWL_STYLE);
3028 SetWindowLongA(hwnd, GWL_STYLE, dwStyle|WS_CLIPSIBLINGS);
3030 if (dwStyle & TCS_TOOLTIPS) {
3031 /* Create tooltip control */
3032 infoPtr->hwndToolTip =
3033 CreateWindowExA (0, TOOLTIPS_CLASSA, NULL, 0,
3034 CW_USEDEFAULT, CW_USEDEFAULT,
3035 CW_USEDEFAULT, CW_USEDEFAULT,
3036 hwnd, 0, 0, 0);
3038 /* Send NM_TOOLTIPSCREATED notification */
3039 if (infoPtr->hwndToolTip) {
3040 NMTOOLTIPSCREATED nmttc;
3042 nmttc.hdr.hwndFrom = hwnd;
3043 nmttc.hdr.idFrom = GetWindowLongA(hwnd, GWL_ID);
3044 nmttc.hdr.code = NM_TOOLTIPSCREATED;
3045 nmttc.hwndToolTips = infoPtr->hwndToolTip;
3047 SendMessageA (GetParent (hwnd), WM_NOTIFY,
3048 (WPARAM)GetWindowLongA(hwnd, GWL_ID), (LPARAM)&nmttc);
3053 * We need to get text information so we need a DC and we need to select
3054 * a font.
3056 hdc = GetDC(hwnd);
3057 hOldFont = SelectObject (hdc, GetStockObject (SYSTEM_FONT));
3059 /* Use the system font to determine the initial height of a tab. */
3060 GetTextMetricsA(hdc, &fontMetrics);
3063 * Make sure there is enough space for the letters + growing the
3064 * selected item + extra space for the selected item.
3066 infoPtr->tabHeight = fontMetrics.tmHeight + SELECTED_TAB_OFFSET +
3067 ((dwStyle & TCS_BUTTONS) ? 2 : 1) *
3068 infoPtr->uVItemPadding;
3070 /* Initialize the width of a tab. */
3071 infoPtr->tabWidth = DEFAULT_TAB_WIDTH;
3072 infoPtr->tabMinWidth = 0;
3074 TRACE("tabH=%d, tabW=%d\n", infoPtr->tabHeight, infoPtr->tabWidth);
3076 SelectObject (hdc, hOldFont);
3077 ReleaseDC(hwnd, hdc);
3079 return 0;
3082 static LRESULT
3083 TAB_Destroy (HWND hwnd, WPARAM wParam, LPARAM lParam)
3085 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
3086 INT iItem;
3088 if (!infoPtr)
3089 return 0;
3091 if (infoPtr->items) {
3092 for (iItem = 0; iItem < infoPtr->uNumItem; iItem++) {
3093 if (infoPtr->items[iItem].pszText)
3094 Free (infoPtr->items[iItem].pszText);
3096 Free (infoPtr->items);
3099 if (infoPtr->hwndToolTip)
3100 DestroyWindow (infoPtr->hwndToolTip);
3102 if (infoPtr->hwndUpDown)
3103 DestroyWindow(infoPtr->hwndUpDown);
3105 if (infoPtr->iHotTracked >= 0)
3106 KillTimer(hwnd, TAB_HOTTRACK_TIMER);
3108 Free (infoPtr);
3109 SetWindowLongA(hwnd, 0, 0);
3110 return 0;
3113 static LRESULT WINAPI
3114 TAB_WindowProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
3117 TRACE("hwnd=%p msg=%x wParam=%x lParam=%lx\n", hwnd, uMsg, wParam, lParam);
3118 if (!TAB_GetInfoPtr(hwnd) && (uMsg != WM_CREATE))
3119 return DefWindowProcA (hwnd, uMsg, wParam, lParam);
3121 switch (uMsg)
3123 case TCM_GETIMAGELIST:
3124 return TAB_GetImageList (hwnd, wParam, lParam);
3126 case TCM_SETIMAGELIST:
3127 return TAB_SetImageList (hwnd, wParam, lParam);
3129 case TCM_GETITEMCOUNT:
3130 return TAB_GetItemCount (hwnd, wParam, lParam);
3132 case TCM_GETITEMA:
3133 return TAB_GetItemA (hwnd, wParam, lParam);
3135 case TCM_GETITEMW:
3136 return TAB_GetItemW (hwnd, wParam, lParam);
3138 case TCM_SETITEMA:
3139 return TAB_SetItemA (hwnd, wParam, lParam);
3141 case TCM_SETITEMW:
3142 return TAB_SetItemW (hwnd, wParam, lParam);
3144 case TCM_DELETEITEM:
3145 return TAB_DeleteItem (hwnd, wParam, lParam);
3147 case TCM_DELETEALLITEMS:
3148 return TAB_DeleteAllItems (hwnd, wParam, lParam);
3150 case TCM_GETITEMRECT:
3151 return TAB_GetItemRect (hwnd, wParam, lParam);
3153 case TCM_GETCURSEL:
3154 return TAB_GetCurSel (hwnd);
3156 case TCM_HITTEST:
3157 return TAB_HitTest (hwnd, wParam, lParam);
3159 case TCM_SETCURSEL:
3160 return TAB_SetCurSel (hwnd, wParam);
3162 case TCM_INSERTITEMA:
3163 return TAB_InsertItemA (hwnd, wParam, lParam);
3165 case TCM_INSERTITEMW:
3166 return TAB_InsertItemW (hwnd, wParam, lParam);
3168 case TCM_SETITEMEXTRA:
3169 FIXME("Unimplemented msg TCM_SETITEMEXTRA\n");
3170 return 0;
3172 case TCM_ADJUSTRECT:
3173 return TAB_AdjustRect (hwnd, (BOOL)wParam, (LPRECT)lParam);
3175 case TCM_SETITEMSIZE:
3176 return TAB_SetItemSize (hwnd, wParam, lParam);
3178 case TCM_REMOVEIMAGE:
3179 FIXME("Unimplemented msg TCM_REMOVEIMAGE\n");
3180 return 0;
3182 case TCM_SETPADDING:
3183 return TAB_SetPadding (hwnd, wParam, lParam);
3185 case TCM_GETROWCOUNT:
3186 return TAB_GetRowCount(hwnd);
3188 case TCM_GETUNICODEFORMAT:
3189 return TAB_GetUnicodeFormat (hwnd);
3191 case TCM_SETUNICODEFORMAT:
3192 return TAB_SetUnicodeFormat (hwnd, wParam);
3194 case TCM_HIGHLIGHTITEM:
3195 return TAB_HighlightItem (hwnd, wParam, lParam);
3197 case TCM_GETTOOLTIPS:
3198 return TAB_GetToolTips (hwnd, wParam, lParam);
3200 case TCM_SETTOOLTIPS:
3201 return TAB_SetToolTips (hwnd, wParam, lParam);
3203 case TCM_GETCURFOCUS:
3204 return TAB_GetCurFocus (hwnd);
3206 case TCM_SETCURFOCUS:
3207 return TAB_SetCurFocus (hwnd, wParam);
3209 case TCM_SETMINTABWIDTH:
3210 return TAB_SetMinTabWidth(hwnd, lParam);
3212 case TCM_DESELECTALL:
3213 FIXME("Unimplemented msg TCM_DESELECTALL\n");
3214 return 0;
3216 case TCM_GETEXTENDEDSTYLE:
3217 FIXME("Unimplemented msg TCM_GETEXTENDEDSTYLE\n");
3218 return 0;
3220 case TCM_SETEXTENDEDSTYLE:
3221 FIXME("Unimplemented msg TCM_SETEXTENDEDSTYLE\n");
3222 return 0;
3224 case WM_GETFONT:
3225 return TAB_GetFont (hwnd, wParam, lParam);
3227 case WM_SETFONT:
3228 return TAB_SetFont (hwnd, wParam, lParam);
3230 case WM_CREATE:
3231 return TAB_Create (hwnd, wParam, lParam);
3233 case WM_NCDESTROY:
3234 return TAB_Destroy (hwnd, wParam, lParam);
3236 case WM_GETDLGCODE:
3237 return DLGC_WANTARROWS | DLGC_WANTCHARS;
3239 case WM_LBUTTONDOWN:
3240 return TAB_LButtonDown (hwnd, wParam, lParam);
3242 case WM_LBUTTONUP:
3243 return TAB_LButtonUp (hwnd, wParam, lParam);
3245 case WM_NOTIFY:
3246 return SendMessageA(GetParent(hwnd), WM_NOTIFY, wParam, lParam);
3248 case WM_RBUTTONDOWN:
3249 return TAB_RButtonDown (hwnd, wParam, lParam);
3251 case WM_MOUSEMOVE:
3252 return TAB_MouseMove (hwnd, wParam, lParam);
3254 case WM_ERASEBKGND:
3255 return TAB_EraseBackground (hwnd, (HDC)wParam);
3257 case WM_PAINT:
3258 return TAB_Paint (hwnd, wParam);
3260 case WM_SIZE:
3261 return TAB_Size (hwnd, wParam, lParam);
3263 case WM_SETREDRAW:
3264 return TAB_SetRedraw (hwnd, wParam);
3266 case WM_HSCROLL:
3267 return TAB_OnHScroll(hwnd, (int)LOWORD(wParam), (int)HIWORD(wParam), (HWND)lParam);
3269 case WM_STYLECHANGED:
3270 TAB_SetItemBounds (hwnd);
3271 InvalidateRect(hwnd, NULL, TRUE);
3272 return 0;
3274 case WM_SYSCOLORCHANGE:
3275 COMCTL32_RefreshSysColors();
3276 return 0;
3278 case WM_KILLFOCUS:
3279 case WM_SETFOCUS:
3280 return TAB_FocusChanging(hwnd, uMsg, wParam, lParam);
3282 case WM_KEYUP:
3283 return TAB_KeyUp(hwnd, wParam);
3284 case WM_NCHITTEST:
3285 return TAB_NCHitTest(hwnd, lParam);
3287 default:
3288 if ((uMsg >= WM_USER) && (uMsg < WM_APP))
3289 WARN("unknown msg %04x wp=%08x lp=%08lx\n",
3290 uMsg, wParam, lParam);
3291 return DefWindowProcA(hwnd, uMsg, wParam, lParam);
3294 return 0;
3298 VOID
3299 TAB_Register (void)
3301 WNDCLASSA wndClass;
3303 ZeroMemory (&wndClass, sizeof(WNDCLASSA));
3304 wndClass.style = CS_GLOBALCLASS | CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
3305 wndClass.lpfnWndProc = (WNDPROC)TAB_WindowProc;
3306 wndClass.cbClsExtra = 0;
3307 wndClass.cbWndExtra = sizeof(TAB_INFO *);
3308 wndClass.hCursor = LoadCursorA (0, (LPSTR)IDC_ARROW);
3309 wndClass.hbrBackground = NULL;
3310 wndClass.lpszClassName = WC_TABCONTROLA;
3312 RegisterClassA (&wndClass);
3316 VOID
3317 TAB_Unregister (void)
3319 UnregisterClassA (WC_TABCONTROLA, NULL);