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