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