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