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