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