mmdevapi/tests: Add tests for volume control interfaces.
[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   if (iSelected < 0)
2444     return;
2445
2446   /* set the items row to the bottommost row or topmost row depending on
2447    * style */
2448   if ((infoPtr->uNumRows > 1) && !(infoPtr->dwStyle & TCS_BUTTONS))
2449   {
2450       TAB_ITEM *selected = TAB_GetItem(infoPtr, iSelected);
2451       INT newselected;
2452       INT iTargetRow;
2453
2454       if(infoPtr->dwStyle & TCS_VERTICAL)
2455         newselected = selected->rect.left;
2456       else
2457         newselected = selected->rect.top;
2458
2459       /* the target row is always (number of rows - 1)
2460          as row 0 is furthest from the clientRect */
2461       iTargetRow = infoPtr->uNumRows - 1;
2462
2463       if (newselected != iTargetRow)
2464       {
2465          UINT i;
2466          if(infoPtr->dwStyle & TCS_VERTICAL)
2467          {
2468            for (i=0; i < infoPtr->uNumItem; i++)
2469            {
2470              /* move everything in the row of the selected item to the iTargetRow */
2471              TAB_ITEM *item = TAB_GetItem(infoPtr, i);
2472
2473              if (item->rect.left == newselected )
2474                  item->rect.left = iTargetRow;
2475              else
2476              {
2477                if (item->rect.left > newselected)
2478                  item->rect.left-=1;
2479              }
2480            }
2481          }
2482          else
2483          {
2484            for (i=0; i < infoPtr->uNumItem; i++)
2485            {
2486              TAB_ITEM *item = TAB_GetItem(infoPtr, i);
2487
2488              if (item->rect.top == newselected )
2489                  item->rect.top = iTargetRow;
2490              else
2491              {
2492                if (item->rect.top > newselected)
2493                  item->rect.top-=1;
2494              }
2495           }
2496         }
2497         TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
2498       }
2499   }
2500
2501   /*
2502    * Do the trivial cases first.
2503    */
2504   if ( (!infoPtr->needsScrolling) ||
2505        (infoPtr->hwndUpDown==0) || (infoPtr->dwStyle & TCS_VERTICAL))
2506     return;
2507
2508   if (infoPtr->leftmostVisible >= iSelected)
2509   {
2510     infoPtr->leftmostVisible = iSelected;
2511   }
2512   else
2513   {
2514      TAB_ITEM *selected = TAB_GetItem(infoPtr, iSelected);
2515      RECT r;
2516      INT width;
2517      UINT i;
2518
2519      /* Calculate the part of the client area that is visible */
2520      GetClientRect(infoPtr->hwnd, &r);
2521      width = r.right;
2522
2523      GetClientRect(infoPtr->hwndUpDown, &r);
2524      width -= r.right;
2525
2526      if ((selected->rect.right -
2527           selected->rect.left) >= width )
2528      {
2529         /* Special case: width of selected item is greater than visible
2530          * part of control.
2531          */
2532         infoPtr->leftmostVisible = iSelected;
2533      }
2534      else
2535      {
2536         for (i = infoPtr->leftmostVisible; i < infoPtr->uNumItem; i++)
2537         {
2538            if ((selected->rect.right - TAB_GetItem(infoPtr, i)->rect.left) < width)
2539               break;
2540         }
2541         infoPtr->leftmostVisible = i;
2542      }
2543   }
2544
2545   if (infoPtr->leftmostVisible != iOrigLeftmostVisible)
2546     TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
2547
2548   SendMessageW(infoPtr->hwndUpDown, UDM_SETPOS, 0,
2549                MAKELONG(infoPtr->leftmostVisible, 0));
2550 }
2551
2552 /******************************************************************************
2553  * TAB_InvalidateTabArea
2554  *
2555  * This method will invalidate the portion of the control that contains the
2556  * tabs. It is called when the state of the control changes and needs
2557  * to be redisplayed
2558  */
2559 static void TAB_InvalidateTabArea(const TAB_INFO *infoPtr)
2560 {
2561   RECT clientRect, rInvalidate, rAdjClient;
2562   INT lastRow = infoPtr->uNumRows - 1;
2563   RECT rect;
2564
2565   if (lastRow < 0) return;
2566
2567   GetClientRect(infoPtr->hwnd, &clientRect);
2568   rInvalidate = clientRect;
2569   rAdjClient = clientRect;
2570
2571   TAB_AdjustRect(infoPtr, 0, &rAdjClient);
2572
2573   TAB_InternalGetItemRect(infoPtr, infoPtr->uNumItem-1 , &rect, NULL);
2574   if ((infoPtr->dwStyle & TCS_BOTTOM) && (infoPtr->dwStyle & TCS_VERTICAL))
2575   {
2576     rInvalidate.left = rAdjClient.right;
2577     if (infoPtr->uNumRows == 1)
2578       rInvalidate.bottom = clientRect.top + rect.bottom + 2 * SELECTED_TAB_OFFSET;
2579   }
2580   else if(infoPtr->dwStyle & TCS_VERTICAL)
2581   {
2582     rInvalidate.right = rAdjClient.left;
2583     if (infoPtr->uNumRows == 1)
2584       rInvalidate.bottom = clientRect.top + rect.bottom + 2 * SELECTED_TAB_OFFSET;
2585   }
2586   else if (infoPtr->dwStyle & TCS_BOTTOM)
2587   {
2588     rInvalidate.top = rAdjClient.bottom;
2589     if (infoPtr->uNumRows == 1)
2590       rInvalidate.right = clientRect.left + rect.right + 2 * SELECTED_TAB_OFFSET;
2591   }
2592   else 
2593   {
2594     rInvalidate.bottom = rAdjClient.top;
2595     if (infoPtr->uNumRows == 1)
2596       rInvalidate.right = clientRect.left + rect.right + 2 * SELECTED_TAB_OFFSET;
2597   }
2598   
2599   /* Punch out the updown control */
2600   if (infoPtr->needsScrolling && (rInvalidate.right > 0)) {
2601     RECT r;
2602     GetClientRect(infoPtr->hwndUpDown, &r);
2603     if (rInvalidate.right > clientRect.right - r.left)
2604       rInvalidate.right = rInvalidate.right - (r.right - r.left);
2605     else
2606       rInvalidate.right = clientRect.right - r.left;
2607   }
2608
2609   TRACE("invalidate (%s)\n", wine_dbgstr_rect(&rInvalidate));
2610
2611   InvalidateRect(infoPtr->hwnd, &rInvalidate, TRUE);
2612 }
2613
2614 static inline LRESULT TAB_Paint (TAB_INFO *infoPtr, HDC hdcPaint)
2615 {
2616   HDC hdc;
2617   PAINTSTRUCT ps;
2618
2619   if (hdcPaint)
2620     hdc = hdcPaint;
2621   else
2622   {
2623     hdc = BeginPaint (infoPtr->hwnd, &ps);
2624     TRACE("erase %d, rect=(%s)\n", ps.fErase, wine_dbgstr_rect(&ps.rcPaint));
2625   }
2626
2627   TAB_Refresh (infoPtr, hdc);
2628
2629   if (!hdcPaint)
2630     EndPaint (infoPtr->hwnd, &ps);
2631
2632   return 0;
2633 }
2634
2635 static LRESULT
2636 TAB_InsertItemT (TAB_INFO *infoPtr, INT iItem, const TCITEMW *pti, BOOL bUnicode)
2637 {
2638   TAB_ITEM *item;
2639   RECT rect;
2640
2641   GetClientRect (infoPtr->hwnd, &rect);
2642   TRACE("Rect: %p %s\n", infoPtr->hwnd, wine_dbgstr_rect(&rect));
2643
2644   if (iItem < 0) return -1;
2645   if (iItem > infoPtr->uNumItem)
2646     iItem = infoPtr->uNumItem;
2647
2648   TAB_DumpItemExternalT(pti, iItem, bUnicode);
2649
2650   if (!(item = Alloc(TAB_ITEM_SIZE(infoPtr)))) return FALSE;
2651   if (DPA_InsertPtr(infoPtr->items, iItem, item) == -1)
2652   {
2653       Free(item);
2654       return FALSE;
2655   }
2656
2657   if (infoPtr->uNumItem == 0)
2658       infoPtr->iSelected = 0;
2659   else if (iItem <= infoPtr->iSelected)
2660       infoPtr->iSelected++;
2661
2662   infoPtr->uNumItem++;
2663
2664   item->pszText = NULL;
2665   if (pti->mask & TCIF_TEXT)
2666   {
2667     if (bUnicode)
2668       Str_SetPtrW (&item->pszText, pti->pszText);
2669     else
2670       Str_SetPtrAtoW (&item->pszText, (LPSTR)pti->pszText);
2671   }
2672
2673   if (pti->mask & TCIF_IMAGE)
2674     item->iImage = pti->iImage;
2675   else
2676     item->iImage = -1;
2677
2678   if (pti->mask & TCIF_PARAM)
2679     memcpy(item->extra, &pti->lParam, EXTRA_ITEM_SIZE(infoPtr));
2680   else
2681     memset(item->extra, 0, EXTRA_ITEM_SIZE(infoPtr));
2682
2683   TAB_SetItemBounds(infoPtr);
2684   if (infoPtr->uNumItem > 1)
2685     TAB_InvalidateTabArea(infoPtr);
2686   else
2687     InvalidateRect(infoPtr->hwnd, NULL, TRUE);
2688
2689   TRACE("[%p]: added item %d %s\n",
2690         infoPtr->hwnd, iItem, debugstr_w(item->pszText));
2691
2692   /* If we haven't set the current focus yet, set it now. */
2693   if (infoPtr->uFocus == -1)
2694     TAB_SetCurFocus(infoPtr, iItem);
2695
2696   return iItem;
2697 }
2698
2699 static LRESULT
2700 TAB_SetItemSize (TAB_INFO *infoPtr, INT cx, INT cy)
2701 {
2702   LONG lResult = 0;
2703   BOOL bNeedPaint = FALSE;
2704
2705   lResult = MAKELONG(infoPtr->tabWidth, infoPtr->tabHeight);
2706
2707   /* UNDOCUMENTED: If requested Width or Height is 0 this means that program wants to use auto size. */
2708   if (infoPtr->dwStyle & TCS_FIXEDWIDTH && (infoPtr->tabWidth != cx))
2709   {
2710     infoPtr->tabWidth = cx;
2711     bNeedPaint = TRUE;
2712   }
2713
2714   if (infoPtr->tabHeight != cy)
2715   {
2716     if ((infoPtr->fHeightSet = (cy != 0)))
2717       infoPtr->tabHeight = cy;
2718
2719     bNeedPaint = TRUE;
2720   }
2721   TRACE("was h=%d,w=%d, now h=%d,w=%d\n",
2722        HIWORD(lResult), LOWORD(lResult),
2723        infoPtr->tabHeight, infoPtr->tabWidth);
2724
2725   if (bNeedPaint)
2726   {
2727     TAB_SetItemBounds(infoPtr);
2728     RedrawWindow(infoPtr->hwnd, NULL, NULL, RDW_ERASE | RDW_INVALIDATE | RDW_UPDATENOW);
2729   }
2730
2731   return lResult;
2732 }
2733
2734 static inline LRESULT TAB_SetMinTabWidth (TAB_INFO *infoPtr, INT cx)
2735 {
2736   INT oldcx = 0;
2737
2738   TRACE("(%p,%d)\n", infoPtr, cx);
2739
2740   if (infoPtr->tabMinWidth < 0)
2741     oldcx = DEFAULT_MIN_TAB_WIDTH;
2742   else
2743     oldcx = infoPtr->tabMinWidth;
2744   infoPtr->tabMinWidth = cx;
2745   TAB_SetItemBounds(infoPtr);
2746   return oldcx;
2747 }
2748
2749 static inline LRESULT 
2750 TAB_HighlightItem (TAB_INFO *infoPtr, INT iItem, BOOL fHighlight)
2751 {
2752   LPDWORD lpState;
2753   DWORD oldState;
2754   RECT r;
2755
2756   TRACE("(%p,%d,%s)\n", infoPtr, iItem, fHighlight ? "true" : "false");
2757
2758   if (iItem < 0 || iItem >= infoPtr->uNumItem)
2759     return FALSE;
2760
2761   lpState = &TAB_GetItem(infoPtr, iItem)->dwState;
2762   oldState = *lpState;
2763
2764   if (fHighlight)
2765     *lpState |= TCIS_HIGHLIGHTED;
2766   else
2767     *lpState &= ~TCIS_HIGHLIGHTED;
2768
2769   if ((oldState != *lpState) && TAB_InternalGetItemRect (infoPtr, iItem, &r, NULL))
2770     InvalidateRect (infoPtr->hwnd, &r, TRUE);
2771
2772   return TRUE;
2773 }
2774
2775 static LRESULT
2776 TAB_SetItemT (TAB_INFO *infoPtr, INT iItem, LPTCITEMW tabItem, BOOL bUnicode)
2777 {
2778   TAB_ITEM *wineItem;
2779
2780   TRACE("(%p,%d,%p,%s)\n", infoPtr, iItem, tabItem, bUnicode ? "true" : "false");
2781
2782   if (iItem < 0 || iItem >= infoPtr->uNumItem)
2783     return FALSE;
2784
2785   TAB_DumpItemExternalT(tabItem, iItem, bUnicode);
2786
2787   wineItem = TAB_GetItem(infoPtr, iItem);
2788
2789   if (tabItem->mask & TCIF_IMAGE)
2790     wineItem->iImage = tabItem->iImage;
2791
2792   if (tabItem->mask & TCIF_PARAM)
2793     memcpy(wineItem->extra, &tabItem->lParam, infoPtr->cbInfo);
2794
2795   if (tabItem->mask & TCIF_RTLREADING)
2796     FIXME("TCIF_RTLREADING\n");
2797
2798   if (tabItem->mask & TCIF_STATE)
2799     wineItem->dwState = (wineItem->dwState & ~tabItem->dwStateMask) |
2800                         ( tabItem->dwState &  tabItem->dwStateMask);
2801
2802   if (tabItem->mask & TCIF_TEXT)
2803   {
2804     Free(wineItem->pszText);
2805     wineItem->pszText = NULL;
2806     if (bUnicode)
2807       Str_SetPtrW(&wineItem->pszText, tabItem->pszText);
2808     else
2809       Str_SetPtrAtoW(&wineItem->pszText, (LPSTR)tabItem->pszText);
2810   }
2811
2812   /* Update and repaint tabs */
2813   TAB_SetItemBounds(infoPtr);
2814   TAB_InvalidateTabArea(infoPtr);
2815
2816   return TRUE;
2817 }
2818
2819 static inline LRESULT TAB_GetItemCount (const TAB_INFO *infoPtr)
2820 {
2821   TRACE("\n");
2822   return infoPtr->uNumItem;
2823 }
2824
2825
2826 static LRESULT
2827 TAB_GetItemT (TAB_INFO *infoPtr, INT iItem, LPTCITEMW tabItem, BOOL bUnicode)
2828 {
2829   TAB_ITEM *wineItem;
2830
2831   TRACE("(%p,%d,%p,%s)\n", infoPtr, iItem, tabItem, bUnicode ? "true" : "false");
2832
2833   if (!tabItem) return FALSE;
2834
2835   if (iItem < 0 || iItem >= infoPtr->uNumItem)
2836   {
2837     /* init requested fields */
2838     if (tabItem->mask & TCIF_IMAGE) tabItem->iImage  = 0;
2839     if (tabItem->mask & TCIF_PARAM) tabItem->lParam  = 0;
2840     if (tabItem->mask & TCIF_STATE) tabItem->dwState = 0;
2841     return FALSE;
2842   }
2843
2844   wineItem = TAB_GetItem(infoPtr, iItem);
2845
2846   if (tabItem->mask & TCIF_IMAGE)
2847     tabItem->iImage = wineItem->iImage;
2848
2849   if (tabItem->mask & TCIF_PARAM)
2850     memcpy(&tabItem->lParam, wineItem->extra, infoPtr->cbInfo);
2851
2852   if (tabItem->mask & TCIF_RTLREADING)
2853     FIXME("TCIF_RTLREADING\n");
2854
2855   if (tabItem->mask & TCIF_STATE)
2856     tabItem->dwState = wineItem->dwState & tabItem->dwStateMask;
2857
2858   if (tabItem->mask & TCIF_TEXT)
2859   {
2860     if (bUnicode)
2861       Str_GetPtrW (wineItem->pszText, tabItem->pszText, tabItem->cchTextMax);
2862     else
2863       Str_GetPtrWtoA (wineItem->pszText, (LPSTR)tabItem->pszText, tabItem->cchTextMax);
2864   }
2865
2866   TAB_DumpItemExternalT(tabItem, iItem, bUnicode);
2867
2868   return TRUE;
2869 }
2870
2871
2872 static LRESULT TAB_DeleteItem (TAB_INFO *infoPtr, INT iItem)
2873 {
2874     TAB_ITEM *item;
2875
2876     TRACE("(%p, %d)\n", infoPtr, iItem);
2877
2878     if (iItem < 0 || iItem >= infoPtr->uNumItem) return FALSE;
2879
2880     item = TAB_GetItem(infoPtr, iItem);
2881     Free(item->pszText);
2882     Free(item);
2883     infoPtr->uNumItem--;
2884     DPA_DeletePtr(infoPtr->items, iItem);
2885
2886     TAB_InvalidateTabArea(infoPtr);
2887
2888     if (infoPtr->uNumItem == 0)
2889     {
2890         if (infoPtr->iHotTracked >= 0)
2891         {
2892             KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
2893             infoPtr->iHotTracked = -1;
2894         }
2895
2896         infoPtr->iSelected = -1;
2897     }
2898     else
2899     {
2900         if (iItem <= infoPtr->iHotTracked)
2901         {
2902             /* When tabs move left/up, the hot track item may change */
2903             FIXME("Recalc hot track\n");
2904         }
2905     }
2906
2907     /* adjust the selected index */
2908     if (iItem == infoPtr->iSelected)
2909         infoPtr->iSelected = -1;
2910     else if (iItem < infoPtr->iSelected)
2911         infoPtr->iSelected--;
2912
2913     /* reposition and repaint tabs */
2914     TAB_SetItemBounds(infoPtr);
2915
2916     return TRUE;
2917 }
2918
2919 static inline LRESULT TAB_DeleteAllItems (TAB_INFO *infoPtr)
2920 {
2921     TRACE("(%p)\n", infoPtr);
2922     while (infoPtr->uNumItem)
2923       TAB_DeleteItem (infoPtr, 0);
2924     return TRUE;
2925 }
2926
2927
2928 static inline LRESULT TAB_GetFont (const TAB_INFO *infoPtr)
2929 {
2930   TRACE("(%p) returning %p\n", infoPtr, infoPtr->hFont);
2931   return (LRESULT)infoPtr->hFont;
2932 }
2933
2934 static inline LRESULT TAB_SetFont (TAB_INFO *infoPtr, HFONT hNewFont)
2935 {
2936   TRACE("(%p,%p)\n", infoPtr, hNewFont);
2937
2938   infoPtr->hFont = hNewFont;
2939
2940   TAB_SetItemBounds(infoPtr);
2941
2942   TAB_InvalidateTabArea(infoPtr);
2943
2944   return 0;
2945 }
2946
2947
2948 static inline LRESULT TAB_GetImageList (const TAB_INFO *infoPtr)
2949 {
2950   TRACE("\n");
2951   return (LRESULT)infoPtr->himl;
2952 }
2953
2954 static inline LRESULT TAB_SetImageList (TAB_INFO *infoPtr, HIMAGELIST himlNew)
2955 {
2956     HIMAGELIST himlPrev = infoPtr->himl;
2957     TRACE("himl=%p\n", himlNew);
2958     infoPtr->himl = himlNew;
2959     TAB_SetItemBounds(infoPtr);
2960     InvalidateRect(infoPtr->hwnd, NULL, TRUE);
2961     return (LRESULT)himlPrev;
2962 }
2963
2964 static inline LRESULT TAB_GetUnicodeFormat (const TAB_INFO *infoPtr)
2965 {
2966     TRACE("(%p)\n", infoPtr);
2967     return infoPtr->bUnicode;
2968 }
2969
2970 static inline LRESULT TAB_SetUnicodeFormat (TAB_INFO *infoPtr, BOOL bUnicode)
2971 {
2972     BOOL bTemp = infoPtr->bUnicode;
2973
2974     TRACE("(%p %d)\n", infoPtr, bUnicode);
2975     infoPtr->bUnicode = bUnicode;
2976
2977     return bTemp;
2978 }
2979
2980 static inline LRESULT TAB_Size (TAB_INFO *infoPtr)
2981 {
2982 /* I'm not really sure what the following code was meant to do.
2983    This is what it is doing:
2984    When WM_SIZE is sent with SIZE_RESTORED, the control
2985    gets positioned in the top left corner.
2986
2987   RECT parent_rect;
2988   HWND parent;
2989   UINT uPosFlags,cx,cy;
2990
2991   uPosFlags=0;
2992   if (!wParam) {
2993     parent = GetParent (hwnd);
2994     GetClientRect(parent, &parent_rect);
2995     cx=LOWORD (lParam);
2996     cy=HIWORD (lParam);
2997     if (GetWindowLongW(hwnd, GWL_STYLE) & CCS_NORESIZE)
2998         uPosFlags |= (SWP_NOSIZE | SWP_NOMOVE);
2999
3000     SetWindowPos (hwnd, 0, parent_rect.left, parent_rect.top,
3001             cx, cy, uPosFlags | SWP_NOZORDER);
3002   } else {
3003     FIXME("WM_SIZE flag %x %lx not handled\n", wParam, lParam);
3004   } */
3005
3006   /* Recompute the size/position of the tabs. */
3007   TAB_SetItemBounds (infoPtr);
3008
3009   /* Force a repaint of the control. */
3010   InvalidateRect(infoPtr->hwnd, NULL, TRUE);
3011
3012   return 0;
3013 }
3014
3015
3016 static LRESULT TAB_Create (HWND hwnd, LPARAM lParam)
3017 {
3018   TAB_INFO *infoPtr;
3019   TEXTMETRICW fontMetrics;
3020   HDC hdc;
3021   HFONT hOldFont;
3022   DWORD dwStyle;
3023
3024   infoPtr = Alloc (sizeof(TAB_INFO));
3025
3026   SetWindowLongPtrW(hwnd, 0, (DWORD_PTR)infoPtr);
3027
3028   infoPtr->hwnd            = hwnd;
3029   infoPtr->hwndNotify      = ((LPCREATESTRUCTW)lParam)->hwndParent;
3030   infoPtr->uNumItem        = 0;
3031   infoPtr->uNumRows        = 0;
3032   infoPtr->uHItemPadding   = 6;
3033   infoPtr->uVItemPadding   = 3;
3034   infoPtr->uHItemPadding_s = 6;
3035   infoPtr->uVItemPadding_s = 3;
3036   infoPtr->hFont           = 0;
3037   infoPtr->items           = DPA_Create(8);
3038   infoPtr->hcurArrow       = LoadCursorW (0, (LPWSTR)IDC_ARROW);
3039   infoPtr->iSelected       = -1;
3040   infoPtr->iHotTracked     = -1;
3041   infoPtr->uFocus          = -1;
3042   infoPtr->hwndToolTip     = 0;
3043   infoPtr->DoRedraw        = TRUE;
3044   infoPtr->needsScrolling  = FALSE;
3045   infoPtr->hwndUpDown      = 0;
3046   infoPtr->leftmostVisible = 0;
3047   infoPtr->fHeightSet      = FALSE;
3048   infoPtr->bUnicode        = IsWindowUnicode (hwnd);
3049   infoPtr->cbInfo          = sizeof(LPARAM);
3050
3051   TRACE("Created tab control, hwnd [%p]\n", hwnd);
3052
3053   /* The tab control always has the WS_CLIPSIBLINGS style. Even
3054      if you don't specify it in CreateWindow. This is necessary in
3055      order for paint to work correctly. This follows windows behaviour. */
3056   dwStyle = GetWindowLongW(hwnd, GWL_STYLE);
3057   SetWindowLongW(hwnd, GWL_STYLE, dwStyle|WS_CLIPSIBLINGS);
3058
3059   infoPtr->dwStyle = dwStyle | WS_CLIPSIBLINGS;
3060   infoPtr->exStyle = (dwStyle & TCS_FLATBUTTONS) ? TCS_EX_FLATSEPARATORS : 0;
3061
3062   if (infoPtr->dwStyle & TCS_TOOLTIPS) {
3063     /* Create tooltip control */
3064     infoPtr->hwndToolTip =
3065       CreateWindowExW (0, TOOLTIPS_CLASSW, NULL, WS_POPUP,
3066                        CW_USEDEFAULT, CW_USEDEFAULT,
3067                        CW_USEDEFAULT, CW_USEDEFAULT,
3068                        hwnd, 0, 0, 0);
3069
3070     /* Send NM_TOOLTIPSCREATED notification */
3071     if (infoPtr->hwndToolTip) {
3072       NMTOOLTIPSCREATED nmttc;
3073
3074       nmttc.hdr.hwndFrom = hwnd;
3075       nmttc.hdr.idFrom = GetWindowLongPtrW(hwnd, GWLP_ID);
3076       nmttc.hdr.code = NM_TOOLTIPSCREATED;
3077       nmttc.hwndToolTips = infoPtr->hwndToolTip;
3078
3079       SendMessageW (infoPtr->hwndNotify, WM_NOTIFY,
3080                     GetWindowLongPtrW(hwnd, GWLP_ID), (LPARAM)&nmttc);
3081     }
3082   }
3083
3084   OpenThemeData (infoPtr->hwnd, themeClass);
3085   
3086   /*
3087    * We need to get text information so we need a DC and we need to select
3088    * a font.
3089    */
3090   hdc = GetDC(hwnd);
3091   hOldFont = SelectObject (hdc, GetStockObject (SYSTEM_FONT));
3092
3093   /* Use the system font to determine the initial height of a tab. */
3094   GetTextMetricsW(hdc, &fontMetrics);
3095
3096   /*
3097    * Make sure there is enough space for the letters + growing the
3098    * selected item + extra space for the selected item.
3099    */
3100   infoPtr->tabHeight = fontMetrics.tmHeight + SELECTED_TAB_OFFSET +
3101                        ((infoPtr->dwStyle & TCS_BUTTONS) ? 2 : 1) *
3102                         infoPtr->uVItemPadding;
3103
3104   /* Initialize the width of a tab. */
3105   if (infoPtr->dwStyle & TCS_FIXEDWIDTH)
3106     infoPtr->tabWidth = GetDeviceCaps(hdc, LOGPIXELSX);
3107
3108   infoPtr->tabMinWidth = -1;
3109
3110   TRACE("tabH=%d, tabW=%d\n", infoPtr->tabHeight, infoPtr->tabWidth);
3111
3112   SelectObject (hdc, hOldFont);
3113   ReleaseDC(hwnd, hdc);
3114
3115   return 0;
3116 }
3117
3118 static LRESULT
3119 TAB_Destroy (TAB_INFO *infoPtr)
3120 {
3121   INT iItem;
3122
3123   SetWindowLongPtrW(infoPtr->hwnd, 0, 0);
3124
3125   for (iItem = infoPtr->uNumItem - 1; iItem >= 0; iItem--)
3126   {
3127       TAB_ITEM *tab = TAB_GetItem(infoPtr, iItem);
3128
3129       DPA_DeletePtr(infoPtr->items, iItem);
3130       infoPtr->uNumItem--;
3131
3132       Free(tab->pszText);
3133       Free(tab);
3134   }
3135   DPA_Destroy(infoPtr->items);
3136   infoPtr->items = NULL;
3137
3138   if (infoPtr->hwndToolTip)
3139     DestroyWindow (infoPtr->hwndToolTip);
3140
3141   if (infoPtr->hwndUpDown)
3142     DestroyWindow(infoPtr->hwndUpDown);
3143
3144   if (infoPtr->iHotTracked >= 0)
3145     KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
3146
3147   CloseThemeData (GetWindowTheme (infoPtr->hwnd));
3148
3149   Free (infoPtr);
3150   return 0;
3151 }
3152
3153 /* update theme after a WM_THEMECHANGED message */
3154 static LRESULT theme_changed(const TAB_INFO *infoPtr)
3155 {
3156     HTHEME theme = GetWindowTheme (infoPtr->hwnd);
3157     CloseThemeData (theme);
3158     OpenThemeData (infoPtr->hwnd, themeClass);
3159     return 0;
3160 }
3161
3162 static LRESULT TAB_NCCalcSize(WPARAM wParam)
3163 {
3164   if (!wParam)
3165     return 0;
3166   return WVR_ALIGNTOP;
3167 }
3168
3169 static inline LRESULT
3170 TAB_SetItemExtra (TAB_INFO *infoPtr, INT cbInfo)
3171 {
3172   TRACE("(%p %d)\n", infoPtr, cbInfo);
3173
3174   if (cbInfo < 0 || infoPtr->uNumItem) return FALSE;
3175
3176   infoPtr->cbInfo = cbInfo;
3177   return TRUE;
3178 }
3179
3180 static LRESULT TAB_RemoveImage (TAB_INFO *infoPtr, INT image)
3181 {
3182   TRACE("%p %d\n", infoPtr, image);
3183
3184   if (ImageList_Remove (infoPtr->himl, image))
3185   {
3186     INT i, *idx;
3187     RECT r;
3188
3189     /* shift indices, repaint items if needed */
3190     for (i = 0; i < infoPtr->uNumItem; i++)
3191     {
3192       idx = &TAB_GetItem(infoPtr, i)->iImage;
3193       if (*idx >= image)
3194       {
3195         if (*idx == image)
3196           *idx = -1;
3197         else
3198           (*idx)--;
3199
3200         /* repaint item */
3201         if (TAB_InternalGetItemRect (infoPtr, i, &r, NULL))
3202           InvalidateRect (infoPtr->hwnd, &r, TRUE);
3203       }
3204     }
3205   }
3206
3207   return 0;
3208 }
3209
3210 static LRESULT
3211 TAB_SetExtendedStyle (TAB_INFO *infoPtr, DWORD exMask, DWORD exStyle)
3212 {
3213   DWORD prevstyle = infoPtr->exStyle;
3214
3215   /* zero mask means all styles */
3216   if (exMask == 0) exMask = ~0;
3217
3218   if (exMask & TCS_EX_REGISTERDROP)
3219   {
3220     FIXME("TCS_EX_REGISTERDROP style unimplemented\n");
3221     exMask  &= ~TCS_EX_REGISTERDROP;
3222     exStyle &= ~TCS_EX_REGISTERDROP;
3223   }
3224
3225   if (exMask & TCS_EX_FLATSEPARATORS)
3226   {
3227     if ((prevstyle ^ exStyle) & TCS_EX_FLATSEPARATORS)
3228     {
3229         infoPtr->exStyle ^= TCS_EX_FLATSEPARATORS;
3230         TAB_InvalidateTabArea(infoPtr);
3231     }
3232   }
3233
3234   return prevstyle;
3235 }
3236
3237 static inline LRESULT
3238 TAB_GetExtendedStyle (const TAB_INFO *infoPtr)
3239 {
3240   return infoPtr->exStyle;
3241 }
3242
3243 static LRESULT
3244 TAB_DeselectAll (TAB_INFO *infoPtr, BOOL excludesel)
3245 {
3246   BOOL paint = FALSE;
3247   INT i, selected = infoPtr->iSelected;
3248
3249   TRACE("(%p, %d)\n", infoPtr, excludesel);
3250
3251   if (!(infoPtr->dwStyle & TCS_BUTTONS))
3252     return 0;
3253
3254   for (i = 0; i < infoPtr->uNumItem; i++)
3255   {
3256     if ((TAB_GetItem(infoPtr, i)->dwState & TCIS_BUTTONPRESSED) &&
3257         (selected != i))
3258     {
3259       TAB_GetItem(infoPtr, i)->dwState &= ~TCIS_BUTTONPRESSED;
3260       paint = TRUE;
3261     }
3262   }
3263
3264   if (!excludesel && (selected != -1))
3265   {
3266     TAB_GetItem(infoPtr, selected)->dwState &= ~TCIS_BUTTONPRESSED;
3267     infoPtr->iSelected = -1;
3268     paint = TRUE;
3269   }
3270
3271   if (paint)
3272     TAB_InvalidateTabArea (infoPtr);
3273
3274   return 0;
3275 }
3276
3277 /***
3278  * DESCRIPTION:
3279  * Processes WM_STYLECHANGED messages.
3280  *
3281  * PARAMETER(S):
3282  * [I] infoPtr : valid pointer to the tab data structure
3283  * [I] wStyleType : window style type (normal or extended)
3284  * [I] lpss : window style information
3285  *
3286  * RETURN:
3287  * Zero
3288  */
3289 static INT TAB_StyleChanged(TAB_INFO *infoPtr, WPARAM wStyleType,
3290                             const STYLESTRUCT *lpss)
3291 {
3292     TRACE("(styletype=%lx, styleOld=0x%08x, styleNew=0x%08x)\n",
3293           wStyleType, lpss->styleOld, lpss->styleNew);
3294
3295     if (wStyleType != GWL_STYLE) return 0;
3296
3297     infoPtr->dwStyle = lpss->styleNew;
3298
3299     TAB_SetItemBounds (infoPtr);
3300     InvalidateRect(infoPtr->hwnd, NULL, TRUE);
3301
3302     return 0;
3303 }
3304
3305 static LRESULT WINAPI
3306 TAB_WindowProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
3307 {
3308     TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
3309
3310     TRACE("hwnd=%p msg=%x wParam=%lx lParam=%lx\n", hwnd, uMsg, wParam, lParam);
3311     if (!infoPtr && (uMsg != WM_CREATE))
3312       return DefWindowProcW (hwnd, uMsg, wParam, lParam);
3313
3314     switch (uMsg)
3315     {
3316     case TCM_GETIMAGELIST:
3317       return TAB_GetImageList (infoPtr);
3318
3319     case TCM_SETIMAGELIST:
3320       return TAB_SetImageList (infoPtr, (HIMAGELIST)lParam);
3321
3322     case TCM_GETITEMCOUNT:
3323       return TAB_GetItemCount (infoPtr);
3324
3325     case TCM_GETITEMA:
3326     case TCM_GETITEMW:
3327       return TAB_GetItemT (infoPtr, (INT)wParam, (LPTCITEMW)lParam, uMsg == TCM_GETITEMW);
3328
3329     case TCM_SETITEMA:
3330     case TCM_SETITEMW:
3331       return TAB_SetItemT (infoPtr, (INT)wParam, (LPTCITEMW)lParam, uMsg == TCM_SETITEMW);
3332
3333     case TCM_DELETEITEM:
3334       return TAB_DeleteItem (infoPtr, (INT)wParam);
3335
3336     case TCM_DELETEALLITEMS:
3337      return TAB_DeleteAllItems (infoPtr);
3338
3339     case TCM_GETITEMRECT:
3340      return TAB_GetItemRect (infoPtr, (INT)wParam, (LPRECT)lParam);
3341
3342     case TCM_GETCURSEL:
3343       return TAB_GetCurSel (infoPtr);
3344
3345     case TCM_HITTEST:
3346       return TAB_HitTest (infoPtr, (LPTCHITTESTINFO)lParam);
3347
3348     case TCM_SETCURSEL:
3349       return TAB_SetCurSel (infoPtr, (INT)wParam);
3350
3351     case TCM_INSERTITEMA:
3352     case TCM_INSERTITEMW:
3353       return TAB_InsertItemT (infoPtr, (INT)wParam, (TCITEMW*)lParam, uMsg == TCM_INSERTITEMW);
3354
3355     case TCM_SETITEMEXTRA:
3356       return TAB_SetItemExtra (infoPtr, (INT)wParam);
3357
3358     case TCM_ADJUSTRECT:
3359       return TAB_AdjustRect (infoPtr, (BOOL)wParam, (LPRECT)lParam);
3360
3361     case TCM_SETITEMSIZE:
3362       return TAB_SetItemSize (infoPtr, (INT)LOWORD(lParam), (INT)HIWORD(lParam));
3363
3364     case TCM_REMOVEIMAGE:
3365       return TAB_RemoveImage (infoPtr, (INT)wParam);
3366
3367     case TCM_SETPADDING:
3368       return TAB_SetPadding (infoPtr, lParam);
3369
3370     case TCM_GETROWCOUNT:
3371       return TAB_GetRowCount(infoPtr);
3372
3373     case TCM_GETUNICODEFORMAT:
3374       return TAB_GetUnicodeFormat (infoPtr);
3375
3376     case TCM_SETUNICODEFORMAT:
3377       return TAB_SetUnicodeFormat (infoPtr, (BOOL)wParam);
3378
3379     case TCM_HIGHLIGHTITEM:
3380       return TAB_HighlightItem (infoPtr, (INT)wParam, (BOOL)LOWORD(lParam));
3381
3382     case TCM_GETTOOLTIPS:
3383       return TAB_GetToolTips (infoPtr);
3384
3385     case TCM_SETTOOLTIPS:
3386       return TAB_SetToolTips (infoPtr, (HWND)wParam);
3387
3388     case TCM_GETCURFOCUS:
3389       return TAB_GetCurFocus (infoPtr);
3390
3391     case TCM_SETCURFOCUS:
3392       return TAB_SetCurFocus (infoPtr, (INT)wParam);
3393
3394     case TCM_SETMINTABWIDTH:
3395       return TAB_SetMinTabWidth(infoPtr, (INT)lParam);
3396
3397     case TCM_DESELECTALL:
3398       return TAB_DeselectAll (infoPtr, (BOOL)wParam);
3399
3400     case TCM_GETEXTENDEDSTYLE:
3401       return TAB_GetExtendedStyle (infoPtr);
3402
3403     case TCM_SETEXTENDEDSTYLE:
3404       return TAB_SetExtendedStyle (infoPtr, wParam, lParam);
3405
3406     case WM_GETFONT:
3407       return TAB_GetFont (infoPtr);
3408
3409     case WM_SETFONT:
3410       return TAB_SetFont (infoPtr, (HFONT)wParam);
3411
3412     case WM_CREATE:
3413       return TAB_Create (hwnd, lParam);
3414
3415     case WM_NCDESTROY:
3416       return TAB_Destroy (infoPtr);
3417
3418     case WM_GETDLGCODE:
3419       return DLGC_WANTARROWS | DLGC_WANTCHARS;
3420
3421     case WM_LBUTTONDOWN:
3422       return TAB_LButtonDown (infoPtr, wParam, lParam);
3423
3424     case WM_LBUTTONUP:
3425       return TAB_LButtonUp (infoPtr);
3426
3427     case WM_NOTIFY:
3428       return SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, wParam, lParam);
3429
3430     case WM_RBUTTONDOWN:
3431       return TAB_RButtonDown (infoPtr);
3432
3433     case WM_MOUSEMOVE:
3434       return TAB_MouseMove (infoPtr, wParam, lParam);
3435
3436     case WM_PRINTCLIENT:
3437     case WM_PAINT:
3438       return TAB_Paint (infoPtr, (HDC)wParam);
3439
3440     case WM_SIZE:
3441       return TAB_Size (infoPtr);
3442
3443     case WM_SETREDRAW:
3444       return TAB_SetRedraw (infoPtr, (BOOL)wParam);
3445
3446     case WM_HSCROLL:
3447       return TAB_OnHScroll(infoPtr, (int)LOWORD(wParam), (int)HIWORD(wParam));
3448
3449     case WM_STYLECHANGED:
3450       return TAB_StyleChanged(infoPtr, wParam, (LPSTYLESTRUCT)lParam);
3451
3452     case WM_SYSCOLORCHANGE:
3453       COMCTL32_RefreshSysColors();
3454       return 0;
3455
3456     case WM_THEMECHANGED:
3457       return theme_changed (infoPtr);
3458
3459     case WM_KILLFOCUS:
3460       TAB_KillFocus(infoPtr);
3461     case WM_SETFOCUS:
3462       TAB_FocusChanging(infoPtr);
3463       break;   /* Don't disturb normal focus behavior */
3464
3465     case WM_KEYDOWN:
3466       return TAB_KeyDown(infoPtr, wParam, lParam);
3467
3468     case WM_NCHITTEST:
3469       return TAB_NCHitTest(infoPtr, lParam);
3470
3471     case WM_NCCALCSIZE:
3472       return TAB_NCCalcSize(wParam);
3473
3474     default:
3475       if (uMsg >= WM_USER && uMsg < WM_APP && !COMCTL32_IsReflectedMessage(uMsg))
3476         WARN("unknown msg %04x wp=%08lx lp=%08lx\n",
3477              uMsg, wParam, lParam);
3478       break;
3479     }
3480     return DefWindowProcW(hwnd, uMsg, wParam, lParam);
3481 }
3482
3483
3484 void
3485 TAB_Register (void)
3486 {
3487   WNDCLASSW wndClass;
3488
3489   ZeroMemory (&wndClass, sizeof(WNDCLASSW));
3490   wndClass.style         = CS_GLOBALCLASS | CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
3491   wndClass.lpfnWndProc   = TAB_WindowProc;
3492   wndClass.cbClsExtra    = 0;
3493   wndClass.cbWndExtra    = sizeof(TAB_INFO *);
3494   wndClass.hCursor       = LoadCursorW (0, (LPWSTR)IDC_ARROW);
3495   wndClass.hbrBackground = (HBRUSH)(COLOR_BTNFACE+1);
3496   wndClass.lpszClassName = WC_TABCONTROLW;
3497
3498   RegisterClassW (&wndClass);
3499 }
3500
3501
3502 void
3503 TAB_Unregister (void)
3504 {
3505     UnregisterClassW (WC_TABCONTROLW, NULL);
3506 }