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