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