xref: /reactos/dll/win32/comctl32/tab.c (revision d5399189)
1 /*
2  * Tab control
3  *
4  * Copyright 1998 Anders Carlsson
5  * Copyright 1999 Alex Priem <alexp@sci.kun.nl>
6  * Copyright 1999 Francis Beaudet
7  * Copyright 2003 Vitaliy Margolen
8  *
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.
13  *
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.
18  *
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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
22  *
23  * NOTES
24  *
25  * This code was audited for completeness against the documented features
26  * of Comctl32.dll version 6.0 on May. 20, 2005, by James Hawkins.
27  *
28  * Unless otherwise noted, we believe this code to be complete, as per
29  * the specification mentioned above.
30  * If you discover missing features, or bugs, please note them below.
31  *
32  * TODO:
33  *
34  *  Styles:
35  *   TCS_MULTISELECT - implement for VK_SPACE selection
36  *   TCS_RIGHT
37  *   TCS_RIGHTJUSTIFY
38  *   TCS_SCROLLOPPOSITE
39  *   TCS_SINGLELINE
40  *   TCIF_RTLREADING
41  *
42  *  Extended Styles:
43  *   TCS_EX_REGISTERDROP
44  *
45  *  Notifications:
46  *   NM_RELEASEDCAPTURE
47  *   TCN_FOCUSCHANGE
48  *   TCN_GETOBJECT
49  *
50  *  Macros:
51  *   TabCtrl_AdjustRect
52  *
53  */
54 
55 #include <assert.h>
56 #include <stdarg.h>
57 #include <string.h>
58 
59 #include "windef.h"
60 #include "winbase.h"
61 #include "wingdi.h"
62 #include "winuser.h"
63 #include "winnls.h"
64 #include "commctrl.h"
65 #include "comctl32.h"
66 #include "uxtheme.h"
67 #include "vssym32.h"
68 #include "wine/debug.h"
69 #include <math.h>
70 
71 WINE_DEFAULT_DEBUG_CHANNEL(tab);
72 
73 typedef struct
74 {
75   DWORD  dwState;
76   LPWSTR pszText;
77   INT    iImage;
78   RECT   rect;      /* bounding rectangle of the item relative to the
79                      * leftmost item (the leftmost item, 0, would have a
80                      * "left" member of 0 in this rectangle)
81                      *
82                      * additionally the top member holds the row number
83                      * and bottom is unused and should be 0 */
84   BYTE   extra[1];  /* Space for caller supplied info, variable size */
85 } TAB_ITEM;
86 
87 /* The size of a tab item depends on how much extra data is requested.
88    TCM_INSERTITEM always stores at least LPARAM sized data. */
89 #define EXTRA_ITEM_SIZE(infoPtr) (max((infoPtr)->cbInfo, sizeof(LPARAM)))
90 #define TAB_ITEM_SIZE(infoPtr) FIELD_OFFSET(TAB_ITEM, extra[EXTRA_ITEM_SIZE(infoPtr)])
91 
92 typedef struct
93 {
94   HWND       hwnd;            /* Tab control window */
95   HWND       hwndNotify;      /* notification window (parent) */
96   UINT       uNumItem;        /* number of tab items */
97   UINT       uNumRows;	      /* number of tab rows */
98   INT        tabHeight;       /* height of the tab row */
99   INT        tabWidth;        /* width of tabs */
100   INT        tabMinWidth;     /* minimum width of items */
101   USHORT     uHItemPadding;   /* amount of horizontal padding, in pixels */
102   USHORT     uVItemPadding;   /* amount of vertical padding, in pixels */
103   USHORT     uHItemPadding_s; /* Set amount of horizontal padding, in pixels */
104   USHORT     uVItemPadding_s; /* Set amount of vertical padding, in pixels */
105   HFONT      hFont;           /* handle to the current font */
106   HCURSOR    hcurArrow;       /* handle to the current cursor */
107   HIMAGELIST himl;            /* handle to an image list (may be 0) */
108   HWND       hwndToolTip;     /* handle to tab's tooltip */
109   INT        leftmostVisible; /* Used for scrolling, this member contains
110                                * the index of the first visible item */
111   INT        iSelected;       /* the currently selected item */
112   INT        iHotTracked;     /* the highlighted item under the mouse */
113   INT        uFocus;          /* item which has the focus */
114   BOOL       DoRedraw;        /* flag for redrawing when tab contents is changed*/
115   BOOL       needsScrolling;  /* TRUE if the size of the tabs is greater than
116                                * the size of the control */
117   BOOL       fHeightSet;      /* was the height of the tabs explicitly set? */
118   BOOL       bUnicode;        /* Unicode control? */
119   HWND       hwndUpDown;      /* Updown control used for scrolling */
120   INT        cbInfo;          /* Number of bytes of caller supplied info per tab */
121 
122   DWORD      exStyle;         /* Extended style used, currently:
123                                  TCS_EX_FLATSEPARATORS, TCS_EX_REGISTERDROP */
124   DWORD      dwStyle;         /* the cached window GWL_STYLE */
125 
126   HDPA       items;           /* dynamic array of TAB_ITEM* pointers */
127 } TAB_INFO;
128 
129 /******************************************************************************
130  * Positioning constants
131  */
132 #define SELECTED_TAB_OFFSET     2
133 #define ROUND_CORNER_SIZE       2
134 #define DISPLAY_AREA_PADDINGX   2
135 #define DISPLAY_AREA_PADDINGY   2
136 #define CONTROL_BORDER_SIZEX    2
137 #define CONTROL_BORDER_SIZEY    2
138 #define BUTTON_SPACINGX         3
139 #define BUTTON_SPACINGY         3
140 #define FLAT_BTN_SPACINGX       8
141 #define DEFAULT_MIN_TAB_WIDTH   54
142 #define DEFAULT_PADDING_X       6
143 #define EXTRA_ICON_PADDING      3
144 
145 #define TAB_GetInfoPtr(hwnd) ((TAB_INFO *)GetWindowLongPtrW(hwnd,0))
146 
147 #define GET_DEFAULT_MIN_TAB_WIDTH(infoPtr) (DEFAULT_MIN_TAB_WIDTH - (DEFAULT_PADDING_X - (infoPtr)->uHItemPadding) * 2)
148 
149 /******************************************************************************
150  * Hot-tracking timer constants
151  */
152 #define TAB_HOTTRACK_TIMER            1
153 #define TAB_HOTTRACK_TIMER_INTERVAL   100   /* milliseconds */
154 
155 static const WCHAR themeClass[] = { 'T','a','b',0 };
156 
157 static inline TAB_ITEM* TAB_GetItem(const TAB_INFO *infoPtr, INT i)
158 {
159     assert(i >= 0 && i < infoPtr->uNumItem);
160     return DPA_GetPtr(infoPtr->items, i);
161 }
162 
163 /******************************************************************************
164  * Prototypes
165  */
166 static void TAB_InvalidateTabArea(const TAB_INFO *);
167 static void TAB_EnsureSelectionVisible(TAB_INFO *);
168 static void TAB_DrawItemInterior(const TAB_INFO *, HDC, INT, RECT*);
169 static LRESULT TAB_DeselectAll(TAB_INFO *, BOOL);
170 static BOOL TAB_InternalGetItemRect(const TAB_INFO *, INT, RECT*, RECT*);
171 
172 static BOOL
173 TAB_SendSimpleNotify (const TAB_INFO *infoPtr, UINT code)
174 {
175     NMHDR nmhdr;
176 
177     nmhdr.hwndFrom = infoPtr->hwnd;
178     nmhdr.idFrom = GetWindowLongPtrW(infoPtr->hwnd, GWLP_ID);
179     nmhdr.code = code;
180 
181     return (BOOL) SendMessageW (infoPtr->hwndNotify, WM_NOTIFY,
182             nmhdr.idFrom, (LPARAM) &nmhdr);
183 }
184 
185 static void
186 TAB_RelayEvent (HWND hwndTip, HWND hwndMsg, UINT uMsg,
187             WPARAM wParam, LPARAM lParam)
188 {
189     MSG msg;
190 
191     msg.hwnd = hwndMsg;
192     msg.message = uMsg;
193     msg.wParam = wParam;
194     msg.lParam = lParam;
195     msg.time = GetMessageTime ();
196     msg.pt.x = (short)LOWORD(GetMessagePos ());
197     msg.pt.y = (short)HIWORD(GetMessagePos ());
198 
199     SendMessageW (hwndTip, TTM_RELAYEVENT, 0, (LPARAM)&msg);
200 }
201 
202 static void
203 TAB_DumpItemExternalT(const TCITEMW *pti, UINT iItem, BOOL isW)
204 {
205     if (TRACE_ON(tab)) {
206 	TRACE("external tab %d, mask=0x%08x, dwState=0x%08x, dwStateMask=0x%08x, cchTextMax=0x%08x\n",
207 	      iItem, pti->mask, pti->dwState, pti->dwStateMask, pti->cchTextMax);
208 	TRACE("external tab %d,   iImage=%d, lParam=0x%08lx, pszTextW=%s\n",
209 	      iItem, pti->iImage, pti->lParam, isW ? debugstr_w(pti->pszText) : debugstr_a((LPSTR)pti->pszText));
210     }
211 }
212 
213 static void
214 TAB_DumpItemInternal(const TAB_INFO *infoPtr, UINT iItem)
215 {
216     if (TRACE_ON(tab)) {
217 	TAB_ITEM *ti = TAB_GetItem(infoPtr, iItem);
218 
219 	TRACE("tab %d, dwState=0x%08x, pszText=%s, iImage=%d\n",
220 	      iItem, ti->dwState, debugstr_w(ti->pszText), ti->iImage);
221 	TRACE("tab %d, rect.left=%d, rect.top(row)=%d\n",
222 	      iItem, ti->rect.left, ti->rect.top);
223     }
224 }
225 
226 /* RETURNS
227  *   the index of the selected tab, or -1 if no tab is selected. */
228 static inline LRESULT TAB_GetCurSel (const TAB_INFO *infoPtr)
229 {
230     TRACE("(%p)\n", infoPtr);
231     return infoPtr->iSelected;
232 }
233 
234 /* RETURNS
235  *   the index of the tab item that has the focus. */
236 static inline LRESULT
237 TAB_GetCurFocus (const TAB_INFO *infoPtr)
238 {
239     TRACE("(%p)\n", infoPtr);
240     return infoPtr->uFocus;
241 }
242 
243 static inline LRESULT TAB_GetToolTips (const TAB_INFO *infoPtr)
244 {
245     TRACE("(%p)\n", infoPtr);
246     return (LRESULT)infoPtr->hwndToolTip;
247 }
248 
249 static inline LRESULT TAB_SetCurSel (TAB_INFO *infoPtr, INT iItem)
250 {
251   INT prevItem = infoPtr->iSelected;
252 
253   TRACE("(%p %d)\n", infoPtr, iItem);
254 
255   if (iItem >= (INT)infoPtr->uNumItem)
256       return -1;
257 
258   if (prevItem != iItem) {
259       if (prevItem != -1)
260           TAB_GetItem(infoPtr, prevItem)->dwState &= ~TCIS_BUTTONPRESSED;
261 
262       if (iItem >= 0)
263       {
264           TAB_GetItem(infoPtr, iItem)->dwState |= TCIS_BUTTONPRESSED;
265           infoPtr->iSelected = iItem;
266           infoPtr->uFocus = iItem;
267       }
268       else
269       {
270           infoPtr->iSelected = -1;
271           infoPtr->uFocus = -1;
272       }
273 
274       TAB_EnsureSelectionVisible(infoPtr);
275       TAB_InvalidateTabArea(infoPtr);
276   }
277 
278   return prevItem;
279 }
280 
281 static LRESULT TAB_SetCurFocus (TAB_INFO *infoPtr, INT iItem)
282 {
283   TRACE("(%p %d)\n", infoPtr, iItem);
284 
285   if (iItem < 0) {
286       infoPtr->uFocus = -1;
287       if (infoPtr->iSelected != -1) {
288           infoPtr->iSelected = -1;
289           TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGE);
290           TAB_InvalidateTabArea(infoPtr);
291       }
292   }
293   else if (iItem < infoPtr->uNumItem) {
294     if (infoPtr->dwStyle & TCS_BUTTONS) {
295       /* set focus to new item, leave selection as is */
296       if (infoPtr->uFocus != iItem) {
297         INT prev_focus = infoPtr->uFocus;
298         RECT r;
299 
300         infoPtr->uFocus = iItem;
301 
302         if (prev_focus != infoPtr->iSelected) {
303           if (TAB_InternalGetItemRect(infoPtr, prev_focus, &r, NULL))
304             InvalidateRect(infoPtr->hwnd, &r, FALSE);
305         }
306 
307         if (TAB_InternalGetItemRect(infoPtr, iItem, &r, NULL))
308             InvalidateRect(infoPtr->hwnd, &r, FALSE);
309 
310         TAB_SendSimpleNotify(infoPtr, TCN_FOCUSCHANGE);
311       }
312     } else {
313       INT oldFocus = infoPtr->uFocus;
314       if (infoPtr->iSelected != iItem || oldFocus == -1 ) {
315         infoPtr->uFocus = iItem;
316         if (oldFocus != -1) {
317           if (!TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGING))  {
318             infoPtr->iSelected = iItem;
319             TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGE);
320           }
321           else
322             infoPtr->iSelected = iItem;
323           TAB_EnsureSelectionVisible(infoPtr);
324           TAB_InvalidateTabArea(infoPtr);
325         }
326       }
327     }
328   }
329   return 0;
330 }
331 
332 static inline LRESULT
333 TAB_SetToolTips (TAB_INFO *infoPtr, HWND hwndToolTip)
334 {
335     TRACE("%p %p\n", infoPtr, hwndToolTip);
336     infoPtr->hwndToolTip = hwndToolTip;
337     return 0;
338 }
339 
340 static inline LRESULT
341 TAB_SetPadding (TAB_INFO *infoPtr, LPARAM lParam)
342 {
343     TRACE("(%p %d %d)\n", infoPtr, LOWORD(lParam), HIWORD(lParam));
344     infoPtr->uHItemPadding_s = LOWORD(lParam);
345     infoPtr->uVItemPadding_s = HIWORD(lParam);
346 
347     return 0;
348 }
349 
350 /******************************************************************************
351  * TAB_InternalGetItemRect
352  *
353  * This method will calculate the rectangle representing a given tab item in
354  * client coordinates. This method takes scrolling into account.
355  *
356  * This method returns TRUE if the item is visible in the window and FALSE
357  * if it is completely outside the client area.
358  */
359 static BOOL TAB_InternalGetItemRect(
360   const TAB_INFO* infoPtr,
361   INT         itemIndex,
362   RECT*       itemRect,
363   RECT*       selectedRect)
364 {
365   RECT tmpItemRect,clientRect;
366 
367   /* Perform a sanity check and a trivial visibility check. */
368   if ( (infoPtr->uNumItem <= 0) ||
369        (itemIndex >= infoPtr->uNumItem) ||
370        (!(((infoPtr->dwStyle & TCS_MULTILINE) || (infoPtr->dwStyle & TCS_VERTICAL))) &&
371          (itemIndex < infoPtr->leftmostVisible)))
372     {
373         TRACE("Not Visible\n");
374         SetRect(itemRect, 0, 0, 0, infoPtr->tabHeight);
375         SetRectEmpty(selectedRect);
376         return FALSE;
377     }
378 
379   /*
380    * Avoid special cases in this procedure by assigning the "out"
381    * parameters if the caller didn't supply them
382    */
383   if (itemRect == NULL)
384     itemRect = &tmpItemRect;
385 
386   /* Retrieve the unmodified item rect. */
387   *itemRect = TAB_GetItem(infoPtr,itemIndex)->rect;
388 
389   /* calculate the times bottom and top based on the row */
390   GetClientRect(infoPtr->hwnd, &clientRect);
391 
392   if ((infoPtr->dwStyle & TCS_BOTTOM) && (infoPtr->dwStyle & TCS_VERTICAL))
393   {
394     itemRect->right  = clientRect.right - SELECTED_TAB_OFFSET - itemRect->left * infoPtr->tabHeight -
395                        ((infoPtr->dwStyle & TCS_BUTTONS) ? itemRect->left * BUTTON_SPACINGX : 0);
396     itemRect->left   = itemRect->right - infoPtr->tabHeight;
397   }
398   else if (infoPtr->dwStyle & TCS_VERTICAL)
399   {
400     itemRect->left   = clientRect.left + SELECTED_TAB_OFFSET + itemRect->left * infoPtr->tabHeight +
401                        ((infoPtr->dwStyle & TCS_BUTTONS) ? itemRect->left * BUTTON_SPACINGX : 0);
402     itemRect->right  = itemRect->left + infoPtr->tabHeight;
403   }
404   else if (infoPtr->dwStyle & TCS_BOTTOM)
405   {
406     itemRect->bottom = clientRect.bottom - itemRect->top * infoPtr->tabHeight -
407                        ((infoPtr->dwStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : SELECTED_TAB_OFFSET);
408     itemRect->top    = itemRect->bottom - infoPtr->tabHeight;
409   }
410   else /* not TCS_BOTTOM and not TCS_VERTICAL */
411   {
412     itemRect->top    = clientRect.top + itemRect->top * infoPtr->tabHeight +
413                        ((infoPtr->dwStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : SELECTED_TAB_OFFSET);
414     itemRect->bottom = itemRect->top + infoPtr->tabHeight;
415  }
416 
417   /*
418    * "scroll" it to make sure the item at the very left of the
419    * tab control is the leftmost visible tab.
420    */
421   if(infoPtr->dwStyle & TCS_VERTICAL)
422   {
423     OffsetRect(itemRect,
424 	     0,
425 	     -TAB_GetItem(infoPtr, infoPtr->leftmostVisible)->rect.top);
426 
427     /*
428      * Move the rectangle so the first item is slightly offset from
429      * the bottom of the tab control.
430      */
431     OffsetRect(itemRect,
432 	     0,
433 	     SELECTED_TAB_OFFSET);
434 
435   } else
436   {
437     OffsetRect(itemRect,
438 	     -TAB_GetItem(infoPtr, infoPtr->leftmostVisible)->rect.left,
439 	     0);
440 
441     /*
442      * Move the rectangle so the first item is slightly offset from
443      * the left of the tab control.
444      */
445     OffsetRect(itemRect,
446 	     SELECTED_TAB_OFFSET,
447 	     0);
448   }
449   TRACE("item %d tab h=%d, rect=(%s)\n",
450         itemIndex, infoPtr->tabHeight, wine_dbgstr_rect(itemRect));
451 
452   /* Now, calculate the position of the item as if it were selected. */
453   if (selectedRect!=NULL)
454   {
455     *selectedRect = *itemRect;
456 
457     /* The rectangle of a selected item is a bit wider. */
458     if(infoPtr->dwStyle & TCS_VERTICAL)
459       InflateRect(selectedRect, 0, SELECTED_TAB_OFFSET);
460     else
461       InflateRect(selectedRect, SELECTED_TAB_OFFSET, 0);
462 
463     /* If it also a bit higher. */
464     if ((infoPtr->dwStyle & TCS_BOTTOM) && (infoPtr->dwStyle & TCS_VERTICAL))
465     {
466       selectedRect->left   -= 2; /* the border is thicker on the right */
467       selectedRect->right  += SELECTED_TAB_OFFSET;
468     }
469     else if (infoPtr->dwStyle & TCS_VERTICAL)
470     {
471       selectedRect->left   -= SELECTED_TAB_OFFSET;
472       selectedRect->right  += 1;
473     }
474     else if (infoPtr->dwStyle & TCS_BOTTOM)
475     {
476       selectedRect->bottom += SELECTED_TAB_OFFSET;
477     }
478     else /* not TCS_BOTTOM and not TCS_VERTICAL */
479     {
480       selectedRect->top    -= SELECTED_TAB_OFFSET;
481       selectedRect->bottom -= 1;
482     }
483   }
484 
485   /* Check for visibility */
486   if (infoPtr->dwStyle & TCS_VERTICAL)
487     return (itemRect->top < clientRect.bottom) && (itemRect->bottom > clientRect.top);
488   else
489     return (itemRect->left < clientRect.right) && (itemRect->right > clientRect.left);
490 }
491 
492 static inline BOOL
493 TAB_GetItemRect(const TAB_INFO *infoPtr, INT item, RECT *rect)
494 {
495   TRACE("(%p, %d, %p)\n", infoPtr, item, rect);
496   return TAB_InternalGetItemRect(infoPtr, item, rect, NULL);
497 }
498 
499 /******************************************************************************
500  * TAB_KeyDown
501  *
502  * This method is called to handle keyboard input
503  */
504 static LRESULT TAB_KeyDown(TAB_INFO* infoPtr, WPARAM keyCode, LPARAM lParam)
505 {
506   INT newItem = -1;
507   NMTCKEYDOWN nm;
508 
509   /* TCN_KEYDOWN notification sent always */
510   nm.hdr.hwndFrom = infoPtr->hwnd;
511   nm.hdr.idFrom = GetWindowLongPtrW(infoPtr->hwnd, GWLP_ID);
512   nm.hdr.code = TCN_KEYDOWN;
513   nm.wVKey = keyCode;
514   nm.flags = lParam;
515   SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, nm.hdr.idFrom, (LPARAM)&nm);
516 
517   switch (keyCode)
518   {
519     case VK_LEFT:
520       newItem = infoPtr->uFocus - 1;
521       break;
522     case VK_RIGHT:
523       newItem = infoPtr->uFocus + 1;
524       break;
525   }
526 
527   /* If we changed to a valid item, change focused item */
528   if (newItem >= 0 && newItem < infoPtr->uNumItem && infoPtr->uFocus != newItem)
529       TAB_SetCurFocus(infoPtr, newItem);
530 
531   return 0;
532 }
533 
534 /*
535  * WM_KILLFOCUS handler
536  */
537 static void TAB_KillFocus(TAB_INFO *infoPtr)
538 {
539   /* clear current focused item back to selected for TCS_BUTTONS */
540   if ((infoPtr->dwStyle & TCS_BUTTONS) && (infoPtr->uFocus != infoPtr->iSelected))
541   {
542     RECT r;
543 
544     if (TAB_InternalGetItemRect(infoPtr, infoPtr->uFocus, &r, NULL))
545       InvalidateRect(infoPtr->hwnd, &r, FALSE);
546 
547     infoPtr->uFocus = infoPtr->iSelected;
548   }
549 }
550 
551 /******************************************************************************
552  * TAB_FocusChanging
553  *
554  * This method is called whenever the focus goes in or out of this control
555  * it is used to update the visual state of the control.
556  */
557 static void TAB_FocusChanging(const TAB_INFO *infoPtr)
558 {
559   RECT      selectedRect;
560   BOOL      isVisible;
561 
562   /*
563    * Get the rectangle for the item.
564    */
565   isVisible = TAB_InternalGetItemRect(infoPtr,
566 				      infoPtr->uFocus,
567 				      NULL,
568 				      &selectedRect);
569 
570   /*
571    * If the rectangle is not completely invisible, invalidate that
572    * portion of the window.
573    */
574   if (isVisible)
575   {
576     TRACE("invalidate (%s)\n", wine_dbgstr_rect(&selectedRect));
577     InvalidateRect(infoPtr->hwnd, &selectedRect, TRUE);
578   }
579 }
580 
581 static INT TAB_InternalHitTest (const TAB_INFO *infoPtr, POINT pt, UINT *flags)
582 {
583   RECT rect;
584   INT iCount;
585 
586   for (iCount = 0; iCount < infoPtr->uNumItem; iCount++)
587   {
588     TAB_InternalGetItemRect(infoPtr, iCount, &rect, NULL);
589 
590     if (PtInRect(&rect, pt))
591     {
592       *flags = TCHT_ONITEM;
593       return iCount;
594     }
595   }
596 
597   *flags = TCHT_NOWHERE;
598   return -1;
599 }
600 
601 static inline LRESULT
602 TAB_HitTest (const TAB_INFO *infoPtr, LPTCHITTESTINFO lptest)
603 {
604   TRACE("(%p, %p)\n", infoPtr, lptest);
605   return TAB_InternalHitTest (infoPtr, lptest->pt, &lptest->flags);
606 }
607 
608 /******************************************************************************
609  * TAB_NCHitTest
610  *
611  * Napster v2b5 has a tab control for its main navigation which has a client
612  * area that covers the whole area of the dialog pages.
613  * That's why it receives all msgs for that area and the underlying dialog ctrls
614  * are dead.
615  * So I decided that we should handle WM_NCHITTEST here and return
616  * HTTRANSPARENT if we don't hit the tab control buttons.
617  * FIXME: WM_NCHITTEST handling correct ? Fix it if you know that Windows
618  * doesn't do it that way. Maybe depends on tab control styles ?
619  */
620 static inline LRESULT
621 TAB_NCHitTest (const TAB_INFO *infoPtr, LPARAM lParam)
622 {
623   POINT pt;
624   UINT dummyflag;
625 
626   pt.x = (short)LOWORD(lParam);
627   pt.y = (short)HIWORD(lParam);
628   ScreenToClient(infoPtr->hwnd, &pt);
629 
630   if (TAB_InternalHitTest(infoPtr, pt, &dummyflag) == -1)
631     return HTTRANSPARENT;
632   else
633     return HTCLIENT;
634 }
635 
636 static LRESULT
637 TAB_LButtonDown (TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
638 {
639   POINT pt;
640   INT newItem;
641   UINT dummy;
642 
643   if (infoPtr->hwndToolTip)
644     TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
645 		    WM_LBUTTONDOWN, wParam, lParam);
646 
647   if (!(infoPtr->dwStyle & TCS_FOCUSNEVER)) {
648     SetFocus (infoPtr->hwnd);
649   }
650 
651   if (infoPtr->hwndToolTip)
652     TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
653 		    WM_LBUTTONDOWN, wParam, lParam);
654 
655   pt.x = (short)LOWORD(lParam);
656   pt.y = (short)HIWORD(lParam);
657 
658   newItem = TAB_InternalHitTest (infoPtr, pt, &dummy);
659 
660   TRACE("On Tab, item %d\n", newItem);
661 
662   if ((newItem != -1) && (infoPtr->iSelected != newItem))
663   {
664     if ((infoPtr->dwStyle & TCS_BUTTONS) && (infoPtr->dwStyle & TCS_MULTISELECT) &&
665         (wParam & MK_CONTROL))
666     {
667       RECT r;
668 
669       /* toggle multiselection */
670       TAB_GetItem(infoPtr, newItem)->dwState ^= TCIS_BUTTONPRESSED;
671       if (TAB_InternalGetItemRect (infoPtr, newItem, &r, NULL))
672         InvalidateRect (infoPtr->hwnd, &r, TRUE);
673     }
674     else
675     {
676       INT i;
677       BOOL pressed = FALSE;
678 
679       /* any button pressed ? */
680       for (i = 0; i < infoPtr->uNumItem; i++)
681         if ((TAB_GetItem (infoPtr, i)->dwState & TCIS_BUTTONPRESSED) &&
682             (infoPtr->iSelected != i))
683         {
684           pressed = TRUE;
685           break;
686         }
687 
688       if (TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGING))
689         return 0;
690 
691       if (pressed)
692         TAB_DeselectAll (infoPtr, FALSE);
693       else
694         TAB_SetCurSel(infoPtr, newItem);
695 
696       TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGE);
697     }
698   }
699 
700   return 0;
701 }
702 
703 static inline LRESULT
704 TAB_LButtonUp (const TAB_INFO *infoPtr)
705 {
706   TAB_SendSimpleNotify(infoPtr, NM_CLICK);
707 
708   return 0;
709 }
710 
711 static inline void
712 TAB_RButtonUp (const TAB_INFO *infoPtr)
713 {
714   TAB_SendSimpleNotify(infoPtr, NM_RCLICK);
715 }
716 
717 /******************************************************************************
718  * TAB_DrawLoneItemInterior
719  *
720  * This calls TAB_DrawItemInterior.  However, TAB_DrawItemInterior is normally
721  * called by TAB_DrawItem which is normally called by TAB_Refresh which sets
722  * up the device context and font.  This routine does the same setup but
723  * only calls TAB_DrawItemInterior for the single specified item.
724  */
725 static void
726 TAB_DrawLoneItemInterior(const TAB_INFO* infoPtr, int iItem)
727 {
728   HDC hdc = GetDC(infoPtr->hwnd);
729   RECT r, rC;
730 
731   /* Clip UpDown control to not draw over it */
732   if (infoPtr->needsScrolling)
733   {
734     GetWindowRect(infoPtr->hwnd, &rC);
735     GetWindowRect(infoPtr->hwndUpDown, &r);
736     ExcludeClipRect(hdc, r.left - rC.left, r.top - rC.top, r.right - rC.left, r.bottom - rC.top);
737   }
738   TAB_DrawItemInterior(infoPtr, hdc, iItem, NULL);
739   ReleaseDC(infoPtr->hwnd, hdc);
740 }
741 
742 /* update a tab after hottracking - invalidate it or just redraw the interior,
743  * based on whether theming is used or not */
744 static inline void hottrack_refresh(const TAB_INFO *infoPtr, int tabIndex)
745 {
746     if (tabIndex == -1) return;
747 
748     if (GetWindowTheme (infoPtr->hwnd))
749     {
750         RECT rect;
751         TAB_InternalGetItemRect(infoPtr, tabIndex, &rect, NULL);
752         InvalidateRect (infoPtr->hwnd, &rect, FALSE);
753     }
754     else
755         TAB_DrawLoneItemInterior(infoPtr, tabIndex);
756 }
757 
758 /******************************************************************************
759  * TAB_HotTrackTimerProc
760  *
761  * When a mouse-move event causes a tab to be highlighted (hot-tracking), a
762  * timer is setup so we can check if the mouse is moved out of our window.
763  * (We don't get an event when the mouse leaves, the mouse-move events just
764  * stop being delivered to our window and just start being delivered to
765  * another window.)  This function is called when the timer triggers so
766  * we can check if the mouse has left our window.  If so, we un-highlight
767  * the hot-tracked tab.
768  */
769 static void CALLBACK
770 TAB_HotTrackTimerProc
771   (
772   HWND hwnd,    /* handle of window for timer messages */
773   UINT uMsg,    /* WM_TIMER message */
774   UINT_PTR idEvent, /* timer identifier */
775   DWORD dwTime  /* current system time */
776   )
777 {
778   TAB_INFO* infoPtr = TAB_GetInfoPtr(hwnd);
779 
780   if (infoPtr != NULL && infoPtr->iHotTracked >= 0)
781   {
782     POINT pt;
783 
784     /*
785     ** If we can't get the cursor position, or if the cursor is outside our
786     ** window, we un-highlight the hot-tracked tab.  Note that the cursor is
787     ** "outside" even if it is within our bounding rect if another window
788     ** overlaps.  Note also that the case where the cursor stayed within our
789     ** window but has moved off the hot-tracked tab will be handled by the
790     ** WM_MOUSEMOVE event.
791     */
792     if (!GetCursorPos(&pt) || WindowFromPoint(pt) != hwnd)
793     {
794       /* Redraw iHotTracked to look normal */
795       INT iRedraw = infoPtr->iHotTracked;
796       infoPtr->iHotTracked = -1;
797       hottrack_refresh (infoPtr, iRedraw);
798 
799       /* Kill this timer */
800       KillTimer(hwnd, TAB_HOTTRACK_TIMER);
801     }
802   }
803 }
804 
805 /******************************************************************************
806  * TAB_RecalcHotTrack
807  *
808  * If a tab control has the TCS_HOTTRACK style, then the tab under the mouse
809  * should be highlighted.  This function determines which tab in a tab control,
810  * if any, is under the mouse and records that information.  The caller may
811  * supply output parameters to receive the item number of the tab item which
812  * was highlighted but isn't any longer and of the tab item which is now
813  * highlighted but wasn't previously.  The caller can use this information to
814  * selectively redraw those tab items.
815  *
816  * If the caller has a mouse position, it can supply it through the pos
817  * parameter.  For example, TAB_MouseMove does this.  Otherwise, the caller
818  * supplies NULL and this function determines the current mouse position
819  * itself.
820  */
821 static void
822 TAB_RecalcHotTrack
823   (
824   TAB_INFO*       infoPtr,
825   const LPARAM*   pos,
826   int*            out_redrawLeave,
827   int*            out_redrawEnter
828   )
829 {
830   int item = -1;
831 
832 
833   if (out_redrawLeave != NULL)
834     *out_redrawLeave = -1;
835   if (out_redrawEnter != NULL)
836     *out_redrawEnter = -1;
837 
838   if ((infoPtr->dwStyle & TCS_HOTTRACK) || GetWindowTheme(infoPtr->hwnd))
839   {
840     POINT pt;
841     UINT  flags;
842 
843     if (pos == NULL)
844     {
845       GetCursorPos(&pt);
846       ScreenToClient(infoPtr->hwnd, &pt);
847     }
848     else
849     {
850       pt.x = (short)LOWORD(*pos);
851       pt.y = (short)HIWORD(*pos);
852     }
853 
854     item = TAB_InternalHitTest(infoPtr, pt, &flags);
855   }
856 
857   if (item != infoPtr->iHotTracked)
858   {
859     if (infoPtr->iHotTracked >= 0)
860     {
861       /* Mark currently hot-tracked to be redrawn to look normal */
862       if (out_redrawLeave != NULL)
863         *out_redrawLeave = infoPtr->iHotTracked;
864 
865       if (item < 0)
866       {
867         /* Kill timer which forces recheck of mouse pos */
868         KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
869       }
870     }
871     else
872     {
873       /* Start timer so we recheck mouse pos */
874       UINT timerID = SetTimer
875         (
876         infoPtr->hwnd,
877         TAB_HOTTRACK_TIMER,
878         TAB_HOTTRACK_TIMER_INTERVAL,
879         TAB_HotTrackTimerProc
880         );
881 
882       if (timerID == 0)
883         return; /* Hot tracking not available */
884     }
885 
886     infoPtr->iHotTracked = item;
887 
888     if (item >= 0)
889     {
890 	/* Mark new hot-tracked to be redrawn to look highlighted */
891       if (out_redrawEnter != NULL)
892         *out_redrawEnter = item;
893     }
894   }
895 }
896 
897 /******************************************************************************
898  * TAB_MouseMove
899  *
900  * Handles the mouse-move event.  Updates tooltips.  Updates hot-tracking.
901  */
902 static LRESULT
903 TAB_MouseMove (TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
904 {
905   int redrawLeave;
906   int redrawEnter;
907 
908   if (infoPtr->hwndToolTip)
909     TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
910 		    WM_LBUTTONDOWN, wParam, lParam);
911 
912   /* Determine which tab to highlight.  Redraw tabs which change highlight
913   ** status. */
914   TAB_RecalcHotTrack(infoPtr, &lParam, &redrawLeave, &redrawEnter);
915 
916   hottrack_refresh (infoPtr, redrawLeave);
917   hottrack_refresh (infoPtr, redrawEnter);
918 
919   return 0;
920 }
921 
922 /******************************************************************************
923  * TAB_AdjustRect
924  *
925  * Calculates the tab control's display area given the window rectangle or
926  * the window rectangle given the requested display rectangle.
927  */
928 static LRESULT TAB_AdjustRect(const TAB_INFO *infoPtr, WPARAM fLarger, LPRECT prc)
929 {
930     LONG *iRightBottom, *iLeftTop;
931 
932     TRACE ("hwnd=%p fLarger=%ld (%s)\n", infoPtr->hwnd, fLarger,
933            wine_dbgstr_rect(prc));
934 
935     if (!prc) return -1;
936 
937     if(infoPtr->dwStyle & TCS_VERTICAL)
938     {
939 	iRightBottom = &(prc->right);
940 	iLeftTop     = &(prc->left);
941     }
942     else
943     {
944 	iRightBottom = &(prc->bottom);
945 	iLeftTop     = &(prc->top);
946     }
947 
948     if (fLarger) /* Go from display rectangle */
949     {
950         /* Add the height of the tabs. */
951 	if (infoPtr->dwStyle & TCS_BOTTOM)
952 	    *iRightBottom += infoPtr->tabHeight * infoPtr->uNumRows;
953 	else
954 	    *iLeftTop -= infoPtr->tabHeight * infoPtr->uNumRows +
955 			 ((infoPtr->dwStyle & TCS_BUTTONS)? 3 * (infoPtr->uNumRows - 1) : 0);
956 
957 	/* Inflate the rectangle for the padding */
958 	InflateRect(prc, DISPLAY_AREA_PADDINGX, DISPLAY_AREA_PADDINGY);
959 
960 	/* Inflate for the border */
961 	InflateRect(prc, CONTROL_BORDER_SIZEX, CONTROL_BORDER_SIZEY);
962     }
963     else /* Go from window rectangle. */
964     {
965 	/* Deflate the rectangle for the border */
966 	InflateRect(prc, -CONTROL_BORDER_SIZEX, -CONTROL_BORDER_SIZEY);
967 
968 	/* Deflate the rectangle for the padding */
969 	InflateRect(prc, -DISPLAY_AREA_PADDINGX, -DISPLAY_AREA_PADDINGY);
970 
971 	/* Remove the height of the tabs. */
972 	if (infoPtr->dwStyle & TCS_BOTTOM)
973 	    *iRightBottom -= infoPtr->tabHeight * infoPtr->uNumRows;
974 	else
975 	    *iLeftTop += (infoPtr->tabHeight) * infoPtr->uNumRows +
976 			 ((infoPtr->dwStyle & TCS_BUTTONS)? 3 * (infoPtr->uNumRows - 1) : 0);
977     }
978 
979   return 0;
980 }
981 
982 /******************************************************************************
983  * TAB_OnHScroll
984  *
985  * This method will handle the notification from the scroll control and
986  * perform the scrolling operation on the tab control.
987  */
988 static LRESULT TAB_OnHScroll(TAB_INFO *infoPtr, int nScrollCode, int nPos)
989 {
990   if(nScrollCode == SB_THUMBPOSITION && nPos != infoPtr->leftmostVisible)
991   {
992      if(nPos < infoPtr->leftmostVisible)
993         infoPtr->leftmostVisible--;
994      else
995         infoPtr->leftmostVisible++;
996 
997      TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
998      TAB_InvalidateTabArea(infoPtr);
999      SendMessageW(infoPtr->hwndUpDown, UDM_SETPOS, 0,
1000                    MAKELONG(infoPtr->leftmostVisible, 0));
1001    }
1002 
1003    return 0;
1004 }
1005 
1006 /******************************************************************************
1007  * TAB_SetupScrolling
1008  *
1009  * This method will check the current scrolling state and make sure the
1010  * scrolling control is displayed (or not).
1011  */
1012 static void TAB_SetupScrolling(
1013   TAB_INFO*   infoPtr,
1014   const RECT* clientRect)
1015 {
1016   static const WCHAR emptyW[] = { 0 };
1017   INT maxRange = 0;
1018 
1019   if (infoPtr->needsScrolling)
1020   {
1021     RECT controlPos;
1022     INT vsize, tabwidth;
1023 
1024     /*
1025      * Calculate the position of the scroll control.
1026      */
1027     controlPos.right = clientRect->right;
1028     controlPos.left  = controlPos.right - 2 * GetSystemMetrics(SM_CXHSCROLL);
1029 
1030     if (infoPtr->dwStyle & TCS_BOTTOM)
1031     {
1032       controlPos.top    = clientRect->bottom - infoPtr->tabHeight;
1033       controlPos.bottom = controlPos.top + GetSystemMetrics(SM_CYHSCROLL);
1034     }
1035     else
1036     {
1037       controlPos.bottom = clientRect->top + infoPtr->tabHeight;
1038       controlPos.top    = controlPos.bottom - GetSystemMetrics(SM_CYHSCROLL);
1039     }
1040 
1041     /*
1042      * If we don't have a scroll control yet, we want to create one.
1043      * If we have one, we want to make sure it's positioned properly.
1044      */
1045     if (infoPtr->hwndUpDown==0)
1046     {
1047       infoPtr->hwndUpDown = CreateWindowW(UPDOWN_CLASSW, emptyW,
1048 					  WS_VISIBLE | WS_CHILD | UDS_HORZ,
1049 					  controlPos.left, controlPos.top,
1050 					  controlPos.right - controlPos.left,
1051 					  controlPos.bottom - controlPos.top,
1052 					  infoPtr->hwnd, NULL, NULL, NULL);
1053     }
1054     else
1055     {
1056       SetWindowPos(infoPtr->hwndUpDown,
1057 		   NULL,
1058 		   controlPos.left, controlPos.top,
1059 		   controlPos.right - controlPos.left,
1060 		   controlPos.bottom - controlPos.top,
1061 		   SWP_SHOWWINDOW | SWP_NOZORDER);
1062     }
1063 
1064     /* Now calculate upper limit of the updown control range.
1065      * We do this by calculating how many tabs will be offscreen when the
1066      * last tab is visible.
1067      */
1068     if(infoPtr->uNumItem)
1069     {
1070        vsize = clientRect->right - (controlPos.right - controlPos.left + 1);
1071        maxRange = infoPtr->uNumItem;
1072        tabwidth = TAB_GetItem(infoPtr, infoPtr->uNumItem - 1)->rect.right;
1073 
1074        for(; maxRange > 0; maxRange--)
1075        {
1076           if(tabwidth - TAB_GetItem(infoPtr,maxRange - 1)->rect.left > vsize)
1077              break;
1078        }
1079 
1080        if(maxRange == infoPtr->uNumItem)
1081           maxRange--;
1082     }
1083   }
1084   else
1085   {
1086     /* If we once had a scroll control... hide it */
1087     if (infoPtr->hwndUpDown)
1088       ShowWindow(infoPtr->hwndUpDown, SW_HIDE);
1089   }
1090   if (infoPtr->hwndUpDown)
1091      SendMessageW(infoPtr->hwndUpDown, UDM_SETRANGE32, 0, maxRange);
1092 }
1093 
1094 /******************************************************************************
1095  * TAB_SetItemBounds
1096  *
1097  * This method will calculate the position rectangles of all the items in the
1098  * control. The rectangle calculated starts at 0 for the first item in the
1099  * list and ignores scrolling and selection.
1100  * It also uses the current font to determine the height of the tab row and
1101  * it checks if all the tabs fit in the client area of the window. If they
1102  * don't, a scrolling control is added.
1103  */
1104 static void TAB_SetItemBounds (TAB_INFO *infoPtr)
1105 {
1106   TEXTMETRICW fontMetrics;
1107   UINT        curItem;
1108   INT         curItemLeftPos;
1109   INT         curItemRowCount;
1110   HFONT       hFont, hOldFont;
1111   HDC         hdc;
1112   RECT        clientRect;
1113   INT         iTemp;
1114   RECT*       rcItem;
1115   INT         iIndex;
1116   INT         icon_width = 0;
1117 
1118   /*
1119    * We need to get text information so we need a DC and we need to select
1120    * a font.
1121    */
1122   hdc = GetDC(infoPtr->hwnd);
1123 
1124   hFont = infoPtr->hFont ? infoPtr->hFont : GetStockObject (SYSTEM_FONT);
1125   hOldFont = SelectObject (hdc, hFont);
1126 
1127   /*
1128    * We will base the rectangle calculations on the client rectangle
1129    * of the control.
1130    */
1131   GetClientRect(infoPtr->hwnd, &clientRect);
1132 
1133   /* if TCS_VERTICAL then swap the height and width so this code places the
1134      tabs along the top of the rectangle and we can just rotate them after
1135      rather than duplicate all of the below code */
1136   if(infoPtr->dwStyle & TCS_VERTICAL)
1137   {
1138      iTemp = clientRect.bottom;
1139      clientRect.bottom = clientRect.right;
1140      clientRect.right = iTemp;
1141   }
1142 
1143   /* Now use hPadding and vPadding */
1144   infoPtr->uHItemPadding = infoPtr->uHItemPadding_s;
1145   infoPtr->uVItemPadding = infoPtr->uVItemPadding_s;
1146 
1147   /* The leftmost item will be "0" aligned */
1148   curItemLeftPos = 0;
1149   curItemRowCount = infoPtr->uNumItem ? 1 : 0;
1150 
1151   if (!(infoPtr->fHeightSet))
1152   {
1153     int item_height;
1154     INT icon_height = 0, cx;
1155 
1156     /* Use the current font to determine the height of a tab. */
1157     GetTextMetricsW(hdc, &fontMetrics);
1158 
1159     /* Get the icon height */
1160     if (infoPtr->himl)
1161       ImageList_GetIconSize(infoPtr->himl, &cx, &icon_height);
1162 
1163     /* Take the highest between font or icon */
1164     if (fontMetrics.tmHeight > icon_height)
1165       item_height = fontMetrics.tmHeight + 2;
1166     else
1167       item_height = icon_height;
1168 
1169     /*
1170      * Make sure there is enough space for the letters + icon + growing the
1171      * selected item + extra space for the selected item.
1172      */
1173     infoPtr->tabHeight = item_height +
1174 	                 ((infoPtr->dwStyle & TCS_BUTTONS) ? 2 : 1) *
1175                           infoPtr->uVItemPadding;
1176 
1177     TRACE("tabH=%d, tmH=%d, iconh=%d\n",
1178 	  infoPtr->tabHeight, fontMetrics.tmHeight, icon_height);
1179   }
1180 
1181   TRACE("client right=%d\n", clientRect.right);
1182 
1183   /* Get the icon width */
1184   if (infoPtr->himl)
1185   {
1186     INT cy;
1187 
1188     ImageList_GetIconSize(infoPtr->himl, &icon_width, &cy);
1189 
1190     if (infoPtr->dwStyle & TCS_FIXEDWIDTH)
1191       icon_width += 4;
1192     else
1193       /* Add padding if icon is present */
1194       icon_width += infoPtr->uHItemPadding;
1195   }
1196 
1197   for (curItem = 0; curItem < infoPtr->uNumItem; curItem++)
1198   {
1199     TAB_ITEM *curr = TAB_GetItem(infoPtr, curItem);
1200 
1201     /* Set the leftmost position of the tab. */
1202     curr->rect.left = curItemLeftPos;
1203 
1204     if (infoPtr->dwStyle & TCS_FIXEDWIDTH)
1205     {
1206       curr->rect.right = curr->rect.left +
1207         max(infoPtr->tabWidth, icon_width);
1208     }
1209     else if (!curr->pszText)
1210     {
1211       /* If no text use minimum tab width including padding. */
1212       if (infoPtr->tabMinWidth < 0)
1213         curr->rect.right = curr->rect.left + GET_DEFAULT_MIN_TAB_WIDTH(infoPtr);
1214       else
1215       {
1216         curr->rect.right = curr->rect.left + infoPtr->tabMinWidth;
1217 
1218         /* Add extra padding if icon is present */
1219         if (infoPtr->himl && infoPtr->tabMinWidth > 0 && infoPtr->tabMinWidth < DEFAULT_MIN_TAB_WIDTH
1220             && infoPtr->uHItemPadding > 1)
1221           curr->rect.right += EXTRA_ICON_PADDING * (infoPtr->uHItemPadding-1);
1222       }
1223     }
1224     else
1225     {
1226       int tabwidth;
1227       SIZE size;
1228       /* Calculate how wide the tab is depending on the text it contains */
1229       GetTextExtentPoint32W(hdc, curr->pszText,
1230                             lstrlenW(curr->pszText), &size);
1231 
1232       tabwidth = size.cx + icon_width + 2 * infoPtr->uHItemPadding;
1233 
1234       if (infoPtr->tabMinWidth < 0)
1235         tabwidth = max(tabwidth, GET_DEFAULT_MIN_TAB_WIDTH(infoPtr));
1236       else
1237         tabwidth = max(tabwidth, infoPtr->tabMinWidth);
1238 
1239       curr->rect.right = curr->rect.left + tabwidth;
1240       TRACE("for <%s>, rect %s\n", debugstr_w(curr->pszText), wine_dbgstr_rect(&curr->rect));
1241     }
1242 
1243     /*
1244      * Check if this is a multiline tab control and if so
1245      * check to see if we should wrap the tabs
1246      *
1247      * Wrap all these tabs. We will arrange them evenly later.
1248      *
1249      */
1250 
1251     if (((infoPtr->dwStyle & TCS_MULTILINE) || (infoPtr->dwStyle & TCS_VERTICAL)) &&
1252         (curr->rect.right >
1253 	(clientRect.right - CONTROL_BORDER_SIZEX - DISPLAY_AREA_PADDINGX)))
1254     {
1255         curr->rect.right -= curr->rect.left;
1256 
1257 	curr->rect.left = 0;
1258         curItemRowCount++;
1259 	TRACE("wrapping <%s>, rect %s\n", debugstr_w(curr->pszText), wine_dbgstr_rect(&curr->rect));
1260     }
1261 
1262     curr->rect.bottom = 0;
1263     curr->rect.top = curItemRowCount - 1;
1264 
1265     TRACE("Rect: %s\n", wine_dbgstr_rect(&curr->rect));
1266 
1267     /*
1268      * The leftmost position of the next item is the rightmost position
1269      * of this one.
1270      */
1271     if (infoPtr->dwStyle & TCS_BUTTONS)
1272     {
1273       curItemLeftPos = curr->rect.right + BUTTON_SPACINGX;
1274       if (infoPtr->dwStyle & TCS_FLATBUTTONS)
1275         curItemLeftPos += FLAT_BTN_SPACINGX;
1276     }
1277     else
1278       curItemLeftPos = curr->rect.right;
1279   }
1280 
1281   if (!((infoPtr->dwStyle & TCS_MULTILINE) || (infoPtr->dwStyle & TCS_VERTICAL)))
1282   {
1283     /*
1284      * Check if we need a scrolling control.
1285      */
1286     infoPtr->needsScrolling = (curItemLeftPos + (2 * SELECTED_TAB_OFFSET) >
1287                                clientRect.right);
1288 
1289     /* Don't need scrolling, then update infoPtr->leftmostVisible */
1290     if(!infoPtr->needsScrolling)
1291       infoPtr->leftmostVisible = 0;
1292   }
1293   else
1294   {
1295     /*
1296      * No scrolling in Multiline or Vertical styles.
1297      */
1298     infoPtr->needsScrolling = FALSE;
1299     infoPtr->leftmostVisible = 0;
1300   }
1301   TAB_SetupScrolling(infoPtr, &clientRect);
1302 
1303   /* Set the number of rows */
1304   infoPtr->uNumRows = curItemRowCount;
1305 
1306   /* Arrange all tabs evenly if style says so */
1307    if (!(infoPtr->dwStyle & TCS_RAGGEDRIGHT) &&
1308        ((infoPtr->dwStyle & TCS_MULTILINE) || (infoPtr->dwStyle & TCS_VERTICAL)) &&
1309        (infoPtr->uNumItem > 0) &&
1310        (infoPtr->uNumRows > 1))
1311    {
1312       INT tabPerRow,remTab,iRow;
1313       UINT iItm;
1314       INT iCount=0;
1315 
1316       /*
1317        * Ok windows tries to even out the rows. place the same
1318        * number of tabs in each row. So lets give that a shot
1319        */
1320 
1321       tabPerRow = infoPtr->uNumItem / (infoPtr->uNumRows);
1322       remTab = infoPtr->uNumItem % (infoPtr->uNumRows);
1323 
1324       for (iItm=0,iRow=0,iCount=0,curItemLeftPos=0;
1325            iItm<infoPtr->uNumItem;
1326            iItm++,iCount++)
1327       {
1328           /* normalize the current rect */
1329           TAB_ITEM *curr = TAB_GetItem(infoPtr, iItm);
1330 
1331           /* shift the item to the left side of the clientRect */
1332           curr->rect.right -= curr->rect.left;
1333           curr->rect.left = 0;
1334 
1335           TRACE("r=%d, cl=%d, cl.r=%d, iCount=%d, iRow=%d, uNumRows=%d, remTab=%d, tabPerRow=%d\n",
1336 	      curr->rect.right, curItemLeftPos, clientRect.right,
1337 	      iCount, iRow, infoPtr->uNumRows, remTab, tabPerRow);
1338 
1339           /* if we have reached the maximum number of tabs on this row */
1340           /* move to the next row, reset our current item left position and */
1341           /* the count of items on this row */
1342 
1343 	  if (infoPtr->dwStyle & TCS_VERTICAL) {
1344 	      /* Vert: Add the remaining tabs in the *last* remainder rows */
1345 	      if (iCount >= ((iRow>=(INT)infoPtr->uNumRows - remTab)?tabPerRow + 1:tabPerRow)) {
1346 		  iRow++;
1347 		  curItemLeftPos = 0;
1348 		  iCount = 0;
1349 	      }
1350 	  } else {
1351 	      /* Horz: Add the remaining tabs in the *first* remainder rows */
1352 	      if (iCount >= ((iRow<remTab)?tabPerRow + 1:tabPerRow)) {
1353 		  iRow++;
1354 		  curItemLeftPos = 0;
1355 		  iCount = 0;
1356 	      }
1357 	  }
1358 
1359           /* shift the item to the right to place it as the next item in this row */
1360           curr->rect.left += curItemLeftPos;
1361           curr->rect.right += curItemLeftPos;
1362           curr->rect.top = iRow;
1363           if (infoPtr->dwStyle & TCS_BUTTONS)
1364 	  {
1365             curItemLeftPos = curr->rect.right + 1;
1366             if (infoPtr->dwStyle & TCS_FLATBUTTONS)
1367 	      curItemLeftPos += FLAT_BTN_SPACINGX;
1368 	  }
1369           else
1370             curItemLeftPos = curr->rect.right;
1371 
1372           TRACE("arranging <%s>, rect %s\n", debugstr_w(curr->pszText), wine_dbgstr_rect(&curr->rect));
1373       }
1374 
1375       /*
1376        * Justify the rows
1377        */
1378       {
1379 	INT widthDiff, iIndexStart=0, iIndexEnd=0;
1380 	INT remainder;
1381 	INT iCount=0;
1382 
1383         while(iIndexStart < infoPtr->uNumItem)
1384         {
1385           TAB_ITEM *start = TAB_GetItem(infoPtr, iIndexStart);
1386 
1387           /*
1388            * find the index of the row
1389            */
1390           /* find the first item on the next row */
1391           for (iIndexEnd=iIndexStart;
1392               (iIndexEnd < infoPtr->uNumItem) &&
1393  	      (TAB_GetItem(infoPtr, iIndexEnd)->rect.top ==
1394                 start->rect.top) ;
1395               iIndexEnd++)
1396           /* intentionally blank */;
1397 
1398           /*
1399            * we need to justify these tabs so they fill the whole given
1400            * client area
1401            *
1402            */
1403           /* find the amount of space remaining on this row */
1404           widthDiff = clientRect.right - (2 * SELECTED_TAB_OFFSET) -
1405 			TAB_GetItem(infoPtr, iIndexEnd - 1)->rect.right;
1406 
1407 	  /* iCount is the number of tab items on this row */
1408 	  iCount = iIndexEnd - iIndexStart;
1409 
1410 	  if (iCount > 1)
1411 	  {
1412 	    remainder = widthDiff % iCount;
1413 	    widthDiff = widthDiff / iCount;
1414 	    /* add widthDiff/iCount, or extra space/items on row, to each item on this row */
1415 	    for (iIndex=iIndexStart, iCount=0; iIndex < iIndexEnd; iIndex++, iCount++)
1416 	    {
1417               TAB_ITEM *item = TAB_GetItem(infoPtr, iIndex);
1418 
1419 	      item->rect.left += iCount * widthDiff;
1420 	      item->rect.right += (iCount + 1) * widthDiff;
1421 
1422               TRACE("adjusting 1 <%s>, rect %s\n", debugstr_w(item->pszText), wine_dbgstr_rect(&item->rect));
1423 
1424 	    }
1425 	    TAB_GetItem(infoPtr, iIndex - 1)->rect.right += remainder;
1426 	  }
1427 	  else /* we have only one item on this row, make it take up the entire row */
1428 	  {
1429 	    start->rect.left = clientRect.left;
1430 	    start->rect.right = clientRect.right - 4;
1431 
1432             TRACE("adjusting 2 <%s>, rect %s\n", debugstr_w(start->pszText), wine_dbgstr_rect(&start->rect));
1433 	  }
1434 
1435 	  iIndexStart = iIndexEnd;
1436 	}
1437       }
1438   }
1439 
1440   /* if TCS_VERTICAL rotate the tabs so they are along the side of the clientRect */
1441   if(infoPtr->dwStyle & TCS_VERTICAL)
1442   {
1443     RECT rcOriginal;
1444     for(iIndex = 0; iIndex < infoPtr->uNumItem; iIndex++)
1445     {
1446       rcItem = &TAB_GetItem(infoPtr, iIndex)->rect;
1447 
1448       rcOriginal = *rcItem;
1449 
1450       /* this is rotating the items by 90 degrees clockwise around the center of the control */
1451       rcItem->top = (rcOriginal.left - clientRect.left);
1452       rcItem->bottom = rcItem->top + (rcOriginal.right - rcOriginal.left);
1453       rcItem->left = rcOriginal.top;
1454       rcItem->right = rcOriginal.bottom;
1455     }
1456   }
1457 
1458   TAB_EnsureSelectionVisible(infoPtr);
1459   TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
1460 
1461   /* Cleanup */
1462   SelectObject (hdc, hOldFont);
1463   ReleaseDC (infoPtr->hwnd, hdc);
1464 }
1465 
1466 
1467 static void
1468 TAB_EraseTabInterior(const TAB_INFO *infoPtr, HDC hdc, INT iItem, const RECT *drawRect)
1469 {
1470     HBRUSH   hbr = CreateSolidBrush (comctl32_color.clrBtnFace);
1471     BOOL     deleteBrush = TRUE;
1472     RECT     rTemp = *drawRect;
1473 
1474     if (infoPtr->dwStyle & TCS_BUTTONS)
1475     {
1476 	if (iItem == infoPtr->iSelected)
1477 	{
1478 	    /* Background color */
1479 	    if (!(infoPtr->dwStyle & TCS_OWNERDRAWFIXED))
1480 	    {
1481 		DeleteObject(hbr);
1482 		hbr = GetSysColorBrush(COLOR_SCROLLBAR);
1483 
1484 		SetTextColor(hdc, comctl32_color.clr3dFace);
1485 		SetBkColor(hdc, comctl32_color.clr3dHilight);
1486 
1487 		/* if COLOR_WINDOW happens to be the same as COLOR_3DHILIGHT
1488 		* we better use 0x55aa bitmap brush to make scrollbar's background
1489 		* look different from the window background.
1490 		*/
1491 		if (comctl32_color.clr3dHilight == comctl32_color.clrWindow)
1492 		    hbr = COMCTL32_hPattern55AABrush;
1493 
1494 		deleteBrush = FALSE;
1495 	    }
1496 	    FillRect(hdc, &rTemp, hbr);
1497 	}
1498 	else  /* ! selected */
1499 	{
1500 	    if (infoPtr->dwStyle & TCS_FLATBUTTONS)
1501 	    {
1502 		InflateRect(&rTemp, 2, 2);
1503 		FillRect(hdc, &rTemp, hbr);
1504 		if (iItem == infoPtr->iHotTracked ||
1505                    (iItem != infoPtr->iSelected && iItem == infoPtr->uFocus))
1506 		    DrawEdge(hdc, &rTemp, BDR_RAISEDINNER, BF_RECT);
1507 	    }
1508 	    else
1509 		FillRect(hdc, &rTemp, hbr);
1510 	}
1511 
1512     }
1513     else /* !TCS_BUTTONS */
1514     {
1515         InflateRect(&rTemp, -2, -2);
1516         if (!GetWindowTheme (infoPtr->hwnd))
1517 	    FillRect(hdc, &rTemp, hbr);
1518     }
1519 
1520     /* highlighting is drawn on top of previous fills */
1521     if (TAB_GetItem(infoPtr, iItem)->dwState & TCIS_HIGHLIGHTED)
1522     {
1523         if (deleteBrush)
1524         {
1525             DeleteObject(hbr);
1526             deleteBrush = FALSE;
1527         }
1528         hbr = GetSysColorBrush(COLOR_HIGHLIGHT);
1529         FillRect(hdc, &rTemp, hbr);
1530     }
1531 
1532     /* Cleanup */
1533     if (deleteBrush) DeleteObject(hbr);
1534 }
1535 
1536 /******************************************************************************
1537  * TAB_DrawItemInterior
1538  *
1539  * This method is used to draw the interior (text and icon) of a single tab
1540  * into the tab control.
1541  */
1542 static void
1543 TAB_DrawItemInterior(const TAB_INFO *infoPtr, HDC hdc, INT iItem, RECT *drawRect)
1544 {
1545   RECT localRect;
1546 
1547   HPEN   htextPen;
1548   HPEN   holdPen;
1549   INT    oldBkMode;
1550   HFONT  hOldFont;
1551 #ifdef __REACTOS__
1552 HTHEME    theme = GetWindowTheme (infoPtr->hwnd);
1553 #endif
1554 
1555 /*  if (drawRect == NULL) */
1556   {
1557     BOOL isVisible;
1558     RECT itemRect;
1559     RECT selectedRect;
1560 
1561     /*
1562      * Get the rectangle for the item.
1563      */
1564     isVisible = TAB_InternalGetItemRect(infoPtr, iItem, &itemRect, &selectedRect);
1565     if (!isVisible)
1566       return;
1567 
1568     /*
1569      * Make sure drawRect points to something valid; simplifies code.
1570      */
1571     drawRect = &localRect;
1572 
1573     /*
1574      * This logic copied from the part of TAB_DrawItem which draws
1575      * the tab background.  It's important to keep it in sync.  I
1576      * would have liked to avoid code duplication, but couldn't figure
1577      * out how without making spaghetti of TAB_DrawItem.
1578      */
1579     if (iItem == infoPtr->iSelected)
1580       *drawRect = selectedRect;
1581     else
1582       *drawRect = itemRect;
1583 
1584     if (infoPtr->dwStyle & TCS_BUTTONS)
1585     {
1586       if (iItem == infoPtr->iSelected)
1587       {
1588 	drawRect->left   += 4;
1589 	drawRect->top    += 4;
1590 	drawRect->right  -= 4;
1591 
1592 	if (infoPtr->dwStyle & TCS_VERTICAL)
1593 	{
1594 	  if (!(infoPtr->dwStyle & TCS_BOTTOM)) drawRect->right  += 1;
1595 	  drawRect->bottom   -= 4;
1596 	}
1597 	else
1598 	{
1599 	  if (infoPtr->dwStyle & TCS_BOTTOM)
1600 	  {
1601 	    drawRect->top    -= 2;
1602 	    drawRect->bottom -= 4;
1603 	  }
1604 	  else
1605 	    drawRect->bottom -= 1;
1606 	}
1607       }
1608       else
1609         InflateRect(drawRect, -2, -2);
1610     }
1611     else
1612     {
1613       if ((infoPtr->dwStyle & TCS_VERTICAL) && (infoPtr->dwStyle & TCS_BOTTOM))
1614       {
1615         if (iItem != infoPtr->iSelected)
1616 	{
1617 	  drawRect->left   += 2;
1618           InflateRect(drawRect, 0, -2);
1619 	}
1620       }
1621       else if (infoPtr->dwStyle & TCS_VERTICAL)
1622       {
1623         if (iItem == infoPtr->iSelected)
1624 	{
1625 	  drawRect->right  += 1;
1626 	}
1627 	else
1628 	{
1629 	  drawRect->right  -= 2;
1630           InflateRect(drawRect, 0, -2);
1631 	}
1632       }
1633       else if (infoPtr->dwStyle & TCS_BOTTOM)
1634       {
1635         if (iItem == infoPtr->iSelected)
1636 	{
1637 	  drawRect->top    -= 2;
1638 	}
1639 	else
1640 	{
1641 	  InflateRect(drawRect, -2, -2);
1642           drawRect->bottom += 2;
1643 	}
1644       }
1645       else
1646       {
1647         if (iItem == infoPtr->iSelected)
1648 	{
1649 	  drawRect->bottom += 3;
1650 	}
1651 	else
1652 	{
1653 	  drawRect->bottom -= 2;
1654 	  InflateRect(drawRect, -2, 0);
1655 	}
1656       }
1657     }
1658   }
1659   TRACE("drawRect=(%s)\n", wine_dbgstr_rect(drawRect));
1660 
1661   /* Clear interior */
1662   TAB_EraseTabInterior (infoPtr, hdc, iItem, drawRect);
1663 
1664   /* Draw the focus rectangle */
1665   if (!(infoPtr->dwStyle & TCS_FOCUSNEVER) &&
1666       (GetFocus() == infoPtr->hwnd) &&
1667       (iItem == infoPtr->uFocus) )
1668   {
1669     RECT rFocus = *drawRect;
1670 
1671     if (!(infoPtr->dwStyle & TCS_BUTTONS)) InflateRect(&rFocus, -3, -3);
1672     if (infoPtr->dwStyle & TCS_BOTTOM && !(infoPtr->dwStyle & TCS_VERTICAL))
1673       rFocus.top -= 3;
1674 
1675     /* focus should stay on selected item for TCS_BUTTONS style */
1676     if (!((infoPtr->dwStyle & TCS_BUTTONS) && (infoPtr->iSelected != iItem)))
1677       DrawFocusRect(hdc, &rFocus);
1678   }
1679 
1680   /*
1681    * Text pen
1682    */
1683   htextPen = CreatePen( PS_SOLID, 1, comctl32_color.clrBtnText );
1684   holdPen  = SelectObject(hdc, htextPen);
1685   hOldFont = SelectObject(hdc, infoPtr->hFont);
1686 
1687   /*
1688    * Setup for text output
1689   */
1690   oldBkMode = SetBkMode(hdc, TRANSPARENT);
1691   if (!GetWindowTheme (infoPtr->hwnd) || (infoPtr->dwStyle & TCS_BUTTONS))
1692   {
1693     if ((infoPtr->dwStyle & TCS_HOTTRACK) && (iItem == infoPtr->iHotTracked) &&
1694         !(infoPtr->dwStyle & TCS_FLATBUTTONS))
1695       SetTextColor(hdc, comctl32_color.clrHighlight);
1696     else if (TAB_GetItem(infoPtr, iItem)->dwState & TCIS_HIGHLIGHTED)
1697       SetTextColor(hdc, comctl32_color.clrHighlightText);
1698     else
1699       SetTextColor(hdc, comctl32_color.clrBtnText);
1700   }
1701 
1702   /*
1703    * if owner draw, tell the owner to draw
1704    */
1705   if ((infoPtr->dwStyle & TCS_OWNERDRAWFIXED) && IsWindow(infoPtr->hwndNotify))
1706   {
1707     DRAWITEMSTRUCT dis;
1708     UINT id;
1709 
1710     drawRect->top += 2;
1711     drawRect->right -= 1;
1712     if ( iItem == infoPtr->iSelected )
1713         InflateRect(drawRect, -1, 0);
1714 
1715     id = (UINT)GetWindowLongPtrW( infoPtr->hwnd, GWLP_ID );
1716 
1717     /* fill DRAWITEMSTRUCT */
1718     dis.CtlType    = ODT_TAB;
1719     dis.CtlID      = id;
1720     dis.itemID     = iItem;
1721     dis.itemAction = ODA_DRAWENTIRE;
1722     dis.itemState = 0;
1723     if ( iItem == infoPtr->iSelected )
1724       dis.itemState |= ODS_SELECTED;
1725     if (infoPtr->uFocus == iItem)
1726       dis.itemState |= ODS_FOCUS;
1727     dis.hwndItem = infoPtr->hwnd;
1728     dis.hDC      = hdc;
1729     dis.rcItem = *drawRect;
1730 
1731     /* when extra data fits ULONG_PTR, store it directly */
1732     if (infoPtr->cbInfo > sizeof(LPARAM))
1733         dis.itemData =  (ULONG_PTR) TAB_GetItem(infoPtr, iItem)->extra;
1734     else
1735     {
1736         /* this could be considered broken on 64 bit, but that's how it works -
1737            only first 4 bytes are copied */
1738         dis.itemData = 0;
1739         memcpy(&dis.itemData, (ULONG_PTR*)TAB_GetItem(infoPtr, iItem)->extra, 4);
1740     }
1741 
1742     /* draw notification */
1743     SendMessageW( infoPtr->hwndNotify, WM_DRAWITEM, id, (LPARAM)&dis );
1744   }
1745   else
1746   {
1747     TAB_ITEM *item = TAB_GetItem(infoPtr, iItem);
1748     RECT rcTemp;
1749     RECT rcImage;
1750 
1751     /* used to center the icon and text in the tab */
1752     RECT rcText;
1753     INT center_offset_h, center_offset_v;
1754 
1755     /* set rcImage to drawRect, we will use top & left in our ImageList_Draw call */
1756     rcImage = *drawRect;
1757 
1758     rcTemp = *drawRect;
1759     SetRectEmpty(&rcText);
1760 
1761     /* get the rectangle that the text fits in */
1762     if (item->pszText)
1763     {
1764       DrawTextW(hdc, item->pszText, -1, &rcText, DT_CALCRECT);
1765     }
1766     /*
1767      * If not owner draw, then do the drawing ourselves.
1768      *
1769      * Draw the icon.
1770      */
1771     if (infoPtr->himl && item->iImage != -1)
1772     {
1773       INT cx;
1774       INT cy;
1775 
1776       ImageList_GetIconSize(infoPtr->himl, &cx, &cy);
1777 
1778       if(infoPtr->dwStyle & TCS_VERTICAL)
1779       {
1780         center_offset_h = ((drawRect->bottom - drawRect->top) - (cy + infoPtr->uHItemPadding + (rcText.right  - rcText.left))) / 2;
1781         center_offset_v = ((drawRect->right - drawRect->left) - cx) / 2;
1782       }
1783       else
1784       {
1785         center_offset_h = ((drawRect->right - drawRect->left) - (cx + infoPtr->uHItemPadding + (rcText.right  - rcText.left))) / 2;
1786         center_offset_v = ((drawRect->bottom - drawRect->top) - cy) / 2;
1787       }
1788 
1789       /* if an item is selected, the icon is shifted up instead of down */
1790       if (iItem == infoPtr->iSelected)
1791         center_offset_v -= infoPtr->uVItemPadding / 2;
1792       else
1793         center_offset_v += infoPtr->uVItemPadding / 2;
1794 
1795       if (infoPtr->dwStyle & TCS_FIXEDWIDTH && infoPtr->dwStyle & (TCS_FORCELABELLEFT | TCS_FORCEICONLEFT))
1796 	center_offset_h = infoPtr->uHItemPadding;
1797 
1798       if (center_offset_h < 2)
1799         center_offset_h = 2;
1800 
1801       if (center_offset_v < 0)
1802         center_offset_v = 0;
1803 
1804       TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%s), textlen=%d\n",
1805 	  debugstr_w(item->pszText), center_offset_h, center_offset_v,
1806           wine_dbgstr_rect(drawRect), (rcText.right-rcText.left));
1807 
1808       if((infoPtr->dwStyle & TCS_VERTICAL) && (infoPtr->dwStyle & TCS_BOTTOM))
1809       {
1810         rcImage.top = drawRect->top + center_offset_h;
1811 	/* if tab is TCS_VERTICAL and TCS_BOTTOM, the text is drawn from the */
1812 	/* right side of the tab, but the image still uses the left as its x position */
1813 	/* this keeps the image always drawn off of the same side of the tab */
1814         rcImage.left = drawRect->right - cx - center_offset_v;
1815         drawRect->top += cy + infoPtr->uHItemPadding;
1816       }
1817       else if(infoPtr->dwStyle & TCS_VERTICAL)
1818       {
1819         rcImage.top  = drawRect->bottom - cy - center_offset_h;
1820 	rcImage.left = drawRect->left + center_offset_v;
1821         drawRect->bottom -= cy + infoPtr->uHItemPadding;
1822       }
1823       else /* normal style, whether TCS_BOTTOM or not */
1824       {
1825         rcImage.left = drawRect->left + center_offset_h;
1826 	rcImage.top = drawRect->top + center_offset_v;
1827         drawRect->left += cx + infoPtr->uHItemPadding;
1828       }
1829 
1830       TRACE("drawing image=%d, left=%d, top=%d\n",
1831 	    item->iImage, rcImage.left, rcImage.top-1);
1832       ImageList_Draw
1833         (
1834         infoPtr->himl,
1835         item->iImage,
1836         hdc,
1837         rcImage.left,
1838         rcImage.top,
1839         ILD_NORMAL
1840         );
1841     }
1842 
1843     /* Now position text */
1844     if (infoPtr->dwStyle & TCS_FIXEDWIDTH && infoPtr->dwStyle & TCS_FORCELABELLEFT)
1845       center_offset_h = infoPtr->uHItemPadding;
1846     else
1847       if(infoPtr->dwStyle & TCS_VERTICAL)
1848         center_offset_h = ((drawRect->bottom - drawRect->top) - (rcText.right - rcText.left)) / 2;
1849       else
1850         center_offset_h = ((drawRect->right - drawRect->left) - (rcText.right - rcText.left)) / 2;
1851 
1852     if(infoPtr->dwStyle & TCS_VERTICAL)
1853     {
1854       if(infoPtr->dwStyle & TCS_BOTTOM)
1855         drawRect->top+=center_offset_h;
1856       else
1857         drawRect->bottom-=center_offset_h;
1858 
1859       center_offset_v = ((drawRect->right - drawRect->left) - (rcText.bottom - rcText.top)) / 2;
1860     }
1861     else
1862     {
1863       drawRect->left += center_offset_h;
1864       center_offset_v = ((drawRect->bottom - drawRect->top) - (rcText.bottom - rcText.top)) / 2;
1865     }
1866 
1867     /* if an item is selected, the text is shifted up instead of down */
1868     if (iItem == infoPtr->iSelected)
1869         center_offset_v -= infoPtr->uVItemPadding / 2;
1870     else
1871         center_offset_v += infoPtr->uVItemPadding / 2;
1872 
1873     if (center_offset_v < 0)
1874       center_offset_v = 0;
1875 
1876     if(infoPtr->dwStyle & TCS_VERTICAL)
1877       drawRect->left += center_offset_v;
1878     else
1879       drawRect->top += center_offset_v;
1880 
1881     /* Draw the text */
1882     if(infoPtr->dwStyle & TCS_VERTICAL) /* if we are vertical rotate the text and each character */
1883     {
1884       LOGFONTW logfont;
1885       HFONT hFont;
1886       INT nEscapement = 900;
1887       INT nOrientation = 900;
1888 
1889       if(infoPtr->dwStyle & TCS_BOTTOM)
1890       {
1891         nEscapement = -900;
1892         nOrientation = -900;
1893       }
1894 
1895       /* to get a font with the escapement and orientation we are looking for, we need to */
1896       /* call CreateFontIndirect, which requires us to set the values of the logfont we pass in */
1897       if (!GetObjectW(infoPtr->hFont, sizeof(logfont), &logfont))
1898         GetObjectW(GetStockObject(DEFAULT_GUI_FONT), sizeof(logfont), &logfont);
1899 
1900       logfont.lfEscapement = nEscapement;
1901       logfont.lfOrientation = nOrientation;
1902       hFont = CreateFontIndirectW(&logfont);
1903       SelectObject(hdc, hFont);
1904 
1905       if (item->pszText)
1906       {
1907         ExtTextOutW(hdc,
1908         (infoPtr->dwStyle & TCS_BOTTOM) ? drawRect->right : drawRect->left,
1909         (!(infoPtr->dwStyle & TCS_BOTTOM)) ? drawRect->bottom : drawRect->top,
1910         ETO_CLIPPED,
1911         drawRect,
1912         item->pszText,
1913         lstrlenW(item->pszText),
1914         0);
1915       }
1916 
1917       DeleteObject(hFont);
1918     }
1919     else
1920     {
1921       TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%s), textlen=%d\n",
1922 	  debugstr_w(item->pszText), center_offset_h, center_offset_v,
1923           wine_dbgstr_rect(drawRect), (rcText.right-rcText.left));
1924 #ifdef __REACTOS__
1925       if (theme && item->pszText)
1926       {
1927           int partIndex = iItem == infoPtr->iSelected ? TABP_TABITEM : TABP_TOPTABITEM;
1928           int stateId = TIS_NORMAL;
1929 
1930           if (iItem == infoPtr->iSelected)
1931               stateId = TIS_SELECTED;
1932           else if (iItem == infoPtr->iHotTracked)
1933               stateId = TIS_HOT;
1934           else if (iItem == infoPtr->uFocus)
1935               stateId = TIS_FOCUSED;
1936 
1937           DrawThemeText(theme,
1938                         hdc,
1939                         partIndex,
1940                         stateId,
1941                         item->pszText,
1942                         lstrlenW(item->pszText),
1943                         DT_LEFT | DT_SINGLELINE, 0, drawRect);
1944       }
1945       else
1946 #endif
1947       if (item->pszText)
1948       {
1949         DrawTextW
1950         (
1951           hdc,
1952           item->pszText,
1953           lstrlenW(item->pszText),
1954           drawRect,
1955           DT_LEFT | DT_SINGLELINE
1956         );
1957       }
1958     }
1959 
1960     *drawRect = rcTemp; /* restore drawRect */
1961   }
1962 
1963   /*
1964   * Cleanup
1965   */
1966   SelectObject(hdc, hOldFont);
1967   SetBkMode(hdc, oldBkMode);
1968   SelectObject(hdc, holdPen);
1969   DeleteObject( htextPen );
1970 }
1971 
1972 /******************************************************************************
1973  * TAB_DrawItem
1974  *
1975  * This method is used to draw a single tab into the tab control.
1976  */
1977 static void TAB_DrawItem(const TAB_INFO *infoPtr, HDC  hdc, INT  iItem)
1978 {
1979   RECT      itemRect;
1980   RECT      selectedRect;
1981   BOOL      isVisible;
1982   RECT      r, fillRect, r1;
1983   INT       clRight = 0;
1984   INT       clBottom = 0;
1985   COLORREF  bkgnd, corner;
1986   HTHEME    theme;
1987 
1988   /*
1989    * Get the rectangle for the item.
1990    */
1991   isVisible = TAB_InternalGetItemRect(infoPtr,
1992 				      iItem,
1993 				      &itemRect,
1994 				      &selectedRect);
1995 
1996   if (isVisible)
1997   {
1998     RECT rUD, rC;
1999 
2000     /* Clip UpDown control to not draw over it */
2001     if (infoPtr->needsScrolling)
2002     {
2003       GetWindowRect(infoPtr->hwnd, &rC);
2004       GetWindowRect(infoPtr->hwndUpDown, &rUD);
2005       ExcludeClipRect(hdc, rUD.left - rC.left, rUD.top - rC.top, rUD.right - rC.left, rUD.bottom - rC.top);
2006     }
2007 
2008     /* If you need to see what the control is doing,
2009      * then override these variables. They will change what
2010      * fill colors are used for filling the tabs, and the
2011      * corners when drawing the edge.
2012      */
2013     bkgnd = comctl32_color.clrBtnFace;
2014     corner = comctl32_color.clrBtnFace;
2015 
2016     if (infoPtr->dwStyle & TCS_BUTTONS)
2017     {
2018       /* Get item rectangle */
2019       r = itemRect;
2020 
2021       /* Separators between flat buttons */
2022       if ((infoPtr->dwStyle & TCS_FLATBUTTONS) && (infoPtr->exStyle & TCS_EX_FLATSEPARATORS))
2023       {
2024 	r1 = r;
2025 	r1.right += (FLAT_BTN_SPACINGX -2);
2026 	DrawEdge(hdc, &r1, EDGE_ETCHED, BF_RIGHT);
2027       }
2028 
2029       if (iItem == infoPtr->iSelected)
2030       {
2031 	DrawEdge(hdc, &r, EDGE_SUNKEN, BF_SOFT|BF_RECT);
2032 
2033 	OffsetRect(&r, 1, 1);
2034       }
2035       else  /* ! selected */
2036       {
2037         DWORD state = TAB_GetItem(infoPtr, iItem)->dwState;
2038 
2039         if ((state & TCIS_BUTTONPRESSED) || (iItem == infoPtr->uFocus))
2040           DrawEdge(hdc, &r, EDGE_SUNKEN, BF_SOFT|BF_RECT);
2041         else
2042           if (!(infoPtr->dwStyle & TCS_FLATBUTTONS))
2043             DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_RECT);
2044       }
2045     }
2046     else /* !TCS_BUTTONS */
2047     {
2048       /* We draw a rectangle of different sizes depending on the selection
2049        * state. */
2050       if (iItem == infoPtr->iSelected) {
2051 	RECT rect;
2052 	GetClientRect (infoPtr->hwnd, &rect);
2053 	clRight = rect.right;
2054 	clBottom = rect.bottom;
2055         r = selectedRect;
2056       }
2057       else
2058         r = itemRect;
2059 
2060       /*
2061        * Erase the background. (Delay it but setup rectangle.)
2062        * This is necessary when drawing the selected item since it is larger
2063        * than the others, it might overlap with stuff already drawn by the
2064        * other tabs
2065        */
2066       fillRect = r;
2067 
2068       /* Draw themed tabs - but only if they are at the top.
2069        * Windows draws even side or bottom tabs themed, with wacky results.
2070        * However, since in Wine apps may get themed that did not opt in via
2071        * a manifest avoid theming when we know the result will be wrong */
2072       if ((theme = GetWindowTheme (infoPtr->hwnd))
2073           && ((infoPtr->dwStyle & (TCS_VERTICAL | TCS_BOTTOM)) == 0))
2074       {
2075           static const int partIds[8] = {
2076               /* Normal item */
2077               TABP_TABITEM,
2078               TABP_TABITEMLEFTEDGE,
2079               TABP_TABITEMRIGHTEDGE,
2080               TABP_TABITEMBOTHEDGE,
2081               /* Selected tab */
2082               TABP_TOPTABITEM,
2083               TABP_TOPTABITEMLEFTEDGE,
2084               TABP_TOPTABITEMRIGHTEDGE,
2085               TABP_TOPTABITEMBOTHEDGE,
2086           };
2087           int partIndex = 0;
2088           int stateId = TIS_NORMAL;
2089 
2090           /* selected and unselected tabs have different parts */
2091           if (iItem == infoPtr->iSelected)
2092               partIndex += 4;
2093           /* The part also differs on the position of a tab on a line.
2094            * "Visually" determining the position works well enough. */
2095           GetClientRect(infoPtr->hwnd, &r1);
2096           if(selectedRect.left == 0)
2097               partIndex += 1;
2098           if(selectedRect.right == r1.right)
2099               partIndex += 2;
2100 
2101           if (iItem == infoPtr->iSelected)
2102               stateId = TIS_SELECTED;
2103           else if (iItem == infoPtr->iHotTracked)
2104               stateId = TIS_HOT;
2105           else if (iItem == infoPtr->uFocus)
2106               stateId = TIS_FOCUSED;
2107 
2108           /* Adjust rectangle for bottommost row */
2109           if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2110             r.bottom += 3;
2111 
2112           DrawThemeBackground (theme, hdc, partIds[partIndex], stateId, &r, NULL);
2113           GetThemeBackgroundContentRect (theme, hdc, partIds[partIndex], stateId, &r, &r);
2114       }
2115       else if(infoPtr->dwStyle & TCS_VERTICAL)
2116       {
2117 	/* These are for adjusting the drawing of a Selected tab      */
2118 	/* The initial values are for the normal case of non-Selected */
2119 	int ZZ = 1;   /* Do not stretch if selected */
2120 	if (iItem == infoPtr->iSelected) {
2121 	    ZZ = 0;
2122 
2123 	    /* if leftmost draw the line longer */
2124 	    if(selectedRect.top == 0)
2125 		fillRect.top += CONTROL_BORDER_SIZEY;
2126 	    /* if rightmost draw the line longer */
2127 	    if(selectedRect.bottom == clBottom)
2128 		fillRect.bottom -= CONTROL_BORDER_SIZEY;
2129 	}
2130 
2131         if (infoPtr->dwStyle & TCS_BOTTOM)
2132         {
2133 	  /* Adjust both rectangles to match native */
2134 	  r.left += (1-ZZ);
2135 
2136           TRACE("<right> item=%d, fill=(%s), edge=(%s)\n",
2137                 iItem, wine_dbgstr_rect(&fillRect), wine_dbgstr_rect(&r));
2138 
2139 	  /* Clear interior */
2140 	  SetBkColor(hdc, bkgnd);
2141 	  ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2142 
2143 	  /* Draw rectangular edge around tab */
2144 	  DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_RIGHT|BF_TOP|BF_BOTTOM);
2145 
2146 	  /* Now erase the top corner and draw diagonal edge */
2147 	  SetBkColor(hdc, corner);
2148 	  r1.left = r.right - ROUND_CORNER_SIZE - 1;
2149 	  r1.top = r.top;
2150 	  r1.right = r.right;
2151 	  r1.bottom = r1.top + ROUND_CORNER_SIZE;
2152 	  ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2153 	  r1.right--;
2154 	  DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPLEFT);
2155 
2156 	  /* Now erase the bottom corner and draw diagonal edge */
2157 	  r1.left = r.right - ROUND_CORNER_SIZE - 1;
2158 	  r1.bottom = r.bottom;
2159 	  r1.right = r.right;
2160 	  r1.top = r1.bottom - ROUND_CORNER_SIZE;
2161 	  ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2162 	  r1.right--;
2163 	  DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMLEFT);
2164 
2165 	  if ((iItem == infoPtr->iSelected) && (selectedRect.top == 0)) {
2166 	      r1 = r;
2167 	      r1.right = r1.left;
2168 	      r1.left--;
2169 	      DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_TOP);
2170 	  }
2171 
2172         }
2173         else
2174         {
2175           TRACE("<left> item=%d, fill=(%s), edge=(%s)\n",
2176                 iItem, wine_dbgstr_rect(&fillRect), wine_dbgstr_rect(&r));
2177 
2178 	  /* Clear interior */
2179 	  SetBkColor(hdc, bkgnd);
2180 	  ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2181 
2182 	  /* Draw rectangular edge around tab */
2183 	  DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_TOP|BF_BOTTOM);
2184 
2185 	  /* Now erase the top corner and draw diagonal edge */
2186 	  SetBkColor(hdc, corner);
2187 	  r1.left = r.left;
2188 	  r1.top = r.top;
2189 	  r1.right = r1.left + ROUND_CORNER_SIZE + 1;
2190 	  r1.bottom = r1.top + ROUND_CORNER_SIZE;
2191 	  ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2192 	  r1.left++;
2193 	  DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPRIGHT);
2194 
2195 	  /* Now erase the bottom corner and draw diagonal edge */
2196 	  r1.left = r.left;
2197 	  r1.bottom = r.bottom;
2198 	  r1.right = r1.left + ROUND_CORNER_SIZE + 1;
2199 	  r1.top = r1.bottom - ROUND_CORNER_SIZE;
2200 	  ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2201 	  r1.left++;
2202 	  DrawEdge(hdc, &r1, EDGE_SUNKEN, BF_DIAGONAL_ENDTOPLEFT);
2203         }
2204       }
2205       else  /* ! TCS_VERTICAL */
2206       {
2207 	/* These are for adjusting the drawing of a Selected tab      */
2208 	/* The initial values are for the normal case of non-Selected */
2209 	if (iItem == infoPtr->iSelected) {
2210 	    /* if leftmost draw the line longer */
2211 	    if(selectedRect.left == 0)
2212 		fillRect.left += CONTROL_BORDER_SIZEX;
2213 	    /* if rightmost draw the line longer */
2214 	    if(selectedRect.right == clRight)
2215 		fillRect.right -= CONTROL_BORDER_SIZEX;
2216 	}
2217 
2218         if (infoPtr->dwStyle & TCS_BOTTOM)
2219         {
2220 	  /* Adjust both rectangles for topmost row */
2221 	  if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2222 	  {
2223 	    fillRect.top -= 2;
2224 	    r.top -= 1;
2225 	  }
2226 
2227           TRACE("<bottom> item=%d, fill=(%s), edge=(%s)\n",
2228                 iItem, wine_dbgstr_rect(&fillRect), wine_dbgstr_rect(&r));
2229 
2230 	  /* Clear interior */
2231 	  SetBkColor(hdc, bkgnd);
2232 	  ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2233 
2234 	  /* Draw rectangular edge around tab */
2235 	  DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_BOTTOM|BF_RIGHT);
2236 
2237 	  /* Now erase the righthand corner and draw diagonal edge */
2238 	  SetBkColor(hdc, corner);
2239 	  r1.left = r.right - ROUND_CORNER_SIZE;
2240 	  r1.bottom = r.bottom;
2241 	  r1.right = r.right;
2242 	  r1.top = r1.bottom - ROUND_CORNER_SIZE - 1;
2243 	  ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2244 	  r1.bottom--;
2245 	  DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMLEFT);
2246 
2247 	  /* Now erase the lefthand corner and draw diagonal edge */
2248 	  r1.left = r.left;
2249 	  r1.bottom = r.bottom;
2250 	  r1.right = r1.left + ROUND_CORNER_SIZE;
2251 	  r1.top = r1.bottom - ROUND_CORNER_SIZE - 1;
2252 	  ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2253 	  r1.bottom--;
2254 	  DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPLEFT);
2255 
2256 	  if (iItem == infoPtr->iSelected)
2257 	  {
2258 	    r.top += 2;
2259 	    r.left += 1;
2260 	    if (selectedRect.left == 0)
2261 	    {
2262 	      r1 = r;
2263 	      r1.bottom = r1.top;
2264 	      r1.top--;
2265 	      DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_LEFT);
2266 	    }
2267 	  }
2268 
2269         }
2270         else
2271         {
2272 	  /* Adjust both rectangles for bottommost row */
2273 	  if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2274 	  {
2275 	    fillRect.bottom += 3;
2276 	    r.bottom += 2;
2277 	  }
2278 
2279           TRACE("<top> item=%d, fill=(%s), edge=(%s)\n",
2280                 iItem, wine_dbgstr_rect(&fillRect), wine_dbgstr_rect(&r));
2281 
2282 	  /* Clear interior */
2283 	  SetBkColor(hdc, bkgnd);
2284 	  ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2285 
2286 	  /* Draw rectangular edge around tab */
2287 	  DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_TOP|BF_RIGHT);
2288 
2289 	  /* Now erase the righthand corner and draw diagonal edge */
2290 	  SetBkColor(hdc, corner);
2291 	  r1.left = r.right - ROUND_CORNER_SIZE;
2292 	  r1.top = r.top;
2293 	  r1.right = r.right;
2294 	  r1.bottom = r1.top + ROUND_CORNER_SIZE + 1;
2295 	  ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2296 	  r1.top++;
2297 	  DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMRIGHT);
2298 
2299 	  /* Now erase the lefthand corner and draw diagonal edge */
2300 	  r1.left = r.left;
2301 	  r1.top = r.top;
2302 	  r1.right = r1.left + ROUND_CORNER_SIZE;
2303 	  r1.bottom = r1.top + ROUND_CORNER_SIZE + 1;
2304 	  ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2305 	  r1.top++;
2306 	  DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPRIGHT);
2307         }
2308       }
2309     }
2310 
2311     TAB_DumpItemInternal(infoPtr, iItem);
2312 
2313     /* This modifies r to be the text rectangle. */
2314     TAB_DrawItemInterior(infoPtr, hdc, iItem, &r);
2315   }
2316 }
2317 
2318 /******************************************************************************
2319  * TAB_DrawBorder
2320  *
2321  * This method is used to draw the raised border around the tab control
2322  * "content" area.
2323  */
2324 static void TAB_DrawBorder(const TAB_INFO *infoPtr, HDC hdc)
2325 {
2326   RECT rect;
2327   HTHEME theme = GetWindowTheme (infoPtr->hwnd);
2328 
2329   GetClientRect (infoPtr->hwnd, &rect);
2330 
2331   /*
2332    * Adjust for the style
2333    */
2334 
2335   if (infoPtr->uNumItem)
2336   {
2337     if ((infoPtr->dwStyle & TCS_BOTTOM) && !(infoPtr->dwStyle & TCS_VERTICAL))
2338       rect.bottom -= infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2339     else if((infoPtr->dwStyle & TCS_BOTTOM) && (infoPtr->dwStyle & TCS_VERTICAL))
2340       rect.right  -= infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2341     else if(infoPtr->dwStyle & TCS_VERTICAL)
2342       rect.left   += infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2343     else /* not TCS_VERTICAL and not TCS_BOTTOM */
2344       rect.top    += infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2345   }
2346 
2347   TRACE("border=(%s)\n", wine_dbgstr_rect(&rect));
2348 
2349   if (theme)
2350       DrawThemeBackground (theme, hdc, TABP_PANE, 0, &rect, NULL);
2351   else
2352       DrawEdge(hdc, &rect, EDGE_RAISED, BF_SOFT|BF_RECT);
2353 }
2354 
2355 /******************************************************************************
2356  * TAB_Refresh
2357  *
2358  * This method repaints the tab control..
2359  */
2360 static void TAB_Refresh (const TAB_INFO *infoPtr, HDC hdc)
2361 {
2362   HFONT hOldFont;
2363   INT i;
2364 
2365   if (!infoPtr->DoRedraw)
2366     return;
2367 
2368   hOldFont = SelectObject (hdc, infoPtr->hFont);
2369 
2370   if (infoPtr->dwStyle & TCS_BUTTONS)
2371   {
2372     for (i = 0; i < infoPtr->uNumItem; i++)
2373       TAB_DrawItem (infoPtr, hdc, i);
2374   }
2375   else
2376   {
2377     /* Draw all the non selected item first */
2378     for (i = 0; i < infoPtr->uNumItem; i++)
2379     {
2380       if (i != infoPtr->iSelected)
2381 	TAB_DrawItem (infoPtr, hdc, i);
2382     }
2383 
2384     /* Now, draw the border, draw it before the selected item
2385      * since the selected item overwrites part of the border. */
2386     TAB_DrawBorder (infoPtr, hdc);
2387 
2388     /* Then, draw the selected item */
2389     TAB_DrawItem (infoPtr, hdc, infoPtr->iSelected);
2390   }
2391 
2392   SelectObject (hdc, hOldFont);
2393 }
2394 
2395 static inline DWORD TAB_GetRowCount (const TAB_INFO *infoPtr)
2396 {
2397   TRACE("(%p)\n", infoPtr);
2398   return infoPtr->uNumRows;
2399 }
2400 
2401 static inline LRESULT TAB_SetRedraw (TAB_INFO *infoPtr, BOOL doRedraw)
2402 {
2403   infoPtr->DoRedraw = doRedraw;
2404   return 0;
2405 }
2406 
2407 /******************************************************************************
2408  * TAB_EnsureSelectionVisible
2409  *
2410  * This method will make sure that the current selection is completely
2411  * visible by scrolling until it is.
2412  */
2413 static void TAB_EnsureSelectionVisible(
2414   TAB_INFO* infoPtr)
2415 {
2416   INT iSelected = infoPtr->iSelected;
2417   INT iOrigLeftmostVisible = infoPtr->leftmostVisible;
2418 
2419   if (iSelected < 0)
2420     return;
2421 
2422   /* set the items row to the bottommost row or topmost row depending on
2423    * style */
2424   if ((infoPtr->uNumRows > 1) && !(infoPtr->dwStyle & TCS_BUTTONS))
2425   {
2426       TAB_ITEM *selected = TAB_GetItem(infoPtr, iSelected);
2427       INT newselected;
2428       INT iTargetRow;
2429 
2430       if(infoPtr->dwStyle & TCS_VERTICAL)
2431         newselected = selected->rect.left;
2432       else
2433         newselected = selected->rect.top;
2434 
2435       /* the target row is always (number of rows - 1)
2436          as row 0 is furthest from the clientRect */
2437       iTargetRow = infoPtr->uNumRows - 1;
2438 
2439       if (newselected != iTargetRow)
2440       {
2441          UINT i;
2442          if(infoPtr->dwStyle & TCS_VERTICAL)
2443          {
2444            for (i=0; i < infoPtr->uNumItem; i++)
2445            {
2446              /* move everything in the row of the selected item to the iTargetRow */
2447              TAB_ITEM *item = TAB_GetItem(infoPtr, i);
2448 
2449              if (item->rect.left == newselected )
2450                  item->rect.left = iTargetRow;
2451              else
2452              {
2453                if (item->rect.left > newselected)
2454                  item->rect.left-=1;
2455              }
2456            }
2457          }
2458          else
2459          {
2460            for (i=0; i < infoPtr->uNumItem; i++)
2461            {
2462              TAB_ITEM *item = TAB_GetItem(infoPtr, i);
2463 
2464              if (item->rect.top == newselected )
2465                  item->rect.top = iTargetRow;
2466              else
2467              {
2468                if (item->rect.top > newselected)
2469                  item->rect.top-=1;
2470              }
2471           }
2472         }
2473         TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
2474       }
2475   }
2476 
2477   /*
2478    * Do the trivial cases first.
2479    */
2480   if ( (!infoPtr->needsScrolling) ||
2481        (infoPtr->hwndUpDown==0) || (infoPtr->dwStyle & TCS_VERTICAL))
2482     return;
2483 
2484   if (infoPtr->leftmostVisible >= iSelected)
2485   {
2486     infoPtr->leftmostVisible = iSelected;
2487   }
2488   else
2489   {
2490      TAB_ITEM *selected = TAB_GetItem(infoPtr, iSelected);
2491      RECT r;
2492      INT width;
2493      UINT i;
2494 
2495      /* Calculate the part of the client area that is visible */
2496      GetClientRect(infoPtr->hwnd, &r);
2497      width = r.right;
2498 
2499      GetClientRect(infoPtr->hwndUpDown, &r);
2500      width -= r.right;
2501 
2502      if ((selected->rect.right -
2503           selected->rect.left) >= width )
2504      {
2505         /* Special case: width of selected item is greater than visible
2506          * part of control.
2507          */
2508         infoPtr->leftmostVisible = iSelected;
2509      }
2510      else
2511      {
2512         for (i = infoPtr->leftmostVisible; i < infoPtr->uNumItem; i++)
2513         {
2514            if ((selected->rect.right - TAB_GetItem(infoPtr, i)->rect.left) < width)
2515               break;
2516         }
2517         infoPtr->leftmostVisible = i;
2518      }
2519   }
2520 
2521   if (infoPtr->leftmostVisible != iOrigLeftmostVisible)
2522     TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
2523 
2524   SendMessageW(infoPtr->hwndUpDown, UDM_SETPOS, 0,
2525                MAKELONG(infoPtr->leftmostVisible, 0));
2526 }
2527 
2528 /******************************************************************************
2529  * TAB_InvalidateTabArea
2530  *
2531  * This method will invalidate the portion of the control that contains the
2532  * tabs. It is called when the state of the control changes and needs
2533  * to be redisplayed
2534  */
2535 static void TAB_InvalidateTabArea(const TAB_INFO *infoPtr)
2536 {
2537   RECT clientRect, rInvalidate, rAdjClient;
2538   INT lastRow = infoPtr->uNumRows - 1;
2539   RECT rect;
2540 
2541   if (lastRow < 0) return;
2542 
2543   GetClientRect(infoPtr->hwnd, &clientRect);
2544   rInvalidate = clientRect;
2545   rAdjClient = clientRect;
2546 
2547   TAB_AdjustRect(infoPtr, 0, &rAdjClient);
2548 
2549   TAB_InternalGetItemRect(infoPtr, infoPtr->uNumItem-1 , &rect, NULL);
2550   if ((infoPtr->dwStyle & TCS_BOTTOM) && (infoPtr->dwStyle & TCS_VERTICAL))
2551   {
2552     rInvalidate.left = rAdjClient.right;
2553     if (infoPtr->uNumRows == 1)
2554       rInvalidate.bottom = clientRect.top + rect.bottom + 2 * SELECTED_TAB_OFFSET;
2555   }
2556   else if(infoPtr->dwStyle & TCS_VERTICAL)
2557   {
2558     rInvalidate.right = rAdjClient.left;
2559     if (infoPtr->uNumRows == 1)
2560       rInvalidate.bottom = clientRect.top + rect.bottom + 2 * SELECTED_TAB_OFFSET;
2561   }
2562   else if (infoPtr->dwStyle & TCS_BOTTOM)
2563   {
2564     rInvalidate.top = rAdjClient.bottom;
2565     if (infoPtr->uNumRows == 1)
2566       rInvalidate.right = clientRect.left + rect.right + 2 * SELECTED_TAB_OFFSET;
2567   }
2568   else
2569   {
2570     rInvalidate.bottom = rAdjClient.top;
2571     if (infoPtr->uNumRows == 1)
2572       rInvalidate.right = clientRect.left + rect.right + 2 * SELECTED_TAB_OFFSET;
2573   }
2574 
2575   /* Punch out the updown control */
2576   if (infoPtr->needsScrolling && (rInvalidate.right > 0)) {
2577     RECT r;
2578     GetClientRect(infoPtr->hwndUpDown, &r);
2579     if (rInvalidate.right > clientRect.right - r.left)
2580       rInvalidate.right = rInvalidate.right - (r.right - r.left);
2581     else
2582       rInvalidate.right = clientRect.right - r.left;
2583   }
2584 
2585   TRACE("invalidate (%s)\n", wine_dbgstr_rect(&rInvalidate));
2586 
2587   InvalidateRect(infoPtr->hwnd, &rInvalidate, TRUE);
2588 }
2589 
2590 static inline LRESULT TAB_Paint (TAB_INFO *infoPtr, HDC hdcPaint)
2591 {
2592   HDC hdc;
2593   PAINTSTRUCT ps;
2594 
2595   if (hdcPaint)
2596     hdc = hdcPaint;
2597   else
2598   {
2599     hdc = BeginPaint (infoPtr->hwnd, &ps);
2600     TRACE("erase %d, rect=(%s)\n", ps.fErase, wine_dbgstr_rect(&ps.rcPaint));
2601   }
2602 
2603   TAB_Refresh (infoPtr, hdc);
2604 
2605   if (!hdcPaint)
2606     EndPaint (infoPtr->hwnd, &ps);
2607 
2608   return 0;
2609 }
2610 
2611 static LRESULT
2612 TAB_InsertItemT (TAB_INFO *infoPtr, INT iItem, const TCITEMW *pti, BOOL bUnicode)
2613 {
2614   TAB_ITEM *item;
2615   RECT rect;
2616 
2617   GetClientRect (infoPtr->hwnd, &rect);
2618   TRACE("Rect: %p %s\n", infoPtr->hwnd, wine_dbgstr_rect(&rect));
2619 
2620   if (iItem < 0) return -1;
2621   if (iItem > infoPtr->uNumItem)
2622     iItem = infoPtr->uNumItem;
2623 
2624   TAB_DumpItemExternalT(pti, iItem, bUnicode);
2625 
2626   if (!(item = Alloc(TAB_ITEM_SIZE(infoPtr)))) return FALSE;
2627   if (DPA_InsertPtr(infoPtr->items, iItem, item) == -1)
2628   {
2629       Free(item);
2630       return FALSE;
2631   }
2632 
2633   if (infoPtr->uNumItem == 0)
2634       infoPtr->iSelected = 0;
2635   else if (iItem <= infoPtr->iSelected)
2636       infoPtr->iSelected++;
2637 
2638   infoPtr->uNumItem++;
2639 
2640   item->pszText = NULL;
2641   if (pti->mask & TCIF_TEXT)
2642   {
2643     if (bUnicode)
2644       Str_SetPtrW (&item->pszText, pti->pszText);
2645     else
2646       Str_SetPtrAtoW (&item->pszText, (LPSTR)pti->pszText);
2647   }
2648 
2649   if (pti->mask & TCIF_IMAGE)
2650     item->iImage = pti->iImage;
2651   else
2652     item->iImage = -1;
2653 
2654   if (pti->mask & TCIF_PARAM)
2655     memcpy(item->extra, &pti->lParam, EXTRA_ITEM_SIZE(infoPtr));
2656   else
2657     memset(item->extra, 0, EXTRA_ITEM_SIZE(infoPtr));
2658 
2659   TAB_SetItemBounds(infoPtr);
2660   if (infoPtr->uNumItem > 1)
2661     TAB_InvalidateTabArea(infoPtr);
2662   else
2663     InvalidateRect(infoPtr->hwnd, NULL, TRUE);
2664 
2665   TRACE("[%p]: added item %d %s\n",
2666         infoPtr->hwnd, iItem, debugstr_w(item->pszText));
2667 
2668   /* If we haven't set the current focus yet, set it now. */
2669   if (infoPtr->uFocus == -1)
2670     TAB_SetCurFocus(infoPtr, iItem);
2671 
2672   return iItem;
2673 }
2674 
2675 static LRESULT
2676 TAB_SetItemSize (TAB_INFO *infoPtr, INT cx, INT cy)
2677 {
2678   LONG lResult = 0;
2679   BOOL bNeedPaint = FALSE;
2680 
2681   lResult = MAKELONG(infoPtr->tabWidth, infoPtr->tabHeight);
2682 
2683   /* UNDOCUMENTED: If requested Width or Height is 0 this means that program wants to use auto size. */
2684   if (infoPtr->dwStyle & TCS_FIXEDWIDTH && (infoPtr->tabWidth != cx))
2685   {
2686     infoPtr->tabWidth = cx;
2687     bNeedPaint = TRUE;
2688   }
2689 
2690   if (infoPtr->tabHeight != cy)
2691   {
2692     if ((infoPtr->fHeightSet = (cy != 0)))
2693       infoPtr->tabHeight = cy;
2694 
2695     bNeedPaint = TRUE;
2696   }
2697   TRACE("was h=%d,w=%d, now h=%d,w=%d\n",
2698        HIWORD(lResult), LOWORD(lResult),
2699        infoPtr->tabHeight, infoPtr->tabWidth);
2700 
2701   if (bNeedPaint)
2702   {
2703     TAB_SetItemBounds(infoPtr);
2704     RedrawWindow(infoPtr->hwnd, NULL, NULL, RDW_ERASE | RDW_INVALIDATE | RDW_UPDATENOW);
2705   }
2706 
2707   return lResult;
2708 }
2709 
2710 static inline LRESULT TAB_SetMinTabWidth (TAB_INFO *infoPtr, INT cx)
2711 {
2712   INT oldcx = 0;
2713 
2714   TRACE("(%p,%d)\n", infoPtr, cx);
2715 
2716   if (infoPtr->tabMinWidth < 0)
2717     oldcx = DEFAULT_MIN_TAB_WIDTH;
2718   else
2719     oldcx = infoPtr->tabMinWidth;
2720   infoPtr->tabMinWidth = cx;
2721   TAB_SetItemBounds(infoPtr);
2722   return oldcx;
2723 }
2724 
2725 static inline LRESULT
2726 TAB_HighlightItem (TAB_INFO *infoPtr, INT iItem, BOOL fHighlight)
2727 {
2728   LPDWORD lpState;
2729   DWORD oldState;
2730   RECT r;
2731 
2732   TRACE("(%p,%d,%s)\n", infoPtr, iItem, fHighlight ? "true" : "false");
2733 
2734   if (iItem < 0 || iItem >= infoPtr->uNumItem)
2735     return FALSE;
2736 
2737   lpState = &TAB_GetItem(infoPtr, iItem)->dwState;
2738   oldState = *lpState;
2739 
2740   if (fHighlight)
2741     *lpState |= TCIS_HIGHLIGHTED;
2742   else
2743     *lpState &= ~TCIS_HIGHLIGHTED;
2744 
2745   if ((oldState != *lpState) && TAB_InternalGetItemRect (infoPtr, iItem, &r, NULL))
2746     InvalidateRect (infoPtr->hwnd, &r, TRUE);
2747 
2748   return TRUE;
2749 }
2750 
2751 static LRESULT
2752 TAB_SetItemT (TAB_INFO *infoPtr, INT iItem, LPTCITEMW tabItem, BOOL bUnicode)
2753 {
2754   TAB_ITEM *wineItem;
2755 
2756   TRACE("(%p,%d,%p,%s)\n", infoPtr, iItem, tabItem, bUnicode ? "true" : "false");
2757 
2758   if (iItem < 0 || iItem >= infoPtr->uNumItem)
2759     return FALSE;
2760 
2761   TAB_DumpItemExternalT(tabItem, iItem, bUnicode);
2762 
2763   wineItem = TAB_GetItem(infoPtr, iItem);
2764 
2765   if (tabItem->mask & TCIF_IMAGE)
2766     wineItem->iImage = tabItem->iImage;
2767 
2768   if (tabItem->mask & TCIF_PARAM)
2769     memcpy(wineItem->extra, &tabItem->lParam, infoPtr->cbInfo);
2770 
2771   if (tabItem->mask & TCIF_RTLREADING)
2772     FIXME("TCIF_RTLREADING\n");
2773 
2774   if (tabItem->mask & TCIF_STATE)
2775     wineItem->dwState = (wineItem->dwState & ~tabItem->dwStateMask) |
2776                         ( tabItem->dwState &  tabItem->dwStateMask);
2777 
2778   if (tabItem->mask & TCIF_TEXT)
2779   {
2780     Free(wineItem->pszText);
2781     wineItem->pszText = NULL;
2782     if (bUnicode)
2783       Str_SetPtrW(&wineItem->pszText, tabItem->pszText);
2784     else
2785       Str_SetPtrAtoW(&wineItem->pszText, (LPSTR)tabItem->pszText);
2786   }
2787 
2788   /* Update and repaint tabs */
2789   TAB_SetItemBounds(infoPtr);
2790   TAB_InvalidateTabArea(infoPtr);
2791 
2792   return TRUE;
2793 }
2794 
2795 static inline LRESULT TAB_GetItemCount (const TAB_INFO *infoPtr)
2796 {
2797   TRACE("\n");
2798   return infoPtr->uNumItem;
2799 }
2800 
2801 
2802 static LRESULT
2803 TAB_GetItemT (TAB_INFO *infoPtr, INT iItem, LPTCITEMW tabItem, BOOL bUnicode)
2804 {
2805   TAB_ITEM *wineItem;
2806 
2807   TRACE("(%p,%d,%p,%s)\n", infoPtr, iItem, tabItem, bUnicode ? "true" : "false");
2808 
2809   if (!tabItem) return FALSE;
2810 
2811   if (iItem < 0 || iItem >= infoPtr->uNumItem)
2812   {
2813     /* init requested fields */
2814     if (tabItem->mask & TCIF_IMAGE) tabItem->iImage  = 0;
2815     if (tabItem->mask & TCIF_PARAM) tabItem->lParam  = 0;
2816     if (tabItem->mask & TCIF_STATE) tabItem->dwState = 0;
2817     return FALSE;
2818   }
2819 
2820   wineItem = TAB_GetItem(infoPtr, iItem);
2821 
2822   if (tabItem->mask & TCIF_IMAGE)
2823     tabItem->iImage = wineItem->iImage;
2824 
2825   if (tabItem->mask & TCIF_PARAM)
2826     memcpy(&tabItem->lParam, wineItem->extra, infoPtr->cbInfo);
2827 
2828   if (tabItem->mask & TCIF_RTLREADING)
2829     FIXME("TCIF_RTLREADING\n");
2830 
2831   if (tabItem->mask & TCIF_STATE)
2832     tabItem->dwState = wineItem->dwState & tabItem->dwStateMask;
2833 
2834   if (tabItem->mask & TCIF_TEXT)
2835   {
2836     if (bUnicode)
2837       Str_GetPtrW (wineItem->pszText, tabItem->pszText, tabItem->cchTextMax);
2838     else
2839       Str_GetPtrWtoA (wineItem->pszText, (LPSTR)tabItem->pszText, tabItem->cchTextMax);
2840   }
2841 
2842   TAB_DumpItemExternalT(tabItem, iItem, bUnicode);
2843 
2844   return TRUE;
2845 }
2846 
2847 
2848 static LRESULT TAB_DeleteItem (TAB_INFO *infoPtr, INT iItem)
2849 {
2850     TAB_ITEM *item;
2851 
2852     TRACE("(%p, %d)\n", infoPtr, iItem);
2853 
2854     if (iItem < 0 || iItem >= infoPtr->uNumItem) return FALSE;
2855 
2856     TAB_InvalidateTabArea(infoPtr);
2857     item = TAB_GetItem(infoPtr, iItem);
2858     Free(item->pszText);
2859     Free(item);
2860     infoPtr->uNumItem--;
2861     DPA_DeletePtr(infoPtr->items, iItem);
2862 
2863     if (infoPtr->uNumItem == 0)
2864     {
2865         if (infoPtr->iHotTracked >= 0)
2866         {
2867             KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
2868             infoPtr->iHotTracked = -1;
2869         }
2870 
2871         infoPtr->iSelected = -1;
2872     }
2873     else
2874     {
2875         if (iItem <= infoPtr->iHotTracked)
2876         {
2877             /* When tabs move left/up, the hot track item may change */
2878             FIXME("Recalc hot track\n");
2879         }
2880     }
2881 
2882     /* adjust the selected index */
2883     if (iItem == infoPtr->iSelected)
2884         infoPtr->iSelected = -1;
2885     else if (iItem < infoPtr->iSelected)
2886         infoPtr->iSelected--;
2887 
2888     /* reposition and repaint tabs */
2889     TAB_SetItemBounds(infoPtr);
2890 
2891     return TRUE;
2892 }
2893 
2894 static inline LRESULT TAB_DeleteAllItems (TAB_INFO *infoPtr)
2895 {
2896     TRACE("(%p)\n", infoPtr);
2897     while (infoPtr->uNumItem)
2898       TAB_DeleteItem (infoPtr, 0);
2899     return TRUE;
2900 }
2901 
2902 
2903 static inline LRESULT TAB_GetFont (const TAB_INFO *infoPtr)
2904 {
2905   TRACE("(%p) returning %p\n", infoPtr, infoPtr->hFont);
2906   return (LRESULT)infoPtr->hFont;
2907 }
2908 
2909 static inline LRESULT TAB_SetFont (TAB_INFO *infoPtr, HFONT hNewFont)
2910 {
2911   TRACE("(%p,%p)\n", infoPtr, hNewFont);
2912 
2913   infoPtr->hFont = hNewFont;
2914 
2915   TAB_SetItemBounds(infoPtr);
2916 
2917   TAB_InvalidateTabArea(infoPtr);
2918 
2919   return 0;
2920 }
2921 
2922 
2923 static inline LRESULT TAB_GetImageList (const TAB_INFO *infoPtr)
2924 {
2925   TRACE("\n");
2926   return (LRESULT)infoPtr->himl;
2927 }
2928 
2929 static inline LRESULT TAB_SetImageList (TAB_INFO *infoPtr, HIMAGELIST himlNew)
2930 {
2931     HIMAGELIST himlPrev = infoPtr->himl;
2932     TRACE("himl=%p\n", himlNew);
2933     infoPtr->himl = himlNew;
2934     TAB_SetItemBounds(infoPtr);
2935     InvalidateRect(infoPtr->hwnd, NULL, TRUE);
2936     return (LRESULT)himlPrev;
2937 }
2938 
2939 static inline LRESULT TAB_GetUnicodeFormat (const TAB_INFO *infoPtr)
2940 {
2941     TRACE("(%p)\n", infoPtr);
2942     return infoPtr->bUnicode;
2943 }
2944 
2945 static inline LRESULT TAB_SetUnicodeFormat (TAB_INFO *infoPtr, BOOL bUnicode)
2946 {
2947     BOOL bTemp = infoPtr->bUnicode;
2948 
2949     TRACE("(%p %d)\n", infoPtr, bUnicode);
2950     infoPtr->bUnicode = bUnicode;
2951 
2952     return bTemp;
2953 }
2954 
2955 static inline LRESULT TAB_Size (TAB_INFO *infoPtr)
2956 {
2957 /* I'm not really sure what the following code was meant to do.
2958    This is what it is doing:
2959    When WM_SIZE is sent with SIZE_RESTORED, the control
2960    gets positioned in the top left corner.
2961 
2962   RECT parent_rect;
2963   HWND parent;
2964   UINT uPosFlags,cx,cy;
2965 
2966   uPosFlags=0;
2967   if (!wParam) {
2968     parent = GetParent (hwnd);
2969     GetClientRect(parent, &parent_rect);
2970     cx=LOWORD (lParam);
2971     cy=HIWORD (lParam);
2972     if (GetWindowLongW(hwnd, GWL_STYLE) & CCS_NORESIZE)
2973         uPosFlags |= (SWP_NOSIZE | SWP_NOMOVE);
2974 
2975     SetWindowPos (hwnd, 0, parent_rect.left, parent_rect.top,
2976             cx, cy, uPosFlags | SWP_NOZORDER);
2977   } else {
2978     FIXME("WM_SIZE flag %x %lx not handled\n", wParam, lParam);
2979   } */
2980 
2981   /* Recompute the size/position of the tabs. */
2982   TAB_SetItemBounds (infoPtr);
2983 
2984   /* Force a repaint of the control. */
2985   InvalidateRect(infoPtr->hwnd, NULL, TRUE);
2986 
2987   return 0;
2988 }
2989 
2990 
2991 static LRESULT TAB_Create (HWND hwnd, LPARAM lParam)
2992 {
2993   TAB_INFO *infoPtr;
2994   TEXTMETRICW fontMetrics;
2995   HDC hdc;
2996   HFONT hOldFont;
2997   DWORD style;
2998 
2999   infoPtr = Alloc (sizeof(TAB_INFO));
3000 
3001   SetWindowLongPtrW(hwnd, 0, (DWORD_PTR)infoPtr);
3002 
3003   infoPtr->hwnd            = hwnd;
3004   infoPtr->hwndNotify      = ((LPCREATESTRUCTW)lParam)->hwndParent;
3005   infoPtr->uNumItem        = 0;
3006   infoPtr->uNumRows        = 0;
3007   infoPtr->uHItemPadding   = 6;
3008   infoPtr->uVItemPadding   = 3;
3009   infoPtr->uHItemPadding_s = 6;
3010   infoPtr->uVItemPadding_s = 3;
3011   infoPtr->hFont           = 0;
3012   infoPtr->items           = DPA_Create(8);
3013   infoPtr->hcurArrow       = LoadCursorW (0, (LPWSTR)IDC_ARROW);
3014   infoPtr->iSelected       = -1;
3015   infoPtr->iHotTracked     = -1;
3016   infoPtr->uFocus          = -1;
3017   infoPtr->hwndToolTip     = 0;
3018   infoPtr->DoRedraw        = TRUE;
3019   infoPtr->needsScrolling  = FALSE;
3020   infoPtr->hwndUpDown      = 0;
3021   infoPtr->leftmostVisible = 0;
3022   infoPtr->fHeightSet      = FALSE;
3023   infoPtr->bUnicode        = IsWindowUnicode (hwnd);
3024   infoPtr->cbInfo          = sizeof(LPARAM);
3025 
3026   TRACE("Created tab control, hwnd [%p]\n", hwnd);
3027 
3028   /* The tab control always has the WS_CLIPSIBLINGS style. Even
3029      if you don't specify it in CreateWindow. This is necessary in
3030      order for paint to work correctly. This follows windows behaviour. */
3031   style = GetWindowLongW(hwnd, GWL_STYLE);
3032   if (style & TCS_VERTICAL) style |= TCS_MULTILINE;
3033   style |= WS_CLIPSIBLINGS;
3034   SetWindowLongW(hwnd, GWL_STYLE, style);
3035 
3036   infoPtr->dwStyle = style;
3037   infoPtr->exStyle = (style & TCS_FLATBUTTONS) ? TCS_EX_FLATSEPARATORS : 0;
3038 
3039   if (infoPtr->dwStyle & TCS_TOOLTIPS) {
3040     /* Create tooltip control */
3041     infoPtr->hwndToolTip =
3042       CreateWindowExW (0, TOOLTIPS_CLASSW, NULL, WS_POPUP,
3043 		       CW_USEDEFAULT, CW_USEDEFAULT,
3044 		       CW_USEDEFAULT, CW_USEDEFAULT,
3045 		       hwnd, 0, 0, 0);
3046 
3047     /* Send NM_TOOLTIPSCREATED notification */
3048     if (infoPtr->hwndToolTip) {
3049       NMTOOLTIPSCREATED nmttc;
3050 
3051       nmttc.hdr.hwndFrom = hwnd;
3052       nmttc.hdr.idFrom = GetWindowLongPtrW(hwnd, GWLP_ID);
3053       nmttc.hdr.code = NM_TOOLTIPSCREATED;
3054       nmttc.hwndToolTips = infoPtr->hwndToolTip;
3055 
3056       SendMessageW (infoPtr->hwndNotify, WM_NOTIFY,
3057                     GetWindowLongPtrW(hwnd, GWLP_ID), (LPARAM)&nmttc);
3058     }
3059   }
3060 
3061   OpenThemeData (infoPtr->hwnd, themeClass);
3062 
3063   /*
3064    * We need to get text information so we need a DC and we need to select
3065    * a font.
3066    */
3067   hdc = GetDC(hwnd);
3068   hOldFont = SelectObject (hdc, GetStockObject (SYSTEM_FONT));
3069 
3070   /* Use the system font to determine the initial height of a tab. */
3071   GetTextMetricsW(hdc, &fontMetrics);
3072 
3073   /*
3074    * Make sure there is enough space for the letters + growing the
3075    * selected item + extra space for the selected item.
3076    */
3077   infoPtr->tabHeight = fontMetrics.tmHeight + SELECTED_TAB_OFFSET +
3078 	               ((infoPtr->dwStyle & TCS_BUTTONS) ? 2 : 1) *
3079                         infoPtr->uVItemPadding;
3080 
3081   /* Initialize the width of a tab. */
3082   if (infoPtr->dwStyle & TCS_FIXEDWIDTH)
3083     infoPtr->tabWidth = GetDeviceCaps(hdc, LOGPIXELSX);
3084 
3085   infoPtr->tabMinWidth = -1;
3086 
3087   TRACE("tabH=%d, tabW=%d\n", infoPtr->tabHeight, infoPtr->tabWidth);
3088 
3089   SelectObject (hdc, hOldFont);
3090   ReleaseDC(hwnd, hdc);
3091 
3092   return 0;
3093 }
3094 
3095 static LRESULT
3096 TAB_Destroy (TAB_INFO *infoPtr)
3097 {
3098   INT iItem;
3099 
3100   SetWindowLongPtrW(infoPtr->hwnd, 0, 0);
3101 
3102   for (iItem = infoPtr->uNumItem - 1; iItem >= 0; iItem--)
3103   {
3104       TAB_ITEM *tab = TAB_GetItem(infoPtr, iItem);
3105 
3106       DPA_DeletePtr(infoPtr->items, iItem);
3107       infoPtr->uNumItem--;
3108 
3109       Free(tab->pszText);
3110       Free(tab);
3111   }
3112   DPA_Destroy(infoPtr->items);
3113   infoPtr->items = NULL;
3114 
3115   if (infoPtr->hwndToolTip)
3116     DestroyWindow (infoPtr->hwndToolTip);
3117 
3118   if (infoPtr->hwndUpDown)
3119     DestroyWindow(infoPtr->hwndUpDown);
3120 
3121   if (infoPtr->iHotTracked >= 0)
3122     KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
3123 
3124   CloseThemeData (GetWindowTheme (infoPtr->hwnd));
3125 
3126   Free (infoPtr);
3127   return 0;
3128 }
3129 
3130 /* update theme after a WM_THEMECHANGED message */
3131 static LRESULT theme_changed(const TAB_INFO *infoPtr)
3132 {
3133     HTHEME theme = GetWindowTheme (infoPtr->hwnd);
3134     CloseThemeData (theme);
3135     OpenThemeData (infoPtr->hwnd, themeClass);
3136     return 0;
3137 }
3138 
3139 static LRESULT TAB_NCCalcSize(WPARAM wParam)
3140 {
3141   if (!wParam)
3142     return 0;
3143   return WVR_ALIGNTOP;
3144 }
3145 
3146 static inline LRESULT
3147 TAB_SetItemExtra (TAB_INFO *infoPtr, INT cbInfo)
3148 {
3149   TRACE("(%p %d)\n", infoPtr, cbInfo);
3150 
3151   if (cbInfo < 0 || infoPtr->uNumItem) return FALSE;
3152 
3153   infoPtr->cbInfo = cbInfo;
3154   return TRUE;
3155 }
3156 
3157 static LRESULT TAB_RemoveImage (TAB_INFO *infoPtr, INT image)
3158 {
3159   TRACE("%p %d\n", infoPtr, image);
3160 
3161   if (ImageList_Remove (infoPtr->himl, image))
3162   {
3163     INT i, *idx;
3164     RECT r;
3165 
3166     /* shift indices, repaint items if needed */
3167     for (i = 0; i < infoPtr->uNumItem; i++)
3168     {
3169       idx = &TAB_GetItem(infoPtr, i)->iImage;
3170       if (*idx >= image)
3171       {
3172         if (*idx == image)
3173           *idx = -1;
3174         else
3175           (*idx)--;
3176 
3177         /* repaint item */
3178         if (TAB_InternalGetItemRect (infoPtr, i, &r, NULL))
3179           InvalidateRect (infoPtr->hwnd, &r, TRUE);
3180       }
3181     }
3182   }
3183 
3184   return 0;
3185 }
3186 
3187 static LRESULT
3188 TAB_SetExtendedStyle (TAB_INFO *infoPtr, DWORD exMask, DWORD exStyle)
3189 {
3190   DWORD prevstyle = infoPtr->exStyle;
3191 
3192   /* zero mask means all styles */
3193   if (exMask == 0) exMask = ~0;
3194 
3195   if (exMask & TCS_EX_REGISTERDROP)
3196   {
3197     FIXME("TCS_EX_REGISTERDROP style unimplemented\n");
3198     exMask  &= ~TCS_EX_REGISTERDROP;
3199     exStyle &= ~TCS_EX_REGISTERDROP;
3200   }
3201 
3202   if (exMask & TCS_EX_FLATSEPARATORS)
3203   {
3204     if ((prevstyle ^ exStyle) & TCS_EX_FLATSEPARATORS)
3205     {
3206         infoPtr->exStyle ^= TCS_EX_FLATSEPARATORS;
3207         TAB_InvalidateTabArea(infoPtr);
3208     }
3209   }
3210 
3211   return prevstyle;
3212 }
3213 
3214 static inline LRESULT
3215 TAB_GetExtendedStyle (const TAB_INFO *infoPtr)
3216 {
3217   return infoPtr->exStyle;
3218 }
3219 
3220 static LRESULT
3221 TAB_DeselectAll (TAB_INFO *infoPtr, BOOL excludesel)
3222 {
3223   BOOL paint = FALSE;
3224   INT i, selected = infoPtr->iSelected;
3225 
3226   TRACE("(%p, %d)\n", infoPtr, excludesel);
3227 
3228   if (!(infoPtr->dwStyle & TCS_BUTTONS))
3229     return 0;
3230 
3231   for (i = 0; i < infoPtr->uNumItem; i++)
3232   {
3233     if ((TAB_GetItem(infoPtr, i)->dwState & TCIS_BUTTONPRESSED) &&
3234         (selected != i))
3235     {
3236       TAB_GetItem(infoPtr, i)->dwState &= ~TCIS_BUTTONPRESSED;
3237       paint = TRUE;
3238     }
3239   }
3240 
3241   if (!excludesel && (selected != -1))
3242   {
3243     TAB_GetItem(infoPtr, selected)->dwState &= ~TCIS_BUTTONPRESSED;
3244     infoPtr->iSelected = -1;
3245     paint = TRUE;
3246   }
3247 
3248   if (paint)
3249     TAB_InvalidateTabArea (infoPtr);
3250 
3251   return 0;
3252 }
3253 
3254 /***
3255  * DESCRIPTION:
3256  * Processes WM_STYLECHANGED messages.
3257  *
3258  * PARAMETER(S):
3259  * [I] infoPtr : valid pointer to the tab data structure
3260  * [I] wStyleType : window style type (normal or extended)
3261  * [I] lpss : window style information
3262  *
3263  * RETURN:
3264  * Zero
3265  */
3266 static INT TAB_StyleChanged(TAB_INFO *infoPtr, WPARAM wStyleType,
3267                             const STYLESTRUCT *lpss)
3268 {
3269     TRACE("(styletype=%lx, styleOld=0x%08x, styleNew=0x%08x)\n",
3270           wStyleType, lpss->styleOld, lpss->styleNew);
3271 
3272     if (wStyleType != GWL_STYLE) return 0;
3273 
3274     infoPtr->dwStyle = lpss->styleNew;
3275 
3276     TAB_SetItemBounds (infoPtr);
3277     InvalidateRect(infoPtr->hwnd, NULL, TRUE);
3278 
3279     return 0;
3280 }
3281 
3282 static LRESULT WINAPI
3283 TAB_WindowProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
3284 {
3285     TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
3286 
3287     TRACE("hwnd=%p msg=%x wParam=%lx lParam=%lx\n", hwnd, uMsg, wParam, lParam);
3288     if (!infoPtr && (uMsg != WM_CREATE))
3289       return DefWindowProcW (hwnd, uMsg, wParam, lParam);
3290 
3291     switch (uMsg)
3292     {
3293     case TCM_GETIMAGELIST:
3294       return TAB_GetImageList (infoPtr);
3295 
3296     case TCM_SETIMAGELIST:
3297       return TAB_SetImageList (infoPtr, (HIMAGELIST)lParam);
3298 
3299     case TCM_GETITEMCOUNT:
3300       return TAB_GetItemCount (infoPtr);
3301 
3302     case TCM_GETITEMA:
3303     case TCM_GETITEMW:
3304       return TAB_GetItemT (infoPtr, (INT)wParam, (LPTCITEMW)lParam, uMsg == TCM_GETITEMW);
3305 
3306     case TCM_SETITEMA:
3307     case TCM_SETITEMW:
3308       return TAB_SetItemT (infoPtr, (INT)wParam, (LPTCITEMW)lParam, uMsg == TCM_SETITEMW);
3309 
3310     case TCM_DELETEITEM:
3311       return TAB_DeleteItem (infoPtr, (INT)wParam);
3312 
3313     case TCM_DELETEALLITEMS:
3314      return TAB_DeleteAllItems (infoPtr);
3315 
3316     case TCM_GETITEMRECT:
3317      return TAB_GetItemRect (infoPtr, (INT)wParam, (LPRECT)lParam);
3318 
3319     case TCM_GETCURSEL:
3320       return TAB_GetCurSel (infoPtr);
3321 
3322     case TCM_HITTEST:
3323       return TAB_HitTest (infoPtr, (LPTCHITTESTINFO)lParam);
3324 
3325     case TCM_SETCURSEL:
3326       return TAB_SetCurSel (infoPtr, (INT)wParam);
3327 
3328     case TCM_INSERTITEMA:
3329     case TCM_INSERTITEMW:
3330       return TAB_InsertItemT (infoPtr, (INT)wParam, (TCITEMW*)lParam, uMsg == TCM_INSERTITEMW);
3331 
3332     case TCM_SETITEMEXTRA:
3333       return TAB_SetItemExtra (infoPtr, (INT)wParam);
3334 
3335     case TCM_ADJUSTRECT:
3336       return TAB_AdjustRect (infoPtr, (BOOL)wParam, (LPRECT)lParam);
3337 
3338     case TCM_SETITEMSIZE:
3339       return TAB_SetItemSize (infoPtr, (INT)LOWORD(lParam), (INT)HIWORD(lParam));
3340 
3341     case TCM_REMOVEIMAGE:
3342       return TAB_RemoveImage (infoPtr, (INT)wParam);
3343 
3344     case TCM_SETPADDING:
3345       return TAB_SetPadding (infoPtr, lParam);
3346 
3347     case TCM_GETROWCOUNT:
3348       return TAB_GetRowCount(infoPtr);
3349 
3350     case TCM_GETUNICODEFORMAT:
3351       return TAB_GetUnicodeFormat (infoPtr);
3352 
3353     case TCM_SETUNICODEFORMAT:
3354       return TAB_SetUnicodeFormat (infoPtr, (BOOL)wParam);
3355 
3356     case TCM_HIGHLIGHTITEM:
3357       return TAB_HighlightItem (infoPtr, (INT)wParam, (BOOL)LOWORD(lParam));
3358 
3359     case TCM_GETTOOLTIPS:
3360       return TAB_GetToolTips (infoPtr);
3361 
3362     case TCM_SETTOOLTIPS:
3363       return TAB_SetToolTips (infoPtr, (HWND)wParam);
3364 
3365     case TCM_GETCURFOCUS:
3366       return TAB_GetCurFocus (infoPtr);
3367 
3368     case TCM_SETCURFOCUS:
3369       return TAB_SetCurFocus (infoPtr, (INT)wParam);
3370 
3371     case TCM_SETMINTABWIDTH:
3372       return TAB_SetMinTabWidth(infoPtr, (INT)lParam);
3373 
3374     case TCM_DESELECTALL:
3375       return TAB_DeselectAll (infoPtr, (BOOL)wParam);
3376 
3377     case TCM_GETEXTENDEDSTYLE:
3378       return TAB_GetExtendedStyle (infoPtr);
3379 
3380     case TCM_SETEXTENDEDSTYLE:
3381       return TAB_SetExtendedStyle (infoPtr, wParam, lParam);
3382 
3383     case WM_GETFONT:
3384       return TAB_GetFont (infoPtr);
3385 
3386     case WM_SETFONT:
3387       return TAB_SetFont (infoPtr, (HFONT)wParam);
3388 
3389     case WM_CREATE:
3390       return TAB_Create (hwnd, lParam);
3391 
3392     case WM_NCDESTROY:
3393       return TAB_Destroy (infoPtr);
3394 
3395     case WM_GETDLGCODE:
3396       return DLGC_WANTARROWS | DLGC_WANTCHARS;
3397 
3398     case WM_LBUTTONDOWN:
3399       return TAB_LButtonDown (infoPtr, wParam, lParam);
3400 
3401     case WM_LBUTTONUP:
3402       return TAB_LButtonUp (infoPtr);
3403 
3404     case WM_NOTIFY:
3405       return SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, wParam, lParam);
3406 
3407     case WM_RBUTTONUP:
3408       TAB_RButtonUp (infoPtr);
3409       return DefWindowProcW (hwnd, uMsg, wParam, lParam);
3410 
3411     case WM_MOUSEMOVE:
3412       return TAB_MouseMove (infoPtr, wParam, lParam);
3413 
3414     case WM_PRINTCLIENT:
3415     case WM_PAINT:
3416       return TAB_Paint (infoPtr, (HDC)wParam);
3417 
3418     case WM_SIZE:
3419       return TAB_Size (infoPtr);
3420 
3421     case WM_SETREDRAW:
3422       return TAB_SetRedraw (infoPtr, (BOOL)wParam);
3423 
3424     case WM_HSCROLL:
3425       return TAB_OnHScroll(infoPtr, (int)LOWORD(wParam), (int)HIWORD(wParam));
3426 
3427     case WM_STYLECHANGED:
3428       return TAB_StyleChanged(infoPtr, wParam, (LPSTYLESTRUCT)lParam);
3429 
3430     case WM_SYSCOLORCHANGE:
3431       COMCTL32_RefreshSysColors();
3432       return 0;
3433 
3434     case WM_THEMECHANGED:
3435       return theme_changed (infoPtr);
3436 
3437     case WM_KILLFOCUS:
3438       TAB_KillFocus(infoPtr);
3439     case WM_SETFOCUS:
3440       TAB_FocusChanging(infoPtr);
3441       break;   /* Don't disturb normal focus behavior */
3442 
3443     case WM_KEYDOWN:
3444       return TAB_KeyDown(infoPtr, wParam, lParam);
3445 
3446     case WM_NCHITTEST:
3447       return TAB_NCHitTest(infoPtr, lParam);
3448 
3449     case WM_NCCALCSIZE:
3450       return TAB_NCCalcSize(wParam);
3451 
3452     default:
3453       if (uMsg >= WM_USER && uMsg < WM_APP && !COMCTL32_IsReflectedMessage(uMsg))
3454 	WARN("unknown msg %04x wp=%08lx lp=%08lx\n",
3455 	     uMsg, wParam, lParam);
3456       break;
3457     }
3458     return DefWindowProcW(hwnd, uMsg, wParam, lParam);
3459 }
3460 
3461 
3462 void
3463 TAB_Register (void)
3464 {
3465   WNDCLASSW wndClass;
3466 
3467   ZeroMemory (&wndClass, sizeof(WNDCLASSW));
3468   wndClass.style         = CS_GLOBALCLASS | CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
3469   wndClass.lpfnWndProc   = TAB_WindowProc;
3470   wndClass.cbClsExtra    = 0;
3471   wndClass.cbWndExtra    = sizeof(TAB_INFO *);
3472   wndClass.hCursor       = LoadCursorW (0, (LPWSTR)IDC_ARROW);
3473   wndClass.hbrBackground = (HBRUSH)(COLOR_BTNFACE+1);
3474   wndClass.lpszClassName = WC_TABCONTROLW;
3475 
3476   RegisterClassW (&wndClass);
3477 }
3478 
3479 
3480 void
3481 TAB_Unregister (void)
3482 {
3483     UnregisterClassW (WC_TABCONTROLW, NULL);
3484 }
3485