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