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