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