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