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