Fix segmentation fault caused by incorrect referencing of client audio
[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) + infoPtr->uVItemPadding)) / 2;
1731     }
1732     else
1733     {
1734       drawRect->left += center_offset_h;
1735       center_offset_v = ((drawRect->bottom - drawRect->top) - ((rcText.bottom - rcText.top) + infoPtr->uVItemPadding)) / 2;
1736     }
1737
1738     if (center_offset_v < 0)
1739       center_offset_v = 0;
1740
1741     if(lStyle & TCS_VERTICAL)
1742       drawRect->left += center_offset_v;
1743     else
1744       drawRect->top += center_offset_v;
1745
1746     /* Draw the text */
1747     if(lStyle & TCS_VERTICAL) /* if we are vertical rotate the text and each character */
1748     {
1749       static const WCHAR ArialW[] = { 'A','r','i','a','l',0 };
1750       LOGFONTW logfont;
1751       HFONT hFont = 0;
1752       INT nEscapement = 900;
1753       INT nOrientation = 900;
1754
1755       if(lStyle & TCS_BOTTOM)
1756       {
1757         nEscapement = -900;
1758         nOrientation = -900;
1759       }
1760
1761       /* to get a font with the escapement and orientation we are looking for, we need to */
1762       /* call CreateFontIndirectA, which requires us to set the values of the logfont we pass in */
1763       if (!GetObjectW((infoPtr->hFont) ?
1764                 infoPtr->hFont : GetStockObject(SYSTEM_FONT),
1765                 sizeof(LOGFONTW),&logfont))
1766       {
1767         INT iPointSize = 9;
1768
1769         lstrcpyW(logfont.lfFaceName, ArialW);
1770         logfont.lfHeight = -MulDiv(iPointSize, GetDeviceCaps(hdc, LOGPIXELSY),
1771                                     72);
1772         logfont.lfWeight = FW_NORMAL;
1773         logfont.lfItalic = 0;
1774         logfont.lfUnderline = 0;
1775         logfont.lfStrikeOut = 0;
1776       }
1777
1778       logfont.lfEscapement = nEscapement;
1779       logfont.lfOrientation = nOrientation;
1780       hFont = CreateFontIndirectW(&logfont);
1781       SelectObject(hdc, hFont);
1782
1783       if (item->pszText)
1784       {
1785         ExtTextOutW(hdc,
1786         (lStyle & TCS_BOTTOM) ? drawRect->right : drawRect->left,
1787         (!(lStyle & TCS_BOTTOM)) ? drawRect->bottom : drawRect->top,
1788         ETO_CLIPPED,
1789         drawRect,
1790         item->pszText,
1791         lstrlenW(item->pszText),
1792         0);
1793       }
1794
1795       DeleteObject(hFont);
1796     }
1797     else
1798     {
1799       TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%ld,%ld)-(%ld,%ld), textlen=%ld\n",
1800           debugstr_w(item->pszText), center_offset_h, center_offset_v,
1801           drawRect->left, drawRect->top, drawRect->right, drawRect->bottom,
1802           (rcText.right-rcText.left));
1803       if (item->pszText)
1804       {
1805         DrawTextW
1806         (
1807           hdc,
1808           item->pszText,
1809           lstrlenW(item->pszText),
1810           drawRect,
1811           DT_LEFT | DT_SINGLELINE
1812         );
1813       }
1814     }
1815
1816     *drawRect = rcTemp; /* restore drawRect */
1817   }
1818
1819   /*
1820   * Cleanup
1821   */
1822   SelectObject(hdc, hOldFont);
1823   SetBkMode(hdc, oldBkMode);
1824   SelectObject(hdc, holdPen);
1825   DeleteObject( htextPen );
1826 }
1827
1828 /******************************************************************************
1829  * TAB_DrawItem
1830  *
1831  * This method is used to draw a single tab into the tab control.
1832  */
1833 static void TAB_DrawItem(
1834   TAB_INFO *infoPtr,
1835   HDC  hdc,
1836   INT  iItem)
1837 {
1838   LONG      lStyle  = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1839   RECT      itemRect;
1840   RECT      selectedRect;
1841   BOOL      isVisible;
1842   RECT      r, fillRect, r1;
1843   INT       clRight = 0;
1844   INT       clBottom = 0;
1845   COLORREF  bkgnd, corner;
1846
1847   /*
1848    * Get the rectangle for the item.
1849    */
1850   isVisible = TAB_InternalGetItemRect(infoPtr,
1851                                       iItem,
1852                                       &itemRect,
1853                                       &selectedRect);
1854
1855   if (isVisible)
1856   {
1857     RECT rUD, rC;
1858
1859     /* Clip UpDown control to not draw over it */
1860     if (infoPtr->needsScrolling)
1861     {
1862       GetWindowRect(infoPtr->hwnd, &rC);
1863       GetWindowRect(infoPtr->hwndUpDown, &rUD);
1864       ExcludeClipRect(hdc, rUD.left - rC.left, rUD.top - rC.top, rUD.right - rC.left, rUD.bottom - rC.top);
1865     }
1866
1867     /* If you need to see what the control is doing,
1868      * then override these variables. They will change what
1869      * fill colors are used for filling the tabs, and the
1870      * corners when drawing the edge.
1871      */
1872     bkgnd = comctl32_color.clrBtnFace;
1873     corner = comctl32_color.clrBtnFace;
1874
1875     if (lStyle & TCS_BUTTONS)
1876     {
1877       /* Get item rectangle */
1878       r = itemRect;
1879
1880       /* Separators between flat buttons */
1881       if (lStyle & TCS_FLATBUTTONS)
1882       {
1883         r1 = r;
1884         r1.right += (FLAT_BTN_SPACINGX -2);
1885         DrawEdge(hdc, &r1, EDGE_ETCHED, BF_RIGHT);
1886       }
1887
1888       if (iItem == infoPtr->iSelected)
1889       {
1890         DrawEdge(hdc, &r, EDGE_SUNKEN, BF_SOFT|BF_RECT);
1891         
1892         OffsetRect(&r, 1, 1);
1893       }
1894       else  /* ! selected */
1895       {
1896         if (!(lStyle & TCS_FLATBUTTONS))
1897           DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_RECT);
1898       }
1899     }
1900     else /* !TCS_BUTTONS */
1901     {
1902       /* We draw a rectangle of different sizes depending on the selection
1903        * state. */
1904       if (iItem == infoPtr->iSelected) {
1905         RECT rect;
1906         GetClientRect (infoPtr->hwnd, &rect);
1907         clRight = rect.right;
1908         clBottom = rect.bottom;
1909         r = selectedRect;
1910       }
1911       else
1912         r = itemRect;
1913
1914       /*
1915        * Erase the background. (Delay it but setup rectangle.)
1916        * This is necessary when drawing the selected item since it is larger
1917        * than the others, it might overlap with stuff already drawn by the
1918        * other tabs
1919        */
1920       fillRect = r;
1921
1922       if(lStyle & TCS_VERTICAL)
1923       {
1924         /* These are for adjusting the drawing of a Selected tab      */
1925         /* The initial values are for the normal case of non-Selected */
1926         int ZZ = 1;   /* Do not strech if selected */
1927         if (iItem == infoPtr->iSelected) {
1928             ZZ = 0;
1929
1930             /* if leftmost draw the line longer */
1931             if(selectedRect.top == 0)
1932                 fillRect.top += CONTROL_BORDER_SIZEY;
1933             /* if rightmost draw the line longer */
1934             if(selectedRect.bottom == clBottom)
1935                 fillRect.bottom -= CONTROL_BORDER_SIZEY;
1936         }
1937
1938         if (lStyle & TCS_BOTTOM)
1939         {
1940           /* Adjust both rectangles to match native */
1941           r.left += (1-ZZ);
1942
1943           TRACE("<right> item=%d, fill=(%ld,%ld)-(%ld,%ld), edge=(%ld,%ld)-(%ld,%ld)\n",
1944                 iItem,
1945                 fillRect.left,fillRect.top,fillRect.right,fillRect.bottom,
1946                 r.left,r.top,r.right,r.bottom);
1947
1948           /* Clear interior */
1949           SetBkColor(hdc, bkgnd);
1950           ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
1951
1952           /* Draw rectangular edge around tab */
1953           DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_RIGHT|BF_TOP|BF_BOTTOM);
1954
1955           /* Now erase the top corner and draw diagonal edge */
1956           SetBkColor(hdc, corner);
1957           r1.left = r.right - ROUND_CORNER_SIZE - 1;
1958           r1.top = r.top;
1959           r1.right = r.right;
1960           r1.bottom = r1.top + ROUND_CORNER_SIZE;
1961           ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
1962           r1.right--;
1963           DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPLEFT);
1964
1965           /* Now erase the bottom corner and draw diagonal edge */
1966           r1.left = r.right - ROUND_CORNER_SIZE - 1;
1967           r1.bottom = r.bottom;
1968           r1.right = r.right;
1969           r1.top = r1.bottom - ROUND_CORNER_SIZE;
1970           ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
1971           r1.right--;
1972           DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMLEFT);
1973
1974           if ((iItem == infoPtr->iSelected) && (selectedRect.top == 0)) {
1975               r1 = r;
1976               r1.right = r1.left;
1977               r1.left--;
1978               DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_TOP);
1979           }
1980
1981         }
1982         else
1983         {
1984           TRACE("<left> item=%d, fill=(%ld,%ld)-(%ld,%ld), edge=(%ld,%ld)-(%ld,%ld)\n",
1985                 iItem,
1986                 fillRect.left,fillRect.top,fillRect.right,fillRect.bottom,
1987                 r.left,r.top,r.right,r.bottom);
1988
1989           /* Clear interior */
1990           SetBkColor(hdc, bkgnd);
1991           ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
1992
1993           /* Draw rectangular edge around tab */
1994           DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_TOP|BF_BOTTOM);
1995
1996           /* Now erase the top corner and draw diagonal edge */
1997           SetBkColor(hdc, corner);
1998           r1.left = r.left;
1999           r1.top = r.top;
2000           r1.right = r1.left + ROUND_CORNER_SIZE + 1;
2001           r1.bottom = r1.top + ROUND_CORNER_SIZE;
2002           ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2003           r1.left++;
2004           DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPRIGHT);
2005
2006           /* Now erase the bottom corner and draw diagonal edge */
2007           r1.left = r.left;
2008           r1.bottom = r.bottom;
2009           r1.right = r1.left + ROUND_CORNER_SIZE + 1;
2010           r1.top = r1.bottom - ROUND_CORNER_SIZE;
2011           ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2012           r1.left++;
2013           DrawEdge(hdc, &r1, EDGE_SUNKEN, BF_DIAGONAL_ENDTOPLEFT);
2014         }
2015       }
2016       else  /* ! TCS_VERTICAL */
2017       {
2018         /* These are for adjusting the drawing of a Selected tab      */
2019         /* The initial values are for the normal case of non-Selected */
2020         if (iItem == infoPtr->iSelected) {
2021             /* if leftmost draw the line longer */
2022             if(selectedRect.left == 0)
2023                 fillRect.left += CONTROL_BORDER_SIZEX;
2024             /* if rightmost draw the line longer */
2025             if(selectedRect.right == clRight)
2026                 fillRect.right -= CONTROL_BORDER_SIZEX;
2027         }
2028
2029         if (lStyle & TCS_BOTTOM)
2030         {
2031           /* Adjust both rectangles for topmost row */
2032           if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2033           {
2034             fillRect.top -= 2;
2035             r.top -= 1;
2036           }
2037
2038           TRACE("<bottom> item=%d, fill=(%ld,%ld)-(%ld,%ld), edge=(%ld,%ld)-(%ld,%ld)\n",
2039                 iItem,
2040                 fillRect.left,fillRect.top,fillRect.right,fillRect.bottom,
2041                 r.left,r.top,r.right,r.bottom);
2042
2043           /* Clear interior */
2044           SetBkColor(hdc, bkgnd);
2045           ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2046
2047           /* Draw rectangular edge around tab */
2048           DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_BOTTOM|BF_RIGHT);
2049
2050           /* Now erase the righthand corner and draw diagonal edge */
2051           SetBkColor(hdc, corner);
2052           r1.left = r.right - ROUND_CORNER_SIZE;
2053           r1.bottom = r.bottom;
2054           r1.right = r.right;
2055           r1.top = r1.bottom - ROUND_CORNER_SIZE - 1;
2056           ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2057           r1.bottom--;
2058           DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMLEFT);
2059
2060           /* Now erase the lefthand corner and draw diagonal edge */
2061           r1.left = r.left;
2062           r1.bottom = r.bottom;
2063           r1.right = r1.left + ROUND_CORNER_SIZE;
2064           r1.top = r1.bottom - ROUND_CORNER_SIZE - 1;
2065           ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2066           r1.bottom--;
2067           DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPLEFT);
2068
2069           if (iItem == infoPtr->iSelected)
2070           {
2071             r.top += 2;
2072             r.left += 1;
2073             if (selectedRect.left == 0)
2074             {
2075               r1 = r;
2076               r1.bottom = r1.top;
2077               r1.top--;
2078               DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_LEFT);
2079             }
2080           }
2081
2082         }
2083         else
2084         {
2085           /* Adjust both rectangles for bottommost row */
2086           if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2087           {
2088             fillRect.bottom += 3;
2089             r.bottom += 2;
2090           }
2091
2092           TRACE("<top> item=%d, fill=(%ld,%ld)-(%ld,%ld), edge=(%ld,%ld)-(%ld,%ld)\n",
2093                 iItem,
2094                 fillRect.left,fillRect.top,fillRect.right,fillRect.bottom,
2095                 r.left,r.top,r.right,r.bottom);
2096
2097           /* Clear interior */
2098           SetBkColor(hdc, bkgnd);
2099           ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2100
2101           /* Draw rectangular edge around tab */
2102           DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_TOP|BF_RIGHT);
2103
2104           /* Now erase the righthand corner and draw diagonal edge */
2105           SetBkColor(hdc, corner);
2106           r1.left = r.right - ROUND_CORNER_SIZE;
2107           r1.top = r.top;
2108           r1.right = r.right;
2109           r1.bottom = r1.top + ROUND_CORNER_SIZE + 1;
2110           ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2111           r1.top++;
2112           DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMRIGHT);
2113
2114           /* Now erase the lefthand corner and draw diagonal edge */
2115           r1.left = r.left;
2116           r1.top = r.top;
2117           r1.right = r1.left + ROUND_CORNER_SIZE;
2118           r1.bottom = r1.top + ROUND_CORNER_SIZE + 1;
2119           ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2120           r1.top++;
2121           DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPRIGHT);
2122         }
2123       }
2124     }
2125
2126     TAB_DumpItemInternal(infoPtr, iItem);
2127
2128     /* This modifies r to be the text rectangle. */
2129     TAB_DrawItemInterior(infoPtr, hdc, iItem, &r);
2130   }
2131 }
2132
2133 /******************************************************************************
2134  * TAB_DrawBorder
2135  *
2136  * This method is used to draw the raised border around the tab control
2137  * "content" area.
2138  */
2139 static void TAB_DrawBorder (TAB_INFO *infoPtr, HDC hdc)
2140 {
2141   RECT rect;
2142   DWORD lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
2143
2144   GetClientRect (infoPtr->hwnd, &rect);
2145
2146   /*
2147    * Adjust for the style
2148    */
2149
2150   if (infoPtr->uNumItem)
2151   {
2152     if ((lStyle & TCS_BOTTOM) && !(lStyle & TCS_VERTICAL))
2153       rect.bottom -= infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2154     else if((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
2155       rect.right  -= infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2156     else if(lStyle & TCS_VERTICAL)
2157       rect.left   += infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2158     else /* not TCS_VERTICAL and not TCS_BOTTOM */
2159       rect.top    += infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2160   }
2161
2162   TRACE("border=(%ld,%ld)-(%ld,%ld)\n",
2163         rect.left, rect.top, rect.right, rect.bottom);
2164
2165   DrawEdge(hdc, &rect, EDGE_RAISED, BF_SOFT|BF_RECT);
2166 }
2167
2168 /******************************************************************************
2169  * TAB_Refresh
2170  *
2171  * This method repaints the tab control..
2172  */
2173 static void TAB_Refresh (TAB_INFO *infoPtr, HDC hdc)
2174 {
2175   HFONT hOldFont;
2176   INT i;
2177
2178   if (!infoPtr->DoRedraw)
2179     return;
2180
2181   hOldFont = SelectObject (hdc, infoPtr->hFont);
2182
2183   if (GetWindowLongW(infoPtr->hwnd, GWL_STYLE) & TCS_BUTTONS)
2184   {
2185     for (i = 0; i < infoPtr->uNumItem; i++)
2186       TAB_DrawItem (infoPtr, hdc, i);
2187   }
2188   else
2189   {
2190     /* Draw all the non selected item first */
2191     for (i = 0; i < infoPtr->uNumItem; i++)
2192     {
2193       if (i != infoPtr->iSelected)
2194         TAB_DrawItem (infoPtr, hdc, i);
2195     }
2196
2197     /* Now, draw the border, draw it before the selected item
2198      * since the selected item overwrites part of the border. */
2199     TAB_DrawBorder (infoPtr, hdc);
2200
2201     /* Then, draw the selected item */
2202     TAB_DrawItem (infoPtr, hdc, infoPtr->iSelected);
2203
2204     /* If we haven't set the current focus yet, set it now.
2205      * Only happens when we first paint the tab controls */
2206     if (infoPtr->uFocus == -1)
2207       TAB_SetCurFocus(infoPtr, infoPtr->iSelected);
2208   }
2209
2210   SelectObject (hdc, hOldFont);
2211 }
2212
2213 static inline DWORD TAB_GetRowCount (const TAB_INFO *infoPtr)
2214 {
2215   return infoPtr->uNumRows;
2216 }
2217
2218 static inline LRESULT TAB_SetRedraw (TAB_INFO *infoPtr, BOOL doRedraw)
2219 {
2220   infoPtr->DoRedraw = doRedraw;
2221   return 0;
2222 }
2223
2224 /******************************************************************************
2225  * TAB_EnsureSelectionVisible
2226  *
2227  * This method will make sure that the current selection is completely
2228  * visible by scrolling until it is.
2229  */
2230 static void TAB_EnsureSelectionVisible(
2231   TAB_INFO* infoPtr)
2232 {
2233   INT iSelected = infoPtr->iSelected;
2234   LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
2235   INT iOrigLeftmostVisible = infoPtr->leftmostVisible;
2236
2237   /* set the items row to the bottommost row or topmost row depending on
2238    * style */
2239   if ((infoPtr->uNumRows > 1) && !(lStyle & TCS_BUTTONS))
2240   {
2241       TAB_ITEM *selected = TAB_GetItem(infoPtr, iSelected);
2242       INT newselected;
2243       INT iTargetRow;
2244
2245       if(lStyle & TCS_VERTICAL)
2246         newselected = selected->rect.left;
2247       else
2248         newselected = selected->rect.top;
2249
2250       /* the target row is always (number of rows - 1)
2251          as row 0 is furthest from the clientRect */
2252       iTargetRow = infoPtr->uNumRows - 1;
2253
2254       if (newselected != iTargetRow)
2255       {
2256          UINT i;
2257          if(lStyle & TCS_VERTICAL)
2258          {
2259            for (i=0; i < infoPtr->uNumItem; i++)
2260            {
2261              /* move everything in the row of the selected item to the iTargetRow */
2262              TAB_ITEM *item = TAB_GetItem(infoPtr, i);
2263
2264              if (item->rect.left == newselected )
2265                  item->rect.left = iTargetRow;
2266              else
2267              {
2268                if (item->rect.left > newselected)
2269                  item->rect.left-=1;
2270              }
2271            }
2272          }
2273          else
2274          {
2275            for (i=0; i < infoPtr->uNumItem; i++)
2276            {
2277              TAB_ITEM *item = TAB_GetItem(infoPtr, i);
2278
2279              if (item->rect.top == newselected )
2280                  item->rect.top = iTargetRow;
2281              else
2282              {
2283                if (item->rect.top > newselected)
2284                  item->rect.top-=1;
2285              }
2286           }
2287         }
2288         TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
2289       }
2290   }
2291
2292   /*
2293    * Do the trivial cases first.
2294    */
2295   if ( (!infoPtr->needsScrolling) ||
2296        (infoPtr->hwndUpDown==0) || (lStyle & TCS_VERTICAL))
2297     return;
2298
2299   if (infoPtr->leftmostVisible >= iSelected)
2300   {
2301     infoPtr->leftmostVisible = iSelected;
2302   }
2303   else
2304   {
2305      TAB_ITEM *selected = TAB_GetItem(infoPtr, iSelected);
2306      RECT r;
2307      INT width;
2308      UINT i;
2309
2310      /* Calculate the part of the client area that is visible */
2311      GetClientRect(infoPtr->hwnd, &r);
2312      width = r.right;
2313
2314      GetClientRect(infoPtr->hwndUpDown, &r);
2315      width -= r.right;
2316
2317      if ((selected->rect.right -
2318           selected->rect.left) >= width )
2319      {
2320         /* Special case: width of selected item is greater than visible
2321          * part of control.
2322          */
2323         infoPtr->leftmostVisible = iSelected;
2324      }
2325      else
2326      {
2327         for (i = infoPtr->leftmostVisible; i < infoPtr->uNumItem; i++)
2328         {
2329            if ((selected->rect.right - TAB_GetItem(infoPtr, i)->rect.left) < width)
2330               break;
2331         }
2332         infoPtr->leftmostVisible = i;
2333      }
2334   }
2335
2336   if (infoPtr->leftmostVisible != iOrigLeftmostVisible)
2337     TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
2338
2339   SendMessageW(infoPtr->hwndUpDown, UDM_SETPOS, 0,
2340                MAKELONG(infoPtr->leftmostVisible, 0));
2341 }
2342
2343 /******************************************************************************
2344  * TAB_InvalidateTabArea
2345  *
2346  * This method will invalidate the portion of the control that contains the
2347  * tabs. It is called when the state of the control changes and needs
2348  * to be redisplayed
2349  */
2350 static void TAB_InvalidateTabArea(TAB_INFO* infoPtr)
2351 {
2352   RECT clientRect, rInvalidate, rAdjClient;
2353   DWORD lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
2354   INT lastRow = infoPtr->uNumRows - 1;
2355   RECT rect;
2356
2357   if (lastRow < 0) return;
2358
2359   GetClientRect(infoPtr->hwnd, &clientRect);
2360   rInvalidate = clientRect;
2361   rAdjClient = clientRect;
2362
2363   TAB_AdjustRect(infoPtr, 0, &rAdjClient);
2364
2365   TAB_InternalGetItemRect(infoPtr, infoPtr->uNumItem-1 , &rect, NULL);
2366   if ((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
2367   {
2368     rInvalidate.left = rAdjClient.right;
2369     if (infoPtr->uNumRows == 1)
2370       rInvalidate.bottom = clientRect.top + rect.bottom + 2 * SELECTED_TAB_OFFSET;
2371   }
2372   else if(lStyle & TCS_VERTICAL)
2373   {
2374     rInvalidate.right = rAdjClient.left;
2375     if (infoPtr->uNumRows == 1)
2376       rInvalidate.bottom = clientRect.top + rect.bottom + 2 * SELECTED_TAB_OFFSET;
2377   }
2378   else if (lStyle & TCS_BOTTOM)
2379   {
2380     rInvalidate.top = rAdjClient.bottom;
2381     if (infoPtr->uNumRows == 1)
2382       rInvalidate.right = clientRect.left + rect.right + 2 * SELECTED_TAB_OFFSET;
2383   }
2384   else 
2385   {
2386     rInvalidate.bottom = rAdjClient.top;
2387     if (infoPtr->uNumRows == 1)
2388       rInvalidate.right = clientRect.left + rect.right + 2 * SELECTED_TAB_OFFSET;
2389   }
2390   
2391   /* Punch out the updown control */
2392   if (infoPtr->needsScrolling && (rInvalidate.right > 0)) {
2393     RECT r;
2394     GetClientRect(infoPtr->hwndUpDown, &r);
2395     if (rInvalidate.right > clientRect.right - r.left)
2396       rInvalidate.right = rInvalidate.right - (r.right - r.left);
2397     else
2398       rInvalidate.right = clientRect.right - r.left;
2399   }
2400   
2401   TRACE("invalidate (%ld,%ld)-(%ld,%ld)\n",
2402         rInvalidate.left,  rInvalidate.top,
2403         rInvalidate.right, rInvalidate.bottom);
2404  
2405   InvalidateRect(infoPtr->hwnd, &rInvalidate, TRUE);
2406 }
2407
2408 static inline LRESULT TAB_Paint (TAB_INFO *infoPtr, HDC hdcPaint)
2409 {
2410   HDC hdc;
2411   PAINTSTRUCT ps;
2412
2413   if (hdcPaint)
2414     hdc = hdcPaint;
2415   else
2416   {
2417     hdc = BeginPaint (infoPtr->hwnd, &ps);
2418     TRACE("erase %d, rect=(%ld,%ld)-(%ld,%ld)\n",
2419          ps.fErase,
2420          ps.rcPaint.left,ps.rcPaint.top,ps.rcPaint.right,ps.rcPaint.bottom);
2421   }
2422
2423   TAB_Refresh (infoPtr, hdc);
2424
2425   if (!hdcPaint)
2426     EndPaint (infoPtr->hwnd, &ps);
2427
2428   return 0;
2429 }
2430
2431 static LRESULT
2432 TAB_InsertItemT (TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam, BOOL bUnicode)
2433 {
2434   TAB_ITEM *item;
2435   TCITEMW *pti;
2436   INT iItem;
2437   RECT rect;
2438
2439   GetClientRect (infoPtr->hwnd, &rect);
2440   TRACE("Rect: %p T %li, L %li, B %li, R %li\n", infoPtr->hwnd,
2441         rect.top, rect.left, rect.bottom, rect.right);
2442
2443   pti = (TCITEMW *)lParam;
2444   iItem = (INT)wParam;
2445
2446   if (iItem < 0) return -1;
2447   if (iItem > infoPtr->uNumItem)
2448     iItem = infoPtr->uNumItem;
2449
2450   TAB_DumpItemExternalT(pti, iItem, bUnicode);
2451
2452
2453   if (infoPtr->uNumItem == 0) {
2454     infoPtr->items = Alloc (TAB_ITEM_SIZE(infoPtr));
2455     infoPtr->uNumItem++;
2456     infoPtr->iSelected = 0;
2457   }
2458   else {
2459     LPBYTE oldItems = (LPBYTE)infoPtr->items;
2460
2461     infoPtr->uNumItem++;
2462     infoPtr->items = Alloc (TAB_ITEM_SIZE(infoPtr) * infoPtr->uNumItem);
2463
2464     /* pre insert copy */
2465     if (iItem > 0) {
2466       memcpy (infoPtr->items, oldItems,
2467               iItem * TAB_ITEM_SIZE(infoPtr));
2468     }
2469
2470     /* post insert copy */
2471     if (iItem < infoPtr->uNumItem - 1) {
2472       memcpy (TAB_GetItem(infoPtr, iItem + 1),
2473               oldItems + iItem * TAB_ITEM_SIZE(infoPtr),
2474               (infoPtr->uNumItem - iItem - 1) * TAB_ITEM_SIZE(infoPtr));
2475
2476     }
2477
2478     if (iItem <= infoPtr->iSelected)
2479       infoPtr->iSelected++;
2480
2481     Free (oldItems);
2482   }
2483
2484   item = TAB_GetItem(infoPtr, iItem);
2485
2486   item->mask = pti->mask;
2487   item->pszText = NULL;
2488
2489   if (pti->mask & TCIF_TEXT)
2490   {
2491     if (bUnicode)
2492       Str_SetPtrW (&item->pszText, pti->pszText);
2493     else
2494       Str_SetPtrAtoW (&item->pszText, (LPSTR)pti->pszText);
2495   }
2496
2497   if (pti->mask & TCIF_IMAGE)
2498     item->iImage = pti->iImage;
2499   else
2500     item->iImage = -1;
2501
2502   if (pti->mask & TCIF_PARAM)
2503     memcpy(item->extra, &pti->lParam, infoPtr->cbInfo);
2504   else
2505     memset(item->extra, 0, infoPtr->cbInfo);
2506   
2507   TAB_SetItemBounds(infoPtr);
2508   if (infoPtr->uNumItem > 1)
2509     TAB_InvalidateTabArea(infoPtr);
2510   else
2511     InvalidateRect(infoPtr->hwnd, NULL, TRUE);
2512
2513   TRACE("[%p]: added item %d %s\n",
2514         infoPtr->hwnd, iItem, debugstr_w(item->pszText));
2515
2516   return iItem;
2517 }
2518
2519 static LRESULT
2520 TAB_SetItemSize (TAB_INFO *infoPtr, LPARAM lParam)
2521 {
2522   LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
2523   LONG lResult = 0;
2524   BOOL bNeedPaint = FALSE;
2525
2526   lResult = MAKELONG(infoPtr->tabWidth, infoPtr->tabHeight);
2527
2528   /* UNDOCUMENTED: If requested Width or Height is 0 this means that program wants to use auto size. */
2529   if (lStyle & TCS_FIXEDWIDTH && (infoPtr->tabWidth != (INT)LOWORD(lParam)))
2530   {
2531     infoPtr->tabWidth = max((INT)LOWORD(lParam), infoPtr->tabMinWidth);
2532     bNeedPaint = TRUE;
2533   }
2534
2535   if (infoPtr->tabHeight != (INT)HIWORD(lParam))
2536   {
2537     if ((infoPtr->fHeightSet = ((INT)HIWORD(lParam) != 0)))
2538       infoPtr->tabHeight = (INT)HIWORD(lParam);
2539
2540     bNeedPaint = TRUE;
2541   }
2542   TRACE("was h=%d,w=%d, now h=%d,w=%d\n",
2543        HIWORD(lResult), LOWORD(lResult),
2544        infoPtr->tabHeight, infoPtr->tabWidth);
2545
2546   if (bNeedPaint)
2547   {
2548     TAB_SetItemBounds(infoPtr);
2549     RedrawWindow(infoPtr->hwnd, NULL, NULL, RDW_ERASE | RDW_INVALIDATE | RDW_UPDATENOW);
2550   }
2551
2552   return lResult;
2553 }
2554
2555 static inline LRESULT TAB_SetMinTabWidth (TAB_INFO *infoPtr, INT cx)
2556 {
2557   INT oldcx = 0;
2558
2559   TRACE("(%p,%d)\n", infoPtr, cx);
2560
2561   if (infoPtr) {
2562     oldcx = infoPtr->tabMinWidth;
2563     infoPtr->tabMinWidth = (cx==-1)?DEFAULT_TAB_WIDTH:cx;
2564   }
2565
2566   return oldcx;
2567 }
2568
2569 static inline LRESULT 
2570 TAB_HighlightItem (TAB_INFO *infoPtr, INT iItem, BOOL fHighlight)
2571 {
2572   LPDWORD lpState;
2573
2574   TRACE("(%p,%d,%s)\n", infoPtr, iItem, fHighlight ? "true" : "false");
2575
2576   if (!infoPtr || iItem < 0 || iItem >= infoPtr->uNumItem)
2577     return FALSE;
2578   
2579   lpState = &TAB_GetItem(infoPtr, iItem)->dwState;
2580
2581   if (fHighlight)
2582     *lpState |= TCIS_HIGHLIGHTED;
2583   else
2584     *lpState &= ~TCIS_HIGHLIGHTED;
2585
2586   return TRUE;
2587 }
2588
2589 static LRESULT
2590 TAB_SetItemT (TAB_INFO *infoPtr, INT iItem, LPTCITEMW tabItem, BOOL bUnicode)
2591 {
2592   TAB_ITEM *wineItem;
2593
2594   TRACE("(%p,%d,%p,%s)\n", infoPtr, iItem, tabItem, bUnicode ? "true" : "false");
2595
2596   if (iItem < 0 || iItem >= infoPtr->uNumItem)
2597     return FALSE;
2598
2599   TAB_DumpItemExternalT(tabItem, iItem, bUnicode);
2600
2601   wineItem = TAB_GetItem(infoPtr, iItem);
2602
2603   if (tabItem->mask & TCIF_IMAGE)
2604     wineItem->iImage = tabItem->iImage;
2605
2606   if (tabItem->mask & TCIF_PARAM)
2607     memcpy(wineItem->extra, &tabItem->lParam, infoPtr->cbInfo);
2608
2609   if (tabItem->mask & TCIF_RTLREADING)
2610     FIXME("TCIF_RTLREADING\n");
2611
2612   if (tabItem->mask & TCIF_STATE)
2613     wineItem->dwState = tabItem->dwState;
2614
2615   if (tabItem->mask & TCIF_TEXT)
2616   {
2617     if (wineItem->pszText)
2618     {
2619       Free(wineItem->pszText);
2620       wineItem->pszText = NULL;
2621     }
2622     if (bUnicode)
2623       Str_SetPtrW(&wineItem->pszText, tabItem->pszText);
2624     else
2625       Str_SetPtrAtoW(&wineItem->pszText, (LPSTR)tabItem->pszText);
2626   }
2627
2628   /* Update and repaint tabs */
2629   TAB_SetItemBounds(infoPtr);
2630   TAB_InvalidateTabArea(infoPtr);
2631
2632   return TRUE;
2633 }
2634
2635 static inline LRESULT TAB_GetItemCount (const TAB_INFO *infoPtr)
2636 {
2637    return infoPtr->uNumItem;
2638 }
2639
2640
2641 static LRESULT
2642 TAB_GetItemT (TAB_INFO *infoPtr, INT iItem, LPTCITEMW tabItem, BOOL bUnicode)
2643 {
2644   TAB_ITEM *wineItem;
2645
2646   TRACE("(%p,%d,%p,%s)\n", infoPtr, iItem, tabItem, bUnicode ? "true" : "false");
2647
2648   if (iItem < 0 || iItem >= infoPtr->uNumItem)
2649     return FALSE;
2650
2651   wineItem = TAB_GetItem(infoPtr, iItem);
2652
2653   if (tabItem->mask & TCIF_IMAGE)
2654     tabItem->iImage = wineItem->iImage;
2655
2656   if (tabItem->mask & TCIF_PARAM)
2657     memcpy(&tabItem->lParam, wineItem->extra, infoPtr->cbInfo);
2658
2659   if (tabItem->mask & TCIF_RTLREADING)
2660     FIXME("TCIF_RTLREADING\n");
2661
2662   if (tabItem->mask & TCIF_STATE)
2663     tabItem->dwState = wineItem->dwState;
2664
2665   if (tabItem->mask & TCIF_TEXT)
2666   {
2667     if (bUnicode)
2668       Str_GetPtrW (wineItem->pszText, tabItem->pszText, tabItem->cchTextMax);
2669     else
2670       Str_GetPtrWtoA (wineItem->pszText, (LPSTR)tabItem->pszText, tabItem->cchTextMax);
2671   }
2672
2673   TAB_DumpItemExternalT(tabItem, iItem, bUnicode);
2674
2675   return TRUE;
2676 }
2677
2678
2679 static LRESULT TAB_DeleteItem (TAB_INFO *infoPtr, INT iItem)
2680 {
2681     BOOL bResult = FALSE;
2682
2683     TRACE("(%p, %d)\n", infoPtr, iItem);
2684
2685     if ((iItem >= 0) && (iItem < infoPtr->uNumItem))
2686     {
2687         TAB_ITEM *item = TAB_GetItem(infoPtr, iItem);
2688         LPBYTE oldItems = (LPBYTE)infoPtr->items;
2689         
2690         TAB_InvalidateTabArea(infoPtr);
2691
2692         if ((item->mask & TCIF_TEXT) && item->pszText)
2693             Free(item->pszText);
2694
2695         infoPtr->uNumItem--;
2696
2697         if (!infoPtr->uNumItem)
2698         {
2699             infoPtr->items = NULL;
2700             if (infoPtr->iHotTracked >= 0)
2701             {
2702                 KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
2703                 infoPtr->iHotTracked = -1;
2704             }
2705         }
2706         else
2707         {
2708             infoPtr->items = Alloc(TAB_ITEM_SIZE(infoPtr) * infoPtr->uNumItem);
2709
2710             if (iItem > 0)
2711                 memcpy(infoPtr->items, oldItems, iItem * TAB_ITEM_SIZE(infoPtr));
2712
2713             if (iItem < infoPtr->uNumItem)
2714                 memcpy(TAB_GetItem(infoPtr, iItem),
2715                        oldItems + (iItem + 1) * TAB_ITEM_SIZE(infoPtr),
2716                        (infoPtr->uNumItem - iItem) * TAB_ITEM_SIZE(infoPtr));
2717
2718             if (iItem <= infoPtr->iHotTracked)
2719             {
2720                 /* When tabs move left/up, the hot track item may change */
2721                 FIXME("Recalc hot track");
2722             }
2723         }
2724         Free(oldItems);
2725
2726         /* Readjust the selected index */
2727         if ((iItem == infoPtr->iSelected) && (iItem > 0))
2728             infoPtr->iSelected--;
2729
2730         if (iItem < infoPtr->iSelected)
2731             infoPtr->iSelected--;
2732
2733         if (infoPtr->uNumItem == 0)
2734             infoPtr->iSelected = -1;
2735
2736         /* Reposition and repaint tabs */
2737         TAB_SetItemBounds(infoPtr);
2738
2739         bResult = TRUE;
2740     }
2741
2742     return bResult;
2743 }
2744
2745 static inline LRESULT TAB_DeleteAllItems (TAB_INFO *infoPtr)
2746 {
2747     TRACE("(%p)\n", infoPtr);
2748     while (infoPtr->uNumItem)
2749       TAB_DeleteItem (infoPtr, 0);
2750     return TRUE;
2751 }
2752
2753
2754 static inline LRESULT TAB_GetFont (const TAB_INFO *infoPtr)
2755 {
2756   TRACE("(%p) returning %p\n", infoPtr, infoPtr->hFont);
2757   return (LRESULT)infoPtr->hFont;
2758 }
2759
2760 static inline LRESULT TAB_SetFont (TAB_INFO *infoPtr, HFONT hNewFont)
2761 {
2762   TRACE("(%p,%p)\n", infoPtr, hNewFont);
2763
2764   infoPtr->hFont = hNewFont;
2765
2766   TAB_SetItemBounds(infoPtr);
2767
2768   TAB_InvalidateTabArea(infoPtr);
2769
2770   return 0;
2771 }
2772
2773
2774 static inline LRESULT TAB_GetImageList (const TAB_INFO *infoPtr)
2775 {
2776   TRACE("\n");
2777   return (LRESULT)infoPtr->himl;
2778 }
2779
2780 static inline LRESULT TAB_SetImageList (TAB_INFO *infoPtr, HIMAGELIST himlNew)
2781 {
2782     HIMAGELIST himlPrev = infoPtr->himl;
2783     TRACE("\n");
2784     infoPtr->himl = himlNew;
2785     return (LRESULT)himlPrev;
2786 }
2787
2788 static inline LRESULT TAB_GetUnicodeFormat (const TAB_INFO *infoPtr)
2789 {
2790     return infoPtr->bUnicode;
2791 }
2792
2793 static inline LRESULT TAB_SetUnicodeFormat (TAB_INFO *infoPtr, BOOL bUnicode)
2794 {
2795     BOOL bTemp = infoPtr->bUnicode;
2796
2797     infoPtr->bUnicode = bUnicode;
2798
2799     return bTemp;
2800 }
2801
2802 static inline LRESULT TAB_Size (TAB_INFO *infoPtr)
2803 {
2804 /* I'm not really sure what the following code was meant to do.
2805    This is what it is doing:
2806    When WM_SIZE is sent with SIZE_RESTORED, the control
2807    gets positioned in the top left corner.
2808
2809   RECT parent_rect;
2810   HWND parent;
2811   UINT uPosFlags,cx,cy;
2812
2813   uPosFlags=0;
2814   if (!wParam) {
2815     parent = GetParent (hwnd);
2816     GetClientRect(parent, &parent_rect);
2817     cx=LOWORD (lParam);
2818     cy=HIWORD (lParam);
2819     if (GetWindowLongW(hwnd, GWL_STYLE) & CCS_NORESIZE)
2820         uPosFlags |= (SWP_NOSIZE | SWP_NOMOVE);
2821
2822     SetWindowPos (hwnd, 0, parent_rect.left, parent_rect.top,
2823             cx, cy, uPosFlags | SWP_NOZORDER);
2824   } else {
2825     FIXME("WM_SIZE flag %x %lx not handled\n", wParam, lParam);
2826   } */
2827
2828   /* Recompute the size/position of the tabs. */
2829   TAB_SetItemBounds (infoPtr);
2830
2831   /* Force a repaint of the control. */
2832   InvalidateRect(infoPtr->hwnd, NULL, TRUE);
2833
2834   return 0;
2835 }
2836
2837
2838 static LRESULT TAB_Create (HWND hwnd, WPARAM wParam, LPARAM lParam)
2839 {
2840   TAB_INFO *infoPtr;
2841   TEXTMETRICW fontMetrics;
2842   HDC hdc;
2843   HFONT hOldFont;
2844   DWORD dwStyle;
2845
2846   infoPtr = (TAB_INFO *)Alloc (sizeof(TAB_INFO));
2847
2848   SetWindowLongPtrW(hwnd, 0, (DWORD_PTR)infoPtr);
2849
2850   infoPtr->hwnd            = hwnd;
2851   infoPtr->hwndNotify      = ((LPCREATESTRUCTW)lParam)->hwndParent;
2852   infoPtr->uNumItem        = 0;
2853   infoPtr->uNumRows        = 0;
2854   infoPtr->uHItemPadding   = 6;
2855   infoPtr->uVItemPadding   = 3;
2856   infoPtr->uHItemPadding_s = 6;
2857   infoPtr->uVItemPadding_s = 3;
2858   infoPtr->hFont           = 0;
2859   infoPtr->items           = 0;
2860   infoPtr->hcurArrow       = LoadCursorW (0, (LPWSTR)IDC_ARROW);
2861   infoPtr->iSelected       = -1;
2862   infoPtr->iHotTracked     = -1;
2863   infoPtr->uFocus          = -1;
2864   infoPtr->hwndToolTip     = 0;
2865   infoPtr->DoRedraw        = TRUE;
2866   infoPtr->needsScrolling  = FALSE;
2867   infoPtr->hwndUpDown      = 0;
2868   infoPtr->leftmostVisible = 0;
2869   infoPtr->fHeightSet      = FALSE;
2870   infoPtr->bUnicode        = IsWindowUnicode (hwnd);
2871   infoPtr->cbInfo          = sizeof(LPARAM);
2872
2873   TRACE("Created tab control, hwnd [%p]\n", hwnd);
2874
2875   /* The tab control always has the WS_CLIPSIBLINGS style. Even
2876      if you don't specify it in CreateWindow. This is necessary in
2877      order for paint to work correctly. This follows windows behaviour. */
2878   dwStyle = GetWindowLongW(hwnd, GWL_STYLE);
2879   SetWindowLongW(hwnd, GWL_STYLE, dwStyle|WS_CLIPSIBLINGS);
2880
2881   if (dwStyle & TCS_TOOLTIPS) {
2882     /* Create tooltip control */
2883     infoPtr->hwndToolTip =
2884       CreateWindowExW (0, TOOLTIPS_CLASSW, NULL, 0,
2885                        CW_USEDEFAULT, CW_USEDEFAULT,
2886                        CW_USEDEFAULT, CW_USEDEFAULT,
2887                        hwnd, 0, 0, 0);
2888
2889     /* Send NM_TOOLTIPSCREATED notification */
2890     if (infoPtr->hwndToolTip) {
2891       NMTOOLTIPSCREATED nmttc;
2892
2893       nmttc.hdr.hwndFrom = hwnd;
2894       nmttc.hdr.idFrom = GetWindowLongPtrW(hwnd, GWLP_ID);
2895       nmttc.hdr.code = NM_TOOLTIPSCREATED;
2896       nmttc.hwndToolTips = infoPtr->hwndToolTip;
2897
2898       SendMessageW (infoPtr->hwndNotify, WM_NOTIFY,
2899                     (WPARAM)GetWindowLongPtrW(hwnd, GWLP_ID), (LPARAM)&nmttc);
2900     }
2901   }
2902
2903   /*
2904    * We need to get text information so we need a DC and we need to select
2905    * a font.
2906    */
2907   hdc = GetDC(hwnd);
2908   hOldFont = SelectObject (hdc, GetStockObject (SYSTEM_FONT));
2909
2910   /* Use the system font to determine the initial height of a tab. */
2911   GetTextMetricsW(hdc, &fontMetrics);
2912
2913   /*
2914    * Make sure there is enough space for the letters + growing the
2915    * selected item + extra space for the selected item.
2916    */
2917   infoPtr->tabHeight = fontMetrics.tmHeight + SELECTED_TAB_OFFSET +
2918                        ((dwStyle & TCS_BUTTONS) ? 2 : 1) *
2919                         infoPtr->uVItemPadding;
2920
2921   /* Initialize the width of a tab. */
2922   infoPtr->tabWidth = DEFAULT_TAB_WIDTH;
2923   infoPtr->tabMinWidth = 0;
2924
2925   TRACE("tabH=%d, tabW=%d\n", infoPtr->tabHeight, infoPtr->tabWidth);
2926
2927   SelectObject (hdc, hOldFont);
2928   ReleaseDC(hwnd, hdc);
2929
2930   return 0;
2931 }
2932
2933 static LRESULT
2934 TAB_Destroy (TAB_INFO *infoPtr)
2935 {
2936   UINT iItem;
2937
2938   if (!infoPtr)
2939       return 0;
2940
2941   SetWindowLongPtrW(infoPtr->hwnd, 0, 0);
2942
2943   if (infoPtr->items) {
2944     for (iItem = 0; iItem < infoPtr->uNumItem; iItem++) {
2945       if (TAB_GetItem(infoPtr, iItem)->pszText)
2946         Free (TAB_GetItem(infoPtr, iItem)->pszText);
2947     }
2948     Free (infoPtr->items);
2949   }
2950
2951   if (infoPtr->hwndToolTip)
2952     DestroyWindow (infoPtr->hwndToolTip);
2953
2954   if (infoPtr->hwndUpDown)
2955     DestroyWindow(infoPtr->hwndUpDown);
2956
2957   if (infoPtr->iHotTracked >= 0)
2958     KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
2959
2960   Free (infoPtr);
2961   return 0;
2962 }
2963
2964 static inline LRESULT
2965 TAB_SetItemExtra (TAB_INFO *infoPtr, INT cbInfo)
2966 {   
2967   if (!infoPtr || cbInfo <= 0)
2968     return FALSE;
2969
2970   if (infoPtr->uNumItem)
2971   {
2972     /* FIXME: MSDN says this is not allowed, but this hasn't been verified */
2973     return FALSE;
2974   }
2975     
2976   infoPtr->cbInfo = cbInfo;
2977   return TRUE;
2978 }
2979
2980 static LRESULT WINAPI
2981 TAB_WindowProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
2982 {
2983     TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2984
2985     TRACE("hwnd=%p msg=%x wParam=%x lParam=%lx\n", hwnd, uMsg, wParam, lParam);
2986     if (!infoPtr && (uMsg != WM_CREATE))
2987       return DefWindowProcW (hwnd, uMsg, wParam, lParam);
2988
2989     switch (uMsg)
2990     {
2991     case TCM_GETIMAGELIST:
2992       return TAB_GetImageList (infoPtr);
2993
2994     case TCM_SETIMAGELIST:
2995       return TAB_SetImageList (infoPtr, (HIMAGELIST)lParam);
2996
2997     case TCM_GETITEMCOUNT:
2998       return TAB_GetItemCount (infoPtr);
2999
3000     case TCM_GETITEMA:
3001     case TCM_GETITEMW:
3002       return TAB_GetItemT (infoPtr, (INT)wParam, (LPTCITEMW)lParam, uMsg == TCM_GETITEMW);
3003
3004     case TCM_SETITEMA:
3005     case TCM_SETITEMW:
3006       return TAB_SetItemT (infoPtr, (INT)wParam, (LPTCITEMW)lParam, uMsg == TCM_SETITEMW);
3007
3008     case TCM_DELETEITEM:
3009       return TAB_DeleteItem (infoPtr, (INT)wParam);
3010
3011     case TCM_DELETEALLITEMS:
3012      return TAB_DeleteAllItems (infoPtr);
3013
3014     case TCM_GETITEMRECT:
3015      return TAB_GetItemRect (infoPtr, wParam, lParam);
3016
3017     case TCM_GETCURSEL:
3018       return TAB_GetCurSel (infoPtr);
3019
3020     case TCM_HITTEST:
3021       return TAB_HitTest (infoPtr, (LPTCHITTESTINFO)lParam);
3022
3023     case TCM_SETCURSEL:
3024       return TAB_SetCurSel (infoPtr, (INT)wParam);
3025
3026     case TCM_INSERTITEMA:
3027     case TCM_INSERTITEMW:
3028       return TAB_InsertItemT (infoPtr, wParam, lParam, uMsg == TCM_INSERTITEMW);
3029
3030     case TCM_SETITEMEXTRA:
3031       return TAB_SetItemExtra (infoPtr, (int)wParam);
3032
3033     case TCM_ADJUSTRECT:
3034       return TAB_AdjustRect (infoPtr, (BOOL)wParam, (LPRECT)lParam);
3035
3036     case TCM_SETITEMSIZE:
3037       return TAB_SetItemSize (infoPtr, lParam);
3038
3039     case TCM_REMOVEIMAGE:
3040       FIXME("Unimplemented msg TCM_REMOVEIMAGE\n");
3041       return 0;
3042
3043     case TCM_SETPADDING:
3044       return TAB_SetPadding (infoPtr, lParam);
3045
3046     case TCM_GETROWCOUNT:
3047       return TAB_GetRowCount(infoPtr);
3048
3049     case TCM_GETUNICODEFORMAT:
3050       return TAB_GetUnicodeFormat (infoPtr);
3051
3052     case TCM_SETUNICODEFORMAT:
3053       return TAB_SetUnicodeFormat (infoPtr, (BOOL)wParam);
3054
3055     case TCM_HIGHLIGHTITEM:
3056       return TAB_HighlightItem (infoPtr, (INT)wParam, (BOOL)LOWORD(lParam));
3057
3058     case TCM_GETTOOLTIPS:
3059       return TAB_GetToolTips (infoPtr);
3060
3061     case TCM_SETTOOLTIPS:
3062       return TAB_SetToolTips (infoPtr, (HWND)wParam);
3063
3064     case TCM_GETCURFOCUS:
3065       return TAB_GetCurFocus (infoPtr);
3066
3067     case TCM_SETCURFOCUS:
3068       return TAB_SetCurFocus (infoPtr, (INT)wParam);
3069
3070     case TCM_SETMINTABWIDTH:
3071       return TAB_SetMinTabWidth(infoPtr, (INT)lParam);
3072
3073     case TCM_DESELECTALL:
3074       FIXME("Unimplemented msg TCM_DESELECTALL\n");
3075       return 0;
3076
3077     case TCM_GETEXTENDEDSTYLE:
3078       FIXME("Unimplemented msg TCM_GETEXTENDEDSTYLE\n");
3079       return 0;
3080
3081     case TCM_SETEXTENDEDSTYLE:
3082       FIXME("Unimplemented msg TCM_SETEXTENDEDSTYLE\n");
3083       return 0;
3084
3085     case WM_GETFONT:
3086       return TAB_GetFont (infoPtr);
3087
3088     case WM_SETFONT:
3089       return TAB_SetFont (infoPtr, (HFONT)wParam);
3090
3091     case WM_CREATE:
3092       return TAB_Create (hwnd, wParam, lParam);
3093
3094     case WM_NCDESTROY:
3095       return TAB_Destroy (infoPtr);
3096
3097     case WM_GETDLGCODE:
3098       return DLGC_WANTARROWS | DLGC_WANTCHARS;
3099
3100     case WM_LBUTTONDOWN:
3101       return TAB_LButtonDown (infoPtr, wParam, lParam);
3102
3103     case WM_LBUTTONUP:
3104       return TAB_LButtonUp (infoPtr);
3105
3106     case WM_NOTIFY:
3107       return SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, wParam, lParam);
3108
3109     case WM_RBUTTONDOWN:
3110       return TAB_RButtonDown (infoPtr);
3111
3112     case WM_MOUSEMOVE:
3113       return TAB_MouseMove (infoPtr, wParam, lParam);
3114
3115     case WM_PAINT:
3116       return TAB_Paint (infoPtr, (HDC)wParam);
3117
3118     case WM_SIZE:
3119       return TAB_Size (infoPtr);
3120
3121     case WM_SETREDRAW:
3122       return TAB_SetRedraw (infoPtr, (BOOL)wParam);
3123
3124     case WM_HSCROLL:
3125       return TAB_OnHScroll(infoPtr, (int)LOWORD(wParam), (int)HIWORD(wParam), (HWND)lParam);
3126
3127     case WM_STYLECHANGED:
3128       TAB_SetItemBounds (infoPtr);
3129       InvalidateRect(hwnd, NULL, TRUE);
3130       return 0;
3131
3132     case WM_SYSCOLORCHANGE:
3133       COMCTL32_RefreshSysColors();
3134       return 0;
3135
3136     case WM_KILLFOCUS:
3137     case WM_SETFOCUS:
3138       TAB_FocusChanging(infoPtr);
3139       break;   /* Don't disturb normal focus behavior */
3140
3141     case WM_KEYUP:
3142       return TAB_KeyUp(infoPtr, wParam);
3143     case WM_NCHITTEST:
3144       return TAB_NCHitTest(infoPtr, lParam);
3145
3146     default:
3147       if (uMsg >= WM_USER && uMsg < WM_APP)
3148         WARN("unknown msg %04x wp=%08x lp=%08lx\n",
3149              uMsg, wParam, lParam);
3150       break;
3151     }
3152     return DefWindowProcW(hwnd, uMsg, wParam, lParam);
3153 }
3154
3155
3156 void
3157 TAB_Register (void)
3158 {
3159   WNDCLASSW wndClass;
3160
3161   ZeroMemory (&wndClass, sizeof(WNDCLASSW));
3162   wndClass.style         = CS_GLOBALCLASS | CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
3163   wndClass.lpfnWndProc   = TAB_WindowProc;
3164   wndClass.cbClsExtra    = 0;
3165   wndClass.cbWndExtra    = sizeof(TAB_INFO *);
3166   wndClass.hCursor       = LoadCursorW (0, (LPWSTR)IDC_ARROW);
3167   wndClass.hbrBackground = (HBRUSH)(COLOR_BTNFACE+1);
3168   wndClass.lpszClassName = WC_TABCONTROLW;
3169
3170   RegisterClassW (&wndClass);
3171 }
3172
3173
3174 void
3175 TAB_Unregister (void)
3176 {
3177     UnregisterClassW (WC_TABCONTROLW, NULL);
3178 }