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