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