Fixed the displaying of the FOURCC codes in _dump_pixelformat.
[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 LRESULT
1660 TAB_SetRedraw (HWND hwnd, WPARAM wParam)
1661 {
1662     TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
1663   
1664   infoPtr->DoRedraw=(BOOL) wParam;
1665   return 0;
1666 }
1667
1668 static LRESULT TAB_EraseBackground(
1669   HWND hwnd, 
1670   HDC  givenDC)
1671 {
1672   HDC  hdc;
1673   RECT clientRect;
1674
1675   HBRUSH brush = CreateSolidBrush(GetSysColor(COLOR_BTNFACE));
1676
1677   hdc = givenDC ? givenDC : GetDC(hwnd);
1678
1679   GetClientRect(hwnd, &clientRect);
1680
1681   FillRect(hdc, &clientRect, brush);
1682
1683   if (givenDC==0)
1684     ReleaseDC(hwnd, hdc);
1685
1686   DeleteObject(brush);
1687
1688   return 0;
1689 }
1690
1691 /******************************************************************************
1692  * TAB_EnsureSelectionVisible
1693  *
1694  * This method will make sure that the current selection is completely
1695  * visible by scrolling until it is.
1696  */
1697 static void TAB_EnsureSelectionVisible(
1698   HWND      hwnd,
1699   TAB_INFO* infoPtr)
1700 {
1701   INT iSelected = infoPtr->iSelected;
1702
1703   INT iOrigLeftmostVisible = infoPtr->leftmostVisible;
1704
1705   /* 
1706    * set the items row to the bottommost row or topmost row depending on 
1707    * style 
1708    */
1709
1710   if (infoPtr->uNumRows > 0)
1711   {
1712       INT newselected=infoPtr->items[iSelected].rect.top;
1713       INT iTargetRow;
1714       LONG lStyle = GetWindowLongA(hwnd, GWL_STYLE);
1715
1716       if (lStyle & TCS_BOTTOM)
1717           iTargetRow = 0;
1718       else
1719           iTargetRow = infoPtr->uNumRows;
1720
1721       if (newselected != iTargetRow)
1722       {
1723          INT i;
1724          for (i=0; i < infoPtr->uNumItem; i++)
1725              if (infoPtr->items[i].rect.top == newselected )
1726                  infoPtr->items[i].rect.top = iTargetRow;
1727              else if (lStyle&TCS_BOTTOM)
1728                  {
1729                   if (infoPtr->items[i].rect.top < newselected)
1730                      infoPtr->items[i].rect.top+=1;
1731                  }
1732                  else 
1733                  {
1734                    if (infoPtr->items[i].rect.top > newselected)
1735                      infoPtr->items[i].rect.top-=1;
1736                  }
1737
1738         TAB_RecalcHotTrack(hwnd, NULL, NULL, NULL);
1739       }
1740   }
1741
1742   /*
1743    * Do the trivial cases first.
1744    */
1745   if ( (!infoPtr->needsScrolling) ||
1746        (infoPtr->hwndUpDown==0) )
1747     return;
1748
1749   if (infoPtr->leftmostVisible >= iSelected)
1750   {
1751     infoPtr->leftmostVisible = iSelected;
1752   }
1753   else
1754   {
1755      RECT r;
1756      INT  width, i;
1757      /*
1758       * Calculate the part of the client area that is visible.
1759       */
1760      GetClientRect(hwnd, &r);
1761      width = r.right;
1762
1763      GetClientRect(infoPtr->hwndUpDown, &r);
1764      width -= r.right;
1765
1766      if ((infoPtr->items[iSelected].rect.right -
1767           infoPtr->items[iSelected].rect.left) >= width )
1768      {
1769         /* Special case: width of selected item is greater than visible
1770          * part of control.
1771          */
1772         infoPtr->leftmostVisible = iSelected;
1773      }
1774      else
1775      {
1776         for (i = infoPtr->leftmostVisible; i < infoPtr->uNumItem; i++)
1777         {
1778            if ((infoPtr->items[iSelected].rect.right -
1779                 infoPtr->items[i].rect.left) < width)
1780               break;
1781         }
1782         infoPtr->leftmostVisible = i;
1783      }
1784   }
1785
1786   if (infoPtr->leftmostVisible != iOrigLeftmostVisible)
1787     TAB_RecalcHotTrack(hwnd, NULL, NULL, NULL);
1788
1789   SendMessageA(infoPtr->hwndUpDown, UDM_SETPOS, 0,
1790                MAKELONG(infoPtr->leftmostVisible, 0));
1791 }
1792
1793 /******************************************************************************
1794  * TAB_InvalidateTabArea
1795  *
1796  * This method will invalidate the portion of the control that contains the
1797  * tabs. It is called when the state of the control changes and needs
1798  * to be redisplayed
1799  */
1800 static void TAB_InvalidateTabArea(
1801   HWND      hwnd,
1802   TAB_INFO* infoPtr)
1803 {
1804   RECT clientRect;
1805
1806   GetClientRect(hwnd, &clientRect);
1807
1808   if (GetWindowLongA(hwnd, GWL_STYLE) & TCS_BOTTOM) 
1809   {
1810     clientRect.top = clientRect.bottom - (infoPtr->tabHeight * 
1811                                            (infoPtr->uNumRows + 1) + 3);
1812   }
1813   else
1814   {
1815     clientRect.bottom = clientRect.top + (infoPtr->tabHeight * 
1816                                            (infoPtr->uNumRows + 1) + 1);
1817   }
1818
1819   InvalidateRect(hwnd, &clientRect, TRUE);
1820 }
1821
1822 static LRESULT
1823 TAB_Paint (HWND hwnd, WPARAM wParam)
1824 {
1825   HDC hdc;
1826   PAINTSTRUCT ps;
1827     
1828   hdc = wParam== 0 ? BeginPaint (hwnd, &ps) : (HDC)wParam;
1829   TAB_Refresh (hwnd, hdc);
1830     
1831   if(!wParam)
1832     EndPaint (hwnd, &ps);
1833
1834   return 0;
1835 }
1836
1837 static LRESULT
1838 TAB_InsertItem (HWND hwnd, WPARAM wParam, LPARAM lParam)
1839 {    
1840   TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
1841   TCITEMA *pti;
1842   INT iItem, len;
1843   RECT rect;
1844   
1845   GetClientRect (hwnd, &rect);
1846   TRACE("Rect: %x T %i, L %i, B %i, R %i\n", hwnd,
1847         rect.top, rect.left, rect.bottom, rect.right);  
1848   
1849   pti = (TCITEMA *)lParam;
1850   iItem = (INT)wParam;
1851   
1852   if (iItem < 0) return -1;
1853   if (iItem > infoPtr->uNumItem)
1854     iItem = infoPtr->uNumItem;
1855   
1856   if (infoPtr->uNumItem == 0) {
1857     infoPtr->items = COMCTL32_Alloc (sizeof (TAB_ITEM));
1858     infoPtr->uNumItem++;
1859     infoPtr->iSelected = 0;
1860   }
1861   else {
1862     TAB_ITEM *oldItems = infoPtr->items;
1863     
1864     infoPtr->uNumItem++;
1865     infoPtr->items = COMCTL32_Alloc (sizeof (TAB_ITEM) * infoPtr->uNumItem);
1866     
1867     /* pre insert copy */
1868     if (iItem > 0) {
1869       memcpy (&infoPtr->items[0], &oldItems[0],
1870               iItem * sizeof(TAB_ITEM));
1871     }
1872     
1873     /* post insert copy */
1874     if (iItem < infoPtr->uNumItem - 1) {
1875       memcpy (&infoPtr->items[iItem+1], &oldItems[iItem],
1876               (infoPtr->uNumItem - iItem - 1) * sizeof(TAB_ITEM));
1877       
1878     }
1879
1880     if (iItem <= infoPtr->iSelected)
1881       infoPtr->iSelected++;
1882
1883     COMCTL32_Free (oldItems);
1884   }
1885   
1886   infoPtr->items[iItem].mask = pti->mask;
1887   if (pti->mask & TCIF_TEXT) {
1888     len = lstrlenA (pti->pszText);
1889     infoPtr->items[iItem].pszText = COMCTL32_Alloc (len+1);
1890     strcpy (infoPtr->items[iItem].pszText, pti->pszText);
1891     infoPtr->items[iItem].cchTextMax = pti->cchTextMax;
1892   }
1893   
1894   if (pti->mask & TCIF_IMAGE)
1895     infoPtr->items[iItem].iImage = pti->iImage;
1896   
1897   if (pti->mask & TCIF_PARAM)
1898     infoPtr->items[iItem].lParam = pti->lParam;
1899   
1900   TAB_SetItemBounds(hwnd);
1901   TAB_InvalidateTabArea(hwnd, infoPtr);
1902   
1903   TRACE("[%04x]: added item %d '%s'\n",
1904         hwnd, iItem, infoPtr->items[iItem].pszText);
1905
1906   return iItem;
1907 }
1908
1909 static LRESULT 
1910 TAB_SetItemSize (HWND hwnd, WPARAM wParam, LPARAM lParam)
1911 {
1912   TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
1913   LONG lStyle = GetWindowLongA(hwnd, GWL_STYLE);
1914   LONG lResult = 0;
1915
1916   if ((lStyle & TCS_FIXEDWIDTH) || (lStyle & TCS_OWNERDRAWFIXED))
1917   {
1918     lResult = MAKELONG(infoPtr->tabWidth, infoPtr->tabHeight);
1919     infoPtr->tabWidth = (INT)LOWORD(lParam);
1920     infoPtr->tabHeight = (INT)HIWORD(lParam);
1921   }
1922   infoPtr->fSizeSet = TRUE;
1923
1924   return lResult;
1925 }
1926
1927 static LRESULT 
1928 TAB_SetItemA (HWND hwnd, WPARAM wParam, LPARAM lParam)
1929 {
1930   TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
1931   TCITEMA *tabItem; 
1932   TAB_ITEM *wineItem; 
1933   INT    iItem,len;
1934
1935   iItem=(INT) wParam;
1936   tabItem=(LPTCITEMA ) lParam;
1937   TRACE("%d %p\n",iItem, tabItem);
1938   if ((iItem<0) || (iItem>=infoPtr->uNumItem)) return FALSE;
1939
1940   wineItem=& infoPtr->items[iItem];
1941
1942   if (tabItem->mask & TCIF_IMAGE)
1943     wineItem->iImage=tabItem->iImage;
1944
1945   if (tabItem->mask & TCIF_PARAM)
1946     wineItem->lParam=tabItem->lParam;
1947
1948   if (tabItem->mask & TCIF_RTLREADING) 
1949     FIXME("TCIF_RTLREADING\n");
1950
1951   if (tabItem->mask & TCIF_STATE) 
1952     wineItem->dwState=tabItem->dwState;
1953
1954   if (tabItem->mask & TCIF_TEXT) {
1955    len=lstrlenA (tabItem->pszText);
1956    if (len>wineItem->cchTextMax) 
1957      wineItem->pszText= COMCTL32_ReAlloc (wineItem->pszText, len+1);
1958    strcpy (wineItem->pszText, tabItem->pszText);
1959   }
1960
1961   /*
1962    * Update and repaint tabs.
1963    */
1964   TAB_SetItemBounds(hwnd);
1965   TAB_InvalidateTabArea(hwnd,infoPtr);
1966
1967   return TRUE;
1968 }
1969
1970 static LRESULT 
1971 TAB_GetItemCount (HWND hwnd, WPARAM wParam, LPARAM lParam)
1972 {
1973    TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
1974
1975    return infoPtr->uNumItem;
1976 }
1977
1978
1979 static LRESULT 
1980 TAB_GetItemA (HWND hwnd, WPARAM wParam, LPARAM lParam)
1981 {
1982    TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
1983    TCITEMA *tabItem;
1984    TAB_ITEM *wineItem;
1985    INT    iItem;
1986
1987   iItem=(INT) wParam;
1988   tabItem=(LPTCITEMA) lParam;
1989   TRACE("\n");
1990   if ((iItem<0) || (iItem>=infoPtr->uNumItem)) return FALSE;
1991
1992   wineItem=& infoPtr->items[iItem];
1993
1994   if (tabItem->mask & TCIF_IMAGE) 
1995     tabItem->iImage=wineItem->iImage;
1996
1997   if (tabItem->mask & TCIF_PARAM) 
1998     tabItem->lParam=wineItem->lParam;
1999
2000   if (tabItem->mask & TCIF_RTLREADING) 
2001     FIXME("TCIF_RTLREADING\n");
2002
2003   if (tabItem->mask & TCIF_STATE) 
2004     tabItem->dwState=wineItem->dwState;
2005
2006   if (tabItem->mask & TCIF_TEXT) 
2007    lstrcpynA (tabItem->pszText, wineItem->pszText, tabItem->cchTextMax);
2008
2009   return TRUE;
2010 }
2011
2012 static LRESULT 
2013 TAB_DeleteItem (HWND hwnd, WPARAM wParam, LPARAM lParam)
2014 {
2015   TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2016   INT iItem = (INT) wParam;
2017   BOOL bResult = FALSE;
2018
2019   if ((iItem >= 0) && (iItem < infoPtr->uNumItem))
2020   {
2021     TAB_ITEM *oldItems = infoPtr->items;
2022     
2023     infoPtr->uNumItem--;
2024     infoPtr->items = COMCTL32_Alloc(sizeof (TAB_ITEM) * infoPtr->uNumItem);
2025     
2026     if (iItem > 0) 
2027       memcpy(&infoPtr->items[0], &oldItems[0], iItem * sizeof(TAB_ITEM));
2028     
2029     if (iItem < infoPtr->uNumItem) 
2030       memcpy(&infoPtr->items[iItem], &oldItems[iItem + 1],
2031               (infoPtr->uNumItem - iItem) * sizeof(TAB_ITEM));
2032     
2033     COMCTL32_Free (oldItems);
2034
2035     /*
2036      * Readjust the selected index.
2037      */
2038     if ((iItem == infoPtr->iSelected) && (iItem > 0))
2039       infoPtr->iSelected--;
2040       
2041     if (iItem < infoPtr->iSelected)
2042       infoPtr->iSelected--;
2043
2044     if (infoPtr->uNumItem == 0)
2045       infoPtr->iSelected = -1;
2046
2047     /*
2048      * Reposition and repaint tabs.
2049      */
2050     TAB_SetItemBounds(hwnd);
2051     TAB_InvalidateTabArea(hwnd,infoPtr);
2052
2053     bResult = TRUE;
2054   }
2055
2056   return bResult;
2057 }
2058
2059 static LRESULT 
2060 TAB_DeleteAllItems (HWND hwnd, WPARAM wParam, LPARAM lParam)
2061 {
2062    TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2063
2064   COMCTL32_Free (infoPtr->items);
2065   infoPtr->uNumItem = 0;
2066   infoPtr->iSelected = -1;
2067   if (infoPtr->iHotTracked >= 0)
2068     KillTimer(hwnd, TAB_HOTTRACK_TIMER);
2069   infoPtr->iHotTracked = -1;
2070  
2071   TAB_SetItemBounds(hwnd);
2072   TAB_InvalidateTabArea(hwnd,infoPtr);
2073   return TRUE;
2074 }
2075
2076
2077 static LRESULT
2078 TAB_GetFont (HWND hwnd, WPARAM wParam, LPARAM lParam)
2079 {
2080   TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2081
2082   TRACE("\n");
2083   return (LRESULT)infoPtr->hFont;
2084 }
2085
2086 static LRESULT
2087 TAB_SetFont (HWND hwnd, WPARAM wParam, LPARAM lParam)
2088
2089 {
2090   TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2091   
2092   TRACE("%x %lx\n",wParam, lParam);
2093   
2094   infoPtr->hFont = (HFONT)wParam;
2095   
2096   TAB_SetItemBounds(hwnd);
2097
2098   TAB_InvalidateTabArea(hwnd, infoPtr);
2099
2100   return 0;
2101 }
2102
2103
2104 static LRESULT
2105 TAB_GetImageList (HWND hwnd, WPARAM wParam, LPARAM lParam)
2106 {
2107   TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2108
2109   TRACE("\n");
2110   return (LRESULT)infoPtr->himl;
2111 }
2112
2113 static LRESULT
2114 TAB_SetImageList (HWND hwnd, WPARAM wParam, LPARAM lParam)
2115 {
2116     TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2117     HIMAGELIST himlPrev;
2118
2119     TRACE("\n");
2120     himlPrev = infoPtr->himl;
2121     infoPtr->himl= (HIMAGELIST)lParam;
2122     return (LRESULT)himlPrev;
2123 }
2124
2125
2126 static LRESULT
2127 TAB_Size (HWND hwnd, WPARAM wParam, LPARAM lParam)
2128
2129 {
2130 /* I'm not really sure what the following code was meant to do.
2131    This is what it is doing:
2132    When WM_SIZE is sent with SIZE_RESTORED, the control
2133    gets positioned in the top left corner.
2134
2135   RECT parent_rect;
2136   HWND parent;
2137   UINT uPosFlags,cx,cy;
2138
2139   uPosFlags=0;
2140   if (!wParam) {
2141     parent = GetParent (hwnd);
2142     GetClientRect(parent, &parent_rect);
2143     cx=LOWORD (lParam);
2144     cy=HIWORD (lParam);
2145     if (GetWindowLongA(hwnd, GWL_STYLE) & CCS_NORESIZE) 
2146         uPosFlags |= (SWP_NOSIZE | SWP_NOMOVE);
2147
2148     SetWindowPos (hwnd, 0, parent_rect.left, parent_rect.top,
2149             cx, cy, uPosFlags | SWP_NOZORDER);
2150   } else {
2151     FIXME (tab,"WM_SIZE flag %x %lx not handled\n", wParam, lParam);
2152   } */
2153
2154   /*
2155    * Recompute the size/position of the tabs.
2156    */
2157   TAB_SetItemBounds (hwnd);
2158
2159   /*
2160    * Force a repaint of the control.
2161    */
2162   InvalidateRect(hwnd, NULL, TRUE);
2163
2164   return 0;
2165 }
2166
2167
2168 static LRESULT 
2169 TAB_Create (HWND hwnd, WPARAM wParam, LPARAM lParam)
2170 {
2171   TAB_INFO *infoPtr;
2172   TEXTMETRICA fontMetrics;
2173   HDC hdc;
2174   HFONT hOldFont;
2175   DWORD dwStyle;
2176
2177   infoPtr = (TAB_INFO *)COMCTL32_Alloc (sizeof(TAB_INFO));
2178
2179   SetWindowLongA(hwnd, 0, (DWORD)infoPtr);
2180    
2181   infoPtr->uNumItem        = 0;
2182   infoPtr->uNumRows        = 0;
2183   infoPtr->hFont           = 0;
2184   infoPtr->items           = 0;
2185   infoPtr->hcurArrow       = LoadCursorA (0, IDC_ARROWA);
2186   infoPtr->iSelected       = -1;
2187   infoPtr->iHotTracked     = -1;
2188   infoPtr->uFocus          = -1;  
2189   infoPtr->hwndToolTip     = 0;
2190   infoPtr->DoRedraw        = TRUE;
2191   infoPtr->needsScrolling  = FALSE;
2192   infoPtr->hwndUpDown      = 0;
2193   infoPtr->leftmostVisible = 0;
2194   infoPtr->fSizeSet        = FALSE;
2195   
2196   TRACE("Created tab control, hwnd [%04x]\n", hwnd); 
2197
2198   /* The tab control always has the WS_CLIPSIBLINGS style. Even 
2199      if you don't specify in CreateWindow. This is necesary in 
2200      order for paint to work correctly. This follows windows behaviour. */
2201   dwStyle = GetWindowLongA(hwnd, GWL_STYLE);
2202   SetWindowLongA(hwnd, GWL_STYLE, dwStyle|WS_CLIPSIBLINGS);
2203
2204   if (dwStyle & TCS_TOOLTIPS) {
2205     /* Create tooltip control */
2206     infoPtr->hwndToolTip =
2207       CreateWindowExA (0, TOOLTIPS_CLASSA, NULL, 0,
2208                        CW_USEDEFAULT, CW_USEDEFAULT,
2209                        CW_USEDEFAULT, CW_USEDEFAULT,
2210                        hwnd, 0, 0, 0);
2211     
2212     /* Send NM_TOOLTIPSCREATED notification */
2213     if (infoPtr->hwndToolTip) {
2214       NMTOOLTIPSCREATED nmttc;
2215       
2216       nmttc.hdr.hwndFrom = hwnd;
2217       nmttc.hdr.idFrom = GetWindowLongA(hwnd, GWL_ID);
2218       nmttc.hdr.code = NM_TOOLTIPSCREATED;
2219       nmttc.hwndToolTips = infoPtr->hwndToolTip;
2220       
2221       SendMessageA (GetParent (hwnd), WM_NOTIFY,
2222                     (WPARAM)GetWindowLongA(hwnd, GWL_ID), (LPARAM)&nmttc);
2223     }
2224   }  
2225     
2226   /*
2227    * We need to get text information so we need a DC and we need to select
2228    * a font.
2229    */
2230   hdc = GetDC(hwnd); 
2231   hOldFont = SelectObject (hdc, GetStockObject (SYSTEM_FONT));
2232
2233   /*
2234    * Use the system font to determine the initial height of a tab.
2235    */
2236   GetTextMetricsA(hdc, &fontMetrics);
2237
2238   /*
2239    * Make sure there is enough space for the letters + growing the 
2240    * selected item + extra space for the selected item.   
2241    */
2242   infoPtr->tabHeight = fontMetrics.tmHeight + 2*VERTICAL_ITEM_PADDING +  
2243                        SELECTED_TAB_OFFSET;
2244
2245   /*
2246    * Initialize the width of a tab.
2247    */
2248   infoPtr->tabWidth = DEFAULT_TAB_WIDTH;
2249
2250   SelectObject (hdc, hOldFont);
2251   ReleaseDC(hwnd, hdc);
2252
2253   return 0;
2254 }
2255
2256 static LRESULT
2257 TAB_Destroy (HWND hwnd, WPARAM wParam, LPARAM lParam)
2258 {
2259   TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2260   INT iItem;
2261
2262   if (!infoPtr)
2263       return 0;
2264
2265   if (infoPtr->items) {
2266     for (iItem = 0; iItem < infoPtr->uNumItem; iItem++) {
2267       if (infoPtr->items[iItem].pszText)
2268         COMCTL32_Free (infoPtr->items[iItem].pszText);
2269     }
2270     COMCTL32_Free (infoPtr->items);
2271   }
2272   
2273   if (infoPtr->hwndToolTip) 
2274     DestroyWindow (infoPtr->hwndToolTip);
2275  
2276   if (infoPtr->hwndUpDown)
2277     DestroyWindow(infoPtr->hwndUpDown);
2278
2279   if (infoPtr->iHotTracked >= 0)
2280     KillTimer(hwnd, TAB_HOTTRACK_TIMER);
2281
2282   COMCTL32_Free (infoPtr);
2283   SetWindowLongA(hwnd, 0, 0);
2284   return 0;
2285 }
2286
2287 static LRESULT WINAPI
2288 TAB_WindowProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
2289 {
2290
2291     TRACE("hwnd=%x msg=%x wParam=%x lParam=%lx\n", hwnd, uMsg, wParam, lParam);
2292     if (!TAB_GetInfoPtr(hwnd) && (uMsg != WM_CREATE))
2293       return DefWindowProcA (hwnd, uMsg, wParam, lParam);
2294
2295     switch (uMsg)
2296     {
2297     case TCM_GETIMAGELIST:
2298       return TAB_GetImageList (hwnd, wParam, lParam);
2299       
2300     case TCM_SETIMAGELIST:
2301       return TAB_SetImageList (hwnd, wParam, lParam);
2302       
2303     case TCM_GETITEMCOUNT:
2304       return TAB_GetItemCount (hwnd, wParam, lParam);
2305       
2306     case TCM_GETITEMA:
2307       return TAB_GetItemA (hwnd, wParam, lParam);
2308       
2309     case TCM_GETITEMW:
2310       FIXME("Unimplemented msg TCM_GETITEMW\n");
2311       return 0;
2312       
2313     case TCM_SETITEMA:
2314       return TAB_SetItemA (hwnd, wParam, lParam);
2315       
2316     case TCM_SETITEMW:
2317       FIXME("Unimplemented msg TCM_SETITEMW\n");
2318       return 0;
2319       
2320     case TCM_DELETEITEM:
2321       return TAB_DeleteItem (hwnd, wParam, lParam);
2322       
2323     case TCM_DELETEALLITEMS:
2324      return TAB_DeleteAllItems (hwnd, wParam, lParam);
2325      
2326     case TCM_GETITEMRECT:
2327      return TAB_GetItemRect (hwnd, wParam, lParam);
2328       
2329     case TCM_GETCURSEL:
2330       return TAB_GetCurSel (hwnd);
2331       
2332     case TCM_HITTEST:
2333       return TAB_HitTest (hwnd, wParam, lParam);
2334       
2335     case TCM_SETCURSEL:
2336       return TAB_SetCurSel (hwnd, wParam);
2337       
2338     case TCM_INSERTITEMA:
2339       return TAB_InsertItem (hwnd, wParam, lParam);
2340       
2341     case TCM_INSERTITEMW:
2342       FIXME("Unimplemented msg TCM_INSERTITEMW\n");
2343       return 0;
2344       
2345     case TCM_SETITEMEXTRA:
2346       FIXME("Unimplemented msg TCM_SETITEMEXTRA\n");
2347       return 0;
2348       
2349     case TCM_ADJUSTRECT:
2350       return TAB_AdjustRect (hwnd, (BOOL)wParam, (LPRECT)lParam);
2351       
2352     case TCM_SETITEMSIZE:
2353       return TAB_SetItemSize (hwnd, wParam, lParam);
2354       
2355     case TCM_REMOVEIMAGE:
2356       FIXME("Unimplemented msg TCM_REMOVEIMAGE\n");
2357       return 0;
2358       
2359     case TCM_SETPADDING:
2360       FIXME("Unimplemented msg TCM_SETPADDING\n");
2361       return 0;
2362       
2363     case TCM_GETROWCOUNT:
2364       FIXME("Unimplemented msg TCM_GETROWCOUNT\n");
2365       return 0;
2366
2367     case TCM_GETUNICODEFORMAT:
2368       FIXME("Unimplemented msg TCM_GETUNICODEFORMAT\n");
2369       return 0;
2370
2371     case TCM_SETUNICODEFORMAT:
2372       FIXME("Unimplemented msg TCM_SETUNICODEFORMAT\n");
2373       return 0;
2374
2375     case TCM_HIGHLIGHTITEM:
2376       FIXME("Unimplemented msg TCM_HIGHLIGHTITEM\n");
2377       return 0;
2378       
2379     case TCM_GETTOOLTIPS:
2380       return TAB_GetToolTips (hwnd, wParam, lParam);
2381       
2382     case TCM_SETTOOLTIPS:
2383       return TAB_SetToolTips (hwnd, wParam, lParam);
2384       
2385     case TCM_GETCURFOCUS:
2386       return TAB_GetCurFocus (hwnd);
2387       
2388     case TCM_SETCURFOCUS:
2389       return TAB_SetCurFocus (hwnd, wParam);
2390       
2391     case TCM_SETMINTABWIDTH:
2392       FIXME("Unimplemented msg TCM_SETMINTABWIDTH\n");
2393       return 0;
2394       
2395     case TCM_DESELECTALL:
2396       FIXME("Unimplemented msg TCM_DESELECTALL\n");
2397       return 0;
2398       
2399     case TCM_GETEXTENDEDSTYLE:
2400       FIXME("Unimplemented msg TCM_GETEXTENDEDSTYLE\n");
2401       return 0;
2402
2403     case TCM_SETEXTENDEDSTYLE:
2404       FIXME("Unimplemented msg TCM_SETEXTENDEDSTYLE\n");
2405       return 0;
2406
2407     case WM_GETFONT:
2408       return TAB_GetFont (hwnd, wParam, lParam);
2409       
2410     case WM_SETFONT:
2411       return TAB_SetFont (hwnd, wParam, lParam);
2412       
2413     case WM_CREATE:
2414       return TAB_Create (hwnd, wParam, lParam);
2415       
2416     case WM_NCDESTROY:
2417       return TAB_Destroy (hwnd, wParam, lParam);
2418       
2419     case WM_GETDLGCODE:
2420       return DLGC_WANTARROWS | DLGC_WANTCHARS;
2421       
2422     case WM_LBUTTONDOWN:
2423       return TAB_LButtonDown (hwnd, wParam, lParam);
2424       
2425     case WM_LBUTTONUP:
2426       return TAB_LButtonUp (hwnd, wParam, lParam);
2427       
2428     case WM_RBUTTONDOWN:
2429       return TAB_RButtonDown (hwnd, wParam, lParam);
2430       
2431     case WM_MOUSEMOVE:
2432       return TAB_MouseMove (hwnd, wParam, lParam);
2433       
2434     case WM_ERASEBKGND:
2435       return TAB_EraseBackground (hwnd, (HDC)wParam);
2436
2437     case WM_PAINT:
2438       return TAB_Paint (hwnd, wParam);
2439
2440     case WM_SIZE:
2441       return TAB_Size (hwnd, wParam, lParam);
2442       
2443     case WM_SETREDRAW:
2444       return TAB_SetRedraw (hwnd, wParam);
2445
2446     case WM_HSCROLL:
2447       return TAB_OnHScroll(hwnd, (int)LOWORD(wParam), (int)HIWORD(wParam), (HWND)lParam);
2448
2449     case WM_STYLECHANGED:
2450       TAB_SetItemBounds (hwnd);
2451       InvalidateRect(hwnd, NULL, TRUE);
2452       return 0;
2453       
2454     case WM_KILLFOCUS:
2455     case WM_SETFOCUS:
2456       return TAB_FocusChanging(hwnd, uMsg, wParam, lParam);
2457
2458     case WM_KEYUP:
2459       return TAB_KeyUp(hwnd, wParam);
2460
2461     default:
2462       if (uMsg >= WM_USER)
2463         WARN("unknown msg %04x wp=%08x lp=%08lx\n",
2464              uMsg, wParam, lParam);
2465       return DefWindowProcA (hwnd, uMsg, wParam, lParam);
2466     }
2467
2468     return 0;
2469 }
2470
2471
2472 VOID
2473 TAB_Register (void)
2474 {
2475   WNDCLASSA wndClass;
2476
2477   ZeroMemory (&wndClass, sizeof(WNDCLASSA));
2478   wndClass.style         = CS_GLOBALCLASS | CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
2479   wndClass.lpfnWndProc   = (WNDPROC)TAB_WindowProc;
2480   wndClass.cbClsExtra    = 0;
2481   wndClass.cbWndExtra    = sizeof(TAB_INFO *);
2482   wndClass.hCursor       = LoadCursorA (0, IDC_ARROWA);
2483   wndClass.hbrBackground = (HBRUSH)NULL;
2484   wndClass.lpszClassName = WC_TABCONTROLA;
2485   
2486   RegisterClassA (&wndClass);
2487 }
2488
2489
2490 VOID
2491 TAB_Unregister (void)
2492 {
2493     UnregisterClassA (WC_TABCONTROLA, (HINSTANCE)NULL);
2494 }
2495