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