Added support for implementing VxDs as separate dlls and loading them
[wine.git] / dlls / comctl32 / tab.c
blobf38dd9cda25cda4ab20f49f0c277ce3f5d481f7d
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_SETITEMEXTRA
31 * TCM_REMOVEIMAGE
32 * TCM_DESELECTALL
33 * TCM_GETEXTENDEDSTYLE
34 * TCM_SETEXTENDEDSTYLE
36 * FIXME:
37 * UpDown control not displayed until after a tab is clicked on
40 #include <stdarg.h>
41 #include <string.h>
43 #include "windef.h"
44 #include "winbase.h"
45 #include "wingdi.h"
46 #include "winuser.h"
47 #include "winnls.h"
48 #include "commctrl.h"
49 #include "comctl32.h"
50 #include "wine/debug.h"
51 #include <math.h>
53 WINE_DEFAULT_DEBUG_CHANNEL(tab);
55 typedef struct
57 UINT mask;
58 DWORD dwState;
59 LPWSTR pszText;
60 INT iImage;
61 LPARAM lParam;
62 RECT rect; /* bounding rectangle of the item relative to the
63 * leftmost item (the leftmost item, 0, would have a
64 * "left" member of 0 in this rectangle)
66 * additionally the top member hold the row number
67 * and bottom is unused and should be 0 */
68 } TAB_ITEM;
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 } TAB_INFO;
100 /******************************************************************************
101 * Positioning constants
103 #define SELECTED_TAB_OFFSET 2
104 #define ROUND_CORNER_SIZE 2
105 #define DISPLAY_AREA_PADDINGX 2
106 #define DISPLAY_AREA_PADDINGY 2
107 #define CONTROL_BORDER_SIZEX 2
108 #define CONTROL_BORDER_SIZEY 2
109 #define BUTTON_SPACINGX 3
110 #define BUTTON_SPACINGY 4
111 #define FLAT_BTN_SPACINGX 8
112 #define DEFAULT_TAB_WIDTH 96
114 #define TAB_GetInfoPtr(hwnd) ((TAB_INFO *)GetWindowLongA(hwnd,0))
116 /******************************************************************************
117 * Hot-tracking timer constants
119 #define TAB_HOTTRACK_TIMER 1
120 #define TAB_HOTTRACK_TIMER_INTERVAL 100 /* milliseconds */
122 /******************************************************************************
123 * Prototypes
125 static void TAB_Refresh (HWND hwnd, HDC hdc);
126 static void TAB_InvalidateTabArea(HWND hwnd, TAB_INFO* infoPtr);
127 static void TAB_EnsureSelectionVisible(HWND hwnd, TAB_INFO* infoPtr);
128 static void TAB_DrawItem(HWND hwnd, HDC hdc, INT iItem);
129 static void TAB_DrawItemInterior(HWND hwnd, HDC hdc, INT iItem, RECT* drawRect);
131 static BOOL
132 TAB_SendSimpleNotify (HWND hwnd, UINT code)
134 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
135 NMHDR nmhdr;
137 nmhdr.hwndFrom = hwnd;
138 nmhdr.idFrom = GetWindowLongA(hwnd, GWL_ID);
139 nmhdr.code = code;
141 return (BOOL) SendMessageA (infoPtr->hwndNotify, WM_NOTIFY,
142 (WPARAM) nmhdr.idFrom, (LPARAM) &nmhdr);
145 static VOID
146 TAB_RelayEvent (HWND hwndTip, HWND hwndMsg, UINT uMsg,
147 WPARAM wParam, LPARAM lParam)
149 MSG msg;
151 msg.hwnd = hwndMsg;
152 msg.message = uMsg;
153 msg.wParam = wParam;
154 msg.lParam = lParam;
155 msg.time = GetMessageTime ();
156 msg.pt.x = LOWORD(GetMessagePos ());
157 msg.pt.y = HIWORD(GetMessagePos ());
159 SendMessageA (hwndTip, TTM_RELAYEVENT, 0, (LPARAM)&msg);
162 static void
163 TAB_DumpItemExternalA(TCITEMA *pti, UINT iItem)
165 if (TRACE_ON(tab)) {
166 TRACE("external tab %d, mask=0x%08x, dwState=0x%08x, dwStateMask=0x%08x, cchTextMax=0x%08x\n",
167 iItem, pti->mask, pti->dwState, pti->dwStateMask, pti->cchTextMax);
168 TRACE("external tab %d, iImage=%d, lParam=0x%08lx, pszTextA=%s\n",
169 iItem, pti->iImage, pti->lParam, debugstr_a(pti->pszText));
174 static void
175 TAB_DumpItemExternalW(TCITEMW *pti, UINT iItem)
177 if (TRACE_ON(tab)) {
178 TRACE("external tab %d, mask=0x%08x, dwState=0x%08lx, dwStateMask=0x%08lx, cchTextMax=0x%08x\n",
179 iItem, pti->mask, pti->dwState, pti->dwStateMask, pti->cchTextMax);
180 TRACE("external tab %d, iImage=%d, lParam=0x%08lx, pszTextW=%s\n",
181 iItem, pti->iImage, pti->lParam, debugstr_w(pti->pszText));
185 static void
186 TAB_DumpItemInternal(TAB_INFO *infoPtr, UINT iItem)
188 if (TRACE_ON(tab)) {
189 TAB_ITEM *ti;
191 ti = &infoPtr->items[iItem];
192 TRACE("tab %d, mask=0x%08x, dwState=0x%08lx, pszText=%s, iImage=%d\n",
193 iItem, ti->mask, ti->dwState, debugstr_w(ti->pszText),
194 ti->iImage);
195 TRACE("tab %d, lParam=0x%08lx, rect.left=%ld, rect.top(row)=%ld\n",
196 iItem, ti->lParam, ti->rect.left, ti->rect.top);
200 static LRESULT
201 TAB_GetCurSel (HWND hwnd)
203 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
205 return infoPtr->iSelected;
208 static LRESULT
209 TAB_GetCurFocus (HWND hwnd)
211 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
213 return infoPtr->uFocus;
216 static LRESULT
217 TAB_GetToolTips (HWND hwnd, WPARAM wParam, LPARAM lParam)
219 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
221 if (infoPtr == NULL) return 0;
222 return (LRESULT)infoPtr->hwndToolTip;
225 static LRESULT
226 TAB_SetCurSel (HWND hwnd,WPARAM wParam)
228 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
229 INT iItem = (INT)wParam;
230 INT prevItem;
232 prevItem = -1;
233 if ((iItem >= 0) && (iItem < infoPtr->uNumItem)) {
234 prevItem=infoPtr->iSelected;
235 infoPtr->iSelected=iItem;
236 TAB_EnsureSelectionVisible(hwnd, infoPtr);
237 TAB_InvalidateTabArea(hwnd, infoPtr);
239 return prevItem;
242 static LRESULT
243 TAB_SetCurFocus (HWND hwnd,WPARAM wParam)
245 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
246 INT iItem=(INT) wParam;
248 if ((iItem < 0) || (iItem >= infoPtr->uNumItem)) return 0;
250 if (GetWindowLongA(hwnd, GWL_STYLE) & TCS_BUTTONS) {
251 FIXME("Should set input focus\n");
252 } else {
253 int oldFocus = infoPtr->uFocus;
254 if (infoPtr->iSelected != iItem || infoPtr->uFocus == -1 ) {
255 infoPtr->uFocus = iItem;
256 if (oldFocus != -1) {
257 if (TAB_SendSimpleNotify(hwnd, TCN_SELCHANGING)!=TRUE) {
258 infoPtr->iSelected = iItem;
259 TAB_SendSimpleNotify(hwnd, TCN_SELCHANGE);
261 else
262 infoPtr->iSelected = iItem;
263 TAB_EnsureSelectionVisible(hwnd, infoPtr);
264 TAB_InvalidateTabArea(hwnd, infoPtr);
268 return 0;
271 static LRESULT
272 TAB_SetToolTips (HWND hwnd, WPARAM wParam, LPARAM lParam)
274 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
276 if (infoPtr == NULL) return 0;
277 infoPtr->hwndToolTip = (HWND)wParam;
278 return 0;
281 static LRESULT
282 TAB_SetPadding (HWND hwnd, WPARAM wParam, LPARAM lParam)
284 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
286 if (infoPtr == NULL) return 0;
287 infoPtr->uHItemPadding_s=LOWORD(lParam);
288 infoPtr->uVItemPadding_s=HIWORD(lParam);
289 return 0;
292 /******************************************************************************
293 * TAB_InternalGetItemRect
295 * This method will calculate the rectangle representing a given tab item in
296 * client coordinates. This method takes scrolling into account.
298 * This method returns TRUE if the item is visible in the window and FALSE
299 * if it is completely outside the client area.
301 static BOOL TAB_InternalGetItemRect(
302 HWND hwnd,
303 TAB_INFO* infoPtr,
304 INT itemIndex,
305 RECT* itemRect,
306 RECT* selectedRect)
308 RECT tmpItemRect,clientRect;
309 LONG lStyle = GetWindowLongA(hwnd, GWL_STYLE);
311 /* Perform a sanity check and a trivial visibility check. */
312 if ( (infoPtr->uNumItem <= 0) ||
313 (itemIndex >= infoPtr->uNumItem) ||
314 (!((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)) && (itemIndex < infoPtr->leftmostVisible)) )
315 return FALSE;
318 * Avoid special cases in this procedure by assigning the "out"
319 * parameters if the caller didn't supply them
321 if (itemRect == NULL)
322 itemRect = &tmpItemRect;
324 /* Retrieve the unmodified item rect. */
325 *itemRect = infoPtr->items[itemIndex].rect;
327 /* calculate the times bottom and top based on the row */
328 GetClientRect(hwnd, &clientRect);
330 if ((lStyle & TCS_BOTTOM) && !(lStyle & TCS_VERTICAL))
332 itemRect->bottom = clientRect.bottom -
333 itemRect->top * (infoPtr->tabHeight - 2) -
334 ((lStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : SELECTED_TAB_OFFSET);
336 itemRect->top = clientRect.bottom -
337 infoPtr->tabHeight -
338 itemRect->top * (infoPtr->tabHeight - 2) -
339 ((lStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : SELECTED_TAB_OFFSET);
341 else if((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
343 itemRect->right = clientRect.right - SELECTED_TAB_OFFSET - itemRect->left * (infoPtr->tabHeight - 2) -
344 ((lStyle & TCS_BUTTONS) ? itemRect->left * BUTTON_SPACINGY : 0);
345 itemRect->left = clientRect.right - infoPtr->tabHeight - itemRect->left * (infoPtr->tabHeight - 2) -
346 ((lStyle & TCS_BUTTONS) ? itemRect->left * BUTTON_SPACINGY : 0);
348 else if((lStyle & TCS_VERTICAL) && !(lStyle & TCS_BOTTOM))
350 itemRect->right = clientRect.left + infoPtr->tabHeight + itemRect->left * (infoPtr->tabHeight - 2) +
351 ((lStyle & TCS_BUTTONS) ? itemRect->left * BUTTON_SPACINGY : 0);
352 itemRect->left = clientRect.left + SELECTED_TAB_OFFSET + itemRect->left * (infoPtr->tabHeight - 2) +
353 ((lStyle & TCS_BUTTONS) ? itemRect->left * BUTTON_SPACINGY : 0);
355 else if(!(lStyle & TCS_VERTICAL) && !(lStyle & TCS_BOTTOM)) /* not TCS_BOTTOM and not TCS_VERTICAL */
357 itemRect->bottom = clientRect.top +
358 infoPtr->tabHeight +
359 itemRect->top * (infoPtr->tabHeight - 2) +
360 ((lStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : SELECTED_TAB_OFFSET);
361 itemRect->top = clientRect.top +
362 itemRect->top * (infoPtr->tabHeight - 2) +
363 ((lStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : SELECTED_TAB_OFFSET);
367 * "scroll" it to make sure the item at the very left of the
368 * tab control is the leftmost visible tab.
370 if(lStyle & TCS_VERTICAL)
372 OffsetRect(itemRect,
374 -(clientRect.bottom - infoPtr->items[infoPtr->leftmostVisible].rect.bottom));
377 * Move the rectangle so the first item is slightly offset from
378 * the bottom of the tab control.
380 OffsetRect(itemRect,
382 -SELECTED_TAB_OFFSET);
384 } else
386 OffsetRect(itemRect,
387 -infoPtr->items[infoPtr->leftmostVisible].rect.left,
391 * Move the rectangle so the first item is slightly offset from
392 * the left of the tab control.
394 OffsetRect(itemRect,
395 SELECTED_TAB_OFFSET,
398 TRACE("item %d tab h=%d, rect=(%ld,%ld)-(%ld,%ld)\n",
399 itemIndex, infoPtr->tabHeight,
400 itemRect->left, itemRect->top, itemRect->right, itemRect->bottom);
402 /* Now, calculate the position of the item as if it were selected. */
403 if (selectedRect!=NULL)
405 CopyRect(selectedRect, itemRect);
407 /* The rectangle of a selected item is a bit wider. */
408 if(lStyle & TCS_VERTICAL)
409 InflateRect(selectedRect, 0, SELECTED_TAB_OFFSET);
410 else
411 InflateRect(selectedRect, SELECTED_TAB_OFFSET, 0);
413 /* If it also a bit higher. */
414 if ((lStyle & TCS_BOTTOM) && !(lStyle & TCS_VERTICAL))
416 selectedRect->bottom += SELECTED_TAB_OFFSET;
418 else if((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
420 selectedRect->left -= 2; /* the border is thicker on the right */
421 selectedRect->right += SELECTED_TAB_OFFSET;
423 else if(lStyle & TCS_VERTICAL)
425 selectedRect->left -= SELECTED_TAB_OFFSET;
426 selectedRect->right += 1;
428 else
430 selectedRect->top -= SELECTED_TAB_OFFSET;
431 selectedRect->bottom -= 1;
435 return TRUE;
438 static BOOL TAB_GetItemRect(HWND hwnd, WPARAM wParam, LPARAM lParam)
440 return TAB_InternalGetItemRect(hwnd, TAB_GetInfoPtr(hwnd), (INT)wParam,
441 (LPRECT)lParam, (LPRECT)NULL);
444 /******************************************************************************
445 * TAB_KeyUp
447 * This method is called to handle keyboard input
449 static LRESULT TAB_KeyUp(
450 HWND hwnd,
451 WPARAM keyCode)
453 TAB_INFO* infoPtr = TAB_GetInfoPtr(hwnd);
454 int newItem = -1;
456 switch (keyCode)
458 case VK_LEFT:
459 newItem = infoPtr->uFocus - 1;
460 break;
461 case VK_RIGHT:
462 newItem = infoPtr->uFocus + 1;
463 break;
467 * If we changed to a valid item, change the selection
469 if ((newItem >= 0) &&
470 (newItem < infoPtr->uNumItem) &&
471 (infoPtr->uFocus != newItem))
473 if (!TAB_SendSimpleNotify(hwnd, TCN_SELCHANGING))
475 infoPtr->iSelected = newItem;
476 infoPtr->uFocus = newItem;
477 TAB_SendSimpleNotify(hwnd, TCN_SELCHANGE);
479 TAB_EnsureSelectionVisible(hwnd, infoPtr);
480 TAB_InvalidateTabArea(hwnd, infoPtr);
484 return 0;
487 /******************************************************************************
488 * TAB_FocusChanging
490 * This method is called whenever the focus goes in or out of this control
491 * it is used to update the visual state of the control.
493 static LRESULT TAB_FocusChanging(
494 HWND hwnd,
495 UINT uMsg,
496 WPARAM wParam,
497 LPARAM lParam)
499 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
500 RECT selectedRect;
501 BOOL isVisible;
504 * Get the rectangle for the item.
506 isVisible = TAB_InternalGetItemRect(hwnd,
507 infoPtr,
508 infoPtr->uFocus,
509 NULL,
510 &selectedRect);
513 * If the rectangle is not completely invisible, invalidate that
514 * portion of the window.
516 if (isVisible)
518 TRACE("invalidate (%ld,%ld)-(%ld,%ld)\n",
519 selectedRect.left,selectedRect.top,
520 selectedRect.right,selectedRect.bottom);
521 InvalidateRect(hwnd, &selectedRect, TRUE);
525 * Don't otherwise disturb normal behavior.
527 return DefWindowProcA (hwnd, uMsg, wParam, lParam);
530 static INT TAB_InternalHitTest (
531 HWND hwnd,
532 TAB_INFO* infoPtr,
533 POINT pt,
534 UINT* flags)
537 RECT rect;
538 INT iCount;
540 for (iCount = 0; iCount < infoPtr->uNumItem; iCount++)
542 TAB_InternalGetItemRect(hwnd, infoPtr, iCount, &rect, NULL);
544 if (PtInRect(&rect, pt))
546 *flags = TCHT_ONITEM;
547 return iCount;
551 *flags = TCHT_NOWHERE;
552 return -1;
555 static LRESULT
556 TAB_HitTest (HWND hwnd, WPARAM wParam, LPARAM lParam)
558 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
559 LPTCHITTESTINFO lptest = (LPTCHITTESTINFO) lParam;
561 return TAB_InternalHitTest (hwnd, infoPtr, lptest->pt, &lptest->flags);
564 /******************************************************************************
565 * TAB_NCHitTest
567 * Napster v2b5 has a tab control for its main navigation which has a client
568 * area that covers the whole area of the dialog pages.
569 * That's why it receives all msgs for that area and the underlying dialog ctrls
570 * are dead.
571 * So I decided that we should handle WM_NCHITTEST here and return
572 * HTTRANSPARENT if we don't hit the tab control buttons.
573 * FIXME: WM_NCHITTEST handling correct ? Fix it if you know that Windows
574 * doesn't do it that way. Maybe depends on tab control styles ?
576 static LRESULT
577 TAB_NCHitTest (HWND hwnd, LPARAM lParam)
579 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
580 POINT pt;
581 UINT dummyflag;
583 pt.x = LOWORD(lParam);
584 pt.y = HIWORD(lParam);
585 ScreenToClient(hwnd, &pt);
587 if (TAB_InternalHitTest(hwnd, infoPtr, pt, &dummyflag) == -1)
588 return HTTRANSPARENT;
589 else
590 return HTCLIENT;
593 static LRESULT
594 TAB_LButtonDown (HWND hwnd, WPARAM wParam, LPARAM lParam)
596 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
597 POINT pt;
598 INT newItem, dummy;
600 if (infoPtr->hwndToolTip)
601 TAB_RelayEvent (infoPtr->hwndToolTip, hwnd,
602 WM_LBUTTONDOWN, wParam, lParam);
604 if (GetWindowLongA(hwnd, GWL_STYLE) & TCS_FOCUSONBUTTONDOWN ) {
605 SetFocus (hwnd);
608 if (infoPtr->hwndToolTip)
609 TAB_RelayEvent (infoPtr->hwndToolTip, hwnd,
610 WM_LBUTTONDOWN, wParam, lParam);
612 pt.x = (INT)LOWORD(lParam);
613 pt.y = (INT)HIWORD(lParam);
615 newItem = TAB_InternalHitTest (hwnd, infoPtr, pt, &dummy);
617 TRACE("On Tab, item %d\n", newItem);
619 if ((newItem != -1) && (infoPtr->iSelected != newItem))
621 if (TAB_SendSimpleNotify(hwnd, TCN_SELCHANGING) != TRUE)
623 infoPtr->iSelected = newItem;
624 infoPtr->uFocus = newItem;
625 TAB_SendSimpleNotify(hwnd, TCN_SELCHANGE);
627 TAB_EnsureSelectionVisible(hwnd, infoPtr);
629 TAB_InvalidateTabArea(hwnd, infoPtr);
632 return 0;
635 static LRESULT
636 TAB_LButtonUp (HWND hwnd, WPARAM wParam, LPARAM lParam)
638 TAB_SendSimpleNotify(hwnd, NM_CLICK);
640 return 0;
643 static LRESULT
644 TAB_RButtonDown (HWND hwnd, WPARAM wParam, LPARAM lParam)
646 TAB_SendSimpleNotify(hwnd, NM_RCLICK);
647 return 0;
650 /******************************************************************************
651 * TAB_DrawLoneItemInterior
653 * This calls TAB_DrawItemInterior. However, TAB_DrawItemInterior is normally
654 * called by TAB_DrawItem which is normally called by TAB_Refresh which sets
655 * up the device context and font. This routine does the same setup but
656 * only calls TAB_DrawItemInterior for the single specified item.
658 static void
659 TAB_DrawLoneItemInterior(HWND hwnd, TAB_INFO* infoPtr, int iItem)
661 HDC hdc = GetDC(hwnd);
662 RECT r, rC;
664 /* Clip UpDown control to not draw over it */
665 if (infoPtr->needsScrolling)
667 GetWindowRect(hwnd, &rC);
668 GetWindowRect(infoPtr->hwndUpDown, &r);
669 ExcludeClipRect(hdc, r.left - rC.left, r.top - rC.top, r.right - rC.left, r.bottom - rC.top);
671 TAB_DrawItemInterior(hwnd, hdc, iItem, NULL);
672 ReleaseDC(hwnd, hdc);
675 /******************************************************************************
676 * TAB_HotTrackTimerProc
678 * When a mouse-move event causes a tab to be highlighted (hot-tracking), a
679 * timer is setup so we can check if the mouse is moved out of our window.
680 * (We don't get an event when the mouse leaves, the mouse-move events just
681 * stop being delivered to our window and just start being delivered to
682 * another window.) This function is called when the timer triggers so
683 * we can check if the mouse has left our window. If so, we un-highlight
684 * the hot-tracked tab.
686 static VOID CALLBACK
687 TAB_HotTrackTimerProc
689 HWND hwnd, /* handle of window for timer messages */
690 UINT uMsg, /* WM_TIMER message */
691 UINT idEvent, /* timer identifier */
692 DWORD dwTime /* current system time */
695 TAB_INFO* infoPtr = TAB_GetInfoPtr(hwnd);
697 if (infoPtr != NULL && infoPtr->iHotTracked >= 0)
699 POINT pt;
702 ** If we can't get the cursor position, or if the cursor is outside our
703 ** window, we un-highlight the hot-tracked tab. Note that the cursor is
704 ** "outside" even if it is within our bounding rect if another window
705 ** overlaps. Note also that the case where the cursor stayed within our
706 ** window but has moved off the hot-tracked tab will be handled by the
707 ** WM_MOUSEMOVE event.
709 if (!GetCursorPos(&pt) || WindowFromPoint(pt) != hwnd)
711 /* Redraw iHotTracked to look normal */
712 INT iRedraw = infoPtr->iHotTracked;
713 infoPtr->iHotTracked = -1;
714 TAB_DrawLoneItemInterior(hwnd, infoPtr, iRedraw);
716 /* Kill this timer */
717 KillTimer(hwnd, TAB_HOTTRACK_TIMER);
722 /******************************************************************************
723 * TAB_RecalcHotTrack
725 * If a tab control has the TCS_HOTTRACK style, then the tab under the mouse
726 * should be highlighted. This function determines which tab in a tab control,
727 * if any, is under the mouse and records that information. The caller may
728 * supply output parameters to receive the item number of the tab item which
729 * was highlighted but isn't any longer and of the tab item which is now
730 * highlighted but wasn't previously. The caller can use this information to
731 * selectively redraw those tab items.
733 * If the caller has a mouse position, it can supply it through the pos
734 * parameter. For example, TAB_MouseMove does this. Otherwise, the caller
735 * supplies NULL and this function determines the current mouse position
736 * itself.
738 static void
739 TAB_RecalcHotTrack
741 HWND hwnd,
742 const LPARAM* pos,
743 int* out_redrawLeave,
744 int* out_redrawEnter
747 TAB_INFO* infoPtr = TAB_GetInfoPtr(hwnd);
749 int item = -1;
752 if (out_redrawLeave != NULL)
753 *out_redrawLeave = -1;
754 if (out_redrawEnter != NULL)
755 *out_redrawEnter = -1;
757 if (GetWindowLongA(hwnd, GWL_STYLE) & TCS_HOTTRACK)
759 POINT pt;
760 UINT flags;
762 if (pos == NULL)
764 GetCursorPos(&pt);
765 ScreenToClient(hwnd, &pt);
767 else
769 pt.x = LOWORD(*pos);
770 pt.y = HIWORD(*pos);
773 item = TAB_InternalHitTest(hwnd, infoPtr, pt, &flags);
776 if (item != infoPtr->iHotTracked)
778 if (infoPtr->iHotTracked >= 0)
780 /* Mark currently hot-tracked to be redrawn to look normal */
781 if (out_redrawLeave != NULL)
782 *out_redrawLeave = infoPtr->iHotTracked;
784 if (item < 0)
786 /* Kill timer which forces recheck of mouse pos */
787 KillTimer(hwnd, TAB_HOTTRACK_TIMER);
790 else
792 /* Start timer so we recheck mouse pos */
793 UINT timerID = SetTimer
795 hwnd,
796 TAB_HOTTRACK_TIMER,
797 TAB_HOTTRACK_TIMER_INTERVAL,
798 TAB_HotTrackTimerProc
801 if (timerID == 0)
802 return; /* Hot tracking not available */
805 infoPtr->iHotTracked = item;
807 if (item >= 0)
809 /* Mark new hot-tracked to be redrawn to look highlighted */
810 if (out_redrawEnter != NULL)
811 *out_redrawEnter = item;
816 /******************************************************************************
817 * TAB_MouseMove
819 * Handles the mouse-move event. Updates tooltips. Updates hot-tracking.
821 static LRESULT
822 TAB_MouseMove (HWND hwnd, WPARAM wParam, LPARAM lParam)
824 int redrawLeave;
825 int redrawEnter;
827 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
829 if (infoPtr->hwndToolTip)
830 TAB_RelayEvent (infoPtr->hwndToolTip, hwnd,
831 WM_LBUTTONDOWN, wParam, lParam);
833 /* Determine which tab to highlight. Redraw tabs which change highlight
834 ** status. */
835 TAB_RecalcHotTrack(hwnd, &lParam, &redrawLeave, &redrawEnter);
837 if (redrawLeave != -1)
838 TAB_DrawLoneItemInterior(hwnd, infoPtr, redrawLeave);
839 if (redrawEnter != -1)
840 TAB_DrawLoneItemInterior(hwnd, infoPtr, redrawEnter);
842 return 0;
845 /******************************************************************************
846 * TAB_AdjustRect
848 * Calculates the tab control's display area given the window rectangle or
849 * the window rectangle given the requested display rectangle.
851 static LRESULT TAB_AdjustRect(
852 HWND hwnd,
853 WPARAM fLarger,
854 LPRECT prc)
856 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
857 DWORD lStyle = GetWindowLongA(hwnd, GWL_STYLE);
859 if(lStyle & TCS_VERTICAL)
861 if (fLarger) /* Go from display rectangle */
863 /* Add the height of the tabs. */
864 if (lStyle & TCS_BOTTOM)
865 prc->right += (infoPtr->tabHeight - 2) * infoPtr->uNumRows + 2;
866 else
867 prc->left -= (infoPtr->tabHeight - 2) * infoPtr->uNumRows + 2;
869 /* FIXME: not sure if these InflateRect's need to have different values for TCS_VERTICAL */
870 /* Inflate the rectangle for the padding */
871 InflateRect(prc, DISPLAY_AREA_PADDINGX, DISPLAY_AREA_PADDINGY);
873 /* Inflate for the border */
874 InflateRect(prc, CONTROL_BORDER_SIZEX, CONTROL_BORDER_SIZEX);
876 else /* Go from window rectangle. */
878 /* FIXME: not sure if these InflateRect's need to have different values for TCS_VERTICAL */
879 /* Deflate the rectangle for the border */
880 InflateRect(prc, -CONTROL_BORDER_SIZEX, -CONTROL_BORDER_SIZEX);
882 /* Deflate the rectangle for the padding */
883 InflateRect(prc, -DISPLAY_AREA_PADDINGX, -DISPLAY_AREA_PADDINGY);
885 /* Remove the height of the tabs. */
886 if (lStyle & TCS_BOTTOM)
887 prc->right -= (infoPtr->tabHeight - 2) * infoPtr->uNumRows + 2;
888 else
889 prc->left += (infoPtr->tabHeight - 2) * infoPtr->uNumRows + 2;
892 else {
893 if (fLarger) /* Go from display rectangle */
895 /* Add the height of the tabs. */
896 if (lStyle & TCS_BOTTOM)
897 prc->bottom += (infoPtr->tabHeight - 2) * infoPtr->uNumRows + 2;
898 else
899 prc->top -= (infoPtr->tabHeight - 2) * infoPtr->uNumRows + 2;
901 /* Inflate the rectangle for the padding */
902 InflateRect(prc, DISPLAY_AREA_PADDINGX, DISPLAY_AREA_PADDINGY);
904 /* Inflate for the border */
905 InflateRect(prc, CONTROL_BORDER_SIZEX, CONTROL_BORDER_SIZEX);
907 else /* Go from window rectangle. */
909 /* Deflate the rectangle for the border */
910 InflateRect(prc, -CONTROL_BORDER_SIZEX, -CONTROL_BORDER_SIZEX);
912 /* Deflate the rectangle for the padding */
913 InflateRect(prc, -DISPLAY_AREA_PADDINGX, -DISPLAY_AREA_PADDINGY);
915 /* Remove the height of the tabs. */
916 if (lStyle & TCS_BOTTOM)
917 prc->bottom -= (infoPtr->tabHeight - 2) * infoPtr->uNumRows + 2;
918 else
919 prc->top += (infoPtr->tabHeight - 2) * infoPtr->uNumRows + 2;
923 return 0;
926 /******************************************************************************
927 * TAB_OnHScroll
929 * This method will handle the notification from the scroll control and
930 * perform the scrolling operation on the tab control.
932 static LRESULT TAB_OnHScroll(
933 HWND hwnd,
934 int nScrollCode,
935 int nPos,
936 HWND hwndScroll)
938 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
940 if(nScrollCode == SB_THUMBPOSITION && nPos != infoPtr->leftmostVisible)
942 if(nPos < infoPtr->leftmostVisible)
943 infoPtr->leftmostVisible--;
944 else
945 infoPtr->leftmostVisible++;
947 TAB_RecalcHotTrack(hwnd, NULL, NULL, NULL);
948 TAB_InvalidateTabArea(hwnd, infoPtr);
949 SendMessageA(infoPtr->hwndUpDown, UDM_SETPOS, 0,
950 MAKELONG(infoPtr->leftmostVisible, 0));
953 return 0;
956 /******************************************************************************
957 * TAB_SetupScrolling
959 * This method will check the current scrolling state and make sure the
960 * scrolling control is displayed (or not).
962 static void TAB_SetupScrolling(
963 HWND hwnd,
964 TAB_INFO* infoPtr,
965 const RECT* clientRect)
967 INT maxRange = 0;
968 DWORD lStyle = GetWindowLongA(hwnd, GWL_STYLE);
970 if (infoPtr->needsScrolling)
972 RECT controlPos;
973 INT vsize, tabwidth;
976 * Calculate the position of the scroll control.
978 if(lStyle & TCS_VERTICAL)
980 controlPos.right = clientRect->right;
981 controlPos.left = controlPos.right - 2 * GetSystemMetrics(SM_CXHSCROLL);
983 if (lStyle & TCS_BOTTOM)
985 controlPos.top = clientRect->bottom - infoPtr->tabHeight;
986 controlPos.bottom = controlPos.top + GetSystemMetrics(SM_CYHSCROLL);
988 else
990 controlPos.bottom = clientRect->top + infoPtr->tabHeight;
991 controlPos.top = controlPos.bottom - GetSystemMetrics(SM_CYHSCROLL);
994 else
996 controlPos.right = clientRect->right;
997 controlPos.left = controlPos.right - 2 * GetSystemMetrics(SM_CXHSCROLL);
999 if (lStyle & TCS_BOTTOM)
1001 controlPos.top = clientRect->bottom - infoPtr->tabHeight;
1002 controlPos.bottom = controlPos.top + GetSystemMetrics(SM_CYHSCROLL);
1004 else
1006 controlPos.bottom = clientRect->top + infoPtr->tabHeight;
1007 controlPos.top = controlPos.bottom - GetSystemMetrics(SM_CYHSCROLL);
1012 * If we don't have a scroll control yet, we want to create one.
1013 * If we have one, we want to make sure it's positioned properly.
1015 if (infoPtr->hwndUpDown==0)
1017 infoPtr->hwndUpDown = CreateWindowA("msctls_updown32",
1019 WS_VISIBLE | WS_CHILD | UDS_HORZ,
1020 controlPos.left, controlPos.top,
1021 controlPos.right - controlPos.left,
1022 controlPos.bottom - controlPos.top,
1023 hwnd,
1024 NULL,
1025 NULL,
1026 NULL);
1028 else
1030 SetWindowPos(infoPtr->hwndUpDown,
1031 NULL,
1032 controlPos.left, controlPos.top,
1033 controlPos.right - controlPos.left,
1034 controlPos.bottom - controlPos.top,
1035 SWP_SHOWWINDOW | SWP_NOZORDER);
1038 /* Now calculate upper limit of the updown control range.
1039 * We do this by calculating how many tabs will be offscreen when the
1040 * last tab is visible.
1042 if(infoPtr->uNumItem)
1044 vsize = clientRect->right - (controlPos.right - controlPos.left + 1);
1045 maxRange = infoPtr->uNumItem;
1046 tabwidth = infoPtr->items[maxRange - 1].rect.right;
1048 for(; maxRange > 0; maxRange--)
1050 if(tabwidth - infoPtr->items[maxRange - 1].rect.left > vsize)
1051 break;
1054 if(maxRange == infoPtr->uNumItem)
1055 maxRange--;
1058 else
1060 /* If we once had a scroll control... hide it */
1061 if (infoPtr->hwndUpDown!=0)
1062 ShowWindow(infoPtr->hwndUpDown, SW_HIDE);
1064 if (infoPtr->hwndUpDown)
1065 SendMessageA(infoPtr->hwndUpDown, UDM_SETRANGE32, 0, maxRange);
1068 /******************************************************************************
1069 * TAB_SetItemBounds
1071 * This method will calculate the position rectangles of all the items in the
1072 * control. The rectangle calculated starts at 0 for the first item in the
1073 * list and ignores scrolling and selection.
1074 * It also uses the current font to determine the height of the tab row and
1075 * it checks if all the tabs fit in the client area of the window. If they
1076 * don't, a scrolling control is added.
1078 static void TAB_SetItemBounds (HWND hwnd)
1080 TAB_INFO* infoPtr = TAB_GetInfoPtr(hwnd);
1081 LONG lStyle = GetWindowLongA(hwnd, GWL_STYLE);
1082 TEXTMETRICA fontMetrics;
1083 INT curItem;
1084 INT curItemLeftPos;
1085 INT curItemRowCount;
1086 HFONT hFont, hOldFont;
1087 HDC hdc;
1088 RECT clientRect;
1089 SIZE size;
1090 INT iTemp;
1091 RECT* rcItem;
1092 INT iIndex;
1093 INT icon_width = 0;
1096 * We need to get text information so we need a DC and we need to select
1097 * a font.
1099 hdc = GetDC(hwnd);
1101 hFont = infoPtr->hFont ? infoPtr->hFont : GetStockObject (SYSTEM_FONT);
1102 hOldFont = SelectObject (hdc, hFont);
1105 * We will base the rectangle calculations on the client rectangle
1106 * of the control.
1108 GetClientRect(hwnd, &clientRect);
1110 /* if TCS_VERTICAL then swap the height and width so this code places the
1111 tabs along the top of the rectangle and we can just rotate them after
1112 rather than duplicate all of the below code */
1113 if(lStyle & TCS_VERTICAL)
1115 iTemp = clientRect.bottom;
1116 clientRect.bottom = clientRect.right;
1117 clientRect.right = iTemp;
1120 /* Now use hPadding and vPadding */
1121 infoPtr->uHItemPadding = infoPtr->uHItemPadding_s;
1122 infoPtr->uVItemPadding = infoPtr->uVItemPadding_s;
1124 /* The leftmost item will be "0" aligned */
1125 curItemLeftPos = 0;
1126 curItemRowCount = infoPtr->uNumItem ? 1 : 0;
1128 if (!(infoPtr->fHeightSet))
1130 int item_height;
1131 int icon_height = 0;
1133 /* Use the current font to determine the height of a tab. */
1134 GetTextMetricsA(hdc, &fontMetrics);
1136 /* Get the icon height */
1137 if (infoPtr->himl)
1138 ImageList_GetIconSize(infoPtr->himl, 0, &icon_height);
1140 /* Take the highest between font or icon */
1141 if (fontMetrics.tmHeight > icon_height)
1142 item_height = fontMetrics.tmHeight + 2;
1143 else
1144 item_height = icon_height;
1147 * Make sure there is enough space for the letters + icon + growing the
1148 * selected item + extra space for the selected item.
1150 infoPtr->tabHeight = item_height + SELECTED_TAB_OFFSET +
1151 ((lStyle & TCS_BUTTONS) ? 2 : 1) *
1152 infoPtr->uVItemPadding;
1154 TRACE("tabH=%d, tmH=%ld, iconh=%d\n",
1155 infoPtr->tabHeight, fontMetrics.tmHeight, icon_height);
1158 TRACE("client right=%ld\n", clientRect.right);
1160 /* Get the icon width */
1161 if (infoPtr->himl)
1163 ImageList_GetIconSize(infoPtr->himl, &icon_width, 0);
1165 if (lStyle & TCS_FIXEDWIDTH)
1166 icon_width += 4;
1167 else
1168 /* Add padding if icon is present */
1169 icon_width += infoPtr->uHItemPadding;
1172 for (curItem = 0; curItem < infoPtr->uNumItem; curItem++)
1174 /* Set the leftmost position of the tab. */
1175 infoPtr->items[curItem].rect.left = curItemLeftPos;
1177 if (lStyle & TCS_FIXEDWIDTH)
1179 infoPtr->items[curItem].rect.right = infoPtr->items[curItem].rect.left +
1180 max(infoPtr->tabWidth, icon_width);
1182 else
1184 int num = 2;
1186 /* Calculate how wide the tab is depending on the text it contains */
1187 GetTextExtentPoint32W(hdc, infoPtr->items[curItem].pszText,
1188 lstrlenW(infoPtr->items[curItem].pszText), &size);
1190 infoPtr->items[curItem].rect.right = infoPtr->items[curItem].rect.left +
1191 size.cx + icon_width +
1192 num * infoPtr->uHItemPadding;
1193 TRACE("for <%s>, l,r=%ld,%ld, num=%d\n",
1194 debugstr_w(infoPtr->items[curItem].pszText),
1195 infoPtr->items[curItem].rect.left,
1196 infoPtr->items[curItem].rect.right,
1197 num);
1201 * Check if this is a multiline tab control and if so
1202 * check to see if we should wrap the tabs
1204 * Wrap all these tabs. We will arange them evenly later.
1208 if (((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)) &&
1209 (infoPtr->items[curItem].rect.right > clientRect.right))
1211 infoPtr->items[curItem].rect.right -=
1212 infoPtr->items[curItem].rect.left;
1214 infoPtr->items[curItem].rect.left = 0;
1215 curItemRowCount++;
1216 TRACE("wrapping <%s>, l,r=%ld,%ld\n",
1217 debugstr_w(infoPtr->items[curItem].pszText),
1218 infoPtr->items[curItem].rect.left,
1219 infoPtr->items[curItem].rect.right);
1222 infoPtr->items[curItem].rect.bottom = 0;
1223 infoPtr->items[curItem].rect.top = curItemRowCount - 1;
1225 TRACE("TextSize: %li\n", size.cx);
1226 TRACE("Rect: T %li, L %li, B %li, R %li\n",
1227 infoPtr->items[curItem].rect.top,
1228 infoPtr->items[curItem].rect.left,
1229 infoPtr->items[curItem].rect.bottom,
1230 infoPtr->items[curItem].rect.right);
1233 * The leftmost position of the next item is the rightmost position
1234 * of this one.
1236 if (lStyle & TCS_BUTTONS)
1238 curItemLeftPos = infoPtr->items[curItem].rect.right + BUTTON_SPACINGX;
1239 if (lStyle & TCS_FLATBUTTONS)
1240 curItemLeftPos += FLAT_BTN_SPACINGX;
1242 else
1243 curItemLeftPos = infoPtr->items[curItem].rect.right;
1246 if (!((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)))
1249 * Check if we need a scrolling control.
1251 infoPtr->needsScrolling = (curItemLeftPos + (2 * SELECTED_TAB_OFFSET) >
1252 clientRect.right);
1254 /* Don't need scrolling, then update infoPtr->leftmostVisible */
1255 if(!infoPtr->needsScrolling)
1256 infoPtr->leftmostVisible = 0;
1258 else
1261 * No scrolling in Multiline or Vertical styles.
1263 infoPtr->needsScrolling = FALSE;
1264 infoPtr->leftmostVisible = 0;
1266 TAB_SetupScrolling(hwnd, infoPtr, &clientRect);
1268 /* Set the number of rows */
1269 infoPtr->uNumRows = curItemRowCount;
1271 /* Arange all tabs evenly if style says so */
1272 if (!(lStyle & TCS_RAGGEDRIGHT) && ((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)) && (infoPtr->uNumItem > 0))
1274 INT tabPerRow,remTab;
1275 INT iRow,iItm;
1276 INT iCount=0;
1279 * Ok windows tries to even out the rows. place the same
1280 * number of tabs in each row. So lets give that a shot
1283 tabPerRow = infoPtr->uNumItem / (infoPtr->uNumRows);
1284 remTab = infoPtr->uNumItem % (infoPtr->uNumRows);
1286 for (iItm=0,iRow=0,iCount=0,curItemLeftPos=0;
1287 iItm<infoPtr->uNumItem;
1288 iItm++,iCount++)
1290 /* normalize the current rect */
1292 /* shift the item to the left side of the clientRect */
1293 infoPtr->items[iItm].rect.right -=
1294 infoPtr->items[iItm].rect.left;
1295 infoPtr->items[iItm].rect.left = 0;
1297 TRACE("r=%ld, cl=%d, cl.r=%ld, iCount=%d, iRow=%d, uNumRows=%d, remTab=%d, tabPerRow=%d\n",
1298 infoPtr->items[iItm].rect.right,
1299 curItemLeftPos, clientRect.right,
1300 iCount, iRow, infoPtr->uNumRows, remTab, tabPerRow);
1302 /* if we have reached the maximum number of tabs on this row */
1303 /* move to the next row, reset our current item left position and */
1304 /* the count of items on this row */
1306 /* ************ FIXME FIXME FIXME *************** */
1307 /* */
1308 /* FIXME: */
1309 /* if vertical, */
1310 /* if item n and n+1 are in the same row, */
1311 /* then the display has n+1 lower (toward the */
1312 /* bottom) than n. We do it just the */
1313 /* opposite!!! */
1314 /* */
1315 /* ************ FIXME FIXME FIXME *************** */
1317 if (lStyle & TCS_VERTICAL) {
1318 /* Vert: Add the remaining tabs in the *last* remainder rows */
1319 if (iCount >= ((iRow>=(INT)infoPtr->uNumRows - remTab)?tabPerRow + 1:tabPerRow)) {
1320 iRow++;
1321 curItemLeftPos = 0;
1322 iCount = 0;
1324 } else {
1325 /* Horz: Add the remaining tabs in the *first* remainder rows */
1326 if (iCount >= ((iRow<remTab)?tabPerRow + 1:tabPerRow)) {
1327 iRow++;
1328 curItemLeftPos = 0;
1329 iCount = 0;
1333 /* shift the item to the right to place it as the next item in this row */
1334 infoPtr->items[iItm].rect.left += curItemLeftPos;
1335 infoPtr->items[iItm].rect.right += curItemLeftPos;
1336 infoPtr->items[iItm].rect.top = iRow;
1337 if (lStyle & TCS_BUTTONS)
1339 curItemLeftPos = infoPtr->items[iItm].rect.right + 1;
1340 if (lStyle & TCS_FLATBUTTONS)
1341 curItemLeftPos += FLAT_BTN_SPACINGX;
1343 else
1344 curItemLeftPos = infoPtr->items[iItm].rect.right;
1346 TRACE("arranging <%s>, l,r=%ld,%ld, row=%ld\n",
1347 debugstr_w(infoPtr->items[iItm].pszText),
1348 infoPtr->items[iItm].rect.left,
1349 infoPtr->items[iItm].rect.right,
1350 infoPtr->items[iItm].rect.top);
1354 * Justify the rows
1357 INT widthDiff, iIndexStart=0, iIndexEnd=0;
1358 INT remainder;
1359 INT iCount=0;
1361 while(iIndexStart < infoPtr->uNumItem)
1364 * find the indexs of the row
1366 /* find the first item on the next row */
1367 for (iIndexEnd=iIndexStart;
1368 (iIndexEnd < infoPtr->uNumItem) &&
1369 (infoPtr->items[iIndexEnd].rect.top ==
1370 infoPtr->items[iIndexStart].rect.top) ;
1371 iIndexEnd++)
1372 /* intentionally blank */;
1375 * we need to justify these tabs so they fill the whole given
1376 * client area
1379 /* find the amount of space remaining on this row */
1380 widthDiff = clientRect.right - (2 * SELECTED_TAB_OFFSET) -
1381 infoPtr->items[iIndexEnd - 1].rect.right;
1383 /* iCount is the number of tab items on this row */
1384 iCount = iIndexEnd - iIndexStart;
1386 if (iCount > 1)
1388 remainder = widthDiff % iCount;
1389 widthDiff = widthDiff / iCount;
1390 /* add widthDiff/iCount, or extra space/items on row, to each item on this row */
1391 for (iIndex=iIndexStart, iCount=0; iIndex < iIndexEnd; iIndex++, iCount++)
1393 infoPtr->items[iIndex].rect.left += iCount * widthDiff;
1394 infoPtr->items[iIndex].rect.right += (iCount + 1) * widthDiff;
1396 TRACE("adjusting 1 <%s>, l,r=%ld,%ld\n",
1397 debugstr_w(infoPtr->items[iIndex].pszText),
1398 infoPtr->items[iIndex].rect.left,
1399 infoPtr->items[iIndex].rect.right);
1402 infoPtr->items[iIndex - 1].rect.right += remainder;
1404 else /* we have only one item on this row, make it take up the entire row */
1406 infoPtr->items[iIndexStart].rect.left = clientRect.left;
1407 infoPtr->items[iIndexStart].rect.right = clientRect.right - 4;
1409 TRACE("adjusting 2 <%s>, l,r=%ld,%ld\n",
1410 debugstr_w(infoPtr->items[iIndexStart].pszText),
1411 infoPtr->items[iIndexStart].rect.left,
1412 infoPtr->items[iIndexStart].rect.right);
1417 iIndexStart = iIndexEnd;
1422 /* if TCS_VERTICAL rotate the tabs so they are along the side of the clientRect */
1423 if(lStyle & TCS_VERTICAL)
1425 RECT rcOriginal;
1426 for(iIndex = 0; iIndex < infoPtr->uNumItem; iIndex++)
1428 rcItem = &(infoPtr->items[iIndex].rect);
1430 rcOriginal = *rcItem;
1432 /* this is rotating the items by 90 degrees around the center of the control */
1433 rcItem->top = (clientRect.right - (rcOriginal.left - clientRect.left)) - (rcOriginal.right - rcOriginal.left);
1434 rcItem->bottom = rcItem->top + (rcOriginal.right - rcOriginal.left);
1435 rcItem->left = rcOriginal.top;
1436 rcItem->right = rcOriginal.bottom;
1440 TAB_EnsureSelectionVisible(hwnd,infoPtr);
1441 TAB_RecalcHotTrack(hwnd, NULL, NULL, NULL);
1443 /* Cleanup */
1444 SelectObject (hdc, hOldFont);
1445 ReleaseDC (hwnd, hdc);
1449 static void
1450 TAB_EraseTabInterior
1452 HWND hwnd,
1453 HDC hdc,
1454 INT iItem,
1455 RECT* drawRect
1458 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
1459 LONG lStyle = GetWindowLongA(hwnd, GWL_STYLE);
1460 HBRUSH hbr = CreateSolidBrush (comctl32_color.clrBtnFace);
1461 BOOL deleteBrush = TRUE;
1462 RECT rTemp = *drawRect;
1464 InflateRect(&rTemp, -2, -2);
1465 if (lStyle & TCS_BUTTONS)
1467 if (iItem == infoPtr->iSelected)
1469 /* Background color */
1470 if (!(lStyle & TCS_OWNERDRAWFIXED))
1472 DeleteObject(hbr);
1473 hbr = GetSysColorBrush(COLOR_SCROLLBAR);
1475 SetTextColor(hdc, comctl32_color.clr3dFace);
1476 SetBkColor(hdc, comctl32_color.clr3dHilight);
1478 /* if COLOR_WINDOW happens to be the same as COLOR_3DHILIGHT
1479 * we better use 0x55aa bitmap brush to make scrollbar's background
1480 * look different from the window background.
1482 if (comctl32_color.clr3dHilight == comctl32_color.clrWindow)
1483 hbr = COMCTL32_hPattern55AABrush;
1485 deleteBrush = FALSE;
1487 FillRect(hdc, &rTemp, hbr);
1489 else /* ! selected */
1491 if (lStyle & TCS_FLATBUTTONS)
1493 FillRect(hdc, drawRect, hbr);
1494 if (iItem == infoPtr->iHotTracked)
1495 DrawEdge(hdc, drawRect, EDGE_RAISED, BF_SOFT|BF_RECT);
1497 else
1498 FillRect(hdc, &rTemp, hbr);
1502 else /* !TCS_BUTTONS */
1504 FillRect(hdc, &rTemp, hbr);
1507 /* Cleanup */
1508 if (deleteBrush) DeleteObject(hbr);
1511 /******************************************************************************
1512 * TAB_DrawItemInterior
1514 * This method is used to draw the interior (text and icon) of a single tab
1515 * into the tab control.
1517 static void
1518 TAB_DrawItemInterior
1520 HWND hwnd,
1521 HDC hdc,
1522 INT iItem,
1523 RECT* drawRect
1526 TAB_INFO* infoPtr = TAB_GetInfoPtr(hwnd);
1527 LONG lStyle = GetWindowLongA(hwnd, GWL_STYLE);
1529 RECT localRect;
1531 HPEN htextPen;
1532 HPEN holdPen;
1533 INT oldBkMode;
1534 HFONT hOldFont;
1536 if (drawRect == NULL)
1538 BOOL isVisible;
1539 RECT itemRect;
1540 RECT selectedRect;
1543 * Get the rectangle for the item.
1545 isVisible = TAB_InternalGetItemRect(hwnd, infoPtr, iItem, &itemRect, &selectedRect);
1546 if (!isVisible)
1547 return;
1550 * Make sure drawRect points to something valid; simplifies code.
1552 drawRect = &localRect;
1555 * This logic copied from the part of TAB_DrawItem which draws
1556 * the tab background. It's important to keep it in sync. I
1557 * would have liked to avoid code duplication, but couldn't figure
1558 * out how without making spaghetti of TAB_DrawItem.
1560 if (lStyle & TCS_BUTTONS)
1562 *drawRect = itemRect;
1563 if (iItem == infoPtr->iSelected)
1565 OffsetRect(drawRect, 1, 1);
1568 else
1570 if (iItem == infoPtr->iSelected)
1572 *drawRect = selectedRect;
1573 if (lStyle & TCS_BOTTOM)
1575 if (lStyle & TCS_VERTICAL)
1577 drawRect->left++;
1579 else
1581 drawRect->top += 3;
1582 drawRect->left += 1;
1586 else
1587 *drawRect = itemRect;
1590 if (lStyle & TCS_BOTTOM && !(lStyle & TCS_VERTICAL))
1592 drawRect->top--;
1593 drawRect->bottom--;
1597 TRACE("drawRect=(%ld,%ld)-(%ld,%ld)\n",
1598 drawRect->left, drawRect->top, drawRect->right, drawRect->bottom);
1600 /* Clear interior */
1601 TAB_EraseTabInterior (hwnd, hdc, iItem, drawRect);
1603 /* Draw the focus rectangle */
1604 if (!(lStyle & TCS_FOCUSNEVER) &&
1605 (GetFocus() == hwnd) &&
1606 (iItem == infoPtr->uFocus) )
1608 RECT rFocus = *drawRect;
1609 InflateRect(&rFocus, -3, -3);
1610 if (lStyle & TCS_BOTTOM && !(lStyle & TCS_VERTICAL))
1611 rFocus.top -= 3;
1612 if (lStyle & TCS_BUTTONS)
1614 rFocus.left -= 3;
1615 rFocus.top -= 3;
1618 DrawFocusRect(hdc, &rFocus);
1622 * Text pen
1624 htextPen = CreatePen( PS_SOLID, 1, GetSysColor(COLOR_BTNTEXT) );
1625 holdPen = SelectObject(hdc, htextPen);
1626 hOldFont = SelectObject(hdc, infoPtr->hFont);
1629 * Setup for text output
1631 oldBkMode = SetBkMode(hdc, TRANSPARENT);
1632 SetTextColor(hdc, (((iItem == infoPtr->iHotTracked) && !(lStyle & TCS_FLATBUTTONS)) |
1633 (infoPtr->items[iItem].dwState & TCIS_HIGHLIGHTED)) ?
1634 comctl32_color.clrHighlight : comctl32_color.clrBtnText);
1637 * if owner draw, tell the owner to draw
1639 if ((lStyle & TCS_OWNERDRAWFIXED) && GetParent(hwnd))
1641 DRAWITEMSTRUCT dis;
1642 UINT id;
1645 * get the control id
1647 id = GetWindowLongA( hwnd, GWL_ID );
1650 * put together the DRAWITEMSTRUCT
1652 dis.CtlType = ODT_TAB;
1653 dis.CtlID = id;
1654 dis.itemID = iItem;
1655 dis.itemAction = ODA_DRAWENTIRE;
1656 dis.itemState = 0;
1657 if ( iItem == infoPtr->iSelected )
1658 dis.itemState |= ODS_SELECTED;
1659 if (infoPtr->uFocus == iItem)
1660 dis.itemState |= ODS_FOCUS;
1661 dis.hwndItem = hwnd; /* */
1662 dis.hDC = hdc;
1663 CopyRect(&dis.rcItem,drawRect);
1664 InflateRect(&dis.rcItem, -2, -2);
1665 dis.itemData = infoPtr->items[iItem].lParam;
1668 * send the draw message
1670 SendMessageA( infoPtr->hwndNotify, WM_DRAWITEM, (WPARAM)id, (LPARAM)&dis );
1672 else
1674 RECT rcTemp;
1675 RECT rcImage;
1677 /* used to center the icon and text in the tab */
1678 RECT rcText;
1679 INT center_offset_h, center_offset_v;
1681 /* set rcImage to drawRect, we will use top & left in our ImageList_Draw call */
1682 rcImage = *drawRect;
1684 rcTemp = *drawRect;
1686 rcText.left = rcText.top = rcText.right = rcText.bottom = 0;
1688 /* get the rectangle that the text fits in */
1689 DrawTextW(hdc, infoPtr->items[iItem].pszText, -1,
1690 &rcText, DT_CALCRECT);
1692 * If not owner draw, then do the drawing ourselves.
1694 * Draw the icon.
1696 if (infoPtr->himl && (infoPtr->items[iItem].mask & TCIF_IMAGE))
1698 INT cx;
1699 INT cy;
1701 ImageList_GetIconSize(infoPtr->himl, &cx, &cy);
1703 if(lStyle & TCS_VERTICAL)
1705 center_offset_h = ((drawRect->bottom - drawRect->top) - (cy + infoPtr->uHItemPadding + (rcText.right - rcText.left))) / 2;
1706 center_offset_v = ((drawRect->right - drawRect->left) - (cx + infoPtr->uVItemPadding)) / 2;
1708 else
1710 center_offset_h = ((drawRect->right - drawRect->left) - (cx + infoPtr->uHItemPadding + (rcText.right - rcText.left))) / 2;
1711 center_offset_v = ((drawRect->bottom - drawRect->top) - (cy + infoPtr->uVItemPadding)) / 2;
1714 if (lStyle & TCS_FIXEDWIDTH && lStyle & (TCS_FORCELABELLEFT | TCS_FORCEICONLEFT))
1715 center_offset_h = infoPtr->uHItemPadding;
1717 if (center_offset_h < 2)
1718 center_offset_h = 2;
1720 TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%ld,%ld)-(%ld,%ld), textlen=%ld\n",
1721 debugstr_w(infoPtr->items[iItem].pszText), center_offset_h, center_offset_v,
1722 drawRect->left, drawRect->top, drawRect->right, drawRect->bottom,
1723 (rcText.right-rcText.left));
1725 if((lStyle & TCS_VERTICAL) && (lStyle & TCS_BOTTOM))
1727 rcImage.top = drawRect->top + center_offset_h;
1728 /* if tab is TCS_VERTICAL and TCS_BOTTOM, the text is drawn from the */
1729 /* right side of the tab, but the image still uses the left as its x position */
1730 /* this keeps the image always drawn off of the same side of the tab */
1731 rcImage.left = drawRect->right - cx - center_offset_v;
1732 drawRect->top += cy + infoPtr->uHItemPadding;
1734 else if(lStyle & TCS_VERTICAL)
1736 rcImage.top = drawRect->bottom - cy - center_offset_h;
1737 rcImage.left = drawRect->left + center_offset_v;
1738 drawRect->bottom -= cy + infoPtr->uHItemPadding;
1740 else /* normal style, whether TCS_BOTTOM or not */
1742 rcImage.left = drawRect->left + center_offset_h;
1743 rcImage.top = drawRect->top + center_offset_v;
1744 drawRect->left += cx + infoPtr->uHItemPadding;
1747 TRACE("drawing image=%d, left=%ld, top=%ld\n",
1748 infoPtr->items[iItem].iImage, rcImage.left, rcImage.top-1);
1749 ImageList_Draw
1751 infoPtr->himl,
1752 infoPtr->items[iItem].iImage,
1753 hdc,
1754 rcImage.left,
1755 rcImage.top,
1756 ILD_NORMAL
1760 /* Now position text */
1761 if (lStyle & TCS_FIXEDWIDTH && lStyle & TCS_FORCELABELLEFT)
1762 center_offset_h = infoPtr->uHItemPadding;
1763 else
1764 if(lStyle & TCS_VERTICAL)
1765 center_offset_h = ((drawRect->bottom - drawRect->top) - (rcText.right - rcText.left)) / 2;
1766 else
1767 center_offset_h = ((drawRect->right - drawRect->left) - (rcText.right - rcText.left)) / 2;
1769 if(lStyle & TCS_VERTICAL)
1771 if(lStyle & TCS_BOTTOM)
1772 drawRect->top+=center_offset_h;
1773 else
1774 drawRect->bottom-=center_offset_h;
1776 drawRect->left += ((drawRect->right - drawRect->left) - ((rcText.bottom - rcText.top) + infoPtr->uVItemPadding)) / 2;
1778 else
1780 drawRect->left += center_offset_h;
1781 drawRect->top += ((drawRect->bottom - drawRect->top) - ((rcText.bottom - rcText.top) + infoPtr->uVItemPadding)) / 2;
1784 /* Draw the text */
1785 if(lStyle & TCS_VERTICAL) /* if we are vertical rotate the text and each character */
1787 LOGFONTA logfont;
1788 HFONT hFont = 0;
1789 INT nEscapement = 900;
1790 INT nOrientation = 900;
1792 if(lStyle & TCS_BOTTOM)
1794 nEscapement = -900;
1795 nOrientation = -900;
1798 /* to get a font with the escapement and orientation we are looking for, we need to */
1799 /* call CreateFontIndirectA, which requires us to set the values of the logfont we pass in */
1800 if (!GetObjectA((infoPtr->hFont) ?
1801 infoPtr->hFont : GetStockObject(SYSTEM_FONT),
1802 sizeof(LOGFONTA),&logfont))
1804 INT iPointSize = 9;
1806 lstrcpyA(logfont.lfFaceName, "Arial");
1807 logfont.lfHeight = -MulDiv(iPointSize, GetDeviceCaps(hdc, LOGPIXELSY),
1808 72);
1809 logfont.lfWeight = FW_NORMAL;
1810 logfont.lfItalic = 0;
1811 logfont.lfUnderline = 0;
1812 logfont.lfStrikeOut = 0;
1815 logfont.lfEscapement = nEscapement;
1816 logfont.lfOrientation = nOrientation;
1817 hFont = CreateFontIndirectA(&logfont);
1818 SelectObject(hdc, hFont);
1820 ExtTextOutW(hdc,
1821 (lStyle & TCS_BOTTOM) ? drawRect->right : drawRect->left,
1822 (!(lStyle & TCS_BOTTOM)) ? drawRect->bottom : drawRect->top,
1823 ETO_CLIPPED,
1824 drawRect,
1825 infoPtr->items[iItem].pszText,
1826 lstrlenW(infoPtr->items[iItem].pszText),
1829 DeleteObject(hFont);
1831 else
1833 DrawTextW
1835 hdc,
1836 infoPtr->items[iItem].pszText,
1837 lstrlenW(infoPtr->items[iItem].pszText),
1838 drawRect,
1839 DT_LEFT | DT_SINGLELINE
1843 *drawRect = rcTemp; /* restore drawRect */
1847 * Cleanup
1849 SelectObject(hdc, hOldFont);
1850 SetBkMode(hdc, oldBkMode);
1851 SelectObject(hdc, holdPen);
1852 DeleteObject( htextPen );
1855 /******************************************************************************
1856 * TAB_DrawItem
1858 * This method is used to draw a single tab into the tab control.
1860 static void TAB_DrawItem(
1861 HWND hwnd,
1862 HDC hdc,
1863 INT iItem)
1865 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
1866 LONG lStyle = GetWindowLongA(hwnd, GWL_STYLE);
1867 RECT itemRect;
1868 RECT selectedRect;
1869 BOOL isVisible;
1870 RECT r, fillRect, r1;
1871 INT clRight = 0;
1872 INT clBottom = 0;
1873 COLORREF bkgnd, corner;
1876 * Get the rectangle for the item.
1878 isVisible = TAB_InternalGetItemRect(hwnd,
1879 infoPtr,
1880 iItem,
1881 &itemRect,
1882 &selectedRect);
1884 if (isVisible)
1886 RECT rUD, rC;
1888 /* Clip UpDown control to not draw over it */
1889 if (infoPtr->needsScrolling)
1891 GetWindowRect(hwnd, &rC);
1892 GetWindowRect(infoPtr->hwndUpDown, &rUD);
1893 ExcludeClipRect(hdc, rUD.left - rC.left, rUD.top - rC.top, rUD.right - rC.left, rUD.bottom - rC.top);
1896 /* If you need to see what the control is doing,
1897 * then override these variables. They will change what
1898 * fill colors are used for filling the tabs, and the
1899 * corners when drawing the edge.
1901 bkgnd = comctl32_color.clrBtnFace;
1902 corner = comctl32_color.clrBtnFace;
1904 if (lStyle & TCS_BUTTONS)
1906 /* Get item rectangle */
1907 r = itemRect;
1909 /* Separators between flat buttons */
1910 if (lStyle & TCS_FLATBUTTONS)
1912 r1 = r;
1913 r1.right += (FLAT_BTN_SPACINGX -2);
1914 DrawEdge(hdc, &r1, EDGE_ETCHED, BF_RIGHT);
1917 if (iItem == infoPtr->iSelected)
1919 DrawEdge(hdc, &r, EDGE_SUNKEN, BF_SOFT|BF_RECT);
1921 OffsetRect(&r, 1, 1);
1923 else /* ! selected */
1925 if (!(lStyle & TCS_FLATBUTTONS))
1926 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_RECT);
1929 else /* !TCS_BUTTONS */
1931 /* We draw a rectangle of different sizes depending on the selection
1932 * state. */
1933 if (iItem == infoPtr->iSelected) {
1934 RECT rect;
1935 GetClientRect (hwnd, &rect);
1936 clRight = rect.right;
1937 clBottom = rect.bottom;
1938 r = selectedRect;
1940 else
1941 r = itemRect;
1944 * Erase the background. (Delay it but setup rectangle.)
1945 * This is necessary when drawing the selected item since it is larger
1946 * than the others, it might overlap with stuff already drawn by the
1947 * other tabs
1949 fillRect = r;
1951 if(lStyle & TCS_VERTICAL)
1953 /* These are for adjusting the drawing of a Selected tab */
1954 /* The initial values are for the normal case of non-Selected */
1955 int ZZ = 1; /* Do not strech if selected */
1956 if (iItem == infoPtr->iSelected) {
1957 ZZ = 0;
1959 /* if leftmost draw the line longer */
1960 if(selectedRect.top == 0)
1961 fillRect.top += 2;
1962 /* if rightmost draw the line longer */
1963 if(selectedRect.bottom == clBottom)
1964 fillRect.bottom -= 2;
1967 if (lStyle & TCS_BOTTOM)
1969 /* Adjust both rectangles to match native */
1970 r.left += (1-ZZ);
1972 TRACE("<left> item=%d, fill=(%ld,%ld)-(%ld,%ld), edge=(%ld,%ld)-(%ld,%ld)\n",
1973 iItem,
1974 fillRect.left,fillRect.top,fillRect.right,fillRect.bottom,
1975 r.left,r.top,r.right,r.bottom);
1977 /* Clear interior */
1978 SetBkColor(hdc, bkgnd);
1979 ExtTextOutA(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
1981 /* Draw rectangular edge around tab */
1982 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_RIGHT|BF_TOP|BF_BOTTOM);
1984 /* Now erase the top corner and draw diagonal edge */
1985 SetBkColor(hdc, corner);
1986 r1.left = r.right - ROUND_CORNER_SIZE - 1;
1987 r1.top = r.top;
1988 r1.right = r.right;
1989 r1.bottom = r1.top + ROUND_CORNER_SIZE;
1990 ExtTextOutA(hdc, 0, 0, 2, &r1, NULL, 0, 0);
1991 r1.right--;
1992 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPLEFT);
1994 /* Now erase the bottom corner and draw diagonal edge */
1995 r1.left = r.right - ROUND_CORNER_SIZE - 1;
1996 r1.bottom = r.bottom;
1997 r1.right = r.right;
1998 r1.top = r1.bottom - ROUND_CORNER_SIZE;
1999 ExtTextOutA(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2000 r1.right--;
2001 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMLEFT);
2003 if ((iItem == infoPtr->iSelected) && (selectedRect.top == 0)) {
2004 r1 = r;
2005 r1.right = r1.left;
2006 r1.left--;
2007 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_TOP);
2011 else
2013 /* Adjust both rectangles to match native */
2014 fillRect.right += (1-ZZ);
2016 TRACE("<left> item=%d, fill=(%ld,%ld)-(%ld,%ld), edge=(%ld,%ld)-(%ld,%ld)\n",
2017 iItem,
2018 fillRect.left,fillRect.top,fillRect.right,fillRect.bottom,
2019 r.left,r.top,r.right,r.bottom);
2021 /* Clear interior */
2022 SetBkColor(hdc, bkgnd);
2023 ExtTextOutA(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2025 /* Draw rectangular edge around tab */
2026 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_TOP|BF_BOTTOM);
2028 /* Now erase the top corner and draw diagonal edge */
2029 SetBkColor(hdc, corner);
2030 r1.left = r.left;
2031 r1.top = r.top;
2032 r1.right = r1.left + ROUND_CORNER_SIZE + 1;
2033 r1.bottom = r1.top + ROUND_CORNER_SIZE;
2034 ExtTextOutA(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2035 r1.left++;
2036 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPRIGHT);
2038 /* Now erase the bottom corner and draw diagonal edge */
2039 r1.left = r.left;
2040 r1.bottom = r.bottom;
2041 r1.right = r1.left + ROUND_CORNER_SIZE + 1;
2042 r1.top = r1.bottom - ROUND_CORNER_SIZE;
2043 ExtTextOutA(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2044 r1.left++;
2045 DrawEdge(hdc, &r1, EDGE_SUNKEN, BF_DIAGONAL_ENDTOPLEFT);
2048 else /* ! TCS_VERTICAL */
2050 /* These are for adjusting the drawing of a Selected tab */
2051 /* The initial values are for the normal case of non-Selected */
2052 int ZZ = 1; /* Do not strech if selected */
2053 if (iItem == infoPtr->iSelected) {
2054 ZZ = 0;
2056 /* if leftmost draw the line longer */
2057 if(selectedRect.left == 0)
2058 fillRect.left += 2;
2059 /* if rightmost draw the line longer */
2060 if(selectedRect.right == clRight)
2061 fillRect.right -= 2;
2064 if (lStyle & TCS_BOTTOM)
2067 /* Adjust both rectangles to match native */
2068 fillRect.top--;
2069 fillRect.bottom--;
2070 r.bottom--;
2071 r.top -= ZZ;
2073 TRACE("<bottom> item=%d, fill=(%ld,%ld)-(%ld,%ld), edge=(%ld,%ld)-(%ld,%ld)\n",
2074 iItem,
2075 fillRect.left,fillRect.top,fillRect.right,fillRect.bottom,
2076 r.left,r.top,r.right,r.bottom);
2078 /* Clear interior */
2079 SetBkColor(hdc, bkgnd);
2080 ExtTextOutA(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2082 /* Draw rectangular edge around tab */
2083 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_BOTTOM|BF_RIGHT);
2085 /* Now erase the righthand corner and draw diagonal edge */
2086 SetBkColor(hdc, corner);
2087 r1.left = r.right - ROUND_CORNER_SIZE;
2088 r1.bottom = r.bottom;
2089 r1.right = r.right;
2090 r1.top = r1.bottom - ROUND_CORNER_SIZE - 1;
2091 ExtTextOutA(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2092 r1.bottom--;
2093 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMLEFT);
2095 /* Now erase the lefthand corner and draw diagonal edge */
2096 r1.left = r.left;
2097 r1.bottom = r.bottom;
2098 r1.right = r1.left + ROUND_CORNER_SIZE;
2099 r1.top = r1.bottom - ROUND_CORNER_SIZE - 1;
2100 ExtTextOutA(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2101 r1.bottom--;
2102 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPLEFT);
2104 if (iItem == infoPtr->iSelected)
2106 r.top += 2;
2107 r.left += 1;
2108 if (selectedRect.left == 0)
2110 r1 = r;
2111 r1.bottom = r1.top;
2112 r1.top--;
2113 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_LEFT);
2118 else
2121 /* Adjust both rectangles to match native */
2122 fillRect.bottom += (1-ZZ);
2124 TRACE("<top> item=%d, fill=(%ld,%ld)-(%ld,%ld), edge=(%ld,%ld)-(%ld,%ld)\n",
2125 iItem,
2126 fillRect.left,fillRect.top,fillRect.right,fillRect.bottom,
2127 r.left,r.top,r.right,r.bottom);
2129 /* Clear interior */
2130 SetBkColor(hdc, bkgnd);
2131 ExtTextOutA(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2133 /* Draw rectangular edge around tab */
2134 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_TOP|BF_RIGHT);
2136 /* Now erase the righthand corner and draw diagonal edge */
2137 SetBkColor(hdc, corner);
2138 r1.left = r.right - ROUND_CORNER_SIZE;
2139 r1.top = r.top;
2140 r1.right = r.right;
2141 r1.bottom = r1.top + ROUND_CORNER_SIZE + 1;
2142 ExtTextOutA(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2143 r1.top++;
2144 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMRIGHT);
2146 /* Now erase the lefthand corner and draw diagonal edge */
2147 r1.left = r.left;
2148 r1.top = r.top;
2149 r1.right = r1.left + ROUND_CORNER_SIZE;
2150 r1.bottom = r1.top + ROUND_CORNER_SIZE + 1;
2151 ExtTextOutA(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2152 r1.top++;
2153 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPRIGHT);
2159 TAB_DumpItemInternal(infoPtr, iItem);
2161 /* This modifies r to be the text rectangle. */
2162 TAB_DrawItemInterior(hwnd, hdc, iItem, &r);
2166 /******************************************************************************
2167 * TAB_DrawBorder
2169 * This method is used to draw the raised border around the tab control
2170 * "content" area.
2172 static void TAB_DrawBorder (HWND hwnd, HDC hdc)
2174 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2175 RECT rect;
2176 DWORD lStyle = GetWindowLongA(hwnd, GWL_STYLE);
2178 GetClientRect (hwnd, &rect);
2181 * Adjust for the style
2184 if (infoPtr->uNumItem)
2186 if ((lStyle & TCS_BOTTOM) && !(lStyle & TCS_VERTICAL))
2188 rect.bottom -= (infoPtr->tabHeight - 2) * infoPtr->uNumRows + 3;
2190 else if((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
2192 rect.right -= (infoPtr->tabHeight - 2) * infoPtr->uNumRows + 2;
2194 else if(lStyle & TCS_VERTICAL)
2196 rect.left += (infoPtr->tabHeight - 2) * infoPtr->uNumRows + 2;
2198 else /* not TCS_VERTICAL and not TCS_BOTTOM */
2200 rect.top += (infoPtr->tabHeight - 2) * infoPtr->uNumRows + 2;
2204 TRACE("border=(%ld,%ld)-(%ld,%ld)\n",
2205 rect.left, rect.top, rect.right, rect.bottom);
2207 DrawEdge(hdc, &rect, EDGE_RAISED, BF_SOFT|BF_RECT);
2210 /******************************************************************************
2211 * TAB_Refresh
2213 * This method repaints the tab control..
2215 static void TAB_Refresh (HWND hwnd, HDC hdc)
2217 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2218 HFONT hOldFont;
2219 INT i;
2221 if (!infoPtr->DoRedraw)
2222 return;
2224 hOldFont = SelectObject (hdc, infoPtr->hFont);
2226 if (GetWindowLongA(hwnd, GWL_STYLE) & TCS_BUTTONS)
2228 for (i = 0; i < infoPtr->uNumItem; i++)
2229 TAB_DrawItem (hwnd, hdc, i);
2231 else
2233 /* Draw all the non selected item first */
2234 for (i = 0; i < infoPtr->uNumItem; i++)
2236 if (i != infoPtr->iSelected)
2237 TAB_DrawItem (hwnd, hdc, i);
2240 /* Now, draw the border, draw it before the selected item
2241 * since the selected item overwrites part of the border. */
2242 TAB_DrawBorder (hwnd, hdc);
2244 /* Then, draw the selected item */
2245 TAB_DrawItem (hwnd, hdc, infoPtr->iSelected);
2247 /* If we haven't set the current focus yet, set it now.
2248 * Only happens when we first paint the tab controls */
2249 if (infoPtr->uFocus == -1)
2250 TAB_SetCurFocus(hwnd, infoPtr->iSelected);
2253 SelectObject (hdc, hOldFont);
2256 static DWORD
2257 TAB_GetRowCount (HWND hwnd )
2259 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2261 return infoPtr->uNumRows;
2264 static LRESULT
2265 TAB_SetRedraw (HWND hwnd, WPARAM wParam)
2267 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2269 infoPtr->DoRedraw=(BOOL) wParam;
2270 return 0;
2273 static LRESULT TAB_EraseBackground(
2274 HWND hwnd,
2275 HDC givenDC)
2277 HDC hdc;
2278 RECT clientRect;
2280 HBRUSH brush = CreateSolidBrush(comctl32_color.clrBtnFace);
2282 hdc = givenDC ? givenDC : GetDC(hwnd);
2284 GetClientRect(hwnd, &clientRect);
2286 FillRect(hdc, &clientRect, brush);
2288 if (givenDC==0)
2289 ReleaseDC(hwnd, hdc);
2291 DeleteObject(brush);
2293 return 0;
2296 /******************************************************************************
2297 * TAB_EnsureSelectionVisible
2299 * This method will make sure that the current selection is completely
2300 * visible by scrolling until it is.
2302 static void TAB_EnsureSelectionVisible(
2303 HWND hwnd,
2304 TAB_INFO* infoPtr)
2306 INT iSelected = infoPtr->iSelected;
2307 LONG lStyle = GetWindowLongA(hwnd, GWL_STYLE);
2308 INT iOrigLeftmostVisible = infoPtr->leftmostVisible;
2310 /* set the items row to the bottommost row or topmost row depending on
2311 * style */
2312 if ((infoPtr->uNumRows > 1) && !(lStyle & TCS_BUTTONS))
2314 INT newselected;
2315 INT iTargetRow;
2317 if(lStyle & TCS_VERTICAL)
2318 newselected = infoPtr->items[iSelected].rect.left;
2319 else
2320 newselected = infoPtr->items[iSelected].rect.top;
2322 /* the target row is always (number of rows - 1)
2323 as row 0 is furthest from the clientRect */
2324 iTargetRow = infoPtr->uNumRows - 1;
2326 if (newselected != iTargetRow)
2328 INT i;
2329 if(lStyle & TCS_VERTICAL)
2331 for (i=0; i < infoPtr->uNumItem; i++)
2333 /* move everything in the row of the selected item to the iTargetRow */
2334 if (infoPtr->items[i].rect.left == newselected )
2335 infoPtr->items[i].rect.left = iTargetRow;
2336 else
2338 if (infoPtr->items[i].rect.left > newselected)
2339 infoPtr->items[i].rect.left-=1;
2343 else
2345 for (i=0; i < infoPtr->uNumItem; i++)
2347 if (infoPtr->items[i].rect.top == newselected )
2348 infoPtr->items[i].rect.top = iTargetRow;
2349 else
2351 if (infoPtr->items[i].rect.top > newselected)
2352 infoPtr->items[i].rect.top-=1;
2356 TAB_RecalcHotTrack(hwnd, NULL, NULL, NULL);
2361 * Do the trivial cases first.
2363 if ( (!infoPtr->needsScrolling) ||
2364 (infoPtr->hwndUpDown==0) || (lStyle & TCS_VERTICAL))
2365 return;
2367 if (infoPtr->leftmostVisible >= iSelected)
2369 infoPtr->leftmostVisible = iSelected;
2371 else
2373 RECT r;
2374 INT width, i;
2376 /* Calculate the part of the client area that is visible */
2377 GetClientRect(hwnd, &r);
2378 width = r.right;
2380 GetClientRect(infoPtr->hwndUpDown, &r);
2381 width -= r.right;
2383 if ((infoPtr->items[iSelected].rect.right -
2384 infoPtr->items[iSelected].rect.left) >= width )
2386 /* Special case: width of selected item is greater than visible
2387 * part of control.
2389 infoPtr->leftmostVisible = iSelected;
2391 else
2393 for (i = infoPtr->leftmostVisible; i < infoPtr->uNumItem; i++)
2395 if ((infoPtr->items[iSelected].rect.right -
2396 infoPtr->items[i].rect.left) < width)
2397 break;
2399 infoPtr->leftmostVisible = i;
2403 if (infoPtr->leftmostVisible != iOrigLeftmostVisible)
2404 TAB_RecalcHotTrack(hwnd, NULL, NULL, NULL);
2406 SendMessageA(infoPtr->hwndUpDown, UDM_SETPOS, 0,
2407 MAKELONG(infoPtr->leftmostVisible, 0));
2410 /******************************************************************************
2411 * TAB_InvalidateTabArea
2413 * This method will invalidate the portion of the control that contains the
2414 * tabs. It is called when the state of the control changes and needs
2415 * to be redisplayed
2417 static void TAB_InvalidateTabArea(
2418 HWND hwnd,
2419 TAB_INFO* infoPtr)
2421 RECT clientRect, rInvalidate;
2422 DWORD lStyle = GetWindowLongA(hwnd, GWL_STYLE);
2423 INT lastRow = infoPtr->uNumRows - 1;
2424 RECT rect;
2426 if (lastRow < 0) return;
2428 GetClientRect(hwnd, &clientRect);
2429 rInvalidate = clientRect;
2431 TAB_InternalGetItemRect(hwnd, infoPtr, infoPtr->uNumItem-1 , &rect, NULL);
2432 if ((lStyle & TCS_BOTTOM) && !(lStyle & TCS_VERTICAL))
2434 rInvalidate.top = clientRect.bottom -
2435 infoPtr->tabHeight -
2436 lastRow * (infoPtr->tabHeight - 2) -
2437 ((lStyle & TCS_BUTTONS) ? lastRow * BUTTON_SPACINGY : 0) - 3;
2438 rInvalidate.right = clientRect.left + rect.right + 2 * SELECTED_TAB_OFFSET;
2440 else if((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
2442 rInvalidate.left = clientRect.right - infoPtr->tabHeight -
2443 lastRow * (infoPtr->tabHeight - 2) -
2444 ((lStyle & TCS_BUTTONS) ? lastRow * BUTTON_SPACINGY : 0) - 2;
2445 rInvalidate.bottom = clientRect.top + rect.bottom + 2 * SELECTED_TAB_OFFSET;
2447 else if(lStyle & TCS_VERTICAL)
2449 rInvalidate.right = clientRect.left + infoPtr->tabHeight +
2450 lastRow * (infoPtr->tabHeight - 2) -
2451 ((lStyle & TCS_BUTTONS) ? lastRow * BUTTON_SPACINGY : 0) + 2;
2452 rInvalidate.bottom = clientRect.top + rect.bottom + 2 * SELECTED_TAB_OFFSET;
2454 else
2456 rInvalidate.bottom = clientRect.top + infoPtr->tabHeight +
2457 lastRow * (infoPtr->tabHeight - 2) +
2458 ((lStyle & TCS_BUTTONS) ? lastRow * BUTTON_SPACINGY : 0) + 2;
2459 rInvalidate.right = clientRect.left + rect.right + 2 * SELECTED_TAB_OFFSET;
2462 /* Punch out the updown control */
2463 if (infoPtr->needsScrolling && (rInvalidate.right > 0)) {
2464 RECT r;
2465 GetClientRect(infoPtr->hwndUpDown, &r);
2466 if (rInvalidate.right > clientRect.right - r.left)
2467 rInvalidate.right = rInvalidate.right - (r.right - r.left);
2468 else
2469 rInvalidate.right = clientRect.right - r.left;
2472 TRACE("invalidate (%ld,%ld)-(%ld,%ld)\n",
2473 clientRect.left,clientRect.top,
2474 clientRect.right,clientRect.bottom);
2476 InvalidateRect(hwnd, &rInvalidate, TRUE);
2479 static LRESULT
2480 TAB_Paint (HWND hwnd, WPARAM wParam)
2482 HDC hdc;
2483 PAINTSTRUCT ps;
2485 if (wParam == 0)
2487 hdc = BeginPaint (hwnd, &ps);
2488 TRACE("erase %d, rect=(%ld,%ld)-(%ld,%ld)\n",
2489 ps.fErase,
2490 ps.rcPaint.left,ps.rcPaint.top,ps.rcPaint.right,ps.rcPaint.bottom);
2492 if (ps.fErase)
2493 TAB_EraseBackground (hwnd, hdc);
2495 } else {
2496 hdc = (HDC)wParam;
2499 TAB_Refresh (hwnd, hdc);
2501 if(!wParam)
2502 EndPaint (hwnd, &ps);
2504 return 0;
2507 static LRESULT
2508 TAB_InsertItemA (HWND hwnd, WPARAM wParam, LPARAM lParam)
2510 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2511 TCITEMA *pti;
2512 INT iItem;
2513 RECT rect;
2515 GetClientRect (hwnd, &rect);
2516 TRACE("Rect: %p T %li, L %li, B %li, R %li\n", hwnd,
2517 rect.top, rect.left, rect.bottom, rect.right);
2519 pti = (TCITEMA *)lParam;
2520 iItem = (INT)wParam;
2522 if (iItem < 0) return -1;
2523 if (iItem > infoPtr->uNumItem)
2524 iItem = infoPtr->uNumItem;
2526 TAB_DumpItemExternalA(pti, iItem);
2529 if (infoPtr->uNumItem == 0) {
2530 infoPtr->items = Alloc (sizeof (TAB_ITEM));
2531 infoPtr->uNumItem++;
2532 infoPtr->iSelected = 0;
2534 else {
2535 TAB_ITEM *oldItems = infoPtr->items;
2537 infoPtr->uNumItem++;
2538 infoPtr->items = Alloc (sizeof (TAB_ITEM) * infoPtr->uNumItem);
2540 /* pre insert copy */
2541 if (iItem > 0) {
2542 memcpy (&infoPtr->items[0], &oldItems[0],
2543 iItem * sizeof(TAB_ITEM));
2546 /* post insert copy */
2547 if (iItem < infoPtr->uNumItem - 1) {
2548 memcpy (&infoPtr->items[iItem+1], &oldItems[iItem],
2549 (infoPtr->uNumItem - iItem - 1) * sizeof(TAB_ITEM));
2553 if (iItem <= infoPtr->iSelected)
2554 infoPtr->iSelected++;
2556 Free (oldItems);
2559 infoPtr->items[iItem].mask = pti->mask;
2560 if (pti->mask & TCIF_TEXT)
2561 Str_SetPtrAtoW (&infoPtr->items[iItem].pszText, pti->pszText);
2563 if (pti->mask & TCIF_IMAGE)
2564 infoPtr->items[iItem].iImage = pti->iImage;
2566 if (pti->mask & TCIF_PARAM)
2567 infoPtr->items[iItem].lParam = pti->lParam;
2569 TAB_SetItemBounds(hwnd);
2570 if (infoPtr->uNumItem > 1)
2571 TAB_InvalidateTabArea(hwnd, infoPtr);
2572 else
2573 InvalidateRect(hwnd, NULL, TRUE);
2575 TRACE("[%p]: added item %d %s\n",
2576 hwnd, iItem, debugstr_w(infoPtr->items[iItem].pszText));
2578 return iItem;
2582 static LRESULT
2583 TAB_InsertItemW (HWND hwnd, WPARAM wParam, LPARAM lParam)
2585 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2586 TCITEMW *pti;
2587 INT iItem;
2588 RECT rect;
2590 GetClientRect (hwnd, &rect);
2591 TRACE("Rect: %p T %li, L %li, B %li, R %li\n", hwnd,
2592 rect.top, rect.left, rect.bottom, rect.right);
2594 pti = (TCITEMW *)lParam;
2595 iItem = (INT)wParam;
2597 if (iItem < 0) return -1;
2598 if (iItem > infoPtr->uNumItem)
2599 iItem = infoPtr->uNumItem;
2601 TAB_DumpItemExternalW(pti, iItem);
2603 if (infoPtr->uNumItem == 0) {
2604 infoPtr->items = Alloc (sizeof (TAB_ITEM));
2605 infoPtr->uNumItem++;
2606 infoPtr->iSelected = 0;
2608 else {
2609 TAB_ITEM *oldItems = infoPtr->items;
2611 infoPtr->uNumItem++;
2612 infoPtr->items = Alloc (sizeof (TAB_ITEM) * infoPtr->uNumItem);
2614 /* pre insert copy */
2615 if (iItem > 0) {
2616 memcpy (&infoPtr->items[0], &oldItems[0],
2617 iItem * sizeof(TAB_ITEM));
2620 /* post insert copy */
2621 if (iItem < infoPtr->uNumItem - 1) {
2622 memcpy (&infoPtr->items[iItem+1], &oldItems[iItem],
2623 (infoPtr->uNumItem - iItem - 1) * sizeof(TAB_ITEM));
2627 if (iItem <= infoPtr->iSelected)
2628 infoPtr->iSelected++;
2630 Free (oldItems);
2633 infoPtr->items[iItem].mask = pti->mask;
2634 if (pti->mask & TCIF_TEXT)
2635 Str_SetPtrW (&infoPtr->items[iItem].pszText, pti->pszText);
2637 if (pti->mask & TCIF_IMAGE)
2638 infoPtr->items[iItem].iImage = pti->iImage;
2640 if (pti->mask & TCIF_PARAM)
2641 infoPtr->items[iItem].lParam = pti->lParam;
2643 TAB_SetItemBounds(hwnd);
2644 if (infoPtr->uNumItem > 1)
2645 TAB_InvalidateTabArea(hwnd, infoPtr);
2646 else
2647 InvalidateRect(hwnd, NULL, TRUE);
2649 TRACE("[%p]: added item %d %s\n",
2650 hwnd, iItem, debugstr_w(infoPtr->items[iItem].pszText));
2652 return iItem;
2656 static LRESULT
2657 TAB_SetItemSize (HWND hwnd, WPARAM wParam, LPARAM lParam)
2659 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2660 LONG lStyle = GetWindowLongA(hwnd, GWL_STYLE);
2661 LONG lResult = 0;
2662 BOOL bNeedPaint = FALSE;
2664 lResult = MAKELONG(infoPtr->tabWidth, infoPtr->tabHeight);
2666 /* UNDOCUMENTED: If requested Width or Height is 0 this means that program wants to use auto size. */
2667 if (lStyle & TCS_FIXEDWIDTH && (infoPtr->tabWidth != (INT)LOWORD(lParam)))
2669 infoPtr->tabWidth = max((INT)LOWORD(lParam), infoPtr->tabMinWidth);
2670 bNeedPaint = TRUE;
2673 if (infoPtr->tabHeight != (INT)HIWORD(lParam))
2675 if ((infoPtr->fHeightSet = ((INT)HIWORD(lParam) != 0)))
2676 infoPtr->tabHeight = (INT)HIWORD(lParam);
2678 bNeedPaint = TRUE;
2680 TRACE("was h=%d,w=%d, now h=%d,w=%d\n",
2681 HIWORD(lResult), LOWORD(lResult),
2682 infoPtr->tabHeight, infoPtr->tabWidth);
2684 if (bNeedPaint)
2686 TAB_SetItemBounds(hwnd);
2687 RedrawWindow(hwnd, NULL, NULL, RDW_ERASE | RDW_INVALIDATE | RDW_UPDATENOW);
2690 return lResult;
2693 static LRESULT
2694 TAB_SetMinTabWidth (HWND hwnd, LPARAM lParam)
2696 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2697 INT cx = (INT)lParam;
2698 INT oldcx;
2700 if (infoPtr) {
2701 oldcx = infoPtr->tabMinWidth;
2702 infoPtr->tabMinWidth = (cx==-1)?DEFAULT_TAB_WIDTH:cx;
2703 } else
2704 return 0;
2706 return oldcx;
2709 static LRESULT
2710 TAB_HighlightItem (HWND hwnd, WPARAM wParam, LPARAM lParam)
2712 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2713 INT iItem = (INT)wParam;
2714 BOOL fHighlight = (BOOL)LOWORD(lParam);
2716 if ((infoPtr) && (iItem>=0) && (iItem<infoPtr->uNumItem)) {
2717 if (fHighlight)
2718 infoPtr->items[iItem].dwState |= TCIS_HIGHLIGHTED;
2719 else
2720 infoPtr->items[iItem].dwState &= ~TCIS_HIGHLIGHTED;
2721 } else
2722 return FALSE;
2724 return TRUE;
2727 static LRESULT
2728 TAB_SetItemA (HWND hwnd, WPARAM wParam, LPARAM lParam)
2730 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2731 TCITEMA *tabItem;
2732 TAB_ITEM *wineItem;
2733 INT iItem;
2735 iItem = (INT)wParam;
2736 tabItem = (LPTCITEMA)lParam;
2738 TRACE("%d %p\n", iItem, tabItem);
2739 if ((iItem<0) || (iItem>=infoPtr->uNumItem)) return FALSE;
2741 TAB_DumpItemExternalA(tabItem, iItem);
2743 wineItem = &infoPtr->items[iItem];
2745 if (tabItem->mask & TCIF_IMAGE)
2746 wineItem->iImage = tabItem->iImage;
2748 if (tabItem->mask & TCIF_PARAM)
2749 wineItem->lParam = tabItem->lParam;
2751 if (tabItem->mask & TCIF_RTLREADING)
2752 FIXME("TCIF_RTLREADING\n");
2754 if (tabItem->mask & TCIF_STATE)
2755 wineItem->dwState = tabItem->dwState;
2757 if (tabItem->mask & TCIF_TEXT)
2758 Str_SetPtrAtoW(&wineItem->pszText, tabItem->pszText);
2760 /* Update and repaint tabs */
2761 TAB_SetItemBounds(hwnd);
2762 TAB_InvalidateTabArea(hwnd,infoPtr);
2764 return TRUE;
2768 static LRESULT
2769 TAB_SetItemW (HWND hwnd, WPARAM wParam, LPARAM lParam)
2771 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2772 TCITEMW *tabItem;
2773 TAB_ITEM *wineItem;
2774 INT iItem;
2776 iItem = (INT)wParam;
2777 tabItem = (LPTCITEMW)lParam;
2779 TRACE("%d %p\n", iItem, tabItem);
2780 if ((iItem<0) || (iItem>=infoPtr->uNumItem)) return FALSE;
2782 TAB_DumpItemExternalW(tabItem, iItem);
2784 wineItem = &infoPtr->items[iItem];
2786 if (tabItem->mask & TCIF_IMAGE)
2787 wineItem->iImage = tabItem->iImage;
2789 if (tabItem->mask & TCIF_PARAM)
2790 wineItem->lParam = tabItem->lParam;
2792 if (tabItem->mask & TCIF_RTLREADING)
2793 FIXME("TCIF_RTLREADING\n");
2795 if (tabItem->mask & TCIF_STATE)
2796 wineItem->dwState = tabItem->dwState;
2798 if (tabItem->mask & TCIF_TEXT)
2799 Str_SetPtrW(&wineItem->pszText, tabItem->pszText);
2801 /* Update and repaint tabs */
2802 TAB_SetItemBounds(hwnd);
2803 TAB_InvalidateTabArea(hwnd,infoPtr);
2805 return TRUE;
2809 static LRESULT
2810 TAB_GetItemCount (HWND hwnd, WPARAM wParam, LPARAM lParam)
2812 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2814 return infoPtr->uNumItem;
2818 static LRESULT
2819 TAB_GetItemA (HWND hwnd, WPARAM wParam, LPARAM lParam)
2821 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2822 TCITEMA *tabItem;
2823 TAB_ITEM *wineItem;
2824 INT iItem;
2826 iItem = (INT)wParam;
2827 tabItem = (LPTCITEMA)lParam;
2828 TRACE("\n");
2829 if ((iItem<0) || (iItem>=infoPtr->uNumItem))
2830 return FALSE;
2832 wineItem = &infoPtr->items[iItem];
2834 if (tabItem->mask & TCIF_IMAGE)
2835 tabItem->iImage = wineItem->iImage;
2837 if (tabItem->mask & TCIF_PARAM)
2838 tabItem->lParam = wineItem->lParam;
2840 if (tabItem->mask & TCIF_RTLREADING)
2841 FIXME("TCIF_RTLREADING\n");
2843 if (tabItem->mask & TCIF_STATE)
2844 tabItem->dwState = wineItem->dwState;
2846 if (tabItem->mask & TCIF_TEXT)
2847 Str_GetPtrWtoA (wineItem->pszText, tabItem->pszText, tabItem->cchTextMax);
2849 TAB_DumpItemExternalA(tabItem, iItem);
2851 return TRUE;
2855 static LRESULT
2856 TAB_GetItemW (HWND hwnd, WPARAM wParam, LPARAM lParam)
2858 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2859 TCITEMW *tabItem;
2860 TAB_ITEM *wineItem;
2861 INT iItem;
2863 iItem = (INT)wParam;
2864 tabItem = (LPTCITEMW)lParam;
2865 TRACE("\n");
2866 if ((iItem<0) || (iItem>=infoPtr->uNumItem))
2867 return FALSE;
2869 wineItem=& infoPtr->items[iItem];
2871 if (tabItem->mask & TCIF_IMAGE)
2872 tabItem->iImage = wineItem->iImage;
2874 if (tabItem->mask & TCIF_PARAM)
2875 tabItem->lParam = wineItem->lParam;
2877 if (tabItem->mask & TCIF_RTLREADING)
2878 FIXME("TCIF_RTLREADING\n");
2880 if (tabItem->mask & TCIF_STATE)
2881 tabItem->dwState = wineItem->dwState;
2883 if (tabItem->mask & TCIF_TEXT)
2884 Str_GetPtrW (wineItem->pszText, tabItem->pszText, tabItem->cchTextMax);
2886 TAB_DumpItemExternalW(tabItem, iItem);
2888 return TRUE;
2892 static LRESULT
2893 TAB_DeleteItem (HWND hwnd, WPARAM wParam, LPARAM lParam)
2895 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2896 INT iItem = (INT) wParam;
2897 BOOL bResult = FALSE;
2899 if ((iItem >= 0) && (iItem < infoPtr->uNumItem))
2901 TAB_ITEM *oldItems = infoPtr->items;
2903 TAB_InvalidateTabArea(hwnd, infoPtr);
2905 infoPtr->uNumItem--;
2906 infoPtr->items = Alloc(sizeof (TAB_ITEM) * infoPtr->uNumItem);
2908 if (iItem > 0)
2909 memcpy(&infoPtr->items[0], &oldItems[0], iItem * sizeof(TAB_ITEM));
2911 if (iItem < infoPtr->uNumItem)
2912 memcpy(&infoPtr->items[iItem], &oldItems[iItem + 1],
2913 (infoPtr->uNumItem - iItem) * sizeof(TAB_ITEM));
2915 Free(oldItems);
2917 /* Readjust the selected index */
2918 if ((iItem == infoPtr->iSelected) && (iItem > 0))
2919 infoPtr->iSelected--;
2921 if (iItem < infoPtr->iSelected)
2922 infoPtr->iSelected--;
2924 if (infoPtr->uNumItem == 0)
2925 infoPtr->iSelected = -1;
2927 /* Reposition and repaint tabs */
2928 TAB_SetItemBounds(hwnd);
2930 bResult = TRUE;
2933 return bResult;
2936 static LRESULT
2937 TAB_DeleteAllItems (HWND hwnd, WPARAM wParam, LPARAM lParam)
2939 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2941 TAB_InvalidateTabArea(hwnd,infoPtr);
2943 Free (infoPtr->items);
2944 infoPtr->uNumItem = 0;
2945 infoPtr->iSelected = -1;
2946 if (infoPtr->iHotTracked >= 0)
2947 KillTimer(hwnd, TAB_HOTTRACK_TIMER);
2948 infoPtr->iHotTracked = -1;
2950 TAB_SetItemBounds(hwnd);
2951 return TRUE;
2955 static LRESULT
2956 TAB_GetFont (HWND hwnd, WPARAM wParam, LPARAM lParam)
2958 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2960 TRACE("\n");
2961 return (LRESULT)infoPtr->hFont;
2964 static LRESULT
2965 TAB_SetFont (HWND hwnd, WPARAM wParam, LPARAM lParam)
2968 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2970 TRACE("%x %lx\n",wParam, lParam);
2972 infoPtr->hFont = (HFONT)wParam;
2974 TAB_SetItemBounds(hwnd);
2976 TAB_InvalidateTabArea(hwnd, infoPtr);
2978 return 0;
2982 static LRESULT
2983 TAB_GetImageList (HWND hwnd, WPARAM wParam, LPARAM lParam)
2985 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2987 TRACE("\n");
2988 return (LRESULT)infoPtr->himl;
2991 static LRESULT
2992 TAB_SetImageList (HWND hwnd, WPARAM wParam, LPARAM lParam)
2994 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2995 HIMAGELIST himlPrev;
2997 TRACE("\n");
2998 himlPrev = infoPtr->himl;
2999 infoPtr->himl= (HIMAGELIST)lParam;
3000 return (LRESULT)himlPrev;
3003 static LRESULT
3004 TAB_GetUnicodeFormat (HWND hwnd)
3006 TAB_INFO *infoPtr = TAB_GetInfoPtr (hwnd);
3007 return infoPtr->bUnicode;
3010 static LRESULT
3011 TAB_SetUnicodeFormat (HWND hwnd, WPARAM wParam)
3013 TAB_INFO *infoPtr = TAB_GetInfoPtr (hwnd);
3014 BOOL bTemp = infoPtr->bUnicode;
3016 infoPtr->bUnicode = (BOOL)wParam;
3018 return bTemp;
3021 static LRESULT
3022 TAB_Size (HWND hwnd, WPARAM wParam, LPARAM lParam)
3025 /* I'm not really sure what the following code was meant to do.
3026 This is what it is doing:
3027 When WM_SIZE is sent with SIZE_RESTORED, the control
3028 gets positioned in the top left corner.
3030 RECT parent_rect;
3031 HWND parent;
3032 UINT uPosFlags,cx,cy;
3034 uPosFlags=0;
3035 if (!wParam) {
3036 parent = GetParent (hwnd);
3037 GetClientRect(parent, &parent_rect);
3038 cx=LOWORD (lParam);
3039 cy=HIWORD (lParam);
3040 if (GetWindowLongA(hwnd, GWL_STYLE) & CCS_NORESIZE)
3041 uPosFlags |= (SWP_NOSIZE | SWP_NOMOVE);
3043 SetWindowPos (hwnd, 0, parent_rect.left, parent_rect.top,
3044 cx, cy, uPosFlags | SWP_NOZORDER);
3045 } else {
3046 FIXME("WM_SIZE flag %x %lx not handled\n", wParam, lParam);
3047 } */
3049 /* Recompute the size/position of the tabs. */
3050 TAB_SetItemBounds (hwnd);
3052 /* Force a repaint of the control. */
3053 InvalidateRect(hwnd, NULL, TRUE);
3055 return 0;
3059 static LRESULT
3060 TAB_Create (HWND hwnd, WPARAM wParam, LPARAM lParam)
3062 TAB_INFO *infoPtr;
3063 TEXTMETRICA fontMetrics;
3064 HDC hdc;
3065 HFONT hOldFont;
3066 DWORD dwStyle;
3068 infoPtr = (TAB_INFO *)Alloc (sizeof(TAB_INFO));
3070 SetWindowLongA(hwnd, 0, (DWORD)infoPtr);
3072 infoPtr->hwndNotify = ((LPCREATESTRUCTW)lParam)->hwndParent;
3073 infoPtr->uNumItem = 0;
3074 infoPtr->uNumRows = 0;
3075 infoPtr->uHItemPadding = 6;
3076 infoPtr->uVItemPadding = 3;
3077 infoPtr->uHItemPadding_s = 6;
3078 infoPtr->uVItemPadding_s = 3;
3079 infoPtr->hFont = 0;
3080 infoPtr->items = 0;
3081 infoPtr->hcurArrow = LoadCursorA (0, (LPSTR)IDC_ARROW);
3082 infoPtr->iSelected = -1;
3083 infoPtr->iHotTracked = -1;
3084 infoPtr->uFocus = -1;
3085 infoPtr->hwndToolTip = 0;
3086 infoPtr->DoRedraw = TRUE;
3087 infoPtr->needsScrolling = FALSE;
3088 infoPtr->hwndUpDown = 0;
3089 infoPtr->leftmostVisible = 0;
3090 infoPtr->fHeightSet = FALSE;
3091 infoPtr->bUnicode = IsWindowUnicode (hwnd);
3093 TRACE("Created tab control, hwnd [%p]\n", hwnd);
3095 /* The tab control always has the WS_CLIPSIBLINGS style. Even
3096 if you don't specify it in CreateWindow. This is necessary in
3097 order for paint to work correctly. This follows windows behaviour. */
3098 dwStyle = GetWindowLongA(hwnd, GWL_STYLE);
3099 SetWindowLongA(hwnd, GWL_STYLE, dwStyle|WS_CLIPSIBLINGS);
3101 if (dwStyle & TCS_TOOLTIPS) {
3102 /* Create tooltip control */
3103 infoPtr->hwndToolTip =
3104 CreateWindowExA (0, TOOLTIPS_CLASSA, NULL, 0,
3105 CW_USEDEFAULT, CW_USEDEFAULT,
3106 CW_USEDEFAULT, CW_USEDEFAULT,
3107 hwnd, 0, 0, 0);
3109 /* Send NM_TOOLTIPSCREATED notification */
3110 if (infoPtr->hwndToolTip) {
3111 NMTOOLTIPSCREATED nmttc;
3113 nmttc.hdr.hwndFrom = hwnd;
3114 nmttc.hdr.idFrom = GetWindowLongA(hwnd, GWL_ID);
3115 nmttc.hdr.code = NM_TOOLTIPSCREATED;
3116 nmttc.hwndToolTips = infoPtr->hwndToolTip;
3118 SendMessageA (infoPtr->hwndNotify, WM_NOTIFY,
3119 (WPARAM)GetWindowLongA(hwnd, GWL_ID), (LPARAM)&nmttc);
3124 * We need to get text information so we need a DC and we need to select
3125 * a font.
3127 hdc = GetDC(hwnd);
3128 hOldFont = SelectObject (hdc, GetStockObject (SYSTEM_FONT));
3130 /* Use the system font to determine the initial height of a tab. */
3131 GetTextMetricsA(hdc, &fontMetrics);
3134 * Make sure there is enough space for the letters + growing the
3135 * selected item + extra space for the selected item.
3137 infoPtr->tabHeight = fontMetrics.tmHeight + SELECTED_TAB_OFFSET +
3138 ((dwStyle & TCS_BUTTONS) ? 2 : 1) *
3139 infoPtr->uVItemPadding;
3141 /* Initialize the width of a tab. */
3142 infoPtr->tabWidth = DEFAULT_TAB_WIDTH;
3143 infoPtr->tabMinWidth = 0;
3145 TRACE("tabH=%d, tabW=%d\n", infoPtr->tabHeight, infoPtr->tabWidth);
3147 SelectObject (hdc, hOldFont);
3148 ReleaseDC(hwnd, hdc);
3150 return 0;
3153 static LRESULT
3154 TAB_Destroy (HWND hwnd, WPARAM wParam, LPARAM lParam)
3156 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
3157 INT iItem;
3159 if (!infoPtr)
3160 return 0;
3162 if (infoPtr->items) {
3163 for (iItem = 0; iItem < infoPtr->uNumItem; iItem++) {
3164 if (infoPtr->items[iItem].pszText)
3165 Free (infoPtr->items[iItem].pszText);
3167 Free (infoPtr->items);
3170 if (infoPtr->hwndToolTip)
3171 DestroyWindow (infoPtr->hwndToolTip);
3173 if (infoPtr->hwndUpDown)
3174 DestroyWindow(infoPtr->hwndUpDown);
3176 if (infoPtr->iHotTracked >= 0)
3177 KillTimer(hwnd, TAB_HOTTRACK_TIMER);
3179 Free (infoPtr);
3180 SetWindowLongA(hwnd, 0, 0);
3181 return 0;
3184 static LRESULT WINAPI
3185 TAB_WindowProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
3187 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
3189 TRACE("hwnd=%p msg=%x wParam=%x lParam=%lx\n", hwnd, uMsg, wParam, lParam);
3190 if (!TAB_GetInfoPtr(hwnd) && (uMsg != WM_CREATE))
3191 return DefWindowProcA (hwnd, uMsg, wParam, lParam);
3193 switch (uMsg)
3195 case TCM_GETIMAGELIST:
3196 return TAB_GetImageList (hwnd, wParam, lParam);
3198 case TCM_SETIMAGELIST:
3199 return TAB_SetImageList (hwnd, wParam, lParam);
3201 case TCM_GETITEMCOUNT:
3202 return TAB_GetItemCount (hwnd, wParam, lParam);
3204 case TCM_GETITEMA:
3205 return TAB_GetItemA (hwnd, wParam, lParam);
3207 case TCM_GETITEMW:
3208 return TAB_GetItemW (hwnd, wParam, lParam);
3210 case TCM_SETITEMA:
3211 return TAB_SetItemA (hwnd, wParam, lParam);
3213 case TCM_SETITEMW:
3214 return TAB_SetItemW (hwnd, wParam, lParam);
3216 case TCM_DELETEITEM:
3217 return TAB_DeleteItem (hwnd, wParam, lParam);
3219 case TCM_DELETEALLITEMS:
3220 return TAB_DeleteAllItems (hwnd, wParam, lParam);
3222 case TCM_GETITEMRECT:
3223 return TAB_GetItemRect (hwnd, wParam, lParam);
3225 case TCM_GETCURSEL:
3226 return TAB_GetCurSel (hwnd);
3228 case TCM_HITTEST:
3229 return TAB_HitTest (hwnd, wParam, lParam);
3231 case TCM_SETCURSEL:
3232 return TAB_SetCurSel (hwnd, wParam);
3234 case TCM_INSERTITEMA:
3235 return TAB_InsertItemA (hwnd, wParam, lParam);
3237 case TCM_INSERTITEMW:
3238 return TAB_InsertItemW (hwnd, wParam, lParam);
3240 case TCM_SETITEMEXTRA:
3241 FIXME("Unimplemented msg TCM_SETITEMEXTRA\n");
3242 return 0;
3244 case TCM_ADJUSTRECT:
3245 return TAB_AdjustRect (hwnd, (BOOL)wParam, (LPRECT)lParam);
3247 case TCM_SETITEMSIZE:
3248 return TAB_SetItemSize (hwnd, wParam, lParam);
3250 case TCM_REMOVEIMAGE:
3251 FIXME("Unimplemented msg TCM_REMOVEIMAGE\n");
3252 return 0;
3254 case TCM_SETPADDING:
3255 return TAB_SetPadding (hwnd, wParam, lParam);
3257 case TCM_GETROWCOUNT:
3258 return TAB_GetRowCount(hwnd);
3260 case TCM_GETUNICODEFORMAT:
3261 return TAB_GetUnicodeFormat (hwnd);
3263 case TCM_SETUNICODEFORMAT:
3264 return TAB_SetUnicodeFormat (hwnd, wParam);
3266 case TCM_HIGHLIGHTITEM:
3267 return TAB_HighlightItem (hwnd, wParam, lParam);
3269 case TCM_GETTOOLTIPS:
3270 return TAB_GetToolTips (hwnd, wParam, lParam);
3272 case TCM_SETTOOLTIPS:
3273 return TAB_SetToolTips (hwnd, wParam, lParam);
3275 case TCM_GETCURFOCUS:
3276 return TAB_GetCurFocus (hwnd);
3278 case TCM_SETCURFOCUS:
3279 return TAB_SetCurFocus (hwnd, wParam);
3281 case TCM_SETMINTABWIDTH:
3282 return TAB_SetMinTabWidth(hwnd, lParam);
3284 case TCM_DESELECTALL:
3285 FIXME("Unimplemented msg TCM_DESELECTALL\n");
3286 return 0;
3288 case TCM_GETEXTENDEDSTYLE:
3289 FIXME("Unimplemented msg TCM_GETEXTENDEDSTYLE\n");
3290 return 0;
3292 case TCM_SETEXTENDEDSTYLE:
3293 FIXME("Unimplemented msg TCM_SETEXTENDEDSTYLE\n");
3294 return 0;
3296 case WM_GETFONT:
3297 return TAB_GetFont (hwnd, wParam, lParam);
3299 case WM_SETFONT:
3300 return TAB_SetFont (hwnd, wParam, lParam);
3302 case WM_CREATE:
3303 return TAB_Create (hwnd, wParam, lParam);
3305 case WM_NCDESTROY:
3306 return TAB_Destroy (hwnd, wParam, lParam);
3308 case WM_GETDLGCODE:
3309 return DLGC_WANTARROWS | DLGC_WANTCHARS;
3311 case WM_LBUTTONDOWN:
3312 return TAB_LButtonDown (hwnd, wParam, lParam);
3314 case WM_LBUTTONUP:
3315 return TAB_LButtonUp (hwnd, wParam, lParam);
3317 case WM_NOTIFY:
3318 return SendMessageA(infoPtr->hwndNotify, WM_NOTIFY, wParam, lParam);
3320 case WM_RBUTTONDOWN:
3321 return TAB_RButtonDown (hwnd, wParam, lParam);
3323 case WM_MOUSEMOVE:
3324 return TAB_MouseMove (hwnd, wParam, lParam);
3326 case WM_ERASEBKGND:
3327 return TAB_EraseBackground (hwnd, (HDC)wParam);
3329 case WM_PAINT:
3330 return TAB_Paint (hwnd, wParam);
3332 case WM_SIZE:
3333 return TAB_Size (hwnd, wParam, lParam);
3335 case WM_SETREDRAW:
3336 return TAB_SetRedraw (hwnd, wParam);
3338 case WM_HSCROLL:
3339 return TAB_OnHScroll(hwnd, (int)LOWORD(wParam), (int)HIWORD(wParam), (HWND)lParam);
3341 case WM_STYLECHANGED:
3342 TAB_SetItemBounds (hwnd);
3343 InvalidateRect(hwnd, NULL, TRUE);
3344 return 0;
3346 case WM_SYSCOLORCHANGE:
3347 COMCTL32_RefreshSysColors();
3348 return 0;
3350 case WM_KILLFOCUS:
3351 case WM_SETFOCUS:
3352 return TAB_FocusChanging(hwnd, uMsg, wParam, lParam);
3354 case WM_KEYUP:
3355 return TAB_KeyUp(hwnd, wParam);
3356 case WM_NCHITTEST:
3357 return TAB_NCHitTest(hwnd, lParam);
3359 default:
3360 if ((uMsg >= WM_USER) && (uMsg < WM_APP))
3361 WARN("unknown msg %04x wp=%08x lp=%08lx\n",
3362 uMsg, wParam, lParam);
3363 return DefWindowProcA(hwnd, uMsg, wParam, lParam);
3366 return 0;
3370 VOID
3371 TAB_Register (void)
3373 WNDCLASSA wndClass;
3375 ZeroMemory (&wndClass, sizeof(WNDCLASSA));
3376 wndClass.style = CS_GLOBALCLASS | CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
3377 wndClass.lpfnWndProc = (WNDPROC)TAB_WindowProc;
3378 wndClass.cbClsExtra = 0;
3379 wndClass.cbWndExtra = sizeof(TAB_INFO *);
3380 wndClass.hCursor = LoadCursorA (0, (LPSTR)IDC_ARROW);
3381 wndClass.hbrBackground = NULL;
3382 wndClass.lpszClassName = WC_TABCONTROLA;
3384 RegisterClassA (&wndClass);
3388 VOID
3389 TAB_Unregister (void)
3391 UnregisterClassA (WC_TABCONTROLA, NULL);