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