Streamline dlls/ and programs/ in terms of indentation.
[wine/dcerpc.git] / dlls / comctl32 / tab.c
blob59ad29a765eac3c19a63bd5e50dbc43e78c8bc4d
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 * Image list support
25 * Unicode support (under construction)
27 * FIXME:
28 * UpDown control not displayed until after a tab is clicked on
31 #include <stdarg.h>
32 #include <string.h>
34 #include "windef.h"
35 #include "winbase.h"
36 #include "wingdi.h"
37 #include "winuser.h"
38 #include "winnls.h"
39 #include "commctrl.h"
40 #include "comctl32.h"
41 #include "wine/debug.h"
42 #include <math.h>
44 WINE_DEFAULT_DEBUG_CHANNEL(tab);
46 typedef struct
48 UINT mask;
49 DWORD dwState;
50 LPWSTR pszText;
51 INT iImage;
52 LPARAM lParam;
53 RECT rect; /* bounding rectangle of the item relative to the
54 * leftmost item (the leftmost item, 0, would have a
55 * "left" member of 0 in this rectangle)
57 * additionally the top member hold the row number
58 * and bottom is unused and should be 0 */
59 } TAB_ITEM;
61 typedef struct
63 HWND hwndNotify; /* notification window (parent) */
64 UINT uNumItem; /* number of tab items */
65 UINT uNumRows; /* number of tab rows */
66 INT tabHeight; /* height of the tab row */
67 INT tabWidth; /* width of tabs */
68 INT tabMinWidth; /* minimum width of items */
69 USHORT uHItemPadding; /* amount of horizontal padding, in pixels */
70 USHORT uVItemPadding; /* amount of vertical padding, in pixels */
71 USHORT uHItemPadding_s; /* Set amount of horizontal padding, in pixels */
72 USHORT uVItemPadding_s; /* Set amount of vertical padding, in pixels */
73 HFONT hFont; /* handle to the current font */
74 HCURSOR hcurArrow; /* handle to the current cursor */
75 HIMAGELIST himl; /* handle to a image list (may be 0) */
76 HWND hwndToolTip; /* handle to tab's tooltip */
77 INT leftmostVisible; /* Used for scrolling, this member contains
78 * the index of the first visible item */
79 INT iSelected; /* the currently selected item */
80 INT iHotTracked; /* the highlighted item under the mouse */
81 INT uFocus; /* item which has the focus */
82 TAB_ITEM* items; /* pointer to an array of TAB_ITEM's */
83 BOOL DoRedraw; /* flag for redrawing when tab contents is changed*/
84 BOOL needsScrolling; /* TRUE if the size of the tabs is greater than
85 * the size of the control */
86 BOOL fHeightSet; /* was the height of the tabs explicitly set? */
87 BOOL bUnicode; /* Unicode control? */
88 HWND hwndUpDown; /* Updown control used for scrolling */
89 } TAB_INFO;
91 /******************************************************************************
92 * Positioning constants
94 #define SELECTED_TAB_OFFSET 2
95 #define ROUND_CORNER_SIZE 2
96 #define DISPLAY_AREA_PADDINGX 2
97 #define DISPLAY_AREA_PADDINGY 2
98 #define CONTROL_BORDER_SIZEX 2
99 #define CONTROL_BORDER_SIZEY 2
100 #define BUTTON_SPACINGX 3
101 #define BUTTON_SPACINGY 4
102 #define FLAT_BTN_SPACINGX 8
103 #define DEFAULT_TAB_WIDTH 96
105 #define TAB_GetInfoPtr(hwnd) ((TAB_INFO *)GetWindowLongA(hwnd,0))
107 /******************************************************************************
108 * Hot-tracking timer constants
110 #define TAB_HOTTRACK_TIMER 1
111 #define TAB_HOTTRACK_TIMER_INTERVAL 100 /* milliseconds */
113 /******************************************************************************
114 * Prototypes
116 static void TAB_Refresh (HWND hwnd, HDC hdc);
117 static void TAB_InvalidateTabArea(HWND hwnd, TAB_INFO* infoPtr);
118 static void TAB_EnsureSelectionVisible(HWND hwnd, TAB_INFO* infoPtr);
119 static void TAB_DrawItem(HWND hwnd, HDC hdc, INT iItem);
120 static void TAB_DrawItemInterior(HWND hwnd, HDC hdc, INT iItem, RECT* drawRect);
122 static BOOL
123 TAB_SendSimpleNotify (HWND hwnd, UINT code)
125 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
126 NMHDR nmhdr;
128 nmhdr.hwndFrom = hwnd;
129 nmhdr.idFrom = GetWindowLongA(hwnd, GWL_ID);
130 nmhdr.code = code;
132 return (BOOL) SendMessageA (infoPtr->hwndNotify, WM_NOTIFY,
133 (WPARAM) nmhdr.idFrom, (LPARAM) &nmhdr);
136 static VOID
137 TAB_RelayEvent (HWND hwndTip, HWND hwndMsg, UINT uMsg,
138 WPARAM wParam, LPARAM lParam)
140 MSG msg;
142 msg.hwnd = hwndMsg;
143 msg.message = uMsg;
144 msg.wParam = wParam;
145 msg.lParam = lParam;
146 msg.time = GetMessageTime ();
147 msg.pt.x = LOWORD(GetMessagePos ());
148 msg.pt.y = HIWORD(GetMessagePos ());
150 SendMessageA (hwndTip, TTM_RELAYEVENT, 0, (LPARAM)&msg);
153 static void
154 TAB_DumpItemExternalA(TCITEMA *pti, UINT iItem)
156 if (TRACE_ON(tab)) {
157 TRACE("external tab %d, mask=0x%08x, dwState=0x%08x, dwStateMask=0x%08x, cchTextMax=0x%08x\n",
158 iItem, pti->mask, pti->dwState, pti->dwStateMask, pti->cchTextMax);
159 TRACE("external tab %d, iImage=%d, lParam=0x%08lx, pszTextA=%s\n",
160 iItem, pti->iImage, pti->lParam, debugstr_a(pti->pszText));
165 static void
166 TAB_DumpItemExternalW(TCITEMW *pti, UINT iItem)
168 if (TRACE_ON(tab)) {
169 TRACE("external tab %d, mask=0x%08x, dwState=0x%08lx, dwStateMask=0x%08lx, cchTextMax=0x%08x\n",
170 iItem, pti->mask, pti->dwState, pti->dwStateMask, pti->cchTextMax);
171 TRACE("external tab %d, iImage=%d, lParam=0x%08lx, pszTextW=%s\n",
172 iItem, pti->iImage, pti->lParam, debugstr_w(pti->pszText));
176 static void
177 TAB_DumpItemInternal(TAB_INFO *infoPtr, UINT iItem)
179 if (TRACE_ON(tab)) {
180 TAB_ITEM *ti;
182 ti = &infoPtr->items[iItem];
183 TRACE("tab %d, mask=0x%08x, dwState=0x%08lx, pszText=%s, iImage=%d\n",
184 iItem, ti->mask, ti->dwState, debugstr_w(ti->pszText),
185 ti->iImage);
186 TRACE("tab %d, lParam=0x%08lx, rect.left=%ld, rect.top(row)=%ld\n",
187 iItem, ti->lParam, ti->rect.left, ti->rect.top);
191 static LRESULT
192 TAB_GetCurSel (HWND hwnd)
194 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
196 return infoPtr->iSelected;
199 static LRESULT
200 TAB_GetCurFocus (HWND hwnd)
202 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
204 return infoPtr->uFocus;
207 static LRESULT
208 TAB_GetToolTips (HWND hwnd, WPARAM wParam, LPARAM lParam)
210 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
212 if (infoPtr == NULL) return 0;
213 return (LRESULT)infoPtr->hwndToolTip;
216 static LRESULT
217 TAB_SetCurSel (HWND hwnd,WPARAM wParam)
219 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
220 INT iItem = (INT)wParam;
221 INT prevItem;
223 prevItem = -1;
224 if ((iItem >= 0) && (iItem < infoPtr->uNumItem)) {
225 prevItem=infoPtr->iSelected;
226 infoPtr->iSelected=iItem;
227 TAB_EnsureSelectionVisible(hwnd, infoPtr);
228 TAB_InvalidateTabArea(hwnd, infoPtr);
230 return prevItem;
233 static LRESULT
234 TAB_SetCurFocus (HWND hwnd,WPARAM wParam)
236 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
237 INT iItem=(INT) wParam;
239 if ((iItem < 0) || (iItem >= infoPtr->uNumItem)) return 0;
241 if (GetWindowLongA(hwnd, GWL_STYLE) & TCS_BUTTONS) {
242 FIXME("Should set input focus\n");
243 } else {
244 int oldFocus = infoPtr->uFocus;
245 if (infoPtr->iSelected != iItem || infoPtr->uFocus == -1 ) {
246 infoPtr->uFocus = iItem;
247 if (oldFocus != -1) {
248 if (TAB_SendSimpleNotify(hwnd, TCN_SELCHANGING)!=TRUE) {
249 infoPtr->iSelected = iItem;
250 TAB_SendSimpleNotify(hwnd, TCN_SELCHANGE);
252 else
253 infoPtr->iSelected = iItem;
254 TAB_EnsureSelectionVisible(hwnd, infoPtr);
255 TAB_InvalidateTabArea(hwnd, infoPtr);
259 return 0;
262 static LRESULT
263 TAB_SetToolTips (HWND hwnd, WPARAM wParam, LPARAM lParam)
265 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
267 if (infoPtr == NULL) return 0;
268 infoPtr->hwndToolTip = (HWND)wParam;
269 return 0;
272 static LRESULT
273 TAB_SetPadding (HWND hwnd, WPARAM wParam, LPARAM lParam)
275 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
277 if (infoPtr == NULL) return 0;
278 infoPtr->uHItemPadding_s=LOWORD(lParam);
279 infoPtr->uVItemPadding_s=HIWORD(lParam);
280 return 0;
283 /******************************************************************************
284 * TAB_InternalGetItemRect
286 * This method will calculate the rectangle representing a given tab item in
287 * client coordinates. This method takes scrolling into account.
289 * This method returns TRUE if the item is visible in the window and FALSE
290 * if it is completely outside the client area.
292 static BOOL TAB_InternalGetItemRect(
293 HWND hwnd,
294 TAB_INFO* infoPtr,
295 INT itemIndex,
296 RECT* itemRect,
297 RECT* selectedRect)
299 RECT tmpItemRect,clientRect;
300 LONG lStyle = GetWindowLongA(hwnd, GWL_STYLE);
302 /* Perform a sanity check and a trivial visibility check. */
303 if ( (infoPtr->uNumItem <= 0) ||
304 (itemIndex >= infoPtr->uNumItem) ||
305 (!((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)) && (itemIndex < infoPtr->leftmostVisible)) )
306 return FALSE;
309 * Avoid special cases in this procedure by assigning the "out"
310 * parameters if the caller didn't supply them
312 if (itemRect == NULL)
313 itemRect = &tmpItemRect;
315 /* Retrieve the unmodified item rect. */
316 *itemRect = infoPtr->items[itemIndex].rect;
318 /* calculate the times bottom and top based on the row */
319 GetClientRect(hwnd, &clientRect);
321 if ((lStyle & TCS_BOTTOM) && !(lStyle & TCS_VERTICAL))
323 itemRect->bottom = clientRect.bottom -
324 itemRect->top * (infoPtr->tabHeight - 2) -
325 ((lStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : SELECTED_TAB_OFFSET);
327 itemRect->top = clientRect.bottom -
328 infoPtr->tabHeight -
329 itemRect->top * (infoPtr->tabHeight - 2) -
330 ((lStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : SELECTED_TAB_OFFSET);
332 else if((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
334 itemRect->right = clientRect.right - SELECTED_TAB_OFFSET - itemRect->left * (infoPtr->tabHeight - 2) -
335 ((lStyle & TCS_BUTTONS) ? itemRect->left * BUTTON_SPACINGY : 0);
336 itemRect->left = clientRect.right - infoPtr->tabHeight - itemRect->left * (infoPtr->tabHeight - 2) -
337 ((lStyle & TCS_BUTTONS) ? itemRect->left * BUTTON_SPACINGY : 0);
339 else if((lStyle & TCS_VERTICAL) && !(lStyle & TCS_BOTTOM))
341 itemRect->right = clientRect.left + infoPtr->tabHeight + itemRect->left * (infoPtr->tabHeight - 2) +
342 ((lStyle & TCS_BUTTONS) ? itemRect->left * BUTTON_SPACINGY : 0);
343 itemRect->left = clientRect.left + SELECTED_TAB_OFFSET + itemRect->left * (infoPtr->tabHeight - 2) +
344 ((lStyle & TCS_BUTTONS) ? itemRect->left * BUTTON_SPACINGY : 0);
346 else if(!(lStyle & TCS_VERTICAL) && !(lStyle & TCS_BOTTOM)) /* not TCS_BOTTOM and not TCS_VERTICAL */
348 itemRect->bottom = clientRect.top +
349 infoPtr->tabHeight +
350 itemRect->top * (infoPtr->tabHeight - 2) +
351 ((lStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : SELECTED_TAB_OFFSET);
352 itemRect->top = clientRect.top +
353 itemRect->top * (infoPtr->tabHeight - 2) +
354 ((lStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : SELECTED_TAB_OFFSET);
358 * "scroll" it to make sure the item at the very left of the
359 * tab control is the leftmost visible tab.
361 if(lStyle & TCS_VERTICAL)
363 OffsetRect(itemRect,
365 -(clientRect.bottom - infoPtr->items[infoPtr->leftmostVisible].rect.bottom));
368 * Move the rectangle so the first item is slightly offset from
369 * the bottom of the tab control.
371 OffsetRect(itemRect,
373 -SELECTED_TAB_OFFSET);
375 } else
377 OffsetRect(itemRect,
378 -infoPtr->items[infoPtr->leftmostVisible].rect.left,
382 * Move the rectangle so the first item is slightly offset from
383 * the left of the tab control.
385 OffsetRect(itemRect,
386 SELECTED_TAB_OFFSET,
389 TRACE("item %d tab h=%d, rect=(%ld,%ld)-(%ld,%ld)\n",
390 itemIndex, infoPtr->tabHeight,
391 itemRect->left, itemRect->top, itemRect->right, itemRect->bottom);
393 /* Now, calculate the position of the item as if it were selected. */
394 if (selectedRect!=NULL)
396 CopyRect(selectedRect, itemRect);
398 /* The rectangle of a selected item is a bit wider. */
399 if(lStyle & TCS_VERTICAL)
400 InflateRect(selectedRect, 0, SELECTED_TAB_OFFSET);
401 else
402 InflateRect(selectedRect, SELECTED_TAB_OFFSET, 0);
404 /* If it also a bit higher. */
405 if ((lStyle & TCS_BOTTOM) && !(lStyle & TCS_VERTICAL))
407 selectedRect->bottom += SELECTED_TAB_OFFSET;
409 else if((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
411 selectedRect->left -= 2; /* the border is thicker on the right */
412 selectedRect->right += SELECTED_TAB_OFFSET;
414 else if(lStyle & TCS_VERTICAL)
416 selectedRect->left -= SELECTED_TAB_OFFSET;
417 selectedRect->right += 1;
419 else
421 selectedRect->top -= SELECTED_TAB_OFFSET;
422 selectedRect->bottom -= 1;
426 return TRUE;
429 static BOOL TAB_GetItemRect(HWND hwnd, WPARAM wParam, LPARAM lParam)
431 return TAB_InternalGetItemRect(hwnd, TAB_GetInfoPtr(hwnd), (INT)wParam,
432 (LPRECT)lParam, (LPRECT)NULL);
435 /******************************************************************************
436 * TAB_KeyUp
438 * This method is called to handle keyboard input
440 static LRESULT TAB_KeyUp(
441 HWND hwnd,
442 WPARAM keyCode)
444 TAB_INFO* infoPtr = TAB_GetInfoPtr(hwnd);
445 int newItem = -1;
447 switch (keyCode)
449 case VK_LEFT:
450 newItem = infoPtr->uFocus - 1;
451 break;
452 case VK_RIGHT:
453 newItem = infoPtr->uFocus + 1;
454 break;
458 * If we changed to a valid item, change the selection
460 if ((newItem >= 0) &&
461 (newItem < infoPtr->uNumItem) &&
462 (infoPtr->uFocus != newItem))
464 if (!TAB_SendSimpleNotify(hwnd, TCN_SELCHANGING))
466 infoPtr->iSelected = newItem;
467 infoPtr->uFocus = newItem;
468 TAB_SendSimpleNotify(hwnd, TCN_SELCHANGE);
470 TAB_EnsureSelectionVisible(hwnd, infoPtr);
471 TAB_InvalidateTabArea(hwnd, infoPtr);
475 return 0;
478 /******************************************************************************
479 * TAB_FocusChanging
481 * This method is called whenever the focus goes in or out of this control
482 * it is used to update the visual state of the control.
484 static LRESULT TAB_FocusChanging(
485 HWND hwnd,
486 UINT uMsg,
487 WPARAM wParam,
488 LPARAM lParam)
490 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
491 RECT selectedRect;
492 BOOL isVisible;
495 * Get the rectangle for the item.
497 isVisible = TAB_InternalGetItemRect(hwnd,
498 infoPtr,
499 infoPtr->uFocus,
500 NULL,
501 &selectedRect);
504 * If the rectangle is not completely invisible, invalidate that
505 * portion of the window.
507 if (isVisible)
509 TRACE("invalidate (%ld,%ld)-(%ld,%ld)\n",
510 selectedRect.left,selectedRect.top,
511 selectedRect.right,selectedRect.bottom);
512 InvalidateRect(hwnd, &selectedRect, TRUE);
516 * Don't otherwise disturb normal behavior.
518 return DefWindowProcA (hwnd, uMsg, wParam, lParam);
521 static INT TAB_InternalHitTest (
522 HWND hwnd,
523 TAB_INFO* infoPtr,
524 POINT pt,
525 UINT* flags)
528 RECT rect;
529 INT iCount;
531 for (iCount = 0; iCount < infoPtr->uNumItem; iCount++)
533 TAB_InternalGetItemRect(hwnd, infoPtr, iCount, &rect, NULL);
535 if (PtInRect(&rect, pt))
537 *flags = TCHT_ONITEM;
538 return iCount;
542 *flags = TCHT_NOWHERE;
543 return -1;
546 static LRESULT
547 TAB_HitTest (HWND hwnd, WPARAM wParam, LPARAM lParam)
549 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
550 LPTCHITTESTINFO lptest = (LPTCHITTESTINFO) lParam;
552 return TAB_InternalHitTest (hwnd, infoPtr, lptest->pt, &lptest->flags);
555 /******************************************************************************
556 * TAB_NCHitTest
558 * Napster v2b5 has a tab control for its main navigation which has a client
559 * area that covers the whole area of the dialog pages.
560 * That's why it receives all msgs for that area and the underlying dialog ctrls
561 * are dead.
562 * So I decided that we should handle WM_NCHITTEST here and return
563 * HTTRANSPARENT if we don't hit the tab control buttons.
564 * FIXME: WM_NCHITTEST handling correct ? Fix it if you know that Windows
565 * doesn't do it that way. Maybe depends on tab control styles ?
567 static LRESULT
568 TAB_NCHitTest (HWND hwnd, LPARAM lParam)
570 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
571 POINT pt;
572 UINT dummyflag;
574 pt.x = LOWORD(lParam);
575 pt.y = HIWORD(lParam);
576 ScreenToClient(hwnd, &pt);
578 if (TAB_InternalHitTest(hwnd, infoPtr, pt, &dummyflag) == -1)
579 return HTTRANSPARENT;
580 else
581 return HTCLIENT;
584 static LRESULT
585 TAB_LButtonDown (HWND hwnd, WPARAM wParam, LPARAM lParam)
587 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
588 POINT pt;
589 INT newItem, dummy;
591 if (infoPtr->hwndToolTip)
592 TAB_RelayEvent (infoPtr->hwndToolTip, hwnd,
593 WM_LBUTTONDOWN, wParam, lParam);
595 if (GetWindowLongA(hwnd, GWL_STYLE) & TCS_FOCUSONBUTTONDOWN ) {
596 SetFocus (hwnd);
599 if (infoPtr->hwndToolTip)
600 TAB_RelayEvent (infoPtr->hwndToolTip, hwnd,
601 WM_LBUTTONDOWN, wParam, lParam);
603 pt.x = (INT)LOWORD(lParam);
604 pt.y = (INT)HIWORD(lParam);
606 newItem = TAB_InternalHitTest (hwnd, infoPtr, pt, &dummy);
608 TRACE("On Tab, item %d\n", newItem);
610 if ((newItem != -1) && (infoPtr->iSelected != newItem))
612 if (TAB_SendSimpleNotify(hwnd, TCN_SELCHANGING) != TRUE)
614 infoPtr->iSelected = newItem;
615 infoPtr->uFocus = newItem;
616 TAB_SendSimpleNotify(hwnd, TCN_SELCHANGE);
618 TAB_EnsureSelectionVisible(hwnd, infoPtr);
620 TAB_InvalidateTabArea(hwnd, infoPtr);
623 return 0;
626 static LRESULT
627 TAB_LButtonUp (HWND hwnd, WPARAM wParam, LPARAM lParam)
629 TAB_SendSimpleNotify(hwnd, NM_CLICK);
631 return 0;
634 static LRESULT
635 TAB_RButtonDown (HWND hwnd, WPARAM wParam, LPARAM lParam)
637 TAB_SendSimpleNotify(hwnd, NM_RCLICK);
638 return 0;
641 /******************************************************************************
642 * TAB_DrawLoneItemInterior
644 * This calls TAB_DrawItemInterior. However, TAB_DrawItemInterior is normally
645 * called by TAB_DrawItem which is normally called by TAB_Refresh which sets
646 * up the device context and font. This routine does the same setup but
647 * only calls TAB_DrawItemInterior for the single specified item.
649 static void
650 TAB_DrawLoneItemInterior(HWND hwnd, TAB_INFO* infoPtr, int iItem)
652 HDC hdc = GetDC(hwnd);
653 RECT r, rC;
655 GetWindowRect(hwnd, &rC);
656 GetWindowRect(infoPtr->hwndUpDown, &r);
657 ExcludeClipRect(hdc, r.left - rC.left, r.top - rC.top, r.right - rC.left, r.bottom - rC.top);
658 TAB_DrawItemInterior(hwnd, hdc, iItem, NULL);
659 ReleaseDC(hwnd, hdc);
662 /******************************************************************************
663 * TAB_HotTrackTimerProc
665 * When a mouse-move event causes a tab to be highlighted (hot-tracking), a
666 * timer is setup so we can check if the mouse is moved out of our window.
667 * (We don't get an event when the mouse leaves, the mouse-move events just
668 * stop being delivered to our window and just start being delivered to
669 * another window.) This function is called when the timer triggers so
670 * we can check if the mouse has left our window. If so, we un-highlight
671 * the hot-tracked tab.
673 static VOID CALLBACK
674 TAB_HotTrackTimerProc
676 HWND hwnd, /* handle of window for timer messages */
677 UINT uMsg, /* WM_TIMER message */
678 UINT idEvent, /* timer identifier */
679 DWORD dwTime /* current system time */
682 TAB_INFO* infoPtr = TAB_GetInfoPtr(hwnd);
684 if (infoPtr != NULL && infoPtr->iHotTracked >= 0)
686 POINT pt;
689 ** If we can't get the cursor position, or if the cursor is outside our
690 ** window, we un-highlight the hot-tracked tab. Note that the cursor is
691 ** "outside" even if it is within our bounding rect if another window
692 ** overlaps. Note also that the case where the cursor stayed within our
693 ** window but has moved off the hot-tracked tab will be handled by the
694 ** WM_MOUSEMOVE event.
696 if (!GetCursorPos(&pt) || WindowFromPoint(pt) != hwnd)
698 /* Redraw iHotTracked to look normal */
699 INT iRedraw = infoPtr->iHotTracked;
700 infoPtr->iHotTracked = -1;
701 TAB_DrawLoneItemInterior(hwnd, infoPtr, iRedraw);
703 /* Kill this timer */
704 KillTimer(hwnd, TAB_HOTTRACK_TIMER);
709 /******************************************************************************
710 * TAB_RecalcHotTrack
712 * If a tab control has the TCS_HOTTRACK style, then the tab under the mouse
713 * should be highlighted. This function determines which tab in a tab control,
714 * if any, is under the mouse and records that information. The caller may
715 * supply output parameters to receive the item number of the tab item which
716 * was highlighted but isn't any longer and of the tab item which is now
717 * highlighted but wasn't previously. The caller can use this information to
718 * selectively redraw those tab items.
720 * If the caller has a mouse position, it can supply it through the pos
721 * parameter. For example, TAB_MouseMove does this. Otherwise, the caller
722 * supplies NULL and this function determines the current mouse position
723 * itself.
725 static void
726 TAB_RecalcHotTrack
728 HWND hwnd,
729 const LPARAM* pos,
730 int* out_redrawLeave,
731 int* out_redrawEnter
734 TAB_INFO* infoPtr = TAB_GetInfoPtr(hwnd);
736 int item = -1;
739 if (out_redrawLeave != NULL)
740 *out_redrawLeave = -1;
741 if (out_redrawEnter != NULL)
742 *out_redrawEnter = -1;
744 if (GetWindowLongA(hwnd, GWL_STYLE) & TCS_HOTTRACK)
746 POINT pt;
747 UINT flags;
749 if (pos == NULL)
751 GetCursorPos(&pt);
752 ScreenToClient(hwnd, &pt);
754 else
756 pt.x = LOWORD(*pos);
757 pt.y = HIWORD(*pos);
760 item = TAB_InternalHitTest(hwnd, infoPtr, pt, &flags);
763 if (item != infoPtr->iHotTracked)
765 if (infoPtr->iHotTracked >= 0)
767 /* Mark currently hot-tracked to be redrawn to look normal */
768 if (out_redrawLeave != NULL)
769 *out_redrawLeave = infoPtr->iHotTracked;
771 if (item < 0)
773 /* Kill timer which forces recheck of mouse pos */
774 KillTimer(hwnd, TAB_HOTTRACK_TIMER);
777 else
779 /* Start timer so we recheck mouse pos */
780 UINT timerID = SetTimer
782 hwnd,
783 TAB_HOTTRACK_TIMER,
784 TAB_HOTTRACK_TIMER_INTERVAL,
785 TAB_HotTrackTimerProc
788 if (timerID == 0)
789 return; /* Hot tracking not available */
792 infoPtr->iHotTracked = item;
794 if (item >= 0)
796 /* Mark new hot-tracked to be redrawn to look highlighted */
797 if (out_redrawEnter != NULL)
798 *out_redrawEnter = item;
803 /******************************************************************************
804 * TAB_MouseMove
806 * Handles the mouse-move event. Updates tooltips. Updates hot-tracking.
808 static LRESULT
809 TAB_MouseMove (HWND hwnd, WPARAM wParam, LPARAM lParam)
811 int redrawLeave;
812 int redrawEnter;
814 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
816 if (infoPtr->hwndToolTip)
817 TAB_RelayEvent (infoPtr->hwndToolTip, hwnd,
818 WM_LBUTTONDOWN, wParam, lParam);
820 /* Determine which tab to highlight. Redraw tabs which change highlight
821 ** status. */
822 TAB_RecalcHotTrack(hwnd, &lParam, &redrawLeave, &redrawEnter);
824 if (redrawLeave != -1)
825 TAB_DrawLoneItemInterior(hwnd, infoPtr, redrawLeave);
826 if (redrawEnter != -1)
827 TAB_DrawLoneItemInterior(hwnd, infoPtr, redrawEnter);
829 return 0;
832 /******************************************************************************
833 * TAB_AdjustRect
835 * Calculates the tab control's display area given the window rectangle or
836 * the window rectangle given the requested display rectangle.
838 static LRESULT TAB_AdjustRect(
839 HWND hwnd,
840 WPARAM fLarger,
841 LPRECT prc)
843 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
844 DWORD lStyle = GetWindowLongA(hwnd, GWL_STYLE);
846 if(lStyle & TCS_VERTICAL)
848 if (fLarger) /* Go from display rectangle */
850 /* Add the height of the tabs. */
851 if (lStyle & TCS_BOTTOM)
852 prc->right += (infoPtr->tabHeight - 2) * infoPtr->uNumRows + 2;
853 else
854 prc->left -= (infoPtr->tabHeight - 2) * infoPtr->uNumRows + 2;
856 /* FIXME: not sure if these InflateRect's need to have different values for TCS_VERTICAL */
857 /* Inflate the rectangle for the padding */
858 InflateRect(prc, DISPLAY_AREA_PADDINGX, DISPLAY_AREA_PADDINGY);
860 /* Inflate for the border */
861 InflateRect(prc, CONTROL_BORDER_SIZEX, CONTROL_BORDER_SIZEX);
863 else /* Go from window rectangle. */
865 /* FIXME: not sure if these InflateRect's need to have different values for TCS_VERTICAL */
866 /* Deflate the rectangle for the border */
867 InflateRect(prc, -CONTROL_BORDER_SIZEX, -CONTROL_BORDER_SIZEX);
869 /* Deflate the rectangle for the padding */
870 InflateRect(prc, -DISPLAY_AREA_PADDINGX, -DISPLAY_AREA_PADDINGY);
872 /* Remove the height of the tabs. */
873 if (lStyle & TCS_BOTTOM)
874 prc->right -= (infoPtr->tabHeight - 2) * infoPtr->uNumRows + 2;
875 else
876 prc->left += (infoPtr->tabHeight - 2) * infoPtr->uNumRows + 2;
879 else {
880 if (fLarger) /* Go from display rectangle */
882 /* Add the height of the tabs. */
883 if (lStyle & TCS_BOTTOM)
884 prc->bottom += (infoPtr->tabHeight - 2) * infoPtr->uNumRows + 2;
885 else
886 prc->top -= (infoPtr->tabHeight - 2) * infoPtr->uNumRows + 2;
888 /* Inflate the rectangle for the padding */
889 InflateRect(prc, DISPLAY_AREA_PADDINGX, DISPLAY_AREA_PADDINGY);
891 /* Inflate for the border */
892 InflateRect(prc, CONTROL_BORDER_SIZEX, CONTROL_BORDER_SIZEX);
894 else /* Go from window rectangle. */
896 /* Deflate the rectangle for the border */
897 InflateRect(prc, -CONTROL_BORDER_SIZEX, -CONTROL_BORDER_SIZEX);
899 /* Deflate the rectangle for the padding */
900 InflateRect(prc, -DISPLAY_AREA_PADDINGX, -DISPLAY_AREA_PADDINGY);
902 /* Remove the height of the tabs. */
903 if (lStyle & TCS_BOTTOM)
904 prc->bottom -= (infoPtr->tabHeight - 2) * infoPtr->uNumRows + 2;
905 else
906 prc->top += (infoPtr->tabHeight - 2) * infoPtr->uNumRows + 2;
910 return 0;
913 /******************************************************************************
914 * TAB_OnHScroll
916 * This method will handle the notification from the scroll control and
917 * perform the scrolling operation on the tab control.
919 static LRESULT TAB_OnHScroll(
920 HWND hwnd,
921 int nScrollCode,
922 int nPos,
923 HWND hwndScroll)
925 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
927 if(nScrollCode == SB_THUMBPOSITION && nPos != infoPtr->leftmostVisible)
929 if(nPos < infoPtr->leftmostVisible)
930 infoPtr->leftmostVisible--;
931 else
932 infoPtr->leftmostVisible++;
934 TAB_RecalcHotTrack(hwnd, NULL, NULL, NULL);
935 TAB_InvalidateTabArea(hwnd, infoPtr);
936 SendMessageA(infoPtr->hwndUpDown, UDM_SETPOS, 0,
937 MAKELONG(infoPtr->leftmostVisible, 0));
940 return 0;
943 /******************************************************************************
944 * TAB_SetupScrolling
946 * This method will check the current scrolling state and make sure the
947 * scrolling control is displayed (or not).
949 static void TAB_SetupScrolling(
950 HWND hwnd,
951 TAB_INFO* infoPtr,
952 const RECT* clientRect)
954 INT maxRange = 0;
955 DWORD lStyle = GetWindowLongA(hwnd, GWL_STYLE);
957 if (infoPtr->needsScrolling)
959 RECT controlPos;
960 INT vsize, tabwidth;
963 * Calculate the position of the scroll control.
965 if(lStyle & TCS_VERTICAL)
967 controlPos.right = clientRect->right;
968 controlPos.left = controlPos.right - 2 * GetSystemMetrics(SM_CXHSCROLL);
970 if (lStyle & TCS_BOTTOM)
972 controlPos.top = clientRect->bottom - infoPtr->tabHeight;
973 controlPos.bottom = controlPos.top + GetSystemMetrics(SM_CYHSCROLL);
975 else
977 controlPos.bottom = clientRect->top + infoPtr->tabHeight;
978 controlPos.top = controlPos.bottom - GetSystemMetrics(SM_CYHSCROLL);
981 else
983 controlPos.right = clientRect->right;
984 controlPos.left = controlPos.right - 2 * GetSystemMetrics(SM_CXHSCROLL);
986 if (lStyle & TCS_BOTTOM)
988 controlPos.top = clientRect->bottom - infoPtr->tabHeight;
989 controlPos.bottom = controlPos.top + GetSystemMetrics(SM_CYHSCROLL);
991 else
993 controlPos.bottom = clientRect->top + infoPtr->tabHeight;
994 controlPos.top = controlPos.bottom - GetSystemMetrics(SM_CYHSCROLL);
999 * If we don't have a scroll control yet, we want to create one.
1000 * If we have one, we want to make sure it's positioned properly.
1002 if (infoPtr->hwndUpDown==0)
1004 infoPtr->hwndUpDown = CreateWindowA("msctls_updown32",
1006 WS_VISIBLE | WS_CHILD | UDS_HORZ,
1007 controlPos.left, controlPos.top,
1008 controlPos.right - controlPos.left,
1009 controlPos.bottom - controlPos.top,
1010 hwnd,
1011 NULL,
1012 NULL,
1013 NULL);
1015 else
1017 SetWindowPos(infoPtr->hwndUpDown,
1018 NULL,
1019 controlPos.left, controlPos.top,
1020 controlPos.right - controlPos.left,
1021 controlPos.bottom - controlPos.top,
1022 SWP_SHOWWINDOW | SWP_NOZORDER);
1025 /* Now calculate upper limit of the updown control range.
1026 * We do this by calculating how many tabs will be offscreen when the
1027 * last tab is visible.
1029 if(infoPtr->uNumItem)
1031 vsize = clientRect->right - (controlPos.right - controlPos.left + 1);
1032 maxRange = infoPtr->uNumItem;
1033 tabwidth = infoPtr->items[maxRange - 1].rect.right;
1035 for(; maxRange > 0; maxRange--)
1037 if(tabwidth - infoPtr->items[maxRange - 1].rect.left > vsize)
1038 break;
1041 if(maxRange == infoPtr->uNumItem)
1042 maxRange--;
1045 else
1047 /* If we once had a scroll control... hide it */
1048 if (infoPtr->hwndUpDown!=0)
1049 ShowWindow(infoPtr->hwndUpDown, SW_HIDE);
1051 if (infoPtr->hwndUpDown)
1052 SendMessageA(infoPtr->hwndUpDown, UDM_SETRANGE32, 0, maxRange);
1055 /******************************************************************************
1056 * TAB_SetItemBounds
1058 * This method will calculate the position rectangles of all the items in the
1059 * control. The rectangle calculated starts at 0 for the first item in the
1060 * list and ignores scrolling and selection.
1061 * It also uses the current font to determine the height of the tab row and
1062 * it checks if all the tabs fit in the client area of the window. If they
1063 * dont, a scrolling control is added.
1065 static void TAB_SetItemBounds (HWND hwnd)
1067 TAB_INFO* infoPtr = TAB_GetInfoPtr(hwnd);
1068 LONG lStyle = GetWindowLongA(hwnd, GWL_STYLE);
1069 TEXTMETRICA fontMetrics;
1070 INT curItem;
1071 INT curItemLeftPos;
1072 INT curItemRowCount;
1073 HFONT hFont, hOldFont;
1074 HDC hdc;
1075 RECT clientRect;
1076 SIZE size;
1077 INT iTemp;
1078 RECT* rcItem;
1079 INT iIndex;
1080 INT icon_width = 0;
1083 * We need to get text information so we need a DC and we need to select
1084 * a font.
1086 hdc = GetDC(hwnd);
1088 hFont = infoPtr->hFont ? infoPtr->hFont : GetStockObject (SYSTEM_FONT);
1089 hOldFont = SelectObject (hdc, hFont);
1092 * We will base the rectangle calculations on the client rectangle
1093 * of the control.
1095 GetClientRect(hwnd, &clientRect);
1097 /* if TCS_VERTICAL then swap the height and width so this code places the
1098 tabs along the top of the rectangle and we can just rotate them after
1099 rather than duplicate all of the below code */
1100 if(lStyle & TCS_VERTICAL)
1102 iTemp = clientRect.bottom;
1103 clientRect.bottom = clientRect.right;
1104 clientRect.right = iTemp;
1107 /* Now use hPadding and vPadding */
1108 infoPtr->uHItemPadding = infoPtr->uHItemPadding_s;
1109 infoPtr->uVItemPadding = infoPtr->uVItemPadding_s;
1111 /* The leftmost item will be "0" aligned */
1112 curItemLeftPos = 0;
1113 curItemRowCount = infoPtr->uNumItem ? 1 : 0;
1115 if (!(infoPtr->fHeightSet))
1117 int item_height;
1118 int icon_height = 0;
1120 /* Use the current font to determine the height of a tab. */
1121 GetTextMetricsA(hdc, &fontMetrics);
1123 /* Get the icon height */
1124 if (infoPtr->himl)
1125 ImageList_GetIconSize(infoPtr->himl, 0, &icon_height);
1127 /* Take the highest between font or icon */
1128 if (fontMetrics.tmHeight > icon_height)
1129 item_height = fontMetrics.tmHeight + 2;
1130 else
1131 item_height = icon_height;
1134 * Make sure there is enough space for the letters + icon + growing the
1135 * selected item + extra space for the selected item.
1137 infoPtr->tabHeight = item_height + SELECTED_TAB_OFFSET +
1138 ((lStyle & TCS_BUTTONS) ? 2 : 1) *
1139 infoPtr->uVItemPadding;
1141 TRACE("tabH=%d, tmH=%ld, iconh=%d\n",
1142 infoPtr->tabHeight, fontMetrics.tmHeight, icon_height);
1145 TRACE("client right=%ld\n", clientRect.right);
1147 /* Get the icon width */
1148 if (infoPtr->himl)
1150 ImageList_GetIconSize(infoPtr->himl, &icon_width, 0);
1152 if (lStyle & TCS_FIXEDWIDTH)
1153 icon_width += 4;
1154 else
1155 /* Add padding if icon is present */
1156 icon_width += infoPtr->uHItemPadding;
1159 for (curItem = 0; curItem < infoPtr->uNumItem; curItem++)
1161 /* Set the leftmost position of the tab. */
1162 infoPtr->items[curItem].rect.left = curItemLeftPos;
1164 if (lStyle & TCS_FIXEDWIDTH)
1166 infoPtr->items[curItem].rect.right = infoPtr->items[curItem].rect.left +
1167 max(infoPtr->tabWidth, icon_width);
1169 else
1171 int num = 2;
1173 /* Calculate how wide the tab is depending on the text it contains */
1174 GetTextExtentPoint32W(hdc, infoPtr->items[curItem].pszText,
1175 lstrlenW(infoPtr->items[curItem].pszText), &size);
1177 infoPtr->items[curItem].rect.right = infoPtr->items[curItem].rect.left +
1178 size.cx + icon_width +
1179 num * infoPtr->uHItemPadding;
1180 TRACE("for <%s>, l,r=%ld,%ld, num=%d\n",
1181 debugstr_w(infoPtr->items[curItem].pszText),
1182 infoPtr->items[curItem].rect.left,
1183 infoPtr->items[curItem].rect.right,
1184 num);
1188 * Check if this is a multiline tab control and if so
1189 * check to see if we should wrap the tabs
1191 * Because we are going to arange all these tabs evenly
1192 * really we are basically just counting rows at this point
1196 if (((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)) &&
1197 (infoPtr->items[curItem].rect.right > clientRect.right))
1199 infoPtr->items[curItem].rect.right -=
1200 infoPtr->items[curItem].rect.left;
1202 infoPtr->items[curItem].rect.left = 0;
1203 curItemRowCount++;
1204 TRACE("wrapping <%s>, l,r=%ld,%ld\n",
1205 debugstr_w(infoPtr->items[curItem].pszText),
1206 infoPtr->items[curItem].rect.left,
1207 infoPtr->items[curItem].rect.right);
1210 infoPtr->items[curItem].rect.bottom = 0;
1211 infoPtr->items[curItem].rect.top = curItemRowCount - 1;
1213 TRACE("TextSize: %li\n", size.cx);
1214 TRACE("Rect: T %li, L %li, B %li, R %li\n",
1215 infoPtr->items[curItem].rect.top,
1216 infoPtr->items[curItem].rect.left,
1217 infoPtr->items[curItem].rect.bottom,
1218 infoPtr->items[curItem].rect.right);
1221 * The leftmost position of the next item is the rightmost position
1222 * of this one.
1224 if (lStyle & TCS_BUTTONS)
1226 curItemLeftPos = infoPtr->items[curItem].rect.right + BUTTON_SPACINGX;
1227 if (lStyle & TCS_FLATBUTTONS)
1228 curItemLeftPos += FLAT_BTN_SPACINGX;
1230 else
1231 curItemLeftPos = infoPtr->items[curItem].rect.right;
1234 if (!((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)))
1237 * Check if we need a scrolling control.
1239 infoPtr->needsScrolling = (curItemLeftPos + (2 * SELECTED_TAB_OFFSET) >
1240 clientRect.right);
1242 /* Don't need scrolling, then update infoPtr->leftmostVisible */
1243 if(!infoPtr->needsScrolling)
1244 infoPtr->leftmostVisible = 0;
1246 TAB_SetupScrolling(hwnd, infoPtr, &clientRect);
1249 /* Set the number of rows */
1250 infoPtr->uNumRows = curItemRowCount;
1252 if (((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)) && (infoPtr->uNumItem > 0))
1254 INT widthDiff, remainder;
1255 INT tabPerRow,remTab;
1256 INT iRow,iItm;
1257 INT iIndexStart=0,iIndexEnd=0, iCount=0;
1260 * Ok windows tries to even out the rows. place the same
1261 * number of tabs in each row. So lets give that a shot
1264 tabPerRow = infoPtr->uNumItem / (infoPtr->uNumRows);
1265 remTab = infoPtr->uNumItem % (infoPtr->uNumRows);
1267 for (iItm=0,iRow=0,iCount=0,curItemLeftPos=0;
1268 iItm<infoPtr->uNumItem;
1269 iItm++,iCount++)
1271 /* normalize the current rect */
1273 /* shift the item to the left side of the clientRect */
1274 infoPtr->items[iItm].rect.right -=
1275 infoPtr->items[iItm].rect.left;
1276 infoPtr->items[iItm].rect.left = 0;
1278 TRACE("r=%ld, cl=%d, cl.r=%ld, iCount=%d, iRow=%d, uNumRows=%d, remTab=%d, tabPerRow=%d\n",
1279 infoPtr->items[iItm].rect.right,
1280 curItemLeftPos, clientRect.right,
1281 iCount, iRow, infoPtr->uNumRows, remTab, tabPerRow);
1283 /* if we have reached the maximum number of tabs on this row */
1284 /* move to the next row, reset our current item left position and */
1285 /* the count of items on this row */
1287 /* ************ FIXME FIXME FIXME *************** */
1288 /* */
1289 /* FIXME: */
1290 /* if vertical, */
1291 /* if item n and n+1 are in the same row, */
1292 /* then the display has n+1 lower (toward the */
1293 /* bottom) than n. We do it just the */
1294 /* opposite!!! */
1295 /* */
1296 /* ************ FIXME FIXME FIXME *************** */
1298 if (lStyle & TCS_VERTICAL) {
1299 /* Vert: Add the remaining tabs in the *last* remainder rows */
1300 if (iCount >= ((iRow>=(INT)infoPtr->uNumRows - remTab)?tabPerRow + 1:tabPerRow)) {
1301 iRow++;
1302 curItemLeftPos = 0;
1303 iCount = 0;
1305 } else {
1306 /* Horz: Add the remaining tabs in the *first* remainder rows */
1307 if (iCount >= ((iRow<remTab)?tabPerRow + 1:tabPerRow)) {
1308 iRow++;
1309 curItemLeftPos = 0;
1310 iCount = 0;
1314 /* shift the item to the right to place it as the next item in this row */
1315 infoPtr->items[iItm].rect.left += curItemLeftPos;
1316 infoPtr->items[iItm].rect.right += curItemLeftPos;
1317 infoPtr->items[iItm].rect.top = iRow;
1318 if (lStyle & TCS_BUTTONS)
1320 curItemLeftPos = infoPtr->items[iItm].rect.right + 1;
1321 if (lStyle & TCS_FLATBUTTONS)
1322 curItemLeftPos += FLAT_BTN_SPACINGX;
1324 else
1325 curItemLeftPos = infoPtr->items[iItm].rect.right;
1327 TRACE("arranging <%s>, l,r=%ld,%ld, row=%ld\n",
1328 debugstr_w(infoPtr->items[iItm].pszText),
1329 infoPtr->items[iItm].rect.left,
1330 infoPtr->items[iItm].rect.right,
1331 infoPtr->items[iItm].rect.top);
1335 * Justify the rows
1338 while(iIndexStart < infoPtr->uNumItem)
1341 * find the indexs of the row
1343 /* find the first item on the next row */
1344 for (iIndexEnd=iIndexStart;
1345 (iIndexEnd < infoPtr->uNumItem) &&
1346 (infoPtr->items[iIndexEnd].rect.top ==
1347 infoPtr->items[iIndexStart].rect.top) ;
1348 iIndexEnd++)
1349 /* intentionally blank */;
1352 * we need to justify these tabs so they fill the whole given
1353 * client area
1356 /* find the amount of space remaining on this row */
1357 widthDiff = clientRect.right - (2 * SELECTED_TAB_OFFSET) -
1358 infoPtr->items[iIndexEnd - 1].rect.right;
1360 /* iCount is the number of tab items on this row */
1361 iCount = iIndexEnd - iIndexStart;
1364 if (iCount > 1)
1366 remainder = widthDiff % iCount;
1367 widthDiff = widthDiff / iCount;
1368 /* add widthDiff/iCount, or extra space/items on row, to each item on this row */
1369 for (iIndex=iIndexStart,iCount=0; iIndex < iIndexEnd;
1370 iIndex++,iCount++)
1372 infoPtr->items[iIndex].rect.left += iCount * widthDiff;
1373 infoPtr->items[iIndex].rect.right += (iCount + 1) * widthDiff;
1375 TRACE("adjusting 1 <%s>, l,r=%ld,%ld\n",
1376 debugstr_w(infoPtr->items[iIndex].pszText),
1377 infoPtr->items[iIndex].rect.left,
1378 infoPtr->items[iIndex].rect.right);
1381 infoPtr->items[iIndex - 1].rect.right += remainder;
1383 else /* we have only one item on this row, make it take up the entire row */
1385 infoPtr->items[iIndexStart].rect.left = clientRect.left;
1386 infoPtr->items[iIndexStart].rect.right = clientRect.right - 4;
1388 TRACE("adjusting 2 <%s>, l,r=%ld,%ld\n",
1389 debugstr_w(infoPtr->items[iIndexStart].pszText),
1390 infoPtr->items[iIndexStart].rect.left,
1391 infoPtr->items[iIndexStart].rect.right);
1396 iIndexStart = iIndexEnd;
1401 /* if TCS_VERTICAL rotate the tabs so they are along the side of the clientRect */
1402 if(lStyle & TCS_VERTICAL)
1404 RECT rcOriginal;
1405 for(iIndex = 0; iIndex < infoPtr->uNumItem; iIndex++)
1407 rcItem = &(infoPtr->items[iIndex].rect);
1409 rcOriginal = *rcItem;
1411 /* this is rotating the items by 90 degrees around the center of the control */
1412 rcItem->top = (clientRect.right - (rcOriginal.left - clientRect.left)) - (rcOriginal.right - rcOriginal.left);
1413 rcItem->bottom = rcItem->top + (rcOriginal.right - rcOriginal.left);
1414 rcItem->left = rcOriginal.top;
1415 rcItem->right = rcOriginal.bottom;
1419 TAB_EnsureSelectionVisible(hwnd,infoPtr);
1420 TAB_RecalcHotTrack(hwnd, NULL, NULL, NULL);
1422 /* Cleanup */
1423 SelectObject (hdc, hOldFont);
1424 ReleaseDC (hwnd, hdc);
1428 static void
1429 TAB_EraseTabInterior
1431 HWND hwnd,
1432 HDC hdc,
1433 INT iItem,
1434 RECT* drawRect
1437 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
1438 LONG lStyle = GetWindowLongA(hwnd, GWL_STYLE);
1439 HBRUSH hbr = CreateSolidBrush (comctl32_color.clrBtnFace);
1440 BOOL deleteBrush = TRUE;
1441 RECT rTemp = *drawRect;
1443 InflateRect(&rTemp, -2, -2);
1444 if (lStyle & TCS_BUTTONS)
1446 if (iItem == infoPtr->iSelected)
1448 /* Background color */
1449 if (!(lStyle & TCS_OWNERDRAWFIXED))
1451 DeleteObject(hbr);
1452 hbr = GetSysColorBrush(COLOR_SCROLLBAR);
1454 SetTextColor(hdc, comctl32_color.clr3dFace);
1455 SetBkColor(hdc, comctl32_color.clr3dHilight);
1457 /* if COLOR_WINDOW happens to be the same as COLOR_3DHILIGHT
1458 * we better use 0x55aa bitmap brush to make scrollbar's background
1459 * look different from the window background.
1461 if (comctl32_color.clr3dHilight == comctl32_color.clrWindow)
1462 hbr = COMCTL32_hPattern55AABrush;
1464 deleteBrush = FALSE;
1466 FillRect(hdc, &rTemp, hbr);
1468 else /* ! selected */
1470 if (lStyle & TCS_FLATBUTTONS)
1472 FillRect(hdc, drawRect, hbr);
1473 if (iItem == infoPtr->iHotTracked)
1474 DrawEdge(hdc, drawRect, EDGE_RAISED, BF_SOFT|BF_RECT);
1476 else
1477 FillRect(hdc, &rTemp, hbr);
1481 else /* !TCS_BUTTONS */
1483 FillRect(hdc, &rTemp, hbr);
1486 /* Cleanup */
1487 if (deleteBrush) DeleteObject(hbr);
1490 /******************************************************************************
1491 * TAB_DrawItemInterior
1493 * This method is used to draw the interior (text and icon) of a single tab
1494 * into the tab control.
1496 static void
1497 TAB_DrawItemInterior
1499 HWND hwnd,
1500 HDC hdc,
1501 INT iItem,
1502 RECT* drawRect
1505 TAB_INFO* infoPtr = TAB_GetInfoPtr(hwnd);
1506 LONG lStyle = GetWindowLongA(hwnd, GWL_STYLE);
1508 RECT localRect;
1510 HPEN htextPen;
1511 HPEN holdPen;
1512 INT oldBkMode;
1513 HFONT hOldFont;
1515 if (drawRect == NULL)
1517 BOOL isVisible;
1518 RECT itemRect;
1519 RECT selectedRect;
1522 * Get the rectangle for the item.
1524 isVisible = TAB_InternalGetItemRect(hwnd, infoPtr, iItem, &itemRect, &selectedRect);
1525 if (!isVisible)
1526 return;
1529 * Make sure drawRect points to something valid; simplifies code.
1531 drawRect = &localRect;
1534 * This logic copied from the part of TAB_DrawItem which draws
1535 * the tab background. It's important to keep it in sync. I
1536 * would have liked to avoid code duplication, but couldn't figure
1537 * out how without making spaghetti of TAB_DrawItem.
1539 if (lStyle & TCS_BUTTONS)
1541 *drawRect = itemRect;
1542 if (iItem == infoPtr->iSelected)
1544 OffsetRect(drawRect, 1, 1);
1547 else
1549 if (iItem == infoPtr->iSelected)
1551 *drawRect = selectedRect;
1552 if (lStyle & TCS_BOTTOM)
1554 if (lStyle & TCS_VERTICAL)
1556 drawRect->left++;
1558 else
1560 drawRect->top += 3;
1561 drawRect->left += 1;
1565 else
1566 *drawRect = itemRect;
1569 if (lStyle & TCS_BOTTOM && !(lStyle & TCS_VERTICAL))
1571 drawRect->top--;
1572 drawRect->bottom--;
1576 TRACE("drawRect=(%ld,%ld)-(%ld,%ld)\n",
1577 drawRect->left, drawRect->top, drawRect->right, drawRect->bottom);
1579 /* Clear interior */
1580 TAB_EraseTabInterior (hwnd, hdc, iItem, drawRect);
1582 /* Draw the focus rectangle */
1583 if (!(lStyle & TCS_FOCUSNEVER) &&
1584 (GetFocus() == hwnd) &&
1585 (iItem == infoPtr->uFocus) )
1587 RECT rFocus = *drawRect;
1588 InflateRect(&rFocus, -3, -3);
1589 if (lStyle & TCS_BOTTOM && !(lStyle & TCS_VERTICAL))
1590 rFocus.top -= 3;
1591 if (lStyle & TCS_BUTTONS)
1593 rFocus.left -= 3;
1594 rFocus.top -= 3;
1597 DrawFocusRect(hdc, &rFocus);
1601 * Text pen
1603 htextPen = CreatePen( PS_SOLID, 1, GetSysColor(COLOR_BTNTEXT) );
1604 holdPen = SelectObject(hdc, htextPen);
1605 hOldFont = SelectObject(hdc, infoPtr->hFont);
1608 * Setup for text output
1610 oldBkMode = SetBkMode(hdc, TRANSPARENT);
1611 SetTextColor(hdc, (((iItem == infoPtr->iHotTracked) && !(lStyle & TCS_FLATBUTTONS)) |
1612 (infoPtr->items[iItem].dwState & TCIS_HIGHLIGHTED)) ?
1613 comctl32_color.clrHighlight : comctl32_color.clrBtnText);
1616 * if owner draw, tell the owner to draw
1618 if ((lStyle & TCS_OWNERDRAWFIXED) && GetParent(hwnd))
1620 DRAWITEMSTRUCT dis;
1621 UINT id;
1624 * get the control id
1626 id = GetWindowLongA( hwnd, GWL_ID );
1629 * put together the DRAWITEMSTRUCT
1631 dis.CtlType = ODT_TAB;
1632 dis.CtlID = id;
1633 dis.itemID = iItem;
1634 dis.itemAction = ODA_DRAWENTIRE;
1635 dis.itemState = 0;
1636 if ( iItem == infoPtr->iSelected )
1637 dis.itemState |= ODS_SELECTED;
1638 if (infoPtr->uFocus == iItem)
1639 dis.itemState |= ODS_FOCUS;
1640 dis.hwndItem = hwnd; /* */
1641 dis.hDC = hdc;
1642 CopyRect(&dis.rcItem,drawRect);
1643 InflateRect(&dis.rcItem, -2, -2);
1644 dis.itemData = infoPtr->items[iItem].lParam;
1647 * send the draw message
1649 SendMessageA( infoPtr->hwndNotify, WM_DRAWITEM, (WPARAM)id, (LPARAM)&dis );
1651 else
1653 RECT rcTemp;
1654 RECT rcImage;
1656 /* used to center the icon and text in the tab */
1657 RECT rcText;
1658 INT center_offset_h, center_offset_v;
1660 /* set rcImage to drawRect, we will use top & left in our ImageList_Draw call */
1661 rcImage = *drawRect;
1663 rcTemp = *drawRect;
1665 rcText.left = rcText.top = rcText.right = rcText.bottom = 0;
1667 /* get the rectangle that the text fits in */
1668 DrawTextW(hdc, infoPtr->items[iItem].pszText, -1,
1669 &rcText, DT_CALCRECT);
1671 * If not owner draw, then do the drawing ourselves.
1673 * Draw the icon.
1675 if (infoPtr->himl && (infoPtr->items[iItem].mask & TCIF_IMAGE))
1677 INT cx;
1678 INT cy;
1680 ImageList_GetIconSize(infoPtr->himl, &cx, &cy);
1682 if(lStyle & TCS_VERTICAL)
1684 center_offset_h = ((drawRect->bottom - drawRect->top) - (cy + infoPtr->uHItemPadding + (rcText.right - rcText.left))) / 2;
1685 center_offset_v = ((drawRect->right - drawRect->left) - (cx + infoPtr->uVItemPadding)) / 2;
1687 else
1689 center_offset_h = ((drawRect->right - drawRect->left) - (cx + infoPtr->uHItemPadding + (rcText.right - rcText.left))) / 2;
1690 center_offset_v = ((drawRect->bottom - drawRect->top) - (cy + infoPtr->uVItemPadding)) / 2;
1693 if (lStyle & TCS_FIXEDWIDTH && lStyle & (TCS_FORCELABELLEFT | TCS_FORCEICONLEFT))
1694 center_offset_h = infoPtr->uHItemPadding;
1696 if (center_offset_h < 2)
1697 center_offset_h = 2;
1699 TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%ld,%ld)-(%ld,%ld), textlen=%ld\n",
1700 debugstr_w(infoPtr->items[iItem].pszText), center_offset_h, center_offset_v,
1701 drawRect->left, drawRect->top, drawRect->right, drawRect->bottom,
1702 (rcText.right-rcText.left));
1704 if((lStyle & TCS_VERTICAL) && (lStyle & TCS_BOTTOM))
1706 rcImage.top = drawRect->top + center_offset_h;
1707 /* if tab is TCS_VERTICAL and TCS_BOTTOM, the text is drawn from the */
1708 /* right side of the tab, but the image still uses the left as its x position */
1709 /* this keeps the image always drawn off of the same side of the tab */
1710 rcImage.left = drawRect->right - cx - center_offset_v;
1711 drawRect->top += cy + infoPtr->uHItemPadding;
1713 else if(lStyle & TCS_VERTICAL)
1715 rcImage.top = drawRect->bottom - cy - center_offset_h;
1716 rcImage.left = drawRect->left + center_offset_v;
1717 drawRect->bottom -= cy + infoPtr->uHItemPadding;
1719 else /* normal style, whether TCS_BOTTOM or not */
1721 rcImage.left = drawRect->left + center_offset_h;
1722 rcImage.top = drawRect->top + center_offset_v;
1723 drawRect->left += cx + infoPtr->uHItemPadding;
1726 TRACE("drawing image=%d, left=%ld, top=%ld\n",
1727 infoPtr->items[iItem].iImage, rcImage.left, rcImage.top-1);
1728 ImageList_Draw
1730 infoPtr->himl,
1731 infoPtr->items[iItem].iImage,
1732 hdc,
1733 rcImage.left,
1734 rcImage.top,
1735 ILD_NORMAL
1739 /* Now position text */
1740 if (lStyle & TCS_FIXEDWIDTH && lStyle & TCS_FORCELABELLEFT)
1741 center_offset_h = infoPtr->uHItemPadding;
1742 else
1743 if(lStyle & TCS_VERTICAL)
1744 center_offset_h = ((drawRect->bottom - drawRect->top) - (rcText.right - rcText.left)) / 2;
1745 else
1746 center_offset_h = ((drawRect->right - drawRect->left) - (rcText.right - rcText.left)) / 2;
1748 if(lStyle & TCS_VERTICAL)
1750 if(lStyle & TCS_BOTTOM)
1751 drawRect->top+=center_offset_h;
1752 else
1753 drawRect->bottom-=center_offset_h;
1755 drawRect->left += ((drawRect->right - drawRect->left) - ((rcText.bottom - rcText.top) + infoPtr->uVItemPadding)) / 2;
1757 else
1759 drawRect->left += center_offset_h;
1760 drawRect->top += ((drawRect->bottom - drawRect->top) - ((rcText.bottom - rcText.top) + infoPtr->uVItemPadding)) / 2;
1763 /* Draw the text */
1764 if(lStyle & TCS_VERTICAL) /* if we are vertical rotate the text and each character */
1766 LOGFONTA logfont;
1767 HFONT hFont = 0;
1768 INT nEscapement = 900;
1769 INT nOrientation = 900;
1771 if(lStyle & TCS_BOTTOM)
1773 nEscapement = -900;
1774 nOrientation = -900;
1777 /* to get a font with the escapement and orientation we are looking for, we need to */
1778 /* call CreateFontIndirectA, which requires us to set the values of the logfont we pass in */
1779 if (!GetObjectA((infoPtr->hFont) ?
1780 infoPtr->hFont : GetStockObject(SYSTEM_FONT),
1781 sizeof(LOGFONTA),&logfont))
1783 INT iPointSize = 9;
1785 lstrcpyA(logfont.lfFaceName, "Arial");
1786 logfont.lfHeight = -MulDiv(iPointSize, GetDeviceCaps(hdc, LOGPIXELSY),
1787 72);
1788 logfont.lfWeight = FW_NORMAL;
1789 logfont.lfItalic = 0;
1790 logfont.lfUnderline = 0;
1791 logfont.lfStrikeOut = 0;
1794 logfont.lfEscapement = nEscapement;
1795 logfont.lfOrientation = nOrientation;
1796 hFont = CreateFontIndirectA(&logfont);
1797 SelectObject(hdc, hFont);
1799 ExtTextOutW(hdc,
1800 (lStyle & TCS_BOTTOM) ? drawRect->right : drawRect->left,
1801 (!(lStyle & TCS_BOTTOM)) ? drawRect->bottom : drawRect->top,
1802 ETO_CLIPPED,
1803 drawRect,
1804 infoPtr->items[iItem].pszText,
1805 lstrlenW(infoPtr->items[iItem].pszText),
1808 DeleteObject(hFont);
1810 else
1812 DrawTextW
1814 hdc,
1815 infoPtr->items[iItem].pszText,
1816 lstrlenW(infoPtr->items[iItem].pszText),
1817 drawRect,
1818 DT_LEFT | DT_SINGLELINE
1822 *drawRect = rcTemp; /* restore drawRect */
1826 * Cleanup
1828 SelectObject(hdc, hOldFont);
1829 SetBkMode(hdc, oldBkMode);
1830 SelectObject(hdc, holdPen);
1831 DeleteObject( htextPen );
1834 /******************************************************************************
1835 * TAB_DrawItem
1837 * This method is used to draw a single tab into the tab control.
1839 static void TAB_DrawItem(
1840 HWND hwnd,
1841 HDC hdc,
1842 INT iItem)
1844 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
1845 LONG lStyle = GetWindowLongA(hwnd, GWL_STYLE);
1846 RECT itemRect;
1847 RECT selectedRect;
1848 BOOL isVisible;
1849 RECT r, fillRect, r1;
1850 INT clRight = 0;
1851 INT clBottom = 0;
1852 COLORREF bkgnd, corner;
1855 * Get the rectangle for the item.
1857 isVisible = TAB_InternalGetItemRect(hwnd,
1858 infoPtr,
1859 iItem,
1860 &itemRect,
1861 &selectedRect);
1863 if (isVisible)
1865 RECT rUD, rC;
1867 /* Clip UpDown control to not draw over it */
1868 GetWindowRect(hwnd, &rC);
1869 GetWindowRect(infoPtr->hwndUpDown, &rUD);
1870 ExcludeClipRect(hdc, rUD.left - rC.left, rUD.top - rC.top, rUD.right - rC.left, rUD.bottom - rC.top);
1872 /* If you need to see what the control is doing,
1873 * then override these variables. They will change what
1874 * fill colors are used for filling the tabs, and the
1875 * corners when drawing the edge.
1877 bkgnd = comctl32_color.clrBtnFace;
1878 corner = comctl32_color.clrBtnFace;
1880 if (lStyle & TCS_BUTTONS)
1882 /* Get item rectangle */
1883 r = itemRect;
1885 /* Separators between flat buttons */
1886 if (lStyle & TCS_FLATBUTTONS)
1888 r1 = r;
1889 r1.right += (FLAT_BTN_SPACINGX -2);
1890 DrawEdge(hdc, &r1, EDGE_ETCHED, BF_RIGHT);
1893 if (iItem == infoPtr->iSelected)
1895 DrawEdge(hdc, &r, EDGE_SUNKEN, BF_SOFT|BF_RECT);
1897 OffsetRect(&r, 1, 1);
1899 else /* ! selected */
1901 if (!(lStyle & TCS_FLATBUTTONS))
1902 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_RECT);
1905 else /* !TCS_BUTTONS */
1907 /* We draw a rectangle of different sizes depending on the selection
1908 * state. */
1909 if (iItem == infoPtr->iSelected) {
1910 RECT rect;
1911 GetClientRect (hwnd, &rect);
1912 clRight = rect.right;
1913 clBottom = rect.bottom;
1914 r = selectedRect;
1916 else
1917 r = itemRect;
1920 * Erase the background. (Delay it but setup rectangle.)
1921 * This is necessary when drawing the selected item since it is larger
1922 * than the others, it might overlap with stuff already drawn by the
1923 * other tabs
1925 fillRect = r;
1927 if(lStyle & TCS_VERTICAL)
1929 /* These are for adjusting the drawing of a Selected tab */
1930 /* The initial values are for the normal case of non-Selected */
1931 int ZZ = 1; /* Do not strech if selected */
1932 if (iItem == infoPtr->iSelected) {
1933 ZZ = 0;
1935 /* if leftmost draw the line longer */
1936 if(selectedRect.top == 0)
1937 fillRect.top += 2;
1938 /* if rightmost draw the line longer */
1939 if(selectedRect.bottom == clBottom)
1940 fillRect.bottom -= 2;
1943 if (lStyle & TCS_BOTTOM)
1945 /* Adjust both rectangles to match native */
1946 r.left += (1-ZZ);
1948 TRACE("<left> item=%d, fill=(%ld,%ld)-(%ld,%ld), edge=(%ld,%ld)-(%ld,%ld)\n",
1949 iItem,
1950 fillRect.left,fillRect.top,fillRect.right,fillRect.bottom,
1951 r.left,r.top,r.right,r.bottom);
1953 /* Clear interior */
1954 SetBkColor(hdc, bkgnd);
1955 ExtTextOutA(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
1957 /* Draw rectangular edge around tab */
1958 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_RIGHT|BF_TOP|BF_BOTTOM);
1960 /* Now erase the top corner and draw diagonal edge */
1961 SetBkColor(hdc, corner);
1962 r1.left = r.right - ROUND_CORNER_SIZE - 1;
1963 r1.top = r.top;
1964 r1.right = r.right;
1965 r1.bottom = r1.top + ROUND_CORNER_SIZE;
1966 ExtTextOutA(hdc, 0, 0, 2, &r1, NULL, 0, 0);
1967 r1.right--;
1968 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPLEFT);
1970 /* Now erase the bottom corner and draw diagonal edge */
1971 r1.left = r.right - ROUND_CORNER_SIZE - 1;
1972 r1.bottom = r.bottom;
1973 r1.right = r.right;
1974 r1.top = r1.bottom - ROUND_CORNER_SIZE;
1975 ExtTextOutA(hdc, 0, 0, 2, &r1, NULL, 0, 0);
1976 r1.right--;
1977 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMLEFT);
1979 if ((iItem == infoPtr->iSelected) && (selectedRect.top == 0)) {
1980 r1 = r;
1981 r1.right = r1.left;
1982 r1.left--;
1983 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_TOP);
1987 else
1989 /* Adjust both rectangles to match native */
1990 fillRect.right += (1-ZZ);
1992 TRACE("<left> item=%d, fill=(%ld,%ld)-(%ld,%ld), edge=(%ld,%ld)-(%ld,%ld)\n",
1993 iItem,
1994 fillRect.left,fillRect.top,fillRect.right,fillRect.bottom,
1995 r.left,r.top,r.right,r.bottom);
1997 /* Clear interior */
1998 SetBkColor(hdc, bkgnd);
1999 ExtTextOutA(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2001 /* Draw rectangular edge around tab */
2002 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_TOP|BF_BOTTOM);
2004 /* Now erase the top corner and draw diagonal edge */
2005 SetBkColor(hdc, corner);
2006 r1.left = r.left;
2007 r1.top = r.top;
2008 r1.right = r1.left + ROUND_CORNER_SIZE + 1;
2009 r1.bottom = r1.top + ROUND_CORNER_SIZE;
2010 ExtTextOutA(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2011 r1.left++;
2012 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPRIGHT);
2014 /* Now erase the bottom corner and draw diagonal edge */
2015 r1.left = r.left;
2016 r1.bottom = r.bottom;
2017 r1.right = r1.left + ROUND_CORNER_SIZE + 1;
2018 r1.top = r1.bottom - ROUND_CORNER_SIZE;
2019 ExtTextOutA(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2020 r1.left++;
2021 DrawEdge(hdc, &r1, EDGE_SUNKEN, BF_DIAGONAL_ENDTOPLEFT);
2024 else /* ! TCS_VERTICAL */
2026 /* These are for adjusting the drawing of a Selected tab */
2027 /* The initial values are for the normal case of non-Selected */
2028 int ZZ = 1; /* Do not strech if selected */
2029 if (iItem == infoPtr->iSelected) {
2030 ZZ = 0;
2032 /* if leftmost draw the line longer */
2033 if(selectedRect.left == 0)
2034 fillRect.left += 2;
2035 /* if rightmost draw the line longer */
2036 if(selectedRect.right == clRight)
2037 fillRect.right -= 2;
2040 if (lStyle & TCS_BOTTOM)
2043 /* Adjust both rectangles to match native */
2044 fillRect.top--;
2045 fillRect.bottom--;
2046 r.bottom--;
2047 r.top -= ZZ;
2049 TRACE("<bottom> item=%d, fill=(%ld,%ld)-(%ld,%ld), edge=(%ld,%ld)-(%ld,%ld)\n",
2050 iItem,
2051 fillRect.left,fillRect.top,fillRect.right,fillRect.bottom,
2052 r.left,r.top,r.right,r.bottom);
2054 /* Clear interior */
2055 SetBkColor(hdc, bkgnd);
2056 ExtTextOutA(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2058 /* Draw rectangular edge around tab */
2059 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_BOTTOM|BF_RIGHT);
2061 /* Now erase the righthand corner and draw diagonal edge */
2062 SetBkColor(hdc, corner);
2063 r1.left = r.right - ROUND_CORNER_SIZE;
2064 r1.bottom = r.bottom;
2065 r1.right = r.right;
2066 r1.top = r1.bottom - ROUND_CORNER_SIZE - 1;
2067 ExtTextOutA(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2068 r1.bottom--;
2069 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMLEFT);
2071 /* Now erase the lefthand corner and draw diagonal edge */
2072 r1.left = r.left;
2073 r1.bottom = r.bottom;
2074 r1.right = r1.left + ROUND_CORNER_SIZE;
2075 r1.top = r1.bottom - ROUND_CORNER_SIZE - 1;
2076 ExtTextOutA(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2077 r1.bottom--;
2078 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPLEFT);
2080 if (iItem == infoPtr->iSelected)
2082 r.top += 2;
2083 r.left += 1;
2084 if (selectedRect.left == 0)
2086 r1 = r;
2087 r1.bottom = r1.top;
2088 r1.top--;
2089 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_LEFT);
2094 else
2097 /* Adjust both rectangles to match native */
2098 fillRect.bottom += (1-ZZ);
2100 TRACE("<top> item=%d, fill=(%ld,%ld)-(%ld,%ld), edge=(%ld,%ld)-(%ld,%ld)\n",
2101 iItem,
2102 fillRect.left,fillRect.top,fillRect.right,fillRect.bottom,
2103 r.left,r.top,r.right,r.bottom);
2105 /* Clear interior */
2106 SetBkColor(hdc, bkgnd);
2107 ExtTextOutA(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2109 /* Draw rectangular edge around tab */
2110 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_TOP|BF_RIGHT);
2112 /* Now erase the righthand corner and draw diagonal edge */
2113 SetBkColor(hdc, corner);
2114 r1.left = r.right - ROUND_CORNER_SIZE;
2115 r1.top = r.top;
2116 r1.right = r.right;
2117 r1.bottom = r1.top + ROUND_CORNER_SIZE + 1;
2118 ExtTextOutA(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2119 r1.top++;
2120 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMRIGHT);
2122 /* Now erase the lefthand corner and draw diagonal edge */
2123 r1.left = r.left;
2124 r1.top = r.top;
2125 r1.right = r1.left + ROUND_CORNER_SIZE;
2126 r1.bottom = r1.top + ROUND_CORNER_SIZE + 1;
2127 ExtTextOutA(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2128 r1.top++;
2129 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPRIGHT);
2135 TAB_DumpItemInternal(infoPtr, iItem);
2137 /* This modifies r to be the text rectangle. */
2138 TAB_DrawItemInterior(hwnd, hdc, iItem, &r);
2142 /******************************************************************************
2143 * TAB_DrawBorder
2145 * This method is used to draw the raised border around the tab control
2146 * "content" area.
2148 static void TAB_DrawBorder (HWND hwnd, HDC hdc)
2150 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2151 RECT rect;
2152 DWORD lStyle = GetWindowLongA(hwnd, GWL_STYLE);
2154 GetClientRect (hwnd, &rect);
2157 * Adjust for the style
2160 if (infoPtr->uNumItem)
2162 if ((lStyle & TCS_BOTTOM) && !(lStyle & TCS_VERTICAL))
2164 rect.bottom -= (infoPtr->tabHeight - 2) * infoPtr->uNumRows + 3;
2166 else if((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
2168 rect.right -= (infoPtr->tabHeight - 2) * infoPtr->uNumRows + 2;
2170 else if(lStyle & TCS_VERTICAL)
2172 rect.left += (infoPtr->tabHeight - 2) * infoPtr->uNumRows + 2;
2174 else /* not TCS_VERTICAL and not TCS_BOTTOM */
2176 rect.top += (infoPtr->tabHeight - 2) * infoPtr->uNumRows + 2;
2180 TRACE("border=(%ld,%ld)-(%ld,%ld)\n",
2181 rect.left, rect.top, rect.right, rect.bottom);
2183 DrawEdge(hdc, &rect, EDGE_RAISED, BF_SOFT|BF_RECT);
2186 /******************************************************************************
2187 * TAB_Refresh
2189 * This method repaints the tab control..
2191 static void TAB_Refresh (HWND hwnd, HDC hdc)
2193 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2194 HFONT hOldFont;
2195 INT i;
2197 if (!infoPtr->DoRedraw)
2198 return;
2200 hOldFont = SelectObject (hdc, infoPtr->hFont);
2202 if (GetWindowLongA(hwnd, GWL_STYLE) & TCS_BUTTONS)
2204 for (i = 0; i < infoPtr->uNumItem; i++)
2205 TAB_DrawItem (hwnd, hdc, i);
2207 else
2209 /* Draw all the non selected item first */
2210 for (i = 0; i < infoPtr->uNumItem; i++)
2212 if (i != infoPtr->iSelected)
2213 TAB_DrawItem (hwnd, hdc, i);
2216 /* Now, draw the border, draw it before the selected item
2217 * since the selected item overwrites part of the border. */
2218 TAB_DrawBorder (hwnd, hdc);
2220 /* Then, draw the selected item */
2221 TAB_DrawItem (hwnd, hdc, infoPtr->iSelected);
2223 /* If we haven't set the current focus yet, set it now.
2224 * Only happens when we first paint the tab controls */
2225 if (infoPtr->uFocus == -1)
2226 TAB_SetCurFocus(hwnd, infoPtr->iSelected);
2229 SelectObject (hdc, hOldFont);
2232 static DWORD
2233 TAB_GetRowCount (HWND hwnd )
2235 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2237 return infoPtr->uNumRows;
2240 static LRESULT
2241 TAB_SetRedraw (HWND hwnd, WPARAM wParam)
2243 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2245 infoPtr->DoRedraw=(BOOL) wParam;
2246 return 0;
2249 static LRESULT TAB_EraseBackground(
2250 HWND hwnd,
2251 HDC givenDC)
2253 HDC hdc;
2254 RECT clientRect;
2256 HBRUSH brush = CreateSolidBrush(comctl32_color.clrBtnFace);
2258 hdc = givenDC ? givenDC : GetDC(hwnd);
2260 GetClientRect(hwnd, &clientRect);
2262 FillRect(hdc, &clientRect, brush);
2264 if (givenDC==0)
2265 ReleaseDC(hwnd, hdc);
2267 DeleteObject(brush);
2269 return 0;
2272 /******************************************************************************
2273 * TAB_EnsureSelectionVisible
2275 * This method will make sure that the current selection is completely
2276 * visible by scrolling until it is.
2278 static void TAB_EnsureSelectionVisible(
2279 HWND hwnd,
2280 TAB_INFO* infoPtr)
2282 INT iSelected = infoPtr->iSelected;
2283 LONG lStyle = GetWindowLongA(hwnd, GWL_STYLE);
2284 INT iOrigLeftmostVisible = infoPtr->leftmostVisible;
2286 /* set the items row to the bottommost row or topmost row depending on
2287 * style */
2288 if ((infoPtr->uNumRows > 1) && !(lStyle & TCS_BUTTONS))
2290 INT newselected;
2291 INT iTargetRow;
2293 if(lStyle & TCS_VERTICAL)
2294 newselected = infoPtr->items[iSelected].rect.left;
2295 else
2296 newselected = infoPtr->items[iSelected].rect.top;
2298 /* the target row is always (number of rows - 1)
2299 as row 0 is furthest from the clientRect */
2300 iTargetRow = infoPtr->uNumRows - 1;
2302 if (newselected != iTargetRow)
2304 INT i;
2305 if(lStyle & TCS_VERTICAL)
2307 for (i=0; i < infoPtr->uNumItem; i++)
2309 /* move everything in the row of the selected item to the iTargetRow */
2310 if (infoPtr->items[i].rect.left == newselected )
2311 infoPtr->items[i].rect.left = iTargetRow;
2312 else
2314 if (infoPtr->items[i].rect.left > newselected)
2315 infoPtr->items[i].rect.left-=1;
2319 else
2321 for (i=0; i < infoPtr->uNumItem; i++)
2323 if (infoPtr->items[i].rect.top == newselected )
2324 infoPtr->items[i].rect.top = iTargetRow;
2325 else
2327 if (infoPtr->items[i].rect.top > newselected)
2328 infoPtr->items[i].rect.top-=1;
2332 TAB_RecalcHotTrack(hwnd, NULL, NULL, NULL);
2337 * Do the trivial cases first.
2339 if ( (!infoPtr->needsScrolling) ||
2340 (infoPtr->hwndUpDown==0) || (lStyle & TCS_VERTICAL))
2341 return;
2343 if (infoPtr->leftmostVisible >= iSelected)
2345 infoPtr->leftmostVisible = iSelected;
2347 else
2349 RECT r;
2350 INT width, i;
2352 /* Calculate the part of the client area that is visible */
2353 GetClientRect(hwnd, &r);
2354 width = r.right;
2356 GetClientRect(infoPtr->hwndUpDown, &r);
2357 width -= r.right;
2359 if ((infoPtr->items[iSelected].rect.right -
2360 infoPtr->items[iSelected].rect.left) >= width )
2362 /* Special case: width of selected item is greater than visible
2363 * part of control.
2365 infoPtr->leftmostVisible = iSelected;
2367 else
2369 for (i = infoPtr->leftmostVisible; i < infoPtr->uNumItem; i++)
2371 if ((infoPtr->items[iSelected].rect.right -
2372 infoPtr->items[i].rect.left) < width)
2373 break;
2375 infoPtr->leftmostVisible = i;
2379 if (infoPtr->leftmostVisible != iOrigLeftmostVisible)
2380 TAB_RecalcHotTrack(hwnd, NULL, NULL, NULL);
2382 SendMessageA(infoPtr->hwndUpDown, UDM_SETPOS, 0,
2383 MAKELONG(infoPtr->leftmostVisible, 0));
2386 /******************************************************************************
2387 * TAB_InvalidateTabArea
2389 * This method will invalidate the portion of the control that contains the
2390 * tabs. It is called when the state of the control changes and needs
2391 * to be redisplayed
2393 static void TAB_InvalidateTabArea(
2394 HWND hwnd,
2395 TAB_INFO* infoPtr)
2397 RECT clientRect, rInvalidate;
2398 DWORD lStyle = GetWindowLongA(hwnd, GWL_STYLE);
2399 INT lastRow = infoPtr->uNumRows - 1;
2400 RECT rect;
2402 if (lastRow < 0) return;
2404 GetClientRect(hwnd, &clientRect);
2405 rInvalidate = clientRect;
2407 TAB_InternalGetItemRect(hwnd, infoPtr, infoPtr->uNumItem-1 , &rect, NULL);
2408 if ((lStyle & TCS_BOTTOM) && !(lStyle & TCS_VERTICAL))
2410 rInvalidate.top = clientRect.bottom -
2411 infoPtr->tabHeight -
2412 lastRow * (infoPtr->tabHeight - 2) -
2413 ((lStyle & TCS_BUTTONS) ? lastRow * BUTTON_SPACINGY : 0) - 3;
2414 rInvalidate.right = clientRect.left + rect.right + 2 * SELECTED_TAB_OFFSET;
2416 else if((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
2418 rInvalidate.left = clientRect.right - infoPtr->tabHeight -
2419 lastRow * (infoPtr->tabHeight - 2) -
2420 ((lStyle & TCS_BUTTONS) ? lastRow * BUTTON_SPACINGY : 0) - 2;
2421 rInvalidate.bottom = clientRect.top + rect.bottom + 2 * SELECTED_TAB_OFFSET;
2423 else if(lStyle & TCS_VERTICAL)
2425 rInvalidate.right = clientRect.left + infoPtr->tabHeight +
2426 lastRow * (infoPtr->tabHeight - 2) -
2427 ((lStyle & TCS_BUTTONS) ? lastRow * BUTTON_SPACINGY : 0) + 2;
2428 rInvalidate.bottom = clientRect.top + rect.bottom + 2 * SELECTED_TAB_OFFSET;
2430 else
2432 rInvalidate.bottom = clientRect.top + infoPtr->tabHeight +
2433 lastRow * (infoPtr->tabHeight - 2) +
2434 ((lStyle & TCS_BUTTONS) ? lastRow * BUTTON_SPACINGY : 0) + 2;
2435 rInvalidate.right = clientRect.left + rect.right + 2 * SELECTED_TAB_OFFSET;
2438 /* Punch out the updown control */
2439 if (infoPtr->needsScrolling && (rInvalidate.right > 0)) {
2440 RECT r;
2441 GetClientRect(infoPtr->hwndUpDown, &r);
2442 if (rInvalidate.right > clientRect.right - r.left)
2443 rInvalidate.right = rInvalidate.right - (r.right - r.left);
2444 else
2445 rInvalidate.right = clientRect.right - r.left;
2448 TRACE("invalidate (%ld,%ld)-(%ld,%ld)\n",
2449 clientRect.left,clientRect.top,
2450 clientRect.right,clientRect.bottom);
2452 InvalidateRect(hwnd, &rInvalidate, TRUE);
2455 static LRESULT
2456 TAB_Paint (HWND hwnd, WPARAM wParam)
2458 HDC hdc;
2459 PAINTSTRUCT ps;
2461 if (wParam == 0)
2463 hdc = BeginPaint (hwnd, &ps);
2464 TRACE("erase %d, rect=(%ld,%ld)-(%ld,%ld)\n",
2465 ps.fErase,
2466 ps.rcPaint.left,ps.rcPaint.top,ps.rcPaint.right,ps.rcPaint.bottom);
2468 if (ps.fErase)
2469 TAB_EraseBackground (hwnd, hdc);
2471 } else {
2472 hdc = (HDC)wParam;
2475 TAB_Refresh (hwnd, hdc);
2477 if(!wParam)
2478 EndPaint (hwnd, &ps);
2480 return 0;
2483 static LRESULT
2484 TAB_InsertItemA (HWND hwnd, WPARAM wParam, LPARAM lParam)
2486 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2487 TCITEMA *pti;
2488 INT iItem;
2489 RECT rect;
2491 GetClientRect (hwnd, &rect);
2492 TRACE("Rect: %p T %li, L %li, B %li, R %li\n", hwnd,
2493 rect.top, rect.left, rect.bottom, rect.right);
2495 pti = (TCITEMA *)lParam;
2496 iItem = (INT)wParam;
2498 if (iItem < 0) return -1;
2499 if (iItem > infoPtr->uNumItem)
2500 iItem = infoPtr->uNumItem;
2502 TAB_DumpItemExternalA(pti, iItem);
2505 if (infoPtr->uNumItem == 0) {
2506 infoPtr->items = Alloc (sizeof (TAB_ITEM));
2507 infoPtr->uNumItem++;
2508 infoPtr->iSelected = 0;
2510 else {
2511 TAB_ITEM *oldItems = infoPtr->items;
2513 infoPtr->uNumItem++;
2514 infoPtr->items = Alloc (sizeof (TAB_ITEM) * infoPtr->uNumItem);
2516 /* pre insert copy */
2517 if (iItem > 0) {
2518 memcpy (&infoPtr->items[0], &oldItems[0],
2519 iItem * sizeof(TAB_ITEM));
2522 /* post insert copy */
2523 if (iItem < infoPtr->uNumItem - 1) {
2524 memcpy (&infoPtr->items[iItem+1], &oldItems[iItem],
2525 (infoPtr->uNumItem - iItem - 1) * sizeof(TAB_ITEM));
2529 if (iItem <= infoPtr->iSelected)
2530 infoPtr->iSelected++;
2532 Free (oldItems);
2535 infoPtr->items[iItem].mask = pti->mask;
2536 if (pti->mask & TCIF_TEXT)
2537 Str_SetPtrAtoW (&infoPtr->items[iItem].pszText, pti->pszText);
2539 if (pti->mask & TCIF_IMAGE)
2540 infoPtr->items[iItem].iImage = pti->iImage;
2542 if (pti->mask & TCIF_PARAM)
2543 infoPtr->items[iItem].lParam = pti->lParam;
2545 TAB_SetItemBounds(hwnd);
2546 if (infoPtr->uNumItem > 1)
2547 TAB_InvalidateTabArea(hwnd, infoPtr);
2548 else
2549 InvalidateRect(hwnd, NULL, TRUE);
2551 TRACE("[%p]: added item %d %s\n",
2552 hwnd, iItem, debugstr_w(infoPtr->items[iItem].pszText));
2554 return iItem;
2558 static LRESULT
2559 TAB_InsertItemW (HWND hwnd, WPARAM wParam, LPARAM lParam)
2561 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2562 TCITEMW *pti;
2563 INT iItem;
2564 RECT rect;
2566 GetClientRect (hwnd, &rect);
2567 TRACE("Rect: %p T %li, L %li, B %li, R %li\n", hwnd,
2568 rect.top, rect.left, rect.bottom, rect.right);
2570 pti = (TCITEMW *)lParam;
2571 iItem = (INT)wParam;
2573 if (iItem < 0) return -1;
2574 if (iItem > infoPtr->uNumItem)
2575 iItem = infoPtr->uNumItem;
2577 TAB_DumpItemExternalW(pti, iItem);
2579 if (infoPtr->uNumItem == 0) {
2580 infoPtr->items = Alloc (sizeof (TAB_ITEM));
2581 infoPtr->uNumItem++;
2582 infoPtr->iSelected = 0;
2584 else {
2585 TAB_ITEM *oldItems = infoPtr->items;
2587 infoPtr->uNumItem++;
2588 infoPtr->items = Alloc (sizeof (TAB_ITEM) * infoPtr->uNumItem);
2590 /* pre insert copy */
2591 if (iItem > 0) {
2592 memcpy (&infoPtr->items[0], &oldItems[0],
2593 iItem * sizeof(TAB_ITEM));
2596 /* post insert copy */
2597 if (iItem < infoPtr->uNumItem - 1) {
2598 memcpy (&infoPtr->items[iItem+1], &oldItems[iItem],
2599 (infoPtr->uNumItem - iItem - 1) * sizeof(TAB_ITEM));
2603 if (iItem <= infoPtr->iSelected)
2604 infoPtr->iSelected++;
2606 Free (oldItems);
2609 infoPtr->items[iItem].mask = pti->mask;
2610 if (pti->mask & TCIF_TEXT)
2611 Str_SetPtrW (&infoPtr->items[iItem].pszText, pti->pszText);
2613 if (pti->mask & TCIF_IMAGE)
2614 infoPtr->items[iItem].iImage = pti->iImage;
2616 if (pti->mask & TCIF_PARAM)
2617 infoPtr->items[iItem].lParam = pti->lParam;
2619 TAB_SetItemBounds(hwnd);
2620 if (infoPtr->uNumItem > 1)
2621 TAB_InvalidateTabArea(hwnd, infoPtr);
2622 else
2623 InvalidateRect(hwnd, NULL, TRUE);
2625 TRACE("[%p]: added item %d %s\n",
2626 hwnd, iItem, debugstr_w(infoPtr->items[iItem].pszText));
2628 return iItem;
2632 static LRESULT
2633 TAB_SetItemSize (HWND hwnd, WPARAM wParam, LPARAM lParam)
2635 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2636 LONG lStyle = GetWindowLongA(hwnd, GWL_STYLE);
2637 LONG lResult = 0;
2638 BOOL bNeedPaint = FALSE;
2640 lResult = MAKELONG(infoPtr->tabWidth, infoPtr->tabHeight);
2642 /* UNDOCUMENTED: If requested Width or Height is 0 this means that program wants to use auto size. */
2643 if (lStyle & TCS_FIXEDWIDTH && (infoPtr->tabWidth != (INT)LOWORD(lParam)))
2645 infoPtr->tabWidth = max((INT)LOWORD(lParam), infoPtr->tabMinWidth);
2646 bNeedPaint = TRUE;
2649 if (infoPtr->tabHeight != (INT)HIWORD(lParam))
2651 if ((infoPtr->fHeightSet = ((INT)HIWORD(lParam) != 0)))
2652 infoPtr->tabHeight = (INT)HIWORD(lParam);
2654 bNeedPaint = TRUE;
2656 TRACE("was h=%d,w=%d, now h=%d,w=%d\n",
2657 HIWORD(lResult), LOWORD(lResult),
2658 infoPtr->tabHeight, infoPtr->tabWidth);
2660 if (bNeedPaint)
2662 TAB_SetItemBounds(hwnd);
2663 RedrawWindow(hwnd, NULL, NULL, RDW_ERASE | RDW_INVALIDATE | RDW_UPDATENOW);
2666 return lResult;
2669 static LRESULT
2670 TAB_SetMinTabWidth (HWND hwnd, LPARAM lParam)
2672 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2673 INT cx = (INT)lParam;
2674 INT oldcx;
2676 if (infoPtr) {
2677 oldcx = infoPtr->tabMinWidth;
2678 infoPtr->tabMinWidth = (cx==-1)?DEFAULT_TAB_WIDTH:cx;
2679 } else
2680 return 0;
2682 return oldcx;
2685 static LRESULT
2686 TAB_HighlightItem (HWND hwnd, WPARAM wParam, LPARAM lParam)
2688 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2689 INT iItem = (INT)wParam;
2690 BOOL fHighlight = (BOOL)LOWORD(lParam);
2692 if ((infoPtr) && (iItem>=0) && (iItem<infoPtr->uNumItem)) {
2693 if (fHighlight)
2694 infoPtr->items[iItem].dwState |= TCIS_HIGHLIGHTED;
2695 else
2696 infoPtr->items[iItem].dwState &= ~TCIS_HIGHLIGHTED;
2697 } else
2698 return FALSE;
2700 return TRUE;
2703 static LRESULT
2704 TAB_SetItemA (HWND hwnd, WPARAM wParam, LPARAM lParam)
2706 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2707 TCITEMA *tabItem;
2708 TAB_ITEM *wineItem;
2709 INT iItem;
2711 iItem = (INT)wParam;
2712 tabItem = (LPTCITEMA)lParam;
2714 TRACE("%d %p\n", iItem, tabItem);
2715 if ((iItem<0) || (iItem>=infoPtr->uNumItem)) return FALSE;
2717 TAB_DumpItemExternalA(tabItem, iItem);
2719 wineItem = &infoPtr->items[iItem];
2721 if (tabItem->mask & TCIF_IMAGE)
2722 wineItem->iImage = tabItem->iImage;
2724 if (tabItem->mask & TCIF_PARAM)
2725 wineItem->lParam = tabItem->lParam;
2727 if (tabItem->mask & TCIF_RTLREADING)
2728 FIXME("TCIF_RTLREADING\n");
2730 if (tabItem->mask & TCIF_STATE)
2731 wineItem->dwState = tabItem->dwState;
2733 if (tabItem->mask & TCIF_TEXT)
2734 Str_SetPtrAtoW(&wineItem->pszText, tabItem->pszText);
2736 /* Update and repaint tabs */
2737 TAB_SetItemBounds(hwnd);
2738 TAB_InvalidateTabArea(hwnd,infoPtr);
2740 return TRUE;
2744 static LRESULT
2745 TAB_SetItemW (HWND hwnd, WPARAM wParam, LPARAM lParam)
2747 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2748 TCITEMW *tabItem;
2749 TAB_ITEM *wineItem;
2750 INT iItem;
2752 iItem = (INT)wParam;
2753 tabItem = (LPTCITEMW)lParam;
2755 TRACE("%d %p\n", iItem, tabItem);
2756 if ((iItem<0) || (iItem>=infoPtr->uNumItem)) return FALSE;
2758 TAB_DumpItemExternalW(tabItem, iItem);
2760 wineItem = &infoPtr->items[iItem];
2762 if (tabItem->mask & TCIF_IMAGE)
2763 wineItem->iImage = tabItem->iImage;
2765 if (tabItem->mask & TCIF_PARAM)
2766 wineItem->lParam = tabItem->lParam;
2768 if (tabItem->mask & TCIF_RTLREADING)
2769 FIXME("TCIF_RTLREADING\n");
2771 if (tabItem->mask & TCIF_STATE)
2772 wineItem->dwState = tabItem->dwState;
2774 if (tabItem->mask & TCIF_TEXT)
2775 Str_SetPtrW(&wineItem->pszText, tabItem->pszText);
2777 /* Update and repaint tabs */
2778 TAB_SetItemBounds(hwnd);
2779 TAB_InvalidateTabArea(hwnd,infoPtr);
2781 return TRUE;
2785 static LRESULT
2786 TAB_GetItemCount (HWND hwnd, WPARAM wParam, LPARAM lParam)
2788 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2790 return infoPtr->uNumItem;
2794 static LRESULT
2795 TAB_GetItemA (HWND hwnd, WPARAM wParam, LPARAM lParam)
2797 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2798 TCITEMA *tabItem;
2799 TAB_ITEM *wineItem;
2800 INT iItem;
2802 iItem = (INT)wParam;
2803 tabItem = (LPTCITEMA)lParam;
2804 TRACE("\n");
2805 if ((iItem<0) || (iItem>=infoPtr->uNumItem))
2806 return FALSE;
2808 wineItem = &infoPtr->items[iItem];
2810 if (tabItem->mask & TCIF_IMAGE)
2811 tabItem->iImage = wineItem->iImage;
2813 if (tabItem->mask & TCIF_PARAM)
2814 tabItem->lParam = wineItem->lParam;
2816 if (tabItem->mask & TCIF_RTLREADING)
2817 FIXME("TCIF_RTLREADING\n");
2819 if (tabItem->mask & TCIF_STATE)
2820 tabItem->dwState = wineItem->dwState;
2822 if (tabItem->mask & TCIF_TEXT)
2823 Str_GetPtrWtoA (wineItem->pszText, tabItem->pszText, tabItem->cchTextMax);
2825 TAB_DumpItemExternalA(tabItem, iItem);
2827 return TRUE;
2831 static LRESULT
2832 TAB_GetItemW (HWND hwnd, WPARAM wParam, LPARAM lParam)
2834 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2835 TCITEMW *tabItem;
2836 TAB_ITEM *wineItem;
2837 INT iItem;
2839 iItem = (INT)wParam;
2840 tabItem = (LPTCITEMW)lParam;
2841 TRACE("\n");
2842 if ((iItem<0) || (iItem>=infoPtr->uNumItem))
2843 return FALSE;
2845 wineItem=& infoPtr->items[iItem];
2847 if (tabItem->mask & TCIF_IMAGE)
2848 tabItem->iImage = wineItem->iImage;
2850 if (tabItem->mask & TCIF_PARAM)
2851 tabItem->lParam = wineItem->lParam;
2853 if (tabItem->mask & TCIF_RTLREADING)
2854 FIXME("TCIF_RTLREADING\n");
2856 if (tabItem->mask & TCIF_STATE)
2857 tabItem->dwState = wineItem->dwState;
2859 if (tabItem->mask & TCIF_TEXT)
2860 Str_GetPtrW (wineItem->pszText, tabItem->pszText, tabItem->cchTextMax);
2862 TAB_DumpItemExternalW(tabItem, iItem);
2864 return TRUE;
2868 static LRESULT
2869 TAB_DeleteItem (HWND hwnd, WPARAM wParam, LPARAM lParam)
2871 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2872 INT iItem = (INT) wParam;
2873 BOOL bResult = FALSE;
2875 if ((iItem >= 0) && (iItem < infoPtr->uNumItem))
2877 TAB_ITEM *oldItems = infoPtr->items;
2879 TAB_InvalidateTabArea(hwnd, infoPtr);
2881 infoPtr->uNumItem--;
2882 infoPtr->items = Alloc(sizeof (TAB_ITEM) * infoPtr->uNumItem);
2884 if (iItem > 0)
2885 memcpy(&infoPtr->items[0], &oldItems[0], iItem * sizeof(TAB_ITEM));
2887 if (iItem < infoPtr->uNumItem)
2888 memcpy(&infoPtr->items[iItem], &oldItems[iItem + 1],
2889 (infoPtr->uNumItem - iItem) * sizeof(TAB_ITEM));
2891 Free(oldItems);
2893 /* Readjust the selected index */
2894 if ((iItem == infoPtr->iSelected) && (iItem > 0))
2895 infoPtr->iSelected--;
2897 if (iItem < infoPtr->iSelected)
2898 infoPtr->iSelected--;
2900 if (infoPtr->uNumItem == 0)
2901 infoPtr->iSelected = -1;
2903 /* Reposition and repaint tabs */
2904 TAB_SetItemBounds(hwnd);
2906 bResult = TRUE;
2909 return bResult;
2912 static LRESULT
2913 TAB_DeleteAllItems (HWND hwnd, WPARAM wParam, LPARAM lParam)
2915 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2917 TAB_InvalidateTabArea(hwnd,infoPtr);
2919 Free (infoPtr->items);
2920 infoPtr->uNumItem = 0;
2921 infoPtr->iSelected = -1;
2922 if (infoPtr->iHotTracked >= 0)
2923 KillTimer(hwnd, TAB_HOTTRACK_TIMER);
2924 infoPtr->iHotTracked = -1;
2926 TAB_SetItemBounds(hwnd);
2927 return TRUE;
2931 static LRESULT
2932 TAB_GetFont (HWND hwnd, WPARAM wParam, LPARAM lParam)
2934 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2936 TRACE("\n");
2937 return (LRESULT)infoPtr->hFont;
2940 static LRESULT
2941 TAB_SetFont (HWND hwnd, WPARAM wParam, LPARAM lParam)
2944 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2946 TRACE("%x %lx\n",wParam, lParam);
2948 infoPtr->hFont = (HFONT)wParam;
2950 TAB_SetItemBounds(hwnd);
2952 TAB_InvalidateTabArea(hwnd, infoPtr);
2954 return 0;
2958 static LRESULT
2959 TAB_GetImageList (HWND hwnd, WPARAM wParam, LPARAM lParam)
2961 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2963 TRACE("\n");
2964 return (LRESULT)infoPtr->himl;
2967 static LRESULT
2968 TAB_SetImageList (HWND hwnd, WPARAM wParam, LPARAM lParam)
2970 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2971 HIMAGELIST himlPrev;
2973 TRACE("\n");
2974 himlPrev = infoPtr->himl;
2975 infoPtr->himl= (HIMAGELIST)lParam;
2976 return (LRESULT)himlPrev;
2979 static LRESULT
2980 TAB_GetUnicodeFormat (HWND hwnd)
2982 TAB_INFO *infoPtr = TAB_GetInfoPtr (hwnd);
2983 return infoPtr->bUnicode;
2986 static LRESULT
2987 TAB_SetUnicodeFormat (HWND hwnd, WPARAM wParam)
2989 TAB_INFO *infoPtr = TAB_GetInfoPtr (hwnd);
2990 BOOL bTemp = infoPtr->bUnicode;
2992 infoPtr->bUnicode = (BOOL)wParam;
2994 return bTemp;
2997 static LRESULT
2998 TAB_Size (HWND hwnd, WPARAM wParam, LPARAM lParam)
3001 /* I'm not really sure what the following code was meant to do.
3002 This is what it is doing:
3003 When WM_SIZE is sent with SIZE_RESTORED, the control
3004 gets positioned in the top left corner.
3006 RECT parent_rect;
3007 HWND parent;
3008 UINT uPosFlags,cx,cy;
3010 uPosFlags=0;
3011 if (!wParam) {
3012 parent = GetParent (hwnd);
3013 GetClientRect(parent, &parent_rect);
3014 cx=LOWORD (lParam);
3015 cy=HIWORD (lParam);
3016 if (GetWindowLongA(hwnd, GWL_STYLE) & CCS_NORESIZE)
3017 uPosFlags |= (SWP_NOSIZE | SWP_NOMOVE);
3019 SetWindowPos (hwnd, 0, parent_rect.left, parent_rect.top,
3020 cx, cy, uPosFlags | SWP_NOZORDER);
3021 } else {
3022 FIXME("WM_SIZE flag %x %lx not handled\n", wParam, lParam);
3023 } */
3025 /* Recompute the size/position of the tabs. */
3026 TAB_SetItemBounds (hwnd);
3028 /* Force a repaint of the control. */
3029 InvalidateRect(hwnd, NULL, TRUE);
3031 return 0;
3035 static LRESULT
3036 TAB_Create (HWND hwnd, WPARAM wParam, LPARAM lParam)
3038 TAB_INFO *infoPtr;
3039 TEXTMETRICA fontMetrics;
3040 HDC hdc;
3041 HFONT hOldFont;
3042 DWORD dwStyle;
3044 infoPtr = (TAB_INFO *)Alloc (sizeof(TAB_INFO));
3046 SetWindowLongA(hwnd, 0, (DWORD)infoPtr);
3048 infoPtr->hwndNotify = ((LPCREATESTRUCTW)lParam)->hwndParent;
3049 infoPtr->uNumItem = 0;
3050 infoPtr->uNumRows = 0;
3051 infoPtr->uHItemPadding = 6;
3052 infoPtr->uVItemPadding = 3;
3053 infoPtr->uHItemPadding_s = 6;
3054 infoPtr->uVItemPadding_s = 3;
3055 infoPtr->hFont = 0;
3056 infoPtr->items = 0;
3057 infoPtr->hcurArrow = LoadCursorA (0, (LPSTR)IDC_ARROW);
3058 infoPtr->iSelected = -1;
3059 infoPtr->iHotTracked = -1;
3060 infoPtr->uFocus = -1;
3061 infoPtr->hwndToolTip = 0;
3062 infoPtr->DoRedraw = TRUE;
3063 infoPtr->needsScrolling = FALSE;
3064 infoPtr->hwndUpDown = 0;
3065 infoPtr->leftmostVisible = 0;
3066 infoPtr->fHeightSet = FALSE;
3067 infoPtr->bUnicode = IsWindowUnicode (hwnd);
3069 TRACE("Created tab control, hwnd [%p]\n", hwnd);
3071 /* The tab control always has the WS_CLIPSIBLINGS style. Even
3072 if you don't specify it in CreateWindow. This is necessary in
3073 order for paint to work correctly. This follows windows behaviour. */
3074 dwStyle = GetWindowLongA(hwnd, GWL_STYLE);
3075 SetWindowLongA(hwnd, GWL_STYLE, dwStyle|WS_CLIPSIBLINGS);
3077 if (dwStyle & TCS_TOOLTIPS) {
3078 /* Create tooltip control */
3079 infoPtr->hwndToolTip =
3080 CreateWindowExA (0, TOOLTIPS_CLASSA, NULL, 0,
3081 CW_USEDEFAULT, CW_USEDEFAULT,
3082 CW_USEDEFAULT, CW_USEDEFAULT,
3083 hwnd, 0, 0, 0);
3085 /* Send NM_TOOLTIPSCREATED notification */
3086 if (infoPtr->hwndToolTip) {
3087 NMTOOLTIPSCREATED nmttc;
3089 nmttc.hdr.hwndFrom = hwnd;
3090 nmttc.hdr.idFrom = GetWindowLongA(hwnd, GWL_ID);
3091 nmttc.hdr.code = NM_TOOLTIPSCREATED;
3092 nmttc.hwndToolTips = infoPtr->hwndToolTip;
3094 SendMessageA (infoPtr->hwndNotify, WM_NOTIFY,
3095 (WPARAM)GetWindowLongA(hwnd, GWL_ID), (LPARAM)&nmttc);
3100 * We need to get text information so we need a DC and we need to select
3101 * a font.
3103 hdc = GetDC(hwnd);
3104 hOldFont = SelectObject (hdc, GetStockObject (SYSTEM_FONT));
3106 /* Use the system font to determine the initial height of a tab. */
3107 GetTextMetricsA(hdc, &fontMetrics);
3110 * Make sure there is enough space for the letters + growing the
3111 * selected item + extra space for the selected item.
3113 infoPtr->tabHeight = fontMetrics.tmHeight + SELECTED_TAB_OFFSET +
3114 ((dwStyle & TCS_BUTTONS) ? 2 : 1) *
3115 infoPtr->uVItemPadding;
3117 /* Initialize the width of a tab. */
3118 infoPtr->tabWidth = DEFAULT_TAB_WIDTH;
3119 infoPtr->tabMinWidth = 0;
3121 TRACE("tabH=%d, tabW=%d\n", infoPtr->tabHeight, infoPtr->tabWidth);
3123 SelectObject (hdc, hOldFont);
3124 ReleaseDC(hwnd, hdc);
3126 return 0;
3129 static LRESULT
3130 TAB_Destroy (HWND hwnd, WPARAM wParam, LPARAM lParam)
3132 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
3133 INT iItem;
3135 if (!infoPtr)
3136 return 0;
3138 if (infoPtr->items) {
3139 for (iItem = 0; iItem < infoPtr->uNumItem; iItem++) {
3140 if (infoPtr->items[iItem].pszText)
3141 Free (infoPtr->items[iItem].pszText);
3143 Free (infoPtr->items);
3146 if (infoPtr->hwndToolTip)
3147 DestroyWindow (infoPtr->hwndToolTip);
3149 if (infoPtr->hwndUpDown)
3150 DestroyWindow(infoPtr->hwndUpDown);
3152 if (infoPtr->iHotTracked >= 0)
3153 KillTimer(hwnd, TAB_HOTTRACK_TIMER);
3155 Free (infoPtr);
3156 SetWindowLongA(hwnd, 0, 0);
3157 return 0;
3160 static LRESULT WINAPI
3161 TAB_WindowProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
3163 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
3165 TRACE("hwnd=%p msg=%x wParam=%x lParam=%lx\n", hwnd, uMsg, wParam, lParam);
3166 if (!TAB_GetInfoPtr(hwnd) && (uMsg != WM_CREATE))
3167 return DefWindowProcA (hwnd, uMsg, wParam, lParam);
3169 switch (uMsg)
3171 case TCM_GETIMAGELIST:
3172 return TAB_GetImageList (hwnd, wParam, lParam);
3174 case TCM_SETIMAGELIST:
3175 return TAB_SetImageList (hwnd, wParam, lParam);
3177 case TCM_GETITEMCOUNT:
3178 return TAB_GetItemCount (hwnd, wParam, lParam);
3180 case TCM_GETITEMA:
3181 return TAB_GetItemA (hwnd, wParam, lParam);
3183 case TCM_GETITEMW:
3184 return TAB_GetItemW (hwnd, wParam, lParam);
3186 case TCM_SETITEMA:
3187 return TAB_SetItemA (hwnd, wParam, lParam);
3189 case TCM_SETITEMW:
3190 return TAB_SetItemW (hwnd, wParam, lParam);
3192 case TCM_DELETEITEM:
3193 return TAB_DeleteItem (hwnd, wParam, lParam);
3195 case TCM_DELETEALLITEMS:
3196 return TAB_DeleteAllItems (hwnd, wParam, lParam);
3198 case TCM_GETITEMRECT:
3199 return TAB_GetItemRect (hwnd, wParam, lParam);
3201 case TCM_GETCURSEL:
3202 return TAB_GetCurSel (hwnd);
3204 case TCM_HITTEST:
3205 return TAB_HitTest (hwnd, wParam, lParam);
3207 case TCM_SETCURSEL:
3208 return TAB_SetCurSel (hwnd, wParam);
3210 case TCM_INSERTITEMA:
3211 return TAB_InsertItemA (hwnd, wParam, lParam);
3213 case TCM_INSERTITEMW:
3214 return TAB_InsertItemW (hwnd, wParam, lParam);
3216 case TCM_SETITEMEXTRA:
3217 FIXME("Unimplemented msg TCM_SETITEMEXTRA\n");
3218 return 0;
3220 case TCM_ADJUSTRECT:
3221 return TAB_AdjustRect (hwnd, (BOOL)wParam, (LPRECT)lParam);
3223 case TCM_SETITEMSIZE:
3224 return TAB_SetItemSize (hwnd, wParam, lParam);
3226 case TCM_REMOVEIMAGE:
3227 FIXME("Unimplemented msg TCM_REMOVEIMAGE\n");
3228 return 0;
3230 case TCM_SETPADDING:
3231 return TAB_SetPadding (hwnd, wParam, lParam);
3233 case TCM_GETROWCOUNT:
3234 return TAB_GetRowCount(hwnd);
3236 case TCM_GETUNICODEFORMAT:
3237 return TAB_GetUnicodeFormat (hwnd);
3239 case TCM_SETUNICODEFORMAT:
3240 return TAB_SetUnicodeFormat (hwnd, wParam);
3242 case TCM_HIGHLIGHTITEM:
3243 return TAB_HighlightItem (hwnd, wParam, lParam);
3245 case TCM_GETTOOLTIPS:
3246 return TAB_GetToolTips (hwnd, wParam, lParam);
3248 case TCM_SETTOOLTIPS:
3249 return TAB_SetToolTips (hwnd, wParam, lParam);
3251 case TCM_GETCURFOCUS:
3252 return TAB_GetCurFocus (hwnd);
3254 case TCM_SETCURFOCUS:
3255 return TAB_SetCurFocus (hwnd, wParam);
3257 case TCM_SETMINTABWIDTH:
3258 return TAB_SetMinTabWidth(hwnd, lParam);
3260 case TCM_DESELECTALL:
3261 FIXME("Unimplemented msg TCM_DESELECTALL\n");
3262 return 0;
3264 case TCM_GETEXTENDEDSTYLE:
3265 FIXME("Unimplemented msg TCM_GETEXTENDEDSTYLE\n");
3266 return 0;
3268 case TCM_SETEXTENDEDSTYLE:
3269 FIXME("Unimplemented msg TCM_SETEXTENDEDSTYLE\n");
3270 return 0;
3272 case WM_GETFONT:
3273 return TAB_GetFont (hwnd, wParam, lParam);
3275 case WM_SETFONT:
3276 return TAB_SetFont (hwnd, wParam, lParam);
3278 case WM_CREATE:
3279 return TAB_Create (hwnd, wParam, lParam);
3281 case WM_NCDESTROY:
3282 return TAB_Destroy (hwnd, wParam, lParam);
3284 case WM_GETDLGCODE:
3285 return DLGC_WANTARROWS | DLGC_WANTCHARS;
3287 case WM_LBUTTONDOWN:
3288 return TAB_LButtonDown (hwnd, wParam, lParam);
3290 case WM_LBUTTONUP:
3291 return TAB_LButtonUp (hwnd, wParam, lParam);
3293 case WM_NOTIFY:
3294 return SendMessageA(infoPtr->hwndNotify, WM_NOTIFY, wParam, lParam);
3296 case WM_RBUTTONDOWN:
3297 return TAB_RButtonDown (hwnd, wParam, lParam);
3299 case WM_MOUSEMOVE:
3300 return TAB_MouseMove (hwnd, wParam, lParam);
3302 case WM_ERASEBKGND:
3303 return TAB_EraseBackground (hwnd, (HDC)wParam);
3305 case WM_PAINT:
3306 return TAB_Paint (hwnd, wParam);
3308 case WM_SIZE:
3309 return TAB_Size (hwnd, wParam, lParam);
3311 case WM_SETREDRAW:
3312 return TAB_SetRedraw (hwnd, wParam);
3314 case WM_HSCROLL:
3315 return TAB_OnHScroll(hwnd, (int)LOWORD(wParam), (int)HIWORD(wParam), (HWND)lParam);
3317 case WM_STYLECHANGED:
3318 TAB_SetItemBounds (hwnd);
3319 InvalidateRect(hwnd, NULL, TRUE);
3320 return 0;
3322 case WM_SYSCOLORCHANGE:
3323 COMCTL32_RefreshSysColors();
3324 return 0;
3326 case WM_KILLFOCUS:
3327 case WM_SETFOCUS:
3328 return TAB_FocusChanging(hwnd, uMsg, wParam, lParam);
3330 case WM_KEYUP:
3331 return TAB_KeyUp(hwnd, wParam);
3332 case WM_NCHITTEST:
3333 return TAB_NCHitTest(hwnd, lParam);
3335 default:
3336 if ((uMsg >= WM_USER) && (uMsg < WM_APP))
3337 WARN("unknown msg %04x wp=%08x lp=%08lx\n",
3338 uMsg, wParam, lParam);
3339 return DefWindowProcA(hwnd, uMsg, wParam, lParam);
3342 return 0;
3346 VOID
3347 TAB_Register (void)
3349 WNDCLASSA wndClass;
3351 ZeroMemory (&wndClass, sizeof(WNDCLASSA));
3352 wndClass.style = CS_GLOBALCLASS | CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
3353 wndClass.lpfnWndProc = (WNDPROC)TAB_WindowProc;
3354 wndClass.cbClsExtra = 0;
3355 wndClass.cbWndExtra = sizeof(TAB_INFO *);
3356 wndClass.hCursor = LoadCursorA (0, (LPSTR)IDC_ARROW);
3357 wndClass.hbrBackground = NULL;
3358 wndClass.lpszClassName = WC_TABCONTROLA;
3360 RegisterClassA (&wndClass);
3364 VOID
3365 TAB_Unregister (void)
3367 UnregisterClassA (WC_TABCONTROLA, NULL);