comctl32: Remove unused parameter from SYSLINK_WrapLine.
[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, wParam, (LPRECT)lParam, 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 (!prc) return -1;
867
868     if(lStyle & TCS_VERTICAL)
869     {
870         iRightBottom = &(prc->right);
871         iLeftTop     = &(prc->left);
872     }
873     else
874     {
875         iRightBottom = &(prc->bottom);
876         iLeftTop     = &(prc->top);
877     }
878
879     if (fLarger) /* Go from display rectangle */
880     {
881         /* Add the height of the tabs. */
882         if (lStyle & TCS_BOTTOM)
883             *iRightBottom += infoPtr->tabHeight * infoPtr->uNumRows;
884         else
885             *iLeftTop -= infoPtr->tabHeight * infoPtr->uNumRows +
886                          ((lStyle & TCS_BUTTONS)? 3 * (infoPtr->uNumRows - 1) : 0);
887
888         /* Inflate the rectangle for the padding */
889         InflateRect(prc, DISPLAY_AREA_PADDINGX, DISPLAY_AREA_PADDINGY); 
890
891         /* Inflate for the border */
892         InflateRect(prc, CONTROL_BORDER_SIZEX, CONTROL_BORDER_SIZEY);
893     }
894     else /* Go from window rectangle. */
895     {
896         /* Deflate the rectangle for the border */
897         InflateRect(prc, -CONTROL_BORDER_SIZEX, -CONTROL_BORDER_SIZEY);
898
899         /* Deflate the rectangle for the padding */
900         InflateRect(prc, -DISPLAY_AREA_PADDINGX, -DISPLAY_AREA_PADDINGY);
901
902         /* Remove the height of the tabs. */
903         if (lStyle & TCS_BOTTOM)
904             *iRightBottom -= infoPtr->tabHeight * infoPtr->uNumRows;
905         else
906             *iLeftTop += (infoPtr->tabHeight) * infoPtr->uNumRows +
907                          ((lStyle & TCS_BUTTONS)? 3 * (infoPtr->uNumRows - 1) : 0);
908     }
909
910   return 0;
911 }
912
913 /******************************************************************************
914  * TAB_OnHScroll
915  *
916  * This method will handle the notification from the scroll control and
917  * perform the scrolling operation on the tab control.
918  */
919 static LRESULT TAB_OnHScroll(TAB_INFO *infoPtr, int nScrollCode, int nPos, HWND hwndScroll)
920 {
921   if(nScrollCode == SB_THUMBPOSITION && nPos != infoPtr->leftmostVisible)
922   {
923      if(nPos < infoPtr->leftmostVisible)
924         infoPtr->leftmostVisible--;
925      else
926         infoPtr->leftmostVisible++;
927
928      TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
929      TAB_InvalidateTabArea(infoPtr);
930      SendMessageW(infoPtr->hwndUpDown, UDM_SETPOS, 0,
931                    MAKELONG(infoPtr->leftmostVisible, 0));
932    }
933
934    return 0;
935 }
936
937 /******************************************************************************
938  * TAB_SetupScrolling
939  *
940  * This method will check the current scrolling state and make sure the
941  * scrolling control is displayed (or not).
942  */
943 static void TAB_SetupScrolling(
944   HWND        hwnd,
945   TAB_INFO*   infoPtr,
946   const RECT* clientRect)
947 {
948   static const WCHAR msctls_updown32W[] = { 'm','s','c','t','l','s','_','u','p','d','o','w','n','3','2',0 };
949   static const WCHAR emptyW[] = { 0 };
950   INT maxRange = 0;
951   DWORD lStyle = GetWindowLongW(hwnd, GWL_STYLE);
952
953   if (infoPtr->needsScrolling)
954   {
955     RECT controlPos;
956     INT vsize, tabwidth;
957
958     /*
959      * Calculate the position of the scroll control.
960      */
961     if(lStyle & TCS_VERTICAL)
962     {
963       controlPos.right = clientRect->right;
964       controlPos.left  = controlPos.right - 2 * GetSystemMetrics(SM_CXHSCROLL);
965
966       if (lStyle & TCS_BOTTOM)
967       {
968         controlPos.top    = clientRect->bottom - infoPtr->tabHeight;
969         controlPos.bottom = controlPos.top + GetSystemMetrics(SM_CYHSCROLL);
970       }
971       else
972       {
973         controlPos.bottom = clientRect->top + infoPtr->tabHeight;
974         controlPos.top    = controlPos.bottom - GetSystemMetrics(SM_CYHSCROLL);
975       }
976     }
977     else
978     {
979       controlPos.right = clientRect->right;
980       controlPos.left  = controlPos.right - 2 * GetSystemMetrics(SM_CXHSCROLL);
981
982       if (lStyle & TCS_BOTTOM)
983       {
984         controlPos.top    = clientRect->bottom - infoPtr->tabHeight;
985         controlPos.bottom = controlPos.top + GetSystemMetrics(SM_CYHSCROLL);
986       }
987       else
988       {
989         controlPos.bottom = clientRect->top + infoPtr->tabHeight;
990         controlPos.top    = controlPos.bottom - GetSystemMetrics(SM_CYHSCROLL);
991       }
992     }
993
994     /*
995      * If we don't have a scroll control yet, we want to create one.
996      * If we have one, we want to make sure it's positioned properly.
997      */
998     if (infoPtr->hwndUpDown==0)
999     {
1000       infoPtr->hwndUpDown = CreateWindowW(msctls_updown32W, emptyW,
1001                                           WS_VISIBLE | WS_CHILD | UDS_HORZ,
1002                                           controlPos.left, controlPos.top,
1003                                           controlPos.right - controlPos.left,
1004                                           controlPos.bottom - controlPos.top,
1005                                           hwnd, NULL, NULL, NULL);
1006     }
1007     else
1008     {
1009       SetWindowPos(infoPtr->hwndUpDown,
1010                    NULL,
1011                    controlPos.left, controlPos.top,
1012                    controlPos.right - controlPos.left,
1013                    controlPos.bottom - controlPos.top,
1014                    SWP_SHOWWINDOW | SWP_NOZORDER);
1015     }
1016
1017     /* Now calculate upper limit of the updown control range.
1018      * We do this by calculating how many tabs will be offscreen when the
1019      * last tab is visible.
1020      */
1021     if(infoPtr->uNumItem)
1022     {
1023        vsize = clientRect->right - (controlPos.right - controlPos.left + 1);
1024        maxRange = infoPtr->uNumItem;
1025        tabwidth = TAB_GetItem(infoPtr, infoPtr->uNumItem - 1)->rect.right;
1026
1027        for(; maxRange > 0; maxRange--)
1028        {
1029           if(tabwidth - TAB_GetItem(infoPtr,maxRange - 1)->rect.left > vsize)
1030              break;
1031        }
1032
1033        if(maxRange == infoPtr->uNumItem)
1034           maxRange--;
1035     }
1036   }
1037   else
1038   {
1039     /* If we once had a scroll control... hide it */
1040     if (infoPtr->hwndUpDown!=0)
1041       ShowWindow(infoPtr->hwndUpDown, SW_HIDE);
1042   }
1043   if (infoPtr->hwndUpDown)
1044      SendMessageW(infoPtr->hwndUpDown, UDM_SETRANGE32, 0, maxRange);
1045 }
1046
1047 /******************************************************************************
1048  * TAB_SetItemBounds
1049  *
1050  * This method will calculate the position rectangles of all the items in the
1051  * control. The rectangle calculated starts at 0 for the first item in the
1052  * list and ignores scrolling and selection.
1053  * It also uses the current font to determine the height of the tab row and
1054  * it checks if all the tabs fit in the client area of the window. If they
1055  * don't, a scrolling control is added.
1056  */
1057 static void TAB_SetItemBounds (TAB_INFO *infoPtr)
1058 {
1059   LONG        lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1060   TEXTMETRICW fontMetrics;
1061   UINT        curItem;
1062   INT         curItemLeftPos;
1063   INT         curItemRowCount;
1064   HFONT       hFont, hOldFont;
1065   HDC         hdc;
1066   RECT        clientRect;
1067   INT         iTemp;
1068   RECT*       rcItem;
1069   INT         iIndex;
1070   INT         icon_width = 0;
1071
1072   /*
1073    * We need to get text information so we need a DC and we need to select
1074    * a font.
1075    */
1076   hdc = GetDC(infoPtr->hwnd);
1077
1078   hFont = infoPtr->hFont ? infoPtr->hFont : GetStockObject (SYSTEM_FONT);
1079   hOldFont = SelectObject (hdc, hFont);
1080
1081   /*
1082    * We will base the rectangle calculations on the client rectangle
1083    * of the control.
1084    */
1085   GetClientRect(infoPtr->hwnd, &clientRect);
1086
1087   /* if TCS_VERTICAL then swap the height and width so this code places the
1088      tabs along the top of the rectangle and we can just rotate them after
1089      rather than duplicate all of the below code */
1090   if(lStyle & TCS_VERTICAL)
1091   {
1092      iTemp = clientRect.bottom;
1093      clientRect.bottom = clientRect.right;
1094      clientRect.right = iTemp;
1095   }
1096
1097   /* Now use hPadding and vPadding */
1098   infoPtr->uHItemPadding = infoPtr->uHItemPadding_s;
1099   infoPtr->uVItemPadding = infoPtr->uVItemPadding_s;
1100   
1101   /* The leftmost item will be "0" aligned */
1102   curItemLeftPos = 0;
1103   curItemRowCount = infoPtr->uNumItem ? 1 : 0;
1104
1105   if (!(infoPtr->fHeightSet))
1106   {
1107     int item_height;
1108     int icon_height = 0;
1109
1110     /* Use the current font to determine the height of a tab. */
1111     GetTextMetricsW(hdc, &fontMetrics);
1112
1113     /* Get the icon height */
1114     if (infoPtr->himl)
1115       ImageList_GetIconSize(infoPtr->himl, 0, &icon_height);
1116
1117     /* Take the highest between font or icon */
1118     if (fontMetrics.tmHeight > icon_height)
1119       item_height = fontMetrics.tmHeight + 2;
1120     else
1121       item_height = icon_height;
1122
1123     /*
1124      * Make sure there is enough space for the letters + icon + growing the
1125      * selected item + extra space for the selected item.
1126      */
1127     infoPtr->tabHeight = item_height + 
1128                          ((lStyle & TCS_BUTTONS) ? 2 : 1) *
1129                           infoPtr->uVItemPadding;
1130
1131     TRACE("tabH=%d, tmH=%d, iconh=%d\n",
1132           infoPtr->tabHeight, fontMetrics.tmHeight, icon_height);
1133   }
1134
1135   TRACE("client right=%d\n", clientRect.right);
1136
1137   /* Get the icon width */
1138   if (infoPtr->himl)
1139   {
1140     ImageList_GetIconSize(infoPtr->himl, &icon_width, 0);
1141
1142     if (lStyle & TCS_FIXEDWIDTH)
1143       icon_width += 4;
1144     else
1145       /* Add padding if icon is present */
1146       icon_width += infoPtr->uHItemPadding;
1147   }
1148
1149   for (curItem = 0; curItem < infoPtr->uNumItem; curItem++)
1150   {
1151     TAB_ITEM *curr = TAB_GetItem(infoPtr, curItem);
1152         
1153     /* Set the leftmost position of the tab. */
1154     curr->rect.left = curItemLeftPos;
1155
1156     if (lStyle & TCS_FIXEDWIDTH)
1157     {
1158       curr->rect.right = curr->rect.left +
1159         max(infoPtr->tabWidth, icon_width);
1160     }
1161     else if (!curr->pszText)
1162     {
1163       /* If no text use minimum tab width including padding. */
1164       if (infoPtr->tabMinWidth < 0)
1165         curr->rect.right = curr->rect.left + GET_DEFAULT_MIN_TAB_WIDTH(infoPtr);
1166       else
1167       {
1168         curr->rect.right = curr->rect.left + infoPtr->tabMinWidth;
1169
1170         /* Add extra padding if icon is present */
1171         if (infoPtr->himl && infoPtr->tabMinWidth > 0 && infoPtr->tabMinWidth < DEFAULT_MIN_TAB_WIDTH
1172             && infoPtr->uHItemPadding > 1)
1173           curr->rect.right += EXTRA_ICON_PADDING * (infoPtr->uHItemPadding-1);
1174       }
1175     }
1176     else
1177     {
1178       int tabwidth;
1179       SIZE size;
1180       /* Calculate how wide the tab is depending on the text it contains */
1181       GetTextExtentPoint32W(hdc, curr->pszText,
1182                             lstrlenW(curr->pszText), &size);
1183
1184       tabwidth = size.cx + icon_width + 2 * infoPtr->uHItemPadding;
1185
1186       if (infoPtr->tabMinWidth < 0)
1187         tabwidth = max(tabwidth, GET_DEFAULT_MIN_TAB_WIDTH(infoPtr));
1188       else
1189         tabwidth = max(tabwidth, infoPtr->tabMinWidth);
1190
1191       curr->rect.right = curr->rect.left + tabwidth;
1192       TRACE("for <%s>, l,r=%d,%d\n",
1193           debugstr_w(curr->pszText), curr->rect.left, curr->rect.right);
1194     }
1195
1196     /*
1197      * Check if this is a multiline tab control and if so
1198      * check to see if we should wrap the tabs
1199      *
1200      * Wrap all these tabs. We will arrange them evenly later.
1201      *
1202      */
1203
1204     if (((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)) &&
1205         (curr->rect.right > 
1206         (clientRect.right - CONTROL_BORDER_SIZEX - DISPLAY_AREA_PADDINGX)))
1207     {
1208         curr->rect.right -= curr->rect.left;
1209
1210         curr->rect.left = 0;
1211         curItemRowCount++;
1212         TRACE("wrapping <%s>, l,r=%d,%d\n", debugstr_w(curr->pszText),
1213             curr->rect.left, curr->rect.right);
1214     }
1215
1216     curr->rect.bottom = 0;
1217     curr->rect.top = curItemRowCount - 1;
1218
1219     TRACE("Rect: %s\n", wine_dbgstr_rect(&curr->rect));
1220
1221     /*
1222      * The leftmost position of the next item is the rightmost position
1223      * of this one.
1224      */
1225     if (lStyle & TCS_BUTTONS)
1226     {
1227       curItemLeftPos = curr->rect.right + BUTTON_SPACINGX;
1228       if (lStyle & TCS_FLATBUTTONS)
1229         curItemLeftPos += FLAT_BTN_SPACINGX;
1230     }
1231     else
1232       curItemLeftPos = curr->rect.right;
1233   }
1234
1235   if (!((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)))
1236   {
1237     /*
1238      * Check if we need a scrolling control.
1239      */
1240     infoPtr->needsScrolling = (curItemLeftPos + (2 * SELECTED_TAB_OFFSET) >
1241                                clientRect.right);
1242
1243     /* Don't need scrolling, then update infoPtr->leftmostVisible */
1244     if(!infoPtr->needsScrolling)
1245       infoPtr->leftmostVisible = 0;
1246   }
1247   else
1248   {
1249     /*
1250      * No scrolling in Multiline or Vertical styles.
1251      */
1252     infoPtr->needsScrolling = FALSE;
1253     infoPtr->leftmostVisible = 0;
1254   }
1255   TAB_SetupScrolling(infoPtr->hwnd, infoPtr, &clientRect);
1256
1257   /* Set the number of rows */
1258   infoPtr->uNumRows = curItemRowCount;
1259
1260   /* Arrange all tabs evenly if style says so */
1261    if (!(lStyle & TCS_RAGGEDRIGHT) &&
1262        ((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)) &&
1263        (infoPtr->uNumItem > 0) &&
1264        (infoPtr->uNumRows > 1))
1265    {
1266       INT tabPerRow,remTab,iRow;
1267       UINT iItm;
1268       INT iCount=0;
1269
1270       /*
1271        * Ok windows tries to even out the rows. place the same
1272        * number of tabs in each row. So lets give that a shot
1273        */
1274
1275       tabPerRow = infoPtr->uNumItem / (infoPtr->uNumRows);
1276       remTab = infoPtr->uNumItem % (infoPtr->uNumRows);
1277
1278       for (iItm=0,iRow=0,iCount=0,curItemLeftPos=0;
1279            iItm<infoPtr->uNumItem;
1280            iItm++,iCount++)
1281       {
1282           /* normalize the current rect */
1283           TAB_ITEM *curr = TAB_GetItem(infoPtr, iItm);
1284  
1285           /* shift the item to the left side of the clientRect */
1286           curr->rect.right -= curr->rect.left;
1287           curr->rect.left = 0;
1288
1289           TRACE("r=%d, cl=%d, cl.r=%d, iCount=%d, iRow=%d, uNumRows=%d, remTab=%d, tabPerRow=%d\n",
1290               curr->rect.right, curItemLeftPos, clientRect.right,
1291               iCount, iRow, infoPtr->uNumRows, remTab, tabPerRow);
1292
1293           /* if we have reached the maximum number of tabs on this row */
1294           /* move to the next row, reset our current item left position and */
1295           /* the count of items on this row */
1296
1297           if (lStyle & TCS_VERTICAL) {
1298               /* Vert: Add the remaining tabs in the *last* remainder rows */
1299               if (iCount >= ((iRow>=(INT)infoPtr->uNumRows - remTab)?tabPerRow + 1:tabPerRow)) {
1300                   iRow++;
1301                   curItemLeftPos = 0;
1302                   iCount = 0;
1303               }
1304           } else {
1305               /* Horz: Add the remaining tabs in the *first* remainder rows */
1306               if (iCount >= ((iRow<remTab)?tabPerRow + 1:tabPerRow)) {
1307                   iRow++;
1308                   curItemLeftPos = 0;
1309                   iCount = 0;
1310               }
1311           }
1312
1313           /* shift the item to the right to place it as the next item in this row */
1314           curr->rect.left += curItemLeftPos;
1315           curr->rect.right += curItemLeftPos;
1316           curr->rect.top = iRow;
1317           if (lStyle & TCS_BUTTONS)
1318           {
1319             curItemLeftPos = curr->rect.right + 1;
1320             if (lStyle & TCS_FLATBUTTONS)
1321               curItemLeftPos += FLAT_BTN_SPACINGX;
1322           }
1323           else
1324             curItemLeftPos = curr->rect.right;
1325
1326           TRACE("arranging <%s>, l,r=%d,%d, row=%d\n",
1327               debugstr_w(curr->pszText), curr->rect.left,
1328               curr->rect.right, curr->rect.top);
1329       }
1330
1331       /*
1332        * Justify the rows
1333        */
1334       {
1335         INT widthDiff, iIndexStart=0, iIndexEnd=0;
1336         INT remainder;
1337         INT iCount=0;
1338
1339         while(iIndexStart < infoPtr->uNumItem)
1340         {
1341           TAB_ITEM *start = TAB_GetItem(infoPtr, iIndexStart);
1342
1343           /*
1344            * find the index of the row
1345            */
1346           /* find the first item on the next row */
1347           for (iIndexEnd=iIndexStart;
1348               (iIndexEnd < infoPtr->uNumItem) &&
1349               (TAB_GetItem(infoPtr, iIndexEnd)->rect.top ==
1350                 start->rect.top) ;
1351               iIndexEnd++)
1352           /* intentionally blank */;
1353
1354           /*
1355            * we need to justify these tabs so they fill the whole given
1356            * client area
1357            *
1358            */
1359           /* find the amount of space remaining on this row */
1360           widthDiff = clientRect.right - (2 * SELECTED_TAB_OFFSET) -
1361                         TAB_GetItem(infoPtr, iIndexEnd - 1)->rect.right;
1362
1363           /* iCount is the number of tab items on this row */
1364           iCount = iIndexEnd - iIndexStart;
1365
1366           if (iCount > 1)
1367           {
1368             remainder = widthDiff % iCount;
1369             widthDiff = widthDiff / iCount;
1370             /* add widthDiff/iCount, or extra space/items on row, to each item on this row */
1371             for (iIndex=iIndexStart, iCount=0; iIndex < iIndexEnd; iIndex++, iCount++)
1372             {
1373               TAB_ITEM *item = TAB_GetItem(infoPtr, iIndex);
1374
1375               item->rect.left += iCount * widthDiff;
1376               item->rect.right += (iCount + 1) * widthDiff;
1377
1378               TRACE("adjusting 1 <%s>, l,r=%d,%d\n",
1379                   debugstr_w(item->pszText),
1380                   item->rect.left, item->rect.right);
1381
1382             }
1383             TAB_GetItem(infoPtr, iIndex - 1)->rect.right += remainder;
1384           }
1385           else /* we have only one item on this row, make it take up the entire row */
1386           {
1387             start->rect.left = clientRect.left;
1388             start->rect.right = clientRect.right - 4;
1389
1390             TRACE("adjusting 2 <%s>, l,r=%d,%d\n",
1391                 debugstr_w(start->pszText),
1392                 start->rect.left, start->rect.right);
1393
1394           }
1395
1396
1397           iIndexStart = iIndexEnd;
1398         }
1399       }
1400   }
1401
1402   /* if TCS_VERTICAL rotate the tabs so they are along the side of the clientRect */
1403   if(lStyle & TCS_VERTICAL)
1404   {
1405     RECT rcOriginal;
1406     for(iIndex = 0; iIndex < infoPtr->uNumItem; iIndex++)
1407     {
1408       rcItem = &TAB_GetItem(infoPtr, iIndex)->rect;
1409
1410       rcOriginal = *rcItem;
1411
1412       /* this is rotating the items by 90 degrees clockwise around the center of the control */
1413       rcItem->top = (rcOriginal.left - clientRect.left);
1414       rcItem->bottom = rcItem->top + (rcOriginal.right - rcOriginal.left);
1415       rcItem->left = rcOriginal.top;
1416       rcItem->right = rcOriginal.bottom;
1417     }
1418   }
1419
1420   TAB_EnsureSelectionVisible(infoPtr);
1421   TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
1422
1423   /* Cleanup */
1424   SelectObject (hdc, hOldFont);
1425   ReleaseDC (infoPtr->hwnd, hdc);
1426 }
1427
1428
1429 static void
1430 TAB_EraseTabInterior(const TAB_INFO *infoPtr, HDC hdc, INT iItem, RECT *drawRect)
1431 {
1432     LONG     lStyle  = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1433     HBRUSH   hbr = CreateSolidBrush (comctl32_color.clrBtnFace);
1434     BOOL     deleteBrush = TRUE;
1435     RECT     rTemp = *drawRect;
1436
1437     InflateRect(&rTemp, -2, -2);
1438     if (lStyle & TCS_BUTTONS)
1439     {
1440         if (iItem == infoPtr->iSelected)
1441         {
1442             /* Background color */
1443             if (!(lStyle & TCS_OWNERDRAWFIXED))
1444             {
1445                 DeleteObject(hbr);
1446                 hbr = GetSysColorBrush(COLOR_SCROLLBAR);
1447
1448                 SetTextColor(hdc, comctl32_color.clr3dFace);
1449                 SetBkColor(hdc, comctl32_color.clr3dHilight);
1450
1451                 /* if COLOR_WINDOW happens to be the same as COLOR_3DHILIGHT
1452                 * we better use 0x55aa bitmap brush to make scrollbar's background
1453                 * look different from the window background.
1454                 */
1455                 if (comctl32_color.clr3dHilight == comctl32_color.clrWindow)
1456                     hbr = COMCTL32_hPattern55AABrush;
1457
1458                 deleteBrush = FALSE;
1459             }
1460             FillRect(hdc, &rTemp, hbr);
1461         }
1462         else  /* ! selected */
1463         {
1464             if (lStyle & TCS_FLATBUTTONS)
1465             {
1466                 FillRect(hdc, drawRect, hbr);
1467                 if (iItem == infoPtr->iHotTracked)
1468                     DrawEdge(hdc, drawRect, EDGE_RAISED, BF_SOFT|BF_RECT);
1469             }
1470             else
1471                 FillRect(hdc, &rTemp, hbr);
1472         }
1473
1474     }
1475     else /* !TCS_BUTTONS */
1476     {
1477         if (!GetWindowTheme (infoPtr->hwnd))
1478             FillRect(hdc, &rTemp, hbr);
1479     }
1480
1481     /* Cleanup */
1482     if (deleteBrush) DeleteObject(hbr);
1483 }
1484
1485 /******************************************************************************
1486  * TAB_DrawItemInterior
1487  *
1488  * This method is used to draw the interior (text and icon) of a single tab
1489  * into the tab control.
1490  */
1491 static void
1492 TAB_DrawItemInterior(const TAB_INFO *infoPtr, HDC hdc, INT iItem, RECT *drawRect)
1493 {
1494   LONG      lStyle  = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1495
1496   RECT localRect;
1497
1498   HPEN   htextPen;
1499   HPEN   holdPen;
1500   INT    oldBkMode;
1501   HFONT  hOldFont;
1502   
1503 /*  if (drawRect == NULL) */
1504   {
1505     BOOL isVisible;
1506     RECT itemRect;
1507     RECT selectedRect;
1508
1509     /*
1510      * Get the rectangle for the item.
1511      */
1512     isVisible = TAB_InternalGetItemRect(infoPtr, iItem, &itemRect, &selectedRect);
1513     if (!isVisible)
1514       return;
1515
1516     /*
1517      * Make sure drawRect points to something valid; simplifies code.
1518      */
1519     drawRect = &localRect;
1520
1521     /*
1522      * This logic copied from the part of TAB_DrawItem which draws
1523      * the tab background.  It's important to keep it in sync.  I
1524      * would have liked to avoid code duplication, but couldn't figure
1525      * out how without making spaghetti of TAB_DrawItem.
1526      */
1527     if (iItem == infoPtr->iSelected)
1528       *drawRect = selectedRect;
1529     else
1530       *drawRect = itemRect;
1531         
1532     if (lStyle & TCS_BUTTONS)
1533     {
1534       if (iItem == infoPtr->iSelected)
1535       {
1536         drawRect->left   += 4;
1537         drawRect->top    += 4;
1538         drawRect->right  -= 4;
1539         drawRect->bottom -= 1;
1540       }
1541       else
1542       {
1543         drawRect->left   += 2;
1544         drawRect->top    += 2;
1545         drawRect->right  -= 2;
1546         drawRect->bottom -= 2;
1547       }
1548     }
1549     else
1550     {
1551       if ((lStyle & TCS_VERTICAL) && (lStyle & TCS_BOTTOM))
1552       {
1553         if (iItem != infoPtr->iSelected)
1554         {
1555           drawRect->left   += 2;
1556           drawRect->top    += 2;
1557           drawRect->bottom -= 2;
1558         }
1559       }
1560       else if (lStyle & TCS_VERTICAL)
1561       {
1562         if (iItem == infoPtr->iSelected)
1563         {
1564           drawRect->right  += 1;
1565         }
1566         else
1567         {
1568           drawRect->top    += 2;
1569           drawRect->right  -= 2;
1570           drawRect->bottom -= 2;
1571         }
1572       }
1573       else if (lStyle & TCS_BOTTOM)
1574       {
1575         if (iItem == infoPtr->iSelected)
1576         {
1577           drawRect->top    -= 2;
1578         }
1579         else
1580         {
1581           InflateRect(drawRect, -2, -2);
1582           drawRect->bottom += 2;
1583         }
1584       }
1585       else
1586       {
1587         if (iItem == infoPtr->iSelected)
1588         {
1589           drawRect->bottom += 3;
1590         }
1591         else
1592         {
1593           drawRect->bottom -= 2;
1594           InflateRect(drawRect, -2, 0);
1595         }
1596       }
1597     }
1598   }
1599   TRACE("drawRect=(%s)\n", wine_dbgstr_rect(drawRect));
1600
1601   /* Clear interior */
1602   TAB_EraseTabInterior (infoPtr, hdc, iItem, drawRect);
1603
1604   /* Draw the focus rectangle */
1605   if (!(lStyle & TCS_FOCUSNEVER) &&
1606       (GetFocus() == infoPtr->hwnd) &&
1607       (iItem == infoPtr->uFocus) )
1608   {
1609     RECT rFocus = *drawRect;
1610     InflateRect(&rFocus, -3, -3);
1611     if (lStyle & TCS_BOTTOM && !(lStyle & TCS_VERTICAL))
1612       rFocus.top -= 3;
1613     if (lStyle & TCS_BUTTONS)
1614     {
1615       rFocus.left -= 3;
1616       rFocus.top -= 3;
1617     }
1618
1619     DrawFocusRect(hdc, &rFocus);
1620   }
1621
1622   /*
1623    * Text pen
1624    */
1625   htextPen = CreatePen( PS_SOLID, 1, GetSysColor(COLOR_BTNTEXT) );
1626   holdPen  = SelectObject(hdc, htextPen);
1627   hOldFont = SelectObject(hdc, infoPtr->hFont);
1628
1629   /*
1630    * Setup for text output
1631   */
1632   oldBkMode = SetBkMode(hdc, TRANSPARENT);
1633   if (!GetWindowTheme (infoPtr->hwnd) || (lStyle & TCS_BUTTONS))
1634       SetTextColor(hdc, (((lStyle & TCS_HOTTRACK) && (iItem == infoPtr->iHotTracked) 
1635                           && !(lStyle & TCS_FLATBUTTONS)) 
1636                         | (TAB_GetItem(infoPtr, iItem)->dwState & TCIS_HIGHLIGHTED)) ?
1637                         comctl32_color.clrHighlight : comctl32_color.clrBtnText);
1638
1639   /*
1640    * if owner draw, tell the owner to draw
1641    */
1642   if ((lStyle & TCS_OWNERDRAWFIXED) && GetParent(infoPtr->hwnd))
1643   {
1644     DRAWITEMSTRUCT dis;
1645     UINT id;
1646
1647     drawRect->top += 2;
1648     drawRect->right -= 1;
1649     if ( iItem == infoPtr->iSelected )
1650     {
1651         drawRect->right -= 1;
1652         drawRect->left += 1;
1653     }
1654
1655     /*
1656      * get the control id
1657      */
1658     id = (UINT)GetWindowLongPtrW( infoPtr->hwnd, GWLP_ID );
1659
1660     /*
1661      * put together the DRAWITEMSTRUCT
1662      */
1663     dis.CtlType    = ODT_TAB;
1664     dis.CtlID      = id;
1665     dis.itemID     = iItem;
1666     dis.itemAction = ODA_DRAWENTIRE;
1667     dis.itemState = 0;
1668     if ( iItem == infoPtr->iSelected )
1669       dis.itemState |= ODS_SELECTED;
1670     if (infoPtr->uFocus == iItem) 
1671       dis.itemState |= ODS_FOCUS;
1672     dis.hwndItem = infoPtr->hwnd;
1673     dis.hDC      = hdc;
1674     CopyRect(&dis.rcItem,drawRect);
1675     dis.itemData = (ULONG_PTR)TAB_GetItem(infoPtr, iItem)->extra;
1676
1677     /*
1678      * send the draw message
1679      */
1680     SendMessageW( infoPtr->hwndNotify, WM_DRAWITEM, (WPARAM)id, (LPARAM)&dis );
1681   }
1682   else
1683   {
1684     TAB_ITEM *item = TAB_GetItem(infoPtr, iItem);
1685     RECT rcTemp;
1686     RECT rcImage;
1687
1688     /* used to center the icon and text in the tab */
1689     RECT rcText;
1690     INT center_offset_h, center_offset_v;
1691
1692     /* set rcImage to drawRect, we will use top & left in our ImageList_Draw call */
1693     rcImage = *drawRect;
1694
1695     rcTemp = *drawRect;
1696
1697     rcText.left = rcText.top = rcText.right = rcText.bottom = 0;
1698
1699     /* get the rectangle that the text fits in */
1700     if (item->pszText)
1701     {
1702       DrawTextW(hdc, item->pszText, -1, &rcText, DT_CALCRECT);
1703     }
1704     /*
1705      * If not owner draw, then do the drawing ourselves.
1706      *
1707      * Draw the icon.
1708      */
1709     if (infoPtr->himl && item->iImage != -1)
1710     {
1711       INT cx;
1712       INT cy;
1713       
1714       ImageList_GetIconSize(infoPtr->himl, &cx, &cy);
1715
1716       if(lStyle & TCS_VERTICAL)
1717       {
1718         center_offset_h = ((drawRect->bottom - drawRect->top) - (cy + infoPtr->uHItemPadding + (rcText.right  - rcText.left))) / 2;
1719         center_offset_v = ((drawRect->right - drawRect->left) - cx) / 2;
1720       }
1721       else
1722       {
1723         center_offset_h = ((drawRect->right - drawRect->left) - (cx + infoPtr->uHItemPadding + (rcText.right  - rcText.left))) / 2;
1724         center_offset_v = ((drawRect->bottom - drawRect->top) - cy) / 2;
1725       }
1726
1727       /* if an item is selected, the icon is shifted up instead of down */
1728       if (iItem == infoPtr->iSelected)
1729         center_offset_v -= infoPtr->uVItemPadding / 2;
1730       else
1731         center_offset_v += infoPtr->uVItemPadding / 2;
1732
1733       if (lStyle & TCS_FIXEDWIDTH && lStyle & (TCS_FORCELABELLEFT | TCS_FORCEICONLEFT))
1734         center_offset_h = infoPtr->uHItemPadding;
1735
1736       if (center_offset_h < 2)
1737         center_offset_h = 2;
1738         
1739       if (center_offset_v < 0)
1740         center_offset_v = 0;
1741         
1742       TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%s), textlen=%d\n",
1743           debugstr_w(item->pszText), center_offset_h, center_offset_v,
1744           wine_dbgstr_rect(drawRect), (rcText.right-rcText.left));
1745
1746       if((lStyle & TCS_VERTICAL) && (lStyle & TCS_BOTTOM))
1747       {
1748         rcImage.top = drawRect->top + center_offset_h;
1749         /* if tab is TCS_VERTICAL and TCS_BOTTOM, the text is drawn from the */
1750         /* right side of the tab, but the image still uses the left as its x position */
1751         /* this keeps the image always drawn off of the same side of the tab */
1752         rcImage.left = drawRect->right - cx - center_offset_v;
1753         drawRect->top += cy + infoPtr->uHItemPadding;
1754       }
1755       else if(lStyle & TCS_VERTICAL)
1756       {
1757         rcImage.top  = drawRect->bottom - cy - center_offset_h;
1758         rcImage.left = drawRect->left + center_offset_v;
1759         drawRect->bottom -= cy + infoPtr->uHItemPadding;
1760       }
1761       else /* normal style, whether TCS_BOTTOM or not */
1762       {
1763         rcImage.left = drawRect->left + center_offset_h;
1764         rcImage.top = drawRect->top + center_offset_v;
1765         drawRect->left += cx + infoPtr->uHItemPadding;
1766       }
1767
1768       TRACE("drawing image=%d, left=%d, top=%d\n",
1769             item->iImage, rcImage.left, rcImage.top-1);
1770       ImageList_Draw
1771         (
1772         infoPtr->himl,
1773         item->iImage,
1774         hdc,
1775         rcImage.left,
1776         rcImage.top,
1777         ILD_NORMAL
1778         );
1779     }
1780
1781     /* Now position text */
1782     if (lStyle & TCS_FIXEDWIDTH && lStyle & TCS_FORCELABELLEFT)
1783       center_offset_h = infoPtr->uHItemPadding;
1784     else
1785       if(lStyle & TCS_VERTICAL)
1786         center_offset_h = ((drawRect->bottom - drawRect->top) - (rcText.right - rcText.left)) / 2;
1787       else
1788         center_offset_h = ((drawRect->right - drawRect->left) - (rcText.right - rcText.left)) / 2;
1789
1790     if(lStyle & TCS_VERTICAL)
1791     {
1792       if(lStyle & TCS_BOTTOM)
1793         drawRect->top+=center_offset_h;
1794       else
1795         drawRect->bottom-=center_offset_h;
1796
1797       center_offset_v = ((drawRect->right - drawRect->left) - (rcText.bottom - rcText.top)) / 2;
1798     }
1799     else
1800     {
1801       drawRect->left += center_offset_h;
1802       center_offset_v = ((drawRect->bottom - drawRect->top) - (rcText.bottom - rcText.top)) / 2;
1803     }
1804
1805     /* if an item is selected, the text is shifted up instead of down */
1806     if (iItem == infoPtr->iSelected)
1807         center_offset_v -= infoPtr->uVItemPadding / 2;
1808     else
1809         center_offset_v += infoPtr->uVItemPadding / 2;
1810
1811     if (center_offset_v < 0)
1812       center_offset_v = 0;
1813
1814     if(lStyle & TCS_VERTICAL)
1815       drawRect->left += center_offset_v;
1816     else
1817       drawRect->top += center_offset_v;
1818
1819     /* Draw the text */
1820     if(lStyle & TCS_VERTICAL) /* if we are vertical rotate the text and each character */
1821     {
1822       static const WCHAR ArialW[] = { 'A','r','i','a','l',0 };
1823       LOGFONTW logfont;
1824       HFONT hFont = 0;
1825       INT nEscapement = 900;
1826       INT nOrientation = 900;
1827
1828       if(lStyle & TCS_BOTTOM)
1829       {
1830         nEscapement = -900;
1831         nOrientation = -900;
1832       }
1833
1834       /* to get a font with the escapement and orientation we are looking for, we need to */
1835       /* call CreateFontIndirectA, which requires us to set the values of the logfont we pass in */
1836       if (!GetObjectW((infoPtr->hFont) ?
1837                 infoPtr->hFont : GetStockObject(SYSTEM_FONT),
1838                 sizeof(LOGFONTW),&logfont))
1839       {
1840         INT iPointSize = 9;
1841
1842         lstrcpyW(logfont.lfFaceName, ArialW);
1843         logfont.lfHeight = -MulDiv(iPointSize, GetDeviceCaps(hdc, LOGPIXELSY),
1844                                     72);
1845         logfont.lfWeight = FW_NORMAL;
1846         logfont.lfItalic = 0;
1847         logfont.lfUnderline = 0;
1848         logfont.lfStrikeOut = 0;
1849       }
1850
1851       logfont.lfEscapement = nEscapement;
1852       logfont.lfOrientation = nOrientation;
1853       hFont = CreateFontIndirectW(&logfont);
1854       SelectObject(hdc, hFont);
1855
1856       if (item->pszText)
1857       {
1858         ExtTextOutW(hdc,
1859         (lStyle & TCS_BOTTOM) ? drawRect->right : drawRect->left,
1860         (!(lStyle & TCS_BOTTOM)) ? drawRect->bottom : drawRect->top,
1861         ETO_CLIPPED,
1862         drawRect,
1863         item->pszText,
1864         lstrlenW(item->pszText),
1865         0);
1866       }
1867
1868       DeleteObject(hFont);
1869     }
1870     else
1871     {
1872       TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%s), textlen=%d\n",
1873           debugstr_w(item->pszText), center_offset_h, center_offset_v,
1874           wine_dbgstr_rect(drawRect), (rcText.right-rcText.left));
1875       if (item->pszText)
1876       {
1877         DrawTextW
1878         (
1879           hdc,
1880           item->pszText,
1881           lstrlenW(item->pszText),
1882           drawRect,
1883           DT_LEFT | DT_SINGLELINE
1884         );
1885       }
1886     }
1887
1888     *drawRect = rcTemp; /* restore drawRect */
1889   }
1890
1891   /*
1892   * Cleanup
1893   */
1894   SelectObject(hdc, hOldFont);
1895   SetBkMode(hdc, oldBkMode);
1896   SelectObject(hdc, holdPen);
1897   DeleteObject( htextPen );
1898 }
1899
1900 /******************************************************************************
1901  * TAB_DrawItem
1902  *
1903  * This method is used to draw a single tab into the tab control.
1904  */
1905 static void TAB_DrawItem(const TAB_INFO *infoPtr, HDC  hdc, INT  iItem)
1906 {
1907   LONG      lStyle  = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1908   RECT      itemRect;
1909   RECT      selectedRect;
1910   BOOL      isVisible;
1911   RECT      r, fillRect, r1;
1912   INT       clRight = 0;
1913   INT       clBottom = 0;
1914   COLORREF  bkgnd, corner;
1915   HTHEME    theme;
1916
1917   /*
1918    * Get the rectangle for the item.
1919    */
1920   isVisible = TAB_InternalGetItemRect(infoPtr,
1921                                       iItem,
1922                                       &itemRect,
1923                                       &selectedRect);
1924
1925   if (isVisible)
1926   {
1927     RECT rUD, rC;
1928
1929     /* Clip UpDown control to not draw over it */
1930     if (infoPtr->needsScrolling)
1931     {
1932       GetWindowRect(infoPtr->hwnd, &rC);
1933       GetWindowRect(infoPtr->hwndUpDown, &rUD);
1934       ExcludeClipRect(hdc, rUD.left - rC.left, rUD.top - rC.top, rUD.right - rC.left, rUD.bottom - rC.top);
1935     }
1936
1937     /* If you need to see what the control is doing,
1938      * then override these variables. They will change what
1939      * fill colors are used for filling the tabs, and the
1940      * corners when drawing the edge.
1941      */
1942     bkgnd = comctl32_color.clrBtnFace;
1943     corner = comctl32_color.clrBtnFace;
1944
1945     if (lStyle & TCS_BUTTONS)
1946     {
1947       /* Get item rectangle */
1948       r = itemRect;
1949
1950       /* Separators between flat buttons */
1951       if (lStyle & TCS_FLATBUTTONS)
1952       {
1953         r1 = r;
1954         r1.right += (FLAT_BTN_SPACINGX -2);
1955         DrawEdge(hdc, &r1, EDGE_ETCHED, BF_RIGHT);
1956       }
1957
1958       if (iItem == infoPtr->iSelected)
1959       {
1960         DrawEdge(hdc, &r, EDGE_SUNKEN, BF_SOFT|BF_RECT);
1961         
1962         OffsetRect(&r, 1, 1);
1963       }
1964       else  /* ! selected */
1965       {
1966         if (!(lStyle & TCS_FLATBUTTONS))
1967           DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_RECT);
1968       }
1969     }
1970     else /* !TCS_BUTTONS */
1971     {
1972       /* We draw a rectangle of different sizes depending on the selection
1973        * state. */
1974       if (iItem == infoPtr->iSelected) {
1975         RECT rect;
1976         GetClientRect (infoPtr->hwnd, &rect);
1977         clRight = rect.right;
1978         clBottom = rect.bottom;
1979         r = selectedRect;
1980       }
1981       else
1982         r = itemRect;
1983
1984       /*
1985        * Erase the background. (Delay it but setup rectangle.)
1986        * This is necessary when drawing the selected item since it is larger
1987        * than the others, it might overlap with stuff already drawn by the
1988        * other tabs
1989        */
1990       fillRect = r;
1991
1992       /* Draw themed tabs - but only if they are at the top.
1993        * Windows draws even side or bottom tabs themed, with wacky results.
1994        * However, since in Wine apps may get themed that did not opt in via
1995        * a manifest avoid theming when we know the result will be wrong */
1996       if ((theme = GetWindowTheme (infoPtr->hwnd)) 
1997           && ((lStyle & (TCS_VERTICAL | TCS_BOTTOM)) == 0))
1998       {
1999           static const int partIds[8] = {
2000               /* Normal item */
2001               TABP_TABITEM,
2002               TABP_TABITEMLEFTEDGE,
2003               TABP_TABITEMRIGHTEDGE,
2004               TABP_TABITEMBOTHEDGE,
2005               /* Selected tab */
2006               TABP_TOPTABITEM,
2007               TABP_TOPTABITEMLEFTEDGE,
2008               TABP_TOPTABITEMRIGHTEDGE,
2009               TABP_TOPTABITEMBOTHEDGE,
2010           };
2011           int partIndex = 0;
2012           int stateId = TIS_NORMAL;
2013
2014           /* selected and unselected tabs have different parts */
2015           if (iItem == infoPtr->iSelected)
2016               partIndex += 4;
2017           /* The part also differs on the position of a tab on a line.
2018            * "Visually" determining the position works well enough. */
2019           if(selectedRect.left == 0)
2020               partIndex += 1;
2021           if(selectedRect.right == clRight)
2022               partIndex += 2;
2023
2024           if (iItem == infoPtr->iSelected)
2025               stateId = TIS_SELECTED;
2026           else if (iItem == infoPtr->iHotTracked)
2027               stateId = TIS_HOT;
2028           else if (iItem == infoPtr->uFocus)
2029               stateId = TIS_FOCUSED;
2030
2031           /* Adjust rectangle for bottommost row */
2032           if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2033             r.bottom += 3;
2034
2035           DrawThemeBackground (theme, hdc, partIds[partIndex], stateId, &r, NULL);
2036           GetThemeBackgroundContentRect (theme, hdc, partIds[partIndex], stateId, &r, &r);
2037       }
2038       else if(lStyle & TCS_VERTICAL)
2039       {
2040         /* These are for adjusting the drawing of a Selected tab      */
2041         /* The initial values are for the normal case of non-Selected */
2042         int ZZ = 1;   /* Do not stretch if selected */
2043         if (iItem == infoPtr->iSelected) {
2044             ZZ = 0;
2045
2046             /* if leftmost draw the line longer */
2047             if(selectedRect.top == 0)
2048                 fillRect.top += CONTROL_BORDER_SIZEY;
2049             /* if rightmost draw the line longer */
2050             if(selectedRect.bottom == clBottom)
2051                 fillRect.bottom -= CONTROL_BORDER_SIZEY;
2052         }
2053
2054         if (lStyle & TCS_BOTTOM)
2055         {
2056           /* Adjust both rectangles to match native */
2057           r.left += (1-ZZ);
2058
2059           TRACE("<right> item=%d, fill=(%s), edge=(%s)\n",
2060                 iItem, wine_dbgstr_rect(&fillRect), wine_dbgstr_rect(&r));
2061
2062           /* Clear interior */
2063           SetBkColor(hdc, bkgnd);
2064           ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2065
2066           /* Draw rectangular edge around tab */
2067           DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_RIGHT|BF_TOP|BF_BOTTOM);
2068
2069           /* Now erase the top corner and draw diagonal edge */
2070           SetBkColor(hdc, corner);
2071           r1.left = r.right - ROUND_CORNER_SIZE - 1;
2072           r1.top = r.top;
2073           r1.right = r.right;
2074           r1.bottom = r1.top + ROUND_CORNER_SIZE;
2075           ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2076           r1.right--;
2077           DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPLEFT);
2078
2079           /* Now erase the bottom corner and draw diagonal edge */
2080           r1.left = r.right - ROUND_CORNER_SIZE - 1;
2081           r1.bottom = r.bottom;
2082           r1.right = r.right;
2083           r1.top = r1.bottom - ROUND_CORNER_SIZE;
2084           ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2085           r1.right--;
2086           DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMLEFT);
2087
2088           if ((iItem == infoPtr->iSelected) && (selectedRect.top == 0)) {
2089               r1 = r;
2090               r1.right = r1.left;
2091               r1.left--;
2092               DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_TOP);
2093           }
2094
2095         }
2096         else
2097         {
2098           TRACE("<left> item=%d, fill=(%s), edge=(%s)\n",
2099                 iItem, wine_dbgstr_rect(&fillRect), wine_dbgstr_rect(&r));
2100
2101           /* Clear interior */
2102           SetBkColor(hdc, bkgnd);
2103           ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2104
2105           /* Draw rectangular edge around tab */
2106           DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_TOP|BF_BOTTOM);
2107
2108           /* Now erase the top corner and draw diagonal edge */
2109           SetBkColor(hdc, corner);
2110           r1.left = r.left;
2111           r1.top = r.top;
2112           r1.right = r1.left + ROUND_CORNER_SIZE + 1;
2113           r1.bottom = r1.top + ROUND_CORNER_SIZE;
2114           ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2115           r1.left++;
2116           DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPRIGHT);
2117
2118           /* Now erase the bottom corner and draw diagonal edge */
2119           r1.left = r.left;
2120           r1.bottom = r.bottom;
2121           r1.right = r1.left + ROUND_CORNER_SIZE + 1;
2122           r1.top = r1.bottom - ROUND_CORNER_SIZE;
2123           ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2124           r1.left++;
2125           DrawEdge(hdc, &r1, EDGE_SUNKEN, BF_DIAGONAL_ENDTOPLEFT);
2126         }
2127       }
2128       else  /* ! TCS_VERTICAL */
2129       {
2130         /* These are for adjusting the drawing of a Selected tab      */
2131         /* The initial values are for the normal case of non-Selected */
2132         if (iItem == infoPtr->iSelected) {
2133             /* if leftmost draw the line longer */
2134             if(selectedRect.left == 0)
2135                 fillRect.left += CONTROL_BORDER_SIZEX;
2136             /* if rightmost draw the line longer */
2137             if(selectedRect.right == clRight)
2138                 fillRect.right -= CONTROL_BORDER_SIZEX;
2139         }
2140
2141         if (lStyle & TCS_BOTTOM)
2142         {
2143           /* Adjust both rectangles for topmost row */
2144           if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2145           {
2146             fillRect.top -= 2;
2147             r.top -= 1;
2148           }
2149
2150           TRACE("<bottom> item=%d, fill=(%s), edge=(%s)\n",
2151                 iItem, wine_dbgstr_rect(&fillRect), wine_dbgstr_rect(&r));
2152
2153           /* Clear interior */
2154           SetBkColor(hdc, bkgnd);
2155           ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2156
2157           /* Draw rectangular edge around tab */
2158           DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_BOTTOM|BF_RIGHT);
2159
2160           /* Now erase the righthand corner and draw diagonal edge */
2161           SetBkColor(hdc, corner);
2162           r1.left = r.right - ROUND_CORNER_SIZE;
2163           r1.bottom = r.bottom;
2164           r1.right = r.right;
2165           r1.top = r1.bottom - ROUND_CORNER_SIZE - 1;
2166           ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2167           r1.bottom--;
2168           DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMLEFT);
2169
2170           /* Now erase the lefthand corner and draw diagonal edge */
2171           r1.left = r.left;
2172           r1.bottom = r.bottom;
2173           r1.right = r1.left + ROUND_CORNER_SIZE;
2174           r1.top = r1.bottom - ROUND_CORNER_SIZE - 1;
2175           ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2176           r1.bottom--;
2177           DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPLEFT);
2178
2179           if (iItem == infoPtr->iSelected)
2180           {
2181             r.top += 2;
2182             r.left += 1;
2183             if (selectedRect.left == 0)
2184             {
2185               r1 = r;
2186               r1.bottom = r1.top;
2187               r1.top--;
2188               DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_LEFT);
2189             }
2190           }
2191
2192         }
2193         else
2194         {
2195           /* Adjust both rectangles for bottommost row */
2196           if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2197           {
2198             fillRect.bottom += 3;
2199             r.bottom += 2;
2200           }
2201
2202           TRACE("<top> item=%d, fill=(%s), edge=(%s)\n",
2203                 iItem, wine_dbgstr_rect(&fillRect), wine_dbgstr_rect(&r));
2204
2205           /* Clear interior */
2206           SetBkColor(hdc, bkgnd);
2207           ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2208
2209           /* Draw rectangular edge around tab */
2210           DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_TOP|BF_RIGHT);
2211
2212           /* Now erase the righthand corner and draw diagonal edge */
2213           SetBkColor(hdc, corner);
2214           r1.left = r.right - ROUND_CORNER_SIZE;
2215           r1.top = r.top;
2216           r1.right = r.right;
2217           r1.bottom = r1.top + ROUND_CORNER_SIZE + 1;
2218           ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2219           r1.top++;
2220           DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMRIGHT);
2221
2222           /* Now erase the lefthand corner and draw diagonal edge */
2223           r1.left = r.left;
2224           r1.top = r.top;
2225           r1.right = r1.left + ROUND_CORNER_SIZE;
2226           r1.bottom = r1.top + ROUND_CORNER_SIZE + 1;
2227           ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2228           r1.top++;
2229           DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPRIGHT);
2230         }
2231       }
2232     }
2233
2234     TAB_DumpItemInternal(infoPtr, iItem);
2235
2236     /* This modifies r to be the text rectangle. */
2237     TAB_DrawItemInterior(infoPtr, hdc, iItem, &r);
2238   }
2239 }
2240
2241 /******************************************************************************
2242  * TAB_DrawBorder
2243  *
2244  * This method is used to draw the raised border around the tab control
2245  * "content" area.
2246  */
2247 static void TAB_DrawBorder(const TAB_INFO *infoPtr, HDC hdc)
2248 {
2249   RECT rect;
2250   DWORD lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
2251   HTHEME theme = GetWindowTheme (infoPtr->hwnd);
2252
2253   GetClientRect (infoPtr->hwnd, &rect);
2254
2255   /*
2256    * Adjust for the style
2257    */
2258
2259   if (infoPtr->uNumItem)
2260   {
2261     if ((lStyle & TCS_BOTTOM) && !(lStyle & TCS_VERTICAL))
2262       rect.bottom -= infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2263     else if((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
2264       rect.right  -= infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2265     else if(lStyle & TCS_VERTICAL)
2266       rect.left   += infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2267     else /* not TCS_VERTICAL and not TCS_BOTTOM */
2268       rect.top    += infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2269   }
2270
2271   TRACE("border=(%s)\n", wine_dbgstr_rect(&rect));
2272
2273   if (theme)
2274       DrawThemeBackground (theme, hdc, TABP_PANE, 0, &rect, NULL);
2275   else
2276       DrawEdge(hdc, &rect, EDGE_RAISED, BF_SOFT|BF_RECT);
2277 }
2278
2279 /******************************************************************************
2280  * TAB_Refresh
2281  *
2282  * This method repaints the tab control..
2283  */
2284 static void TAB_Refresh (TAB_INFO *infoPtr, HDC hdc)
2285 {
2286   HFONT hOldFont;
2287   INT i;
2288
2289   if (!infoPtr->DoRedraw)
2290     return;
2291
2292   hOldFont = SelectObject (hdc, infoPtr->hFont);
2293
2294   if (GetWindowLongW(infoPtr->hwnd, GWL_STYLE) & TCS_BUTTONS)
2295   {
2296     for (i = 0; i < infoPtr->uNumItem; i++)
2297       TAB_DrawItem (infoPtr, hdc, i);
2298   }
2299   else
2300   {
2301     /* Draw all the non selected item first */
2302     for (i = 0; i < infoPtr->uNumItem; i++)
2303     {
2304       if (i != infoPtr->iSelected)
2305         TAB_DrawItem (infoPtr, hdc, i);
2306     }
2307
2308     /* Now, draw the border, draw it before the selected item
2309      * since the selected item overwrites part of the border. */
2310     TAB_DrawBorder (infoPtr, hdc);
2311
2312     /* Then, draw the selected item */
2313     TAB_DrawItem (infoPtr, hdc, infoPtr->iSelected);
2314   }
2315
2316   SelectObject (hdc, hOldFont);
2317 }
2318
2319 static inline DWORD TAB_GetRowCount (const TAB_INFO *infoPtr)
2320 {
2321   return infoPtr->uNumRows;
2322 }
2323
2324 static inline LRESULT TAB_SetRedraw (TAB_INFO *infoPtr, BOOL doRedraw)
2325 {
2326   infoPtr->DoRedraw = doRedraw;
2327   return 0;
2328 }
2329
2330 /******************************************************************************
2331  * TAB_EnsureSelectionVisible
2332  *
2333  * This method will make sure that the current selection is completely
2334  * visible by scrolling until it is.
2335  */
2336 static void TAB_EnsureSelectionVisible(
2337   TAB_INFO* infoPtr)
2338 {
2339   INT iSelected = infoPtr->iSelected;
2340   LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
2341   INT iOrigLeftmostVisible = infoPtr->leftmostVisible;
2342
2343   /* set the items row to the bottommost row or topmost row depending on
2344    * style */
2345   if ((infoPtr->uNumRows > 1) && !(lStyle & TCS_BUTTONS))
2346   {
2347       TAB_ITEM *selected = TAB_GetItem(infoPtr, iSelected);
2348       INT newselected;
2349       INT iTargetRow;
2350
2351       if(lStyle & TCS_VERTICAL)
2352         newselected = selected->rect.left;
2353       else
2354         newselected = selected->rect.top;
2355
2356       /* the target row is always (number of rows - 1)
2357          as row 0 is furthest from the clientRect */
2358       iTargetRow = infoPtr->uNumRows - 1;
2359
2360       if (newselected != iTargetRow)
2361       {
2362          UINT i;
2363          if(lStyle & TCS_VERTICAL)
2364          {
2365            for (i=0; i < infoPtr->uNumItem; i++)
2366            {
2367              /* move everything in the row of the selected item to the iTargetRow */
2368              TAB_ITEM *item = TAB_GetItem(infoPtr, i);
2369
2370              if (item->rect.left == newselected )
2371                  item->rect.left = iTargetRow;
2372              else
2373              {
2374                if (item->rect.left > newselected)
2375                  item->rect.left-=1;
2376              }
2377            }
2378          }
2379          else
2380          {
2381            for (i=0; i < infoPtr->uNumItem; i++)
2382            {
2383              TAB_ITEM *item = TAB_GetItem(infoPtr, i);
2384
2385              if (item->rect.top == newselected )
2386                  item->rect.top = iTargetRow;
2387              else
2388              {
2389                if (item->rect.top > newselected)
2390                  item->rect.top-=1;
2391              }
2392           }
2393         }
2394         TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
2395       }
2396   }
2397
2398   /*
2399    * Do the trivial cases first.
2400    */
2401   if ( (!infoPtr->needsScrolling) ||
2402        (infoPtr->hwndUpDown==0) || (lStyle & TCS_VERTICAL))
2403     return;
2404
2405   if (infoPtr->leftmostVisible >= iSelected)
2406   {
2407     infoPtr->leftmostVisible = iSelected;
2408   }
2409   else
2410   {
2411      TAB_ITEM *selected = TAB_GetItem(infoPtr, iSelected);
2412      RECT r;
2413      INT width;
2414      UINT i;
2415
2416      /* Calculate the part of the client area that is visible */
2417      GetClientRect(infoPtr->hwnd, &r);
2418      width = r.right;
2419
2420      GetClientRect(infoPtr->hwndUpDown, &r);
2421      width -= r.right;
2422
2423      if ((selected->rect.right -
2424           selected->rect.left) >= width )
2425      {
2426         /* Special case: width of selected item is greater than visible
2427          * part of control.
2428          */
2429         infoPtr->leftmostVisible = iSelected;
2430      }
2431      else
2432      {
2433         for (i = infoPtr->leftmostVisible; i < infoPtr->uNumItem; i++)
2434         {
2435            if ((selected->rect.right - TAB_GetItem(infoPtr, i)->rect.left) < width)
2436               break;
2437         }
2438         infoPtr->leftmostVisible = i;
2439      }
2440   }
2441
2442   if (infoPtr->leftmostVisible != iOrigLeftmostVisible)
2443     TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
2444
2445   SendMessageW(infoPtr->hwndUpDown, UDM_SETPOS, 0,
2446                MAKELONG(infoPtr->leftmostVisible, 0));
2447 }
2448
2449 /******************************************************************************
2450  * TAB_InvalidateTabArea
2451  *
2452  * This method will invalidate the portion of the control that contains the
2453  * tabs. It is called when the state of the control changes and needs
2454  * to be redisplayed
2455  */
2456 static void TAB_InvalidateTabArea(const TAB_INFO *infoPtr)
2457 {
2458   RECT clientRect, rInvalidate, rAdjClient;
2459   DWORD lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
2460   INT lastRow = infoPtr->uNumRows - 1;
2461   RECT rect;
2462
2463   if (lastRow < 0) return;
2464
2465   GetClientRect(infoPtr->hwnd, &clientRect);
2466   rInvalidate = clientRect;
2467   rAdjClient = clientRect;
2468
2469   TAB_AdjustRect(infoPtr, 0, &rAdjClient);
2470
2471   TAB_InternalGetItemRect(infoPtr, infoPtr->uNumItem-1 , &rect, NULL);
2472   if ((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
2473   {
2474     rInvalidate.left = rAdjClient.right;
2475     if (infoPtr->uNumRows == 1)
2476       rInvalidate.bottom = clientRect.top + rect.bottom + 2 * SELECTED_TAB_OFFSET;
2477   }
2478   else if(lStyle & TCS_VERTICAL)
2479   {
2480     rInvalidate.right = rAdjClient.left;
2481     if (infoPtr->uNumRows == 1)
2482       rInvalidate.bottom = clientRect.top + rect.bottom + 2 * SELECTED_TAB_OFFSET;
2483   }
2484   else if (lStyle & TCS_BOTTOM)
2485   {
2486     rInvalidate.top = rAdjClient.bottom;
2487     if (infoPtr->uNumRows == 1)
2488       rInvalidate.right = clientRect.left + rect.right + 2 * SELECTED_TAB_OFFSET;
2489   }
2490   else 
2491   {
2492     rInvalidate.bottom = rAdjClient.top;
2493     if (infoPtr->uNumRows == 1)
2494       rInvalidate.right = clientRect.left + rect.right + 2 * SELECTED_TAB_OFFSET;
2495   }
2496   
2497   /* Punch out the updown control */
2498   if (infoPtr->needsScrolling && (rInvalidate.right > 0)) {
2499     RECT r;
2500     GetClientRect(infoPtr->hwndUpDown, &r);
2501     if (rInvalidate.right > clientRect.right - r.left)
2502       rInvalidate.right = rInvalidate.right - (r.right - r.left);
2503     else
2504       rInvalidate.right = clientRect.right - r.left;
2505   }
2506
2507   TRACE("invalidate (%s)\n", wine_dbgstr_rect(&rInvalidate));
2508
2509   InvalidateRect(infoPtr->hwnd, &rInvalidate, TRUE);
2510 }
2511
2512 static inline LRESULT TAB_Paint (TAB_INFO *infoPtr, HDC hdcPaint)
2513 {
2514   HDC hdc;
2515   PAINTSTRUCT ps;
2516
2517   if (hdcPaint)
2518     hdc = hdcPaint;
2519   else
2520   {
2521     hdc = BeginPaint (infoPtr->hwnd, &ps);
2522     TRACE("erase %d, rect=(%s)\n", ps.fErase, wine_dbgstr_rect(&ps.rcPaint));
2523   }
2524
2525   TAB_Refresh (infoPtr, hdc);
2526
2527   if (!hdcPaint)
2528     EndPaint (infoPtr->hwnd, &ps);
2529
2530   return 0;
2531 }
2532
2533 static LRESULT
2534 TAB_InsertItemT (TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam, BOOL bUnicode)
2535 {
2536   TAB_ITEM *item;
2537   TCITEMW *pti;
2538   INT iItem;
2539   RECT rect;
2540
2541   GetClientRect (infoPtr->hwnd, &rect);
2542   TRACE("Rect: %p %s\n", infoPtr->hwnd, wine_dbgstr_rect(&rect));
2543
2544   pti = (TCITEMW *)lParam;
2545   iItem = (INT)wParam;
2546
2547   if (iItem < 0) return -1;
2548   if (iItem > infoPtr->uNumItem)
2549     iItem = infoPtr->uNumItem;
2550
2551   TAB_DumpItemExternalT(pti, iItem, bUnicode);
2552
2553
2554   if (infoPtr->uNumItem == 0) {
2555     infoPtr->items = Alloc (TAB_ITEM_SIZE(infoPtr));
2556     infoPtr->uNumItem++;
2557     infoPtr->iSelected = 0;
2558   }
2559   else {
2560     LPBYTE oldItems = (LPBYTE)infoPtr->items;
2561
2562     infoPtr->uNumItem++;
2563     infoPtr->items = Alloc (TAB_ITEM_SIZE(infoPtr) * infoPtr->uNumItem);
2564
2565     /* pre insert copy */
2566     if (iItem > 0) {
2567       memcpy (infoPtr->items, oldItems,
2568               iItem * TAB_ITEM_SIZE(infoPtr));
2569     }
2570
2571     /* post insert copy */
2572     if (iItem < infoPtr->uNumItem - 1) {
2573       memcpy (TAB_GetItem(infoPtr, iItem + 1),
2574               oldItems + iItem * TAB_ITEM_SIZE(infoPtr),
2575               (infoPtr->uNumItem - iItem - 1) * TAB_ITEM_SIZE(infoPtr));
2576
2577     }
2578
2579     if (iItem <= infoPtr->iSelected)
2580       infoPtr->iSelected++;
2581
2582     Free (oldItems);
2583   }
2584
2585   item = TAB_GetItem(infoPtr, iItem);
2586
2587   item->pszText = NULL;
2588
2589   if (pti->mask & TCIF_TEXT)
2590   {
2591     if (bUnicode)
2592       Str_SetPtrW (&item->pszText, pti->pszText);
2593     else
2594       Str_SetPtrAtoW (&item->pszText, (LPSTR)pti->pszText);
2595   }
2596
2597   if (pti->mask & TCIF_IMAGE)
2598     item->iImage = pti->iImage;
2599   else
2600     item->iImage = -1;
2601
2602   if (pti->mask & TCIF_PARAM)
2603     memcpy(item->extra, &pti->lParam, infoPtr->cbInfo);
2604   else
2605     memset(item->extra, 0, infoPtr->cbInfo);
2606   
2607   TAB_SetItemBounds(infoPtr);
2608   if (infoPtr->uNumItem > 1)
2609     TAB_InvalidateTabArea(infoPtr);
2610   else
2611     InvalidateRect(infoPtr->hwnd, NULL, TRUE);
2612
2613   TRACE("[%p]: added item %d %s\n",
2614         infoPtr->hwnd, iItem, debugstr_w(item->pszText));
2615
2616   /* If we haven't set the current focus yet, set it now. */
2617   if (infoPtr->uFocus == -1)
2618     TAB_SetCurFocus(infoPtr, iItem);
2619
2620   return iItem;
2621 }
2622
2623 static LRESULT
2624 TAB_SetItemSize (TAB_INFO *infoPtr, LPARAM lParam)
2625 {
2626   LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
2627   LONG lResult = 0;
2628   BOOL bNeedPaint = FALSE;
2629
2630   lResult = MAKELONG(infoPtr->tabWidth, infoPtr->tabHeight);
2631
2632   /* UNDOCUMENTED: If requested Width or Height is 0 this means that program wants to use auto size. */
2633   if (lStyle & TCS_FIXEDWIDTH && (infoPtr->tabWidth != (INT)LOWORD(lParam)))
2634   {
2635     infoPtr->tabWidth = (INT)LOWORD(lParam);
2636     bNeedPaint = TRUE;
2637   }
2638
2639   if (infoPtr->tabHeight != (INT)HIWORD(lParam))
2640   {
2641     if ((infoPtr->fHeightSet = ((INT)HIWORD(lParam) != 0)))
2642       infoPtr->tabHeight = (INT)HIWORD(lParam);
2643
2644     bNeedPaint = TRUE;
2645   }
2646   TRACE("was h=%d,w=%d, now h=%d,w=%d\n",
2647        HIWORD(lResult), LOWORD(lResult),
2648        infoPtr->tabHeight, infoPtr->tabWidth);
2649
2650   if (bNeedPaint)
2651   {
2652     TAB_SetItemBounds(infoPtr);
2653     RedrawWindow(infoPtr->hwnd, NULL, NULL, RDW_ERASE | RDW_INVALIDATE | RDW_UPDATENOW);
2654   }
2655
2656   return lResult;
2657 }
2658
2659 static inline LRESULT TAB_SetMinTabWidth (TAB_INFO *infoPtr, INT cx)
2660 {
2661   INT oldcx = 0;
2662
2663   TRACE("(%p,%d)\n", infoPtr, cx);
2664
2665   if (infoPtr->tabMinWidth < 0)
2666     oldcx = DEFAULT_MIN_TAB_WIDTH;
2667   else
2668     oldcx = infoPtr->tabMinWidth;
2669   infoPtr->tabMinWidth = cx;
2670   TAB_SetItemBounds(infoPtr);
2671   return oldcx;
2672 }
2673
2674 static inline LRESULT 
2675 TAB_HighlightItem (TAB_INFO *infoPtr, INT iItem, BOOL fHighlight)
2676 {
2677   LPDWORD lpState;
2678
2679   TRACE("(%p,%d,%s)\n", infoPtr, iItem, fHighlight ? "true" : "false");
2680
2681   if (!infoPtr || iItem < 0 || iItem >= infoPtr->uNumItem)
2682     return FALSE;
2683   
2684   lpState = &TAB_GetItem(infoPtr, iItem)->dwState;
2685
2686   if (fHighlight)
2687     *lpState |= TCIS_HIGHLIGHTED;
2688   else
2689     *lpState &= ~TCIS_HIGHLIGHTED;
2690
2691   return TRUE;
2692 }
2693
2694 static LRESULT
2695 TAB_SetItemT (TAB_INFO *infoPtr, INT iItem, LPTCITEMW tabItem, BOOL bUnicode)
2696 {
2697   TAB_ITEM *wineItem;
2698
2699   TRACE("(%p,%d,%p,%s)\n", infoPtr, iItem, tabItem, bUnicode ? "true" : "false");
2700
2701   if (iItem < 0 || iItem >= infoPtr->uNumItem)
2702     return FALSE;
2703
2704   TAB_DumpItemExternalT(tabItem, iItem, bUnicode);
2705
2706   wineItem = TAB_GetItem(infoPtr, iItem);
2707
2708   if (tabItem->mask & TCIF_IMAGE)
2709     wineItem->iImage = tabItem->iImage;
2710
2711   if (tabItem->mask & TCIF_PARAM)
2712     memcpy(wineItem->extra, &tabItem->lParam, infoPtr->cbInfo);
2713
2714   if (tabItem->mask & TCIF_RTLREADING)
2715     FIXME("TCIF_RTLREADING\n");
2716
2717   if (tabItem->mask & TCIF_STATE)
2718     wineItem->dwState = tabItem->dwState;
2719
2720   if (tabItem->mask & TCIF_TEXT)
2721   {
2722     Free(wineItem->pszText);
2723     wineItem->pszText = NULL;
2724     if (bUnicode)
2725       Str_SetPtrW(&wineItem->pszText, tabItem->pszText);
2726     else
2727       Str_SetPtrAtoW(&wineItem->pszText, (LPSTR)tabItem->pszText);
2728   }
2729
2730   /* Update and repaint tabs */
2731   TAB_SetItemBounds(infoPtr);
2732   TAB_InvalidateTabArea(infoPtr);
2733
2734   return TRUE;
2735 }
2736
2737 static inline LRESULT TAB_GetItemCount (const TAB_INFO *infoPtr)
2738 {
2739    return infoPtr->uNumItem;
2740 }
2741
2742
2743 static LRESULT
2744 TAB_GetItemT (TAB_INFO *infoPtr, INT iItem, LPTCITEMW tabItem, BOOL bUnicode)
2745 {
2746   TAB_ITEM *wineItem;
2747
2748   TRACE("(%p,%d,%p,%s)\n", infoPtr, iItem, tabItem, bUnicode ? "true" : "false");
2749
2750   if (iItem < 0 || iItem >= infoPtr->uNumItem)
2751     return FALSE;
2752
2753   wineItem = TAB_GetItem(infoPtr, iItem);
2754
2755   if (tabItem->mask & TCIF_IMAGE)
2756     tabItem->iImage = wineItem->iImage;
2757
2758   if (tabItem->mask & TCIF_PARAM)
2759     memcpy(&tabItem->lParam, wineItem->extra, infoPtr->cbInfo);
2760
2761   if (tabItem->mask & TCIF_RTLREADING)
2762     FIXME("TCIF_RTLREADING\n");
2763
2764   if (tabItem->mask & TCIF_STATE)
2765     tabItem->dwState = wineItem->dwState;
2766
2767   if (tabItem->mask & TCIF_TEXT)
2768   {
2769     if (bUnicode)
2770       Str_GetPtrW (wineItem->pszText, tabItem->pszText, tabItem->cchTextMax);
2771     else
2772       Str_GetPtrWtoA (wineItem->pszText, (LPSTR)tabItem->pszText, tabItem->cchTextMax);
2773   }
2774
2775   TAB_DumpItemExternalT(tabItem, iItem, bUnicode);
2776
2777   return TRUE;
2778 }
2779
2780
2781 static LRESULT TAB_DeleteItem (TAB_INFO *infoPtr, INT iItem)
2782 {
2783     BOOL bResult = FALSE;
2784
2785     TRACE("(%p, %d)\n", infoPtr, iItem);
2786
2787     if ((iItem >= 0) && (iItem < infoPtr->uNumItem))
2788     {
2789         TAB_ITEM *item = TAB_GetItem(infoPtr, iItem);
2790         LPBYTE oldItems = (LPBYTE)infoPtr->items;
2791
2792         TAB_InvalidateTabArea(infoPtr);
2793         Free(item->pszText);
2794         infoPtr->uNumItem--;
2795
2796         if (!infoPtr->uNumItem)
2797         {
2798             infoPtr->items = NULL;
2799             if (infoPtr->iHotTracked >= 0)
2800             {
2801                 KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
2802                 infoPtr->iHotTracked = -1;
2803             }
2804         }
2805         else
2806         {
2807             infoPtr->items = Alloc(TAB_ITEM_SIZE(infoPtr) * infoPtr->uNumItem);
2808
2809             if (iItem > 0)
2810                 memcpy(infoPtr->items, oldItems, iItem * TAB_ITEM_SIZE(infoPtr));
2811
2812             if (iItem < infoPtr->uNumItem)
2813                 memcpy(TAB_GetItem(infoPtr, iItem),
2814                        oldItems + (iItem + 1) * TAB_ITEM_SIZE(infoPtr),
2815                        (infoPtr->uNumItem - iItem) * TAB_ITEM_SIZE(infoPtr));
2816
2817             if (iItem <= infoPtr->iHotTracked)
2818             {
2819                 /* When tabs move left/up, the hot track item may change */
2820                 FIXME("Recalc hot track\n");
2821             }
2822         }
2823         Free(oldItems);
2824
2825         /* Readjust the selected index */
2826         if ((iItem == infoPtr->iSelected) && (iItem > 0))
2827             infoPtr->iSelected--;
2828
2829         if (iItem < infoPtr->iSelected)
2830             infoPtr->iSelected--;
2831
2832         if (infoPtr->uNumItem == 0)
2833             infoPtr->iSelected = -1;
2834
2835         /* Reposition and repaint tabs */
2836         TAB_SetItemBounds(infoPtr);
2837
2838         bResult = TRUE;
2839     }
2840
2841     return bResult;
2842 }
2843
2844 static inline LRESULT TAB_DeleteAllItems (TAB_INFO *infoPtr)
2845 {
2846     TRACE("(%p)\n", infoPtr);
2847     while (infoPtr->uNumItem)
2848       TAB_DeleteItem (infoPtr, 0);
2849     return TRUE;
2850 }
2851
2852
2853 static inline LRESULT TAB_GetFont (const TAB_INFO *infoPtr)
2854 {
2855   TRACE("(%p) returning %p\n", infoPtr, infoPtr->hFont);
2856   return (LRESULT)infoPtr->hFont;
2857 }
2858
2859 static inline LRESULT TAB_SetFont (TAB_INFO *infoPtr, HFONT hNewFont)
2860 {
2861   TRACE("(%p,%p)\n", infoPtr, hNewFont);
2862
2863   infoPtr->hFont = hNewFont;
2864
2865   TAB_SetItemBounds(infoPtr);
2866
2867   TAB_InvalidateTabArea(infoPtr);
2868
2869   return 0;
2870 }
2871
2872
2873 static inline LRESULT TAB_GetImageList (const TAB_INFO *infoPtr)
2874 {
2875   TRACE("\n");
2876   return (LRESULT)infoPtr->himl;
2877 }
2878
2879 static inline LRESULT TAB_SetImageList (TAB_INFO *infoPtr, HIMAGELIST himlNew)
2880 {
2881     HIMAGELIST himlPrev = infoPtr->himl;
2882     TRACE("\n");
2883     infoPtr->himl = himlNew;
2884     TAB_SetItemBounds(infoPtr);
2885     InvalidateRect(infoPtr->hwnd, NULL, TRUE);
2886     return (LRESULT)himlPrev;
2887 }
2888
2889 static inline LRESULT TAB_GetUnicodeFormat (const TAB_INFO *infoPtr)
2890 {
2891     return infoPtr->bUnicode;
2892 }
2893
2894 static inline LRESULT TAB_SetUnicodeFormat (TAB_INFO *infoPtr, BOOL bUnicode)
2895 {
2896     BOOL bTemp = infoPtr->bUnicode;
2897
2898     infoPtr->bUnicode = bUnicode;
2899
2900     return bTemp;
2901 }
2902
2903 static inline LRESULT TAB_Size (TAB_INFO *infoPtr)
2904 {
2905 /* I'm not really sure what the following code was meant to do.
2906    This is what it is doing:
2907    When WM_SIZE is sent with SIZE_RESTORED, the control
2908    gets positioned in the top left corner.
2909
2910   RECT parent_rect;
2911   HWND parent;
2912   UINT uPosFlags,cx,cy;
2913
2914   uPosFlags=0;
2915   if (!wParam) {
2916     parent = GetParent (hwnd);
2917     GetClientRect(parent, &parent_rect);
2918     cx=LOWORD (lParam);
2919     cy=HIWORD (lParam);
2920     if (GetWindowLongW(hwnd, GWL_STYLE) & CCS_NORESIZE)
2921         uPosFlags |= (SWP_NOSIZE | SWP_NOMOVE);
2922
2923     SetWindowPos (hwnd, 0, parent_rect.left, parent_rect.top,
2924             cx, cy, uPosFlags | SWP_NOZORDER);
2925   } else {
2926     FIXME("WM_SIZE flag %x %lx not handled\n", wParam, lParam);
2927   } */
2928
2929   /* Recompute the size/position of the tabs. */
2930   TAB_SetItemBounds (infoPtr);
2931
2932   /* Force a repaint of the control. */
2933   InvalidateRect(infoPtr->hwnd, NULL, TRUE);
2934
2935   return 0;
2936 }
2937
2938
2939 static LRESULT TAB_Create (HWND hwnd, WPARAM wParam, LPARAM lParam)
2940 {
2941   TAB_INFO *infoPtr;
2942   TEXTMETRICW fontMetrics;
2943   HDC hdc;
2944   HFONT hOldFont;
2945   DWORD dwStyle;
2946
2947   infoPtr = Alloc (sizeof(TAB_INFO));
2948
2949   SetWindowLongPtrW(hwnd, 0, (DWORD_PTR)infoPtr);
2950
2951   infoPtr->hwnd            = hwnd;
2952   infoPtr->hwndNotify      = ((LPCREATESTRUCTW)lParam)->hwndParent;
2953   infoPtr->uNumItem        = 0;
2954   infoPtr->uNumRows        = 0;
2955   infoPtr->uHItemPadding   = 6;
2956   infoPtr->uVItemPadding   = 3;
2957   infoPtr->uHItemPadding_s = 6;
2958   infoPtr->uVItemPadding_s = 3;
2959   infoPtr->hFont           = 0;
2960   infoPtr->items           = 0;
2961   infoPtr->hcurArrow       = LoadCursorW (0, (LPWSTR)IDC_ARROW);
2962   infoPtr->iSelected       = -1;
2963   infoPtr->iHotTracked     = -1;
2964   infoPtr->uFocus          = -1;
2965   infoPtr->hwndToolTip     = 0;
2966   infoPtr->DoRedraw        = TRUE;
2967   infoPtr->needsScrolling  = FALSE;
2968   infoPtr->hwndUpDown      = 0;
2969   infoPtr->leftmostVisible = 0;
2970   infoPtr->fHeightSet      = FALSE;
2971   infoPtr->bUnicode        = IsWindowUnicode (hwnd);
2972   infoPtr->cbInfo          = sizeof(LPARAM);
2973
2974   TRACE("Created tab control, hwnd [%p]\n", hwnd);
2975
2976   /* The tab control always has the WS_CLIPSIBLINGS style. Even
2977      if you don't specify it in CreateWindow. This is necessary in
2978      order for paint to work correctly. This follows windows behaviour. */
2979   dwStyle = GetWindowLongW(hwnd, GWL_STYLE);
2980   SetWindowLongW(hwnd, GWL_STYLE, dwStyle|WS_CLIPSIBLINGS);
2981
2982   if (dwStyle & TCS_TOOLTIPS) {
2983     /* Create tooltip control */
2984     infoPtr->hwndToolTip =
2985       CreateWindowExW (0, TOOLTIPS_CLASSW, NULL, WS_POPUP,
2986                        CW_USEDEFAULT, CW_USEDEFAULT,
2987                        CW_USEDEFAULT, CW_USEDEFAULT,
2988                        hwnd, 0, 0, 0);
2989
2990     /* Send NM_TOOLTIPSCREATED notification */
2991     if (infoPtr->hwndToolTip) {
2992       NMTOOLTIPSCREATED nmttc;
2993
2994       nmttc.hdr.hwndFrom = hwnd;
2995       nmttc.hdr.idFrom = GetWindowLongPtrW(hwnd, GWLP_ID);
2996       nmttc.hdr.code = NM_TOOLTIPSCREATED;
2997       nmttc.hwndToolTips = infoPtr->hwndToolTip;
2998
2999       SendMessageW (infoPtr->hwndNotify, WM_NOTIFY,
3000                     (WPARAM)GetWindowLongPtrW(hwnd, GWLP_ID), (LPARAM)&nmttc);
3001     }
3002   }
3003
3004   OpenThemeData (infoPtr->hwnd, themeClass);
3005   
3006   /*
3007    * We need to get text information so we need a DC and we need to select
3008    * a font.
3009    */
3010   hdc = GetDC(hwnd);
3011   hOldFont = SelectObject (hdc, GetStockObject (SYSTEM_FONT));
3012
3013   /* Use the system font to determine the initial height of a tab. */
3014   GetTextMetricsW(hdc, &fontMetrics);
3015
3016   /*
3017    * Make sure there is enough space for the letters + growing the
3018    * selected item + extra space for the selected item.
3019    */
3020   infoPtr->tabHeight = fontMetrics.tmHeight + SELECTED_TAB_OFFSET +
3021                        ((dwStyle & TCS_BUTTONS) ? 2 : 1) *
3022                         infoPtr->uVItemPadding;
3023
3024   /* Initialize the width of a tab. */
3025   if (dwStyle & TCS_FIXEDWIDTH)
3026     infoPtr->tabWidth = GetDeviceCaps(hdc, LOGPIXELSX);
3027
3028   infoPtr->tabMinWidth = -1;
3029
3030   TRACE("tabH=%d, tabW=%d\n", infoPtr->tabHeight, infoPtr->tabWidth);
3031
3032   SelectObject (hdc, hOldFont);
3033   ReleaseDC(hwnd, hdc);
3034
3035   return 0;
3036 }
3037
3038 static LRESULT
3039 TAB_Destroy (TAB_INFO *infoPtr)
3040 {
3041   UINT iItem;
3042
3043   if (!infoPtr)
3044       return 0;
3045
3046   SetWindowLongPtrW(infoPtr->hwnd, 0, 0);
3047
3048   if (infoPtr->items) {
3049     for (iItem = 0; iItem < infoPtr->uNumItem; iItem++) {
3050       Free (TAB_GetItem(infoPtr, iItem)->pszText);
3051     }
3052     Free (infoPtr->items);
3053   }
3054
3055   if (infoPtr->hwndToolTip)
3056     DestroyWindow (infoPtr->hwndToolTip);
3057
3058   if (infoPtr->hwndUpDown)
3059     DestroyWindow(infoPtr->hwndUpDown);
3060
3061   if (infoPtr->iHotTracked >= 0)
3062     KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
3063
3064   CloseThemeData (GetWindowTheme (infoPtr->hwnd));
3065   
3066   Free (infoPtr);
3067   return 0;
3068 }
3069
3070 /* update theme after a WM_THEMECHANGED message */
3071 static LRESULT theme_changed(const TAB_INFO *infoPtr)
3072 {
3073     HTHEME theme = GetWindowTheme (infoPtr->hwnd);
3074     CloseThemeData (theme);
3075     OpenThemeData (infoPtr->hwnd, themeClass);
3076     return 0;
3077 }
3078
3079 static LRESULT TAB_NCCalcSize(HWND hwnd, WPARAM wParam, LPARAM lParam)
3080 {
3081   if (!wParam)
3082     return 0;
3083   return WVR_ALIGNTOP;
3084 }
3085
3086 static inline LRESULT
3087 TAB_SetItemExtra (TAB_INFO *infoPtr, INT cbInfo)
3088 {
3089   if (!infoPtr || cbInfo <= 0)
3090     return FALSE;
3091
3092   if (infoPtr->uNumItem)
3093   {
3094     /* FIXME: MSDN says this is not allowed, but this hasn't been verified */
3095     return FALSE;
3096   }
3097     
3098   infoPtr->cbInfo = cbInfo;
3099   return TRUE;
3100 }
3101
3102 static LRESULT WINAPI
3103 TAB_WindowProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
3104 {
3105     TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
3106
3107     TRACE("hwnd=%p msg=%x wParam=%lx lParam=%lx\n", hwnd, uMsg, wParam, lParam);
3108     if (!infoPtr && (uMsg != WM_CREATE))
3109       return DefWindowProcW (hwnd, uMsg, wParam, lParam);
3110
3111     switch (uMsg)
3112     {
3113     case TCM_GETIMAGELIST:
3114       return TAB_GetImageList (infoPtr);
3115
3116     case TCM_SETIMAGELIST:
3117       return TAB_SetImageList (infoPtr, (HIMAGELIST)lParam);
3118
3119     case TCM_GETITEMCOUNT:
3120       return TAB_GetItemCount (infoPtr);
3121
3122     case TCM_GETITEMA:
3123     case TCM_GETITEMW:
3124       return TAB_GetItemT (infoPtr, (INT)wParam, (LPTCITEMW)lParam, uMsg == TCM_GETITEMW);
3125
3126     case TCM_SETITEMA:
3127     case TCM_SETITEMW:
3128       return TAB_SetItemT (infoPtr, (INT)wParam, (LPTCITEMW)lParam, uMsg == TCM_SETITEMW);
3129
3130     case TCM_DELETEITEM:
3131       return TAB_DeleteItem (infoPtr, (INT)wParam);
3132
3133     case TCM_DELETEALLITEMS:
3134      return TAB_DeleteAllItems (infoPtr);
3135
3136     case TCM_GETITEMRECT:
3137      return TAB_GetItemRect (infoPtr, wParam, lParam);
3138
3139     case TCM_GETCURSEL:
3140       return TAB_GetCurSel (infoPtr);
3141
3142     case TCM_HITTEST:
3143       return TAB_HitTest (infoPtr, (LPTCHITTESTINFO)lParam);
3144
3145     case TCM_SETCURSEL:
3146       return TAB_SetCurSel (infoPtr, (INT)wParam);
3147
3148     case TCM_INSERTITEMA:
3149     case TCM_INSERTITEMW:
3150       return TAB_InsertItemT (infoPtr, wParam, lParam, uMsg == TCM_INSERTITEMW);
3151
3152     case TCM_SETITEMEXTRA:
3153       return TAB_SetItemExtra (infoPtr, (int)wParam);
3154
3155     case TCM_ADJUSTRECT:
3156       return TAB_AdjustRect (infoPtr, (BOOL)wParam, (LPRECT)lParam);
3157
3158     case TCM_SETITEMSIZE:
3159       return TAB_SetItemSize (infoPtr, lParam);
3160
3161     case TCM_REMOVEIMAGE:
3162       FIXME("Unimplemented msg TCM_REMOVEIMAGE\n");
3163       return 0;
3164
3165     case TCM_SETPADDING:
3166       return TAB_SetPadding (infoPtr, lParam);
3167
3168     case TCM_GETROWCOUNT:
3169       return TAB_GetRowCount(infoPtr);
3170
3171     case TCM_GETUNICODEFORMAT:
3172       return TAB_GetUnicodeFormat (infoPtr);
3173
3174     case TCM_SETUNICODEFORMAT:
3175       return TAB_SetUnicodeFormat (infoPtr, (BOOL)wParam);
3176
3177     case TCM_HIGHLIGHTITEM:
3178       return TAB_HighlightItem (infoPtr, (INT)wParam, (BOOL)LOWORD(lParam));
3179
3180     case TCM_GETTOOLTIPS:
3181       return TAB_GetToolTips (infoPtr);
3182
3183     case TCM_SETTOOLTIPS:
3184       return TAB_SetToolTips (infoPtr, (HWND)wParam);
3185
3186     case TCM_GETCURFOCUS:
3187       return TAB_GetCurFocus (infoPtr);
3188
3189     case TCM_SETCURFOCUS:
3190       return TAB_SetCurFocus (infoPtr, (INT)wParam);
3191
3192     case TCM_SETMINTABWIDTH:
3193       return TAB_SetMinTabWidth(infoPtr, (INT)lParam);
3194
3195     case TCM_DESELECTALL:
3196       FIXME("Unimplemented msg TCM_DESELECTALL\n");
3197       return 0;
3198
3199     case TCM_GETEXTENDEDSTYLE:
3200       FIXME("Unimplemented msg TCM_GETEXTENDEDSTYLE\n");
3201       return 0;
3202
3203     case TCM_SETEXTENDEDSTYLE:
3204       FIXME("Unimplemented msg TCM_SETEXTENDEDSTYLE\n");
3205       return 0;
3206
3207     case WM_GETFONT:
3208       return TAB_GetFont (infoPtr);
3209
3210     case WM_SETFONT:
3211       return TAB_SetFont (infoPtr, (HFONT)wParam);
3212
3213     case WM_CREATE:
3214       return TAB_Create (hwnd, wParam, lParam);
3215
3216     case WM_NCDESTROY:
3217       return TAB_Destroy (infoPtr);
3218
3219     case WM_GETDLGCODE:
3220       return DLGC_WANTARROWS | DLGC_WANTCHARS;
3221
3222     case WM_LBUTTONDOWN:
3223       return TAB_LButtonDown (infoPtr, wParam, lParam);
3224
3225     case WM_LBUTTONUP:
3226       return TAB_LButtonUp (infoPtr);
3227
3228     case WM_NOTIFY:
3229       return SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, wParam, lParam);
3230
3231     case WM_RBUTTONDOWN:
3232       return TAB_RButtonDown (infoPtr);
3233
3234     case WM_MOUSEMOVE:
3235       return TAB_MouseMove (infoPtr, wParam, lParam);
3236
3237     case WM_PRINTCLIENT:
3238     case WM_PAINT:
3239       return TAB_Paint (infoPtr, (HDC)wParam);
3240
3241     case WM_SIZE:
3242       return TAB_Size (infoPtr);
3243
3244     case WM_SETREDRAW:
3245       return TAB_SetRedraw (infoPtr, (BOOL)wParam);
3246
3247     case WM_HSCROLL:
3248       return TAB_OnHScroll(infoPtr, (int)LOWORD(wParam), (int)HIWORD(wParam), (HWND)lParam);
3249
3250     case WM_STYLECHANGED:
3251       TAB_SetItemBounds (infoPtr);
3252       InvalidateRect(hwnd, NULL, TRUE);
3253       return 0;
3254
3255     case WM_SYSCOLORCHANGE:
3256       COMCTL32_RefreshSysColors();
3257       return 0;
3258
3259     case WM_THEMECHANGED:
3260       return theme_changed (infoPtr);
3261
3262     case WM_KILLFOCUS:
3263     case WM_SETFOCUS:
3264       TAB_FocusChanging(infoPtr);
3265       break;   /* Don't disturb normal focus behavior */
3266
3267     case WM_KEYUP:
3268       return TAB_KeyUp(infoPtr, wParam);
3269     case WM_NCHITTEST:
3270       return TAB_NCHitTest(infoPtr, lParam);
3271
3272     case WM_NCCALCSIZE:
3273       return TAB_NCCalcSize(hwnd, wParam, lParam);
3274
3275     default:
3276       if (uMsg >= WM_USER && uMsg < WM_APP && !COMCTL32_IsReflectedMessage(uMsg))
3277         WARN("unknown msg %04x wp=%08lx lp=%08lx\n",
3278              uMsg, wParam, lParam);
3279       break;
3280     }
3281     return DefWindowProcW(hwnd, uMsg, wParam, lParam);
3282 }
3283
3284
3285 void
3286 TAB_Register (void)
3287 {
3288   WNDCLASSW wndClass;
3289
3290   ZeroMemory (&wndClass, sizeof(WNDCLASSW));
3291   wndClass.style         = CS_GLOBALCLASS | CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
3292   wndClass.lpfnWndProc   = TAB_WindowProc;
3293   wndClass.cbClsExtra    = 0;
3294   wndClass.cbWndExtra    = sizeof(TAB_INFO *);
3295   wndClass.hCursor       = LoadCursorW (0, (LPWSTR)IDC_ARROW);
3296   wndClass.hbrBackground = (HBRUSH)(COLOR_BTNFACE+1);
3297   wndClass.lpszClassName = WC_TABCONTROLW;
3298
3299   RegisterClassW (&wndClass);
3300 }
3301
3302
3303 void
3304 TAB_Unregister (void)
3305 {
3306     UnregisterClassW (WC_TABCONTROLW, NULL);
3307 }