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