Rename COMCTL32_{Alloc,ReAlloc,Free} to {Alloc,ReAlloc,Free}.
[wine/hacks.git] / dlls / comctl32 / tab.c
blobfddb4e8dfffa91e8d7ea478c8e10e3b12dcd9286
1 /*
2 * Tab control
4 * Copyright 1998 Anders Carlsson
5 * Copyright 1999 Alex Priem <alexp@sci.kun.nl>
6 * Copyright 1999 Francis Beaudet
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public
10 * License as published by the Free Software Foundation; either
11 * version 2.1 of the License, or (at your option) any later version.
13 * This library is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Lesser General Public License for more details.
18 * You should have received a copy of the GNU Lesser General Public
19 * License along with this library; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22 * TODO:
23 * Image list support
24 * Unicode support (under construction)
26 * FIXME:
27 * UpDown control not displayed until after a tab is clicked on
30 #include <stdarg.h>
31 #include <string.h>
33 #include "windef.h"
34 #include "winbase.h"
35 #include "wingdi.h"
36 #include "winuser.h"
37 #include "winnls.h"
38 #include "commctrl.h"
39 #include "comctl32.h"
40 #include "wine/debug.h"
41 #include <math.h>
43 WINE_DEFAULT_DEBUG_CHANNEL(tab);
45 typedef struct
47 UINT mask;
48 DWORD dwState;
49 LPWSTR pszText;
50 INT iImage;
51 LPARAM lParam;
52 RECT rect; /* bounding rectangle of the item relative to the
53 * leftmost item (the leftmost item, 0, would have a
54 * "left" member of 0 in this rectangle)
56 * additionally the top member hold the row number
57 * and bottom is unused and should be 0 */
58 } TAB_ITEM;
60 typedef struct
62 UINT uNumItem; /* number of tab items */
63 UINT uNumRows; /* number of tab rows */
64 INT tabHeight; /* height of the tab row */
65 INT tabWidth; /* width of tabs */
66 INT tabMinWidth; /* minimum width of items */
67 USHORT uHItemPadding; /* amount of horizontal padding, in pixels */
68 USHORT uVItemPadding; /* amount of vertical padding, in pixels */
69 HFONT hFont; /* handle to the current font */
70 HCURSOR hcurArrow; /* handle to the current cursor */
71 HIMAGELIST himl; /* handle to a image list (may be 0) */
72 HWND hwndToolTip; /* handle to tab's tooltip */
73 INT leftmostVisible; /* Used for scrolling, this member contains
74 * the index of the first visible item */
75 INT iSelected; /* the currently selected item */
76 INT iHotTracked; /* the highlighted item under the mouse */
77 INT uFocus; /* item which has the focus */
78 TAB_ITEM* items; /* pointer to an array of TAB_ITEM's */
79 BOOL DoRedraw; /* flag for redrawing when tab contents is changed*/
80 BOOL needsScrolling; /* TRUE if the size of the tabs is greater than
81 * the size of the control */
82 BOOL fSizeSet; /* was the size of the tabs explicitly set? */
83 BOOL bUnicode; /* Unicode control? */
84 HWND hwndUpDown; /* Updown control used for scrolling */
85 } TAB_INFO;
87 /******************************************************************************
88 * Positioning constants
90 #define SELECTED_TAB_OFFSET 2
91 #define ROUND_CORNER_SIZE 2
92 #define DISPLAY_AREA_PADDINGX 2
93 #define DISPLAY_AREA_PADDINGY 2
94 #define CONTROL_BORDER_SIZEX 2
95 #define CONTROL_BORDER_SIZEY 2
96 #define BUTTON_SPACINGX 4
97 #define BUTTON_SPACINGY 4
98 #define FLAT_BTN_SPACINGX 8
99 #define DEFAULT_TAB_WIDTH 96
101 #define TAB_GetInfoPtr(hwnd) ((TAB_INFO *)GetWindowLongA(hwnd,0))
103 /******************************************************************************
104 * Hot-tracking timer constants
106 #define TAB_HOTTRACK_TIMER 1
107 #define TAB_HOTTRACK_TIMER_INTERVAL 100 /* milliseconds */
109 /******************************************************************************
110 * Prototypes
112 static void TAB_Refresh (HWND hwnd, HDC hdc);
113 static void TAB_InvalidateTabArea(HWND hwnd, TAB_INFO* infoPtr);
114 static void TAB_EnsureSelectionVisible(HWND hwnd, TAB_INFO* infoPtr);
115 static void TAB_DrawItem(HWND hwnd, HDC hdc, INT iItem);
116 static void TAB_DrawItemInterior(HWND hwnd, HDC hdc, INT iItem, RECT* drawRect);
118 static BOOL
119 TAB_SendSimpleNotify (HWND hwnd, UINT code)
121 NMHDR nmhdr;
123 nmhdr.hwndFrom = hwnd;
124 nmhdr.idFrom = GetWindowLongA(hwnd, GWL_ID);
125 nmhdr.code = code;
127 return (BOOL) SendMessageA (GetParent (hwnd), WM_NOTIFY,
128 (WPARAM) nmhdr.idFrom, (LPARAM) &nmhdr);
131 static VOID
132 TAB_RelayEvent (HWND hwndTip, HWND hwndMsg, UINT uMsg,
133 WPARAM wParam, LPARAM lParam)
135 MSG msg;
137 msg.hwnd = hwndMsg;
138 msg.message = uMsg;
139 msg.wParam = wParam;
140 msg.lParam = lParam;
141 msg.time = GetMessageTime ();
142 msg.pt.x = LOWORD(GetMessagePos ());
143 msg.pt.y = HIWORD(GetMessagePos ());
145 SendMessageA (hwndTip, TTM_RELAYEVENT, 0, (LPARAM)&msg);
148 static void
149 TAB_DumpItemExternalA(TCITEMA *pti, UINT iItem)
151 if (TRACE_ON(tab)) {
152 TRACE("external tab %d, mask=0x%08x, dwState=0x%08x, dwStateMask=0x%08x, cchTextMax=0x%08x\n",
153 iItem, pti->mask, pti->dwState, pti->dwStateMask, pti->cchTextMax);
154 TRACE("external tab %d, iImage=%d, lParam=0x%08lx, pszTextA=%s\n",
155 iItem, pti->iImage, pti->lParam, debugstr_a(pti->pszText));
160 static void
161 TAB_DumpItemExternalW(TCITEMW *pti, UINT iItem)
163 if (TRACE_ON(tab)) {
164 TRACE("external tab %d, mask=0x%08x, dwState=0x%08lx, dwStateMask=0x%08lx, cchTextMax=0x%08x\n",
165 iItem, pti->mask, pti->dwState, pti->dwStateMask, pti->cchTextMax);
166 TRACE("external tab %d, iImage=%d, lParam=0x%08lx, pszTextW=%s\n",
167 iItem, pti->iImage, pti->lParam, debugstr_w(pti->pszText));
171 static void
172 TAB_DumpItemInternal(TAB_INFO *infoPtr, UINT iItem)
174 if (TRACE_ON(tab)) {
175 TAB_ITEM *ti;
177 ti = &infoPtr->items[iItem];
178 TRACE("tab %d, mask=0x%08x, dwState=0x%08lx, pszText=%s, iImage=%d\n",
179 iItem, ti->mask, ti->dwState, debugstr_w(ti->pszText),
180 ti->iImage);
181 TRACE("tab %d, lParam=0x%08lx, rect.left=%ld, rect.top(row)=%ld\n",
182 iItem, ti->lParam, ti->rect.left, ti->rect.top);
186 static LRESULT
187 TAB_GetCurSel (HWND hwnd)
189 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
191 return infoPtr->iSelected;
194 static LRESULT
195 TAB_GetCurFocus (HWND hwnd)
197 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
199 return infoPtr->uFocus;
202 static LRESULT
203 TAB_GetToolTips (HWND hwnd, WPARAM wParam, LPARAM lParam)
205 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
207 if (infoPtr == NULL) return 0;
208 return (LRESULT)infoPtr->hwndToolTip;
211 static LRESULT
212 TAB_SetCurSel (HWND hwnd,WPARAM wParam)
214 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
215 INT iItem = (INT)wParam;
216 INT prevItem;
218 prevItem = -1;
219 if ((iItem >= 0) && (iItem < infoPtr->uNumItem)) {
220 prevItem=infoPtr->iSelected;
221 infoPtr->iSelected=iItem;
222 TAB_EnsureSelectionVisible(hwnd, infoPtr);
223 TAB_InvalidateTabArea(hwnd, infoPtr);
225 return prevItem;
228 static LRESULT
229 TAB_SetCurFocus (HWND hwnd,WPARAM wParam)
231 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
232 INT iItem=(INT) wParam;
234 if ((iItem < 0) || (iItem >= infoPtr->uNumItem)) return 0;
236 if (GetWindowLongA(hwnd, GWL_STYLE) & TCS_BUTTONS) {
237 FIXME("Should set input focus\n");
238 } else {
239 int oldFocus = infoPtr->uFocus;
240 if (infoPtr->iSelected != iItem || infoPtr->uFocus == -1 ) {
241 infoPtr->uFocus = iItem;
242 if (oldFocus != -1) {
243 if (TAB_SendSimpleNotify(hwnd, TCN_SELCHANGING)!=TRUE) {
244 infoPtr->iSelected = iItem;
245 TAB_SendSimpleNotify(hwnd, TCN_SELCHANGE);
247 else
248 infoPtr->iSelected = iItem;
249 TAB_EnsureSelectionVisible(hwnd, infoPtr);
250 TAB_InvalidateTabArea(hwnd, infoPtr);
254 return 0;
257 static LRESULT
258 TAB_SetToolTips (HWND hwnd, WPARAM wParam, LPARAM lParam)
260 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
262 if (infoPtr == NULL) return 0;
263 infoPtr->hwndToolTip = (HWND)wParam;
264 return 0;
267 static LRESULT
268 TAB_SetPadding (HWND hwnd, WPARAM wParam, LPARAM lParam)
270 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
272 if (infoPtr == NULL) return 0;
273 infoPtr->uHItemPadding=LOWORD(lParam);
274 infoPtr->uVItemPadding=HIWORD(lParam);
275 return 0;
278 /******************************************************************************
279 * TAB_InternalGetItemRect
281 * This method will calculate the rectangle representing a given tab item in
282 * client coordinates. This method takes scrolling into account.
284 * This method returns TRUE if the item is visible in the window and FALSE
285 * if it is completely outside the client area.
287 static BOOL TAB_InternalGetItemRect(
288 HWND hwnd,
289 TAB_INFO* infoPtr,
290 INT itemIndex,
291 RECT* itemRect,
292 RECT* selectedRect)
294 RECT tmpItemRect,clientRect;
295 LONG lStyle = GetWindowLongA(hwnd, GWL_STYLE);
297 /* Perform a sanity check and a trivial visibility check. */
298 if ( (infoPtr->uNumItem <= 0) ||
299 (itemIndex >= infoPtr->uNumItem) ||
300 (!((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)) && (itemIndex < infoPtr->leftmostVisible)) )
301 return FALSE;
304 * Avoid special cases in this procedure by assigning the "out"
305 * parameters if the caller didn't supply them
307 if (itemRect == NULL)
308 itemRect = &tmpItemRect;
310 /* Retrieve the unmodified item rect. */
311 *itemRect = infoPtr->items[itemIndex].rect;
313 /* calculate the times bottom and top based on the row */
314 GetClientRect(hwnd, &clientRect);
316 if ((lStyle & TCS_BOTTOM) && !(lStyle & TCS_VERTICAL))
318 itemRect->bottom = clientRect.bottom -
319 SELECTED_TAB_OFFSET -
320 itemRect->top * (infoPtr->tabHeight - 2) -
321 ((lStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : 0);
323 itemRect->top = clientRect.bottom -
324 infoPtr->tabHeight -
325 itemRect->top * (infoPtr->tabHeight - 2) -
326 ((lStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : 0);
328 else if((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
330 itemRect->right = clientRect.right - SELECTED_TAB_OFFSET - itemRect->left * (infoPtr->tabHeight - 2) -
331 ((lStyle & TCS_BUTTONS) ? itemRect->left * BUTTON_SPACINGY : 0);
332 itemRect->left = clientRect.right - infoPtr->tabHeight - itemRect->left * (infoPtr->tabHeight - 2) -
333 ((lStyle & TCS_BUTTONS) ? itemRect->left * BUTTON_SPACINGY : 0);
335 else if((lStyle & TCS_VERTICAL) && !(lStyle & TCS_BOTTOM))
337 itemRect->right = clientRect.left + infoPtr->tabHeight + itemRect->left * (infoPtr->tabHeight - 2) +
338 ((lStyle & TCS_BUTTONS) ? itemRect->left * BUTTON_SPACINGY : 0);
339 itemRect->left = clientRect.left + SELECTED_TAB_OFFSET + itemRect->left * (infoPtr->tabHeight - 2) +
340 ((lStyle & TCS_BUTTONS) ? itemRect->left * BUTTON_SPACINGY : 0);
342 else if(!(lStyle & TCS_VERTICAL) && !(lStyle & TCS_BOTTOM)) /* not TCS_BOTTOM and not TCS_VERTICAL */
344 itemRect->bottom = clientRect.top +
345 infoPtr->tabHeight +
346 itemRect->top * (infoPtr->tabHeight - 2) +
347 ((lStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : 0);
348 itemRect->top = clientRect.top +
349 SELECTED_TAB_OFFSET +
350 itemRect->top * (infoPtr->tabHeight - 2) +
351 ((lStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : 0);
355 * "scroll" it to make sure the item at the very left of the
356 * tab control is the leftmost visible tab.
358 if(lStyle & TCS_VERTICAL)
360 OffsetRect(itemRect,
362 -(clientRect.bottom - infoPtr->items[infoPtr->leftmostVisible].rect.bottom));
365 * Move the rectangle so the first item is slightly offset from
366 * the bottom of the tab control.
368 OffsetRect(itemRect,
370 -SELECTED_TAB_OFFSET);
372 } else
374 OffsetRect(itemRect,
375 -infoPtr->items[infoPtr->leftmostVisible].rect.left,
379 * Move the rectangle so the first item is slightly offset from
380 * the left of the tab control.
382 OffsetRect(itemRect,
383 SELECTED_TAB_OFFSET,
386 TRACE("item %d tab h=%d, rect=(%ld,%ld)-(%ld,%ld)\n",
387 itemIndex, infoPtr->tabHeight,
388 itemRect->left, itemRect->top, itemRect->right, itemRect->bottom);
390 /* Now, calculate the position of the item as if it were selected. */
391 if (selectedRect!=NULL)
393 CopyRect(selectedRect, itemRect);
395 /* The rectangle of a selected item is a bit wider. */
396 if(lStyle & TCS_VERTICAL)
397 InflateRect(selectedRect, 0, SELECTED_TAB_OFFSET);
398 else
399 InflateRect(selectedRect, SELECTED_TAB_OFFSET, 0);
401 /* If it also a bit higher. */
402 if ((lStyle & TCS_BOTTOM) && !(lStyle & TCS_VERTICAL))
404 selectedRect->top -= 2; /* the border is thicker on the bottom */
405 selectedRect->bottom += SELECTED_TAB_OFFSET;
407 else if((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
409 selectedRect->left -= 2; /* the border is thicker on the right */
410 selectedRect->right += SELECTED_TAB_OFFSET;
412 else if(lStyle & TCS_VERTICAL)
414 selectedRect->left -= SELECTED_TAB_OFFSET;
415 selectedRect->right += 1;
417 else
419 selectedRect->top -= SELECTED_TAB_OFFSET;
420 selectedRect->bottom += 1;
424 return TRUE;
427 static BOOL TAB_GetItemRect(HWND hwnd, WPARAM wParam, LPARAM lParam)
429 return TAB_InternalGetItemRect(hwnd, TAB_GetInfoPtr(hwnd), (INT)wParam,
430 (LPRECT)lParam, (LPRECT)NULL);
433 /******************************************************************************
434 * TAB_KeyUp
436 * This method is called to handle keyboard input
438 static LRESULT TAB_KeyUp(
439 HWND hwnd,
440 WPARAM keyCode)
442 TAB_INFO* infoPtr = TAB_GetInfoPtr(hwnd);
443 int newItem = -1;
445 switch (keyCode)
447 case VK_LEFT:
448 newItem = infoPtr->uFocus - 1;
449 break;
450 case VK_RIGHT:
451 newItem = infoPtr->uFocus + 1;
452 break;
456 * If we changed to a valid item, change the selection
458 if ((newItem >= 0) &&
459 (newItem < infoPtr->uNumItem) &&
460 (infoPtr->uFocus != newItem))
462 if (!TAB_SendSimpleNotify(hwnd, TCN_SELCHANGING))
464 infoPtr->iSelected = newItem;
465 infoPtr->uFocus = newItem;
466 TAB_SendSimpleNotify(hwnd, TCN_SELCHANGE);
468 TAB_EnsureSelectionVisible(hwnd, infoPtr);
469 TAB_InvalidateTabArea(hwnd, infoPtr);
473 return 0;
476 /******************************************************************************
477 * TAB_FocusChanging
479 * This method is called whenever the focus goes in or out of this control
480 * it is used to update the visual state of the control.
482 static LRESULT TAB_FocusChanging(
483 HWND hwnd,
484 UINT uMsg,
485 WPARAM wParam,
486 LPARAM lParam)
488 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
489 RECT selectedRect;
490 BOOL isVisible;
493 * Get the rectangle for the item.
495 isVisible = TAB_InternalGetItemRect(hwnd,
496 infoPtr,
497 infoPtr->uFocus,
498 NULL,
499 &selectedRect);
502 * If the rectangle is not completely invisible, invalidate that
503 * portion of the window.
505 if (isVisible)
507 TRACE("invalidate (%ld,%ld)-(%ld,%ld)\n",
508 selectedRect.left,selectedRect.top,
509 selectedRect.right,selectedRect.bottom);
510 InvalidateRect(hwnd, &selectedRect, TRUE);
514 * Don't otherwise disturb normal behavior.
516 return DefWindowProcA (hwnd, uMsg, wParam, lParam);
519 static INT TAB_InternalHitTest (
520 HWND hwnd,
521 TAB_INFO* infoPtr,
522 POINT pt,
523 UINT* flags)
526 RECT rect;
527 INT iCount;
529 for (iCount = 0; iCount < infoPtr->uNumItem; iCount++)
531 TAB_InternalGetItemRect(hwnd, infoPtr, iCount, &rect, NULL);
533 if (PtInRect(&rect, pt))
535 *flags = TCHT_ONITEM;
536 return iCount;
540 *flags = TCHT_NOWHERE;
541 return -1;
544 static LRESULT
545 TAB_HitTest (HWND hwnd, WPARAM wParam, LPARAM lParam)
547 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
548 LPTCHITTESTINFO lptest = (LPTCHITTESTINFO) lParam;
550 return TAB_InternalHitTest (hwnd, infoPtr, lptest->pt, &lptest->flags);
553 /******************************************************************************
554 * TAB_NCHitTest
556 * Napster v2b5 has a tab control for its main navigation which has a client
557 * area that covers the whole area of the dialog pages.
558 * That's why it receives all msgs for that area and the underlying dialog ctrls
559 * are dead.
560 * So I decided that we should handle WM_NCHITTEST here and return
561 * HTTRANSPARENT if we don't hit the tab control buttons.
562 * FIXME: WM_NCHITTEST handling correct ? Fix it if you know that Windows
563 * doesn't do it that way. Maybe depends on tab control styles ?
565 static LRESULT
566 TAB_NCHitTest (HWND hwnd, LPARAM lParam)
568 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
569 POINT pt;
570 UINT dummyflag;
572 pt.x = LOWORD(lParam);
573 pt.y = HIWORD(lParam);
574 ScreenToClient(hwnd, &pt);
576 if (TAB_InternalHitTest(hwnd, infoPtr, pt, &dummyflag) == -1)
577 return HTTRANSPARENT;
578 else
579 return HTCLIENT;
582 static LRESULT
583 TAB_LButtonDown (HWND hwnd, WPARAM wParam, LPARAM lParam)
585 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
586 POINT pt;
587 INT newItem, dummy;
589 if (infoPtr->hwndToolTip)
590 TAB_RelayEvent (infoPtr->hwndToolTip, hwnd,
591 WM_LBUTTONDOWN, wParam, lParam);
593 if (GetWindowLongA(hwnd, GWL_STYLE) & TCS_FOCUSONBUTTONDOWN ) {
594 SetFocus (hwnd);
597 if (infoPtr->hwndToolTip)
598 TAB_RelayEvent (infoPtr->hwndToolTip, hwnd,
599 WM_LBUTTONDOWN, wParam, lParam);
601 pt.x = (INT)LOWORD(lParam);
602 pt.y = (INT)HIWORD(lParam);
604 newItem = TAB_InternalHitTest (hwnd, infoPtr, pt, &dummy);
606 TRACE("On Tab, item %d\n", newItem);
608 if ((newItem != -1) && (infoPtr->iSelected != newItem))
610 if (TAB_SendSimpleNotify(hwnd, TCN_SELCHANGING) != TRUE)
612 infoPtr->iSelected = newItem;
613 infoPtr->uFocus = newItem;
614 TAB_SendSimpleNotify(hwnd, TCN_SELCHANGE);
616 TAB_EnsureSelectionVisible(hwnd, infoPtr);
618 TAB_InvalidateTabArea(hwnd, infoPtr);
621 return 0;
624 static LRESULT
625 TAB_LButtonUp (HWND hwnd, WPARAM wParam, LPARAM lParam)
627 TAB_SendSimpleNotify(hwnd, NM_CLICK);
629 return 0;
632 static LRESULT
633 TAB_RButtonDown (HWND hwnd, WPARAM wParam, LPARAM lParam)
635 TAB_SendSimpleNotify(hwnd, NM_RCLICK);
636 return 0;
639 /******************************************************************************
640 * TAB_DrawLoneItemInterior
642 * This calls TAB_DrawItemInterior. However, TAB_DrawItemInterior is normally
643 * called by TAB_DrawItem which is normally called by TAB_Refresh which sets
644 * up the device context and font. This routine does the same setup but
645 * only calls TAB_DrawItemInterior for the single specified item.
647 static void
648 TAB_DrawLoneItemInterior(HWND hwnd, TAB_INFO* infoPtr, int iItem)
650 HDC hdc = GetDC(hwnd);
651 HFONT hOldFont = SelectObject(hdc, infoPtr->hFont);
652 TAB_DrawItemInterior(hwnd, hdc, iItem, NULL);
653 SelectObject(hdc, hOldFont);
654 ReleaseDC(hwnd, hdc);
657 /******************************************************************************
658 * TAB_HotTrackTimerProc
660 * When a mouse-move event causes a tab to be highlighted (hot-tracking), a
661 * timer is setup so we can check if the mouse is moved out of our window.
662 * (We don't get an event when the mouse leaves, the mouse-move events just
663 * stop being delivered to our window and just start being delivered to
664 * another window.) This function is called when the timer triggers so
665 * we can check if the mouse has left our window. If so, we un-highlight
666 * the hot-tracked tab.
668 static VOID CALLBACK
669 TAB_HotTrackTimerProc
671 HWND hwnd, /* handle of window for timer messages */
672 UINT uMsg, /* WM_TIMER message */
673 UINT idEvent, /* timer identifier */
674 DWORD dwTime /* current system time */
677 TAB_INFO* infoPtr = TAB_GetInfoPtr(hwnd);
679 if (infoPtr != NULL && infoPtr->iHotTracked >= 0)
681 POINT pt;
684 ** If we can't get the cursor position, or if the cursor is outside our
685 ** window, we un-highlight the hot-tracked tab. Note that the cursor is
686 ** "outside" even if it is within our bounding rect if another window
687 ** overlaps. Note also that the case where the cursor stayed within our
688 ** window but has moved off the hot-tracked tab will be handled by the
689 ** WM_MOUSEMOVE event.
691 if (!GetCursorPos(&pt) || WindowFromPoint(pt) != hwnd)
693 /* Redraw iHotTracked to look normal */
694 INT iRedraw = infoPtr->iHotTracked;
695 infoPtr->iHotTracked = -1;
696 TAB_DrawLoneItemInterior(hwnd, infoPtr, iRedraw);
698 /* Kill this timer */
699 KillTimer(hwnd, TAB_HOTTRACK_TIMER);
704 /******************************************************************************
705 * TAB_RecalcHotTrack
707 * If a tab control has the TCS_HOTTRACK style, then the tab under the mouse
708 * should be highlighted. This function determines which tab in a tab control,
709 * if any, is under the mouse and records that information. The caller may
710 * supply output parameters to receive the item number of the tab item which
711 * was highlighted but isn't any longer and of the tab item which is now
712 * highlighted but wasn't previously. The caller can use this information to
713 * selectively redraw those tab items.
715 * If the caller has a mouse position, it can supply it through the pos
716 * parameter. For example, TAB_MouseMove does this. Otherwise, the caller
717 * supplies NULL and this function determines the current mouse position
718 * itself.
720 static void
721 TAB_RecalcHotTrack
723 HWND hwnd,
724 const LPARAM* pos,
725 int* out_redrawLeave,
726 int* out_redrawEnter
729 TAB_INFO* infoPtr = TAB_GetInfoPtr(hwnd);
731 int item = -1;
734 if (out_redrawLeave != NULL)
735 *out_redrawLeave = -1;
736 if (out_redrawEnter != NULL)
737 *out_redrawEnter = -1;
739 if (GetWindowLongA(hwnd, GWL_STYLE) & TCS_HOTTRACK)
741 POINT pt;
742 UINT flags;
744 if (pos == NULL)
746 GetCursorPos(&pt);
747 ScreenToClient(hwnd, &pt);
749 else
751 pt.x = LOWORD(*pos);
752 pt.y = HIWORD(*pos);
755 item = TAB_InternalHitTest(hwnd, infoPtr, pt, &flags);
758 if (item != infoPtr->iHotTracked)
760 if (infoPtr->iHotTracked >= 0)
762 /* Mark currently hot-tracked to be redrawn to look normal */
763 if (out_redrawLeave != NULL)
764 *out_redrawLeave = infoPtr->iHotTracked;
766 if (item < 0)
768 /* Kill timer which forces recheck of mouse pos */
769 KillTimer(hwnd, TAB_HOTTRACK_TIMER);
772 else
774 /* Start timer so we recheck mouse pos */
775 UINT timerID = SetTimer
777 hwnd,
778 TAB_HOTTRACK_TIMER,
779 TAB_HOTTRACK_TIMER_INTERVAL,
780 TAB_HotTrackTimerProc
783 if (timerID == 0)
784 return; /* Hot tracking not available */
787 infoPtr->iHotTracked = item;
789 if (item >= 0)
791 /* Mark new hot-tracked to be redrawn to look highlighted */
792 if (out_redrawEnter != NULL)
793 *out_redrawEnter = item;
798 /******************************************************************************
799 * TAB_MouseMove
801 * Handles the mouse-move event. Updates tooltips. Updates hot-tracking.
803 static LRESULT
804 TAB_MouseMove (HWND hwnd, WPARAM wParam, LPARAM lParam)
806 int redrawLeave;
807 int redrawEnter;
809 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
811 if (infoPtr->hwndToolTip)
812 TAB_RelayEvent (infoPtr->hwndToolTip, hwnd,
813 WM_LBUTTONDOWN, wParam, lParam);
815 /* Determine which tab to highlight. Redraw tabs which change highlight
816 ** status. */
817 TAB_RecalcHotTrack(hwnd, &lParam, &redrawLeave, &redrawEnter);
819 if (redrawLeave != -1)
820 TAB_DrawLoneItemInterior(hwnd, infoPtr, redrawLeave);
821 if (redrawEnter != -1)
822 TAB_DrawLoneItemInterior(hwnd, infoPtr, redrawEnter);
824 return 0;
827 /******************************************************************************
828 * TAB_AdjustRect
830 * Calculates the tab control's display area given the window rectangle or
831 * the window rectangle given the requested display rectangle.
833 static LRESULT TAB_AdjustRect(
834 HWND hwnd,
835 WPARAM fLarger,
836 LPRECT prc)
838 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
839 DWORD lStyle = GetWindowLongA(hwnd, GWL_STYLE);
841 if(lStyle & TCS_VERTICAL)
843 if (fLarger) /* Go from display rectangle */
845 /* Add the height of the tabs. */
846 if (lStyle & TCS_BOTTOM)
847 prc->right += (infoPtr->tabHeight - 2) * infoPtr->uNumRows + 2;
848 else
849 prc->left -= (infoPtr->tabHeight - 2) * infoPtr->uNumRows + 2;
851 /* FIXME: not sure if these InflateRect's need to have different values for TCS_VERTICAL */
852 /* Inflate the rectangle for the padding */
853 InflateRect(prc, DISPLAY_AREA_PADDINGX, DISPLAY_AREA_PADDINGY);
855 /* Inflate for the border */
856 InflateRect(prc, CONTROL_BORDER_SIZEX, CONTROL_BORDER_SIZEX);
858 else /* Go from window rectangle. */
860 /* FIXME: not sure if these InflateRect's need to have different values for TCS_VERTICAL */
861 /* Deflate the rectangle for the border */
862 InflateRect(prc, -CONTROL_BORDER_SIZEX, -CONTROL_BORDER_SIZEX);
864 /* Deflate the rectangle for the padding */
865 InflateRect(prc, -DISPLAY_AREA_PADDINGX, -DISPLAY_AREA_PADDINGY);
867 /* Remove the height of the tabs. */
868 if (lStyle & TCS_BOTTOM)
869 prc->right -= (infoPtr->tabHeight - 2) * infoPtr->uNumRows + 2;
870 else
871 prc->left += (infoPtr->tabHeight - 2) * infoPtr->uNumRows + 2;
874 else {
875 if (fLarger) /* Go from display rectangle */
877 /* Add the height of the tabs. */
878 if (lStyle & TCS_BOTTOM)
879 prc->bottom += (infoPtr->tabHeight - 2) * infoPtr->uNumRows + 2;
880 else
881 prc->top -= (infoPtr->tabHeight - 2) * infoPtr->uNumRows + 2;
883 /* Inflate the rectangle for the padding */
884 InflateRect(prc, DISPLAY_AREA_PADDINGX, DISPLAY_AREA_PADDINGY);
886 /* Inflate for the border */
887 InflateRect(prc, CONTROL_BORDER_SIZEX, CONTROL_BORDER_SIZEX);
889 else /* Go from window rectangle. */
891 /* Deflate the rectangle for the border */
892 InflateRect(prc, -CONTROL_BORDER_SIZEX, -CONTROL_BORDER_SIZEX);
894 /* Deflate the rectangle for the padding */
895 InflateRect(prc, -DISPLAY_AREA_PADDINGX, -DISPLAY_AREA_PADDINGY);
897 /* Remove the height of the tabs. */
898 if (lStyle & TCS_BOTTOM)
899 prc->bottom -= (infoPtr->tabHeight - 2) * infoPtr->uNumRows + 2;
900 else
901 prc->top += (infoPtr->tabHeight - 2) * infoPtr->uNumRows + 2;
905 return 0;
908 /******************************************************************************
909 * TAB_OnHScroll
911 * This method will handle the notification from the scroll control and
912 * perform the scrolling operation on the tab control.
914 static LRESULT TAB_OnHScroll(
915 HWND hwnd,
916 int nScrollCode,
917 int nPos,
918 HWND hwndScroll)
920 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
922 if(nScrollCode == SB_THUMBPOSITION && nPos != infoPtr->leftmostVisible)
924 if(nPos < infoPtr->leftmostVisible)
925 infoPtr->leftmostVisible--;
926 else
927 infoPtr->leftmostVisible++;
929 TAB_RecalcHotTrack(hwnd, NULL, NULL, NULL);
930 TAB_InvalidateTabArea(hwnd, infoPtr);
931 SendMessageA(infoPtr->hwndUpDown, UDM_SETPOS, 0,
932 MAKELONG(infoPtr->leftmostVisible, 0));
935 return 0;
938 /******************************************************************************
939 * TAB_SetupScrolling
941 * This method will check the current scrolling state and make sure the
942 * scrolling control is displayed (or not).
944 static void TAB_SetupScrolling(
945 HWND hwnd,
946 TAB_INFO* infoPtr,
947 const RECT* clientRect)
949 INT maxRange = 0;
950 DWORD lStyle = GetWindowLongA(hwnd, GWL_STYLE);
952 if (infoPtr->needsScrolling)
954 RECT controlPos;
955 INT vsize, tabwidth;
958 * Calculate the position of the scroll control.
960 if(lStyle & TCS_VERTICAL)
962 controlPos.right = clientRect->right;
963 controlPos.left = controlPos.right - 2 * GetSystemMetrics(SM_CXHSCROLL);
965 if (lStyle & TCS_BOTTOM)
967 controlPos.top = clientRect->bottom - infoPtr->tabHeight;
968 controlPos.bottom = controlPos.top + GetSystemMetrics(SM_CYHSCROLL);
970 else
972 controlPos.bottom = clientRect->top + infoPtr->tabHeight;
973 controlPos.top = controlPos.bottom - GetSystemMetrics(SM_CYHSCROLL);
976 else
978 controlPos.right = clientRect->right;
979 controlPos.left = controlPos.right - 2 * GetSystemMetrics(SM_CXHSCROLL);
981 if (lStyle & TCS_BOTTOM)
983 controlPos.top = clientRect->bottom - infoPtr->tabHeight;
984 controlPos.bottom = controlPos.top + GetSystemMetrics(SM_CYHSCROLL);
986 else
988 controlPos.bottom = clientRect->top + infoPtr->tabHeight;
989 controlPos.top = controlPos.bottom - GetSystemMetrics(SM_CYHSCROLL);
994 * If we don't have a scroll control yet, we want to create one.
995 * If we have one, we want to make sure it's positioned properly.
997 if (infoPtr->hwndUpDown==0)
999 infoPtr->hwndUpDown = CreateWindowA("msctls_updown32",
1001 WS_VISIBLE | WS_CHILD | UDS_HORZ,
1002 controlPos.left, controlPos.top,
1003 controlPos.right - controlPos.left,
1004 controlPos.bottom - controlPos.top,
1005 hwnd,
1006 NULL,
1007 NULL,
1008 NULL);
1010 else
1012 SetWindowPos(infoPtr->hwndUpDown,
1013 NULL,
1014 controlPos.left, controlPos.top,
1015 controlPos.right - controlPos.left,
1016 controlPos.bottom - controlPos.top,
1017 SWP_SHOWWINDOW | SWP_NOZORDER);
1020 /* Now calculate upper limit of the updown control range.
1021 * We do this by calculating how many tabs will be offscreen when the
1022 * last tab is visible.
1024 if(infoPtr->uNumItem)
1026 vsize = clientRect->right - (controlPos.right - controlPos.left + 1);
1027 maxRange = infoPtr->uNumItem;
1028 tabwidth = infoPtr->items[maxRange - 1].rect.right;
1030 for(; maxRange > 0; maxRange--)
1032 if(tabwidth - infoPtr->items[maxRange - 1].rect.left > vsize)
1033 break;
1036 if(maxRange == infoPtr->uNumItem)
1037 maxRange--;
1040 else
1042 /* If we once had a scroll control... hide it */
1043 if (infoPtr->hwndUpDown!=0)
1044 ShowWindow(infoPtr->hwndUpDown, SW_HIDE);
1046 if (infoPtr->hwndUpDown)
1047 SendMessageA(infoPtr->hwndUpDown, UDM_SETRANGE32, 0, maxRange);
1050 /******************************************************************************
1051 * TAB_SetItemBounds
1053 * This method will calculate the position rectangles of all the items in the
1054 * control. The rectangle calculated starts at 0 for the first item in the
1055 * list and ignores scrolling and selection.
1056 * It also uses the current font to determine the height of the tab row and
1057 * it checks if all the tabs fit in the client area of the window. If they
1058 * dont, a scrolling control is added.
1060 static void TAB_SetItemBounds (HWND hwnd)
1062 TAB_INFO* infoPtr = TAB_GetInfoPtr(hwnd);
1063 LONG lStyle = GetWindowLongA(hwnd, GWL_STYLE);
1064 TEXTMETRICA fontMetrics;
1065 INT curItem;
1066 INT curItemLeftPos;
1067 INT curItemRowCount;
1068 HFONT hFont, hOldFont;
1069 HDC hdc;
1070 RECT clientRect;
1071 SIZE size;
1072 INT iTemp;
1073 RECT* rcItem;
1074 INT iIndex;
1077 * We need to get text information so we need a DC and we need to select
1078 * a font.
1080 hdc = GetDC(hwnd);
1082 hFont = infoPtr->hFont ? infoPtr->hFont : GetStockObject (SYSTEM_FONT);
1083 hOldFont = SelectObject (hdc, hFont);
1086 * We will base the rectangle calculations on the client rectangle
1087 * of the control.
1089 GetClientRect(hwnd, &clientRect);
1091 /* if TCS_VERTICAL then swap the height and width so this code places the
1092 tabs along the top of the rectangle and we can just rotate them after
1093 rather than duplicate all of the below code */
1094 if(lStyle & TCS_VERTICAL)
1096 iTemp = clientRect.bottom;
1097 clientRect.bottom = clientRect.right;
1098 clientRect.right = iTemp;
1101 /* The leftmost item will be "0" aligned */
1102 curItemLeftPos = 0;
1103 curItemRowCount = infoPtr->uNumItem ? 1 : 0;
1105 if (!(lStyle & TCS_FIXEDWIDTH) && !((lStyle & TCS_OWNERDRAWFIXED) && infoPtr->fSizeSet) )
1107 int item_height;
1108 int icon_height = 0;
1110 /* Use the current font to determine the height of a tab. */
1111 GetTextMetricsA(hdc, &fontMetrics);
1113 /* Get the icon height */
1114 if (infoPtr->himl)
1115 ImageList_GetIconSize(infoPtr->himl, 0, &icon_height);
1117 /* Take the highest between font or icon */
1118 if (fontMetrics.tmHeight > icon_height)
1119 item_height = fontMetrics.tmHeight + 2;
1120 else
1121 item_height = icon_height;
1124 * Make sure there is enough space for the letters + icon + growing the
1125 * selected item + extra space for the selected item.
1127 infoPtr->tabHeight = item_height + SELECTED_TAB_OFFSET +
1128 ((lStyle & TCS_BUTTONS) ? 2 : 1) *
1129 infoPtr->uVItemPadding;
1131 TRACE("tabH=%d, tmH=%ld, iconh=%d\n",
1132 infoPtr->tabHeight, fontMetrics.tmHeight, icon_height);
1135 TRACE("client right=%ld\n", clientRect.right);
1137 for (curItem = 0; curItem < infoPtr->uNumItem; curItem++)
1139 /* Set the leftmost position of the tab. */
1140 infoPtr->items[curItem].rect.left = curItemLeftPos;
1142 if ( (lStyle & TCS_FIXEDWIDTH) || ((lStyle & TCS_OWNERDRAWFIXED) && infoPtr->fSizeSet))
1144 infoPtr->items[curItem].rect.right = infoPtr->items[curItem].rect.left +
1145 infoPtr->tabWidth +
1146 2 * infoPtr->uHItemPadding;
1148 else
1150 int icon_width = 0;
1151 int num = 2;
1153 /* Calculate how wide the tab is depending on the text it contains */
1154 GetTextExtentPoint32W(hdc, infoPtr->items[curItem].pszText,
1155 lstrlenW(infoPtr->items[curItem].pszText), &size);
1157 /* under Windows, there seems to be a minimum width of 2x the height
1158 * for button style tabs */
1159 if (lStyle & TCS_BUTTONS)
1160 size.cx = max(size.cx, 2 * (infoPtr->tabHeight - 2));
1162 /* Add the icon width */
1163 if (infoPtr->himl)
1165 ImageList_GetIconSize(infoPtr->himl, &icon_width, 0);
1166 num++;
1169 infoPtr->items[curItem].rect.right = infoPtr->items[curItem].rect.left +
1170 size.cx + icon_width +
1171 num * infoPtr->uHItemPadding;
1172 TRACE("for <%s>, l,r=%ld,%ld, num=%d\n",
1173 debugstr_w(infoPtr->items[curItem].pszText),
1174 infoPtr->items[curItem].rect.left,
1175 infoPtr->items[curItem].rect.right,
1176 num);
1180 * Check if this is a multiline tab control and if so
1181 * check to see if we should wrap the tabs
1183 * Because we are going to arange all these tabs evenly
1184 * really we are basically just counting rows at this point
1188 if (((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)) &&
1189 (infoPtr->items[curItem].rect.right > clientRect.right))
1191 infoPtr->items[curItem].rect.right -=
1192 infoPtr->items[curItem].rect.left;
1194 infoPtr->items[curItem].rect.left = 0;
1195 curItemRowCount++;
1196 TRACE("wrapping <%s>, l,r=%ld,%ld\n",
1197 debugstr_w(infoPtr->items[curItem].pszText),
1198 infoPtr->items[curItem].rect.left,
1199 infoPtr->items[curItem].rect.right);
1202 infoPtr->items[curItem].rect.bottom = 0;
1203 infoPtr->items[curItem].rect.top = curItemRowCount - 1;
1205 TRACE("TextSize: %li\n", size.cx);
1206 TRACE("Rect: T %li, L %li, B %li, R %li\n",
1207 infoPtr->items[curItem].rect.top,
1208 infoPtr->items[curItem].rect.left,
1209 infoPtr->items[curItem].rect.bottom,
1210 infoPtr->items[curItem].rect.right);
1213 * The leftmost position of the next item is the rightmost position
1214 * of this one.
1216 if (lStyle & TCS_BUTTONS)
1218 curItemLeftPos = infoPtr->items[curItem].rect.right + 1;
1219 if (lStyle & TCS_FLATBUTTONS)
1220 curItemLeftPos += FLAT_BTN_SPACINGX;
1222 else
1223 curItemLeftPos = infoPtr->items[curItem].rect.right;
1226 if (!((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)))
1229 * Check if we need a scrolling control.
1231 infoPtr->needsScrolling = (curItemLeftPos + (2 * SELECTED_TAB_OFFSET) >
1232 clientRect.right);
1234 /* Don't need scrolling, then update infoPtr->leftmostVisible */
1235 if(!infoPtr->needsScrolling)
1236 infoPtr->leftmostVisible = 0;
1238 TAB_SetupScrolling(hwnd, infoPtr, &clientRect);
1241 /* Set the number of rows */
1242 infoPtr->uNumRows = curItemRowCount;
1244 if (((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)) && (infoPtr->uNumItem > 0))
1246 INT widthDiff, remainder;
1247 INT tabPerRow,remTab;
1248 INT iRow,iItm;
1249 INT iIndexStart=0,iIndexEnd=0, iCount=0;
1252 * Ok windows tries to even out the rows. place the same
1253 * number of tabs in each row. So lets give that a shot
1256 tabPerRow = infoPtr->uNumItem / (infoPtr->uNumRows);
1257 remTab = infoPtr->uNumItem % (infoPtr->uNumRows);
1259 for (iItm=0,iRow=0,iCount=0,curItemLeftPos=0;
1260 iItm<infoPtr->uNumItem;
1261 iItm++,iCount++)
1263 /* normalize the current rect */
1265 /* shift the item to the left side of the clientRect */
1266 infoPtr->items[iItm].rect.right -=
1267 infoPtr->items[iItm].rect.left;
1268 infoPtr->items[iItm].rect.left = 0;
1270 TRACE("r=%ld, cl=%d, cl.r=%ld, iCount=%d, iRow=%d, uNumRows=%d, remTab=%d, tabPerRow=%d\n",
1271 infoPtr->items[iItm].rect.right,
1272 curItemLeftPos, clientRect.right,
1273 iCount, iRow, infoPtr->uNumRows, remTab, tabPerRow);
1275 /* if we have reached the maximum number of tabs on this row */
1276 /* move to the next row, reset our current item left position and */
1277 /* the count of items on this row */
1279 /* ************ FIXME FIXME FIXME *************** */
1280 /* */
1281 /* FIXME: */
1282 /* if vertical, */
1283 /* if item n and n+1 are in the same row, */
1284 /* then the display has n+1 lower (toward the */
1285 /* bottom) than n. We do it just the */
1286 /* opposite!!! */
1287 /* */
1288 /* ************ FIXME FIXME FIXME *************** */
1290 if (lStyle & TCS_VERTICAL) {
1291 /* Vert: Add the remaining tabs in the *last* remainder rows */
1292 if (iCount >= ((iRow>=(INT)infoPtr->uNumRows - remTab)?tabPerRow + 1:tabPerRow)) {
1293 iRow++;
1294 curItemLeftPos = 0;
1295 iCount = 0;
1297 } else {
1298 /* Horz: Add the remaining tabs in the *first* remainder rows */
1299 if (iCount >= ((iRow<remTab)?tabPerRow + 1:tabPerRow)) {
1300 iRow++;
1301 curItemLeftPos = 0;
1302 iCount = 0;
1306 /* shift the item to the right to place it as the next item in this row */
1307 infoPtr->items[iItm].rect.left += curItemLeftPos;
1308 infoPtr->items[iItm].rect.right += curItemLeftPos;
1309 infoPtr->items[iItm].rect.top = iRow;
1310 if (lStyle & TCS_BUTTONS)
1312 curItemLeftPos = infoPtr->items[iItm].rect.right + 1;
1313 if (lStyle & TCS_FLATBUTTONS)
1314 curItemLeftPos += FLAT_BTN_SPACINGX;
1316 else
1317 curItemLeftPos = infoPtr->items[iItm].rect.right;
1319 TRACE("arranging <%s>, l,r=%ld,%ld, row=%ld\n",
1320 debugstr_w(infoPtr->items[iItm].pszText),
1321 infoPtr->items[iItm].rect.left,
1322 infoPtr->items[iItm].rect.right,
1323 infoPtr->items[iItm].rect.top);
1327 * Justify the rows
1330 while(iIndexStart < infoPtr->uNumItem)
1333 * find the indexs of the row
1335 /* find the first item on the next row */
1336 for (iIndexEnd=iIndexStart;
1337 (iIndexEnd < infoPtr->uNumItem) &&
1338 (infoPtr->items[iIndexEnd].rect.top ==
1339 infoPtr->items[iIndexStart].rect.top) ;
1340 iIndexEnd++)
1341 /* intentionally blank */;
1344 * we need to justify these tabs so they fill the whole given
1345 * client area
1348 /* find the amount of space remaining on this row */
1349 widthDiff = clientRect.right - (2 * SELECTED_TAB_OFFSET) -
1350 infoPtr->items[iIndexEnd - 1].rect.right;
1352 /* iCount is the number of tab items on this row */
1353 iCount = iIndexEnd - iIndexStart;
1356 if (iCount > 1)
1358 remainder = widthDiff % iCount;
1359 widthDiff = widthDiff / iCount;
1360 /* add widthDiff/iCount, or extra space/items on row, to each item on this row */
1361 for (iIndex=iIndexStart,iCount=0; iIndex < iIndexEnd;
1362 iIndex++,iCount++)
1364 infoPtr->items[iIndex].rect.left += iCount * widthDiff;
1365 infoPtr->items[iIndex].rect.right += (iCount + 1) * widthDiff;
1367 TRACE("adjusting 1 <%s>, l,r=%ld,%ld\n",
1368 debugstr_w(infoPtr->items[iIndex].pszText),
1369 infoPtr->items[iIndex].rect.left,
1370 infoPtr->items[iIndex].rect.right);
1373 infoPtr->items[iIndex - 1].rect.right += remainder;
1375 else /* we have only one item on this row, make it take up the entire row */
1377 infoPtr->items[iIndexStart].rect.left = clientRect.left;
1378 infoPtr->items[iIndexStart].rect.right = clientRect.right - 4;
1380 TRACE("adjusting 2 <%s>, l,r=%ld,%ld\n",
1381 debugstr_w(infoPtr->items[iIndexStart].pszText),
1382 infoPtr->items[iIndexStart].rect.left,
1383 infoPtr->items[iIndexStart].rect.right);
1388 iIndexStart = iIndexEnd;
1393 /* if TCS_VERTICAL rotate the tabs so they are along the side of the clientRect */
1394 if(lStyle & TCS_VERTICAL)
1396 RECT rcOriginal;
1397 for(iIndex = 0; iIndex < infoPtr->uNumItem; iIndex++)
1399 rcItem = &(infoPtr->items[iIndex].rect);
1401 rcOriginal = *rcItem;
1403 /* this is rotating the items by 90 degrees around the center of the control */
1404 rcItem->top = (clientRect.right - (rcOriginal.left - clientRect.left)) - (rcOriginal.right - rcOriginal.left);
1405 rcItem->bottom = rcItem->top + (rcOriginal.right - rcOriginal.left);
1406 rcItem->left = rcOriginal.top;
1407 rcItem->right = rcOriginal.bottom;
1411 TAB_EnsureSelectionVisible(hwnd,infoPtr);
1412 TAB_RecalcHotTrack(hwnd, NULL, NULL, NULL);
1414 /* Cleanup */
1415 SelectObject (hdc, hOldFont);
1416 ReleaseDC (hwnd, hdc);
1419 /******************************************************************************
1420 * TAB_DrawItemInterior
1422 * This method is used to draw the interior (text and icon) of a single tab
1423 * into the tab control.
1425 static void
1426 TAB_DrawItemInterior
1428 HWND hwnd,
1429 HDC hdc,
1430 INT iItem,
1431 RECT* drawRect
1434 TAB_INFO* infoPtr = TAB_GetInfoPtr(hwnd);
1435 LONG lStyle = GetWindowLongA(hwnd, GWL_STYLE);
1437 RECT localRect;
1439 HPEN htextPen;
1440 HPEN holdPen;
1441 INT oldBkMode;
1443 if (drawRect == NULL)
1445 BOOL isVisible;
1446 RECT itemRect;
1447 RECT selectedRect;
1450 * Get the rectangle for the item.
1452 isVisible = TAB_InternalGetItemRect(hwnd, infoPtr, iItem, &itemRect, &selectedRect);
1453 if (!isVisible)
1454 return;
1457 * Make sure drawRect points to something valid; simplifies code.
1459 drawRect = &localRect;
1462 * This logic copied from the part of TAB_DrawItem which draws
1463 * the tab background. It's important to keep it in sync. I
1464 * would have liked to avoid code duplication, but couldn't figure
1465 * out how without making spaghetti of TAB_DrawItem.
1467 if (lStyle & TCS_BUTTONS)
1469 *drawRect = itemRect;
1470 if (iItem == infoPtr->iSelected)
1472 drawRect->right--;
1473 drawRect->bottom--;
1476 else
1478 if (iItem == infoPtr->iSelected)
1479 *drawRect = selectedRect;
1480 else
1481 *drawRect = itemRect;
1482 drawRect->right--;
1483 drawRect->bottom--;
1488 * Text pen
1490 htextPen = CreatePen( PS_SOLID, 1, GetSysColor(COLOR_BTNTEXT) );
1491 holdPen = SelectObject(hdc, htextPen);
1493 oldBkMode = SetBkMode(hdc, TRANSPARENT);
1494 SetTextColor(hdc, ( (iItem == infoPtr->iHotTracked) | (infoPtr->items[iItem].dwState & TCIS_HIGHLIGHTED)) ?
1495 comctl32_color.clrHighlight : comctl32_color.clrBtnText);
1499 * if owner draw, tell the owner to draw
1501 if ((lStyle & TCS_OWNERDRAWFIXED) && GetParent(hwnd))
1503 DRAWITEMSTRUCT dis;
1504 UINT id;
1507 * get the control id
1509 id = GetWindowLongA( hwnd, GWL_ID );
1512 * put together the DRAWITEMSTRUCT
1514 dis.CtlType = ODT_TAB;
1515 dis.CtlID = id;
1516 dis.itemID = iItem;
1517 dis.itemAction = ODA_DRAWENTIRE;
1518 dis.itemState = 0;
1519 if ( iItem == infoPtr->iSelected )
1520 dis.itemState |= ODS_SELECTED;
1521 if (infoPtr->uFocus == iItem)
1522 dis.itemState |= ODS_FOCUS;
1523 dis.hwndItem = hwnd; /* */
1524 dis.hDC = hdc;
1525 CopyRect(&dis.rcItem,drawRect);
1526 dis.itemData = infoPtr->items[iItem].lParam;
1529 * send the draw message
1531 SendMessageA( GetParent(hwnd), WM_DRAWITEM, (WPARAM)id, (LPARAM)&dis );
1533 else
1535 INT cx;
1536 INT cy;
1537 UINT uHorizAlign;
1538 RECT rcTemp;
1539 RECT rcImage;
1540 LOGFONTA logfont;
1541 HFONT hFont = 0;
1542 HFONT hOldFont = 0; /* stop uninitialized warning */
1544 INT nEscapement = 0; /* stop uninitialized warning */
1545 INT nOrientation = 0; /* stop uninitialized warning */
1546 INT iPointSize;
1548 /* used to center the icon and text in the tab */
1549 RECT rcText;
1550 INT center_offset;
1553 * Deflate the rectangle to acount for the padding
1555 if(lStyle & TCS_VERTICAL)
1556 InflateRect(drawRect, -infoPtr->uVItemPadding, -infoPtr->uHItemPadding);
1557 else
1558 InflateRect(drawRect, -infoPtr->uHItemPadding, -infoPtr->uVItemPadding);
1560 /* set rcImage to drawRect, we will use top & left in our ImageList_Draw call */
1561 rcImage = *drawRect;
1563 rcTemp = *drawRect;
1565 rcText.left = rcText.top = rcText.right = rcText.bottom = 0;
1568 * Setup for text output
1570 oldBkMode = SetBkMode(hdc, TRANSPARENT);
1571 SetTextColor(hdc, ((iItem == infoPtr->iHotTracked) | (infoPtr->items[iItem].dwState & TCIS_HIGHLIGHTED))?
1572 comctl32_color.clrHighlight : comctl32_color.clrBtnText);
1574 /* get the rectangle that the text fits in */
1575 DrawTextW(hdc, infoPtr->items[iItem].pszText, -1,
1576 &rcText, DT_CALCRECT);
1577 rcText.right += 4;
1579 * If not owner draw, then do the drawing ourselves.
1581 * Draw the icon.
1583 if (infoPtr->himl && (infoPtr->items[iItem].mask & TCIF_IMAGE))
1585 ImageList_GetIconSize(infoPtr->himl, &cx, &cy);
1587 if(lStyle & TCS_VERTICAL)
1588 center_offset = ((drawRect->bottom - drawRect->top) - (cy + infoPtr->uHItemPadding + (rcText.right - rcText.left))) / 2;
1589 else
1590 center_offset = ((drawRect->right - drawRect->left) - (cx + infoPtr->uHItemPadding + (rcText.right - rcText.left))) / 2;
1592 TRACE("for <%s>, c_o=%d, draw=(%ld,%ld)-(%ld,%ld), textlen=%ld\n",
1593 debugstr_w(infoPtr->items[iItem].pszText), center_offset,
1594 drawRect->left, drawRect->top, drawRect->right, drawRect->bottom,
1595 (rcText.right-rcText.left));
1597 if((lStyle & TCS_VERTICAL) && (lStyle & TCS_BOTTOM))
1599 rcImage.top = drawRect->top + center_offset;
1600 rcImage.left = drawRect->right - cx; /* if tab is TCS_VERTICAL and TCS_BOTTOM, the text is drawn from the */
1601 /* right side of the tab, but the image still uses the left as its x position */
1602 /* this keeps the image always drawn off of the same side of the tab */
1603 drawRect->top = rcImage.top + (cx + infoPtr->uHItemPadding);
1605 else if(lStyle & TCS_VERTICAL)
1607 rcImage.top = drawRect->bottom - cy - center_offset;
1608 rcImage.left--;
1609 drawRect->bottom = rcImage.top - infoPtr->uHItemPadding;
1611 else /* normal style, whether TCS_BOTTOM or not */
1613 rcImage.left = drawRect->left + center_offset + 3;
1614 drawRect->left = rcImage.left + cx + infoPtr->uHItemPadding;
1615 rcImage.top -= (lStyle & TCS_BOTTOM) ? 2 : 1;
1618 TRACE("drawing image=%d, left=%ld, top=%ld\n",
1619 infoPtr->items[iItem].iImage, rcImage.left, rcImage.top-1);
1620 ImageList_Draw
1622 infoPtr->himl,
1623 infoPtr->items[iItem].iImage,
1624 hdc,
1625 rcImage.left,
1626 rcImage.top,
1627 ILD_NORMAL
1629 } else /* no image, so just shift the drawRect borders around */
1631 if(lStyle & TCS_VERTICAL)
1633 center_offset = 0;
1635 currently the rcText rect is flawed because the rotated font does not
1636 often match the horizontal font. So leave this as 0
1637 ((drawRect->bottom - drawRect->top) - (rcText.right - rcText.left)) / 2;
1639 if(lStyle & TCS_BOTTOM)
1640 drawRect->top+=center_offset;
1641 else
1642 drawRect->bottom-=center_offset;
1644 else
1646 center_offset = ((drawRect->right - drawRect->left) - (rcText.right - rcText.left)) / 2;
1647 drawRect->left+=center_offset;
1651 /* Draw the text */
1652 if (lStyle & TCS_RIGHTJUSTIFY)
1653 uHorizAlign = DT_CENTER;
1654 else
1655 uHorizAlign = DT_LEFT;
1657 if(lStyle & TCS_VERTICAL) /* if we are vertical rotate the text and each character */
1659 if(lStyle & TCS_BOTTOM)
1661 nEscapement = -900;
1662 nOrientation = -900;
1664 else
1666 nEscapement = 900;
1667 nOrientation = 900;
1671 /* to get a font with the escapement and orientation we are looking for, we need to */
1672 /* call CreateFontIndirectA, which requires us to set the values of the logfont we pass in */
1673 if(lStyle & TCS_VERTICAL)
1675 if (!GetObjectA((infoPtr->hFont) ?
1676 infoPtr->hFont : GetStockObject(SYSTEM_FONT),
1677 sizeof(LOGFONTA),&logfont))
1679 iPointSize = 9;
1681 lstrcpyA(logfont.lfFaceName, "Arial");
1682 logfont.lfHeight = -MulDiv(iPointSize, GetDeviceCaps(hdc, LOGPIXELSY),
1683 72);
1684 logfont.lfWeight = FW_NORMAL;
1685 logfont.lfItalic = 0;
1686 logfont.lfUnderline = 0;
1687 logfont.lfStrikeOut = 0;
1690 logfont.lfEscapement = nEscapement;
1691 logfont.lfOrientation = nOrientation;
1692 hFont = CreateFontIndirectA(&logfont);
1693 hOldFont = SelectObject(hdc, hFont);
1696 if (lStyle & TCS_VERTICAL)
1698 ExtTextOutW(hdc,
1699 (lStyle & TCS_BOTTOM) ? drawRect->right : drawRect->left,
1700 (!(lStyle & TCS_BOTTOM)) ? drawRect->bottom : drawRect->top,
1701 ETO_CLIPPED,
1702 drawRect,
1703 infoPtr->items[iItem].pszText,
1704 lstrlenW(infoPtr->items[iItem].pszText),
1707 else
1709 DrawTextW
1711 hdc,
1712 infoPtr->items[iItem].pszText,
1713 lstrlenW(infoPtr->items[iItem].pszText),
1714 drawRect,
1715 uHorizAlign | DT_SINGLELINE
1719 /* clean things up */
1720 *drawRect = rcTemp; /* restore drawRect */
1722 if(lStyle & TCS_VERTICAL)
1724 SelectObject(hdc, hOldFont); /* restore the original font */
1725 if (hFont)
1726 DeleteObject(hFont);
1731 * Cleanup
1733 SetBkMode(hdc, oldBkMode);
1734 SelectObject(hdc, holdPen);
1735 DeleteObject( htextPen );
1738 /******************************************************************************
1739 * TAB_DrawItem
1741 * This method is used to draw a single tab into the tab control.
1743 static void TAB_DrawItem(
1744 HWND hwnd,
1745 HDC hdc,
1746 INT iItem)
1748 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
1749 LONG lStyle = GetWindowLongA(hwnd, GWL_STYLE);
1750 RECT itemRect;
1751 RECT selectedRect;
1752 BOOL isVisible;
1753 RECT r, fillRect, r1;
1754 INT clRight = 0;
1755 INT clBottom = 0;
1756 COLORREF bkgnd, corner;
1759 * Get the rectangle for the item.
1761 isVisible = TAB_InternalGetItemRect(hwnd,
1762 infoPtr,
1763 iItem,
1764 &itemRect,
1765 &selectedRect);
1767 if (isVisible)
1769 /* If you need to see what the control is doing,
1770 * then override these variables. They will change what
1771 * fill colors are used for filling the tabs, and the
1772 * corners when drawing the edge.
1774 bkgnd = comctl32_color.clrBtnFace;
1775 corner = comctl32_color.clrBtnFace;
1777 if (lStyle & TCS_BUTTONS)
1779 HBRUSH hbr = CreateSolidBrush (bkgnd);
1780 BOOL deleteBrush = TRUE;
1782 /* Get item rectangle */
1783 r = itemRect;
1785 /* Separators between flat buttons */
1786 if (lStyle & TCS_FLATBUTTONS)
1788 r1 = r;
1789 r1.right += (FLAT_BTN_SPACINGX -2);
1790 DrawEdge(hdc, &r1, EDGE_ETCHED, BF_RIGHT);
1793 if (iItem == infoPtr->iSelected)
1795 /* Background color */
1796 if (!((lStyle & TCS_OWNERDRAWFIXED) && infoPtr->fSizeSet))
1798 DeleteObject(hbr);
1799 hbr = GetSysColorBrush(COLOR_SCROLLBAR);
1801 SetTextColor(hdc, comctl32_color.clr3dFace);
1802 SetBkColor(hdc, comctl32_color.clr3dHilight);
1804 /* if COLOR_WINDOW happens to be the same as COLOR_3DHILIGHT
1805 * we better use 0x55aa bitmap brush to make scrollbar's background
1806 * look different from the window background.
1808 if (comctl32_color.clr3dHilight == comctl32_color.clrWindow)
1809 hbr = COMCTL32_hPattern55AABrush;
1811 deleteBrush = FALSE;
1814 /* Clear interior */
1815 FillRect(hdc, &r, hbr);
1817 DrawEdge(hdc, &r, EDGE_SUNKEN, BF_SOFT|BF_RECT);
1819 else /* ! selected */
1821 if (!(lStyle & TCS_FLATBUTTONS))
1823 /* Clear interior */
1824 FillRect(hdc, &r, hbr);
1826 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_RECT);
1830 /* Cleanup */
1831 if (deleteBrush) DeleteObject(hbr);
1833 else /* !TCS_BUTTONS */
1835 /* We draw a rectangle of different sizes depending on the selection
1836 * state. */
1837 if (iItem == infoPtr->iSelected) {
1838 RECT rect;
1839 GetClientRect (hwnd, &rect);
1840 clRight = rect.right;
1841 clBottom = rect.bottom;
1842 r = selectedRect;
1844 else
1845 r = itemRect;
1848 * Erase the background. (Delay it but setup rectangle.)
1849 * This is necessary when drawing the selected item since it is larger
1850 * than the others, it might overlap with stuff already drawn by the
1851 * other tabs
1853 fillRect = r;
1855 if(lStyle & TCS_VERTICAL)
1857 /* These are for adjusting the drawing of a Selected tab */
1858 /* The initial values are for the normal case of non-Selected */
1859 int ZZ = 1; /* Do not strech if selected */
1860 if (iItem == infoPtr->iSelected) {
1861 ZZ = 0;
1863 /* if leftmost draw the line longer */
1864 if(selectedRect.top == 0)
1865 fillRect.top += 2;
1866 /* if rightmost draw the line longer */
1867 if(selectedRect.bottom == clBottom)
1868 fillRect.bottom -= 2;
1871 if (lStyle & TCS_BOTTOM)
1873 /* Adjust both rectangles to match native */
1874 r.left += (1-ZZ);
1876 TRACE("<left> item=%d, fill=(%ld,%ld)-(%ld,%ld), edge=(%ld,%ld)-(%ld,%ld)\n",
1877 iItem,
1878 fillRect.left,fillRect.top,fillRect.right,fillRect.bottom,
1879 r.left,r.top,r.right,r.bottom);
1881 /* Clear interior */
1882 SetBkColor(hdc, bkgnd);
1883 ExtTextOutA(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
1885 /* Draw rectangular edge around tab */
1886 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_RIGHT|BF_TOP|BF_BOTTOM);
1888 /* Now erase the top corner and draw diagonal edge */
1889 SetBkColor(hdc, corner);
1890 r1.left = r.right - ROUND_CORNER_SIZE - 1;
1891 r1.top = r.top;
1892 r1.right = r.right;
1893 r1.bottom = r1.top + ROUND_CORNER_SIZE;
1894 ExtTextOutA(hdc, 0, 0, 2, &r1, NULL, 0, 0);
1895 r1.right--;
1896 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPLEFT);
1898 /* Now erase the bottom corner and draw diagonal edge */
1899 r1.left = r.right - ROUND_CORNER_SIZE - 1;
1900 r1.bottom = r.bottom;
1901 r1.right = r.right;
1902 r1.top = r1.bottom - ROUND_CORNER_SIZE;
1903 ExtTextOutA(hdc, 0, 0, 2, &r1, NULL, 0, 0);
1904 r1.right--;
1905 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMLEFT);
1907 if ((iItem == infoPtr->iSelected) && (selectedRect.top == 0)) {
1908 r1 = r;
1909 r1.right = r1.left;
1910 r1.left--;
1911 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_TOP);
1915 else
1917 /* Adjust both rectangles to match native */
1918 fillRect.right += (1-ZZ);
1920 TRACE("<left> item=%d, fill=(%ld,%ld)-(%ld,%ld), edge=(%ld,%ld)-(%ld,%ld)\n",
1921 iItem,
1922 fillRect.left,fillRect.top,fillRect.right,fillRect.bottom,
1923 r.left,r.top,r.right,r.bottom);
1925 /* Clear interior */
1926 SetBkColor(hdc, bkgnd);
1927 ExtTextOutA(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
1929 /* Draw rectangular edge around tab */
1930 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_TOP|BF_BOTTOM);
1932 /* Now erase the top corner and draw diagonal edge */
1933 SetBkColor(hdc, corner);
1934 r1.left = r.left;
1935 r1.top = r.top;
1936 r1.right = r1.left + ROUND_CORNER_SIZE + 1;
1937 r1.bottom = r1.top + ROUND_CORNER_SIZE;
1938 ExtTextOutA(hdc, 0, 0, 2, &r1, NULL, 0, 0);
1939 r1.left++;
1940 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPRIGHT);
1942 /* Now erase the bottom corner and draw diagonal edge */
1943 r1.left = r.left;
1944 r1.bottom = r.bottom;
1945 r1.right = r1.left + ROUND_CORNER_SIZE + 1;
1946 r1.top = r1.bottom - ROUND_CORNER_SIZE;
1947 ExtTextOutA(hdc, 0, 0, 2, &r1, NULL, 0, 0);
1948 r1.left++;
1949 DrawEdge(hdc, &r1, EDGE_SUNKEN, BF_DIAGONAL_ENDTOPLEFT);
1952 else /* ! TCS_VERTICAL */
1954 /* These are for adjusting the drawing of a Selected tab */
1955 /* The initial values are for the normal case of non-Selected */
1956 int ZZ = 1; /* Do not strech if selected */
1957 if (iItem == infoPtr->iSelected) {
1958 ZZ = 0;
1960 /* if leftmost draw the line longer */
1961 if(selectedRect.left == 0)
1962 fillRect.left += 2;
1963 /* if rightmost draw the line longer */
1964 if(selectedRect.right == clRight)
1965 fillRect.right -= 2;
1968 if (lStyle & TCS_BOTTOM)
1971 /* Adjust both rectangles to match native */
1972 fillRect.top--;
1973 fillRect.bottom--;
1974 r.bottom--;
1975 r.top -= ZZ;
1977 TRACE("<bottom> item=%d, fill=(%ld,%ld)-(%ld,%ld), edge=(%ld,%ld)-(%ld,%ld)\n",
1978 iItem,
1979 fillRect.left,fillRect.top,fillRect.right,fillRect.bottom,
1980 r.left,r.top,r.right,r.bottom);
1982 /* Clear interior */
1983 SetBkColor(hdc, bkgnd);
1984 ExtTextOutA(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
1986 /* Draw rectangular edge around tab */
1987 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_BOTTOM|BF_RIGHT);
1989 /* Now erase the righthand corner and draw diagonal edge */
1990 SetBkColor(hdc, corner);
1991 r1.left = r.right - ROUND_CORNER_SIZE;
1992 r1.bottom = r.bottom;
1993 r1.right = r.right;
1994 r1.top = r1.bottom - ROUND_CORNER_SIZE - 1;
1995 ExtTextOutA(hdc, 0, 0, 2, &r1, NULL, 0, 0);
1996 r1.bottom--;
1997 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMLEFT);
1999 /* Now erase the lefthand corner and draw diagonal edge */
2000 r1.left = r.left;
2001 r1.bottom = r.bottom;
2002 r1.right = r1.left + ROUND_CORNER_SIZE;
2003 r1.top = r1.bottom - ROUND_CORNER_SIZE - 1;
2004 ExtTextOutA(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2005 r1.bottom--;
2006 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPLEFT);
2008 if ((iItem == infoPtr->iSelected) && (selectedRect.left == 0)) {
2009 r1 = r;
2010 r1.bottom = r1.top;
2011 r1.top--;
2012 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_LEFT);
2016 else
2019 /* Adjust both rectangles to match native */
2020 fillRect.bottom += (1-ZZ);
2022 TRACE("<top> item=%d, fill=(%ld,%ld)-(%ld,%ld), edge=(%ld,%ld)-(%ld,%ld)\n",
2023 iItem,
2024 fillRect.left,fillRect.top,fillRect.right,fillRect.bottom,
2025 r.left,r.top,r.right,r.bottom);
2027 /* Clear interior */
2028 SetBkColor(hdc, bkgnd);
2029 ExtTextOutA(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2031 /* Draw rectangular edge around tab */
2032 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_TOP|BF_RIGHT);
2034 /* Now erase the righthand corner and draw diagonal edge */
2035 SetBkColor(hdc, corner);
2036 r1.left = r.right - ROUND_CORNER_SIZE;
2037 r1.top = r.top;
2038 r1.right = r.right;
2039 r1.bottom = r1.top + ROUND_CORNER_SIZE + 1;
2040 ExtTextOutA(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2041 r1.top++;
2042 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMRIGHT);
2044 /* Now erase the lefthand corner and draw diagonal edge */
2045 r1.left = r.left;
2046 r1.top = r.top;
2047 r1.right = r1.left + ROUND_CORNER_SIZE;
2048 r1.bottom = r1.top + ROUND_CORNER_SIZE + 1;
2049 ExtTextOutA(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2050 r1.top++;
2051 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPRIGHT);
2057 TAB_DumpItemInternal(infoPtr, iItem);
2059 /* This modifies r to be the text rectangle. */
2061 HFONT hOldFont = SelectObject(hdc, infoPtr->hFont);
2062 TAB_DrawItemInterior(hwnd, hdc, iItem, &r);
2063 SelectObject(hdc,hOldFont);
2066 /* Draw the focus rectangle */
2067 if (((lStyle & TCS_FOCUSNEVER) == 0) &&
2068 (GetFocus() == hwnd) &&
2069 (iItem == infoPtr->uFocus) )
2071 r = itemRect;
2072 InflateRect(&r, -1, -1);
2074 DrawFocusRect(hdc, &r);
2079 /******************************************************************************
2080 * TAB_DrawBorder
2082 * This method is used to draw the raised border around the tab control
2083 * "content" area.
2085 static void TAB_DrawBorder (HWND hwnd, HDC hdc)
2087 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2088 RECT rect;
2089 DWORD lStyle = GetWindowLongA(hwnd, GWL_STYLE);
2091 GetClientRect (hwnd, &rect);
2094 * Adjust for the style
2097 if (infoPtr->uNumItem)
2099 if ((lStyle & TCS_BOTTOM) && !(lStyle & TCS_VERTICAL))
2101 rect.bottom -= (infoPtr->tabHeight - 2) * infoPtr->uNumRows + 3;
2103 else if((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
2105 rect.right -= (infoPtr->tabHeight - 2) * infoPtr->uNumRows + 2;
2107 else if(lStyle & TCS_VERTICAL)
2109 rect.left += (infoPtr->tabHeight - 2) * infoPtr->uNumRows + 2;
2111 else /* not TCS_VERTICAL and not TCS_BOTTOM */
2113 rect.top += (infoPtr->tabHeight - 2) * infoPtr->uNumRows + 2;
2117 TRACE("border=(%ld,%ld)-(%ld,%ld)\n",
2118 rect.left, rect.top, rect.right, rect.bottom);
2120 DrawEdge(hdc, &rect, EDGE_RAISED, BF_SOFT|BF_RECT);
2123 /******************************************************************************
2124 * TAB_Refresh
2126 * This method repaints the tab control..
2128 static void TAB_Refresh (HWND hwnd, HDC hdc)
2130 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2131 HFONT hOldFont;
2132 INT i;
2134 if (!infoPtr->DoRedraw)
2135 return;
2137 hOldFont = SelectObject (hdc, infoPtr->hFont);
2139 if (GetWindowLongA(hwnd, GWL_STYLE) & TCS_BUTTONS)
2141 for (i = 0; i < infoPtr->uNumItem; i++)
2142 TAB_DrawItem (hwnd, hdc, i);
2144 else
2146 /* Draw all the non selected item first */
2147 for (i = 0; i < infoPtr->uNumItem; i++)
2149 if (i != infoPtr->iSelected)
2150 TAB_DrawItem (hwnd, hdc, i);
2153 /* Now, draw the border, draw it before the selected item
2154 * since the selected item overwrites part of the border. */
2155 TAB_DrawBorder (hwnd, hdc);
2157 /* Then, draw the selected item */
2158 TAB_DrawItem (hwnd, hdc, infoPtr->iSelected);
2160 /* If we haven't set the current focus yet, set it now.
2161 * Only happens when we first paint the tab controls */
2162 if (infoPtr->uFocus == -1)
2163 TAB_SetCurFocus(hwnd, infoPtr->iSelected);
2166 SelectObject (hdc, hOldFont);
2169 static DWORD
2170 TAB_GetRowCount (HWND hwnd )
2172 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2174 return infoPtr->uNumRows;
2177 static LRESULT
2178 TAB_SetRedraw (HWND hwnd, WPARAM wParam)
2180 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2182 infoPtr->DoRedraw=(BOOL) wParam;
2183 return 0;
2186 static LRESULT TAB_EraseBackground(
2187 HWND hwnd,
2188 HDC givenDC)
2190 HDC hdc;
2191 RECT clientRect;
2193 HBRUSH brush = CreateSolidBrush(comctl32_color.clrBtnFace);
2195 hdc = givenDC ? givenDC : GetDC(hwnd);
2197 GetClientRect(hwnd, &clientRect);
2199 FillRect(hdc, &clientRect, brush);
2201 if (givenDC==0)
2202 ReleaseDC(hwnd, hdc);
2204 DeleteObject(brush);
2206 return 0;
2209 /******************************************************************************
2210 * TAB_EnsureSelectionVisible
2212 * This method will make sure that the current selection is completely
2213 * visible by scrolling until it is.
2215 static void TAB_EnsureSelectionVisible(
2216 HWND hwnd,
2217 TAB_INFO* infoPtr)
2219 INT iSelected = infoPtr->iSelected;
2220 LONG lStyle = GetWindowLongA(hwnd, GWL_STYLE);
2221 INT iOrigLeftmostVisible = infoPtr->leftmostVisible;
2223 /* set the items row to the bottommost row or topmost row depending on
2224 * style */
2225 if ((infoPtr->uNumRows > 1) && !(lStyle & TCS_BUTTONS))
2227 INT newselected;
2228 INT iTargetRow;
2230 if(lStyle & TCS_VERTICAL)
2231 newselected = infoPtr->items[iSelected].rect.left;
2232 else
2233 newselected = infoPtr->items[iSelected].rect.top;
2235 /* the target row is always (number of rows - 1)
2236 as row 0 is furthest from the clientRect */
2237 iTargetRow = infoPtr->uNumRows - 1;
2239 if (newselected != iTargetRow)
2241 INT i;
2242 if(lStyle & TCS_VERTICAL)
2244 for (i=0; i < infoPtr->uNumItem; i++)
2246 /* move everything in the row of the selected item to the iTargetRow */
2247 if (infoPtr->items[i].rect.left == newselected )
2248 infoPtr->items[i].rect.left = iTargetRow;
2249 else
2251 if (infoPtr->items[i].rect.left > newselected)
2252 infoPtr->items[i].rect.left-=1;
2256 else
2258 for (i=0; i < infoPtr->uNumItem; i++)
2260 if (infoPtr->items[i].rect.top == newselected )
2261 infoPtr->items[i].rect.top = iTargetRow;
2262 else
2264 if (infoPtr->items[i].rect.top > newselected)
2265 infoPtr->items[i].rect.top-=1;
2269 TAB_RecalcHotTrack(hwnd, NULL, NULL, NULL);
2274 * Do the trivial cases first.
2276 if ( (!infoPtr->needsScrolling) ||
2277 (infoPtr->hwndUpDown==0) || (lStyle & TCS_VERTICAL))
2278 return;
2280 if (infoPtr->leftmostVisible >= iSelected)
2282 infoPtr->leftmostVisible = iSelected;
2284 else
2286 RECT r;
2287 INT width, i;
2289 /* Calculate the part of the client area that is visible */
2290 GetClientRect(hwnd, &r);
2291 width = r.right;
2293 GetClientRect(infoPtr->hwndUpDown, &r);
2294 width -= r.right;
2296 if ((infoPtr->items[iSelected].rect.right -
2297 infoPtr->items[iSelected].rect.left) >= width )
2299 /* Special case: width of selected item is greater than visible
2300 * part of control.
2302 infoPtr->leftmostVisible = iSelected;
2304 else
2306 for (i = infoPtr->leftmostVisible; i < infoPtr->uNumItem; i++)
2308 if ((infoPtr->items[iSelected].rect.right -
2309 infoPtr->items[i].rect.left) < width)
2310 break;
2312 infoPtr->leftmostVisible = i;
2316 if (infoPtr->leftmostVisible != iOrigLeftmostVisible)
2317 TAB_RecalcHotTrack(hwnd, NULL, NULL, NULL);
2319 SendMessageA(infoPtr->hwndUpDown, UDM_SETPOS, 0,
2320 MAKELONG(infoPtr->leftmostVisible, 0));
2323 /******************************************************************************
2324 * TAB_InvalidateTabArea
2326 * This method will invalidate the portion of the control that contains the
2327 * tabs. It is called when the state of the control changes and needs
2328 * to be redisplayed
2330 static void TAB_InvalidateTabArea(
2331 HWND hwnd,
2332 TAB_INFO* infoPtr)
2334 RECT clientRect, r;
2335 DWORD lStyle = GetWindowLongA(hwnd, GWL_STYLE);
2336 INT lastRow = infoPtr->uNumRows - 1;
2338 if (lastRow < 0) return;
2340 GetClientRect(hwnd, &clientRect);
2342 if ((lStyle & TCS_BOTTOM) && !(lStyle & TCS_VERTICAL))
2344 clientRect.top = clientRect.bottom -
2345 infoPtr->tabHeight -
2346 lastRow * (infoPtr->tabHeight - 2) -
2347 ((lStyle & TCS_BUTTONS) ? lastRow * BUTTON_SPACINGY : 0) - 3;
2349 else if((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
2351 clientRect.left = clientRect.right - infoPtr->tabHeight -
2352 lastRow * (infoPtr->tabHeight - 2) -
2353 ((lStyle & TCS_BUTTONS) ? lastRow * BUTTON_SPACINGY : 0) - 2;
2355 else if(lStyle & TCS_VERTICAL)
2357 clientRect.right = clientRect.left + infoPtr->tabHeight +
2358 lastRow * (infoPtr->tabHeight - 2) -
2359 ((lStyle & TCS_BUTTONS) ? lastRow * BUTTON_SPACINGY : 0) + 2;
2362 else
2364 clientRect.bottom = clientRect.top + infoPtr->tabHeight +
2365 lastRow * (infoPtr->tabHeight - 2) +
2366 ((lStyle & TCS_BUTTONS) ? lastRow * BUTTON_SPACINGY : 0) + 2;
2369 /* Punch out the updown control */
2370 if (infoPtr->needsScrolling && (clientRect.right > 0)) {
2371 GetClientRect(infoPtr->hwndUpDown, &r);
2372 clientRect.right = clientRect.right - (r.right - r.left);
2375 TRACE("invalidate (%ld,%ld)-(%ld,%ld)\n",
2376 clientRect.left,clientRect.top,
2377 clientRect.right,clientRect.bottom);
2379 InvalidateRect(hwnd, &clientRect, TRUE);
2382 static LRESULT
2383 TAB_Paint (HWND hwnd, WPARAM wParam)
2385 HDC hdc;
2386 PAINTSTRUCT ps;
2388 hdc = wParam== 0 ? BeginPaint (hwnd, &ps) : (HDC)wParam;
2390 TRACE("erase %d, rect=(%ld,%ld)-(%ld,%ld)\n",
2391 ps.fErase,
2392 ps.rcPaint.left,ps.rcPaint.top,ps.rcPaint.right,ps.rcPaint.bottom);
2394 if (ps.fErase)
2395 TAB_EraseBackground (hwnd, hdc);
2397 TAB_Refresh (hwnd, hdc);
2399 if(!wParam)
2400 EndPaint (hwnd, &ps);
2402 return 0;
2405 static LRESULT
2406 TAB_InsertItemA (HWND hwnd, WPARAM wParam, LPARAM lParam)
2408 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2409 TCITEMA *pti;
2410 INT iItem;
2411 RECT rect;
2413 GetClientRect (hwnd, &rect);
2414 TRACE("Rect: %p T %li, L %li, B %li, R %li\n", hwnd,
2415 rect.top, rect.left, rect.bottom, rect.right);
2417 pti = (TCITEMA *)lParam;
2418 iItem = (INT)wParam;
2420 if (iItem < 0) return -1;
2421 if (iItem > infoPtr->uNumItem)
2422 iItem = infoPtr->uNumItem;
2424 TAB_DumpItemExternalA(pti, iItem);
2427 if (infoPtr->uNumItem == 0) {
2428 infoPtr->items = Alloc (sizeof (TAB_ITEM));
2429 infoPtr->uNumItem++;
2430 infoPtr->iSelected = 0;
2432 else {
2433 TAB_ITEM *oldItems = infoPtr->items;
2435 infoPtr->uNumItem++;
2436 infoPtr->items = Alloc (sizeof (TAB_ITEM) * infoPtr->uNumItem);
2438 /* pre insert copy */
2439 if (iItem > 0) {
2440 memcpy (&infoPtr->items[0], &oldItems[0],
2441 iItem * sizeof(TAB_ITEM));
2444 /* post insert copy */
2445 if (iItem < infoPtr->uNumItem - 1) {
2446 memcpy (&infoPtr->items[iItem+1], &oldItems[iItem],
2447 (infoPtr->uNumItem - iItem - 1) * sizeof(TAB_ITEM));
2451 if (iItem <= infoPtr->iSelected)
2452 infoPtr->iSelected++;
2454 Free (oldItems);
2457 infoPtr->items[iItem].mask = pti->mask;
2458 if (pti->mask & TCIF_TEXT)
2459 Str_SetPtrAtoW (&infoPtr->items[iItem].pszText, pti->pszText);
2461 if (pti->mask & TCIF_IMAGE)
2462 infoPtr->items[iItem].iImage = pti->iImage;
2464 if (pti->mask & TCIF_PARAM)
2465 infoPtr->items[iItem].lParam = pti->lParam;
2467 TAB_SetItemBounds(hwnd);
2468 if (infoPtr->uNumItem > 1)
2469 TAB_InvalidateTabArea(hwnd, infoPtr);
2470 else
2471 InvalidateRect(hwnd, NULL, TRUE);
2473 TRACE("[%p]: added item %d %s\n",
2474 hwnd, iItem, debugstr_w(infoPtr->items[iItem].pszText));
2476 return iItem;
2480 static LRESULT
2481 TAB_InsertItemW (HWND hwnd, WPARAM wParam, LPARAM lParam)
2483 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2484 TCITEMW *pti;
2485 INT iItem;
2486 RECT rect;
2488 GetClientRect (hwnd, &rect);
2489 TRACE("Rect: %p T %li, L %li, B %li, R %li\n", hwnd,
2490 rect.top, rect.left, rect.bottom, rect.right);
2492 pti = (TCITEMW *)lParam;
2493 iItem = (INT)wParam;
2495 if (iItem < 0) return -1;
2496 if (iItem > infoPtr->uNumItem)
2497 iItem = infoPtr->uNumItem;
2499 TAB_DumpItemExternalW(pti, iItem);
2501 if (infoPtr->uNumItem == 0) {
2502 infoPtr->items = Alloc (sizeof (TAB_ITEM));
2503 infoPtr->uNumItem++;
2504 infoPtr->iSelected = 0;
2506 else {
2507 TAB_ITEM *oldItems = infoPtr->items;
2509 infoPtr->uNumItem++;
2510 infoPtr->items = Alloc (sizeof (TAB_ITEM) * infoPtr->uNumItem);
2512 /* pre insert copy */
2513 if (iItem > 0) {
2514 memcpy (&infoPtr->items[0], &oldItems[0],
2515 iItem * sizeof(TAB_ITEM));
2518 /* post insert copy */
2519 if (iItem < infoPtr->uNumItem - 1) {
2520 memcpy (&infoPtr->items[iItem+1], &oldItems[iItem],
2521 (infoPtr->uNumItem - iItem - 1) * sizeof(TAB_ITEM));
2525 if (iItem <= infoPtr->iSelected)
2526 infoPtr->iSelected++;
2528 Free (oldItems);
2531 infoPtr->items[iItem].mask = pti->mask;
2532 if (pti->mask & TCIF_TEXT)
2533 Str_SetPtrW (&infoPtr->items[iItem].pszText, pti->pszText);
2535 if (pti->mask & TCIF_IMAGE)
2536 infoPtr->items[iItem].iImage = pti->iImage;
2538 if (pti->mask & TCIF_PARAM)
2539 infoPtr->items[iItem].lParam = pti->lParam;
2541 TAB_SetItemBounds(hwnd);
2542 if (infoPtr->uNumItem > 1)
2543 TAB_InvalidateTabArea(hwnd, infoPtr);
2544 else
2545 InvalidateRect(hwnd, NULL, TRUE);
2547 TRACE("[%p]: added item %d %s\n",
2548 hwnd, iItem, debugstr_w(infoPtr->items[iItem].pszText));
2550 return iItem;
2554 static LRESULT
2555 TAB_SetItemSize (HWND hwnd, WPARAM wParam, LPARAM lParam)
2557 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2558 LONG lStyle = GetWindowLongA(hwnd, GWL_STYLE);
2559 LONG lResult = 0;
2561 TRACE("\n");
2562 if ((lStyle & TCS_FIXEDWIDTH) || (lStyle & TCS_OWNERDRAWFIXED))
2564 lResult = MAKELONG(infoPtr->tabWidth, infoPtr->tabHeight);
2565 /* UNDOCUMENTED: If requested Width or Height is 0 this means that program wants to use default. */
2566 if (LOWORD(lParam)) infoPtr->tabWidth = max((INT)LOWORD(lParam), infoPtr->tabMinWidth);
2567 if (HIWORD(lParam)) infoPtr->tabHeight = (INT)HIWORD(lParam);
2568 TRACE("was h=%d,w=%d, now h=%d,w=%d\n",
2569 HIWORD(lResult), LOWORD(lResult),
2570 infoPtr->tabHeight, infoPtr->tabWidth);
2572 infoPtr->fSizeSet = TRUE;
2574 return lResult;
2577 static LRESULT
2578 TAB_SetMinTabWidth (HWND hwnd, LPARAM lParam)
2580 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2581 INT cx = (INT)lParam;
2582 INT oldcx;
2584 if (infoPtr) {
2585 oldcx = infoPtr->tabMinWidth;
2586 infoPtr->tabMinWidth = (cx==-1)?DEFAULT_TAB_WIDTH:cx;
2587 } else
2588 return 0;
2590 return oldcx;
2593 static LRESULT
2594 TAB_HighlightItem (HWND hwnd, WPARAM wParam, LPARAM lParam)
2596 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2597 INT iItem = (INT)wParam;
2598 BOOL fHighlight = (BOOL)LOWORD(lParam);
2600 if ((infoPtr) && (iItem>=0) && (iItem<infoPtr->uNumItem)) {
2601 if (fHighlight)
2602 infoPtr->items[iItem].dwState |= TCIS_HIGHLIGHTED;
2603 else
2604 infoPtr->items[iItem].dwState &= ~TCIS_HIGHLIGHTED;
2605 } else
2606 return FALSE;
2608 return TRUE;
2611 static LRESULT
2612 TAB_SetItemA (HWND hwnd, WPARAM wParam, LPARAM lParam)
2614 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2615 TCITEMA *tabItem;
2616 TAB_ITEM *wineItem;
2617 INT iItem;
2619 iItem = (INT)wParam;
2620 tabItem = (LPTCITEMA)lParam;
2622 TRACE("%d %p\n", iItem, tabItem);
2623 if ((iItem<0) || (iItem>=infoPtr->uNumItem)) return FALSE;
2625 TAB_DumpItemExternalA(tabItem, iItem);
2627 wineItem = &infoPtr->items[iItem];
2629 if (tabItem->mask & TCIF_IMAGE)
2630 wineItem->iImage = tabItem->iImage;
2632 if (tabItem->mask & TCIF_PARAM)
2633 wineItem->lParam = tabItem->lParam;
2635 if (tabItem->mask & TCIF_RTLREADING)
2636 FIXME("TCIF_RTLREADING\n");
2638 if (tabItem->mask & TCIF_STATE)
2639 wineItem->dwState = tabItem->dwState;
2641 if (tabItem->mask & TCIF_TEXT)
2642 Str_SetPtrAtoW(&wineItem->pszText, tabItem->pszText);
2644 /* Update and repaint tabs */
2645 TAB_SetItemBounds(hwnd);
2646 TAB_InvalidateTabArea(hwnd,infoPtr);
2648 return TRUE;
2652 static LRESULT
2653 TAB_SetItemW (HWND hwnd, WPARAM wParam, LPARAM lParam)
2655 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2656 TCITEMW *tabItem;
2657 TAB_ITEM *wineItem;
2658 INT iItem;
2660 iItem = (INT)wParam;
2661 tabItem = (LPTCITEMW)lParam;
2663 TRACE("%d %p\n", iItem, tabItem);
2664 if ((iItem<0) || (iItem>=infoPtr->uNumItem)) return FALSE;
2666 TAB_DumpItemExternalW(tabItem, iItem);
2668 wineItem = &infoPtr->items[iItem];
2670 if (tabItem->mask & TCIF_IMAGE)
2671 wineItem->iImage = tabItem->iImage;
2673 if (tabItem->mask & TCIF_PARAM)
2674 wineItem->lParam = tabItem->lParam;
2676 if (tabItem->mask & TCIF_RTLREADING)
2677 FIXME("TCIF_RTLREADING\n");
2679 if (tabItem->mask & TCIF_STATE)
2680 wineItem->dwState = tabItem->dwState;
2682 if (tabItem->mask & TCIF_TEXT)
2683 Str_SetPtrW(&wineItem->pszText, tabItem->pszText);
2685 /* Update and repaint tabs */
2686 TAB_SetItemBounds(hwnd);
2687 TAB_InvalidateTabArea(hwnd,infoPtr);
2689 return TRUE;
2693 static LRESULT
2694 TAB_GetItemCount (HWND hwnd, WPARAM wParam, LPARAM lParam)
2696 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2698 return infoPtr->uNumItem;
2702 static LRESULT
2703 TAB_GetItemA (HWND hwnd, WPARAM wParam, LPARAM lParam)
2705 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2706 TCITEMA *tabItem;
2707 TAB_ITEM *wineItem;
2708 INT iItem;
2710 iItem = (INT)wParam;
2711 tabItem = (LPTCITEMA)lParam;
2712 TRACE("\n");
2713 if ((iItem<0) || (iItem>=infoPtr->uNumItem))
2714 return FALSE;
2716 wineItem = &infoPtr->items[iItem];
2718 if (tabItem->mask & TCIF_IMAGE)
2719 tabItem->iImage = wineItem->iImage;
2721 if (tabItem->mask & TCIF_PARAM)
2722 tabItem->lParam = wineItem->lParam;
2724 if (tabItem->mask & TCIF_RTLREADING)
2725 FIXME("TCIF_RTLREADING\n");
2727 if (tabItem->mask & TCIF_STATE)
2728 tabItem->dwState = wineItem->dwState;
2730 if (tabItem->mask & TCIF_TEXT)
2731 Str_GetPtrWtoA (wineItem->pszText, tabItem->pszText, tabItem->cchTextMax);
2733 TAB_DumpItemExternalA(tabItem, iItem);
2735 return TRUE;
2739 static LRESULT
2740 TAB_GetItemW (HWND hwnd, WPARAM wParam, LPARAM lParam)
2742 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2743 TCITEMW *tabItem;
2744 TAB_ITEM *wineItem;
2745 INT iItem;
2747 iItem = (INT)wParam;
2748 tabItem = (LPTCITEMW)lParam;
2749 TRACE("\n");
2750 if ((iItem<0) || (iItem>=infoPtr->uNumItem))
2751 return FALSE;
2753 wineItem=& infoPtr->items[iItem];
2755 if (tabItem->mask & TCIF_IMAGE)
2756 tabItem->iImage = wineItem->iImage;
2758 if (tabItem->mask & TCIF_PARAM)
2759 tabItem->lParam = wineItem->lParam;
2761 if (tabItem->mask & TCIF_RTLREADING)
2762 FIXME("TCIF_RTLREADING\n");
2764 if (tabItem->mask & TCIF_STATE)
2765 tabItem->dwState = wineItem->dwState;
2767 if (tabItem->mask & TCIF_TEXT)
2768 Str_GetPtrW (wineItem->pszText, tabItem->pszText, tabItem->cchTextMax);
2770 TAB_DumpItemExternalW(tabItem, iItem);
2772 return TRUE;
2776 static LRESULT
2777 TAB_DeleteItem (HWND hwnd, WPARAM wParam, LPARAM lParam)
2779 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2780 INT iItem = (INT) wParam;
2781 BOOL bResult = FALSE;
2783 if ((iItem >= 0) && (iItem < infoPtr->uNumItem))
2785 TAB_ITEM *oldItems = infoPtr->items;
2787 infoPtr->uNumItem--;
2788 infoPtr->items = Alloc(sizeof (TAB_ITEM) * infoPtr->uNumItem);
2790 if (iItem > 0)
2791 memcpy(&infoPtr->items[0], &oldItems[0], iItem * sizeof(TAB_ITEM));
2793 if (iItem < infoPtr->uNumItem)
2794 memcpy(&infoPtr->items[iItem], &oldItems[iItem + 1],
2795 (infoPtr->uNumItem - iItem) * sizeof(TAB_ITEM));
2797 Free(oldItems);
2799 /* Readjust the selected index */
2800 if ((iItem == infoPtr->iSelected) && (iItem > 0))
2801 infoPtr->iSelected--;
2803 if (iItem < infoPtr->iSelected)
2804 infoPtr->iSelected--;
2806 if (infoPtr->uNumItem == 0)
2807 infoPtr->iSelected = -1;
2809 /* Reposition and repaint tabs */
2810 TAB_SetItemBounds(hwnd);
2811 TAB_InvalidateTabArea(hwnd,infoPtr);
2813 bResult = TRUE;
2816 return bResult;
2819 static LRESULT
2820 TAB_DeleteAllItems (HWND hwnd, WPARAM wParam, LPARAM lParam)
2822 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2824 Free (infoPtr->items);
2825 infoPtr->uNumItem = 0;
2826 infoPtr->iSelected = -1;
2827 if (infoPtr->iHotTracked >= 0)
2828 KillTimer(hwnd, TAB_HOTTRACK_TIMER);
2829 infoPtr->iHotTracked = -1;
2831 TAB_SetItemBounds(hwnd);
2832 TAB_InvalidateTabArea(hwnd,infoPtr);
2833 return TRUE;
2837 static LRESULT
2838 TAB_GetFont (HWND hwnd, WPARAM wParam, LPARAM lParam)
2840 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2842 TRACE("\n");
2843 return (LRESULT)infoPtr->hFont;
2846 static LRESULT
2847 TAB_SetFont (HWND hwnd, WPARAM wParam, LPARAM lParam)
2850 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2852 TRACE("%x %lx\n",wParam, lParam);
2854 infoPtr->hFont = (HFONT)wParam;
2856 TAB_SetItemBounds(hwnd);
2858 TAB_InvalidateTabArea(hwnd, infoPtr);
2860 return 0;
2864 static LRESULT
2865 TAB_GetImageList (HWND hwnd, WPARAM wParam, LPARAM lParam)
2867 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2869 TRACE("\n");
2870 return (LRESULT)infoPtr->himl;
2873 static LRESULT
2874 TAB_SetImageList (HWND hwnd, WPARAM wParam, LPARAM lParam)
2876 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2877 HIMAGELIST himlPrev;
2879 TRACE("\n");
2880 himlPrev = infoPtr->himl;
2881 infoPtr->himl= (HIMAGELIST)lParam;
2882 return (LRESULT)himlPrev;
2885 static LRESULT
2886 TAB_GetUnicodeFormat (HWND hwnd)
2888 TAB_INFO *infoPtr = TAB_GetInfoPtr (hwnd);
2889 return infoPtr->bUnicode;
2892 static LRESULT
2893 TAB_SetUnicodeFormat (HWND hwnd, WPARAM wParam)
2895 TAB_INFO *infoPtr = TAB_GetInfoPtr (hwnd);
2896 BOOL bTemp = infoPtr->bUnicode;
2898 infoPtr->bUnicode = (BOOL)wParam;
2900 return bTemp;
2903 static LRESULT
2904 TAB_Size (HWND hwnd, WPARAM wParam, LPARAM lParam)
2907 /* I'm not really sure what the following code was meant to do.
2908 This is what it is doing:
2909 When WM_SIZE is sent with SIZE_RESTORED, the control
2910 gets positioned in the top left corner.
2912 RECT parent_rect;
2913 HWND parent;
2914 UINT uPosFlags,cx,cy;
2916 uPosFlags=0;
2917 if (!wParam) {
2918 parent = GetParent (hwnd);
2919 GetClientRect(parent, &parent_rect);
2920 cx=LOWORD (lParam);
2921 cy=HIWORD (lParam);
2922 if (GetWindowLongA(hwnd, GWL_STYLE) & CCS_NORESIZE)
2923 uPosFlags |= (SWP_NOSIZE | SWP_NOMOVE);
2925 SetWindowPos (hwnd, 0, parent_rect.left, parent_rect.top,
2926 cx, cy, uPosFlags | SWP_NOZORDER);
2927 } else {
2928 FIXME("WM_SIZE flag %x %lx not handled\n", wParam, lParam);
2929 } */
2931 /* Recompute the size/position of the tabs. */
2932 TAB_SetItemBounds (hwnd);
2934 /* Force a repaint of the control. */
2935 InvalidateRect(hwnd, NULL, TRUE);
2937 return 0;
2941 static LRESULT
2942 TAB_Create (HWND hwnd, WPARAM wParam, LPARAM lParam)
2944 TAB_INFO *infoPtr;
2945 TEXTMETRICA fontMetrics;
2946 HDC hdc;
2947 HFONT hOldFont;
2948 DWORD dwStyle;
2950 infoPtr = (TAB_INFO *)Alloc (sizeof(TAB_INFO));
2952 SetWindowLongA(hwnd, 0, (DWORD)infoPtr);
2954 infoPtr->uNumItem = 0;
2955 infoPtr->uNumRows = 0;
2956 infoPtr->uHItemPadding = 6;
2957 infoPtr->uVItemPadding = 3;
2958 infoPtr->hFont = 0;
2959 infoPtr->items = 0;
2960 infoPtr->hcurArrow = LoadCursorA (0, (LPSTR)IDC_ARROW);
2961 infoPtr->iSelected = -1;
2962 infoPtr->iHotTracked = -1;
2963 infoPtr->uFocus = -1;
2964 infoPtr->hwndToolTip = 0;
2965 infoPtr->DoRedraw = TRUE;
2966 infoPtr->needsScrolling = FALSE;
2967 infoPtr->hwndUpDown = 0;
2968 infoPtr->leftmostVisible = 0;
2969 infoPtr->fSizeSet = FALSE;
2970 infoPtr->bUnicode = IsWindowUnicode (hwnd);
2972 TRACE("Created tab control, hwnd [%p]\n", hwnd);
2974 /* The tab control always has the WS_CLIPSIBLINGS style. Even
2975 if you don't specify it in CreateWindow. This is necessary in
2976 order for paint to work correctly. This follows windows behaviour. */
2977 dwStyle = GetWindowLongA(hwnd, GWL_STYLE);
2978 SetWindowLongA(hwnd, GWL_STYLE, dwStyle|WS_CLIPSIBLINGS);
2980 if (dwStyle & TCS_TOOLTIPS) {
2981 /* Create tooltip control */
2982 infoPtr->hwndToolTip =
2983 CreateWindowExA (0, TOOLTIPS_CLASSA, NULL, 0,
2984 CW_USEDEFAULT, CW_USEDEFAULT,
2985 CW_USEDEFAULT, CW_USEDEFAULT,
2986 hwnd, 0, 0, 0);
2988 /* Send NM_TOOLTIPSCREATED notification */
2989 if (infoPtr->hwndToolTip) {
2990 NMTOOLTIPSCREATED nmttc;
2992 nmttc.hdr.hwndFrom = hwnd;
2993 nmttc.hdr.idFrom = GetWindowLongA(hwnd, GWL_ID);
2994 nmttc.hdr.code = NM_TOOLTIPSCREATED;
2995 nmttc.hwndToolTips = infoPtr->hwndToolTip;
2997 SendMessageA (GetParent (hwnd), WM_NOTIFY,
2998 (WPARAM)GetWindowLongA(hwnd, GWL_ID), (LPARAM)&nmttc);
3003 * We need to get text information so we need a DC and we need to select
3004 * a font.
3006 hdc = GetDC(hwnd);
3007 hOldFont = SelectObject (hdc, GetStockObject (SYSTEM_FONT));
3009 /* Use the system font to determine the initial height of a tab. */
3010 GetTextMetricsA(hdc, &fontMetrics);
3013 * Make sure there is enough space for the letters + growing the
3014 * selected item + extra space for the selected item.
3016 infoPtr->tabHeight = fontMetrics.tmHeight + SELECTED_TAB_OFFSET +
3017 ((dwStyle & TCS_BUTTONS) ? 2 : 1) *
3018 infoPtr->uVItemPadding;
3020 /* Initialize the width of a tab. */
3021 infoPtr->tabWidth = DEFAULT_TAB_WIDTH;
3022 /* The minimum width is the default width at creation */
3023 infoPtr->tabMinWidth = DEFAULT_TAB_WIDTH;
3025 TRACE("tabH=%d, tabW=%d\n", infoPtr->tabHeight, infoPtr->tabWidth);
3027 SelectObject (hdc, hOldFont);
3028 ReleaseDC(hwnd, hdc);
3030 return 0;
3033 static LRESULT
3034 TAB_Destroy (HWND hwnd, WPARAM wParam, LPARAM lParam)
3036 TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
3037 INT iItem;
3039 if (!infoPtr)
3040 return 0;
3042 if (infoPtr->items) {
3043 for (iItem = 0; iItem < infoPtr->uNumItem; iItem++) {
3044 if (infoPtr->items[iItem].pszText)
3045 Free (infoPtr->items[iItem].pszText);
3047 Free (infoPtr->items);
3050 if (infoPtr->hwndToolTip)
3051 DestroyWindow (infoPtr->hwndToolTip);
3053 if (infoPtr->hwndUpDown)
3054 DestroyWindow(infoPtr->hwndUpDown);
3056 if (infoPtr->iHotTracked >= 0)
3057 KillTimer(hwnd, TAB_HOTTRACK_TIMER);
3059 Free (infoPtr);
3060 SetWindowLongA(hwnd, 0, 0);
3061 return 0;
3064 static LRESULT WINAPI
3065 TAB_WindowProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
3068 TRACE("hwnd=%p msg=%x wParam=%x lParam=%lx\n", hwnd, uMsg, wParam, lParam);
3069 if (!TAB_GetInfoPtr(hwnd) && (uMsg != WM_CREATE))
3070 return DefWindowProcA (hwnd, uMsg, wParam, lParam);
3072 switch (uMsg)
3074 case TCM_GETIMAGELIST:
3075 return TAB_GetImageList (hwnd, wParam, lParam);
3077 case TCM_SETIMAGELIST:
3078 return TAB_SetImageList (hwnd, wParam, lParam);
3080 case TCM_GETITEMCOUNT:
3081 return TAB_GetItemCount (hwnd, wParam, lParam);
3083 case TCM_GETITEMA:
3084 return TAB_GetItemA (hwnd, wParam, lParam);
3086 case TCM_GETITEMW:
3087 return TAB_GetItemW (hwnd, wParam, lParam);
3089 case TCM_SETITEMA:
3090 return TAB_SetItemA (hwnd, wParam, lParam);
3092 case TCM_SETITEMW:
3093 return TAB_SetItemW (hwnd, wParam, lParam);
3095 case TCM_DELETEITEM:
3096 return TAB_DeleteItem (hwnd, wParam, lParam);
3098 case TCM_DELETEALLITEMS:
3099 return TAB_DeleteAllItems (hwnd, wParam, lParam);
3101 case TCM_GETITEMRECT:
3102 return TAB_GetItemRect (hwnd, wParam, lParam);
3104 case TCM_GETCURSEL:
3105 return TAB_GetCurSel (hwnd);
3107 case TCM_HITTEST:
3108 return TAB_HitTest (hwnd, wParam, lParam);
3110 case TCM_SETCURSEL:
3111 return TAB_SetCurSel (hwnd, wParam);
3113 case TCM_INSERTITEMA:
3114 return TAB_InsertItemA (hwnd, wParam, lParam);
3116 case TCM_INSERTITEMW:
3117 return TAB_InsertItemW (hwnd, wParam, lParam);
3119 case TCM_SETITEMEXTRA:
3120 FIXME("Unimplemented msg TCM_SETITEMEXTRA\n");
3121 return 0;
3123 case TCM_ADJUSTRECT:
3124 return TAB_AdjustRect (hwnd, (BOOL)wParam, (LPRECT)lParam);
3126 case TCM_SETITEMSIZE:
3127 return TAB_SetItemSize (hwnd, wParam, lParam);
3129 case TCM_REMOVEIMAGE:
3130 FIXME("Unimplemented msg TCM_REMOVEIMAGE\n");
3131 return 0;
3133 case TCM_SETPADDING:
3134 return TAB_SetPadding (hwnd, wParam, lParam);
3136 case TCM_GETROWCOUNT:
3137 return TAB_GetRowCount(hwnd);
3139 case TCM_GETUNICODEFORMAT:
3140 return TAB_GetUnicodeFormat (hwnd);
3142 case TCM_SETUNICODEFORMAT:
3143 return TAB_SetUnicodeFormat (hwnd, wParam);
3145 case TCM_HIGHLIGHTITEM:
3146 return TAB_HighlightItem (hwnd, wParam, lParam);
3148 case TCM_GETTOOLTIPS:
3149 return TAB_GetToolTips (hwnd, wParam, lParam);
3151 case TCM_SETTOOLTIPS:
3152 return TAB_SetToolTips (hwnd, wParam, lParam);
3154 case TCM_GETCURFOCUS:
3155 return TAB_GetCurFocus (hwnd);
3157 case TCM_SETCURFOCUS:
3158 return TAB_SetCurFocus (hwnd, wParam);
3160 case TCM_SETMINTABWIDTH:
3161 return TAB_SetMinTabWidth(hwnd, lParam);
3163 case TCM_DESELECTALL:
3164 FIXME("Unimplemented msg TCM_DESELECTALL\n");
3165 return 0;
3167 case TCM_GETEXTENDEDSTYLE:
3168 FIXME("Unimplemented msg TCM_GETEXTENDEDSTYLE\n");
3169 return 0;
3171 case TCM_SETEXTENDEDSTYLE:
3172 FIXME("Unimplemented msg TCM_SETEXTENDEDSTYLE\n");
3173 return 0;
3175 case WM_GETFONT:
3176 return TAB_GetFont (hwnd, wParam, lParam);
3178 case WM_SETFONT:
3179 return TAB_SetFont (hwnd, wParam, lParam);
3181 case WM_CREATE:
3182 return TAB_Create (hwnd, wParam, lParam);
3184 case WM_NCDESTROY:
3185 return TAB_Destroy (hwnd, wParam, lParam);
3187 case WM_GETDLGCODE:
3188 return DLGC_WANTARROWS | DLGC_WANTCHARS;
3190 case WM_LBUTTONDOWN:
3191 return TAB_LButtonDown (hwnd, wParam, lParam);
3193 case WM_LBUTTONUP:
3194 return TAB_LButtonUp (hwnd, wParam, lParam);
3196 case WM_NOTIFY:
3197 return SendMessageA(GetParent(hwnd), WM_NOTIFY, wParam, lParam);
3199 case WM_RBUTTONDOWN:
3200 return TAB_RButtonDown (hwnd, wParam, lParam);
3202 case WM_MOUSEMOVE:
3203 return TAB_MouseMove (hwnd, wParam, lParam);
3205 case WM_ERASEBKGND:
3206 return TAB_EraseBackground (hwnd, (HDC)wParam);
3208 case WM_PAINT:
3209 return TAB_Paint (hwnd, wParam);
3211 case WM_SIZE:
3212 return TAB_Size (hwnd, wParam, lParam);
3214 case WM_SETREDRAW:
3215 return TAB_SetRedraw (hwnd, wParam);
3217 case WM_HSCROLL:
3218 return TAB_OnHScroll(hwnd, (int)LOWORD(wParam), (int)HIWORD(wParam), (HWND)lParam);
3220 case WM_STYLECHANGED:
3221 TAB_SetItemBounds (hwnd);
3222 InvalidateRect(hwnd, NULL, TRUE);
3223 return 0;
3225 case WM_SYSCOLORCHANGE:
3226 COMCTL32_RefreshSysColors();
3227 return 0;
3229 case WM_KILLFOCUS:
3230 case WM_SETFOCUS:
3231 return TAB_FocusChanging(hwnd, uMsg, wParam, lParam);
3233 case WM_KEYUP:
3234 return TAB_KeyUp(hwnd, wParam);
3235 case WM_NCHITTEST:
3236 return TAB_NCHitTest(hwnd, lParam);
3238 default:
3239 if ((uMsg >= WM_USER) && (uMsg < WM_APP))
3240 WARN("unknown msg %04x wp=%08x lp=%08lx\n",
3241 uMsg, wParam, lParam);
3242 return DefWindowProcA(hwnd, uMsg, wParam, lParam);
3245 return 0;
3249 VOID
3250 TAB_Register (void)
3252 WNDCLASSA wndClass;
3254 ZeroMemory (&wndClass, sizeof(WNDCLASSA));
3255 wndClass.style = CS_GLOBALCLASS | CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
3256 wndClass.lpfnWndProc = (WNDPROC)TAB_WindowProc;
3257 wndClass.cbClsExtra = 0;
3258 wndClass.cbWndExtra = sizeof(TAB_INFO *);
3259 wndClass.hCursor = LoadCursorA (0, (LPSTR)IDC_ARROW);
3260 wndClass.hbrBackground = NULL;
3261 wndClass.lpszClassName = WC_TABCONTROLA;
3263 RegisterClassA (&wndClass);
3267 VOID
3268 TAB_Unregister (void)
3270 UnregisterClassA (WC_TABCONTROLA, NULL);