comctl32/tab: Use dpa to store item data.
[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 "tmschema.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       TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGING);
688
689       if (pressed)
690         TAB_DeselectAll (infoPtr, FALSE);
691       else
692         TAB_SetCurSel(infoPtr, newItem);
693
694       TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGE);
695     }
696   }
697
698   return 0;
699 }
700
701 static inline LRESULT
702 TAB_LButtonUp (const TAB_INFO *infoPtr)
703 {
704   TAB_SendSimpleNotify(infoPtr, NM_CLICK);
705
706   return 0;
707 }
708
709 static inline LRESULT
710 TAB_RButtonDown (const TAB_INFO *infoPtr)
711 {
712   TAB_SendSimpleNotify(infoPtr, NM_RCLICK);
713   return 0;
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         memcpy(&dis.itemData, (ULONG_PTR*)TAB_GetItem(infoPtr, iItem)->extra, 4);
1774     }
1775
1776     /* draw notification */
1777     SendMessageW( infoPtr->hwndNotify, WM_DRAWITEM, id, (LPARAM)&dis );
1778   }
1779   else
1780   {
1781     TAB_ITEM *item = TAB_GetItem(infoPtr, iItem);
1782     RECT rcTemp;
1783     RECT rcImage;
1784
1785     /* used to center the icon and text in the tab */
1786     RECT rcText;
1787     INT center_offset_h, center_offset_v;
1788
1789     /* set rcImage to drawRect, we will use top & left in our ImageList_Draw call */
1790     rcImage = *drawRect;
1791
1792     rcTemp = *drawRect;
1793
1794     rcText.left = rcText.top = rcText.right = rcText.bottom = 0;
1795
1796     /* get the rectangle that the text fits in */
1797     if (item->pszText)
1798     {
1799       DrawTextW(hdc, item->pszText, -1, &rcText, DT_CALCRECT);
1800     }
1801     /*
1802      * If not owner draw, then do the drawing ourselves.
1803      *
1804      * Draw the icon.
1805      */
1806     if (infoPtr->himl && item->iImage != -1)
1807     {
1808       INT cx;
1809       INT cy;
1810       
1811       ImageList_GetIconSize(infoPtr->himl, &cx, &cy);
1812
1813       if(infoPtr->dwStyle & TCS_VERTICAL)
1814       {
1815         center_offset_h = ((drawRect->bottom - drawRect->top) - (cy + infoPtr->uHItemPadding + (rcText.right  - rcText.left))) / 2;
1816         center_offset_v = ((drawRect->right - drawRect->left) - cx) / 2;
1817       }
1818       else
1819       {
1820         center_offset_h = ((drawRect->right - drawRect->left) - (cx + infoPtr->uHItemPadding + (rcText.right  - rcText.left))) / 2;
1821         center_offset_v = ((drawRect->bottom - drawRect->top) - cy) / 2;
1822       }
1823
1824       /* if an item is selected, the icon is shifted up instead of down */
1825       if (iItem == infoPtr->iSelected)
1826         center_offset_v -= infoPtr->uVItemPadding / 2;
1827       else
1828         center_offset_v += infoPtr->uVItemPadding / 2;
1829
1830       if (infoPtr->dwStyle & TCS_FIXEDWIDTH && infoPtr->dwStyle & (TCS_FORCELABELLEFT | TCS_FORCEICONLEFT))
1831         center_offset_h = infoPtr->uHItemPadding;
1832
1833       if (center_offset_h < 2)
1834         center_offset_h = 2;
1835         
1836       if (center_offset_v < 0)
1837         center_offset_v = 0;
1838         
1839       TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%s), textlen=%d\n",
1840           debugstr_w(item->pszText), center_offset_h, center_offset_v,
1841           wine_dbgstr_rect(drawRect), (rcText.right-rcText.left));
1842
1843       if((infoPtr->dwStyle & TCS_VERTICAL) && (infoPtr->dwStyle & TCS_BOTTOM))
1844       {
1845         rcImage.top = drawRect->top + center_offset_h;
1846         /* if tab is TCS_VERTICAL and TCS_BOTTOM, the text is drawn from the */
1847         /* right side of the tab, but the image still uses the left as its x position */
1848         /* this keeps the image always drawn off of the same side of the tab */
1849         rcImage.left = drawRect->right - cx - center_offset_v;
1850         drawRect->top += cy + infoPtr->uHItemPadding;
1851       }
1852       else if(infoPtr->dwStyle & TCS_VERTICAL)
1853       {
1854         rcImage.top  = drawRect->bottom - cy - center_offset_h;
1855         rcImage.left = drawRect->left + center_offset_v;
1856         drawRect->bottom -= cy + infoPtr->uHItemPadding;
1857       }
1858       else /* normal style, whether TCS_BOTTOM or not */
1859       {
1860         rcImage.left = drawRect->left + center_offset_h;
1861         rcImage.top = drawRect->top + center_offset_v;
1862         drawRect->left += cx + infoPtr->uHItemPadding;
1863       }
1864
1865       TRACE("drawing image=%d, left=%d, top=%d\n",
1866             item->iImage, rcImage.left, rcImage.top-1);
1867       ImageList_Draw
1868         (
1869         infoPtr->himl,
1870         item->iImage,
1871         hdc,
1872         rcImage.left,
1873         rcImage.top,
1874         ILD_NORMAL
1875         );
1876     }
1877
1878     /* Now position text */
1879     if (infoPtr->dwStyle & TCS_FIXEDWIDTH && infoPtr->dwStyle & TCS_FORCELABELLEFT)
1880       center_offset_h = infoPtr->uHItemPadding;
1881     else
1882       if(infoPtr->dwStyle & TCS_VERTICAL)
1883         center_offset_h = ((drawRect->bottom - drawRect->top) - (rcText.right - rcText.left)) / 2;
1884       else
1885         center_offset_h = ((drawRect->right - drawRect->left) - (rcText.right - rcText.left)) / 2;
1886
1887     if(infoPtr->dwStyle & TCS_VERTICAL)
1888     {
1889       if(infoPtr->dwStyle & TCS_BOTTOM)
1890         drawRect->top+=center_offset_h;
1891       else
1892         drawRect->bottom-=center_offset_h;
1893
1894       center_offset_v = ((drawRect->right - drawRect->left) - (rcText.bottom - rcText.top)) / 2;
1895     }
1896     else
1897     {
1898       drawRect->left += center_offset_h;
1899       center_offset_v = ((drawRect->bottom - drawRect->top) - (rcText.bottom - rcText.top)) / 2;
1900     }
1901
1902     /* if an item is selected, the text is shifted up instead of down */
1903     if (iItem == infoPtr->iSelected)
1904         center_offset_v -= infoPtr->uVItemPadding / 2;
1905     else
1906         center_offset_v += infoPtr->uVItemPadding / 2;
1907
1908     if (center_offset_v < 0)
1909       center_offset_v = 0;
1910
1911     if(infoPtr->dwStyle & TCS_VERTICAL)
1912       drawRect->left += center_offset_v;
1913     else
1914       drawRect->top += center_offset_v;
1915
1916     /* Draw the text */
1917     if(infoPtr->dwStyle & TCS_VERTICAL) /* if we are vertical rotate the text and each character */
1918     {
1919       static const WCHAR ArialW[] = { 'A','r','i','a','l',0 };
1920       LOGFONTW logfont;
1921       HFONT hFont = 0;
1922       INT nEscapement = 900;
1923       INT nOrientation = 900;
1924
1925       if(infoPtr->dwStyle & TCS_BOTTOM)
1926       {
1927         nEscapement = -900;
1928         nOrientation = -900;
1929       }
1930
1931       /* to get a font with the escapement and orientation we are looking for, we need to */
1932       /* call CreateFontIndirectA, which requires us to set the values of the logfont we pass in */
1933       if (!GetObjectW((infoPtr->hFont) ?
1934                 infoPtr->hFont : GetStockObject(SYSTEM_FONT),
1935                 sizeof(LOGFONTW),&logfont))
1936       {
1937         INT iPointSize = 9;
1938
1939         lstrcpyW(logfont.lfFaceName, ArialW);
1940         logfont.lfHeight = -MulDiv(iPointSize, GetDeviceCaps(hdc, LOGPIXELSY),
1941                                     72);
1942         logfont.lfWeight = FW_NORMAL;
1943         logfont.lfItalic = 0;
1944         logfont.lfUnderline = 0;
1945         logfont.lfStrikeOut = 0;
1946       }
1947
1948       logfont.lfEscapement = nEscapement;
1949       logfont.lfOrientation = nOrientation;
1950       hFont = CreateFontIndirectW(&logfont);
1951       SelectObject(hdc, hFont);
1952
1953       if (item->pszText)
1954       {
1955         ExtTextOutW(hdc,
1956         (infoPtr->dwStyle & TCS_BOTTOM) ? drawRect->right : drawRect->left,
1957         (!(infoPtr->dwStyle & TCS_BOTTOM)) ? drawRect->bottom : drawRect->top,
1958         ETO_CLIPPED,
1959         drawRect,
1960         item->pszText,
1961         lstrlenW(item->pszText),
1962         0);
1963       }
1964
1965       DeleteObject(hFont);
1966     }
1967     else
1968     {
1969       TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%s), textlen=%d\n",
1970           debugstr_w(item->pszText), center_offset_h, center_offset_v,
1971           wine_dbgstr_rect(drawRect), (rcText.right-rcText.left));
1972       if (item->pszText)
1973       {
1974         DrawTextW
1975         (
1976           hdc,
1977           item->pszText,
1978           lstrlenW(item->pszText),
1979           drawRect,
1980           DT_LEFT | DT_SINGLELINE
1981         );
1982       }
1983     }
1984
1985     *drawRect = rcTemp; /* restore drawRect */
1986   }
1987
1988   /*
1989   * Cleanup
1990   */
1991   SelectObject(hdc, hOldFont);
1992   SetBkMode(hdc, oldBkMode);
1993   SelectObject(hdc, holdPen);
1994   DeleteObject( htextPen );
1995 }
1996
1997 /******************************************************************************
1998  * TAB_DrawItem
1999  *
2000  * This method is used to draw a single tab into the tab control.
2001  */
2002 static void TAB_DrawItem(const TAB_INFO *infoPtr, HDC  hdc, INT  iItem)
2003 {
2004   RECT      itemRect;
2005   RECT      selectedRect;
2006   BOOL      isVisible;
2007   RECT      r, fillRect, r1;
2008   INT       clRight = 0;
2009   INT       clBottom = 0;
2010   COLORREF  bkgnd, corner;
2011   HTHEME    theme;
2012
2013   /*
2014    * Get the rectangle for the item.
2015    */
2016   isVisible = TAB_InternalGetItemRect(infoPtr,
2017                                       iItem,
2018                                       &itemRect,
2019                                       &selectedRect);
2020
2021   if (isVisible)
2022   {
2023     RECT rUD, rC;
2024
2025     /* Clip UpDown control to not draw over it */
2026     if (infoPtr->needsScrolling)
2027     {
2028       GetWindowRect(infoPtr->hwnd, &rC);
2029       GetWindowRect(infoPtr->hwndUpDown, &rUD);
2030       ExcludeClipRect(hdc, rUD.left - rC.left, rUD.top - rC.top, rUD.right - rC.left, rUD.bottom - rC.top);
2031     }
2032
2033     /* If you need to see what the control is doing,
2034      * then override these variables. They will change what
2035      * fill colors are used for filling the tabs, and the
2036      * corners when drawing the edge.
2037      */
2038     bkgnd = comctl32_color.clrBtnFace;
2039     corner = comctl32_color.clrBtnFace;
2040
2041     if (infoPtr->dwStyle & TCS_BUTTONS)
2042     {
2043       /* Get item rectangle */
2044       r = itemRect;
2045
2046       /* Separators between flat buttons */
2047       if ((infoPtr->dwStyle & TCS_FLATBUTTONS) && (infoPtr->exStyle & TCS_EX_FLATSEPARATORS))
2048       {
2049         r1 = r;
2050         r1.right += (FLAT_BTN_SPACINGX -2);
2051         DrawEdge(hdc, &r1, EDGE_ETCHED, BF_RIGHT);
2052       }
2053
2054       if (iItem == infoPtr->iSelected)
2055       {
2056         DrawEdge(hdc, &r, EDGE_SUNKEN, BF_SOFT|BF_RECT);
2057         
2058         OffsetRect(&r, 1, 1);
2059       }
2060       else  /* ! selected */
2061       {
2062         DWORD state = TAB_GetItem(infoPtr, iItem)->dwState;
2063
2064         if ((state & TCIS_BUTTONPRESSED) || (iItem == infoPtr->uFocus))
2065           DrawEdge(hdc, &r, EDGE_SUNKEN, BF_SOFT|BF_RECT);
2066         else
2067           if (!(infoPtr->dwStyle & TCS_FLATBUTTONS))
2068             DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_RECT);
2069       }
2070     }
2071     else /* !TCS_BUTTONS */
2072     {
2073       /* We draw a rectangle of different sizes depending on the selection
2074        * state. */
2075       if (iItem == infoPtr->iSelected) {
2076         RECT rect;
2077         GetClientRect (infoPtr->hwnd, &rect);
2078         clRight = rect.right;
2079         clBottom = rect.bottom;
2080         r = selectedRect;
2081       }
2082       else
2083         r = itemRect;
2084
2085       /*
2086        * Erase the background. (Delay it but setup rectangle.)
2087        * This is necessary when drawing the selected item since it is larger
2088        * than the others, it might overlap with stuff already drawn by the
2089        * other tabs
2090        */
2091       fillRect = r;
2092
2093       /* Draw themed tabs - but only if they are at the top.
2094        * Windows draws even side or bottom tabs themed, with wacky results.
2095        * However, since in Wine apps may get themed that did not opt in via
2096        * a manifest avoid theming when we know the result will be wrong */
2097       if ((theme = GetWindowTheme (infoPtr->hwnd)) 
2098           && ((infoPtr->dwStyle & (TCS_VERTICAL | TCS_BOTTOM)) == 0))
2099       {
2100           static const int partIds[8] = {
2101               /* Normal item */
2102               TABP_TABITEM,
2103               TABP_TABITEMLEFTEDGE,
2104               TABP_TABITEMRIGHTEDGE,
2105               TABP_TABITEMBOTHEDGE,
2106               /* Selected tab */
2107               TABP_TOPTABITEM,
2108               TABP_TOPTABITEMLEFTEDGE,
2109               TABP_TOPTABITEMRIGHTEDGE,
2110               TABP_TOPTABITEMBOTHEDGE,
2111           };
2112           int partIndex = 0;
2113           int stateId = TIS_NORMAL;
2114
2115           /* selected and unselected tabs have different parts */
2116           if (iItem == infoPtr->iSelected)
2117               partIndex += 4;
2118           /* The part also differs on the position of a tab on a line.
2119            * "Visually" determining the position works well enough. */
2120           if(selectedRect.left == 0)
2121               partIndex += 1;
2122           if(selectedRect.right == clRight)
2123               partIndex += 2;
2124
2125           if (iItem == infoPtr->iSelected)
2126               stateId = TIS_SELECTED;
2127           else if (iItem == infoPtr->iHotTracked)
2128               stateId = TIS_HOT;
2129           else if (iItem == infoPtr->uFocus)
2130               stateId = TIS_FOCUSED;
2131
2132           /* Adjust rectangle for bottommost row */
2133           if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2134             r.bottom += 3;
2135
2136           DrawThemeBackground (theme, hdc, partIds[partIndex], stateId, &r, NULL);
2137           GetThemeBackgroundContentRect (theme, hdc, partIds[partIndex], stateId, &r, &r);
2138       }
2139       else if(infoPtr->dwStyle & TCS_VERTICAL)
2140       {
2141         /* These are for adjusting the drawing of a Selected tab      */
2142         /* The initial values are for the normal case of non-Selected */
2143         int ZZ = 1;   /* Do not stretch if selected */
2144         if (iItem == infoPtr->iSelected) {
2145             ZZ = 0;
2146
2147             /* if leftmost draw the line longer */
2148             if(selectedRect.top == 0)
2149                 fillRect.top += CONTROL_BORDER_SIZEY;
2150             /* if rightmost draw the line longer */
2151             if(selectedRect.bottom == clBottom)
2152                 fillRect.bottom -= CONTROL_BORDER_SIZEY;
2153         }
2154
2155         if (infoPtr->dwStyle & TCS_BOTTOM)
2156         {
2157           /* Adjust both rectangles to match native */
2158           r.left += (1-ZZ);
2159
2160           TRACE("<right> item=%d, fill=(%s), edge=(%s)\n",
2161                 iItem, wine_dbgstr_rect(&fillRect), wine_dbgstr_rect(&r));
2162
2163           /* Clear interior */
2164           SetBkColor(hdc, bkgnd);
2165           ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2166
2167           /* Draw rectangular edge around tab */
2168           DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_RIGHT|BF_TOP|BF_BOTTOM);
2169
2170           /* Now erase the top corner and draw diagonal edge */
2171           SetBkColor(hdc, corner);
2172           r1.left = r.right - ROUND_CORNER_SIZE - 1;
2173           r1.top = r.top;
2174           r1.right = r.right;
2175           r1.bottom = r1.top + ROUND_CORNER_SIZE;
2176           ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2177           r1.right--;
2178           DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPLEFT);
2179
2180           /* Now erase the bottom corner and draw diagonal edge */
2181           r1.left = r.right - ROUND_CORNER_SIZE - 1;
2182           r1.bottom = r.bottom;
2183           r1.right = r.right;
2184           r1.top = r1.bottom - ROUND_CORNER_SIZE;
2185           ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2186           r1.right--;
2187           DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMLEFT);
2188
2189           if ((iItem == infoPtr->iSelected) && (selectedRect.top == 0)) {
2190               r1 = r;
2191               r1.right = r1.left;
2192               r1.left--;
2193               DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_TOP);
2194           }
2195
2196         }
2197         else
2198         {
2199           TRACE("<left> item=%d, fill=(%s), edge=(%s)\n",
2200                 iItem, wine_dbgstr_rect(&fillRect), wine_dbgstr_rect(&r));
2201
2202           /* Clear interior */
2203           SetBkColor(hdc, bkgnd);
2204           ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2205
2206           /* Draw rectangular edge around tab */
2207           DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_TOP|BF_BOTTOM);
2208
2209           /* Now erase the top corner and draw diagonal edge */
2210           SetBkColor(hdc, corner);
2211           r1.left = r.left;
2212           r1.top = r.top;
2213           r1.right = r1.left + ROUND_CORNER_SIZE + 1;
2214           r1.bottom = r1.top + ROUND_CORNER_SIZE;
2215           ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2216           r1.left++;
2217           DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPRIGHT);
2218
2219           /* Now erase the bottom corner and draw diagonal edge */
2220           r1.left = r.left;
2221           r1.bottom = r.bottom;
2222           r1.right = r1.left + ROUND_CORNER_SIZE + 1;
2223           r1.top = r1.bottom - ROUND_CORNER_SIZE;
2224           ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2225           r1.left++;
2226           DrawEdge(hdc, &r1, EDGE_SUNKEN, BF_DIAGONAL_ENDTOPLEFT);
2227         }
2228       }
2229       else  /* ! TCS_VERTICAL */
2230       {
2231         /* These are for adjusting the drawing of a Selected tab      */
2232         /* The initial values are for the normal case of non-Selected */
2233         if (iItem == infoPtr->iSelected) {
2234             /* if leftmost draw the line longer */
2235             if(selectedRect.left == 0)
2236                 fillRect.left += CONTROL_BORDER_SIZEX;
2237             /* if rightmost draw the line longer */
2238             if(selectedRect.right == clRight)
2239                 fillRect.right -= CONTROL_BORDER_SIZEX;
2240         }
2241
2242         if (infoPtr->dwStyle & TCS_BOTTOM)
2243         {
2244           /* Adjust both rectangles for topmost row */
2245           if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2246           {
2247             fillRect.top -= 2;
2248             r.top -= 1;
2249           }
2250
2251           TRACE("<bottom> item=%d, fill=(%s), edge=(%s)\n",
2252                 iItem, wine_dbgstr_rect(&fillRect), wine_dbgstr_rect(&r));
2253
2254           /* Clear interior */
2255           SetBkColor(hdc, bkgnd);
2256           ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2257
2258           /* Draw rectangular edge around tab */
2259           DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_BOTTOM|BF_RIGHT);
2260
2261           /* Now erase the righthand corner and draw diagonal edge */
2262           SetBkColor(hdc, corner);
2263           r1.left = r.right - ROUND_CORNER_SIZE;
2264           r1.bottom = r.bottom;
2265           r1.right = r.right;
2266           r1.top = r1.bottom - ROUND_CORNER_SIZE - 1;
2267           ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2268           r1.bottom--;
2269           DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMLEFT);
2270
2271           /* Now erase the lefthand corner and draw diagonal edge */
2272           r1.left = r.left;
2273           r1.bottom = r.bottom;
2274           r1.right = r1.left + ROUND_CORNER_SIZE;
2275           r1.top = r1.bottom - ROUND_CORNER_SIZE - 1;
2276           ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2277           r1.bottom--;
2278           DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPLEFT);
2279
2280           if (iItem == infoPtr->iSelected)
2281           {
2282             r.top += 2;
2283             r.left += 1;
2284             if (selectedRect.left == 0)
2285             {
2286               r1 = r;
2287               r1.bottom = r1.top;
2288               r1.top--;
2289               DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_LEFT);
2290             }
2291           }
2292
2293         }
2294         else
2295         {
2296           /* Adjust both rectangles for bottommost row */
2297           if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2298           {
2299             fillRect.bottom += 3;
2300             r.bottom += 2;
2301           }
2302
2303           TRACE("<top> item=%d, fill=(%s), edge=(%s)\n",
2304                 iItem, wine_dbgstr_rect(&fillRect), wine_dbgstr_rect(&r));
2305
2306           /* Clear interior */
2307           SetBkColor(hdc, bkgnd);
2308           ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2309
2310           /* Draw rectangular edge around tab */
2311           DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_TOP|BF_RIGHT);
2312
2313           /* Now erase the righthand corner and draw diagonal edge */
2314           SetBkColor(hdc, corner);
2315           r1.left = r.right - ROUND_CORNER_SIZE;
2316           r1.top = r.top;
2317           r1.right = r.right;
2318           r1.bottom = r1.top + ROUND_CORNER_SIZE + 1;
2319           ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2320           r1.top++;
2321           DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMRIGHT);
2322
2323           /* Now erase the lefthand corner and draw diagonal edge */
2324           r1.left = r.left;
2325           r1.top = r.top;
2326           r1.right = r1.left + ROUND_CORNER_SIZE;
2327           r1.bottom = r1.top + ROUND_CORNER_SIZE + 1;
2328           ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2329           r1.top++;
2330           DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPRIGHT);
2331         }
2332       }
2333     }
2334
2335     TAB_DumpItemInternal(infoPtr, iItem);
2336
2337     /* This modifies r to be the text rectangle. */
2338     TAB_DrawItemInterior(infoPtr, hdc, iItem, &r);
2339   }
2340 }
2341
2342 /******************************************************************************
2343  * TAB_DrawBorder
2344  *
2345  * This method is used to draw the raised border around the tab control
2346  * "content" area.
2347  */
2348 static void TAB_DrawBorder(const TAB_INFO *infoPtr, HDC hdc)
2349 {
2350   RECT rect;
2351   HTHEME theme = GetWindowTheme (infoPtr->hwnd);
2352
2353   GetClientRect (infoPtr->hwnd, &rect);
2354
2355   /*
2356    * Adjust for the style
2357    */
2358
2359   if (infoPtr->uNumItem)
2360   {
2361     if ((infoPtr->dwStyle & TCS_BOTTOM) && !(infoPtr->dwStyle & TCS_VERTICAL))
2362       rect.bottom -= infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2363     else if((infoPtr->dwStyle & TCS_BOTTOM) && (infoPtr->dwStyle & TCS_VERTICAL))
2364       rect.right  -= infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2365     else if(infoPtr->dwStyle & TCS_VERTICAL)
2366       rect.left   += infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2367     else /* not TCS_VERTICAL and not TCS_BOTTOM */
2368       rect.top    += infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2369   }
2370
2371   TRACE("border=(%s)\n", wine_dbgstr_rect(&rect));
2372
2373   if (theme)
2374       DrawThemeBackground (theme, hdc, TABP_PANE, 0, &rect, NULL);
2375   else
2376       DrawEdge(hdc, &rect, EDGE_RAISED, BF_SOFT|BF_RECT);
2377 }
2378
2379 /******************************************************************************
2380  * TAB_Refresh
2381  *
2382  * This method repaints the tab control..
2383  */
2384 static void TAB_Refresh (const TAB_INFO *infoPtr, HDC hdc)
2385 {
2386   HFONT hOldFont;
2387   INT i;
2388
2389   if (!infoPtr->DoRedraw)
2390     return;
2391
2392   hOldFont = SelectObject (hdc, infoPtr->hFont);
2393
2394   if (infoPtr->dwStyle & TCS_BUTTONS)
2395   {
2396     for (i = 0; i < infoPtr->uNumItem; i++)
2397       TAB_DrawItem (infoPtr, hdc, i);
2398   }
2399   else
2400   {
2401     /* Draw all the non selected item first */
2402     for (i = 0; i < infoPtr->uNumItem; i++)
2403     {
2404       if (i != infoPtr->iSelected)
2405         TAB_DrawItem (infoPtr, hdc, i);
2406     }
2407
2408     /* Now, draw the border, draw it before the selected item
2409      * since the selected item overwrites part of the border. */
2410     TAB_DrawBorder (infoPtr, hdc);
2411
2412     /* Then, draw the selected item */
2413     TAB_DrawItem (infoPtr, hdc, infoPtr->iSelected);
2414   }
2415
2416   SelectObject (hdc, hOldFont);
2417 }
2418
2419 static inline DWORD TAB_GetRowCount (const TAB_INFO *infoPtr)
2420 {
2421   TRACE("(%p)\n", infoPtr);
2422   return infoPtr->uNumRows;
2423 }
2424
2425 static inline LRESULT TAB_SetRedraw (TAB_INFO *infoPtr, BOOL doRedraw)
2426 {
2427   infoPtr->DoRedraw = doRedraw;
2428   return 0;
2429 }
2430
2431 /******************************************************************************
2432  * TAB_EnsureSelectionVisible
2433  *
2434  * This method will make sure that the current selection is completely
2435  * visible by scrolling until it is.
2436  */
2437 static void TAB_EnsureSelectionVisible(
2438   TAB_INFO* infoPtr)
2439 {
2440   INT iSelected = infoPtr->iSelected;
2441   INT iOrigLeftmostVisible = infoPtr->leftmostVisible;
2442
2443   /* set the items row to the bottommost row or topmost row depending on
2444    * style */
2445   if ((infoPtr->uNumRows > 1) && !(infoPtr->dwStyle & TCS_BUTTONS))
2446   {
2447       TAB_ITEM *selected = TAB_GetItem(infoPtr, iSelected);
2448       INT newselected;
2449       INT iTargetRow;
2450
2451       if(infoPtr->dwStyle & TCS_VERTICAL)
2452         newselected = selected->rect.left;
2453       else
2454         newselected = selected->rect.top;
2455
2456       /* the target row is always (number of rows - 1)
2457          as row 0 is furthest from the clientRect */
2458       iTargetRow = infoPtr->uNumRows - 1;
2459
2460       if (newselected != iTargetRow)
2461       {
2462          UINT i;
2463          if(infoPtr->dwStyle & TCS_VERTICAL)
2464          {
2465            for (i=0; i < infoPtr->uNumItem; i++)
2466            {
2467              /* move everything in the row of the selected item to the iTargetRow */
2468              TAB_ITEM *item = TAB_GetItem(infoPtr, i);
2469
2470              if (item->rect.left == newselected )
2471                  item->rect.left = iTargetRow;
2472              else
2473              {
2474                if (item->rect.left > newselected)
2475                  item->rect.left-=1;
2476              }
2477            }
2478          }
2479          else
2480          {
2481            for (i=0; i < infoPtr->uNumItem; i++)
2482            {
2483              TAB_ITEM *item = TAB_GetItem(infoPtr, i);
2484
2485              if (item->rect.top == newselected )
2486                  item->rect.top = iTargetRow;
2487              else
2488              {
2489                if (item->rect.top > newselected)
2490                  item->rect.top-=1;
2491              }
2492           }
2493         }
2494         TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
2495       }
2496   }
2497
2498   /*
2499    * Do the trivial cases first.
2500    */
2501   if ( (!infoPtr->needsScrolling) ||
2502        (infoPtr->hwndUpDown==0) || (infoPtr->dwStyle & TCS_VERTICAL))
2503     return;
2504
2505   if (infoPtr->leftmostVisible >= iSelected)
2506   {
2507     if (iSelected >= 0) infoPtr->leftmostVisible = iSelected;
2508   }
2509   else
2510   {
2511      TAB_ITEM *selected = TAB_GetItem(infoPtr, iSelected);
2512      RECT r;
2513      INT width;
2514      UINT i;
2515
2516      /* Calculate the part of the client area that is visible */
2517      GetClientRect(infoPtr->hwnd, &r);
2518      width = r.right;
2519
2520      GetClientRect(infoPtr->hwndUpDown, &r);
2521      width -= r.right;
2522
2523      if ((selected->rect.right -
2524           selected->rect.left) >= width )
2525      {
2526         /* Special case: width of selected item is greater than visible
2527          * part of control.
2528          */
2529         infoPtr->leftmostVisible = iSelected;
2530      }
2531      else
2532      {
2533         for (i = infoPtr->leftmostVisible; i < infoPtr->uNumItem; i++)
2534         {
2535            if ((selected->rect.right - TAB_GetItem(infoPtr, i)->rect.left) < width)
2536               break;
2537         }
2538         infoPtr->leftmostVisible = i;
2539      }
2540   }
2541
2542   if (infoPtr->leftmostVisible != iOrigLeftmostVisible)
2543     TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
2544
2545   SendMessageW(infoPtr->hwndUpDown, UDM_SETPOS, 0,
2546                MAKELONG(infoPtr->leftmostVisible, 0));
2547 }
2548
2549 /******************************************************************************
2550  * TAB_InvalidateTabArea
2551  *
2552  * This method will invalidate the portion of the control that contains the
2553  * tabs. It is called when the state of the control changes and needs
2554  * to be redisplayed
2555  */
2556 static void TAB_InvalidateTabArea(const TAB_INFO *infoPtr)
2557 {
2558   RECT clientRect, rInvalidate, rAdjClient;
2559   INT lastRow = infoPtr->uNumRows - 1;
2560   RECT rect;
2561
2562   if (lastRow < 0) return;
2563
2564   GetClientRect(infoPtr->hwnd, &clientRect);
2565   rInvalidate = clientRect;
2566   rAdjClient = clientRect;
2567
2568   TAB_AdjustRect(infoPtr, 0, &rAdjClient);
2569
2570   TAB_InternalGetItemRect(infoPtr, infoPtr->uNumItem-1 , &rect, NULL);
2571   if ((infoPtr->dwStyle & TCS_BOTTOM) && (infoPtr->dwStyle & TCS_VERTICAL))
2572   {
2573     rInvalidate.left = rAdjClient.right;
2574     if (infoPtr->uNumRows == 1)
2575       rInvalidate.bottom = clientRect.top + rect.bottom + 2 * SELECTED_TAB_OFFSET;
2576   }
2577   else if(infoPtr->dwStyle & TCS_VERTICAL)
2578   {
2579     rInvalidate.right = rAdjClient.left;
2580     if (infoPtr->uNumRows == 1)
2581       rInvalidate.bottom = clientRect.top + rect.bottom + 2 * SELECTED_TAB_OFFSET;
2582   }
2583   else if (infoPtr->dwStyle & TCS_BOTTOM)
2584   {
2585     rInvalidate.top = rAdjClient.bottom;
2586     if (infoPtr->uNumRows == 1)
2587       rInvalidate.right = clientRect.left + rect.right + 2 * SELECTED_TAB_OFFSET;
2588   }
2589   else 
2590   {
2591     rInvalidate.bottom = rAdjClient.top;
2592     if (infoPtr->uNumRows == 1)
2593       rInvalidate.right = clientRect.left + rect.right + 2 * SELECTED_TAB_OFFSET;
2594   }
2595   
2596   /* Punch out the updown control */
2597   if (infoPtr->needsScrolling && (rInvalidate.right > 0)) {
2598     RECT r;
2599     GetClientRect(infoPtr->hwndUpDown, &r);
2600     if (rInvalidate.right > clientRect.right - r.left)
2601       rInvalidate.right = rInvalidate.right - (r.right - r.left);
2602     else
2603       rInvalidate.right = clientRect.right - r.left;
2604   }
2605
2606   TRACE("invalidate (%s)\n", wine_dbgstr_rect(&rInvalidate));
2607
2608   InvalidateRect(infoPtr->hwnd, &rInvalidate, TRUE);
2609 }
2610
2611 static inline LRESULT TAB_Paint (TAB_INFO *infoPtr, HDC hdcPaint)
2612 {
2613   HDC hdc;
2614   PAINTSTRUCT ps;
2615
2616   if (hdcPaint)
2617     hdc = hdcPaint;
2618   else
2619   {
2620     hdc = BeginPaint (infoPtr->hwnd, &ps);
2621     TRACE("erase %d, rect=(%s)\n", ps.fErase, wine_dbgstr_rect(&ps.rcPaint));
2622   }
2623
2624   TAB_Refresh (infoPtr, hdc);
2625
2626   if (!hdcPaint)
2627     EndPaint (infoPtr->hwnd, &ps);
2628
2629   return 0;
2630 }
2631
2632 static LRESULT
2633 TAB_InsertItemT (TAB_INFO *infoPtr, INT iItem, const TCITEMW *pti, BOOL bUnicode)
2634 {
2635   TAB_ITEM *item;
2636   RECT rect;
2637
2638   GetClientRect (infoPtr->hwnd, &rect);
2639   TRACE("Rect: %p %s\n", infoPtr->hwnd, wine_dbgstr_rect(&rect));
2640
2641   if (iItem < 0) return -1;
2642   if (iItem > infoPtr->uNumItem)
2643     iItem = infoPtr->uNumItem;
2644
2645   TAB_DumpItemExternalT(pti, iItem, bUnicode);
2646
2647   if (!(item = Alloc(sizeof(TAB_ITEM_SIZE(infoPtr))))) return FALSE;
2648   if (DPA_InsertPtr(infoPtr->items, iItem, item) == -1)
2649   {
2650       Free(item);
2651       return FALSE;
2652   }
2653
2654   if (infoPtr->uNumItem == 0)
2655       infoPtr->iSelected = 0;
2656   else if (iItem <= infoPtr->iSelected)
2657       infoPtr->iSelected++;
2658
2659   infoPtr->uNumItem++;
2660
2661   item->pszText = NULL;
2662   if (pti->mask & TCIF_TEXT)
2663   {
2664     if (bUnicode)
2665       Str_SetPtrW (&item->pszText, pti->pszText);
2666     else
2667       Str_SetPtrAtoW (&item->pszText, (LPSTR)pti->pszText);
2668   }
2669
2670   if (pti->mask & TCIF_IMAGE)
2671     item->iImage = pti->iImage;
2672   else
2673     item->iImage = -1;
2674
2675   if (pti->mask & TCIF_PARAM)
2676     memcpy(item->extra, &pti->lParam, EXTRA_ITEM_SIZE(infoPtr));
2677   else
2678     memset(item->extra, 0, EXTRA_ITEM_SIZE(infoPtr));
2679
2680   TAB_SetItemBounds(infoPtr);
2681   if (infoPtr->uNumItem > 1)
2682     TAB_InvalidateTabArea(infoPtr);
2683   else
2684     InvalidateRect(infoPtr->hwnd, NULL, TRUE);
2685
2686   TRACE("[%p]: added item %d %s\n",
2687         infoPtr->hwnd, iItem, debugstr_w(item->pszText));
2688
2689   /* If we haven't set the current focus yet, set it now. */
2690   if (infoPtr->uFocus == -1)
2691     TAB_SetCurFocus(infoPtr, iItem);
2692
2693   return iItem;
2694 }
2695
2696 static LRESULT
2697 TAB_SetItemSize (TAB_INFO *infoPtr, INT cx, INT cy)
2698 {
2699   LONG lResult = 0;
2700   BOOL bNeedPaint = FALSE;
2701
2702   lResult = MAKELONG(infoPtr->tabWidth, infoPtr->tabHeight);
2703
2704   /* UNDOCUMENTED: If requested Width or Height is 0 this means that program wants to use auto size. */
2705   if (infoPtr->dwStyle & TCS_FIXEDWIDTH && (infoPtr->tabWidth != cx))
2706   {
2707     infoPtr->tabWidth = cx;
2708     bNeedPaint = TRUE;
2709   }
2710
2711   if (infoPtr->tabHeight != cy)
2712   {
2713     if ((infoPtr->fHeightSet = (cy != 0)))
2714       infoPtr->tabHeight = cy;
2715
2716     bNeedPaint = TRUE;
2717   }
2718   TRACE("was h=%d,w=%d, now h=%d,w=%d\n",
2719        HIWORD(lResult), LOWORD(lResult),
2720        infoPtr->tabHeight, infoPtr->tabWidth);
2721
2722   if (bNeedPaint)
2723   {
2724     TAB_SetItemBounds(infoPtr);
2725     RedrawWindow(infoPtr->hwnd, NULL, NULL, RDW_ERASE | RDW_INVALIDATE | RDW_UPDATENOW);
2726   }
2727
2728   return lResult;
2729 }
2730
2731 static inline LRESULT TAB_SetMinTabWidth (TAB_INFO *infoPtr, INT cx)
2732 {
2733   INT oldcx = 0;
2734
2735   TRACE("(%p,%d)\n", infoPtr, cx);
2736
2737   if (infoPtr->tabMinWidth < 0)
2738     oldcx = DEFAULT_MIN_TAB_WIDTH;
2739   else
2740     oldcx = infoPtr->tabMinWidth;
2741   infoPtr->tabMinWidth = cx;
2742   TAB_SetItemBounds(infoPtr);
2743   return oldcx;
2744 }
2745
2746 static inline LRESULT 
2747 TAB_HighlightItem (TAB_INFO *infoPtr, INT iItem, BOOL fHighlight)
2748 {
2749   LPDWORD lpState;
2750   DWORD oldState;
2751   RECT r;
2752
2753   TRACE("(%p,%d,%s)\n", infoPtr, iItem, fHighlight ? "true" : "false");
2754
2755   if (iItem < 0 || iItem >= infoPtr->uNumItem)
2756     return FALSE;
2757
2758   lpState = &TAB_GetItem(infoPtr, iItem)->dwState;
2759   oldState = *lpState;
2760
2761   if (fHighlight)
2762     *lpState |= TCIS_HIGHLIGHTED;
2763   else
2764     *lpState &= ~TCIS_HIGHLIGHTED;
2765
2766   if ((oldState != *lpState) && TAB_InternalGetItemRect (infoPtr, iItem, &r, NULL))
2767     InvalidateRect (infoPtr->hwnd, &r, TRUE);
2768
2769   return TRUE;
2770 }
2771
2772 static LRESULT
2773 TAB_SetItemT (TAB_INFO *infoPtr, INT iItem, LPTCITEMW tabItem, BOOL bUnicode)
2774 {
2775   TAB_ITEM *wineItem;
2776
2777   TRACE("(%p,%d,%p,%s)\n", infoPtr, iItem, tabItem, bUnicode ? "true" : "false");
2778
2779   if (iItem < 0 || iItem >= infoPtr->uNumItem)
2780     return FALSE;
2781
2782   TAB_DumpItemExternalT(tabItem, iItem, bUnicode);
2783
2784   wineItem = TAB_GetItem(infoPtr, iItem);
2785
2786   if (tabItem->mask & TCIF_IMAGE)
2787     wineItem->iImage = tabItem->iImage;
2788
2789   if (tabItem->mask & TCIF_PARAM)
2790     memcpy(wineItem->extra, &tabItem->lParam, infoPtr->cbInfo);
2791
2792   if (tabItem->mask & TCIF_RTLREADING)
2793     FIXME("TCIF_RTLREADING\n");
2794
2795   if (tabItem->mask & TCIF_STATE)
2796     wineItem->dwState = (wineItem->dwState & ~tabItem->dwStateMask) |
2797                         ( tabItem->dwState &  tabItem->dwStateMask);
2798
2799   if (tabItem->mask & TCIF_TEXT)
2800   {
2801     Free(wineItem->pszText);
2802     wineItem->pszText = NULL;
2803     if (bUnicode)
2804       Str_SetPtrW(&wineItem->pszText, tabItem->pszText);
2805     else
2806       Str_SetPtrAtoW(&wineItem->pszText, (LPSTR)tabItem->pszText);
2807   }
2808
2809   /* Update and repaint tabs */
2810   TAB_SetItemBounds(infoPtr);
2811   TAB_InvalidateTabArea(infoPtr);
2812
2813   return TRUE;
2814 }
2815
2816 static inline LRESULT TAB_GetItemCount (const TAB_INFO *infoPtr)
2817 {
2818   TRACE("\n");
2819   return infoPtr->uNumItem;
2820 }
2821
2822
2823 static LRESULT
2824 TAB_GetItemT (TAB_INFO *infoPtr, INT iItem, LPTCITEMW tabItem, BOOL bUnicode)
2825 {
2826   TAB_ITEM *wineItem;
2827
2828   TRACE("(%p,%d,%p,%s)\n", infoPtr, iItem, tabItem, bUnicode ? "true" : "false");
2829
2830   if (!tabItem) return FALSE;
2831
2832   if (iItem < 0 || iItem >= infoPtr->uNumItem)
2833   {
2834     /* init requested fields */
2835     if (tabItem->mask & TCIF_IMAGE) tabItem->iImage  = 0;
2836     if (tabItem->mask & TCIF_PARAM) tabItem->lParam  = 0;
2837     if (tabItem->mask & TCIF_STATE) tabItem->dwState = 0;
2838     return FALSE;
2839   }
2840
2841   wineItem = TAB_GetItem(infoPtr, iItem);
2842
2843   if (tabItem->mask & TCIF_IMAGE)
2844     tabItem->iImage = wineItem->iImage;
2845
2846   if (tabItem->mask & TCIF_PARAM)
2847     memcpy(&tabItem->lParam, wineItem->extra, infoPtr->cbInfo);
2848
2849   if (tabItem->mask & TCIF_RTLREADING)
2850     FIXME("TCIF_RTLREADING\n");
2851
2852   if (tabItem->mask & TCIF_STATE)
2853     tabItem->dwState = wineItem->dwState & tabItem->dwStateMask;
2854
2855   if (tabItem->mask & TCIF_TEXT)
2856   {
2857     if (bUnicode)
2858       Str_GetPtrW (wineItem->pszText, tabItem->pszText, tabItem->cchTextMax);
2859     else
2860       Str_GetPtrWtoA (wineItem->pszText, (LPSTR)tabItem->pszText, tabItem->cchTextMax);
2861   }
2862
2863   TAB_DumpItemExternalT(tabItem, iItem, bUnicode);
2864
2865   return TRUE;
2866 }
2867
2868
2869 static LRESULT TAB_DeleteItem (TAB_INFO *infoPtr, INT iItem)
2870 {
2871     TAB_ITEM *item;
2872
2873     TRACE("(%p, %d)\n", infoPtr, iItem);
2874
2875     if (iItem < 0 || iItem >= infoPtr->uNumItem) return FALSE;
2876
2877     item = TAB_GetItem(infoPtr, iItem);
2878     Free(item->pszText);
2879     Free(item);
2880     infoPtr->uNumItem--;
2881     DPA_DeletePtr(infoPtr->items, iItem);
2882
2883     TAB_InvalidateTabArea(infoPtr);
2884
2885     if (infoPtr->uNumItem == 0)
2886     {
2887         if (infoPtr->iHotTracked >= 0)
2888         {
2889             KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
2890             infoPtr->iHotTracked = -1;
2891         }
2892
2893         infoPtr->iSelected = -1;
2894     }
2895     else
2896     {
2897         if (iItem <= infoPtr->iHotTracked)
2898         {
2899             /* When tabs move left/up, the hot track item may change */
2900             FIXME("Recalc hot track\n");
2901         }
2902     }
2903
2904     /* adjust the selected index */
2905     if (iItem == infoPtr->iSelected)
2906         infoPtr->iSelected = -1;
2907     else if (iItem < infoPtr->iSelected)
2908         infoPtr->iSelected--;
2909
2910     /* reposition and repaint tabs */
2911     TAB_SetItemBounds(infoPtr);
2912
2913     return TRUE;
2914 }
2915
2916 static inline LRESULT TAB_DeleteAllItems (TAB_INFO *infoPtr)
2917 {
2918     TRACE("(%p)\n", infoPtr);
2919     while (infoPtr->uNumItem)
2920       TAB_DeleteItem (infoPtr, 0);
2921     return TRUE;
2922 }
2923
2924
2925 static inline LRESULT TAB_GetFont (const TAB_INFO *infoPtr)
2926 {
2927   TRACE("(%p) returning %p\n", infoPtr, infoPtr->hFont);
2928   return (LRESULT)infoPtr->hFont;
2929 }
2930
2931 static inline LRESULT TAB_SetFont (TAB_INFO *infoPtr, HFONT hNewFont)
2932 {
2933   TRACE("(%p,%p)\n", infoPtr, hNewFont);
2934
2935   infoPtr->hFont = hNewFont;
2936
2937   TAB_SetItemBounds(infoPtr);
2938
2939   TAB_InvalidateTabArea(infoPtr);
2940
2941   return 0;
2942 }
2943
2944
2945 static inline LRESULT TAB_GetImageList (const TAB_INFO *infoPtr)
2946 {
2947   TRACE("\n");
2948   return (LRESULT)infoPtr->himl;
2949 }
2950
2951 static inline LRESULT TAB_SetImageList (TAB_INFO *infoPtr, HIMAGELIST himlNew)
2952 {
2953     HIMAGELIST himlPrev = infoPtr->himl;
2954     TRACE("himl=%p\n", himlNew);
2955     infoPtr->himl = himlNew;
2956     TAB_SetItemBounds(infoPtr);
2957     InvalidateRect(infoPtr->hwnd, NULL, TRUE);
2958     return (LRESULT)himlPrev;
2959 }
2960
2961 static inline LRESULT TAB_GetUnicodeFormat (const TAB_INFO *infoPtr)
2962 {
2963     TRACE("(%p)\n", infoPtr);
2964     return infoPtr->bUnicode;
2965 }
2966
2967 static inline LRESULT TAB_SetUnicodeFormat (TAB_INFO *infoPtr, BOOL bUnicode)
2968 {
2969     BOOL bTemp = infoPtr->bUnicode;
2970
2971     TRACE("(%p %d)\n", infoPtr, bUnicode);
2972     infoPtr->bUnicode = bUnicode;
2973
2974     return bTemp;
2975 }
2976
2977 static inline LRESULT TAB_Size (TAB_INFO *infoPtr)
2978 {
2979 /* I'm not really sure what the following code was meant to do.
2980    This is what it is doing:
2981    When WM_SIZE is sent with SIZE_RESTORED, the control
2982    gets positioned in the top left corner.
2983
2984   RECT parent_rect;
2985   HWND parent;
2986   UINT uPosFlags,cx,cy;
2987
2988   uPosFlags=0;
2989   if (!wParam) {
2990     parent = GetParent (hwnd);
2991     GetClientRect(parent, &parent_rect);
2992     cx=LOWORD (lParam);
2993     cy=HIWORD (lParam);
2994     if (GetWindowLongW(hwnd, GWL_STYLE) & CCS_NORESIZE)
2995         uPosFlags |= (SWP_NOSIZE | SWP_NOMOVE);
2996
2997     SetWindowPos (hwnd, 0, parent_rect.left, parent_rect.top,
2998             cx, cy, uPosFlags | SWP_NOZORDER);
2999   } else {
3000     FIXME("WM_SIZE flag %x %lx not handled\n", wParam, lParam);
3001   } */
3002
3003   /* Recompute the size/position of the tabs. */
3004   TAB_SetItemBounds (infoPtr);
3005
3006   /* Force a repaint of the control. */
3007   InvalidateRect(infoPtr->hwnd, NULL, TRUE);
3008
3009   return 0;
3010 }
3011
3012
3013 static LRESULT TAB_Create (HWND hwnd, LPARAM lParam)
3014 {
3015   TAB_INFO *infoPtr;
3016   TEXTMETRICW fontMetrics;
3017   HDC hdc;
3018   HFONT hOldFont;
3019   DWORD dwStyle;
3020
3021   infoPtr = Alloc (sizeof(TAB_INFO));
3022
3023   SetWindowLongPtrW(hwnd, 0, (DWORD_PTR)infoPtr);
3024
3025   infoPtr->hwnd            = hwnd;
3026   infoPtr->hwndNotify      = ((LPCREATESTRUCTW)lParam)->hwndParent;
3027   infoPtr->uNumItem        = 0;
3028   infoPtr->uNumRows        = 0;
3029   infoPtr->uHItemPadding   = 6;
3030   infoPtr->uVItemPadding   = 3;
3031   infoPtr->uHItemPadding_s = 6;
3032   infoPtr->uVItemPadding_s = 3;
3033   infoPtr->hFont           = 0;
3034   infoPtr->items           = DPA_Create(8);
3035   infoPtr->hcurArrow       = LoadCursorW (0, (LPWSTR)IDC_ARROW);
3036   infoPtr->iSelected       = -1;
3037   infoPtr->iHotTracked     = -1;
3038   infoPtr->uFocus          = -1;
3039   infoPtr->hwndToolTip     = 0;
3040   infoPtr->DoRedraw        = TRUE;
3041   infoPtr->needsScrolling  = FALSE;
3042   infoPtr->hwndUpDown      = 0;
3043   infoPtr->leftmostVisible = 0;
3044   infoPtr->fHeightSet      = FALSE;
3045   infoPtr->bUnicode        = IsWindowUnicode (hwnd);
3046   infoPtr->cbInfo          = sizeof(LPARAM);
3047
3048   TRACE("Created tab control, hwnd [%p]\n", hwnd);
3049
3050   /* The tab control always has the WS_CLIPSIBLINGS style. Even
3051      if you don't specify it in CreateWindow. This is necessary in
3052      order for paint to work correctly. This follows windows behaviour. */
3053   dwStyle = GetWindowLongW(hwnd, GWL_STYLE);
3054   SetWindowLongW(hwnd, GWL_STYLE, dwStyle|WS_CLIPSIBLINGS);
3055
3056   infoPtr->dwStyle = dwStyle | WS_CLIPSIBLINGS;
3057   infoPtr->exStyle = (dwStyle & TCS_FLATBUTTONS) ? TCS_EX_FLATSEPARATORS : 0;
3058
3059   if (infoPtr->dwStyle & TCS_TOOLTIPS) {
3060     /* Create tooltip control */
3061     infoPtr->hwndToolTip =
3062       CreateWindowExW (0, TOOLTIPS_CLASSW, NULL, WS_POPUP,
3063                        CW_USEDEFAULT, CW_USEDEFAULT,
3064                        CW_USEDEFAULT, CW_USEDEFAULT,
3065                        hwnd, 0, 0, 0);
3066
3067     /* Send NM_TOOLTIPSCREATED notification */
3068     if (infoPtr->hwndToolTip) {
3069       NMTOOLTIPSCREATED nmttc;
3070
3071       nmttc.hdr.hwndFrom = hwnd;
3072       nmttc.hdr.idFrom = GetWindowLongPtrW(hwnd, GWLP_ID);
3073       nmttc.hdr.code = NM_TOOLTIPSCREATED;
3074       nmttc.hwndToolTips = infoPtr->hwndToolTip;
3075
3076       SendMessageW (infoPtr->hwndNotify, WM_NOTIFY,
3077                     GetWindowLongPtrW(hwnd, GWLP_ID), (LPARAM)&nmttc);
3078     }
3079   }
3080
3081   OpenThemeData (infoPtr->hwnd, themeClass);
3082   
3083   /*
3084    * We need to get text information so we need a DC and we need to select
3085    * a font.
3086    */
3087   hdc = GetDC(hwnd);
3088   hOldFont = SelectObject (hdc, GetStockObject (SYSTEM_FONT));
3089
3090   /* Use the system font to determine the initial height of a tab. */
3091   GetTextMetricsW(hdc, &fontMetrics);
3092
3093   /*
3094    * Make sure there is enough space for the letters + growing the
3095    * selected item + extra space for the selected item.
3096    */
3097   infoPtr->tabHeight = fontMetrics.tmHeight + SELECTED_TAB_OFFSET +
3098                        ((infoPtr->dwStyle & TCS_BUTTONS) ? 2 : 1) *
3099                         infoPtr->uVItemPadding;
3100
3101   /* Initialize the width of a tab. */
3102   if (infoPtr->dwStyle & TCS_FIXEDWIDTH)
3103     infoPtr->tabWidth = GetDeviceCaps(hdc, LOGPIXELSX);
3104
3105   infoPtr->tabMinWidth = -1;
3106
3107   TRACE("tabH=%d, tabW=%d\n", infoPtr->tabHeight, infoPtr->tabWidth);
3108
3109   SelectObject (hdc, hOldFont);
3110   ReleaseDC(hwnd, hdc);
3111
3112   return 0;
3113 }
3114
3115 static LRESULT
3116 TAB_Destroy (TAB_INFO *infoPtr)
3117 {
3118   INT iItem;
3119
3120   SetWindowLongPtrW(infoPtr->hwnd, 0, 0);
3121
3122   for (iItem = infoPtr->uNumItem - 1; iItem >= 0; iItem--)
3123   {
3124       TAB_ITEM *tab = TAB_GetItem(infoPtr, iItem);
3125
3126       DPA_DeletePtr(infoPtr->items, iItem);
3127       infoPtr->uNumItem--;
3128
3129       Free(tab->pszText);
3130       Free(tab);
3131   }
3132   DPA_Destroy(infoPtr->items);
3133   infoPtr->items = NULL;
3134
3135   if (infoPtr->hwndToolTip)
3136     DestroyWindow (infoPtr->hwndToolTip);
3137
3138   if (infoPtr->hwndUpDown)
3139     DestroyWindow(infoPtr->hwndUpDown);
3140
3141   if (infoPtr->iHotTracked >= 0)
3142     KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
3143
3144   CloseThemeData (GetWindowTheme (infoPtr->hwnd));
3145
3146   Free (infoPtr);
3147   return 0;
3148 }
3149
3150 /* update theme after a WM_THEMECHANGED message */
3151 static LRESULT theme_changed(const TAB_INFO *infoPtr)
3152 {
3153     HTHEME theme = GetWindowTheme (infoPtr->hwnd);
3154     CloseThemeData (theme);
3155     OpenThemeData (infoPtr->hwnd, themeClass);
3156     return 0;
3157 }
3158
3159 static LRESULT TAB_NCCalcSize(WPARAM wParam)
3160 {
3161   if (!wParam)
3162     return 0;
3163   return WVR_ALIGNTOP;
3164 }
3165
3166 static inline LRESULT
3167 TAB_SetItemExtra (TAB_INFO *infoPtr, INT cbInfo)
3168 {
3169   TRACE("(%p %d)\n", infoPtr, cbInfo);
3170
3171   if (cbInfo < 0 || infoPtr->uNumItem) return FALSE;
3172
3173   infoPtr->cbInfo = cbInfo;
3174   return TRUE;
3175 }
3176
3177 static LRESULT TAB_RemoveImage (TAB_INFO *infoPtr, INT image)
3178 {
3179   TRACE("%p %d\n", infoPtr, image);
3180
3181   if (ImageList_Remove (infoPtr->himl, image))
3182   {
3183     INT i, *idx;
3184     RECT r;
3185
3186     /* shift indices, repaint items if needed */
3187     for (i = 0; i < infoPtr->uNumItem; i++)
3188     {
3189       idx = &TAB_GetItem(infoPtr, i)->iImage;
3190       if (*idx >= image)
3191       {
3192         if (*idx == image)
3193           *idx = -1;
3194         else
3195           (*idx)--;
3196
3197         /* repaint item */
3198         if (TAB_InternalGetItemRect (infoPtr, i, &r, NULL))
3199           InvalidateRect (infoPtr->hwnd, &r, TRUE);
3200       }
3201     }
3202   }
3203
3204   return 0;
3205 }
3206
3207 static LRESULT
3208 TAB_SetExtendedStyle (TAB_INFO *infoPtr, DWORD exMask, DWORD exStyle)
3209 {
3210   DWORD prevstyle = infoPtr->exStyle;
3211
3212   /* zero mask means all styles */
3213   if (exMask == 0) exMask = ~0;
3214
3215   if (exMask & TCS_EX_REGISTERDROP)
3216   {
3217     FIXME("TCS_EX_REGISTERDROP style unimplemented\n");
3218     exMask  &= ~TCS_EX_REGISTERDROP;
3219     exStyle &= ~TCS_EX_REGISTERDROP;
3220   }
3221
3222   if (exMask & TCS_EX_FLATSEPARATORS)
3223   {
3224     if ((prevstyle ^ exStyle) & TCS_EX_FLATSEPARATORS)
3225     {
3226         infoPtr->exStyle ^= TCS_EX_FLATSEPARATORS;
3227         TAB_InvalidateTabArea(infoPtr);
3228     }
3229   }
3230
3231   return prevstyle;
3232 }
3233
3234 static inline LRESULT
3235 TAB_GetExtendedStyle (const TAB_INFO *infoPtr)
3236 {
3237   return infoPtr->exStyle;
3238 }
3239
3240 static LRESULT
3241 TAB_DeselectAll (TAB_INFO *infoPtr, BOOL excludesel)
3242 {
3243   BOOL paint = FALSE;
3244   INT i, selected = infoPtr->iSelected;
3245
3246   TRACE("(%p, %d)\n", infoPtr, excludesel);
3247
3248   if (!(infoPtr->dwStyle & TCS_BUTTONS))
3249     return 0;
3250
3251   for (i = 0; i < infoPtr->uNumItem; i++)
3252   {
3253     if ((TAB_GetItem(infoPtr, i)->dwState & TCIS_BUTTONPRESSED) &&
3254         (selected != i))
3255     {
3256       TAB_GetItem(infoPtr, i)->dwState &= ~TCIS_BUTTONPRESSED;
3257       paint = TRUE;
3258     }
3259   }
3260
3261   if (!excludesel && (selected != -1))
3262   {
3263     TAB_GetItem(infoPtr, selected)->dwState &= ~TCIS_BUTTONPRESSED;
3264     infoPtr->iSelected = -1;
3265     paint = TRUE;
3266   }
3267
3268   if (paint)
3269     TAB_InvalidateTabArea (infoPtr);
3270
3271   return 0;
3272 }
3273
3274 /***
3275  * DESCRIPTION:
3276  * Processes WM_STYLECHANGED messages.
3277  *
3278  * PARAMETER(S):
3279  * [I] infoPtr : valid pointer to the tab data structure
3280  * [I] wStyleType : window style type (normal or extended)
3281  * [I] lpss : window style information
3282  *
3283  * RETURN:
3284  * Zero
3285  */
3286 static INT TAB_StyleChanged(TAB_INFO *infoPtr, WPARAM wStyleType,
3287                             const STYLESTRUCT *lpss)
3288 {
3289     TRACE("(styletype=%lx, styleOld=0x%08x, styleNew=0x%08x)\n",
3290           wStyleType, lpss->styleOld, lpss->styleNew);
3291
3292     if (wStyleType != GWL_STYLE) return 0;
3293
3294     infoPtr->dwStyle = lpss->styleNew;
3295
3296     TAB_SetItemBounds (infoPtr);
3297     InvalidateRect(infoPtr->hwnd, NULL, TRUE);
3298
3299     return 0;
3300 }
3301
3302 static LRESULT WINAPI
3303 TAB_WindowProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
3304 {
3305     TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
3306
3307     TRACE("hwnd=%p msg=%x wParam=%lx lParam=%lx\n", hwnd, uMsg, wParam, lParam);
3308     if (!infoPtr && (uMsg != WM_CREATE))
3309       return DefWindowProcW (hwnd, uMsg, wParam, lParam);
3310
3311     switch (uMsg)
3312     {
3313     case TCM_GETIMAGELIST:
3314       return TAB_GetImageList (infoPtr);
3315
3316     case TCM_SETIMAGELIST:
3317       return TAB_SetImageList (infoPtr, (HIMAGELIST)lParam);
3318
3319     case TCM_GETITEMCOUNT:
3320       return TAB_GetItemCount (infoPtr);
3321
3322     case TCM_GETITEMA:
3323     case TCM_GETITEMW:
3324       return TAB_GetItemT (infoPtr, (INT)wParam, (LPTCITEMW)lParam, uMsg == TCM_GETITEMW);
3325
3326     case TCM_SETITEMA:
3327     case TCM_SETITEMW:
3328       return TAB_SetItemT (infoPtr, (INT)wParam, (LPTCITEMW)lParam, uMsg == TCM_SETITEMW);
3329
3330     case TCM_DELETEITEM:
3331       return TAB_DeleteItem (infoPtr, (INT)wParam);
3332
3333     case TCM_DELETEALLITEMS:
3334      return TAB_DeleteAllItems (infoPtr);
3335
3336     case TCM_GETITEMRECT:
3337      return TAB_GetItemRect (infoPtr, (INT)wParam, (LPRECT)lParam);
3338
3339     case TCM_GETCURSEL:
3340       return TAB_GetCurSel (infoPtr);
3341
3342     case TCM_HITTEST:
3343       return TAB_HitTest (infoPtr, (LPTCHITTESTINFO)lParam);
3344
3345     case TCM_SETCURSEL:
3346       return TAB_SetCurSel (infoPtr, (INT)wParam);
3347
3348     case TCM_INSERTITEMA:
3349     case TCM_INSERTITEMW:
3350       return TAB_InsertItemT (infoPtr, (INT)wParam, (TCITEMW*)lParam, uMsg == TCM_INSERTITEMW);
3351
3352     case TCM_SETITEMEXTRA:
3353       return TAB_SetItemExtra (infoPtr, (INT)wParam);
3354
3355     case TCM_ADJUSTRECT:
3356       return TAB_AdjustRect (infoPtr, (BOOL)wParam, (LPRECT)lParam);
3357
3358     case TCM_SETITEMSIZE:
3359       return TAB_SetItemSize (infoPtr, (INT)LOWORD(lParam), (INT)HIWORD(lParam));
3360
3361     case TCM_REMOVEIMAGE:
3362       return TAB_RemoveImage (infoPtr, (INT)wParam);
3363
3364     case TCM_SETPADDING:
3365       return TAB_SetPadding (infoPtr, lParam);
3366
3367     case TCM_GETROWCOUNT:
3368       return TAB_GetRowCount(infoPtr);
3369
3370     case TCM_GETUNICODEFORMAT:
3371       return TAB_GetUnicodeFormat (infoPtr);
3372
3373     case TCM_SETUNICODEFORMAT:
3374       return TAB_SetUnicodeFormat (infoPtr, (BOOL)wParam);
3375
3376     case TCM_HIGHLIGHTITEM:
3377       return TAB_HighlightItem (infoPtr, (INT)wParam, (BOOL)LOWORD(lParam));
3378
3379     case TCM_GETTOOLTIPS:
3380       return TAB_GetToolTips (infoPtr);
3381
3382     case TCM_SETTOOLTIPS:
3383       return TAB_SetToolTips (infoPtr, (HWND)wParam);
3384
3385     case TCM_GETCURFOCUS:
3386       return TAB_GetCurFocus (infoPtr);
3387
3388     case TCM_SETCURFOCUS:
3389       return TAB_SetCurFocus (infoPtr, (INT)wParam);
3390
3391     case TCM_SETMINTABWIDTH:
3392       return TAB_SetMinTabWidth(infoPtr, (INT)lParam);
3393
3394     case TCM_DESELECTALL:
3395       return TAB_DeselectAll (infoPtr, (BOOL)wParam);
3396
3397     case TCM_GETEXTENDEDSTYLE:
3398       return TAB_GetExtendedStyle (infoPtr);
3399
3400     case TCM_SETEXTENDEDSTYLE:
3401       return TAB_SetExtendedStyle (infoPtr, wParam, lParam);
3402
3403     case WM_GETFONT:
3404       return TAB_GetFont (infoPtr);
3405
3406     case WM_SETFONT:
3407       return TAB_SetFont (infoPtr, (HFONT)wParam);
3408
3409     case WM_CREATE:
3410       return TAB_Create (hwnd, lParam);
3411
3412     case WM_NCDESTROY:
3413       return TAB_Destroy (infoPtr);
3414
3415     case WM_GETDLGCODE:
3416       return DLGC_WANTARROWS | DLGC_WANTCHARS;
3417
3418     case WM_LBUTTONDOWN:
3419       return TAB_LButtonDown (infoPtr, wParam, lParam);
3420
3421     case WM_LBUTTONUP:
3422       return TAB_LButtonUp (infoPtr);
3423
3424     case WM_NOTIFY:
3425       return SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, wParam, lParam);
3426
3427     case WM_RBUTTONDOWN:
3428       return TAB_RButtonDown (infoPtr);
3429
3430     case WM_MOUSEMOVE:
3431       return TAB_MouseMove (infoPtr, wParam, lParam);
3432
3433     case WM_PRINTCLIENT:
3434     case WM_PAINT:
3435       return TAB_Paint (infoPtr, (HDC)wParam);
3436
3437     case WM_SIZE:
3438       return TAB_Size (infoPtr);
3439
3440     case WM_SETREDRAW:
3441       return TAB_SetRedraw (infoPtr, (BOOL)wParam);
3442
3443     case WM_HSCROLL:
3444       return TAB_OnHScroll(infoPtr, (int)LOWORD(wParam), (int)HIWORD(wParam));
3445
3446     case WM_STYLECHANGED:
3447       return TAB_StyleChanged(infoPtr, wParam, (LPSTYLESTRUCT)lParam);
3448
3449     case WM_SYSCOLORCHANGE:
3450       COMCTL32_RefreshSysColors();
3451       return 0;
3452
3453     case WM_THEMECHANGED:
3454       return theme_changed (infoPtr);
3455
3456     case WM_KILLFOCUS:
3457       TAB_KillFocus(infoPtr);
3458     case WM_SETFOCUS:
3459       TAB_FocusChanging(infoPtr);
3460       break;   /* Don't disturb normal focus behavior */
3461
3462     case WM_KEYDOWN:
3463       return TAB_KeyDown(infoPtr, wParam, lParam);
3464
3465     case WM_NCHITTEST:
3466       return TAB_NCHitTest(infoPtr, lParam);
3467
3468     case WM_NCCALCSIZE:
3469       return TAB_NCCalcSize(wParam);
3470
3471     default:
3472       if (uMsg >= WM_USER && uMsg < WM_APP && !COMCTL32_IsReflectedMessage(uMsg))
3473         WARN("unknown msg %04x wp=%08lx lp=%08lx\n",
3474              uMsg, wParam, lParam);
3475       break;
3476     }
3477     return DefWindowProcW(hwnd, uMsg, wParam, lParam);
3478 }
3479
3480
3481 void
3482 TAB_Register (void)
3483 {
3484   WNDCLASSW wndClass;
3485
3486   ZeroMemory (&wndClass, sizeof(WNDCLASSW));
3487   wndClass.style         = CS_GLOBALCLASS | CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
3488   wndClass.lpfnWndProc   = TAB_WindowProc;
3489   wndClass.cbClsExtra    = 0;
3490   wndClass.cbWndExtra    = sizeof(TAB_INFO *);
3491   wndClass.hCursor       = LoadCursorW (0, (LPWSTR)IDC_ARROW);
3492   wndClass.hbrBackground = (HBRUSH)(COLOR_BTNFACE+1);
3493   wndClass.lpszClassName = WC_TABCONTROLW;
3494
3495   RegisterClassW (&wndClass);
3496 }
3497
3498
3499 void
3500 TAB_Unregister (void)
3501 {
3502     UnregisterClassW (WC_TABCONTROLW, NULL);
3503 }