mscoree/tests: Added some simple tests for GetCORVersion.
[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, cx;
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, &cx, &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     INT cy;
1199
1200     ImageList_GetIconSize(infoPtr->himl, &icon_width, &cy);
1201
1202     if (infoPtr->dwStyle & TCS_FIXEDWIDTH)
1203       icon_width += 4;
1204     else
1205       /* Add padding if icon is present */
1206       icon_width += infoPtr->uHItemPadding;
1207   }
1208
1209   for (curItem = 0; curItem < infoPtr->uNumItem; curItem++)
1210   {
1211     TAB_ITEM *curr = TAB_GetItem(infoPtr, curItem);
1212         
1213     /* Set the leftmost position of the tab. */
1214     curr->rect.left = curItemLeftPos;
1215
1216     if (infoPtr->dwStyle & TCS_FIXEDWIDTH)
1217     {
1218       curr->rect.right = curr->rect.left +
1219         max(infoPtr->tabWidth, icon_width);
1220     }
1221     else if (!curr->pszText)
1222     {
1223       /* If no text use minimum tab width including padding. */
1224       if (infoPtr->tabMinWidth < 0)
1225         curr->rect.right = curr->rect.left + GET_DEFAULT_MIN_TAB_WIDTH(infoPtr);
1226       else
1227       {
1228         curr->rect.right = curr->rect.left + infoPtr->tabMinWidth;
1229
1230         /* Add extra padding if icon is present */
1231         if (infoPtr->himl && infoPtr->tabMinWidth > 0 && infoPtr->tabMinWidth < DEFAULT_MIN_TAB_WIDTH
1232             && infoPtr->uHItemPadding > 1)
1233           curr->rect.right += EXTRA_ICON_PADDING * (infoPtr->uHItemPadding-1);
1234       }
1235     }
1236     else
1237     {
1238       int tabwidth;
1239       SIZE size;
1240       /* Calculate how wide the tab is depending on the text it contains */
1241       GetTextExtentPoint32W(hdc, curr->pszText,
1242                             lstrlenW(curr->pszText), &size);
1243
1244       tabwidth = size.cx + icon_width + 2 * infoPtr->uHItemPadding;
1245
1246       if (infoPtr->tabMinWidth < 0)
1247         tabwidth = max(tabwidth, GET_DEFAULT_MIN_TAB_WIDTH(infoPtr));
1248       else
1249         tabwidth = max(tabwidth, infoPtr->tabMinWidth);
1250
1251       curr->rect.right = curr->rect.left + tabwidth;
1252       TRACE("for <%s>, l,r=%d,%d\n",
1253           debugstr_w(curr->pszText), curr->rect.left, curr->rect.right);
1254     }
1255
1256     /*
1257      * Check if this is a multiline tab control and if so
1258      * check to see if we should wrap the tabs
1259      *
1260      * Wrap all these tabs. We will arrange them evenly later.
1261      *
1262      */
1263
1264     if (((infoPtr->dwStyle & TCS_MULTILINE) || (infoPtr->dwStyle & TCS_VERTICAL)) &&
1265         (curr->rect.right > 
1266         (clientRect.right - CONTROL_BORDER_SIZEX - DISPLAY_AREA_PADDINGX)))
1267     {
1268         curr->rect.right -= curr->rect.left;
1269
1270         curr->rect.left = 0;
1271         curItemRowCount++;
1272         TRACE("wrapping <%s>, l,r=%d,%d\n", debugstr_w(curr->pszText),
1273             curr->rect.left, curr->rect.right);
1274     }
1275
1276     curr->rect.bottom = 0;
1277     curr->rect.top = curItemRowCount - 1;
1278
1279     TRACE("Rect: %s\n", wine_dbgstr_rect(&curr->rect));
1280
1281     /*
1282      * The leftmost position of the next item is the rightmost position
1283      * of this one.
1284      */
1285     if (infoPtr->dwStyle & TCS_BUTTONS)
1286     {
1287       curItemLeftPos = curr->rect.right + BUTTON_SPACINGX;
1288       if (infoPtr->dwStyle & TCS_FLATBUTTONS)
1289         curItemLeftPos += FLAT_BTN_SPACINGX;
1290     }
1291     else
1292       curItemLeftPos = curr->rect.right;
1293   }
1294
1295   if (!((infoPtr->dwStyle & TCS_MULTILINE) || (infoPtr->dwStyle & TCS_VERTICAL)))
1296   {
1297     /*
1298      * Check if we need a scrolling control.
1299      */
1300     infoPtr->needsScrolling = (curItemLeftPos + (2 * SELECTED_TAB_OFFSET) >
1301                                clientRect.right);
1302
1303     /* Don't need scrolling, then update infoPtr->leftmostVisible */
1304     if(!infoPtr->needsScrolling)
1305       infoPtr->leftmostVisible = 0;
1306   }
1307   else
1308   {
1309     /*
1310      * No scrolling in Multiline or Vertical styles.
1311      */
1312     infoPtr->needsScrolling = FALSE;
1313     infoPtr->leftmostVisible = 0;
1314   }
1315   TAB_SetupScrolling(infoPtr, &clientRect);
1316
1317   /* Set the number of rows */
1318   infoPtr->uNumRows = curItemRowCount;
1319
1320   /* Arrange all tabs evenly if style says so */
1321    if (!(infoPtr->dwStyle & TCS_RAGGEDRIGHT) &&
1322        ((infoPtr->dwStyle & TCS_MULTILINE) || (infoPtr->dwStyle & TCS_VERTICAL)) &&
1323        (infoPtr->uNumItem > 0) &&
1324        (infoPtr->uNumRows > 1))
1325    {
1326       INT tabPerRow,remTab,iRow;
1327       UINT iItm;
1328       INT iCount=0;
1329
1330       /*
1331        * Ok windows tries to even out the rows. place the same
1332        * number of tabs in each row. So lets give that a shot
1333        */
1334
1335       tabPerRow = infoPtr->uNumItem / (infoPtr->uNumRows);
1336       remTab = infoPtr->uNumItem % (infoPtr->uNumRows);
1337
1338       for (iItm=0,iRow=0,iCount=0,curItemLeftPos=0;
1339            iItm<infoPtr->uNumItem;
1340            iItm++,iCount++)
1341       {
1342           /* normalize the current rect */
1343           TAB_ITEM *curr = TAB_GetItem(infoPtr, iItm);
1344  
1345           /* shift the item to the left side of the clientRect */
1346           curr->rect.right -= curr->rect.left;
1347           curr->rect.left = 0;
1348
1349           TRACE("r=%d, cl=%d, cl.r=%d, iCount=%d, iRow=%d, uNumRows=%d, remTab=%d, tabPerRow=%d\n",
1350               curr->rect.right, curItemLeftPos, clientRect.right,
1351               iCount, iRow, infoPtr->uNumRows, remTab, tabPerRow);
1352
1353           /* if we have reached the maximum number of tabs on this row */
1354           /* move to the next row, reset our current item left position and */
1355           /* the count of items on this row */
1356
1357           if (infoPtr->dwStyle & TCS_VERTICAL) {
1358               /* Vert: Add the remaining tabs in the *last* remainder rows */
1359               if (iCount >= ((iRow>=(INT)infoPtr->uNumRows - remTab)?tabPerRow + 1:tabPerRow)) {
1360                   iRow++;
1361                   curItemLeftPos = 0;
1362                   iCount = 0;
1363               }
1364           } else {
1365               /* Horz: Add the remaining tabs in the *first* remainder rows */
1366               if (iCount >= ((iRow<remTab)?tabPerRow + 1:tabPerRow)) {
1367                   iRow++;
1368                   curItemLeftPos = 0;
1369                   iCount = 0;
1370               }
1371           }
1372
1373           /* shift the item to the right to place it as the next item in this row */
1374           curr->rect.left += curItemLeftPos;
1375           curr->rect.right += curItemLeftPos;
1376           curr->rect.top = iRow;
1377           if (infoPtr->dwStyle & TCS_BUTTONS)
1378           {
1379             curItemLeftPos = curr->rect.right + 1;
1380             if (infoPtr->dwStyle & TCS_FLATBUTTONS)
1381               curItemLeftPos += FLAT_BTN_SPACINGX;
1382           }
1383           else
1384             curItemLeftPos = curr->rect.right;
1385
1386           TRACE("arranging <%s>, l,r=%d,%d, row=%d\n",
1387               debugstr_w(curr->pszText), curr->rect.left,
1388               curr->rect.right, curr->rect.top);
1389       }
1390
1391       /*
1392        * Justify the rows
1393        */
1394       {
1395         INT widthDiff, iIndexStart=0, iIndexEnd=0;
1396         INT remainder;
1397         INT iCount=0;
1398
1399         while(iIndexStart < infoPtr->uNumItem)
1400         {
1401           TAB_ITEM *start = TAB_GetItem(infoPtr, iIndexStart);
1402
1403           /*
1404            * find the index of the row
1405            */
1406           /* find the first item on the next row */
1407           for (iIndexEnd=iIndexStart;
1408               (iIndexEnd < infoPtr->uNumItem) &&
1409               (TAB_GetItem(infoPtr, iIndexEnd)->rect.top ==
1410                 start->rect.top) ;
1411               iIndexEnd++)
1412           /* intentionally blank */;
1413
1414           /*
1415            * we need to justify these tabs so they fill the whole given
1416            * client area
1417            *
1418            */
1419           /* find the amount of space remaining on this row */
1420           widthDiff = clientRect.right - (2 * SELECTED_TAB_OFFSET) -
1421                         TAB_GetItem(infoPtr, iIndexEnd - 1)->rect.right;
1422
1423           /* iCount is the number of tab items on this row */
1424           iCount = iIndexEnd - iIndexStart;
1425
1426           if (iCount > 1)
1427           {
1428             remainder = widthDiff % iCount;
1429             widthDiff = widthDiff / iCount;
1430             /* add widthDiff/iCount, or extra space/items on row, to each item on this row */
1431             for (iIndex=iIndexStart, iCount=0; iIndex < iIndexEnd; iIndex++, iCount++)
1432             {
1433               TAB_ITEM *item = TAB_GetItem(infoPtr, iIndex);
1434
1435               item->rect.left += iCount * widthDiff;
1436               item->rect.right += (iCount + 1) * widthDiff;
1437
1438               TRACE("adjusting 1 <%s>, l,r=%d,%d\n",
1439                   debugstr_w(item->pszText),
1440                   item->rect.left, item->rect.right);
1441
1442             }
1443             TAB_GetItem(infoPtr, iIndex - 1)->rect.right += remainder;
1444           }
1445           else /* we have only one item on this row, make it take up the entire row */
1446           {
1447             start->rect.left = clientRect.left;
1448             start->rect.right = clientRect.right - 4;
1449
1450             TRACE("adjusting 2 <%s>, l,r=%d,%d\n",
1451                 debugstr_w(start->pszText),
1452                 start->rect.left, start->rect.right);
1453
1454           }
1455
1456
1457           iIndexStart = iIndexEnd;
1458         }
1459       }
1460   }
1461
1462   /* if TCS_VERTICAL rotate the tabs so they are along the side of the clientRect */
1463   if(infoPtr->dwStyle & TCS_VERTICAL)
1464   {
1465     RECT rcOriginal;
1466     for(iIndex = 0; iIndex < infoPtr->uNumItem; iIndex++)
1467     {
1468       rcItem = &TAB_GetItem(infoPtr, iIndex)->rect;
1469
1470       rcOriginal = *rcItem;
1471
1472       /* this is rotating the items by 90 degrees clockwise around the center of the control */
1473       rcItem->top = (rcOriginal.left - clientRect.left);
1474       rcItem->bottom = rcItem->top + (rcOriginal.right - rcOriginal.left);
1475       rcItem->left = rcOriginal.top;
1476       rcItem->right = rcOriginal.bottom;
1477     }
1478   }
1479
1480   TAB_EnsureSelectionVisible(infoPtr);
1481   TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
1482
1483   /* Cleanup */
1484   SelectObject (hdc, hOldFont);
1485   ReleaseDC (infoPtr->hwnd, hdc);
1486 }
1487
1488
1489 static void
1490 TAB_EraseTabInterior(const TAB_INFO *infoPtr, HDC hdc, INT iItem, const RECT *drawRect)
1491 {
1492     HBRUSH   hbr = CreateSolidBrush (comctl32_color.clrBtnFace);
1493     BOOL     deleteBrush = TRUE;
1494     RECT     rTemp = *drawRect;
1495
1496     if (infoPtr->dwStyle & TCS_BUTTONS)
1497     {
1498         if (iItem == infoPtr->iSelected)
1499         {
1500             /* Background color */
1501             if (!(infoPtr->dwStyle & TCS_OWNERDRAWFIXED))
1502             {
1503                 DeleteObject(hbr);
1504                 hbr = GetSysColorBrush(COLOR_SCROLLBAR);
1505
1506                 SetTextColor(hdc, comctl32_color.clr3dFace);
1507                 SetBkColor(hdc, comctl32_color.clr3dHilight);
1508
1509                 /* if COLOR_WINDOW happens to be the same as COLOR_3DHILIGHT
1510                 * we better use 0x55aa bitmap brush to make scrollbar's background
1511                 * look different from the window background.
1512                 */
1513                 if (comctl32_color.clr3dHilight == comctl32_color.clrWindow)
1514                     hbr = COMCTL32_hPattern55AABrush;
1515
1516                 deleteBrush = FALSE;
1517             }
1518             FillRect(hdc, &rTemp, hbr);
1519         }
1520         else  /* ! selected */
1521         {
1522             if (infoPtr->dwStyle & TCS_FLATBUTTONS)
1523             {
1524                 InflateRect(&rTemp, 2, 2);
1525                 FillRect(hdc, &rTemp, hbr);
1526                 if (iItem == infoPtr->iHotTracked ||
1527                    (iItem != infoPtr->iSelected && iItem == infoPtr->uFocus))
1528                     DrawEdge(hdc, &rTemp, BDR_RAISEDINNER, BF_RECT);
1529             }
1530             else
1531                 FillRect(hdc, &rTemp, hbr);
1532         }
1533
1534     }
1535     else /* !TCS_BUTTONS */
1536     {
1537         InflateRect(&rTemp, -2, -2);
1538         if (!GetWindowTheme (infoPtr->hwnd))
1539             FillRect(hdc, &rTemp, hbr);
1540     }
1541
1542     /* highlighting is drawn on top of previous fills */
1543     if (TAB_GetItem(infoPtr, iItem)->dwState & TCIS_HIGHLIGHTED)
1544     {
1545         if (deleteBrush)
1546         {
1547             DeleteObject(hbr);
1548             deleteBrush = FALSE;
1549         }
1550         hbr = GetSysColorBrush(COLOR_HIGHLIGHT);
1551         FillRect(hdc, &rTemp, hbr);
1552     }
1553
1554     /* Cleanup */
1555     if (deleteBrush) DeleteObject(hbr);
1556 }
1557
1558 /******************************************************************************
1559  * TAB_DrawItemInterior
1560  *
1561  * This method is used to draw the interior (text and icon) of a single tab
1562  * into the tab control.
1563  */
1564 static void
1565 TAB_DrawItemInterior(const TAB_INFO *infoPtr, HDC hdc, INT iItem, RECT *drawRect)
1566 {
1567   RECT localRect;
1568
1569   HPEN   htextPen;
1570   HPEN   holdPen;
1571   INT    oldBkMode;
1572   HFONT  hOldFont;
1573   
1574 /*  if (drawRect == NULL) */
1575   {
1576     BOOL isVisible;
1577     RECT itemRect;
1578     RECT selectedRect;
1579
1580     /*
1581      * Get the rectangle for the item.
1582      */
1583     isVisible = TAB_InternalGetItemRect(infoPtr, iItem, &itemRect, &selectedRect);
1584     if (!isVisible)
1585       return;
1586
1587     /*
1588      * Make sure drawRect points to something valid; simplifies code.
1589      */
1590     drawRect = &localRect;
1591
1592     /*
1593      * This logic copied from the part of TAB_DrawItem which draws
1594      * the tab background.  It's important to keep it in sync.  I
1595      * would have liked to avoid code duplication, but couldn't figure
1596      * out how without making spaghetti of TAB_DrawItem.
1597      */
1598     if (iItem == infoPtr->iSelected)
1599       *drawRect = selectedRect;
1600     else
1601       *drawRect = itemRect;
1602         
1603     if (infoPtr->dwStyle & TCS_BUTTONS)
1604     {
1605       if (iItem == infoPtr->iSelected)
1606       {
1607         drawRect->left   += 4;
1608         drawRect->top    += 4;
1609         drawRect->right  -= 4;
1610
1611         if (infoPtr->dwStyle & TCS_VERTICAL)
1612         {
1613           if (!(infoPtr->dwStyle & TCS_BOTTOM)) drawRect->right  += 1;
1614           drawRect->bottom   -= 4;
1615         }
1616         else
1617         {
1618           if (infoPtr->dwStyle & TCS_BOTTOM)
1619           {
1620             drawRect->top    -= 2;
1621             drawRect->bottom -= 4;
1622           }
1623           else
1624             drawRect->bottom -= 1;
1625         }
1626       }
1627       else
1628       {
1629         drawRect->left   += 2;
1630         drawRect->top    += 2;
1631         drawRect->right  -= 2;
1632         drawRect->bottom -= 2;
1633       }
1634     }
1635     else
1636     {
1637       if ((infoPtr->dwStyle & TCS_VERTICAL) && (infoPtr->dwStyle & TCS_BOTTOM))
1638       {
1639         if (iItem != infoPtr->iSelected)
1640         {
1641           drawRect->left   += 2;
1642           drawRect->top    += 2;
1643           drawRect->bottom -= 2;
1644         }
1645       }
1646       else if (infoPtr->dwStyle & TCS_VERTICAL)
1647       {
1648         if (iItem == infoPtr->iSelected)
1649         {
1650           drawRect->right  += 1;
1651         }
1652         else
1653         {
1654           drawRect->top    += 2;
1655           drawRect->right  -= 2;
1656           drawRect->bottom -= 2;
1657         }
1658       }
1659       else if (infoPtr->dwStyle & TCS_BOTTOM)
1660       {
1661         if (iItem == infoPtr->iSelected)
1662         {
1663           drawRect->top    -= 2;
1664         }
1665         else
1666         {
1667           InflateRect(drawRect, -2, -2);
1668           drawRect->bottom += 2;
1669         }
1670       }
1671       else
1672       {
1673         if (iItem == infoPtr->iSelected)
1674         {
1675           drawRect->bottom += 3;
1676         }
1677         else
1678         {
1679           drawRect->bottom -= 2;
1680           InflateRect(drawRect, -2, 0);
1681         }
1682       }
1683     }
1684   }
1685   TRACE("drawRect=(%s)\n", wine_dbgstr_rect(drawRect));
1686
1687   /* Clear interior */
1688   TAB_EraseTabInterior (infoPtr, hdc, iItem, drawRect);
1689
1690   /* Draw the focus rectangle */
1691   if (!(infoPtr->dwStyle & TCS_FOCUSNEVER) &&
1692       (GetFocus() == infoPtr->hwnd) &&
1693       (iItem == infoPtr->uFocus) )
1694   {
1695     RECT rFocus = *drawRect;
1696
1697     if (!(infoPtr->dwStyle & TCS_BUTTONS)) InflateRect(&rFocus, -3, -3);
1698     if (infoPtr->dwStyle & TCS_BOTTOM && !(infoPtr->dwStyle & TCS_VERTICAL))
1699       rFocus.top -= 3;
1700
1701     /* focus should stay on selected item for TCS_BUTTONS style */
1702     if (!((infoPtr->dwStyle & TCS_BUTTONS) && (infoPtr->iSelected != iItem)))
1703       DrawFocusRect(hdc, &rFocus);
1704   }
1705
1706   /*
1707    * Text pen
1708    */
1709   htextPen = CreatePen( PS_SOLID, 1, comctl32_color.clrBtnText );
1710   holdPen  = SelectObject(hdc, htextPen);
1711   hOldFont = SelectObject(hdc, infoPtr->hFont);
1712
1713   /*
1714    * Setup for text output
1715   */
1716   oldBkMode = SetBkMode(hdc, TRANSPARENT);
1717   if (!GetWindowTheme (infoPtr->hwnd) || (infoPtr->dwStyle & TCS_BUTTONS))
1718   {
1719     if ((infoPtr->dwStyle & TCS_HOTTRACK) && (iItem == infoPtr->iHotTracked) &&
1720         !(infoPtr->dwStyle & TCS_FLATBUTTONS))
1721       SetTextColor(hdc, comctl32_color.clrHighlight);
1722     else if (TAB_GetItem(infoPtr, iItem)->dwState & TCIS_HIGHLIGHTED)
1723       SetTextColor(hdc, comctl32_color.clrHighlightText);
1724     else
1725       SetTextColor(hdc, comctl32_color.clrBtnText);
1726   }
1727
1728   /*
1729    * if owner draw, tell the owner to draw
1730    */
1731   if ((infoPtr->dwStyle & TCS_OWNERDRAWFIXED) && GetParent(infoPtr->hwnd))
1732   {
1733     DRAWITEMSTRUCT dis;
1734     UINT id;
1735
1736     drawRect->top += 2;
1737     drawRect->right -= 1;
1738     if ( iItem == infoPtr->iSelected )
1739     {
1740         drawRect->right -= 1;
1741         drawRect->left += 1;
1742     }
1743
1744     /*
1745      * get the control id
1746      */
1747     id = (UINT)GetWindowLongPtrW( infoPtr->hwnd, GWLP_ID );
1748
1749     /*
1750      * put together the DRAWITEMSTRUCT
1751      */
1752     dis.CtlType    = ODT_TAB;
1753     dis.CtlID      = id;
1754     dis.itemID     = iItem;
1755     dis.itemAction = ODA_DRAWENTIRE;
1756     dis.itemState = 0;
1757     if ( iItem == infoPtr->iSelected )
1758       dis.itemState |= ODS_SELECTED;
1759     if (infoPtr->uFocus == iItem) 
1760       dis.itemState |= ODS_FOCUS;
1761     dis.hwndItem = infoPtr->hwnd;
1762     dis.hDC      = hdc;
1763     CopyRect(&dis.rcItem,drawRect);
1764     dis.itemData = (ULONG_PTR)TAB_GetItem(infoPtr, iItem)->extra;
1765
1766     /*
1767      * send the draw message
1768      */
1769     SendMessageW( infoPtr->hwndNotify, WM_DRAWITEM, id, (LPARAM)&dis );
1770   }
1771   else
1772   {
1773     TAB_ITEM *item = TAB_GetItem(infoPtr, iItem);
1774     RECT rcTemp;
1775     RECT rcImage;
1776
1777     /* used to center the icon and text in the tab */
1778     RECT rcText;
1779     INT center_offset_h, center_offset_v;
1780
1781     /* set rcImage to drawRect, we will use top & left in our ImageList_Draw call */
1782     rcImage = *drawRect;
1783
1784     rcTemp = *drawRect;
1785
1786     rcText.left = rcText.top = rcText.right = rcText.bottom = 0;
1787
1788     /* get the rectangle that the text fits in */
1789     if (item->pszText)
1790     {
1791       DrawTextW(hdc, item->pszText, -1, &rcText, DT_CALCRECT);
1792     }
1793     /*
1794      * If not owner draw, then do the drawing ourselves.
1795      *
1796      * Draw the icon.
1797      */
1798     if (infoPtr->himl && item->iImage != -1)
1799     {
1800       INT cx;
1801       INT cy;
1802       
1803       ImageList_GetIconSize(infoPtr->himl, &cx, &cy);
1804
1805       if(infoPtr->dwStyle & TCS_VERTICAL)
1806       {
1807         center_offset_h = ((drawRect->bottom - drawRect->top) - (cy + infoPtr->uHItemPadding + (rcText.right  - rcText.left))) / 2;
1808         center_offset_v = ((drawRect->right - drawRect->left) - cx) / 2;
1809       }
1810       else
1811       {
1812         center_offset_h = ((drawRect->right - drawRect->left) - (cx + infoPtr->uHItemPadding + (rcText.right  - rcText.left))) / 2;
1813         center_offset_v = ((drawRect->bottom - drawRect->top) - cy) / 2;
1814       }
1815
1816       /* if an item is selected, the icon is shifted up instead of down */
1817       if (iItem == infoPtr->iSelected)
1818         center_offset_v -= infoPtr->uVItemPadding / 2;
1819       else
1820         center_offset_v += infoPtr->uVItemPadding / 2;
1821
1822       if (infoPtr->dwStyle & TCS_FIXEDWIDTH && infoPtr->dwStyle & (TCS_FORCELABELLEFT | TCS_FORCEICONLEFT))
1823         center_offset_h = infoPtr->uHItemPadding;
1824
1825       if (center_offset_h < 2)
1826         center_offset_h = 2;
1827         
1828       if (center_offset_v < 0)
1829         center_offset_v = 0;
1830         
1831       TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%s), textlen=%d\n",
1832           debugstr_w(item->pszText), center_offset_h, center_offset_v,
1833           wine_dbgstr_rect(drawRect), (rcText.right-rcText.left));
1834
1835       if((infoPtr->dwStyle & TCS_VERTICAL) && (infoPtr->dwStyle & TCS_BOTTOM))
1836       {
1837         rcImage.top = drawRect->top + center_offset_h;
1838         /* if tab is TCS_VERTICAL and TCS_BOTTOM, the text is drawn from the */
1839         /* right side of the tab, but the image still uses the left as its x position */
1840         /* this keeps the image always drawn off of the same side of the tab */
1841         rcImage.left = drawRect->right - cx - center_offset_v;
1842         drawRect->top += cy + infoPtr->uHItemPadding;
1843       }
1844       else if(infoPtr->dwStyle & TCS_VERTICAL)
1845       {
1846         rcImage.top  = drawRect->bottom - cy - center_offset_h;
1847         rcImage.left = drawRect->left + center_offset_v;
1848         drawRect->bottom -= cy + infoPtr->uHItemPadding;
1849       }
1850       else /* normal style, whether TCS_BOTTOM or not */
1851       {
1852         rcImage.left = drawRect->left + center_offset_h;
1853         rcImage.top = drawRect->top + center_offset_v;
1854         drawRect->left += cx + infoPtr->uHItemPadding;
1855       }
1856
1857       TRACE("drawing image=%d, left=%d, top=%d\n",
1858             item->iImage, rcImage.left, rcImage.top-1);
1859       ImageList_Draw
1860         (
1861         infoPtr->himl,
1862         item->iImage,
1863         hdc,
1864         rcImage.left,
1865         rcImage.top,
1866         ILD_NORMAL
1867         );
1868     }
1869
1870     /* Now position text */
1871     if (infoPtr->dwStyle & TCS_FIXEDWIDTH && infoPtr->dwStyle & TCS_FORCELABELLEFT)
1872       center_offset_h = infoPtr->uHItemPadding;
1873     else
1874       if(infoPtr->dwStyle & TCS_VERTICAL)
1875         center_offset_h = ((drawRect->bottom - drawRect->top) - (rcText.right - rcText.left)) / 2;
1876       else
1877         center_offset_h = ((drawRect->right - drawRect->left) - (rcText.right - rcText.left)) / 2;
1878
1879     if(infoPtr->dwStyle & TCS_VERTICAL)
1880     {
1881       if(infoPtr->dwStyle & TCS_BOTTOM)
1882         drawRect->top+=center_offset_h;
1883       else
1884         drawRect->bottom-=center_offset_h;
1885
1886       center_offset_v = ((drawRect->right - drawRect->left) - (rcText.bottom - rcText.top)) / 2;
1887     }
1888     else
1889     {
1890       drawRect->left += center_offset_h;
1891       center_offset_v = ((drawRect->bottom - drawRect->top) - (rcText.bottom - rcText.top)) / 2;
1892     }
1893
1894     /* if an item is selected, the text is shifted up instead of down */
1895     if (iItem == infoPtr->iSelected)
1896         center_offset_v -= infoPtr->uVItemPadding / 2;
1897     else
1898         center_offset_v += infoPtr->uVItemPadding / 2;
1899
1900     if (center_offset_v < 0)
1901       center_offset_v = 0;
1902
1903     if(infoPtr->dwStyle & TCS_VERTICAL)
1904       drawRect->left += center_offset_v;
1905     else
1906       drawRect->top += center_offset_v;
1907
1908     /* Draw the text */
1909     if(infoPtr->dwStyle & TCS_VERTICAL) /* if we are vertical rotate the text and each character */
1910     {
1911       static const WCHAR ArialW[] = { 'A','r','i','a','l',0 };
1912       LOGFONTW logfont;
1913       HFONT hFont = 0;
1914       INT nEscapement = 900;
1915       INT nOrientation = 900;
1916
1917       if(infoPtr->dwStyle & TCS_BOTTOM)
1918       {
1919         nEscapement = -900;
1920         nOrientation = -900;
1921       }
1922
1923       /* to get a font with the escapement and orientation we are looking for, we need to */
1924       /* call CreateFontIndirectA, which requires us to set the values of the logfont we pass in */
1925       if (!GetObjectW((infoPtr->hFont) ?
1926                 infoPtr->hFont : GetStockObject(SYSTEM_FONT),
1927                 sizeof(LOGFONTW),&logfont))
1928       {
1929         INT iPointSize = 9;
1930
1931         lstrcpyW(logfont.lfFaceName, ArialW);
1932         logfont.lfHeight = -MulDiv(iPointSize, GetDeviceCaps(hdc, LOGPIXELSY),
1933                                     72);
1934         logfont.lfWeight = FW_NORMAL;
1935         logfont.lfItalic = 0;
1936         logfont.lfUnderline = 0;
1937         logfont.lfStrikeOut = 0;
1938       }
1939
1940       logfont.lfEscapement = nEscapement;
1941       logfont.lfOrientation = nOrientation;
1942       hFont = CreateFontIndirectW(&logfont);
1943       SelectObject(hdc, hFont);
1944
1945       if (item->pszText)
1946       {
1947         ExtTextOutW(hdc,
1948         (infoPtr->dwStyle & TCS_BOTTOM) ? drawRect->right : drawRect->left,
1949         (!(infoPtr->dwStyle & TCS_BOTTOM)) ? drawRect->bottom : drawRect->top,
1950         ETO_CLIPPED,
1951         drawRect,
1952         item->pszText,
1953         lstrlenW(item->pszText),
1954         0);
1955       }
1956
1957       DeleteObject(hFont);
1958     }
1959     else
1960     {
1961       TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%s), textlen=%d\n",
1962           debugstr_w(item->pszText), center_offset_h, center_offset_v,
1963           wine_dbgstr_rect(drawRect), (rcText.right-rcText.left));
1964       if (item->pszText)
1965       {
1966         DrawTextW
1967         (
1968           hdc,
1969           item->pszText,
1970           lstrlenW(item->pszText),
1971           drawRect,
1972           DT_LEFT | DT_SINGLELINE
1973         );
1974       }
1975     }
1976
1977     *drawRect = rcTemp; /* restore drawRect */
1978   }
1979
1980   /*
1981   * Cleanup
1982   */
1983   SelectObject(hdc, hOldFont);
1984   SetBkMode(hdc, oldBkMode);
1985   SelectObject(hdc, holdPen);
1986   DeleteObject( htextPen );
1987 }
1988
1989 /******************************************************************************
1990  * TAB_DrawItem
1991  *
1992  * This method is used to draw a single tab into the tab control.
1993  */
1994 static void TAB_DrawItem(const TAB_INFO *infoPtr, HDC  hdc, INT  iItem)
1995 {
1996   RECT      itemRect;
1997   RECT      selectedRect;
1998   BOOL      isVisible;
1999   RECT      r, fillRect, r1;
2000   INT       clRight = 0;
2001   INT       clBottom = 0;
2002   COLORREF  bkgnd, corner;
2003   HTHEME    theme;
2004
2005   /*
2006    * Get the rectangle for the item.
2007    */
2008   isVisible = TAB_InternalGetItemRect(infoPtr,
2009                                       iItem,
2010                                       &itemRect,
2011                                       &selectedRect);
2012
2013   if (isVisible)
2014   {
2015     RECT rUD, rC;
2016
2017     /* Clip UpDown control to not draw over it */
2018     if (infoPtr->needsScrolling)
2019     {
2020       GetWindowRect(infoPtr->hwnd, &rC);
2021       GetWindowRect(infoPtr->hwndUpDown, &rUD);
2022       ExcludeClipRect(hdc, rUD.left - rC.left, rUD.top - rC.top, rUD.right - rC.left, rUD.bottom - rC.top);
2023     }
2024
2025     /* If you need to see what the control is doing,
2026      * then override these variables. They will change what
2027      * fill colors are used for filling the tabs, and the
2028      * corners when drawing the edge.
2029      */
2030     bkgnd = comctl32_color.clrBtnFace;
2031     corner = comctl32_color.clrBtnFace;
2032
2033     if (infoPtr->dwStyle & TCS_BUTTONS)
2034     {
2035       /* Get item rectangle */
2036       r = itemRect;
2037
2038       /* Separators between flat buttons */
2039       if ((infoPtr->dwStyle & TCS_FLATBUTTONS) && (infoPtr->exStyle & TCS_EX_FLATSEPARATORS))
2040       {
2041         r1 = r;
2042         r1.right += (FLAT_BTN_SPACINGX -2);
2043         DrawEdge(hdc, &r1, EDGE_ETCHED, BF_RIGHT);
2044       }
2045
2046       if (iItem == infoPtr->iSelected)
2047       {
2048         DrawEdge(hdc, &r, EDGE_SUNKEN, BF_SOFT|BF_RECT);
2049         
2050         OffsetRect(&r, 1, 1);
2051       }
2052       else  /* ! selected */
2053       {
2054         DWORD state = TAB_GetItem(infoPtr, iItem)->dwState;
2055
2056         if ((state & TCIS_BUTTONPRESSED) || (iItem == infoPtr->uFocus))
2057           DrawEdge(hdc, &r, EDGE_SUNKEN, BF_SOFT|BF_RECT);
2058         else
2059           if (!(infoPtr->dwStyle & TCS_FLATBUTTONS))
2060             DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_RECT);
2061       }
2062     }
2063     else /* !TCS_BUTTONS */
2064     {
2065       /* We draw a rectangle of different sizes depending on the selection
2066        * state. */
2067       if (iItem == infoPtr->iSelected) {
2068         RECT rect;
2069         GetClientRect (infoPtr->hwnd, &rect);
2070         clRight = rect.right;
2071         clBottom = rect.bottom;
2072         r = selectedRect;
2073       }
2074       else
2075         r = itemRect;
2076
2077       /*
2078        * Erase the background. (Delay it but setup rectangle.)
2079        * This is necessary when drawing the selected item since it is larger
2080        * than the others, it might overlap with stuff already drawn by the
2081        * other tabs
2082        */
2083       fillRect = r;
2084
2085       /* Draw themed tabs - but only if they are at the top.
2086        * Windows draws even side or bottom tabs themed, with wacky results.
2087        * However, since in Wine apps may get themed that did not opt in via
2088        * a manifest avoid theming when we know the result will be wrong */
2089       if ((theme = GetWindowTheme (infoPtr->hwnd)) 
2090           && ((infoPtr->dwStyle & (TCS_VERTICAL | TCS_BOTTOM)) == 0))
2091       {
2092           static const int partIds[8] = {
2093               /* Normal item */
2094               TABP_TABITEM,
2095               TABP_TABITEMLEFTEDGE,
2096               TABP_TABITEMRIGHTEDGE,
2097               TABP_TABITEMBOTHEDGE,
2098               /* Selected tab */
2099               TABP_TOPTABITEM,
2100               TABP_TOPTABITEMLEFTEDGE,
2101               TABP_TOPTABITEMRIGHTEDGE,
2102               TABP_TOPTABITEMBOTHEDGE,
2103           };
2104           int partIndex = 0;
2105           int stateId = TIS_NORMAL;
2106
2107           /* selected and unselected tabs have different parts */
2108           if (iItem == infoPtr->iSelected)
2109               partIndex += 4;
2110           /* The part also differs on the position of a tab on a line.
2111            * "Visually" determining the position works well enough. */
2112           if(selectedRect.left == 0)
2113               partIndex += 1;
2114           if(selectedRect.right == clRight)
2115               partIndex += 2;
2116
2117           if (iItem == infoPtr->iSelected)
2118               stateId = TIS_SELECTED;
2119           else if (iItem == infoPtr->iHotTracked)
2120               stateId = TIS_HOT;
2121           else if (iItem == infoPtr->uFocus)
2122               stateId = TIS_FOCUSED;
2123
2124           /* Adjust rectangle for bottommost row */
2125           if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2126             r.bottom += 3;
2127
2128           DrawThemeBackground (theme, hdc, partIds[partIndex], stateId, &r, NULL);
2129           GetThemeBackgroundContentRect (theme, hdc, partIds[partIndex], stateId, &r, &r);
2130       }
2131       else if(infoPtr->dwStyle & TCS_VERTICAL)
2132       {
2133         /* These are for adjusting the drawing of a Selected tab      */
2134         /* The initial values are for the normal case of non-Selected */
2135         int ZZ = 1;   /* Do not stretch if selected */
2136         if (iItem == infoPtr->iSelected) {
2137             ZZ = 0;
2138
2139             /* if leftmost draw the line longer */
2140             if(selectedRect.top == 0)
2141                 fillRect.top += CONTROL_BORDER_SIZEY;
2142             /* if rightmost draw the line longer */
2143             if(selectedRect.bottom == clBottom)
2144                 fillRect.bottom -= CONTROL_BORDER_SIZEY;
2145         }
2146
2147         if (infoPtr->dwStyle & TCS_BOTTOM)
2148         {
2149           /* Adjust both rectangles to match native */
2150           r.left += (1-ZZ);
2151
2152           TRACE("<right> item=%d, fill=(%s), edge=(%s)\n",
2153                 iItem, wine_dbgstr_rect(&fillRect), wine_dbgstr_rect(&r));
2154
2155           /* Clear interior */
2156           SetBkColor(hdc, bkgnd);
2157           ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2158
2159           /* Draw rectangular edge around tab */
2160           DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_RIGHT|BF_TOP|BF_BOTTOM);
2161
2162           /* Now erase the top corner and draw diagonal edge */
2163           SetBkColor(hdc, corner);
2164           r1.left = r.right - ROUND_CORNER_SIZE - 1;
2165           r1.top = r.top;
2166           r1.right = r.right;
2167           r1.bottom = r1.top + ROUND_CORNER_SIZE;
2168           ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2169           r1.right--;
2170           DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPLEFT);
2171
2172           /* Now erase the bottom corner and draw diagonal edge */
2173           r1.left = r.right - ROUND_CORNER_SIZE - 1;
2174           r1.bottom = r.bottom;
2175           r1.right = r.right;
2176           r1.top = r1.bottom - ROUND_CORNER_SIZE;
2177           ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2178           r1.right--;
2179           DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMLEFT);
2180
2181           if ((iItem == infoPtr->iSelected) && (selectedRect.top == 0)) {
2182               r1 = r;
2183               r1.right = r1.left;
2184               r1.left--;
2185               DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_TOP);
2186           }
2187
2188         }
2189         else
2190         {
2191           TRACE("<left> item=%d, fill=(%s), edge=(%s)\n",
2192                 iItem, wine_dbgstr_rect(&fillRect), wine_dbgstr_rect(&r));
2193
2194           /* Clear interior */
2195           SetBkColor(hdc, bkgnd);
2196           ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2197
2198           /* Draw rectangular edge around tab */
2199           DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_TOP|BF_BOTTOM);
2200
2201           /* Now erase the top corner and draw diagonal edge */
2202           SetBkColor(hdc, corner);
2203           r1.left = r.left;
2204           r1.top = r.top;
2205           r1.right = r1.left + ROUND_CORNER_SIZE + 1;
2206           r1.bottom = r1.top + ROUND_CORNER_SIZE;
2207           ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2208           r1.left++;
2209           DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPRIGHT);
2210
2211           /* Now erase the bottom corner and draw diagonal edge */
2212           r1.left = r.left;
2213           r1.bottom = r.bottom;
2214           r1.right = r1.left + ROUND_CORNER_SIZE + 1;
2215           r1.top = r1.bottom - ROUND_CORNER_SIZE;
2216           ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2217           r1.left++;
2218           DrawEdge(hdc, &r1, EDGE_SUNKEN, BF_DIAGONAL_ENDTOPLEFT);
2219         }
2220       }
2221       else  /* ! TCS_VERTICAL */
2222       {
2223         /* These are for adjusting the drawing of a Selected tab      */
2224         /* The initial values are for the normal case of non-Selected */
2225         if (iItem == infoPtr->iSelected) {
2226             /* if leftmost draw the line longer */
2227             if(selectedRect.left == 0)
2228                 fillRect.left += CONTROL_BORDER_SIZEX;
2229             /* if rightmost draw the line longer */
2230             if(selectedRect.right == clRight)
2231                 fillRect.right -= CONTROL_BORDER_SIZEX;
2232         }
2233
2234         if (infoPtr->dwStyle & TCS_BOTTOM)
2235         {
2236           /* Adjust both rectangles for topmost row */
2237           if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2238           {
2239             fillRect.top -= 2;
2240             r.top -= 1;
2241           }
2242
2243           TRACE("<bottom> item=%d, fill=(%s), edge=(%s)\n",
2244                 iItem, wine_dbgstr_rect(&fillRect), wine_dbgstr_rect(&r));
2245
2246           /* Clear interior */
2247           SetBkColor(hdc, bkgnd);
2248           ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2249
2250           /* Draw rectangular edge around tab */
2251           DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_BOTTOM|BF_RIGHT);
2252
2253           /* Now erase the righthand corner and draw diagonal edge */
2254           SetBkColor(hdc, corner);
2255           r1.left = r.right - ROUND_CORNER_SIZE;
2256           r1.bottom = r.bottom;
2257           r1.right = r.right;
2258           r1.top = r1.bottom - ROUND_CORNER_SIZE - 1;
2259           ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2260           r1.bottom--;
2261           DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMLEFT);
2262
2263           /* Now erase the lefthand corner and draw diagonal edge */
2264           r1.left = r.left;
2265           r1.bottom = r.bottom;
2266           r1.right = r1.left + ROUND_CORNER_SIZE;
2267           r1.top = r1.bottom - ROUND_CORNER_SIZE - 1;
2268           ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2269           r1.bottom--;
2270           DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPLEFT);
2271
2272           if (iItem == infoPtr->iSelected)
2273           {
2274             r.top += 2;
2275             r.left += 1;
2276             if (selectedRect.left == 0)
2277             {
2278               r1 = r;
2279               r1.bottom = r1.top;
2280               r1.top--;
2281               DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_LEFT);
2282             }
2283           }
2284
2285         }
2286         else
2287         {
2288           /* Adjust both rectangles for bottommost row */
2289           if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2290           {
2291             fillRect.bottom += 3;
2292             r.bottom += 2;
2293           }
2294
2295           TRACE("<top> item=%d, fill=(%s), edge=(%s)\n",
2296                 iItem, wine_dbgstr_rect(&fillRect), wine_dbgstr_rect(&r));
2297
2298           /* Clear interior */
2299           SetBkColor(hdc, bkgnd);
2300           ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2301
2302           /* Draw rectangular edge around tab */
2303           DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_TOP|BF_RIGHT);
2304
2305           /* Now erase the righthand corner and draw diagonal edge */
2306           SetBkColor(hdc, corner);
2307           r1.left = r.right - ROUND_CORNER_SIZE;
2308           r1.top = r.top;
2309           r1.right = r.right;
2310           r1.bottom = r1.top + ROUND_CORNER_SIZE + 1;
2311           ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2312           r1.top++;
2313           DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMRIGHT);
2314
2315           /* Now erase the lefthand corner and draw diagonal edge */
2316           r1.left = r.left;
2317           r1.top = r.top;
2318           r1.right = r1.left + ROUND_CORNER_SIZE;
2319           r1.bottom = r1.top + ROUND_CORNER_SIZE + 1;
2320           ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2321           r1.top++;
2322           DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPRIGHT);
2323         }
2324       }
2325     }
2326
2327     TAB_DumpItemInternal(infoPtr, iItem);
2328
2329     /* This modifies r to be the text rectangle. */
2330     TAB_DrawItemInterior(infoPtr, hdc, iItem, &r);
2331   }
2332 }
2333
2334 /******************************************************************************
2335  * TAB_DrawBorder
2336  *
2337  * This method is used to draw the raised border around the tab control
2338  * "content" area.
2339  */
2340 static void TAB_DrawBorder(const TAB_INFO *infoPtr, HDC hdc)
2341 {
2342   RECT rect;
2343   HTHEME theme = GetWindowTheme (infoPtr->hwnd);
2344
2345   GetClientRect (infoPtr->hwnd, &rect);
2346
2347   /*
2348    * Adjust for the style
2349    */
2350
2351   if (infoPtr->uNumItem)
2352   {
2353     if ((infoPtr->dwStyle & TCS_BOTTOM) && !(infoPtr->dwStyle & TCS_VERTICAL))
2354       rect.bottom -= infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2355     else if((infoPtr->dwStyle & TCS_BOTTOM) && (infoPtr->dwStyle & TCS_VERTICAL))
2356       rect.right  -= infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2357     else if(infoPtr->dwStyle & TCS_VERTICAL)
2358       rect.left   += infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2359     else /* not TCS_VERTICAL and not TCS_BOTTOM */
2360       rect.top    += infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2361   }
2362
2363   TRACE("border=(%s)\n", wine_dbgstr_rect(&rect));
2364
2365   if (theme)
2366       DrawThemeBackground (theme, hdc, TABP_PANE, 0, &rect, NULL);
2367   else
2368       DrawEdge(hdc, &rect, EDGE_RAISED, BF_SOFT|BF_RECT);
2369 }
2370
2371 /******************************************************************************
2372  * TAB_Refresh
2373  *
2374  * This method repaints the tab control..
2375  */
2376 static void TAB_Refresh (const TAB_INFO *infoPtr, HDC hdc)
2377 {
2378   HFONT hOldFont;
2379   INT i;
2380
2381   if (!infoPtr->DoRedraw)
2382     return;
2383
2384   hOldFont = SelectObject (hdc, infoPtr->hFont);
2385
2386   if (infoPtr->dwStyle & TCS_BUTTONS)
2387   {
2388     for (i = 0; i < infoPtr->uNumItem; i++)
2389       TAB_DrawItem (infoPtr, hdc, i);
2390   }
2391   else
2392   {
2393     /* Draw all the non selected item first */
2394     for (i = 0; i < infoPtr->uNumItem; i++)
2395     {
2396       if (i != infoPtr->iSelected)
2397         TAB_DrawItem (infoPtr, hdc, i);
2398     }
2399
2400     /* Now, draw the border, draw it before the selected item
2401      * since the selected item overwrites part of the border. */
2402     TAB_DrawBorder (infoPtr, hdc);
2403
2404     /* Then, draw the selected item */
2405     TAB_DrawItem (infoPtr, hdc, infoPtr->iSelected);
2406   }
2407
2408   SelectObject (hdc, hOldFont);
2409 }
2410
2411 static inline DWORD TAB_GetRowCount (const TAB_INFO *infoPtr)
2412 {
2413   TRACE("(%p)\n", infoPtr);
2414   return infoPtr->uNumRows;
2415 }
2416
2417 static inline LRESULT TAB_SetRedraw (TAB_INFO *infoPtr, BOOL doRedraw)
2418 {
2419   infoPtr->DoRedraw = doRedraw;
2420   return 0;
2421 }
2422
2423 /******************************************************************************
2424  * TAB_EnsureSelectionVisible
2425  *
2426  * This method will make sure that the current selection is completely
2427  * visible by scrolling until it is.
2428  */
2429 static void TAB_EnsureSelectionVisible(
2430   TAB_INFO* infoPtr)
2431 {
2432   INT iSelected = infoPtr->iSelected;
2433   INT iOrigLeftmostVisible = infoPtr->leftmostVisible;
2434
2435   /* set the items row to the bottommost row or topmost row depending on
2436    * style */
2437   if ((infoPtr->uNumRows > 1) && !(infoPtr->dwStyle & TCS_BUTTONS))
2438   {
2439       TAB_ITEM *selected = TAB_GetItem(infoPtr, iSelected);
2440       INT newselected;
2441       INT iTargetRow;
2442
2443       if(infoPtr->dwStyle & TCS_VERTICAL)
2444         newselected = selected->rect.left;
2445       else
2446         newselected = selected->rect.top;
2447
2448       /* the target row is always (number of rows - 1)
2449          as row 0 is furthest from the clientRect */
2450       iTargetRow = infoPtr->uNumRows - 1;
2451
2452       if (newselected != iTargetRow)
2453       {
2454          UINT i;
2455          if(infoPtr->dwStyle & TCS_VERTICAL)
2456          {
2457            for (i=0; i < infoPtr->uNumItem; i++)
2458            {
2459              /* move everything in the row of the selected item to the iTargetRow */
2460              TAB_ITEM *item = TAB_GetItem(infoPtr, i);
2461
2462              if (item->rect.left == newselected )
2463                  item->rect.left = iTargetRow;
2464              else
2465              {
2466                if (item->rect.left > newselected)
2467                  item->rect.left-=1;
2468              }
2469            }
2470          }
2471          else
2472          {
2473            for (i=0; i < infoPtr->uNumItem; i++)
2474            {
2475              TAB_ITEM *item = TAB_GetItem(infoPtr, i);
2476
2477              if (item->rect.top == newselected )
2478                  item->rect.top = iTargetRow;
2479              else
2480              {
2481                if (item->rect.top > newselected)
2482                  item->rect.top-=1;
2483              }
2484           }
2485         }
2486         TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
2487       }
2488   }
2489
2490   /*
2491    * Do the trivial cases first.
2492    */
2493   if ( (!infoPtr->needsScrolling) ||
2494        (infoPtr->hwndUpDown==0) || (infoPtr->dwStyle & TCS_VERTICAL))
2495     return;
2496
2497   if (infoPtr->leftmostVisible >= iSelected)
2498   {
2499     infoPtr->leftmostVisible = iSelected;
2500   }
2501   else
2502   {
2503      TAB_ITEM *selected = TAB_GetItem(infoPtr, iSelected);
2504      RECT r;
2505      INT width;
2506      UINT i;
2507
2508      /* Calculate the part of the client area that is visible */
2509      GetClientRect(infoPtr->hwnd, &r);
2510      width = r.right;
2511
2512      GetClientRect(infoPtr->hwndUpDown, &r);
2513      width -= r.right;
2514
2515      if ((selected->rect.right -
2516           selected->rect.left) >= width )
2517      {
2518         /* Special case: width of selected item is greater than visible
2519          * part of control.
2520          */
2521         infoPtr->leftmostVisible = iSelected;
2522      }
2523      else
2524      {
2525         for (i = infoPtr->leftmostVisible; i < infoPtr->uNumItem; i++)
2526         {
2527            if ((selected->rect.right - TAB_GetItem(infoPtr, i)->rect.left) < width)
2528               break;
2529         }
2530         infoPtr->leftmostVisible = i;
2531      }
2532   }
2533
2534   if (infoPtr->leftmostVisible != iOrigLeftmostVisible)
2535     TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
2536
2537   SendMessageW(infoPtr->hwndUpDown, UDM_SETPOS, 0,
2538                MAKELONG(infoPtr->leftmostVisible, 0));
2539 }
2540
2541 /******************************************************************************
2542  * TAB_InvalidateTabArea
2543  *
2544  * This method will invalidate the portion of the control that contains the
2545  * tabs. It is called when the state of the control changes and needs
2546  * to be redisplayed
2547  */
2548 static void TAB_InvalidateTabArea(const TAB_INFO *infoPtr)
2549 {
2550   RECT clientRect, rInvalidate, rAdjClient;
2551   INT lastRow = infoPtr->uNumRows - 1;
2552   RECT rect;
2553
2554   if (lastRow < 0) return;
2555
2556   GetClientRect(infoPtr->hwnd, &clientRect);
2557   rInvalidate = clientRect;
2558   rAdjClient = clientRect;
2559
2560   TAB_AdjustRect(infoPtr, 0, &rAdjClient);
2561
2562   TAB_InternalGetItemRect(infoPtr, infoPtr->uNumItem-1 , &rect, NULL);
2563   if ((infoPtr->dwStyle & TCS_BOTTOM) && (infoPtr->dwStyle & TCS_VERTICAL))
2564   {
2565     rInvalidate.left = rAdjClient.right;
2566     if (infoPtr->uNumRows == 1)
2567       rInvalidate.bottom = clientRect.top + rect.bottom + 2 * SELECTED_TAB_OFFSET;
2568   }
2569   else if(infoPtr->dwStyle & TCS_VERTICAL)
2570   {
2571     rInvalidate.right = rAdjClient.left;
2572     if (infoPtr->uNumRows == 1)
2573       rInvalidate.bottom = clientRect.top + rect.bottom + 2 * SELECTED_TAB_OFFSET;
2574   }
2575   else if (infoPtr->dwStyle & TCS_BOTTOM)
2576   {
2577     rInvalidate.top = rAdjClient.bottom;
2578     if (infoPtr->uNumRows == 1)
2579       rInvalidate.right = clientRect.left + rect.right + 2 * SELECTED_TAB_OFFSET;
2580   }
2581   else 
2582   {
2583     rInvalidate.bottom = rAdjClient.top;
2584     if (infoPtr->uNumRows == 1)
2585       rInvalidate.right = clientRect.left + rect.right + 2 * SELECTED_TAB_OFFSET;
2586   }
2587   
2588   /* Punch out the updown control */
2589   if (infoPtr->needsScrolling && (rInvalidate.right > 0)) {
2590     RECT r;
2591     GetClientRect(infoPtr->hwndUpDown, &r);
2592     if (rInvalidate.right > clientRect.right - r.left)
2593       rInvalidate.right = rInvalidate.right - (r.right - r.left);
2594     else
2595       rInvalidate.right = clientRect.right - r.left;
2596   }
2597
2598   TRACE("invalidate (%s)\n", wine_dbgstr_rect(&rInvalidate));
2599
2600   InvalidateRect(infoPtr->hwnd, &rInvalidate, TRUE);
2601 }
2602
2603 static inline LRESULT TAB_Paint (TAB_INFO *infoPtr, HDC hdcPaint)
2604 {
2605   HDC hdc;
2606   PAINTSTRUCT ps;
2607
2608   if (hdcPaint)
2609     hdc = hdcPaint;
2610   else
2611   {
2612     hdc = BeginPaint (infoPtr->hwnd, &ps);
2613     TRACE("erase %d, rect=(%s)\n", ps.fErase, wine_dbgstr_rect(&ps.rcPaint));
2614   }
2615
2616   TAB_Refresh (infoPtr, hdc);
2617
2618   if (!hdcPaint)
2619     EndPaint (infoPtr->hwnd, &ps);
2620
2621   return 0;
2622 }
2623
2624 static LRESULT
2625 TAB_InsertItemT (TAB_INFO *infoPtr, INT iItem, const TCITEMW *pti, BOOL bUnicode)
2626 {
2627   TAB_ITEM *item;
2628   RECT rect;
2629
2630   GetClientRect (infoPtr->hwnd, &rect);
2631   TRACE("Rect: %p %s\n", infoPtr->hwnd, wine_dbgstr_rect(&rect));
2632
2633   if (iItem < 0) return -1;
2634   if (iItem > infoPtr->uNumItem)
2635     iItem = infoPtr->uNumItem;
2636
2637   TAB_DumpItemExternalT(pti, iItem, bUnicode);
2638
2639
2640   if (infoPtr->uNumItem == 0) {
2641     infoPtr->items = Alloc (TAB_ITEM_SIZE(infoPtr));
2642     infoPtr->uNumItem++;
2643     infoPtr->iSelected = 0;
2644   }
2645   else {
2646     LPBYTE oldItems = (LPBYTE)infoPtr->items;
2647
2648     infoPtr->uNumItem++;
2649     infoPtr->items = Alloc (TAB_ITEM_SIZE(infoPtr) * infoPtr->uNumItem);
2650
2651     /* pre insert copy */
2652     if (iItem > 0) {
2653       memcpy (infoPtr->items, oldItems,
2654               iItem * TAB_ITEM_SIZE(infoPtr));
2655     }
2656
2657     /* post insert copy */
2658     if (iItem < infoPtr->uNumItem - 1) {
2659       memcpy (TAB_GetItem(infoPtr, iItem + 1),
2660               oldItems + iItem * TAB_ITEM_SIZE(infoPtr),
2661               (infoPtr->uNumItem - iItem - 1) * TAB_ITEM_SIZE(infoPtr));
2662
2663     }
2664
2665     if (iItem <= infoPtr->iSelected)
2666       infoPtr->iSelected++;
2667
2668     Free (oldItems);
2669   }
2670
2671   item = TAB_GetItem(infoPtr, iItem);
2672
2673   item->pszText = NULL;
2674
2675   if (pti->mask & TCIF_TEXT)
2676   {
2677     if (bUnicode)
2678       Str_SetPtrW (&item->pszText, pti->pszText);
2679     else
2680       Str_SetPtrAtoW (&item->pszText, (LPSTR)pti->pszText);
2681   }
2682
2683   if (pti->mask & TCIF_IMAGE)
2684     item->iImage = pti->iImage;
2685   else
2686     item->iImage = -1;
2687
2688   if (pti->mask & TCIF_PARAM)
2689     memcpy(item->extra, &pti->lParam, infoPtr->cbInfo);
2690   else
2691     memset(item->extra, 0, infoPtr->cbInfo);
2692   
2693   TAB_SetItemBounds(infoPtr);
2694   if (infoPtr->uNumItem > 1)
2695     TAB_InvalidateTabArea(infoPtr);
2696   else
2697     InvalidateRect(infoPtr->hwnd, NULL, TRUE);
2698
2699   TRACE("[%p]: added item %d %s\n",
2700         infoPtr->hwnd, iItem, debugstr_w(item->pszText));
2701
2702   /* If we haven't set the current focus yet, set it now. */
2703   if (infoPtr->uFocus == -1)
2704     TAB_SetCurFocus(infoPtr, iItem);
2705
2706   return iItem;
2707 }
2708
2709 static LRESULT
2710 TAB_SetItemSize (TAB_INFO *infoPtr, INT cx, INT cy)
2711 {
2712   LONG lResult = 0;
2713   BOOL bNeedPaint = FALSE;
2714
2715   lResult = MAKELONG(infoPtr->tabWidth, infoPtr->tabHeight);
2716
2717   /* UNDOCUMENTED: If requested Width or Height is 0 this means that program wants to use auto size. */
2718   if (infoPtr->dwStyle & TCS_FIXEDWIDTH && (infoPtr->tabWidth != cx))
2719   {
2720     infoPtr->tabWidth = cx;
2721     bNeedPaint = TRUE;
2722   }
2723
2724   if (infoPtr->tabHeight != cy)
2725   {
2726     if ((infoPtr->fHeightSet = (cy != 0)))
2727       infoPtr->tabHeight = cy;
2728
2729     bNeedPaint = TRUE;
2730   }
2731   TRACE("was h=%d,w=%d, now h=%d,w=%d\n",
2732        HIWORD(lResult), LOWORD(lResult),
2733        infoPtr->tabHeight, infoPtr->tabWidth);
2734
2735   if (bNeedPaint)
2736   {
2737     TAB_SetItemBounds(infoPtr);
2738     RedrawWindow(infoPtr->hwnd, NULL, NULL, RDW_ERASE | RDW_INVALIDATE | RDW_UPDATENOW);
2739   }
2740
2741   return lResult;
2742 }
2743
2744 static inline LRESULT TAB_SetMinTabWidth (TAB_INFO *infoPtr, INT cx)
2745 {
2746   INT oldcx = 0;
2747
2748   TRACE("(%p,%d)\n", infoPtr, cx);
2749
2750   if (infoPtr->tabMinWidth < 0)
2751     oldcx = DEFAULT_MIN_TAB_WIDTH;
2752   else
2753     oldcx = infoPtr->tabMinWidth;
2754   infoPtr->tabMinWidth = cx;
2755   TAB_SetItemBounds(infoPtr);
2756   return oldcx;
2757 }
2758
2759 static inline LRESULT 
2760 TAB_HighlightItem (TAB_INFO *infoPtr, INT iItem, BOOL fHighlight)
2761 {
2762   LPDWORD lpState;
2763   DWORD oldState;
2764   RECT r;
2765
2766   TRACE("(%p,%d,%s)\n", infoPtr, iItem, fHighlight ? "true" : "false");
2767
2768   if (iItem < 0 || iItem >= infoPtr->uNumItem)
2769     return FALSE;
2770
2771   lpState = &TAB_GetItem(infoPtr, iItem)->dwState;
2772   oldState = *lpState;
2773
2774   if (fHighlight)
2775     *lpState |= TCIS_HIGHLIGHTED;
2776   else
2777     *lpState &= ~TCIS_HIGHLIGHTED;
2778
2779   if ((oldState != *lpState) && TAB_InternalGetItemRect (infoPtr, iItem, &r, NULL))
2780     InvalidateRect (infoPtr->hwnd, &r, TRUE);
2781
2782   return TRUE;
2783 }
2784
2785 static LRESULT
2786 TAB_SetItemT (TAB_INFO *infoPtr, INT iItem, LPTCITEMW tabItem, BOOL bUnicode)
2787 {
2788   TAB_ITEM *wineItem;
2789
2790   TRACE("(%p,%d,%p,%s)\n", infoPtr, iItem, tabItem, bUnicode ? "true" : "false");
2791
2792   if (iItem < 0 || iItem >= infoPtr->uNumItem)
2793     return FALSE;
2794
2795   TAB_DumpItemExternalT(tabItem, iItem, bUnicode);
2796
2797   wineItem = TAB_GetItem(infoPtr, iItem);
2798
2799   if (tabItem->mask & TCIF_IMAGE)
2800     wineItem->iImage = tabItem->iImage;
2801
2802   if (tabItem->mask & TCIF_PARAM)
2803     memcpy(wineItem->extra, &tabItem->lParam, infoPtr->cbInfo);
2804
2805   if (tabItem->mask & TCIF_RTLREADING)
2806     FIXME("TCIF_RTLREADING\n");
2807
2808   if (tabItem->mask & TCIF_STATE)
2809     wineItem->dwState = (wineItem->dwState & ~tabItem->dwStateMask) |
2810                         ( tabItem->dwState &  tabItem->dwStateMask);
2811
2812   if (tabItem->mask & TCIF_TEXT)
2813   {
2814     Free(wineItem->pszText);
2815     wineItem->pszText = NULL;
2816     if (bUnicode)
2817       Str_SetPtrW(&wineItem->pszText, tabItem->pszText);
2818     else
2819       Str_SetPtrAtoW(&wineItem->pszText, (LPSTR)tabItem->pszText);
2820   }
2821
2822   /* Update and repaint tabs */
2823   TAB_SetItemBounds(infoPtr);
2824   TAB_InvalidateTabArea(infoPtr);
2825
2826   return TRUE;
2827 }
2828
2829 static inline LRESULT TAB_GetItemCount (const TAB_INFO *infoPtr)
2830 {
2831   TRACE("\n");
2832   return infoPtr->uNumItem;
2833 }
2834
2835
2836 static LRESULT
2837 TAB_GetItemT (TAB_INFO *infoPtr, INT iItem, LPTCITEMW tabItem, BOOL bUnicode)
2838 {
2839   TAB_ITEM *wineItem;
2840
2841   TRACE("(%p,%d,%p,%s)\n", infoPtr, iItem, tabItem, bUnicode ? "true" : "false");
2842
2843   if (!tabItem) return FALSE;
2844
2845   if (iItem < 0 || iItem >= infoPtr->uNumItem)
2846   {
2847     /* init requested fields */
2848     if (tabItem->mask & TCIF_IMAGE) tabItem->iImage  = 0;
2849     if (tabItem->mask & TCIF_PARAM) tabItem->lParam  = 0;
2850     if (tabItem->mask & TCIF_STATE) tabItem->dwState = 0;
2851     return FALSE;
2852   }
2853
2854   wineItem = TAB_GetItem(infoPtr, iItem);
2855
2856   if (tabItem->mask & TCIF_IMAGE)
2857     tabItem->iImage = wineItem->iImage;
2858
2859   if (tabItem->mask & TCIF_PARAM)
2860     memcpy(&tabItem->lParam, wineItem->extra, infoPtr->cbInfo);
2861
2862   if (tabItem->mask & TCIF_RTLREADING)
2863     FIXME("TCIF_RTLREADING\n");
2864
2865   if (tabItem->mask & TCIF_STATE)
2866     tabItem->dwState = wineItem->dwState & tabItem->dwStateMask;
2867
2868   if (tabItem->mask & TCIF_TEXT)
2869   {
2870     if (bUnicode)
2871       Str_GetPtrW (wineItem->pszText, tabItem->pszText, tabItem->cchTextMax);
2872     else
2873       Str_GetPtrWtoA (wineItem->pszText, (LPSTR)tabItem->pszText, tabItem->cchTextMax);
2874   }
2875
2876   TAB_DumpItemExternalT(tabItem, iItem, bUnicode);
2877
2878   return TRUE;
2879 }
2880
2881
2882 static LRESULT TAB_DeleteItem (TAB_INFO *infoPtr, INT iItem)
2883 {
2884     BOOL bResult = FALSE;
2885
2886     TRACE("(%p, %d)\n", infoPtr, iItem);
2887
2888     if ((iItem >= 0) && (iItem < infoPtr->uNumItem))
2889     {
2890         TAB_ITEM *item = TAB_GetItem(infoPtr, iItem);
2891         LPBYTE oldItems = (LPBYTE)infoPtr->items;
2892
2893         TAB_InvalidateTabArea(infoPtr);
2894         Free(item->pszText);
2895         infoPtr->uNumItem--;
2896
2897         if (!infoPtr->uNumItem)
2898         {
2899             infoPtr->items = NULL;
2900             if (infoPtr->iHotTracked >= 0)
2901             {
2902                 KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
2903                 infoPtr->iHotTracked = -1;
2904             }
2905         }
2906         else
2907         {
2908             infoPtr->items = Alloc(TAB_ITEM_SIZE(infoPtr) * infoPtr->uNumItem);
2909
2910             if (iItem > 0)
2911                 memcpy(infoPtr->items, oldItems, iItem * TAB_ITEM_SIZE(infoPtr));
2912
2913             if (iItem < infoPtr->uNumItem)
2914                 memcpy(TAB_GetItem(infoPtr, iItem),
2915                        oldItems + (iItem + 1) * TAB_ITEM_SIZE(infoPtr),
2916                        (infoPtr->uNumItem - iItem) * TAB_ITEM_SIZE(infoPtr));
2917
2918             if (iItem <= infoPtr->iHotTracked)
2919             {
2920                 /* When tabs move left/up, the hot track item may change */
2921                 FIXME("Recalc hot track\n");
2922             }
2923         }
2924         Free(oldItems);
2925
2926         /* Readjust the selected index */
2927         if (iItem == infoPtr->iSelected)
2928             infoPtr->iSelected = -1;
2929         else if (iItem < infoPtr->iSelected)
2930             infoPtr->iSelected--;
2931
2932         if (infoPtr->uNumItem == 0)
2933             infoPtr->iSelected = -1;
2934
2935         /* Reposition and repaint tabs */
2936         TAB_SetItemBounds(infoPtr);
2937
2938         bResult = TRUE;
2939     }
2940
2941     return bResult;
2942 }
2943
2944 static inline LRESULT TAB_DeleteAllItems (TAB_INFO *infoPtr)
2945 {
2946     TRACE("(%p)\n", infoPtr);
2947     while (infoPtr->uNumItem)
2948       TAB_DeleteItem (infoPtr, 0);
2949     return TRUE;
2950 }
2951
2952
2953 static inline LRESULT TAB_GetFont (const TAB_INFO *infoPtr)
2954 {
2955   TRACE("(%p) returning %p\n", infoPtr, infoPtr->hFont);
2956   return (LRESULT)infoPtr->hFont;
2957 }
2958
2959 static inline LRESULT TAB_SetFont (TAB_INFO *infoPtr, HFONT hNewFont)
2960 {
2961   TRACE("(%p,%p)\n", infoPtr, hNewFont);
2962
2963   infoPtr->hFont = hNewFont;
2964
2965   TAB_SetItemBounds(infoPtr);
2966
2967   TAB_InvalidateTabArea(infoPtr);
2968
2969   return 0;
2970 }
2971
2972
2973 static inline LRESULT TAB_GetImageList (const TAB_INFO *infoPtr)
2974 {
2975   TRACE("\n");
2976   return (LRESULT)infoPtr->himl;
2977 }
2978
2979 static inline LRESULT TAB_SetImageList (TAB_INFO *infoPtr, HIMAGELIST himlNew)
2980 {
2981     HIMAGELIST himlPrev = infoPtr->himl;
2982     TRACE("himl=%p\n", himlNew);
2983     infoPtr->himl = himlNew;
2984     TAB_SetItemBounds(infoPtr);
2985     InvalidateRect(infoPtr->hwnd, NULL, TRUE);
2986     return (LRESULT)himlPrev;
2987 }
2988
2989 static inline LRESULT TAB_GetUnicodeFormat (const TAB_INFO *infoPtr)
2990 {
2991     TRACE("(%p)\n", infoPtr);
2992     return infoPtr->bUnicode;
2993 }
2994
2995 static inline LRESULT TAB_SetUnicodeFormat (TAB_INFO *infoPtr, BOOL bUnicode)
2996 {
2997     BOOL bTemp = infoPtr->bUnicode;
2998
2999     TRACE("(%p %d)\n", infoPtr, bUnicode);
3000     infoPtr->bUnicode = bUnicode;
3001
3002     return bTemp;
3003 }
3004
3005 static inline LRESULT TAB_Size (TAB_INFO *infoPtr)
3006 {
3007 /* I'm not really sure what the following code was meant to do.
3008    This is what it is doing:
3009    When WM_SIZE is sent with SIZE_RESTORED, the control
3010    gets positioned in the top left corner.
3011
3012   RECT parent_rect;
3013   HWND parent;
3014   UINT uPosFlags,cx,cy;
3015
3016   uPosFlags=0;
3017   if (!wParam) {
3018     parent = GetParent (hwnd);
3019     GetClientRect(parent, &parent_rect);
3020     cx=LOWORD (lParam);
3021     cy=HIWORD (lParam);
3022     if (GetWindowLongW(hwnd, GWL_STYLE) & CCS_NORESIZE)
3023         uPosFlags |= (SWP_NOSIZE | SWP_NOMOVE);
3024
3025     SetWindowPos (hwnd, 0, parent_rect.left, parent_rect.top,
3026             cx, cy, uPosFlags | SWP_NOZORDER);
3027   } else {
3028     FIXME("WM_SIZE flag %x %lx not handled\n", wParam, lParam);
3029   } */
3030
3031   /* Recompute the size/position of the tabs. */
3032   TAB_SetItemBounds (infoPtr);
3033
3034   /* Force a repaint of the control. */
3035   InvalidateRect(infoPtr->hwnd, NULL, TRUE);
3036
3037   return 0;
3038 }
3039
3040
3041 static LRESULT TAB_Create (HWND hwnd, LPARAM lParam)
3042 {
3043   TAB_INFO *infoPtr;
3044   TEXTMETRICW fontMetrics;
3045   HDC hdc;
3046   HFONT hOldFont;
3047   DWORD dwStyle;
3048
3049   infoPtr = Alloc (sizeof(TAB_INFO));
3050
3051   SetWindowLongPtrW(hwnd, 0, (DWORD_PTR)infoPtr);
3052
3053   infoPtr->hwnd            = hwnd;
3054   infoPtr->hwndNotify      = ((LPCREATESTRUCTW)lParam)->hwndParent;
3055   infoPtr->uNumItem        = 0;
3056   infoPtr->uNumRows        = 0;
3057   infoPtr->uHItemPadding   = 6;
3058   infoPtr->uVItemPadding   = 3;
3059   infoPtr->uHItemPadding_s = 6;
3060   infoPtr->uVItemPadding_s = 3;
3061   infoPtr->hFont           = 0;
3062   infoPtr->items           = 0;
3063   infoPtr->hcurArrow       = LoadCursorW (0, (LPWSTR)IDC_ARROW);
3064   infoPtr->iSelected       = -1;
3065   infoPtr->iHotTracked     = -1;
3066   infoPtr->uFocus          = -1;
3067   infoPtr->hwndToolTip     = 0;
3068   infoPtr->DoRedraw        = TRUE;
3069   infoPtr->needsScrolling  = FALSE;
3070   infoPtr->hwndUpDown      = 0;
3071   infoPtr->leftmostVisible = 0;
3072   infoPtr->fHeightSet      = FALSE;
3073   infoPtr->bUnicode        = IsWindowUnicode (hwnd);
3074   infoPtr->cbInfo          = sizeof(LPARAM);
3075
3076   TRACE("Created tab control, hwnd [%p]\n", hwnd);
3077
3078   /* The tab control always has the WS_CLIPSIBLINGS style. Even
3079      if you don't specify it in CreateWindow. This is necessary in
3080      order for paint to work correctly. This follows windows behaviour. */
3081   dwStyle = GetWindowLongW(hwnd, GWL_STYLE);
3082   SetWindowLongW(hwnd, GWL_STYLE, dwStyle|WS_CLIPSIBLINGS);
3083
3084   infoPtr->dwStyle = dwStyle | WS_CLIPSIBLINGS;
3085   infoPtr->exStyle = (dwStyle & TCS_FLATBUTTONS) ? TCS_EX_FLATSEPARATORS : 0;
3086
3087   if (infoPtr->dwStyle & TCS_TOOLTIPS) {
3088     /* Create tooltip control */
3089     infoPtr->hwndToolTip =
3090       CreateWindowExW (0, TOOLTIPS_CLASSW, NULL, WS_POPUP,
3091                        CW_USEDEFAULT, CW_USEDEFAULT,
3092                        CW_USEDEFAULT, CW_USEDEFAULT,
3093                        hwnd, 0, 0, 0);
3094
3095     /* Send NM_TOOLTIPSCREATED notification */
3096     if (infoPtr->hwndToolTip) {
3097       NMTOOLTIPSCREATED nmttc;
3098
3099       nmttc.hdr.hwndFrom = hwnd;
3100       nmttc.hdr.idFrom = GetWindowLongPtrW(hwnd, GWLP_ID);
3101       nmttc.hdr.code = NM_TOOLTIPSCREATED;
3102       nmttc.hwndToolTips = infoPtr->hwndToolTip;
3103
3104       SendMessageW (infoPtr->hwndNotify, WM_NOTIFY,
3105                     GetWindowLongPtrW(hwnd, GWLP_ID), (LPARAM)&nmttc);
3106     }
3107   }
3108
3109   OpenThemeData (infoPtr->hwnd, themeClass);
3110   
3111   /*
3112    * We need to get text information so we need a DC and we need to select
3113    * a font.
3114    */
3115   hdc = GetDC(hwnd);
3116   hOldFont = SelectObject (hdc, GetStockObject (SYSTEM_FONT));
3117
3118   /* Use the system font to determine the initial height of a tab. */
3119   GetTextMetricsW(hdc, &fontMetrics);
3120
3121   /*
3122    * Make sure there is enough space for the letters + growing the
3123    * selected item + extra space for the selected item.
3124    */
3125   infoPtr->tabHeight = fontMetrics.tmHeight + SELECTED_TAB_OFFSET +
3126                        ((infoPtr->dwStyle & TCS_BUTTONS) ? 2 : 1) *
3127                         infoPtr->uVItemPadding;
3128
3129   /* Initialize the width of a tab. */
3130   if (infoPtr->dwStyle & TCS_FIXEDWIDTH)
3131     infoPtr->tabWidth = GetDeviceCaps(hdc, LOGPIXELSX);
3132
3133   infoPtr->tabMinWidth = -1;
3134
3135   TRACE("tabH=%d, tabW=%d\n", infoPtr->tabHeight, infoPtr->tabWidth);
3136
3137   SelectObject (hdc, hOldFont);
3138   ReleaseDC(hwnd, hdc);
3139
3140   return 0;
3141 }
3142
3143 static LRESULT
3144 TAB_Destroy (TAB_INFO *infoPtr)
3145 {
3146   UINT iItem;
3147
3148   SetWindowLongPtrW(infoPtr->hwnd, 0, 0);
3149
3150   if (infoPtr->items) {
3151     for (iItem = 0; iItem < infoPtr->uNumItem; iItem++) {
3152       Free (TAB_GetItem(infoPtr, iItem)->pszText);
3153     }
3154     Free (infoPtr->items);
3155   }
3156
3157   if (infoPtr->hwndToolTip)
3158     DestroyWindow (infoPtr->hwndToolTip);
3159
3160   if (infoPtr->hwndUpDown)
3161     DestroyWindow(infoPtr->hwndUpDown);
3162
3163   if (infoPtr->iHotTracked >= 0)
3164     KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
3165
3166   CloseThemeData (GetWindowTheme (infoPtr->hwnd));
3167
3168   Free (infoPtr);
3169   return 0;
3170 }
3171
3172 /* update theme after a WM_THEMECHANGED message */
3173 static LRESULT theme_changed(const TAB_INFO *infoPtr)
3174 {
3175     HTHEME theme = GetWindowTheme (infoPtr->hwnd);
3176     CloseThemeData (theme);
3177     OpenThemeData (infoPtr->hwnd, themeClass);
3178     return 0;
3179 }
3180
3181 static LRESULT TAB_NCCalcSize(WPARAM wParam)
3182 {
3183   if (!wParam)
3184     return 0;
3185   return WVR_ALIGNTOP;
3186 }
3187
3188 static inline LRESULT
3189 TAB_SetItemExtra (TAB_INFO *infoPtr, INT cbInfo)
3190 {
3191   TRACE("(%p %d)\n", infoPtr, cbInfo);
3192
3193   if (cbInfo <= 0)
3194     return FALSE;
3195
3196   if (infoPtr->uNumItem)
3197   {
3198     /* FIXME: MSDN says this is not allowed, but this hasn't been verified */
3199     return FALSE;
3200   }
3201     
3202   infoPtr->cbInfo = cbInfo;
3203   return TRUE;
3204 }
3205
3206 static LRESULT TAB_RemoveImage (TAB_INFO *infoPtr, INT image)
3207 {
3208   TRACE("%p %d\n", infoPtr, image);
3209
3210   if (ImageList_Remove (infoPtr->himl, image))
3211   {
3212     INT i, *idx;
3213     RECT r;
3214
3215     /* shift indices, repaint items if needed */
3216     for (i = 0; i < infoPtr->uNumItem; i++)
3217     {
3218       idx = &TAB_GetItem(infoPtr, i)->iImage;
3219       if (*idx >= image)
3220       {
3221         if (*idx == image)
3222           *idx = -1;
3223         else
3224           (*idx)--;
3225
3226         /* repaint item */
3227         if (TAB_InternalGetItemRect (infoPtr, i, &r, NULL))
3228           InvalidateRect (infoPtr->hwnd, &r, TRUE);
3229       }
3230     }
3231   }
3232
3233   return 0;
3234 }
3235
3236 static LRESULT
3237 TAB_SetExtendedStyle (TAB_INFO *infoPtr, DWORD exMask, DWORD exStyle)
3238 {
3239   DWORD prevstyle = infoPtr->exStyle;
3240
3241   /* zero mask means all styles */
3242   if (exMask == 0) exMask = ~0;
3243
3244   if (exMask & TCS_EX_REGISTERDROP)
3245   {
3246     FIXME("TCS_EX_REGISTERDROP style unimplemented\n");
3247     exMask  &= ~TCS_EX_REGISTERDROP;
3248     exStyle &= ~TCS_EX_REGISTERDROP;
3249   }
3250
3251   if (exMask & TCS_EX_FLATSEPARATORS)
3252   {
3253     if ((prevstyle ^ exStyle) & TCS_EX_FLATSEPARATORS)
3254     {
3255         infoPtr->exStyle ^= TCS_EX_FLATSEPARATORS;
3256         TAB_InvalidateTabArea(infoPtr);
3257     }
3258   }
3259
3260   return prevstyle;
3261 }
3262
3263 static inline LRESULT
3264 TAB_GetExtendedStyle (const TAB_INFO *infoPtr)
3265 {
3266   return infoPtr->exStyle;
3267 }
3268
3269 static LRESULT
3270 TAB_DeselectAll (TAB_INFO *infoPtr, BOOL excludesel)
3271 {
3272   BOOL paint = FALSE;
3273   INT i, selected = infoPtr->iSelected;
3274
3275   TRACE("(%p, %d)\n", infoPtr, excludesel);
3276
3277   if (!(infoPtr->dwStyle & TCS_BUTTONS))
3278     return 0;
3279
3280   for (i = 0; i < infoPtr->uNumItem; i++)
3281   {
3282     if ((TAB_GetItem(infoPtr, i)->dwState & TCIS_BUTTONPRESSED) &&
3283         (selected != i))
3284     {
3285       TAB_GetItem(infoPtr, i)->dwState &= ~TCIS_BUTTONPRESSED;
3286       paint = TRUE;
3287     }
3288   }
3289
3290   if (!excludesel && (selected != -1))
3291   {
3292     TAB_GetItem(infoPtr, selected)->dwState &= ~TCIS_BUTTONPRESSED;
3293     infoPtr->iSelected = -1;
3294     paint = TRUE;
3295   }
3296
3297   if (paint)
3298     TAB_InvalidateTabArea (infoPtr);
3299
3300   return 0;
3301 }
3302
3303 /***
3304  * DESCRIPTION:
3305  * Processes WM_STYLECHANGED messages.
3306  *
3307  * PARAMETER(S):
3308  * [I] infoPtr : valid pointer to the tab data structure
3309  * [I] wStyleType : window style type (normal or extended)
3310  * [I] lpss : window style information
3311  *
3312  * RETURN:
3313  * Zero
3314  */
3315 static INT TAB_StyleChanged(TAB_INFO *infoPtr, WPARAM wStyleType,
3316                             const STYLESTRUCT *lpss)
3317 {
3318     TRACE("(styletype=%lx, styleOld=0x%08x, styleNew=0x%08x)\n",
3319           wStyleType, lpss->styleOld, lpss->styleNew);
3320
3321     if (wStyleType != GWL_STYLE) return 0;
3322
3323     infoPtr->dwStyle = lpss->styleNew;
3324
3325     TAB_SetItemBounds (infoPtr);
3326     InvalidateRect(infoPtr->hwnd, NULL, TRUE);
3327
3328     return 0;
3329 }
3330
3331 static LRESULT WINAPI
3332 TAB_WindowProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
3333 {
3334     TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
3335
3336     TRACE("hwnd=%p msg=%x wParam=%lx lParam=%lx\n", hwnd, uMsg, wParam, lParam);
3337     if (!infoPtr && (uMsg != WM_CREATE))
3338       return DefWindowProcW (hwnd, uMsg, wParam, lParam);
3339
3340     switch (uMsg)
3341     {
3342     case TCM_GETIMAGELIST:
3343       return TAB_GetImageList (infoPtr);
3344
3345     case TCM_SETIMAGELIST:
3346       return TAB_SetImageList (infoPtr, (HIMAGELIST)lParam);
3347
3348     case TCM_GETITEMCOUNT:
3349       return TAB_GetItemCount (infoPtr);
3350
3351     case TCM_GETITEMA:
3352     case TCM_GETITEMW:
3353       return TAB_GetItemT (infoPtr, (INT)wParam, (LPTCITEMW)lParam, uMsg == TCM_GETITEMW);
3354
3355     case TCM_SETITEMA:
3356     case TCM_SETITEMW:
3357       return TAB_SetItemT (infoPtr, (INT)wParam, (LPTCITEMW)lParam, uMsg == TCM_SETITEMW);
3358
3359     case TCM_DELETEITEM:
3360       return TAB_DeleteItem (infoPtr, (INT)wParam);
3361
3362     case TCM_DELETEALLITEMS:
3363      return TAB_DeleteAllItems (infoPtr);
3364
3365     case TCM_GETITEMRECT:
3366      return TAB_GetItemRect (infoPtr, (INT)wParam, (LPRECT)lParam);
3367
3368     case TCM_GETCURSEL:
3369       return TAB_GetCurSel (infoPtr);
3370
3371     case TCM_HITTEST:
3372       return TAB_HitTest (infoPtr, (LPTCHITTESTINFO)lParam);
3373
3374     case TCM_SETCURSEL:
3375       return TAB_SetCurSel (infoPtr, (INT)wParam);
3376
3377     case TCM_INSERTITEMA:
3378     case TCM_INSERTITEMW:
3379       return TAB_InsertItemT (infoPtr, (INT)wParam, (TCITEMW*)lParam, uMsg == TCM_INSERTITEMW);
3380
3381     case TCM_SETITEMEXTRA:
3382       return TAB_SetItemExtra (infoPtr, (INT)wParam);
3383
3384     case TCM_ADJUSTRECT:
3385       return TAB_AdjustRect (infoPtr, (BOOL)wParam, (LPRECT)lParam);
3386
3387     case TCM_SETITEMSIZE:
3388       return TAB_SetItemSize (infoPtr, (INT)LOWORD(lParam), (INT)HIWORD(lParam));
3389
3390     case TCM_REMOVEIMAGE:
3391       return TAB_RemoveImage (infoPtr, (INT)wParam);
3392
3393     case TCM_SETPADDING:
3394       return TAB_SetPadding (infoPtr, lParam);
3395
3396     case TCM_GETROWCOUNT:
3397       return TAB_GetRowCount(infoPtr);
3398
3399     case TCM_GETUNICODEFORMAT:
3400       return TAB_GetUnicodeFormat (infoPtr);
3401
3402     case TCM_SETUNICODEFORMAT:
3403       return TAB_SetUnicodeFormat (infoPtr, (BOOL)wParam);
3404
3405     case TCM_HIGHLIGHTITEM:
3406       return TAB_HighlightItem (infoPtr, (INT)wParam, (BOOL)LOWORD(lParam));
3407
3408     case TCM_GETTOOLTIPS:
3409       return TAB_GetToolTips (infoPtr);
3410
3411     case TCM_SETTOOLTIPS:
3412       return TAB_SetToolTips (infoPtr, (HWND)wParam);
3413
3414     case TCM_GETCURFOCUS:
3415       return TAB_GetCurFocus (infoPtr);
3416
3417     case TCM_SETCURFOCUS:
3418       return TAB_SetCurFocus (infoPtr, (INT)wParam);
3419
3420     case TCM_SETMINTABWIDTH:
3421       return TAB_SetMinTabWidth(infoPtr, (INT)lParam);
3422
3423     case TCM_DESELECTALL:
3424       return TAB_DeselectAll (infoPtr, (BOOL)wParam);
3425
3426     case TCM_GETEXTENDEDSTYLE:
3427       return TAB_GetExtendedStyle (infoPtr);
3428
3429     case TCM_SETEXTENDEDSTYLE:
3430       return TAB_SetExtendedStyle (infoPtr, wParam, lParam);
3431
3432     case WM_GETFONT:
3433       return TAB_GetFont (infoPtr);
3434
3435     case WM_SETFONT:
3436       return TAB_SetFont (infoPtr, (HFONT)wParam);
3437
3438     case WM_CREATE:
3439       return TAB_Create (hwnd, lParam);
3440
3441     case WM_NCDESTROY:
3442       return TAB_Destroy (infoPtr);
3443
3444     case WM_GETDLGCODE:
3445       return DLGC_WANTARROWS | DLGC_WANTCHARS;
3446
3447     case WM_LBUTTONDOWN:
3448       return TAB_LButtonDown (infoPtr, wParam, lParam);
3449
3450     case WM_LBUTTONUP:
3451       return TAB_LButtonUp (infoPtr);
3452
3453     case WM_NOTIFY:
3454       return SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, wParam, lParam);
3455
3456     case WM_RBUTTONDOWN:
3457       return TAB_RButtonDown (infoPtr);
3458
3459     case WM_MOUSEMOVE:
3460       return TAB_MouseMove (infoPtr, wParam, lParam);
3461
3462     case WM_PRINTCLIENT:
3463     case WM_PAINT:
3464       return TAB_Paint (infoPtr, (HDC)wParam);
3465
3466     case WM_SIZE:
3467       return TAB_Size (infoPtr);
3468
3469     case WM_SETREDRAW:
3470       return TAB_SetRedraw (infoPtr, (BOOL)wParam);
3471
3472     case WM_HSCROLL:
3473       return TAB_OnHScroll(infoPtr, (int)LOWORD(wParam), (int)HIWORD(wParam));
3474
3475     case WM_STYLECHANGED:
3476       return TAB_StyleChanged(infoPtr, wParam, (LPSTYLESTRUCT)lParam);
3477
3478     case WM_SYSCOLORCHANGE:
3479       COMCTL32_RefreshSysColors();
3480       return 0;
3481
3482     case WM_THEMECHANGED:
3483       return theme_changed (infoPtr);
3484
3485     case WM_KILLFOCUS:
3486       TAB_KillFocus(infoPtr);
3487     case WM_SETFOCUS:
3488       TAB_FocusChanging(infoPtr);
3489       break;   /* Don't disturb normal focus behavior */
3490
3491     case WM_KEYDOWN:
3492       return TAB_KeyDown(infoPtr, wParam, lParam);
3493
3494     case WM_NCHITTEST:
3495       return TAB_NCHitTest(infoPtr, lParam);
3496
3497     case WM_NCCALCSIZE:
3498       return TAB_NCCalcSize(wParam);
3499
3500     default:
3501       if (uMsg >= WM_USER && uMsg < WM_APP && !COMCTL32_IsReflectedMessage(uMsg))
3502         WARN("unknown msg %04x wp=%08lx lp=%08lx\n",
3503              uMsg, wParam, lParam);
3504       break;
3505     }
3506     return DefWindowProcW(hwnd, uMsg, wParam, lParam);
3507 }
3508
3509
3510 void
3511 TAB_Register (void)
3512 {
3513   WNDCLASSW wndClass;
3514
3515   ZeroMemory (&wndClass, sizeof(WNDCLASSW));
3516   wndClass.style         = CS_GLOBALCLASS | CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
3517   wndClass.lpfnWndProc   = TAB_WindowProc;
3518   wndClass.cbClsExtra    = 0;
3519   wndClass.cbWndExtra    = sizeof(TAB_INFO *);
3520   wndClass.hCursor       = LoadCursorW (0, (LPWSTR)IDC_ARROW);
3521   wndClass.hbrBackground = (HBRUSH)(COLOR_BTNFACE+1);
3522   wndClass.lpszClassName = WC_TABCONTROLW;
3523
3524   RegisterClassW (&wndClass);
3525 }
3526
3527
3528 void
3529 TAB_Unregister (void)
3530 {
3531     UnregisterClassW (WC_TABCONTROLW, NULL);
3532 }