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