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