When including 'wine/port.h', include it first.
[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
1862     /* Draw the focus rectangle */
1863     if (((lStyle & TCS_FOCUSNEVER) == 0) &&
1864          (GetFocus() == hwnd) &&
1865          (iItem == infoPtr->uFocus) )
1866     {
1867       r = itemRect;
1868       InflateRect(&r, -1, -1);
1869
1870       DrawFocusRect(hdc, &r);
1871     }
1872
1873     /* Cleanup */
1874     SelectObject(hdc, holdPen);
1875     if (deleteBrush) DeleteObject(hbr);
1876   }
1877 }
1878
1879 /******************************************************************************
1880  * TAB_DrawBorder
1881  *
1882  * This method is used to draw the raised border around the tab control
1883  * "content" area.
1884  */         
1885 static void TAB_DrawBorder (HWND hwnd, HDC hdc)
1886 {
1887   TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
1888   HPEN htmPen;
1889   HPEN hwPen  = GetSysColorPen (COLOR_3DHILIGHT);
1890   HPEN hbPen  = GetSysColorPen (COLOR_3DDKSHADOW);
1891   HPEN hShade = GetSysColorPen (COLOR_BTNSHADOW);
1892   RECT rect;
1893   DWORD lStyle = GetWindowLongA(hwnd, GWL_STYLE);
1894
1895   GetClientRect (hwnd, &rect);
1896
1897   /*
1898    * Adjust for the style
1899    */
1900
1901   if (infoPtr->uNumItem)
1902   {
1903     if ((lStyle & TCS_BOTTOM) && !(lStyle & TCS_VERTICAL))
1904     {
1905       rect.bottom -= (infoPtr->tabHeight - 2) * infoPtr->uNumRows + 2;
1906     }
1907     else if((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
1908     {
1909       rect.right -= (infoPtr->tabHeight - 2) * infoPtr->uNumRows + 2;
1910     }
1911     else if(lStyle & TCS_VERTICAL)
1912     {
1913       rect.left += (infoPtr->tabHeight - 2) * infoPtr->uNumRows + 2;
1914     }
1915     else /* not TCS_VERTICAL and not TCS_BOTTOM */
1916     {
1917       rect.top += (infoPtr->tabHeight - 2) * infoPtr->uNumRows + 1;
1918     }
1919   }
1920
1921   /*
1922    * Shave-off the right and bottom margins (exluded in the
1923    * rect)
1924    */
1925   rect.right--;
1926   rect.bottom--;
1927
1928   /* highlight */
1929   htmPen = SelectObject (hdc, hwPen);
1930
1931   MoveToEx (hdc, rect.left, rect.bottom, NULL);
1932   LineTo (hdc, rect.left, rect.top);
1933   LineTo (hdc, rect.right, rect.top);
1934
1935   /* Dark Shadow */
1936   SelectObject (hdc, hbPen);
1937   LineTo (hdc, rect.right, rect.bottom );
1938   LineTo (hdc, rect.left, rect.bottom);
1939
1940   /* shade */
1941   SelectObject (hdc, hShade );
1942   MoveToEx (hdc, rect.right - 1, rect.top, NULL);
1943   LineTo   (hdc, rect.right - 1, rect.bottom - 1);
1944   LineTo   (hdc, rect.left,    rect.bottom - 1);
1945
1946   SelectObject(hdc, htmPen);
1947 }
1948
1949 /******************************************************************************
1950  * TAB_Refresh
1951  *
1952  * This method repaints the tab control..
1953  */             
1954 static void TAB_Refresh (HWND hwnd, HDC hdc)
1955 {
1956   TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
1957   HFONT hOldFont;
1958   INT i;
1959
1960   if (!infoPtr->DoRedraw)
1961     return;
1962
1963   hOldFont = SelectObject (hdc, infoPtr->hFont);
1964
1965   if (GetWindowLongA(hwnd, GWL_STYLE) & TCS_BUTTONS)
1966   {
1967     for (i = 0; i < infoPtr->uNumItem; i++) 
1968       TAB_DrawItem (hwnd, hdc, i);
1969   }
1970   else
1971   {
1972     /* Draw all the non selected item first */
1973     for (i = 0; i < infoPtr->uNumItem; i++) 
1974     {
1975       if (i != infoPtr->iSelected)
1976         TAB_DrawItem (hwnd, hdc, i);
1977     }
1978
1979     /* Now, draw the border, draw it before the selected item
1980      * since the selected item overwrites part of the border. */
1981     TAB_DrawBorder (hwnd, hdc);
1982
1983     /* Then, draw the selected item */
1984     TAB_DrawItem (hwnd, hdc, infoPtr->iSelected);
1985
1986     /* If we haven't set the current focus yet, set it now.
1987      * Only happens when we first paint the tab controls */
1988     if (infoPtr->uFocus == -1)
1989       TAB_SetCurFocus(hwnd, infoPtr->iSelected);
1990   }
1991
1992   SelectObject (hdc, hOldFont);
1993 }
1994
1995 static DWORD
1996 TAB_GetRowCount (HWND hwnd )
1997 {
1998   TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
1999
2000   return infoPtr->uNumRows;
2001 }
2002
2003 static LRESULT
2004 TAB_SetRedraw (HWND hwnd, WPARAM wParam)
2005 {
2006     TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2007   
2008   infoPtr->DoRedraw=(BOOL) wParam;
2009   return 0;
2010 }
2011
2012 static LRESULT TAB_EraseBackground(
2013   HWND hwnd, 
2014   HDC  givenDC)
2015 {
2016   HDC  hdc;
2017   RECT clientRect;
2018
2019   HBRUSH brush = CreateSolidBrush(GetSysColor(COLOR_BTNFACE));
2020
2021   hdc = givenDC ? givenDC : GetDC(hwnd);
2022
2023   GetClientRect(hwnd, &clientRect);
2024
2025   FillRect(hdc, &clientRect, brush);
2026
2027   if (givenDC==0)
2028     ReleaseDC(hwnd, hdc);
2029
2030   DeleteObject(brush);
2031
2032   return 0;
2033 }
2034
2035 /******************************************************************************
2036  * TAB_EnsureSelectionVisible
2037  *
2038  * This method will make sure that the current selection is completely
2039  * visible by scrolling until it is.
2040  */
2041 static void TAB_EnsureSelectionVisible(
2042   HWND      hwnd,
2043   TAB_INFO* infoPtr)
2044 {
2045   INT iSelected = infoPtr->iSelected;
2046   LONG lStyle = GetWindowLongA(hwnd, GWL_STYLE);
2047   INT iOrigLeftmostVisible = infoPtr->leftmostVisible;
2048
2049   /* set the items row to the bottommost row or topmost row depending on
2050    * style */
2051   if ((infoPtr->uNumRows > 1) && !(lStyle & TCS_BUTTONS))
2052   {
2053       INT newselected;
2054       INT iTargetRow;
2055
2056       if(lStyle & TCS_VERTICAL)
2057         newselected = infoPtr->items[iSelected].rect.left;
2058       else
2059         newselected = infoPtr->items[iSelected].rect.top;
2060
2061       /* the target row is always (number of rows - 1)
2062          as row 0 is furthest from the clientRect */
2063       iTargetRow = infoPtr->uNumRows - 1;
2064
2065       if (newselected != iTargetRow)
2066       {
2067          INT i;
2068          if(lStyle & TCS_VERTICAL)
2069          {
2070            for (i=0; i < infoPtr->uNumItem; i++)
2071            {
2072              /* move everything in the row of the selected item to the iTargetRow */
2073              if (infoPtr->items[i].rect.left == newselected )
2074                  infoPtr->items[i].rect.left = iTargetRow;
2075              else
2076              {
2077                if (infoPtr->items[i].rect.left > newselected)
2078                  infoPtr->items[i].rect.left-=1;
2079              }
2080            }
2081          }
2082          else
2083          {
2084            for (i=0; i < infoPtr->uNumItem; i++)
2085            {
2086              if (infoPtr->items[i].rect.top == newselected )
2087                  infoPtr->items[i].rect.top = iTargetRow;
2088              else
2089              {
2090                if (infoPtr->items[i].rect.top > newselected)
2091                  infoPtr->items[i].rect.top-=1;
2092              }
2093           }
2094         }
2095         TAB_RecalcHotTrack(hwnd, NULL, NULL, NULL);
2096       }
2097   }
2098
2099   /*
2100    * Do the trivial cases first.
2101    */
2102   if ( (!infoPtr->needsScrolling) ||
2103        (infoPtr->hwndUpDown==0) || (lStyle & TCS_VERTICAL))
2104     return;
2105
2106   if (infoPtr->leftmostVisible >= iSelected)
2107   {
2108     infoPtr->leftmostVisible = iSelected;
2109   }
2110   else
2111   {
2112      RECT r;
2113      INT  width, i;
2114
2115      /* Calculate the part of the client area that is visible */
2116      GetClientRect(hwnd, &r);
2117      width = r.right;
2118
2119      GetClientRect(infoPtr->hwndUpDown, &r);
2120      width -= r.right;
2121
2122      if ((infoPtr->items[iSelected].rect.right -
2123           infoPtr->items[iSelected].rect.left) >= width )
2124      {
2125         /* Special case: width of selected item is greater than visible
2126          * part of control.
2127          */
2128         infoPtr->leftmostVisible = iSelected;
2129      }
2130      else
2131      {
2132         for (i = infoPtr->leftmostVisible; i < infoPtr->uNumItem; i++)
2133         {
2134            if ((infoPtr->items[iSelected].rect.right -
2135                 infoPtr->items[i].rect.left) < width)
2136               break;
2137         }
2138         infoPtr->leftmostVisible = i;
2139      }
2140   }
2141
2142   if (infoPtr->leftmostVisible != iOrigLeftmostVisible)
2143     TAB_RecalcHotTrack(hwnd, NULL, NULL, NULL);
2144
2145   SendMessageA(infoPtr->hwndUpDown, UDM_SETPOS, 0,
2146                MAKELONG(infoPtr->leftmostVisible, 0));
2147 }
2148
2149 /******************************************************************************
2150  * TAB_InvalidateTabArea
2151  *
2152  * This method will invalidate the portion of the control that contains the
2153  * tabs. It is called when the state of the control changes and needs
2154  * to be redisplayed
2155  */
2156 static void TAB_InvalidateTabArea(
2157   HWND      hwnd,
2158   TAB_INFO* infoPtr)
2159 {
2160   RECT clientRect;
2161   DWORD lStyle = GetWindowLongA(hwnd, GWL_STYLE);
2162   INT lastRow = infoPtr->uNumRows - 1;
2163
2164   if (lastRow < 0) return;
2165
2166   GetClientRect(hwnd, &clientRect);
2167
2168   if ((lStyle & TCS_BOTTOM) && !(lStyle & TCS_VERTICAL))
2169   {
2170     clientRect.top = clientRect.bottom -
2171                    infoPtr->tabHeight -
2172                    lastRow * (infoPtr->tabHeight - 2) -
2173                    ((lStyle & TCS_BUTTONS) ? lastRow * BUTTON_SPACINGY : 0) - 2;
2174   }
2175   else if((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
2176   {
2177     clientRect.left = clientRect.right - infoPtr->tabHeight -
2178                       lastRow * (infoPtr->tabHeight - 2) -
2179                       ((lStyle & TCS_BUTTONS) ? lastRow * BUTTON_SPACINGY : 0) - 2;
2180   }
2181   else if(lStyle & TCS_VERTICAL)
2182   {
2183     clientRect.right = clientRect.left + infoPtr->tabHeight +
2184                        lastRow * (infoPtr->tabHeight - 2) -
2185                       ((lStyle & TCS_BUTTONS) ? lastRow * BUTTON_SPACINGY : 0) + 1;
2186
2187   }
2188   else
2189   {
2190     clientRect.bottom = clientRect.top + infoPtr->tabHeight +
2191                       lastRow * (infoPtr->tabHeight - 2) +
2192                       ((lStyle & TCS_BUTTONS) ? lastRow * BUTTON_SPACINGY : 0) + 1;
2193   }
2194
2195   InvalidateRect(hwnd, &clientRect, TRUE);
2196 }
2197
2198 static LRESULT
2199 TAB_Paint (HWND hwnd, WPARAM wParam)
2200 {
2201   HDC hdc;
2202   PAINTSTRUCT ps;
2203     
2204   hdc = wParam== 0 ? BeginPaint (hwnd, &ps) : (HDC)wParam;
2205   TAB_Refresh (hwnd, hdc);
2206     
2207   if(!wParam)
2208     EndPaint (hwnd, &ps);
2209
2210   return 0;
2211 }
2212
2213 static LRESULT
2214 TAB_InsertItemA (HWND hwnd, WPARAM wParam, LPARAM lParam)
2215 {    
2216   TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2217   TCITEMA *pti;
2218   INT iItem;
2219   RECT rect;
2220   
2221   GetClientRect (hwnd, &rect);
2222   TRACE("Rect: %x T %i, L %i, B %i, R %i\n", hwnd,
2223         rect.top, rect.left, rect.bottom, rect.right);  
2224   
2225   pti = (TCITEMA *)lParam;
2226   iItem = (INT)wParam;
2227   
2228   if (iItem < 0) return -1;
2229   if (iItem > infoPtr->uNumItem)
2230     iItem = infoPtr->uNumItem;
2231   
2232   if (infoPtr->uNumItem == 0) {
2233     infoPtr->items = COMCTL32_Alloc (sizeof (TAB_ITEM));
2234     infoPtr->uNumItem++;
2235     infoPtr->iSelected = 0;
2236   }
2237   else {
2238     TAB_ITEM *oldItems = infoPtr->items;
2239     
2240     infoPtr->uNumItem++;
2241     infoPtr->items = COMCTL32_Alloc (sizeof (TAB_ITEM) * infoPtr->uNumItem);
2242     
2243     /* pre insert copy */
2244     if (iItem > 0) {
2245       memcpy (&infoPtr->items[0], &oldItems[0],
2246               iItem * sizeof(TAB_ITEM));
2247     }
2248     
2249     /* post insert copy */
2250     if (iItem < infoPtr->uNumItem - 1) {
2251       memcpy (&infoPtr->items[iItem+1], &oldItems[iItem],
2252               (infoPtr->uNumItem - iItem - 1) * sizeof(TAB_ITEM));
2253       
2254     }
2255
2256     if (iItem <= infoPtr->iSelected)
2257       infoPtr->iSelected++;
2258
2259     COMCTL32_Free (oldItems);
2260   }
2261   
2262   infoPtr->items[iItem].mask = pti->mask;
2263   if (pti->mask & TCIF_TEXT)
2264     Str_SetPtrAtoW (&infoPtr->items[iItem].pszText, pti->pszText);
2265
2266   if (pti->mask & TCIF_IMAGE)
2267     infoPtr->items[iItem].iImage = pti->iImage;
2268   
2269   if (pti->mask & TCIF_PARAM)
2270     infoPtr->items[iItem].lParam = pti->lParam;
2271   
2272   TAB_SetItemBounds(hwnd);
2273   TAB_InvalidateTabArea(hwnd, infoPtr);
2274   
2275   TRACE("[%04x]: added item %d %s\n",
2276         hwnd, iItem, debugstr_w(infoPtr->items[iItem].pszText));
2277
2278   return iItem;
2279 }
2280
2281
2282 static LRESULT
2283 TAB_InsertItemW (HWND hwnd, WPARAM wParam, LPARAM lParam)
2284 {
2285   TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2286   TCITEMW *pti;
2287   INT iItem;
2288   RECT rect;
2289
2290   GetClientRect (hwnd, &rect);
2291   TRACE("Rect: %x T %i, L %i, B %i, R %i\n", hwnd,
2292         rect.top, rect.left, rect.bottom, rect.right);
2293
2294   pti = (TCITEMW *)lParam;
2295   iItem = (INT)wParam;
2296
2297   if (iItem < 0) return -1;
2298   if (iItem > infoPtr->uNumItem)
2299     iItem = infoPtr->uNumItem;
2300
2301   if (infoPtr->uNumItem == 0) {
2302     infoPtr->items = COMCTL32_Alloc (sizeof (TAB_ITEM));
2303     infoPtr->uNumItem++;
2304     infoPtr->iSelected = 0;
2305   }
2306   else {
2307     TAB_ITEM *oldItems = infoPtr->items;
2308
2309     infoPtr->uNumItem++;
2310     infoPtr->items = COMCTL32_Alloc (sizeof (TAB_ITEM) * infoPtr->uNumItem);
2311
2312     /* pre insert copy */
2313     if (iItem > 0) {
2314       memcpy (&infoPtr->items[0], &oldItems[0],
2315               iItem * sizeof(TAB_ITEM));
2316     }
2317
2318     /* post insert copy */
2319     if (iItem < infoPtr->uNumItem - 1) {
2320       memcpy (&infoPtr->items[iItem+1], &oldItems[iItem],
2321               (infoPtr->uNumItem - iItem - 1) * sizeof(TAB_ITEM));
2322
2323   }
2324   
2325     if (iItem <= infoPtr->iSelected)
2326       infoPtr->iSelected++;
2327
2328     COMCTL32_Free (oldItems);
2329   }
2330
2331   infoPtr->items[iItem].mask = pti->mask;
2332   if (pti->mask & TCIF_TEXT)
2333     Str_SetPtrW (&infoPtr->items[iItem].pszText, pti->pszText);
2334
2335   if (pti->mask & TCIF_IMAGE)
2336     infoPtr->items[iItem].iImage = pti->iImage;
2337   
2338   if (pti->mask & TCIF_PARAM)
2339     infoPtr->items[iItem].lParam = pti->lParam;
2340   
2341   TAB_SetItemBounds(hwnd);
2342   TAB_InvalidateTabArea(hwnd, infoPtr);
2343   
2344   TRACE("[%04x]: added item %d %s\n",
2345         hwnd, iItem, debugstr_w(infoPtr->items[iItem].pszText));
2346
2347   return iItem;
2348 }
2349
2350
2351 static LRESULT 
2352 TAB_SetItemSize (HWND hwnd, WPARAM wParam, LPARAM lParam)
2353 {
2354   TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2355   LONG lStyle = GetWindowLongA(hwnd, GWL_STYLE);
2356   LONG lResult = 0;
2357
2358   if ((lStyle & TCS_FIXEDWIDTH) || (lStyle & TCS_OWNERDRAWFIXED))
2359   {
2360     lResult = MAKELONG(infoPtr->tabWidth, infoPtr->tabHeight);
2361     infoPtr->tabWidth = (INT)LOWORD(lParam);
2362     infoPtr->tabHeight = (INT)HIWORD(lParam);
2363   }
2364   infoPtr->fSizeSet = TRUE;
2365
2366   return lResult;
2367 }
2368
2369 static LRESULT 
2370 TAB_SetItemA (HWND hwnd, WPARAM wParam, LPARAM lParam)
2371 {
2372   TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2373   TCITEMA *tabItem; 
2374   TAB_ITEM *wineItem;
2375   INT    iItem;
2376
2377   iItem = (INT)wParam;
2378   tabItem = (LPTCITEMA)lParam;
2379
2380   TRACE("%d %p\n", iItem, tabItem);
2381   if ((iItem<0) || (iItem>=infoPtr->uNumItem)) return FALSE;
2382
2383   wineItem = &infoPtr->items[iItem];
2384
2385   if (tabItem->mask & TCIF_IMAGE)
2386     wineItem->iImage = tabItem->iImage;
2387
2388   if (tabItem->mask & TCIF_PARAM)
2389     wineItem->lParam = tabItem->lParam;
2390
2391   if (tabItem->mask & TCIF_RTLREADING) 
2392     FIXME("TCIF_RTLREADING\n");
2393
2394   if (tabItem->mask & TCIF_STATE) 
2395     wineItem->dwState = tabItem->dwState;
2396
2397   if (tabItem->mask & TCIF_TEXT)
2398    Str_SetPtrAtoW(&wineItem->pszText, tabItem->pszText);
2399
2400   /* Update and repaint tabs */
2401   TAB_SetItemBounds(hwnd);
2402   TAB_InvalidateTabArea(hwnd,infoPtr);
2403
2404   return TRUE;
2405   }
2406
2407
2408 static LRESULT
2409 TAB_SetItemW (HWND hwnd, WPARAM wParam, LPARAM lParam)
2410 {
2411   TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2412   TCITEMW *tabItem;
2413   TAB_ITEM *wineItem;
2414   INT    iItem;
2415
2416   iItem = (INT)wParam;
2417   tabItem = (LPTCITEMW)lParam;
2418
2419   TRACE("%d %p\n", iItem, tabItem);
2420   if ((iItem<0) || (iItem>=infoPtr->uNumItem)) return FALSE;
2421
2422   wineItem = &infoPtr->items[iItem];
2423
2424   if (tabItem->mask & TCIF_IMAGE)
2425     wineItem->iImage = tabItem->iImage;
2426
2427   if (tabItem->mask & TCIF_PARAM)
2428     wineItem->lParam = tabItem->lParam;
2429
2430   if (tabItem->mask & TCIF_RTLREADING)
2431     FIXME("TCIF_RTLREADING\n");
2432
2433   if (tabItem->mask & TCIF_STATE)
2434     wineItem->dwState = tabItem->dwState;
2435
2436   if (tabItem->mask & TCIF_TEXT)
2437    Str_SetPtrW(&wineItem->pszText, tabItem->pszText);
2438
2439   /* Update and repaint tabs */
2440   TAB_SetItemBounds(hwnd);
2441   TAB_InvalidateTabArea(hwnd,infoPtr);
2442
2443   return TRUE;
2444 }
2445
2446
2447 static LRESULT 
2448 TAB_GetItemCount (HWND hwnd, WPARAM wParam, LPARAM lParam)
2449 {
2450    TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2451
2452    return infoPtr->uNumItem;
2453 }
2454
2455
2456 static LRESULT 
2457 TAB_GetItemA (HWND hwnd, WPARAM wParam, LPARAM lParam)
2458 {
2459    TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2460    TCITEMA *tabItem;
2461    TAB_ITEM *wineItem;
2462    INT    iItem;
2463
2464   iItem = (INT)wParam;
2465   tabItem = (LPTCITEMA)lParam;
2466   TRACE("\n");
2467   if ((iItem<0) || (iItem>=infoPtr->uNumItem))
2468     return FALSE;
2469
2470   wineItem = &infoPtr->items[iItem];
2471
2472   if (tabItem->mask & TCIF_IMAGE) 
2473     tabItem->iImage = wineItem->iImage;
2474
2475   if (tabItem->mask & TCIF_PARAM) 
2476     tabItem->lParam = wineItem->lParam;
2477
2478   if (tabItem->mask & TCIF_RTLREADING) 
2479     FIXME("TCIF_RTLREADING\n");
2480
2481   if (tabItem->mask & TCIF_STATE) 
2482     tabItem->dwState = wineItem->dwState;
2483
2484   if (tabItem->mask & TCIF_TEXT) 
2485    Str_GetPtrWtoA (wineItem->pszText, tabItem->pszText, tabItem->cchTextMax);
2486
2487   return TRUE;
2488 }
2489
2490
2491 static LRESULT 
2492 TAB_GetItemW (HWND hwnd, WPARAM wParam, LPARAM lParam)
2493 {
2494   TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2495   TCITEMW *tabItem;
2496   TAB_ITEM *wineItem;
2497   INT    iItem;
2498
2499   iItem = (INT)wParam;
2500   tabItem = (LPTCITEMW)lParam;
2501   TRACE("\n");
2502   if ((iItem<0) || (iItem>=infoPtr->uNumItem))
2503     return FALSE;
2504
2505   wineItem=& infoPtr->items[iItem];
2506
2507   if (tabItem->mask & TCIF_IMAGE)
2508     tabItem->iImage = wineItem->iImage;
2509
2510   if (tabItem->mask & TCIF_PARAM)
2511     tabItem->lParam = wineItem->lParam;
2512
2513   if (tabItem->mask & TCIF_RTLREADING)
2514     FIXME("TCIF_RTLREADING\n");
2515
2516   if (tabItem->mask & TCIF_STATE)
2517     tabItem->dwState = wineItem->dwState;
2518
2519   if (tabItem->mask & TCIF_TEXT)
2520    Str_GetPtrW (wineItem->pszText, tabItem->pszText, tabItem->cchTextMax);
2521
2522   return TRUE;
2523 }
2524
2525
2526 static LRESULT 
2527 TAB_DeleteItem (HWND hwnd, WPARAM wParam, LPARAM lParam)
2528 {
2529   TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2530   INT iItem = (INT) wParam;
2531   BOOL bResult = FALSE;
2532
2533   if ((iItem >= 0) && (iItem < infoPtr->uNumItem))
2534   {
2535     TAB_ITEM *oldItems = infoPtr->items;
2536     
2537     infoPtr->uNumItem--;
2538     infoPtr->items = COMCTL32_Alloc(sizeof (TAB_ITEM) * infoPtr->uNumItem);
2539     
2540     if (iItem > 0) 
2541       memcpy(&infoPtr->items[0], &oldItems[0], iItem * sizeof(TAB_ITEM));
2542     
2543     if (iItem < infoPtr->uNumItem) 
2544       memcpy(&infoPtr->items[iItem], &oldItems[iItem + 1],
2545               (infoPtr->uNumItem - iItem) * sizeof(TAB_ITEM));
2546     
2547     COMCTL32_Free(oldItems);
2548
2549     /* Readjust the selected index */
2550     if ((iItem == infoPtr->iSelected) && (iItem > 0))
2551       infoPtr->iSelected--;
2552       
2553     if (iItem < infoPtr->iSelected)
2554       infoPtr->iSelected--;
2555
2556     if (infoPtr->uNumItem == 0)
2557       infoPtr->iSelected = -1;
2558
2559     /* Reposition and repaint tabs */
2560     TAB_SetItemBounds(hwnd);
2561     TAB_InvalidateTabArea(hwnd,infoPtr);
2562
2563     bResult = TRUE;
2564   }
2565
2566   return bResult;
2567 }
2568
2569 static LRESULT 
2570 TAB_DeleteAllItems (HWND hwnd, WPARAM wParam, LPARAM lParam)
2571 {
2572    TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2573
2574   COMCTL32_Free (infoPtr->items);
2575   infoPtr->uNumItem = 0;
2576   infoPtr->iSelected = -1;
2577   if (infoPtr->iHotTracked >= 0)
2578     KillTimer(hwnd, TAB_HOTTRACK_TIMER);
2579   infoPtr->iHotTracked = -1;
2580  
2581   TAB_SetItemBounds(hwnd);
2582   TAB_InvalidateTabArea(hwnd,infoPtr);
2583   return TRUE;
2584 }
2585
2586
2587 static LRESULT
2588 TAB_GetFont (HWND hwnd, WPARAM wParam, LPARAM lParam)
2589 {
2590   TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2591
2592   TRACE("\n");
2593   return (LRESULT)infoPtr->hFont;
2594 }
2595
2596 static LRESULT
2597 TAB_SetFont (HWND hwnd, WPARAM wParam, LPARAM lParam)
2598
2599 {
2600   TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2601   
2602   TRACE("%x %lx\n",wParam, lParam);
2603   
2604   infoPtr->hFont = (HFONT)wParam;
2605   
2606   TAB_SetItemBounds(hwnd);
2607
2608   TAB_InvalidateTabArea(hwnd, infoPtr);
2609
2610   return 0;
2611 }
2612
2613
2614 static LRESULT
2615 TAB_GetImageList (HWND hwnd, WPARAM wParam, LPARAM lParam)
2616 {
2617   TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2618
2619   TRACE("\n");
2620   return (LRESULT)infoPtr->himl;
2621 }
2622
2623 static LRESULT
2624 TAB_SetImageList (HWND hwnd, WPARAM wParam, LPARAM lParam)
2625 {
2626     TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2627     HIMAGELIST himlPrev;
2628
2629     TRACE("\n");
2630     himlPrev = infoPtr->himl;
2631     infoPtr->himl= (HIMAGELIST)lParam;
2632     return (LRESULT)himlPrev;
2633 }
2634
2635 static LRESULT
2636 TAB_GetUnicodeFormat (HWND hwnd)
2637 {
2638     TAB_INFO *infoPtr = TAB_GetInfoPtr (hwnd);
2639     return infoPtr->bUnicode;
2640 }
2641
2642 static LRESULT
2643 TAB_SetUnicodeFormat (HWND hwnd, WPARAM wParam)
2644 {
2645     TAB_INFO *infoPtr = TAB_GetInfoPtr (hwnd);
2646     BOOL bTemp = infoPtr->bUnicode;
2647
2648     infoPtr->bUnicode = (BOOL)wParam;
2649
2650     return bTemp;
2651 }
2652
2653 static LRESULT
2654 TAB_Size (HWND hwnd, WPARAM wParam, LPARAM lParam)
2655
2656 {
2657 /* I'm not really sure what the following code was meant to do.
2658    This is what it is doing:
2659    When WM_SIZE is sent with SIZE_RESTORED, the control
2660    gets positioned in the top left corner.
2661
2662   RECT parent_rect;
2663   HWND parent;
2664   UINT uPosFlags,cx,cy;
2665
2666   uPosFlags=0;
2667   if (!wParam) {
2668     parent = GetParent (hwnd);
2669     GetClientRect(parent, &parent_rect);
2670     cx=LOWORD (lParam);
2671     cy=HIWORD (lParam);
2672     if (GetWindowLongA(hwnd, GWL_STYLE) & CCS_NORESIZE) 
2673         uPosFlags |= (SWP_NOSIZE | SWP_NOMOVE);
2674
2675     SetWindowPos (hwnd, 0, parent_rect.left, parent_rect.top,
2676             cx, cy, uPosFlags | SWP_NOZORDER);
2677   } else {
2678     FIXME("WM_SIZE flag %x %lx not handled\n", wParam, lParam);
2679   } */
2680
2681   /* Recompute the size/position of the tabs. */
2682   TAB_SetItemBounds (hwnd);
2683
2684   /* Force a repaint of the control. */
2685   InvalidateRect(hwnd, NULL, TRUE);
2686
2687   return 0;
2688 }
2689
2690
2691 static LRESULT 
2692 TAB_Create (HWND hwnd, WPARAM wParam, LPARAM lParam)
2693 {
2694   TAB_INFO *infoPtr;
2695   TEXTMETRICA fontMetrics;
2696   HDC hdc;
2697   HFONT hOldFont;
2698   DWORD dwStyle;
2699
2700   infoPtr = (TAB_INFO *)COMCTL32_Alloc (sizeof(TAB_INFO));
2701
2702   SetWindowLongA(hwnd, 0, (DWORD)infoPtr);
2703    
2704   infoPtr->uNumItem        = 0;
2705   infoPtr->uNumRows        = 0;
2706   infoPtr->hFont           = 0;
2707   infoPtr->items           = 0;
2708   infoPtr->hcurArrow       = LoadCursorA (0, IDC_ARROWA);
2709   infoPtr->iSelected       = -1;
2710   infoPtr->iHotTracked     = -1;
2711   infoPtr->uFocus          = -1;  
2712   infoPtr->hwndToolTip     = 0;
2713   infoPtr->DoRedraw        = TRUE;
2714   infoPtr->needsScrolling  = FALSE;
2715   infoPtr->hwndUpDown      = 0;
2716   infoPtr->leftmostVisible = 0;
2717   infoPtr->fSizeSet        = FALSE;
2718   infoPtr->bUnicode        = IsWindowUnicode (hwnd);
2719   
2720   TRACE("Created tab control, hwnd [%04x]\n", hwnd); 
2721
2722   /* The tab control always has the WS_CLIPSIBLINGS style. Even 
2723      if you don't specify it in CreateWindow. This is necessary in 
2724      order for paint to work correctly. This follows windows behaviour. */
2725   dwStyle = GetWindowLongA(hwnd, GWL_STYLE);
2726   SetWindowLongA(hwnd, GWL_STYLE, dwStyle|WS_CLIPSIBLINGS);
2727
2728   if (dwStyle & TCS_TOOLTIPS) {
2729     /* Create tooltip control */
2730     infoPtr->hwndToolTip =
2731       CreateWindowExA (0, TOOLTIPS_CLASSA, NULL, 0,
2732                        CW_USEDEFAULT, CW_USEDEFAULT,
2733                        CW_USEDEFAULT, CW_USEDEFAULT,
2734                        hwnd, 0, 0, 0);
2735     
2736     /* Send NM_TOOLTIPSCREATED notification */
2737     if (infoPtr->hwndToolTip) {
2738       NMTOOLTIPSCREATED nmttc;
2739       
2740       nmttc.hdr.hwndFrom = hwnd;
2741       nmttc.hdr.idFrom = GetWindowLongA(hwnd, GWL_ID);
2742       nmttc.hdr.code = NM_TOOLTIPSCREATED;
2743       nmttc.hwndToolTips = infoPtr->hwndToolTip;
2744       
2745       SendMessageA (GetParent (hwnd), WM_NOTIFY,
2746                     (WPARAM)GetWindowLongA(hwnd, GWL_ID), (LPARAM)&nmttc);
2747     }
2748   }  
2749     
2750   /*
2751    * We need to get text information so we need a DC and we need to select
2752    * a font.
2753    */
2754   hdc = GetDC(hwnd); 
2755   hOldFont = SelectObject (hdc, GetStockObject (SYSTEM_FONT));
2756
2757   /* Use the system font to determine the initial height of a tab. */
2758   GetTextMetricsA(hdc, &fontMetrics);
2759
2760   /*
2761    * Make sure there is enough space for the letters + growing the 
2762    * selected item + extra space for the selected item.   
2763    */
2764   infoPtr->tabHeight = fontMetrics.tmHeight + 2 * VERTICAL_ITEM_PADDING +
2765                        SELECTED_TAB_OFFSET;
2766
2767   /* Initialize the width of a tab. */
2768   infoPtr->tabWidth = DEFAULT_TAB_WIDTH;
2769
2770   SelectObject (hdc, hOldFont);
2771   ReleaseDC(hwnd, hdc);
2772
2773   return 0;
2774 }
2775
2776 static LRESULT
2777 TAB_Destroy (HWND hwnd, WPARAM wParam, LPARAM lParam)
2778 {
2779   TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2780   INT iItem;
2781
2782   if (!infoPtr)
2783       return 0;
2784
2785   if (infoPtr->items) {
2786     for (iItem = 0; iItem < infoPtr->uNumItem; iItem++) {
2787       if (infoPtr->items[iItem].pszText)
2788         COMCTL32_Free (infoPtr->items[iItem].pszText);
2789     }
2790     COMCTL32_Free (infoPtr->items);
2791   }
2792   
2793   if (infoPtr->hwndToolTip) 
2794     DestroyWindow (infoPtr->hwndToolTip);
2795  
2796   if (infoPtr->hwndUpDown)
2797     DestroyWindow(infoPtr->hwndUpDown);
2798
2799   if (infoPtr->iHotTracked >= 0)
2800     KillTimer(hwnd, TAB_HOTTRACK_TIMER);
2801
2802   COMCTL32_Free (infoPtr);
2803   SetWindowLongA(hwnd, 0, 0);
2804   return 0;
2805 }
2806
2807 static LRESULT WINAPI
2808 TAB_WindowProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
2809 {
2810
2811     TRACE("hwnd=%x msg=%x wParam=%x lParam=%lx\n", hwnd, uMsg, wParam, lParam);
2812     if (!TAB_GetInfoPtr(hwnd) && (uMsg != WM_CREATE))
2813       return DefWindowProcA (hwnd, uMsg, wParam, lParam);
2814
2815     switch (uMsg)
2816     {
2817     case TCM_GETIMAGELIST:
2818       return TAB_GetImageList (hwnd, wParam, lParam);
2819       
2820     case TCM_SETIMAGELIST:
2821       return TAB_SetImageList (hwnd, wParam, lParam);
2822       
2823     case TCM_GETITEMCOUNT:
2824       return TAB_GetItemCount (hwnd, wParam, lParam);
2825       
2826     case TCM_GETITEMA:
2827       return TAB_GetItemA (hwnd, wParam, lParam);
2828       
2829     case TCM_GETITEMW:
2830       return TAB_GetItemW (hwnd, wParam, lParam);
2831       
2832     case TCM_SETITEMA:
2833       return TAB_SetItemA (hwnd, wParam, lParam);
2834       
2835     case TCM_SETITEMW:
2836       return TAB_SetItemW (hwnd, wParam, lParam);
2837       
2838     case TCM_DELETEITEM:
2839       return TAB_DeleteItem (hwnd, wParam, lParam);
2840       
2841     case TCM_DELETEALLITEMS:
2842      return TAB_DeleteAllItems (hwnd, wParam, lParam);
2843      
2844     case TCM_GETITEMRECT:
2845      return TAB_GetItemRect (hwnd, wParam, lParam);
2846       
2847     case TCM_GETCURSEL:
2848       return TAB_GetCurSel (hwnd);
2849       
2850     case TCM_HITTEST:
2851       return TAB_HitTest (hwnd, wParam, lParam);
2852       
2853     case TCM_SETCURSEL:
2854       return TAB_SetCurSel (hwnd, wParam);
2855       
2856     case TCM_INSERTITEMA:
2857       return TAB_InsertItemA (hwnd, wParam, lParam);
2858       
2859     case TCM_INSERTITEMW:
2860       return TAB_InsertItemW (hwnd, wParam, lParam);
2861       
2862     case TCM_SETITEMEXTRA:
2863       FIXME("Unimplemented msg TCM_SETITEMEXTRA\n");
2864       return 0;
2865       
2866     case TCM_ADJUSTRECT:
2867       return TAB_AdjustRect (hwnd, (BOOL)wParam, (LPRECT)lParam);
2868       
2869     case TCM_SETITEMSIZE:
2870       return TAB_SetItemSize (hwnd, wParam, lParam);
2871       
2872     case TCM_REMOVEIMAGE:
2873       FIXME("Unimplemented msg TCM_REMOVEIMAGE\n");
2874       return 0;
2875       
2876     case TCM_SETPADDING:
2877       FIXME("Unimplemented msg TCM_SETPADDING\n");
2878       return 0;
2879       
2880     case TCM_GETROWCOUNT:
2881       return TAB_GetRowCount(hwnd);
2882
2883     case TCM_GETUNICODEFORMAT:
2884       return TAB_GetUnicodeFormat (hwnd);
2885
2886     case TCM_SETUNICODEFORMAT:
2887       return TAB_SetUnicodeFormat (hwnd, wParam);
2888
2889     case TCM_HIGHLIGHTITEM:
2890       FIXME("Unimplemented msg TCM_HIGHLIGHTITEM\n");
2891       return 0;
2892       
2893     case TCM_GETTOOLTIPS:
2894       return TAB_GetToolTips (hwnd, wParam, lParam);
2895       
2896     case TCM_SETTOOLTIPS:
2897       return TAB_SetToolTips (hwnd, wParam, lParam);
2898       
2899     case TCM_GETCURFOCUS:
2900       return TAB_GetCurFocus (hwnd);
2901       
2902     case TCM_SETCURFOCUS:
2903       return TAB_SetCurFocus (hwnd, wParam);
2904       
2905     case TCM_SETMINTABWIDTH:
2906       FIXME("Unimplemented msg TCM_SETMINTABWIDTH\n");
2907       return 0;
2908       
2909     case TCM_DESELECTALL:
2910       FIXME("Unimplemented msg TCM_DESELECTALL\n");
2911       return 0;
2912       
2913     case TCM_GETEXTENDEDSTYLE:
2914       FIXME("Unimplemented msg TCM_GETEXTENDEDSTYLE\n");
2915       return 0;
2916
2917     case TCM_SETEXTENDEDSTYLE:
2918       FIXME("Unimplemented msg TCM_SETEXTENDEDSTYLE\n");
2919       return 0;
2920
2921     case WM_GETFONT:
2922       return TAB_GetFont (hwnd, wParam, lParam);
2923       
2924     case WM_SETFONT:
2925       return TAB_SetFont (hwnd, wParam, lParam);
2926       
2927     case WM_CREATE:
2928       return TAB_Create (hwnd, wParam, lParam);
2929       
2930     case WM_NCDESTROY:
2931       return TAB_Destroy (hwnd, wParam, lParam);
2932       
2933     case WM_GETDLGCODE:
2934       return DLGC_WANTARROWS | DLGC_WANTCHARS;
2935       
2936     case WM_LBUTTONDOWN:
2937       return TAB_LButtonDown (hwnd, wParam, lParam);
2938       
2939     case WM_LBUTTONUP:
2940       return TAB_LButtonUp (hwnd, wParam, lParam);
2941       
2942     case WM_RBUTTONDOWN:
2943       return TAB_RButtonDown (hwnd, wParam, lParam);
2944       
2945     case WM_MOUSEMOVE:
2946       return TAB_MouseMove (hwnd, wParam, lParam);
2947       
2948     case WM_ERASEBKGND:
2949       return TAB_EraseBackground (hwnd, (HDC)wParam);
2950
2951     case WM_PAINT:
2952       return TAB_Paint (hwnd, wParam);
2953
2954     case WM_SIZE:
2955       return TAB_Size (hwnd, wParam, lParam);
2956       
2957     case WM_SETREDRAW:
2958       return TAB_SetRedraw (hwnd, wParam);
2959
2960     case WM_HSCROLL:
2961       return TAB_OnHScroll(hwnd, (int)LOWORD(wParam), (int)HIWORD(wParam), (HWND)lParam);
2962
2963     case WM_STYLECHANGED:
2964       TAB_SetItemBounds (hwnd);
2965       InvalidateRect(hwnd, NULL, TRUE);
2966       return 0;
2967       
2968     case WM_KILLFOCUS:
2969     case WM_SETFOCUS:
2970       return TAB_FocusChanging(hwnd, uMsg, wParam, lParam);
2971
2972     case WM_KEYUP:
2973       return TAB_KeyUp(hwnd, wParam);
2974     case WM_NCHITTEST:
2975       return TAB_NCHitTest(hwnd, lParam);
2976
2977     default:
2978       if (uMsg >= WM_USER)
2979         WARN("unknown msg %04x wp=%08x lp=%08lx\n",
2980              uMsg, wParam, lParam);
2981       return DefWindowProcA (hwnd, uMsg, wParam, lParam);
2982     }
2983
2984     return 0;
2985 }
2986
2987
2988 VOID
2989 TAB_Register (void)
2990 {
2991   WNDCLASSA wndClass;
2992
2993   ZeroMemory (&wndClass, sizeof(WNDCLASSA));
2994   wndClass.style         = CS_GLOBALCLASS | CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
2995   wndClass.lpfnWndProc   = (WNDPROC)TAB_WindowProc;
2996   wndClass.cbClsExtra    = 0;
2997   wndClass.cbWndExtra    = sizeof(TAB_INFO *);
2998   wndClass.hCursor       = LoadCursorA (0, IDC_ARROWA);
2999   wndClass.hbrBackground = (HBRUSH)NULL;
3000   wndClass.lpszClassName = WC_TABCONTROLA;
3001   
3002   RegisterClassA (&wndClass);
3003 }
3004
3005
3006 VOID
3007 TAB_Unregister (void)
3008 {
3009     UnregisterClassA (WC_TABCONTROLA, (HINSTANCE)NULL);
3010 }