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