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