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