Removed some unnecessary includes.
[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;
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
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 = ((drawRect->bottom - drawRect->top) - (rcText.right - rcText.left)) / 2;
1487
1488         if(lStyle & TCS_BOTTOM)
1489           drawRect->top+=center_offset;
1490         else
1491           drawRect->bottom-=center_offset;
1492       }
1493       else
1494       {
1495         center_offset = ((drawRect->right - drawRect->left) - (rcText.right - rcText.left)) / 2;
1496         drawRect->left+=center_offset;
1497       }
1498     }
1499
1500     /* Draw the text */
1501     if (lStyle & TCS_RIGHTJUSTIFY)
1502       uHorizAlign = DT_CENTER;
1503     else
1504       uHorizAlign = DT_LEFT;
1505
1506     if(lStyle & TCS_VERTICAL) /* if we are vertical rotate the text and each character */
1507     {
1508       if(lStyle & TCS_BOTTOM)
1509       {
1510         nEscapement = -900;
1511         nOrientation = -900;
1512       }
1513       else
1514       {
1515         nEscapement = 900;
1516         nOrientation = 900;
1517       }
1518     }
1519
1520     /* to get a font with the escapement and orientation we are looking for, we need to */
1521     /* call CreateFontIndirectA, which requires us to set the values of the logfont we pass in */
1522     if(lStyle & TCS_VERTICAL)
1523     {
1524       iPointSize = 9;
1525
1526       lstrcpyA(logfont.lfFaceName, "Arial");
1527       logfont.lfHeight = -MulDiv(iPointSize, GetDeviceCaps(hdc, LOGPIXELSY), 72);
1528       logfont.lfWeight = FW_NORMAL;
1529       logfont.lfItalic = 0;
1530       logfont.lfUnderline = 0;
1531       logfont.lfStrikeOut = 0;
1532
1533       logfont.lfEscapement = nEscapement;
1534       logfont.lfOrientation = nOrientation;
1535       hFont = CreateFontIndirectA(&logfont);
1536       hOldFont = SelectObject(hdc, hFont);
1537     }
1538
1539     ExtTextOutW(hdc,
1540        ((lStyle & TCS_VERTICAL) && (lStyle & TCS_BOTTOM)) ? drawRect->right : drawRect->left,
1541        ((lStyle & TCS_VERTICAL) && !(lStyle & TCS_BOTTOM)) ? drawRect->bottom : drawRect->top,
1542        0,
1543        0,
1544        infoPtr->items[iItem].pszText,
1545        lstrlenW(infoPtr->items[iItem].pszText),
1546        0);
1547
1548
1549     /* clean things up */
1550     *drawRect = rcTemp; /* restore drawRect */
1551
1552     if(lStyle & TCS_VERTICAL)
1553       SelectObject(hdc, hOldFont); /* restore the original font */
1554   }
1555
1556   /*
1557   * Cleanup
1558   */
1559   SetBkMode(hdc, oldBkMode);
1560   SelectObject(hdc, holdPen);
1561 }
1562
1563 /******************************************************************************
1564  * TAB_DrawItem
1565  *
1566  * This method is used to draw a single tab into the tab control.
1567  */         
1568 static void TAB_DrawItem(
1569   HWND hwnd, 
1570   HDC  hdc, 
1571   INT  iItem)
1572 {
1573   TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
1574   LONG      lStyle  = GetWindowLongA(hwnd, GWL_STYLE);
1575   RECT      itemRect;
1576   RECT      selectedRect;
1577   BOOL      isVisible;
1578   RECT      r;
1579
1580   /*
1581    * Get the rectangle for the item.
1582    */
1583   isVisible = TAB_InternalGetItemRect(hwnd,
1584                                       infoPtr,
1585                                       iItem,
1586                                       &itemRect,
1587                                       &selectedRect);
1588
1589   if (isVisible)
1590   {
1591     HBRUSH hbr       = CreateSolidBrush (GetSysColor(COLOR_BTNFACE));    
1592     HPEN   hwPen     = GetSysColorPen (COLOR_3DHILIGHT);
1593     HPEN hbPen  = GetSysColorPen (COLOR_3DDKSHADOW);
1594     HPEN hShade = GetSysColorPen (COLOR_BTNSHADOW);
1595
1596     HPEN   holdPen;
1597     BOOL   deleteBrush = TRUE;
1598
1599     if (lStyle & TCS_BUTTONS)
1600     {
1601       /* Get item rectangle */
1602       r = itemRect;
1603
1604       holdPen = SelectObject (hdc, hwPen);
1605
1606       /* Separators between flat buttons */
1607       /* FIXME: test and correct this if necessary for TCS_FLATBUTTONS style */
1608       if (lStyle & TCS_FLATBUTTONS) 
1609       {
1610         int x = r.right + FLAT_BTN_SPACINGX - 2;
1611
1612         /* highlight */
1613         MoveToEx (hdc, x, r.bottom - 1, NULL);
1614         LineTo   (hdc, x, r.top - 1);
1615         x--;
1616
1617         /* shadow */
1618         SelectObject(hdc, hbPen);
1619         MoveToEx (hdc, x, r.bottom - 1, NULL);
1620         LineTo   (hdc, x, r.top - 1);
1621
1622         /* shade */
1623         SelectObject (hdc, hShade );
1624         MoveToEx (hdc, x - 1, r.bottom - 1, NULL);
1625         LineTo   (hdc, x - 1, r.top - 1);
1626       }
1627
1628       if (iItem == infoPtr->iSelected)
1629       {
1630         /* Background color */
1631         if (!((lStyle & TCS_OWNERDRAWFIXED) && infoPtr->fSizeSet))
1632         {
1633               COLORREF bk = GetSysColor(COLOR_3DHILIGHT);
1634               DeleteObject(hbr);
1635               hbr = GetSysColorBrush(COLOR_SCROLLBAR);
1636
1637               SetTextColor(hdc, GetSysColor(COLOR_3DFACE));
1638               SetBkColor(hdc, bk);
1639
1640               /* if COLOR_WINDOW happens to be the same as COLOR_3DHILIGHT
1641                * we better use 0x55aa bitmap brush to make scrollbar's background
1642                * look different from the window background.
1643                */
1644                if (bk == GetSysColor(COLOR_WINDOW))
1645                   hbr = COMCTL32_hPattern55AABrush;
1646
1647               deleteBrush = FALSE;
1648         }
1649
1650         /* Erase the background */
1651         FillRect(hdc, &r, hbr);
1652
1653         /*
1654          * Draw the tab now.
1655          * The rectangles calculated exclude the right and bottom
1656          * borders of the rectangle. To simplify the following code, those
1657          * borders are shaved-off beforehand.
1658          */
1659         r.right--;
1660         r.bottom--;
1661
1662         /* highlight */
1663         SelectObject(hdc, hwPen);
1664         MoveToEx (hdc, r.left, r.bottom, NULL);
1665         LineTo   (hdc, r.right, r.bottom);
1666         LineTo   (hdc, r.right, r.top + 1);
1667         
1668         /* shadow */
1669         SelectObject(hdc, hbPen);
1670         LineTo  (hdc, r.left + 1, r.top + 1);
1671         LineTo  (hdc, r.left + 1, r.bottom);
1672
1673         /* shade */
1674         SelectObject (hdc, hShade );
1675         MoveToEx (hdc, r.right, r.top, NULL);
1676         LineTo   (hdc, r.left, r.top);
1677         LineTo   (hdc, r.left, r.bottom);
1678       }
1679       else
1680       {
1681         /* Erase the background */
1682         FillRect(hdc, &r, hbr);
1683
1684         if (!(lStyle & TCS_FLATBUTTONS))
1685         {
1686           /* highlight */
1687           MoveToEx (hdc, r.left, r.bottom, NULL);
1688           LineTo   (hdc, r.left, r.top);
1689           LineTo   (hdc, r.right, r.top);
1690          
1691           /* shadow */
1692           SelectObject(hdc, hbPen);
1693           LineTo  (hdc, r.right, r.bottom);
1694           LineTo  (hdc, r.left, r.bottom);
1695
1696           /* shade */
1697           SelectObject (hdc, hShade );
1698           MoveToEx (hdc, r.right - 1, r.top, NULL);
1699           LineTo   (hdc, r.right - 1, r.bottom - 1);
1700           LineTo   (hdc, r.left + 1, r.bottom - 1);
1701         }
1702       }
1703     }
1704     else /* !TCS_BUTTONS */
1705     {
1706       /* Background color */
1707       DeleteObject(hbr);
1708       hbr = CreateSolidBrush(GetSysColor(COLOR_BTNFACE));    
1709
1710       /* We draw a rectangle of different sizes depending on the selection
1711        * state. */
1712       if (iItem == infoPtr->iSelected)
1713         r = selectedRect;
1714       else
1715         r = itemRect;
1716
1717       /*
1718        * Erase the background.
1719        * This is necessary when drawing the selected item since it is larger 
1720        * than the others, it might overlap with stuff already drawn by the 
1721        * other tabs
1722        */     
1723       FillRect(hdc, &r, hbr);
1724
1725       /*
1726        * Draw the tab now.
1727        * The rectangles calculated exclude the right and bottom
1728        * borders of the rectangle. To simplify the following code, those
1729        * borders are shaved-off beforehand.
1730        */
1731       r.right--;
1732       r.bottom--;
1733       
1734       holdPen = SelectObject (hdc, hwPen);
1735       if(lStyle & TCS_VERTICAL)
1736       {
1737         if (lStyle & TCS_BOTTOM)
1738         {
1739           /* highlight */
1740           MoveToEx (hdc, r.left, r.top, NULL);
1741           LineTo   (hdc, r.right - ROUND_CORNER_SIZE, r.top);
1742           LineTo   (hdc, r.right, r.top + ROUND_CORNER_SIZE);
1743
1744           /* shadow */
1745           SelectObject(hdc, hbPen);
1746           LineTo  (hdc, r.right, r.bottom - ROUND_CORNER_SIZE);
1747           LineTo  (hdc, r.right - ROUND_CORNER_SIZE, r.bottom);
1748           LineTo  (hdc, r.left - 1, r.bottom);
1749
1750           /* shade */
1751           SelectObject (hdc, hShade );
1752           MoveToEx (hdc, r.right - 1, r.top, NULL);
1753           LineTo   (hdc, r.right - 1, r.bottom - 1);
1754           LineTo   (hdc, r.left - 1,    r.bottom - 1);
1755         }
1756         else
1757         {
1758           /* highlight */
1759           MoveToEx (hdc, r.right, r.top, NULL);
1760           LineTo   (hdc, r.left + ROUND_CORNER_SIZE, r.top);
1761           LineTo   (hdc, r.left, r.top + ROUND_CORNER_SIZE);
1762           LineTo   (hdc, r.left, r.bottom - ROUND_CORNER_SIZE);
1763
1764           /* shadow */
1765           SelectObject(hdc, hbPen);
1766           LineTo (hdc, r.left + ROUND_CORNER_SIZE,  r.bottom);
1767           LineTo (hdc, r.right + 1, r.bottom);
1768
1769           /* shade */
1770           SelectObject (hdc, hShade );
1771           MoveToEx (hdc, r.left + ROUND_CORNER_SIZE - 1, r.bottom - 1, NULL);
1772           LineTo   (hdc, r.right + 1, r.bottom - 1);
1773         }
1774       }
1775       else
1776       {
1777         if (lStyle & TCS_BOTTOM)
1778         {
1779           /* highlight */
1780           MoveToEx (hdc, r.left, r.top, NULL);
1781           LineTo   (hdc, r.left, r.bottom - ROUND_CORNER_SIZE);
1782           LineTo   (hdc, r.left + ROUND_CORNER_SIZE, r.bottom);
1783
1784           /* shadow */
1785           SelectObject(hdc, hbPen);
1786           LineTo  (hdc, r.right - ROUND_CORNER_SIZE, r.bottom);
1787           LineTo  (hdc, r.right, r.bottom - ROUND_CORNER_SIZE);
1788           LineTo  (hdc, r.right, r.top - 1);
1789
1790           /* shade */
1791           SelectObject (hdc, hShade );
1792           MoveToEx   (hdc, r.left, r.bottom - 1, NULL);
1793           LineTo   (hdc, r.right - ROUND_CORNER_SIZE - 1, r.bottom - 1);
1794           LineTo   (hdc, r.right - 1, r.bottom - ROUND_CORNER_SIZE - 1);
1795           LineTo  (hdc, r.right - 1, r.top - 1);
1796         }
1797         else
1798         {
1799           /* highlight */
1800           if(infoPtr->items[iItem].rect.left == 0) /* if leftmost draw the line longer */
1801             MoveToEx (hdc, r.left, r.bottom, NULL);
1802           else
1803             MoveToEx (hdc, r.left, r.bottom - 1, NULL);
1804
1805           LineTo   (hdc, r.left, r.top + ROUND_CORNER_SIZE);
1806           LineTo   (hdc, r.left + ROUND_CORNER_SIZE, r.top);
1807           LineTo   (hdc, r.right - ROUND_CORNER_SIZE, r.top);
1808
1809           /* shadow */
1810           SelectObject(hdc, hbPen);
1811           LineTo (hdc, r.right,  r.top + ROUND_CORNER_SIZE);
1812           LineTo (hdc, r.right,  r.bottom + 1);
1813
1814
1815           /* shade */
1816           SelectObject (hdc, hShade );
1817           MoveToEx (hdc, r.right - 1, r.top + ROUND_CORNER_SIZE, NULL);
1818           LineTo   (hdc, r.right - 1, r.bottom + 1);
1819         }
1820       }
1821     }
1822   
1823     /* This modifies r to be the text rectangle. */
1824     TAB_DrawItemInterior(hwnd, hdc, iItem, &r);
1825
1826     /* Draw the focus rectangle */
1827     if (((lStyle & TCS_FOCUSNEVER) == 0) &&
1828          (GetFocus() == hwnd) &&
1829          (iItem == infoPtr->uFocus) )
1830     {
1831       r = itemRect;
1832       InflateRect(&r, -1, -1);
1833
1834       DrawFocusRect(hdc, &r);
1835     }
1836
1837     /* Cleanup */
1838     SelectObject(hdc, holdPen);
1839     if (deleteBrush) DeleteObject(hbr);
1840   }
1841 }
1842
1843 /******************************************************************************
1844  * TAB_DrawBorder
1845  *
1846  * This method is used to draw the raised border around the tab control
1847  * "content" area.
1848  */         
1849 static void TAB_DrawBorder (HWND hwnd, HDC hdc)
1850 {
1851   TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
1852   HPEN htmPen;
1853   HPEN hwPen  = GetSysColorPen (COLOR_3DHILIGHT);
1854   HPEN hbPen  = GetSysColorPen (COLOR_3DDKSHADOW);
1855   HPEN hShade = GetSysColorPen (COLOR_BTNSHADOW);
1856   RECT rect;
1857   DWORD lStyle = GetWindowLongA(hwnd, GWL_STYLE);
1858
1859   GetClientRect (hwnd, &rect);
1860
1861   /*
1862    * Adjust for the style
1863    */
1864   if ((lStyle & TCS_BOTTOM) && !(lStyle & TCS_VERTICAL))
1865   {
1866     rect.bottom -= (infoPtr->tabHeight - 2) * (infoPtr->uNumRows + 1) + 2;
1867   }
1868   else if((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
1869   {
1870     rect.right -= (infoPtr->tabHeight - 2) * (infoPtr->uNumRows + 1) + 2;
1871   }
1872   else if(lStyle & TCS_VERTICAL)
1873   {
1874     rect.left += (infoPtr->tabHeight - 2) * (infoPtr->uNumRows + 1) + 2;
1875   }
1876   else /* not TCS_VERTICAL and not TCS_BOTTOM */
1877   {
1878     rect.top += (infoPtr->tabHeight - 2) * (infoPtr->uNumRows + 1) + 1;
1879   }
1880
1881   /*
1882    * Shave-off the right and bottom margins (exluded in the
1883    * rect)
1884    */
1885   rect.right--;
1886   rect.bottom--;
1887
1888   /* highlight */
1889   htmPen = SelectObject (hdc, hwPen);
1890
1891   MoveToEx (hdc, rect.left, rect.bottom, NULL);
1892   LineTo (hdc, rect.left, rect.top);
1893   LineTo (hdc, rect.right, rect.top);
1894
1895   /* Dark Shadow */
1896   SelectObject (hdc, hbPen);
1897   LineTo (hdc, rect.right, rect.bottom );
1898   LineTo (hdc, rect.left, rect.bottom);
1899
1900   /* shade */
1901   SelectObject (hdc, hShade );
1902   MoveToEx (hdc, rect.right - 1, rect.top, NULL);
1903   LineTo   (hdc, rect.right - 1, rect.bottom - 1);
1904   LineTo   (hdc, rect.left,    rect.bottom - 1);
1905
1906   SelectObject(hdc, htmPen);
1907 }
1908
1909 /******************************************************************************
1910  * TAB_Refresh
1911  *
1912  * This method repaints the tab control..
1913  */             
1914 static void TAB_Refresh (HWND hwnd, HDC hdc)
1915 {
1916   TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
1917   HFONT hOldFont;
1918   INT i;
1919
1920   if (!infoPtr->DoRedraw)
1921     return;
1922
1923   hOldFont = SelectObject (hdc, infoPtr->hFont);
1924
1925   if (GetWindowLongA(hwnd, GWL_STYLE) & TCS_BUTTONS)
1926   {
1927     for (i = 0; i < infoPtr->uNumItem; i++) 
1928       TAB_DrawItem (hwnd, hdc, i);
1929   }
1930   else
1931   {
1932     /* Draw all the non selected item first */
1933     for (i = 0; i < infoPtr->uNumItem; i++) 
1934     {
1935       if (i != infoPtr->iSelected)
1936         TAB_DrawItem (hwnd, hdc, i);
1937     }
1938
1939     /* Now, draw the border, draw it before the selected item
1940      * since the selected item overwrites part of the border. */
1941     TAB_DrawBorder (hwnd, hdc);
1942
1943     /* Then, draw the selected item */
1944     TAB_DrawItem (hwnd, hdc, infoPtr->iSelected);
1945
1946     /* If we haven't set the current focus yet, set it now.
1947      * Only happens when we first paint the tab controls */
1948     if (infoPtr->uFocus == -1)
1949       TAB_SetCurFocus(hwnd, infoPtr->iSelected);
1950   }
1951
1952   SelectObject (hdc, hOldFont);
1953 }
1954
1955 static DWORD
1956 TAB_GetRowCount (HWND hwnd )
1957 {
1958   TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
1959   
1960   return infoPtr->uNumRows;
1961 }
1962
1963 static LRESULT
1964 TAB_SetRedraw (HWND hwnd, WPARAM wParam)
1965 {
1966     TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
1967   
1968   infoPtr->DoRedraw=(BOOL) wParam;
1969   return 0;
1970 }
1971
1972 static LRESULT TAB_EraseBackground(
1973   HWND hwnd, 
1974   HDC  givenDC)
1975 {
1976   HDC  hdc;
1977   RECT clientRect;
1978
1979   HBRUSH brush = CreateSolidBrush(GetSysColor(COLOR_BTNFACE));
1980
1981   hdc = givenDC ? givenDC : GetDC(hwnd);
1982
1983   GetClientRect(hwnd, &clientRect);
1984
1985   FillRect(hdc, &clientRect, brush);
1986
1987   if (givenDC==0)
1988     ReleaseDC(hwnd, hdc);
1989
1990   DeleteObject(brush);
1991
1992   return 0;
1993 }
1994
1995 /******************************************************************************
1996  * TAB_EnsureSelectionVisible
1997  *
1998  * This method will make sure that the current selection is completely
1999  * visible by scrolling until it is.
2000  */
2001 static void TAB_EnsureSelectionVisible(
2002   HWND      hwnd,
2003   TAB_INFO* infoPtr)
2004 {
2005   INT iSelected = infoPtr->iSelected;
2006   LONG lStyle = GetWindowLongA(hwnd, GWL_STYLE);
2007   INT iOrigLeftmostVisible = infoPtr->leftmostVisible;
2008
2009   /* set the items row to the bottommost row or topmost row depending on
2010    * style */
2011   if ((infoPtr->uNumRows > 0) && !(lStyle & TCS_BUTTONS))
2012   {
2013       INT newselected;
2014       INT iTargetRow;
2015
2016       if(lStyle & TCS_VERTICAL)
2017         newselected = infoPtr->items[iSelected].rect.left;
2018       else
2019         newselected = infoPtr->items[iSelected].rect.top;
2020
2021       /* the target row is always the number of rows as 0 is the row furthest from the clientRect */
2022       iTargetRow = infoPtr->uNumRows;
2023
2024       if (newselected != iTargetRow)
2025       {
2026          INT i;
2027          if(lStyle & TCS_VERTICAL)
2028          {
2029            for (i=0; i < infoPtr->uNumItem; i++)
2030            {
2031              /* move everything in the row of the selected item to the iTargetRow */
2032              if (infoPtr->items[i].rect.left == newselected )
2033                  infoPtr->items[i].rect.left = iTargetRow;
2034              else
2035              {
2036                if (infoPtr->items[i].rect.left > newselected)
2037                  infoPtr->items[i].rect.left-=1;
2038              }
2039            }
2040          }
2041          else
2042          {
2043            for (i=0; i < infoPtr->uNumItem; i++)
2044            {
2045              if (infoPtr->items[i].rect.top == newselected )
2046                  infoPtr->items[i].rect.top = iTargetRow;
2047              else
2048              {
2049                if (infoPtr->items[i].rect.top > newselected)
2050                  infoPtr->items[i].rect.top-=1;
2051              }
2052           }
2053         }
2054         TAB_RecalcHotTrack(hwnd, NULL, NULL, NULL);
2055       }
2056   }
2057
2058   /*
2059    * Do the trivial cases first.
2060    */
2061   if ( (!infoPtr->needsScrolling) ||
2062        (infoPtr->hwndUpDown==0) || (lStyle & TCS_VERTICAL))
2063     return;
2064
2065   if (infoPtr->leftmostVisible >= iSelected)
2066   {
2067     infoPtr->leftmostVisible = iSelected;
2068   }
2069   else
2070   {
2071      RECT r;
2072      INT  width, i;
2073
2074      /* Calculate the part of the client area that is visible */
2075      GetClientRect(hwnd, &r);
2076      width = r.right;
2077
2078      GetClientRect(infoPtr->hwndUpDown, &r);
2079      width -= r.right;
2080
2081      if ((infoPtr->items[iSelected].rect.right -
2082           infoPtr->items[iSelected].rect.left) >= width )
2083      {
2084         /* Special case: width of selected item is greater than visible
2085          * part of control.
2086          */
2087         infoPtr->leftmostVisible = iSelected;
2088      }
2089      else
2090      {
2091         for (i = infoPtr->leftmostVisible; i < infoPtr->uNumItem; i++)
2092         {
2093            if ((infoPtr->items[iSelected].rect.right -
2094                 infoPtr->items[i].rect.left) < width)
2095               break;
2096         }
2097         infoPtr->leftmostVisible = i;
2098      }
2099   }
2100
2101   if (infoPtr->leftmostVisible != iOrigLeftmostVisible)
2102     TAB_RecalcHotTrack(hwnd, NULL, NULL, NULL);
2103
2104   SendMessageA(infoPtr->hwndUpDown, UDM_SETPOS, 0,
2105                MAKELONG(infoPtr->leftmostVisible, 0));
2106 }
2107
2108 /******************************************************************************
2109  * TAB_InvalidateTabArea
2110  *
2111  * This method will invalidate the portion of the control that contains the
2112  * tabs. It is called when the state of the control changes and needs
2113  * to be redisplayed
2114  */
2115 static void TAB_InvalidateTabArea(
2116   HWND      hwnd,
2117   TAB_INFO* infoPtr)
2118 {
2119   RECT clientRect;
2120   DWORD lStyle = GetWindowLongA(hwnd, GWL_STYLE);
2121
2122   GetClientRect(hwnd, &clientRect);
2123
2124   if ((lStyle & TCS_BOTTOM) && !(lStyle & TCS_VERTICAL))
2125   {
2126     clientRect.top = clientRect.bottom -
2127                    infoPtr->tabHeight -
2128                    (infoPtr->uNumRows) * (infoPtr->tabHeight - 2) -
2129                    ((lStyle & TCS_BUTTONS) ? (infoPtr->uNumRows) * BUTTON_SPACINGY : 0) - 2;
2130   }
2131   else if((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
2132   {
2133     clientRect.left = clientRect.right - infoPtr->tabHeight -
2134                       (infoPtr->uNumRows) * (infoPtr->tabHeight - 2) -
2135                       ((lStyle & TCS_BUTTONS) ? (infoPtr->uNumRows) * BUTTON_SPACINGY : 0) - 2;
2136   }
2137   else if(lStyle & TCS_VERTICAL)
2138   {
2139     clientRect.right = clientRect.left + infoPtr->tabHeight +
2140                        (infoPtr->uNumRows) * (infoPtr->tabHeight - 2) -
2141                       ((lStyle & TCS_BUTTONS) ? (infoPtr->uNumRows) * BUTTON_SPACINGY : 0) + 1;
2142
2143   }
2144   else
2145   {
2146     clientRect.bottom = clientRect.top + infoPtr->tabHeight +
2147                       (infoPtr->uNumRows) * (infoPtr->tabHeight - 2) +
2148                       ((lStyle & TCS_BUTTONS) ? (infoPtr->uNumRows) * BUTTON_SPACINGY : 0) + 1;
2149   }
2150
2151   InvalidateRect(hwnd, &clientRect, TRUE);
2152 }
2153
2154 static LRESULT
2155 TAB_Paint (HWND hwnd, WPARAM wParam)
2156 {
2157   HDC hdc;
2158   PAINTSTRUCT ps;
2159     
2160   hdc = wParam== 0 ? BeginPaint (hwnd, &ps) : (HDC)wParam;
2161   TAB_Refresh (hwnd, hdc);
2162     
2163   if(!wParam)
2164     EndPaint (hwnd, &ps);
2165
2166   return 0;
2167 }
2168
2169 static LRESULT
2170 TAB_InsertItemA (HWND hwnd, WPARAM wParam, LPARAM lParam)
2171 {    
2172   TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2173   TCITEMA *pti;
2174   INT iItem;
2175   RECT rect;
2176   
2177   GetClientRect (hwnd, &rect);
2178   TRACE("Rect: %x T %i, L %i, B %i, R %i\n", hwnd,
2179         rect.top, rect.left, rect.bottom, rect.right);  
2180   
2181   pti = (TCITEMA *)lParam;
2182   iItem = (INT)wParam;
2183   
2184   if (iItem < 0) return -1;
2185   if (iItem > infoPtr->uNumItem)
2186     iItem = infoPtr->uNumItem;
2187   
2188   if (infoPtr->uNumItem == 0) {
2189     infoPtr->items = COMCTL32_Alloc (sizeof (TAB_ITEM));
2190     infoPtr->uNumItem++;
2191     infoPtr->iSelected = 0;
2192   }
2193   else {
2194     TAB_ITEM *oldItems = infoPtr->items;
2195     
2196     infoPtr->uNumItem++;
2197     infoPtr->items = COMCTL32_Alloc (sizeof (TAB_ITEM) * infoPtr->uNumItem);
2198     
2199     /* pre insert copy */
2200     if (iItem > 0) {
2201       memcpy (&infoPtr->items[0], &oldItems[0],
2202               iItem * sizeof(TAB_ITEM));
2203     }
2204     
2205     /* post insert copy */
2206     if (iItem < infoPtr->uNumItem - 1) {
2207       memcpy (&infoPtr->items[iItem+1], &oldItems[iItem],
2208               (infoPtr->uNumItem - iItem - 1) * sizeof(TAB_ITEM));
2209       
2210     }
2211
2212     if (iItem <= infoPtr->iSelected)
2213       infoPtr->iSelected++;
2214
2215     COMCTL32_Free (oldItems);
2216   }
2217   
2218   infoPtr->items[iItem].mask = pti->mask;
2219   if (pti->mask & TCIF_TEXT)
2220     Str_SetPtrAtoW (&infoPtr->items[iItem].pszText, pti->pszText);
2221
2222   if (pti->mask & TCIF_IMAGE)
2223     infoPtr->items[iItem].iImage = pti->iImage;
2224   
2225   if (pti->mask & TCIF_PARAM)
2226     infoPtr->items[iItem].lParam = pti->lParam;
2227   
2228   TAB_SetItemBounds(hwnd);
2229   TAB_InvalidateTabArea(hwnd, infoPtr);
2230   
2231   TRACE("[%04x]: added item %d '%s'\n",
2232         hwnd, iItem, debugstr_w(infoPtr->items[iItem].pszText));
2233
2234   return iItem;
2235 }
2236
2237
2238 static LRESULT
2239 TAB_InsertItemW (HWND hwnd, WPARAM wParam, LPARAM lParam)
2240 {
2241   TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2242   TCITEMW *pti;
2243   INT iItem;
2244   RECT rect;
2245
2246   GetClientRect (hwnd, &rect);
2247   TRACE("Rect: %x T %i, L %i, B %i, R %i\n", hwnd,
2248         rect.top, rect.left, rect.bottom, rect.right);
2249
2250   pti = (TCITEMW *)lParam;
2251   iItem = (INT)wParam;
2252
2253   if (iItem < 0) return -1;
2254   if (iItem > infoPtr->uNumItem)
2255     iItem = infoPtr->uNumItem;
2256
2257   if (infoPtr->uNumItem == 0) {
2258     infoPtr->items = COMCTL32_Alloc (sizeof (TAB_ITEM));
2259     infoPtr->uNumItem++;
2260     infoPtr->iSelected = 0;
2261   }
2262   else {
2263     TAB_ITEM *oldItems = infoPtr->items;
2264
2265     infoPtr->uNumItem++;
2266     infoPtr->items = COMCTL32_Alloc (sizeof (TAB_ITEM) * infoPtr->uNumItem);
2267
2268     /* pre insert copy */
2269     if (iItem > 0) {
2270       memcpy (&infoPtr->items[0], &oldItems[0],
2271               iItem * sizeof(TAB_ITEM));
2272     }
2273
2274     /* post insert copy */
2275     if (iItem < infoPtr->uNumItem - 1) {
2276       memcpy (&infoPtr->items[iItem+1], &oldItems[iItem],
2277               (infoPtr->uNumItem - iItem - 1) * sizeof(TAB_ITEM));
2278
2279   }
2280   
2281     if (iItem <= infoPtr->iSelected)
2282       infoPtr->iSelected++;
2283
2284     COMCTL32_Free (oldItems);
2285   }
2286
2287   infoPtr->items[iItem].mask = pti->mask;
2288   if (pti->mask & TCIF_TEXT)
2289     Str_SetPtrW (&infoPtr->items[iItem].pszText, pti->pszText);
2290
2291   if (pti->mask & TCIF_IMAGE)
2292     infoPtr->items[iItem].iImage = pti->iImage;
2293   
2294   if (pti->mask & TCIF_PARAM)
2295     infoPtr->items[iItem].lParam = pti->lParam;
2296   
2297   TAB_SetItemBounds(hwnd);
2298   TAB_InvalidateTabArea(hwnd, infoPtr);
2299   
2300   TRACE("[%04x]: added item %d '%s'\n",
2301         hwnd, iItem, debugstr_w(infoPtr->items[iItem].pszText));
2302
2303   return iItem;
2304 }
2305
2306
2307 static LRESULT 
2308 TAB_SetItemSize (HWND hwnd, WPARAM wParam, LPARAM lParam)
2309 {
2310   TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2311   LONG lStyle = GetWindowLongA(hwnd, GWL_STYLE);
2312   LONG lResult = 0;
2313
2314   if ((lStyle & TCS_FIXEDWIDTH) || (lStyle & TCS_OWNERDRAWFIXED))
2315   {
2316     lResult = MAKELONG(infoPtr->tabWidth, infoPtr->tabHeight);
2317     infoPtr->tabWidth = (INT)LOWORD(lParam);
2318     infoPtr->tabHeight = (INT)HIWORD(lParam);
2319   }
2320   infoPtr->fSizeSet = TRUE;
2321
2322   return lResult;
2323 }
2324
2325 static LRESULT 
2326 TAB_SetItemA (HWND hwnd, WPARAM wParam, LPARAM lParam)
2327 {
2328   TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2329   TCITEMA *tabItem; 
2330   TAB_ITEM *wineItem;
2331   INT    iItem;
2332
2333   iItem = (INT)wParam;
2334   tabItem = (LPTCITEMA)lParam;
2335
2336   TRACE("%d %p\n", iItem, tabItem);
2337   if ((iItem<0) || (iItem>=infoPtr->uNumItem)) return FALSE;
2338
2339   wineItem = &infoPtr->items[iItem];
2340
2341   if (tabItem->mask & TCIF_IMAGE)
2342     wineItem->iImage = tabItem->iImage;
2343
2344   if (tabItem->mask & TCIF_PARAM)
2345     wineItem->lParam = tabItem->lParam;
2346
2347   if (tabItem->mask & TCIF_RTLREADING) 
2348     FIXME("TCIF_RTLREADING\n");
2349
2350   if (tabItem->mask & TCIF_STATE) 
2351     wineItem->dwState = tabItem->dwState;
2352
2353   if (tabItem->mask & TCIF_TEXT)
2354    Str_SetPtrAtoW(&wineItem->pszText, tabItem->pszText);
2355
2356   /* Update and repaint tabs */
2357   TAB_SetItemBounds(hwnd);
2358   TAB_InvalidateTabArea(hwnd,infoPtr);
2359
2360   return TRUE;
2361   }
2362
2363
2364 static LRESULT
2365 TAB_SetItemW (HWND hwnd, WPARAM wParam, LPARAM lParam)
2366 {
2367   TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2368   TCITEMW *tabItem;
2369   TAB_ITEM *wineItem;
2370   INT    iItem;
2371
2372   iItem = (INT)wParam;
2373   tabItem = (LPTCITEMW)lParam;
2374
2375   TRACE("%d %p\n", iItem, tabItem);
2376   if ((iItem<0) || (iItem>=infoPtr->uNumItem)) return FALSE;
2377
2378   wineItem = &infoPtr->items[iItem];
2379
2380   if (tabItem->mask & TCIF_IMAGE)
2381     wineItem->iImage = tabItem->iImage;
2382
2383   if (tabItem->mask & TCIF_PARAM)
2384     wineItem->lParam = tabItem->lParam;
2385
2386   if (tabItem->mask & TCIF_RTLREADING)
2387     FIXME("TCIF_RTLREADING\n");
2388
2389   if (tabItem->mask & TCIF_STATE)
2390     wineItem->dwState = tabItem->dwState;
2391
2392   if (tabItem->mask & TCIF_TEXT)
2393    Str_SetPtrW(&wineItem->pszText, tabItem->pszText);
2394
2395   /* Update and repaint tabs */
2396   TAB_SetItemBounds(hwnd);
2397   TAB_InvalidateTabArea(hwnd,infoPtr);
2398
2399   return TRUE;
2400 }
2401
2402
2403 static LRESULT 
2404 TAB_GetItemCount (HWND hwnd, WPARAM wParam, LPARAM lParam)
2405 {
2406    TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2407
2408    return infoPtr->uNumItem;
2409 }
2410
2411
2412 static LRESULT 
2413 TAB_GetItemA (HWND hwnd, WPARAM wParam, LPARAM lParam)
2414 {
2415    TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2416    TCITEMA *tabItem;
2417    TAB_ITEM *wineItem;
2418    INT    iItem;
2419
2420   iItem = (INT)wParam;
2421   tabItem = (LPTCITEMA)lParam;
2422   TRACE("\n");
2423   if ((iItem<0) || (iItem>=infoPtr->uNumItem))
2424     return FALSE;
2425
2426   wineItem=& infoPtr->items[iItem];
2427
2428   if (tabItem->mask & TCIF_IMAGE) 
2429     tabItem->iImage = wineItem->iImage;
2430
2431   if (tabItem->mask & TCIF_PARAM) 
2432     tabItem->lParam = wineItem->lParam;
2433
2434   if (tabItem->mask & TCIF_RTLREADING) 
2435     FIXME("TCIF_RTLREADING\n");
2436
2437   if (tabItem->mask & TCIF_STATE) 
2438     tabItem->dwState = wineItem->dwState;
2439
2440   if (tabItem->mask & TCIF_TEXT) 
2441    Str_GetPtrWtoA (wineItem->pszText, tabItem->pszText, tabItem->cchTextMax);
2442
2443   return TRUE;
2444 }
2445
2446
2447 static LRESULT 
2448 TAB_GetItemW (HWND hwnd, WPARAM wParam, LPARAM lParam)
2449 {
2450   TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2451   TCITEMW *tabItem;
2452   TAB_ITEM *wineItem;
2453   INT    iItem;
2454
2455   iItem = (INT)wParam;
2456   tabItem = (LPTCITEMW)lParam;
2457   TRACE("\n");
2458   if ((iItem<0) || (iItem>=infoPtr->uNumItem))
2459     return FALSE;
2460
2461   wineItem=& infoPtr->items[iItem];
2462
2463   if (tabItem->mask & TCIF_IMAGE)
2464     tabItem->iImage = wineItem->iImage;
2465
2466   if (tabItem->mask & TCIF_PARAM)
2467     tabItem->lParam = wineItem->lParam;
2468
2469   if (tabItem->mask & TCIF_RTLREADING)
2470     FIXME("TCIF_RTLREADING\n");
2471
2472   if (tabItem->mask & TCIF_STATE)
2473     tabItem->dwState = wineItem->dwState;
2474
2475   if (tabItem->mask & TCIF_TEXT)
2476    Str_GetPtrW (wineItem->pszText, tabItem->pszText, tabItem->cchTextMax);
2477
2478   return TRUE;
2479 }
2480
2481
2482 static LRESULT 
2483 TAB_DeleteItem (HWND hwnd, WPARAM wParam, LPARAM lParam)
2484 {
2485   TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2486   INT iItem = (INT) wParam;
2487   BOOL bResult = FALSE;
2488
2489   if ((iItem >= 0) && (iItem < infoPtr->uNumItem))
2490   {
2491     TAB_ITEM *oldItems = infoPtr->items;
2492     
2493     infoPtr->uNumItem--;
2494     infoPtr->items = COMCTL32_Alloc(sizeof (TAB_ITEM) * infoPtr->uNumItem);
2495     
2496     if (iItem > 0) 
2497       memcpy(&infoPtr->items[0], &oldItems[0], iItem * sizeof(TAB_ITEM));
2498     
2499     if (iItem < infoPtr->uNumItem) 
2500       memcpy(&infoPtr->items[iItem], &oldItems[iItem + 1],
2501               (infoPtr->uNumItem - iItem) * sizeof(TAB_ITEM));
2502     
2503     COMCTL32_Free(oldItems);
2504
2505     /* Readjust the selected index */
2506     if ((iItem == infoPtr->iSelected) && (iItem > 0))
2507       infoPtr->iSelected--;
2508       
2509     if (iItem < infoPtr->iSelected)
2510       infoPtr->iSelected--;
2511
2512     if (infoPtr->uNumItem == 0)
2513       infoPtr->iSelected = -1;
2514
2515     /* Reposition and repaint tabs */
2516     TAB_SetItemBounds(hwnd);
2517     TAB_InvalidateTabArea(hwnd,infoPtr);
2518
2519     bResult = TRUE;
2520   }
2521
2522   return bResult;
2523 }
2524
2525 static LRESULT 
2526 TAB_DeleteAllItems (HWND hwnd, WPARAM wParam, LPARAM lParam)
2527 {
2528    TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2529
2530   COMCTL32_Free (infoPtr->items);
2531   infoPtr->uNumItem = 0;
2532   infoPtr->iSelected = -1;
2533   if (infoPtr->iHotTracked >= 0)
2534     KillTimer(hwnd, TAB_HOTTRACK_TIMER);
2535   infoPtr->iHotTracked = -1;
2536  
2537   TAB_SetItemBounds(hwnd);
2538   TAB_InvalidateTabArea(hwnd,infoPtr);
2539   return TRUE;
2540 }
2541
2542
2543 static LRESULT
2544 TAB_GetFont (HWND hwnd, WPARAM wParam, LPARAM lParam)
2545 {
2546   TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2547
2548   TRACE("\n");
2549   return (LRESULT)infoPtr->hFont;
2550 }
2551
2552 static LRESULT
2553 TAB_SetFont (HWND hwnd, WPARAM wParam, LPARAM lParam)
2554
2555 {
2556   TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2557   
2558   TRACE("%x %lx\n",wParam, lParam);
2559   
2560   infoPtr->hFont = (HFONT)wParam;
2561   
2562   TAB_SetItemBounds(hwnd);
2563
2564   TAB_InvalidateTabArea(hwnd, infoPtr);
2565
2566   return 0;
2567 }
2568
2569
2570 static LRESULT
2571 TAB_GetImageList (HWND hwnd, WPARAM wParam, LPARAM lParam)
2572 {
2573   TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2574
2575   TRACE("\n");
2576   return (LRESULT)infoPtr->himl;
2577 }
2578
2579 static LRESULT
2580 TAB_SetImageList (HWND hwnd, WPARAM wParam, LPARAM lParam)
2581 {
2582     TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2583     HIMAGELIST himlPrev;
2584
2585     TRACE("\n");
2586     himlPrev = infoPtr->himl;
2587     infoPtr->himl= (HIMAGELIST)lParam;
2588     return (LRESULT)himlPrev;
2589 }
2590
2591 static LRESULT
2592 TAB_GetUnicodeFormat (HWND hwnd)
2593 {
2594     TAB_INFO *infoPtr = TAB_GetInfoPtr (hwnd);
2595     return infoPtr->bUnicode;
2596 }
2597
2598 static LRESULT
2599 TAB_SetUnicodeFormat (HWND hwnd, WPARAM wParam)
2600 {
2601     TAB_INFO *infoPtr = TAB_GetInfoPtr (hwnd);
2602     BOOL bTemp = infoPtr->bUnicode;
2603
2604     infoPtr->bUnicode = (BOOL)wParam;
2605
2606     return bTemp;
2607 }
2608
2609 static LRESULT
2610 TAB_Size (HWND hwnd, WPARAM wParam, LPARAM lParam)
2611
2612 {
2613 /* I'm not really sure what the following code was meant to do.
2614    This is what it is doing:
2615    When WM_SIZE is sent with SIZE_RESTORED, the control
2616    gets positioned in the top left corner.
2617
2618   RECT parent_rect;
2619   HWND parent;
2620   UINT uPosFlags,cx,cy;
2621
2622   uPosFlags=0;
2623   if (!wParam) {
2624     parent = GetParent (hwnd);
2625     GetClientRect(parent, &parent_rect);
2626     cx=LOWORD (lParam);
2627     cy=HIWORD (lParam);
2628     if (GetWindowLongA(hwnd, GWL_STYLE) & CCS_NORESIZE) 
2629         uPosFlags |= (SWP_NOSIZE | SWP_NOMOVE);
2630
2631     SetWindowPos (hwnd, 0, parent_rect.left, parent_rect.top,
2632             cx, cy, uPosFlags | SWP_NOZORDER);
2633   } else {
2634     FIXME("WM_SIZE flag %x %lx not handled\n", wParam, lParam);
2635   } */
2636
2637   /* Recompute the size/position of the tabs. */
2638   TAB_SetItemBounds (hwnd);
2639
2640   /* Force a repaint of the control. */
2641   InvalidateRect(hwnd, NULL, TRUE);
2642
2643   return 0;
2644 }
2645
2646
2647 static LRESULT 
2648 TAB_Create (HWND hwnd, WPARAM wParam, LPARAM lParam)
2649 {
2650   TAB_INFO *infoPtr;
2651   TEXTMETRICA fontMetrics;
2652   HDC hdc;
2653   HFONT hOldFont;
2654   DWORD dwStyle;
2655
2656   infoPtr = (TAB_INFO *)COMCTL32_Alloc (sizeof(TAB_INFO));
2657
2658   SetWindowLongA(hwnd, 0, (DWORD)infoPtr);
2659    
2660   infoPtr->uNumItem        = 0;
2661   infoPtr->uNumRows        = 0;
2662   infoPtr->hFont           = 0;
2663   infoPtr->items           = 0;
2664   infoPtr->hcurArrow       = LoadCursorA (0, IDC_ARROWA);
2665   infoPtr->iSelected       = -1;
2666   infoPtr->iHotTracked     = -1;
2667   infoPtr->uFocus          = -1;  
2668   infoPtr->hwndToolTip     = 0;
2669   infoPtr->DoRedraw        = TRUE;
2670   infoPtr->needsScrolling  = FALSE;
2671   infoPtr->hwndUpDown      = 0;
2672   infoPtr->leftmostVisible = 0;
2673   infoPtr->fSizeSet        = FALSE;
2674   infoPtr->bUnicode        = IsWindowUnicode (hwnd);
2675   
2676   TRACE("Created tab control, hwnd [%04x]\n", hwnd); 
2677
2678   /* The tab control always has the WS_CLIPSIBLINGS style. Even 
2679      if you don't specify it in CreateWindow. This is necessary in 
2680      order for paint to work correctly. This follows windows behaviour. */
2681   dwStyle = GetWindowLongA(hwnd, GWL_STYLE);
2682   SetWindowLongA(hwnd, GWL_STYLE, dwStyle|WS_CLIPSIBLINGS);
2683
2684   if (dwStyle & TCS_TOOLTIPS) {
2685     /* Create tooltip control */
2686     infoPtr->hwndToolTip =
2687       CreateWindowExA (0, TOOLTIPS_CLASSA, NULL, 0,
2688                        CW_USEDEFAULT, CW_USEDEFAULT,
2689                        CW_USEDEFAULT, CW_USEDEFAULT,
2690                        hwnd, 0, 0, 0);
2691     
2692     /* Send NM_TOOLTIPSCREATED notification */
2693     if (infoPtr->hwndToolTip) {
2694       NMTOOLTIPSCREATED nmttc;
2695       
2696       nmttc.hdr.hwndFrom = hwnd;
2697       nmttc.hdr.idFrom = GetWindowLongA(hwnd, GWL_ID);
2698       nmttc.hdr.code = NM_TOOLTIPSCREATED;
2699       nmttc.hwndToolTips = infoPtr->hwndToolTip;
2700       
2701       SendMessageA (GetParent (hwnd), WM_NOTIFY,
2702                     (WPARAM)GetWindowLongA(hwnd, GWL_ID), (LPARAM)&nmttc);
2703     }
2704   }  
2705     
2706   /*
2707    * We need to get text information so we need a DC and we need to select
2708    * a font.
2709    */
2710   hdc = GetDC(hwnd); 
2711   hOldFont = SelectObject (hdc, GetStockObject (SYSTEM_FONT));
2712
2713   /* Use the system font to determine the initial height of a tab. */
2714   GetTextMetricsA(hdc, &fontMetrics);
2715
2716   /*
2717    * Make sure there is enough space for the letters + growing the 
2718    * selected item + extra space for the selected item.   
2719    */
2720   infoPtr->tabHeight = fontMetrics.tmHeight + 2 * VERTICAL_ITEM_PADDING +
2721                        SELECTED_TAB_OFFSET;
2722
2723   /* Initialize the width of a tab. */
2724   infoPtr->tabWidth = DEFAULT_TAB_WIDTH;
2725
2726   SelectObject (hdc, hOldFont);
2727   ReleaseDC(hwnd, hdc);
2728
2729   return 0;
2730 }
2731
2732 static LRESULT
2733 TAB_Destroy (HWND hwnd, WPARAM wParam, LPARAM lParam)
2734 {
2735   TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2736   INT iItem;
2737
2738   if (!infoPtr)
2739       return 0;
2740
2741   if (infoPtr->items) {
2742     for (iItem = 0; iItem < infoPtr->uNumItem; iItem++) {
2743       if (infoPtr->items[iItem].pszText)
2744         COMCTL32_Free (infoPtr->items[iItem].pszText);
2745     }
2746     COMCTL32_Free (infoPtr->items);
2747   }
2748   
2749   if (infoPtr->hwndToolTip) 
2750     DestroyWindow (infoPtr->hwndToolTip);
2751  
2752   if (infoPtr->hwndUpDown)
2753     DestroyWindow(infoPtr->hwndUpDown);
2754
2755   if (infoPtr->iHotTracked >= 0)
2756     KillTimer(hwnd, TAB_HOTTRACK_TIMER);
2757
2758   COMCTL32_Free (infoPtr);
2759   SetWindowLongA(hwnd, 0, 0);
2760   return 0;
2761 }
2762
2763 static LRESULT WINAPI
2764 TAB_WindowProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
2765 {
2766
2767     TRACE("hwnd=%x msg=%x wParam=%x lParam=%lx\n", hwnd, uMsg, wParam, lParam);
2768     if (!TAB_GetInfoPtr(hwnd) && (uMsg != WM_CREATE))
2769       return DefWindowProcA (hwnd, uMsg, wParam, lParam);
2770
2771     switch (uMsg)
2772     {
2773     case TCM_GETIMAGELIST:
2774       return TAB_GetImageList (hwnd, wParam, lParam);
2775       
2776     case TCM_SETIMAGELIST:
2777       return TAB_SetImageList (hwnd, wParam, lParam);
2778       
2779     case TCM_GETITEMCOUNT:
2780       return TAB_GetItemCount (hwnd, wParam, lParam);
2781       
2782     case TCM_GETITEMA:
2783       return TAB_GetItemA (hwnd, wParam, lParam);
2784       
2785     case TCM_GETITEMW:
2786       return TAB_GetItemW (hwnd, wParam, lParam);
2787       
2788     case TCM_SETITEMA:
2789       return TAB_SetItemA (hwnd, wParam, lParam);
2790       
2791     case TCM_SETITEMW:
2792       return TAB_SetItemW (hwnd, wParam, lParam);
2793       
2794     case TCM_DELETEITEM:
2795       return TAB_DeleteItem (hwnd, wParam, lParam);
2796       
2797     case TCM_DELETEALLITEMS:
2798      return TAB_DeleteAllItems (hwnd, wParam, lParam);
2799      
2800     case TCM_GETITEMRECT:
2801      return TAB_GetItemRect (hwnd, wParam, lParam);
2802       
2803     case TCM_GETCURSEL:
2804       return TAB_GetCurSel (hwnd);
2805       
2806     case TCM_HITTEST:
2807       return TAB_HitTest (hwnd, wParam, lParam);
2808       
2809     case TCM_SETCURSEL:
2810       return TAB_SetCurSel (hwnd, wParam);
2811       
2812     case TCM_INSERTITEMA:
2813       return TAB_InsertItemA (hwnd, wParam, lParam);
2814       
2815     case TCM_INSERTITEMW:
2816       return TAB_InsertItemW (hwnd, wParam, lParam);
2817       
2818     case TCM_SETITEMEXTRA:
2819       FIXME("Unimplemented msg TCM_SETITEMEXTRA\n");
2820       return 0;
2821       
2822     case TCM_ADJUSTRECT:
2823       return TAB_AdjustRect (hwnd, (BOOL)wParam, (LPRECT)lParam);
2824       
2825     case TCM_SETITEMSIZE:
2826       return TAB_SetItemSize (hwnd, wParam, lParam);
2827       
2828     case TCM_REMOVEIMAGE:
2829       FIXME("Unimplemented msg TCM_REMOVEIMAGE\n");
2830       return 0;
2831       
2832     case TCM_SETPADDING:
2833       FIXME("Unimplemented msg TCM_SETPADDING\n");
2834       return 0;
2835       
2836     case TCM_GETROWCOUNT:
2837       return TAB_GetRowCount(hwnd);
2838
2839     case TCM_GETUNICODEFORMAT:
2840       return TAB_GetUnicodeFormat (hwnd);
2841
2842     case TCM_SETUNICODEFORMAT:
2843       return TAB_SetUnicodeFormat (hwnd, wParam);
2844
2845     case TCM_HIGHLIGHTITEM:
2846       FIXME("Unimplemented msg TCM_HIGHLIGHTITEM\n");
2847       return 0;
2848       
2849     case TCM_GETTOOLTIPS:
2850       return TAB_GetToolTips (hwnd, wParam, lParam);
2851       
2852     case TCM_SETTOOLTIPS:
2853       return TAB_SetToolTips (hwnd, wParam, lParam);
2854       
2855     case TCM_GETCURFOCUS:
2856       return TAB_GetCurFocus (hwnd);
2857       
2858     case TCM_SETCURFOCUS:
2859       return TAB_SetCurFocus (hwnd, wParam);
2860       
2861     case TCM_SETMINTABWIDTH:
2862       FIXME("Unimplemented msg TCM_SETMINTABWIDTH\n");
2863       return 0;
2864       
2865     case TCM_DESELECTALL:
2866       FIXME("Unimplemented msg TCM_DESELECTALL\n");
2867       return 0;
2868       
2869     case TCM_GETEXTENDEDSTYLE:
2870       FIXME("Unimplemented msg TCM_GETEXTENDEDSTYLE\n");
2871       return 0;
2872
2873     case TCM_SETEXTENDEDSTYLE:
2874       FIXME("Unimplemented msg TCM_SETEXTENDEDSTYLE\n");
2875       return 0;
2876
2877     case WM_GETFONT:
2878       return TAB_GetFont (hwnd, wParam, lParam);
2879       
2880     case WM_SETFONT:
2881       return TAB_SetFont (hwnd, wParam, lParam);
2882       
2883     case WM_CREATE:
2884       return TAB_Create (hwnd, wParam, lParam);
2885       
2886     case WM_NCDESTROY:
2887       return TAB_Destroy (hwnd, wParam, lParam);
2888       
2889     case WM_GETDLGCODE:
2890       return DLGC_WANTARROWS | DLGC_WANTCHARS;
2891       
2892     case WM_LBUTTONDOWN:
2893       return TAB_LButtonDown (hwnd, wParam, lParam);
2894       
2895     case WM_LBUTTONUP:
2896       return TAB_LButtonUp (hwnd, wParam, lParam);
2897       
2898     case WM_RBUTTONDOWN:
2899       return TAB_RButtonDown (hwnd, wParam, lParam);
2900       
2901     case WM_MOUSEMOVE:
2902       return TAB_MouseMove (hwnd, wParam, lParam);
2903       
2904     case WM_ERASEBKGND:
2905       return TAB_EraseBackground (hwnd, (HDC)wParam);
2906
2907     case WM_PAINT:
2908       return TAB_Paint (hwnd, wParam);
2909
2910     case WM_SIZE:
2911       return TAB_Size (hwnd, wParam, lParam);
2912       
2913     case WM_SETREDRAW:
2914       return TAB_SetRedraw (hwnd, wParam);
2915
2916     case WM_HSCROLL:
2917       return TAB_OnHScroll(hwnd, (int)LOWORD(wParam), (int)HIWORD(wParam), (HWND)lParam);
2918
2919     case WM_STYLECHANGED:
2920       TAB_SetItemBounds (hwnd);
2921       InvalidateRect(hwnd, NULL, TRUE);
2922       return 0;
2923       
2924     case WM_KILLFOCUS:
2925     case WM_SETFOCUS:
2926       return TAB_FocusChanging(hwnd, uMsg, wParam, lParam);
2927
2928     case WM_KEYUP:
2929       return TAB_KeyUp(hwnd, wParam);
2930     case WM_NCHITTEST:
2931       return TAB_NCHitTest(hwnd, lParam);
2932
2933     default:
2934       if (uMsg >= WM_USER)
2935         WARN("unknown msg %04x wp=%08x lp=%08lx\n",
2936              uMsg, wParam, lParam);
2937       return DefWindowProcA (hwnd, uMsg, wParam, lParam);
2938     }
2939
2940     return 0;
2941 }
2942
2943
2944 VOID
2945 TAB_Register (void)
2946 {
2947   WNDCLASSA wndClass;
2948
2949   ZeroMemory (&wndClass, sizeof(WNDCLASSA));
2950   wndClass.style         = CS_GLOBALCLASS | CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
2951   wndClass.lpfnWndProc   = (WNDPROC)TAB_WindowProc;
2952   wndClass.cbClsExtra    = 0;
2953   wndClass.cbWndExtra    = sizeof(TAB_INFO *);
2954   wndClass.hCursor       = LoadCursorA (0, IDC_ARROWA);
2955   wndClass.hbrBackground = (HBRUSH)NULL;
2956   wndClass.lpszClassName = WC_TABCONTROLA;
2957   
2958   RegisterClassA (&wndClass);
2959 }
2960
2961
2962 VOID
2963 TAB_Unregister (void)
2964 {
2965     UnregisterClassA (WC_TABCONTROLA, (HINSTANCE)NULL);
2966 }