tab.c 91.3 KB
Newer Older
Alexandre Julliard's avatar
Alexandre Julliard committed
1 2 3 4
/*
 * Tab control
 *
 * Copyright 1998 Anders Carlsson
Alex Priem's avatar
Alex Priem committed
5
 * Copyright 1999 Alex Priem <alexp@sci.kun.nl>
Francis Beaudet's avatar
Francis Beaudet committed
6
 * Copyright 1999 Francis Beaudet
7
 * Copyright 2003 Vitaliy Margolen
Alexandre Julliard's avatar
Alexandre Julliard committed
8
 *
9 10 11 12 13 14 15 16 17 18 19 20
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * 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
 *
James Hawkins's avatar
James Hawkins committed
23 24 25 26 27 28 29 30 31
 * NOTES
 *
 * This code was audited for completeness against the documented features
 * of Comctl32.dll version 6.0 on May. 20, 2005, by James Hawkins.
 *
 * Unless otherwise noted, we believe this code to be complete, as per
 * the specification mentioned above.
 * If you discover missing features, or bugs, please note them below.
 *
Alexandre Julliard's avatar
Alexandre Julliard committed
32
 * TODO:
33
 *
34
 *  Styles:
James Hawkins's avatar
James Hawkins committed
35 36 37 38 39
 *   TCS_MULTISELECT
 *   TCS_RIGHT
 *   TCS_RIGHTJUSTIFY
 *   TCS_SCROLLOPPOSITE
 *   TCS_SINGLELINE
40 41
 *   TCIF_RTLREADING
 *
James Hawkins's avatar
James Hawkins committed
42 43 44 45 46 47 48 49 50 51 52 53 54
 *  Extended Styles:
 *   TCS_EX_FLATSEPARATORS
 *   TCS_EX_REGISTERDROP
 *
 *  States:
 *   TCIS_BUTTONPRESSED
 *
 *  Notifications:
 *   NM_RELEASEDCAPTURE
 *   TCN_FOCUSCHANGE
 *   TCN_GETOBJECT
 *   TCN_KEYDOWN
 *
55 56 57 58 59 60
 *  Messages:
 *   TCM_REMOVEIMAGE
 *   TCM_DESELECTALL
 *   TCM_GETEXTENDEDSTYLE
 *   TCM_SETEXTENDEDSTYLE
 *
James Hawkins's avatar
James Hawkins committed
61 62 63
 *  Macros:
 *   TabCtrl_AdjustRect
 *
Alexandre Julliard's avatar
Alexandre Julliard committed
64 65
 */

66
#include <stdarg.h>
67 68
#include <string.h>

69
#include "windef.h"
70
#include "winbase.h"
71 72 73
#include "wingdi.h"
#include "winuser.h"
#include "winnls.h"
Alexandre Julliard's avatar
Alexandre Julliard committed
74
#include "commctrl.h"
75
#include "comctl32.h"
76 77
#include "uxtheme.h"
#include "tmschema.h"
78
#include "wine/debug.h"
79
#include <math.h>
Alexandre Julliard's avatar
Alexandre Julliard committed
80

81
WINE_DEFAULT_DEBUG_CHANNEL(tab);
82 83 84 85

typedef struct
{
  DWORD  dwState;
86
  LPWSTR pszText;
87
  INT    iImage;
88 89 90 91
  RECT   rect;      /* bounding rectangle of the item relative to the
                     * leftmost item (the leftmost item, 0, would have a
                     * "left" member of 0 in this rectangle)
                     *
92
                     * additionally the top member holds the row number
93 94
                     * and bottom is unused and should be 0 */
  BYTE   extra[1];  /* Space for caller supplied info, variable size */
95 96
} TAB_ITEM;

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 101
typedef struct
{
102
  HWND       hwnd;            /* Tab control window */
103
  HWND       hwndNotify;      /* notification window (parent) */
104 105 106 107
  UINT       uNumItem;        /* number of tab items */
  UINT       uNumRows;	      /* number of tab rows */
  INT        tabHeight;       /* height of the tab row */
  INT        tabWidth;        /* width of tabs */
108
  INT        tabMinWidth;     /* minimum width of items */
109 110
  USHORT     uHItemPadding;   /* amount of horizontal padding, in pixels */
  USHORT     uVItemPadding;   /* amount of vertical padding, in pixels */
111 112
  USHORT     uHItemPadding_s; /* Set amount of horizontal padding, in pixels */
  USHORT     uVItemPadding_s; /* Set amount of vertical padding, in pixels */
113 114
  HFONT      hFont;           /* handle to the current font */
  HCURSOR    hcurArrow;       /* handle to the current cursor */
115
  HIMAGELIST himl;            /* handle to an image list (may be 0) */
116 117
  HWND       hwndToolTip;     /* handle to tab's tooltip */
  INT        leftmostVisible; /* Used for scrolling, this member contains
118
                               * the index of the first visible item */
119 120 121 122 123
  INT        iSelected;       /* the currently selected item */
  INT        iHotTracked;     /* the highlighted item under the mouse */
  INT        uFocus;          /* item which has the focus */
  TAB_ITEM*  items;           /* pointer to an array of TAB_ITEM's */
  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

Francis Beaudet's avatar
Francis Beaudet committed
132 133 134
/******************************************************************************
 * Positioning constants
 */
135
#define SELECTED_TAB_OFFSET     2
Francis Beaudet's avatar
Francis Beaudet committed
136
#define ROUND_CORNER_SIZE       2
Alexandre Julliard's avatar
Alexandre Julliard committed
137 138
#define DISPLAY_AREA_PADDINGX   2
#define DISPLAY_AREA_PADDINGY   2
139 140
#define CONTROL_BORDER_SIZEX    2
#define CONTROL_BORDER_SIZEY    2
141
#define BUTTON_SPACINGX         3
142
#define BUTTON_SPACINGY         3
143
#define FLAT_BTN_SPACINGX       8
144 145 146 147
#define DEFAULT_MIN_TAB_WIDTH   54
#define DEFAULT_TAB_WIDTH_FIXED 96
#define DEFAULT_PADDING_X       6
#define EXTRA_ICON_PADDING      3
Alexandre Julliard's avatar
Alexandre Julliard committed
148

149
#define TAB_GetInfoPtr(hwnd) ((TAB_INFO *)GetWindowLongPtrW(hwnd,0))
150 151 152
/* Since items are variable sized, cannot directly access them */
#define TAB_GetItem(info,i) \
  ((TAB_ITEM*)((LPBYTE)info->items + (i) * TAB_ITEM_SIZE(info)))
153

154 155
#define GET_DEFAULT_MIN_TAB_WIDTH(infoPtr) (DEFAULT_MIN_TAB_WIDTH - (DEFAULT_PADDING_X - (infoPtr)->uHItemPadding) * 2)

156 157 158 159 160 161
/******************************************************************************
 * Hot-tracking timer constants
 */
#define TAB_HOTTRACK_TIMER            1
#define TAB_HOTTRACK_TIMER_INTERVAL   100   /* milliseconds */

162 163
static const WCHAR themeClass[] = { 'T','a','b',0 };

Francis Beaudet's avatar
Francis Beaudet committed
164 165 166
/******************************************************************************
 * Prototypes
 */
167
static void TAB_InvalidateTabArea(const TAB_INFO *);
168
static void TAB_EnsureSelectionVisible(TAB_INFO *);
169
static void TAB_DrawItemInterior(const TAB_INFO *, HDC, INT, RECT*);
170

171
static BOOL
172
TAB_SendSimpleNotify (const TAB_INFO *infoPtr, UINT code)
Alexandre Julliard's avatar
Alexandre Julliard committed
173 174 175
{
    NMHDR nmhdr;

176 177
    nmhdr.hwndFrom = infoPtr->hwnd;
    nmhdr.idFrom = GetWindowLongPtrW(infoPtr->hwnd, GWLP_ID);
Alexandre Julliard's avatar
Alexandre Julliard committed
178 179
    nmhdr.code = code;

180
    return (BOOL) SendMessageW (infoPtr->hwndNotify, WM_NOTIFY,
181
            nmhdr.idFrom, (LPARAM) &nmhdr);
Alexandre Julliard's avatar
Alexandre Julliard committed
182 183
}

Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
184
static void
185 186
TAB_RelayEvent (HWND hwndTip, HWND hwndMsg, UINT uMsg,
            WPARAM wParam, LPARAM lParam)
Alex Priem's avatar
Alex Priem committed
187
{
188
    MSG msg;
Alex Priem's avatar
Alex Priem committed
189 190 191 192 193 194

    msg.hwnd = hwndMsg;
    msg.message = uMsg;
    msg.wParam = wParam;
    msg.lParam = lParam;
    msg.time = GetMessageTime ();
195 196
    msg.pt.x = (short)LOWORD(GetMessagePos ());
    msg.pt.y = (short)HIWORD(GetMessagePos ());
Alex Priem's avatar
Alex Priem committed
197

198
    SendMessageW (hwndTip, TTM_RELAYEVENT, 0, (LPARAM)&msg);
Alex Priem's avatar
Alex Priem committed
199 200
}

201
static void
202
TAB_DumpItemExternalT(const TCITEMW *pti, UINT iItem, BOOL isW)
203 204
{
    if (TRACE_ON(tab)) {
205
	TRACE("external tab %d, mask=0x%08x, dwState=0x%08x, dwStateMask=0x%08x, cchTextMax=0x%08x\n",
206 207
	      iItem, pti->mask, pti->dwState, pti->dwStateMask, pti->cchTextMax);
	TRACE("external tab %d,   iImage=%d, lParam=0x%08lx, pszTextW=%s\n",
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
208
	      iItem, pti->iImage, pti->lParam, isW ? debugstr_w(pti->pszText) : debugstr_a((LPSTR)pti->pszText));
209 210 211 212
    }
}

static void
213
TAB_DumpItemInternal(const TAB_INFO *infoPtr, UINT iItem)
214 215 216 217
{
    if (TRACE_ON(tab)) {
	TAB_ITEM *ti;

218
	ti = TAB_GetItem(infoPtr, iItem);
219 220
	TRACE("tab %d, dwState=0x%08x, pszText=%s, iImage=%d\n",
	      iItem, ti->dwState, debugstr_w(ti->pszText), ti->iImage);
221
	TRACE("tab %d, rect.left=%d, rect.top(row)=%d\n",
222
	      iItem, ti->rect.left, ti->rect.top);
223 224 225
    }
}

226 227
/* RETURNS
 *   the index of the selected tab, or -1 if no tab is selected. */
228
static inline LRESULT TAB_GetCurSel (const TAB_INFO *infoPtr)
Alexandre Julliard's avatar
Alexandre Julliard committed
229 230 231 232
{
    return infoPtr->iSelected;
}

233
/* RETURNS
234
 *   the index of the tab item that has the focus. */
235 236
static inline LRESULT
TAB_GetCurFocus (const TAB_INFO *infoPtr)
Alex Priem's avatar
Alex Priem committed
237 238 239 240
{
    return infoPtr->uFocus;
}

241
static inline LRESULT TAB_GetToolTips (const TAB_INFO *infoPtr)
Alex Priem's avatar
Alex Priem committed
242 243
{
    if (infoPtr == NULL) return 0;
244
    return (LRESULT)infoPtr->hwndToolTip;
Alex Priem's avatar
Alex Priem committed
245 246
}

247
static inline LRESULT TAB_SetCurSel (TAB_INFO *infoPtr, INT iItem)
248
{
249
  INT prevItem = infoPtr->iSelected;
250

251 252 253 254 255
  if (iItem < 0)
      infoPtr->iSelected=-1;
  else if (iItem >= infoPtr->uNumItem)
      return -1;
  else {
256 257
      if (infoPtr->iSelected != iItem) {
          infoPtr->iSelected=iItem;
258
          infoPtr->uFocus=iItem;
259 260 261
          TAB_EnsureSelectionVisible(infoPtr);
          TAB_InvalidateTabArea(infoPtr);
      }
Francis Beaudet's avatar
Francis Beaudet committed
262 263
  }
  return prevItem;
264 265
}

266
static LRESULT TAB_SetCurFocus (TAB_INFO *infoPtr, INT iItem)
Alex Priem's avatar
Alex Priem committed
267
{
268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285
  if (iItem < 0)
      infoPtr->uFocus = -1;
  else if (iItem < infoPtr->uNumItem) {
    if (GetWindowLongW(infoPtr->hwnd, GWL_STYLE) & TCS_BUTTONS) {
      FIXME("Should set input focus\n");
    } else {
      int oldFocus = infoPtr->uFocus;
      if (infoPtr->iSelected != iItem || oldFocus == -1 ) {
        infoPtr->uFocus = iItem;
        if (oldFocus != -1) {
          if (!TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGING))  {
            infoPtr->iSelected = iItem;
            TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGE);
          }
          else
            infoPtr->iSelected = iItem;
          TAB_EnsureSelectionVisible(infoPtr);
          TAB_InvalidateTabArea(infoPtr);
286
        }
Francis Beaudet's avatar
Francis Beaudet committed
287 288 289
      }
    }
  }
Alex Priem's avatar
Alex Priem committed
290 291 292
  return 0;
}

293 294
static inline LRESULT
TAB_SetToolTips (TAB_INFO *infoPtr, HWND hwndToolTip)
Alex Priem's avatar
Alex Priem committed
295
{
296 297
    if (infoPtr)
        infoPtr->hwndToolTip = hwndToolTip;
Alex Priem's avatar
Alex Priem committed
298 299 300
    return 0;
}

301 302
static inline LRESULT
TAB_SetPadding (TAB_INFO *infoPtr, LPARAM lParam)
303
{
304 305 306 307 308
    if (infoPtr)
    {
        infoPtr->uHItemPadding_s=LOWORD(lParam);
        infoPtr->uVItemPadding_s=HIWORD(lParam);
    }
309 310 311
    return 0;
}

Francis Beaudet's avatar
Francis Beaudet committed
312 313 314 315 316 317 318 319 320 321
/******************************************************************************
 * TAB_InternalGetItemRect
 *
 * This method will calculate the rectangle representing a given tab item in
 * client coordinates. This method takes scrolling into account.
 *
 * This method returns TRUE if the item is visible in the window and FALSE
 * if it is completely outside the client area.
 */
static BOOL TAB_InternalGetItemRect(
322
  const TAB_INFO* infoPtr,
Francis Beaudet's avatar
Francis Beaudet committed
323 324 325 326
  INT         itemIndex,
  RECT*       itemRect,
  RECT*       selectedRect)
{
327
  RECT tmpItemRect,clientRect;
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
328
  LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
329

330 331
  /* Perform a sanity check and a trivial visibility check. */
  if ( (infoPtr->uNumItem <= 0) ||
Francis Beaudet's avatar
Francis Beaudet committed
332
       (itemIndex >= infoPtr->uNumItem) ||
333
       (!((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)) && (itemIndex < infoPtr->leftmostVisible)) )
334 335 336 337 338 339 340 341 342 343 344 345
    {
        TRACE("Not Visible\n");
        /* need to initialize these to empty rects */
        if (itemRect)
        {
            memset(itemRect,0,sizeof(RECT));
            itemRect->bottom = infoPtr->tabHeight;
        }
        if (selectedRect)
            memset(selectedRect,0,sizeof(RECT));
        return FALSE;
    }
Francis Beaudet's avatar
Francis Beaudet committed
346 347 348 349 350

  /*
   * Avoid special cases in this procedure by assigning the "out"
   * parameters if the caller didn't supply them
   */
351
  if (itemRect == NULL)
Francis Beaudet's avatar
Francis Beaudet committed
352
    itemRect = &tmpItemRect;
353

354
  /* Retrieve the unmodified item rect. */
355
  *itemRect = TAB_GetItem(infoPtr,itemIndex)->rect;
Francis Beaudet's avatar
Francis Beaudet committed
356

357
  /* calculate the times bottom and top based on the row */
358
  GetClientRect(infoPtr->hwnd, &clientRect);
359

360
  if ((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
361
  {
362 363 364
    itemRect->right  = clientRect.right - SELECTED_TAB_OFFSET - itemRect->left * infoPtr->tabHeight -
                       ((lStyle & TCS_BUTTONS) ? itemRect->left * BUTTON_SPACINGX : 0);
    itemRect->left   = itemRect->right - infoPtr->tabHeight;
365
  }
366
  else if (lStyle & TCS_VERTICAL)
367
  {
368 369 370
    itemRect->left   = clientRect.left + SELECTED_TAB_OFFSET + itemRect->left * infoPtr->tabHeight +
                       ((lStyle & TCS_BUTTONS) ? itemRect->left * BUTTON_SPACINGX : 0);
    itemRect->right  = itemRect->left + infoPtr->tabHeight;
371
  }
372
  else if (lStyle & TCS_BOTTOM)
373
  {
374 375 376
    itemRect->bottom = clientRect.bottom - itemRect->top * infoPtr->tabHeight -
                       ((lStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : SELECTED_TAB_OFFSET);
    itemRect->top    = itemRect->bottom - infoPtr->tabHeight;
377
  }
378
  else /* not TCS_BOTTOM and not TCS_VERTICAL */
379
  {
380 381 382
    itemRect->top    = clientRect.top + itemRect->top * infoPtr->tabHeight +
                       ((lStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : SELECTED_TAB_OFFSET);
    itemRect->bottom = itemRect->top + infoPtr->tabHeight;
383 384
 }

Francis Beaudet's avatar
Francis Beaudet committed
385
  /*
386
   * "scroll" it to make sure the item at the very left of the
Francis Beaudet's avatar
Francis Beaudet committed
387 388
   * tab control is the leftmost visible tab.
   */
389 390 391 392
  if(lStyle & TCS_VERTICAL)
  {
    OffsetRect(itemRect,
	     0,
393
	     -TAB_GetItem(infoPtr, infoPtr->leftmostVisible)->rect.top);
394 395 396 397 398 399 400

    /*
     * Move the rectangle so the first item is slightly offset from
     * the bottom of the tab control.
     */
    OffsetRect(itemRect,
	     0,
401
	     SELECTED_TAB_OFFSET);
402 403 404 405

  } else
  {
    OffsetRect(itemRect,
406
	     -TAB_GetItem(infoPtr, infoPtr->leftmostVisible)->rect.left,
Francis Beaudet's avatar
Francis Beaudet committed
407 408
	     0);

409 410 411 412 413
    /*
     * Move the rectangle so the first item is slightly offset from
     * the left of the tab control.
     */
    OffsetRect(itemRect,
Francis Beaudet's avatar
Francis Beaudet committed
414 415
	     SELECTED_TAB_OFFSET,
	     0);
416
  }
417 418
  TRACE("item %d tab h=%d, rect=(%s)\n",
        itemIndex, infoPtr->tabHeight, wine_dbgstr_rect(itemRect));
Francis Beaudet's avatar
Francis Beaudet committed
419

420
  /* Now, calculate the position of the item as if it were selected. */
Francis Beaudet's avatar
Francis Beaudet committed
421 422 423 424
  if (selectedRect!=NULL)
  {
    CopyRect(selectedRect, itemRect);

425 426 427 428 429
    /* The rectangle of a selected item is a bit wider. */
    if(lStyle & TCS_VERTICAL)
      InflateRect(selectedRect, 0, SELECTED_TAB_OFFSET);
    else
      InflateRect(selectedRect, SELECTED_TAB_OFFSET, 0);
Francis Beaudet's avatar
Francis Beaudet committed
430

431
    /* If it also a bit higher. */
432
    if ((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
433
    {
434 435
      selectedRect->left   -= 2; /* the border is thicker on the right */
      selectedRect->right  += SELECTED_TAB_OFFSET;
436
    }
437
    else if (lStyle & TCS_VERTICAL)
438
    {
439 440
      selectedRect->left   -= SELECTED_TAB_OFFSET;
      selectedRect->right  += 1;
441
    }
442
    else if (lStyle & TCS_BOTTOM)
443
    {
444
      selectedRect->bottom += SELECTED_TAB_OFFSET;
Francis Beaudet's avatar
Francis Beaudet committed
445
    }
446
    else /* not TCS_BOTTOM and not TCS_VERTICAL */
Francis Beaudet's avatar
Francis Beaudet committed
447
    {
448
      selectedRect->top    -= SELECTED_TAB_OFFSET;
449
      selectedRect->bottom -= 1;
Francis Beaudet's avatar
Francis Beaudet committed
450 451 452
    }
  }

453 454 455 456 457
  /* Check for visibility */
  if (lStyle & TCS_VERTICAL)
    return (itemRect->top < clientRect.bottom) && (itemRect->bottom > clientRect.top);
  else
    return (itemRect->left < clientRect.right) && (itemRect->right > clientRect.left);
Francis Beaudet's avatar
Francis Beaudet committed
458 459
}

460
static inline BOOL
461
TAB_GetItemRect(const TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
462
{
463
  return TAB_InternalGetItemRect(infoPtr, (INT)wParam, (LPRECT)lParam, (LPRECT)NULL);
464 465
}

Francis Beaudet's avatar
Francis Beaudet committed
466 467 468 469 470
/******************************************************************************
 * TAB_KeyUp
 *
 * This method is called to handle keyboard input
 */
471
static LRESULT TAB_KeyUp(TAB_INFO* infoPtr, WPARAM keyCode)
Francis Beaudet's avatar
Francis Beaudet committed
472 473 474 475 476 477
{
  int       newItem = -1;

  switch (keyCode)
  {
    case VK_LEFT:
478
      newItem = infoPtr->uFocus - 1;
Francis Beaudet's avatar
Francis Beaudet committed
479 480
      break;
    case VK_RIGHT:
481
      newItem = infoPtr->uFocus + 1;
Francis Beaudet's avatar
Francis Beaudet committed
482 483
      break;
  }
484

Francis Beaudet's avatar
Francis Beaudet committed
485 486 487
  /*
   * If we changed to a valid item, change the selection
   */
488 489 490
  if (newItem >= 0 &&
      newItem < infoPtr->uNumItem &&
      infoPtr->uFocus != newItem)
Francis Beaudet's avatar
Francis Beaudet committed
491
  {
492
    if (!TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGING))
Francis Beaudet's avatar
Francis Beaudet committed
493 494 495
    {
      infoPtr->iSelected = newItem;
      infoPtr->uFocus    = newItem;
496
      TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGE);
Francis Beaudet's avatar
Francis Beaudet committed
497

498 499
      TAB_EnsureSelectionVisible(infoPtr);
      TAB_InvalidateTabArea(infoPtr);
Francis Beaudet's avatar
Francis Beaudet committed
500 501 502 503 504 505 506 507 508 509 510 511
    }
  }

  return 0;
}

/******************************************************************************
 * TAB_FocusChanging
 *
 * This method is called whenever the focus goes in or out of this control
 * it is used to update the visual state of the control.
 */
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
512
static void TAB_FocusChanging(const TAB_INFO *infoPtr)
Francis Beaudet's avatar
Francis Beaudet committed
513 514 515 516 517 518 519
{
  RECT      selectedRect;
  BOOL      isVisible;

  /*
   * Get the rectangle for the item.
   */
520
  isVisible = TAB_InternalGetItemRect(infoPtr,
Francis Beaudet's avatar
Francis Beaudet committed
521 522 523
				      infoPtr->uFocus,
				      NULL,
				      &selectedRect);
524

Francis Beaudet's avatar
Francis Beaudet committed
525 526 527 528 529 530
  /*
   * If the rectangle is not completely invisible, invalidate that
   * portion of the window.
   */
  if (isVisible)
  {
531
    TRACE("invalidate (%s)\n", wine_dbgstr_rect(&selectedRect));
532
    InvalidateRect(infoPtr->hwnd, &selectedRect, TRUE);
Francis Beaudet's avatar
Francis Beaudet committed
533 534
  }
}
Alex Priem's avatar
Alex Priem committed
535

536
static INT TAB_InternalHitTest (const TAB_INFO *infoPtr, POINT pt, UINT *flags)
Alex Priem's avatar
Alex Priem committed
537
{
538
  RECT rect;
539
  INT iCount;
540

541
  for (iCount = 0; iCount < infoPtr->uNumItem; iCount++)
Francis Beaudet's avatar
Francis Beaudet committed
542
  {
543
    TAB_InternalGetItemRect(infoPtr, iCount, &rect, NULL);
Francis Beaudet's avatar
Francis Beaudet committed
544

545
    if (PtInRect(&rect, pt))
Francis Beaudet's avatar
Francis Beaudet committed
546 547 548 549 550 551
    {
      *flags = TCHT_ONITEM;
      return iCount;
    }
  }

552
  *flags = TCHT_NOWHERE;
Alex Priem's avatar
Alex Priem committed
553 554 555
  return -1;
}

556
static inline LRESULT
557
TAB_HitTest (const TAB_INFO *infoPtr, LPTCHITTESTINFO lptest)
Alex Priem's avatar
Alex Priem committed
558
{
559
  return TAB_InternalHitTest (infoPtr, lptest->pt, &lptest->flags);
Alex Priem's avatar
Alex Priem committed
560 561
}

562 563 564 565 566 567 568 569 570 571 572 573
/******************************************************************************
 * TAB_NCHitTest
 *
 * Napster v2b5 has a tab control for its main navigation which has a client
 * area that covers the whole area of the dialog pages.
 * That's why it receives all msgs for that area and the underlying dialog ctrls
 * are dead.
 * So I decided that we should handle WM_NCHITTEST here and return
 * HTTRANSPARENT if we don't hit the tab control buttons.
 * FIXME: WM_NCHITTEST handling correct ? Fix it if you know that Windows
 * doesn't do it that way. Maybe depends on tab control styles ?
 */
574
static inline LRESULT
575
TAB_NCHitTest (const TAB_INFO *infoPtr, LPARAM lParam)
576 577 578 579
{
  POINT pt;
  UINT dummyflag;

580 581
  pt.x = (short)LOWORD(lParam);
  pt.y = (short)HIWORD(lParam);
582
  ScreenToClient(infoPtr->hwnd, &pt);
583

584
  if (TAB_InternalHitTest(infoPtr, pt, &dummyflag) == -1)
585 586 587 588
    return HTTRANSPARENT;
  else
    return HTCLIENT;
}
Alex Priem's avatar
Alex Priem committed
589 590

static LRESULT
591
TAB_LButtonDown (TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
Alex Priem's avatar
Alex Priem committed
592
{
593
  POINT pt;
Mike McCormack's avatar
Mike McCormack committed
594 595
  INT newItem;
  UINT dummy;
Alex Priem's avatar
Alex Priem committed
596

Francis Beaudet's avatar
Francis Beaudet committed
597
  if (infoPtr->hwndToolTip)
598
    TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
Francis Beaudet's avatar
Francis Beaudet committed
599
		    WM_LBUTTONDOWN, wParam, lParam);
Alex Priem's avatar
Alex Priem committed
600

Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
601
  if (GetWindowLongW(infoPtr->hwnd, GWL_STYLE) & TCS_FOCUSONBUTTONDOWN ) {
602
    SetFocus (infoPtr->hwnd);
Francis Beaudet's avatar
Francis Beaudet committed
603
  }
Alex Priem's avatar
Alex Priem committed
604

Francis Beaudet's avatar
Francis Beaudet committed
605
  if (infoPtr->hwndToolTip)
606
    TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
Francis Beaudet's avatar
Francis Beaudet committed
607
		    WM_LBUTTONDOWN, wParam, lParam);
608

609 610
  pt.x = (short)LOWORD(lParam);
  pt.y = (short)HIWORD(lParam);
611

612
  newItem = TAB_InternalHitTest (infoPtr, pt, &dummy);
613

614
  TRACE("On Tab, item %d\n", newItem);
615

616
  if (newItem != -1 && infoPtr->iSelected != newItem)
Francis Beaudet's avatar
Francis Beaudet committed
617
  {
618
    if (!TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGING))
Francis Beaudet's avatar
Francis Beaudet committed
619 620 621
    {
      infoPtr->iSelected = newItem;
      infoPtr->uFocus    = newItem;
622
      TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGE);
Alexandre Julliard's avatar
Alexandre Julliard committed
623

624
      TAB_EnsureSelectionVisible(infoPtr);
Francis Beaudet's avatar
Francis Beaudet committed
625

626
      TAB_InvalidateTabArea(infoPtr);
Francis Beaudet's avatar
Francis Beaudet committed
627 628
    }
  }
629 630 631
  return 0;
}

632 633
static inline LRESULT
TAB_LButtonUp (const TAB_INFO *infoPtr)
634
{
635
  TAB_SendSimpleNotify(infoPtr, NM_CLICK);
Francis Beaudet's avatar
Francis Beaudet committed
636 637

  return 0;
Alexandre Julliard's avatar
Alexandre Julliard committed
638 639
}

640 641
static inline LRESULT
TAB_RButtonDown (const TAB_INFO *infoPtr)
Alex Priem's avatar
Alex Priem committed
642
{
643
  TAB_SendSimpleNotify(infoPtr, NM_RCLICK);
Francis Beaudet's avatar
Francis Beaudet committed
644
  return 0;
Alex Priem's avatar
Alex Priem committed
645 646
}

647 648 649 650 651 652 653 654 655
/******************************************************************************
 * TAB_DrawLoneItemInterior
 *
 * This calls TAB_DrawItemInterior.  However, TAB_DrawItemInterior is normally
 * called by TAB_DrawItem which is normally called by TAB_Refresh which sets
 * up the device context and font.  This routine does the same setup but
 * only calls TAB_DrawItemInterior for the single specified item.
 */
static void
656
TAB_DrawLoneItemInterior(const TAB_INFO* infoPtr, int iItem)
657
{
658
  HDC hdc = GetDC(infoPtr->hwnd);
659 660
  RECT r, rC;

661 662 663
  /* Clip UpDown control to not draw over it */
  if (infoPtr->needsScrolling)
  {
664
    GetWindowRect(infoPtr->hwnd, &rC);
665 666 667
    GetWindowRect(infoPtr->hwndUpDown, &r);
    ExcludeClipRect(hdc, r.left - rC.left, r.top - rC.top, r.right - rC.left, r.bottom - rC.top);
  }
668 669
  TAB_DrawItemInterior(infoPtr, hdc, iItem, NULL);
  ReleaseDC(infoPtr->hwnd, hdc);
670 671
}

672 673
/* update a tab after hottracking - invalidate it or just redraw the interior,
 * based on whether theming is used or not */
674
static inline void hottrack_refresh(const TAB_INFO *infoPtr, int tabIndex)
675 676 677 678 679 680 681 682 683 684 685 686 687
{
    if (tabIndex == -1) return;

    if (GetWindowTheme (infoPtr->hwnd))
    {
        RECT rect;
        TAB_InternalGetItemRect(infoPtr, tabIndex, &rect, NULL);
        InvalidateRect (infoPtr->hwnd, &rect, FALSE);
    }
    else
        TAB_DrawLoneItemInterior(infoPtr, tabIndex);
}

688 689 690 691 692 693 694 695 696 697 698
/******************************************************************************
 * TAB_HotTrackTimerProc
 *
 * When a mouse-move event causes a tab to be highlighted (hot-tracking), a
 * timer is setup so we can check if the mouse is moved out of our window.
 * (We don't get an event when the mouse leaves, the mouse-move events just
 * stop being delivered to our window and just start being delivered to
 * another window.)  This function is called when the timer triggers so
 * we can check if the mouse has left our window.  If so, we un-highlight
 * the hot-tracked tab.
 */
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
699
static void CALLBACK
700 701
TAB_HotTrackTimerProc
  (
702 703
  HWND hwnd,    /* handle of window for timer messages */
  UINT uMsg,    /* WM_TIMER message */
Frank Richter's avatar
Frank Richter committed
704
  UINT_PTR idEvent, /* timer identifier */
705
  DWORD dwTime  /* current system time */
706 707 708 709 710 711 712 713 714 715 716 717 718 719
  )
{
  TAB_INFO* infoPtr = TAB_GetInfoPtr(hwnd);

  if (infoPtr != NULL && infoPtr->iHotTracked >= 0)
  {
    POINT pt;

    /*
    ** If we can't get the cursor position, or if the cursor is outside our
    ** window, we un-highlight the hot-tracked tab.  Note that the cursor is
    ** "outside" even if it is within our bounding rect if another window
    ** overlaps.  Note also that the case where the cursor stayed within our
    ** window but has moved off the hot-tracked tab will be handled by the
720
    ** WM_MOUSEMOVE event.
721 722 723
    */
    if (!GetCursorPos(&pt) || WindowFromPoint(pt) != hwnd)
    {
724
      /* Redraw iHotTracked to look normal */
725 726
      INT iRedraw = infoPtr->iHotTracked;
      infoPtr->iHotTracked = -1;
727
      hottrack_refresh (infoPtr, iRedraw);
728

729
      /* Kill this timer */
730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753
      KillTimer(hwnd, TAB_HOTTRACK_TIMER);
    }
  }
}

/******************************************************************************
 * TAB_RecalcHotTrack
 *
 * If a tab control has the TCS_HOTTRACK style, then the tab under the mouse
 * should be highlighted.  This function determines which tab in a tab control,
 * if any, is under the mouse and records that information.  The caller may
 * supply output parameters to receive the item number of the tab item which
 * was highlighted but isn't any longer and of the tab item which is now
 * highlighted but wasn't previously.  The caller can use this information to
 * selectively redraw those tab items.
 *
 * If the caller has a mouse position, it can supply it through the pos
 * parameter.  For example, TAB_MouseMove does this.  Otherwise, the caller
 * supplies NULL and this function determines the current mouse position
 * itself.
 */
static void
TAB_RecalcHotTrack
  (
754
  TAB_INFO*       infoPtr,
755 756 757 758 759 760 761 762 763 764 765 766 767
  const LPARAM*   pos,
  int*            out_redrawLeave,
  int*            out_redrawEnter
  )
{
  int item = -1;


  if (out_redrawLeave != NULL)
    *out_redrawLeave = -1;
  if (out_redrawEnter != NULL)
    *out_redrawEnter = -1;

768 769
  if ((GetWindowLongW(infoPtr->hwnd, GWL_STYLE) & TCS_HOTTRACK)
      || GetWindowTheme (infoPtr->hwnd))
770 771 772 773 774 775 776
  {
    POINT pt;
    UINT  flags;

    if (pos == NULL)
    {
      GetCursorPos(&pt);
777
      ScreenToClient(infoPtr->hwnd, &pt);
778 779 780
    }
    else
    {
781 782
      pt.x = (short)LOWORD(*pos);
      pt.y = (short)HIWORD(*pos);
783 784
    }

785
    item = TAB_InternalHitTest(infoPtr, pt, &flags);
786 787 788 789 790 791
  }

  if (item != infoPtr->iHotTracked)
  {
    if (infoPtr->iHotTracked >= 0)
    {
792
      /* Mark currently hot-tracked to be redrawn to look normal */
793 794 795 796 797
      if (out_redrawLeave != NULL)
        *out_redrawLeave = infoPtr->iHotTracked;

      if (item < 0)
      {
798
        /* Kill timer which forces recheck of mouse pos */
799
        KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
800 801 802 803
      }
    }
    else
    {
804
      /* Start timer so we recheck mouse pos */
805 806
      UINT timerID = SetTimer
        (
807
        infoPtr->hwnd,
808 809 810 811 812 813 814 815 816 817 818 819 820
        TAB_HOTTRACK_TIMER,
        TAB_HOTTRACK_TIMER_INTERVAL,
        TAB_HotTrackTimerProc
        );

      if (timerID == 0)
        return; /* Hot tracking not available */
    }

    infoPtr->iHotTracked = item;

    if (item >= 0)
    {
821
	/* Mark new hot-tracked to be redrawn to look highlighted */
822 823 824 825 826 827 828 829 830 831 832
      if (out_redrawEnter != NULL)
        *out_redrawEnter = item;
    }
  }
}

/******************************************************************************
 * TAB_MouseMove
 *
 * Handles the mouse-move event.  Updates tooltips.  Updates hot-tracking.
 */
Alex Priem's avatar
Alex Priem committed
833
static LRESULT
834
TAB_MouseMove (TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
Alex Priem's avatar
Alex Priem committed
835
{
836 837 838
  int redrawLeave;
  int redrawEnter;

Francis Beaudet's avatar
Francis Beaudet committed
839
  if (infoPtr->hwndToolTip)
840
    TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
Francis Beaudet's avatar
Francis Beaudet committed
841
		    WM_LBUTTONDOWN, wParam, lParam);
842 843 844

  /* Determine which tab to highlight.  Redraw tabs which change highlight
  ** status. */
845
  TAB_RecalcHotTrack(infoPtr, &lParam, &redrawLeave, &redrawEnter);
846

847 848
  hottrack_refresh (infoPtr, redrawLeave);
  hottrack_refresh (infoPtr, redrawEnter);
849

Francis Beaudet's avatar
Francis Beaudet committed
850
  return 0;
Alex Priem's avatar
Alex Priem committed
851
}
852

853 854 855
/******************************************************************************
 * TAB_AdjustRect
 *
Andreas Mohr's avatar
Andreas Mohr committed
856
 * Calculates the tab control's display area given the window rectangle or
857 858
 * the window rectangle given the requested display rectangle.
 */
859
static LRESULT TAB_AdjustRect(const TAB_INFO *infoPtr, WPARAM fLarger, LPRECT prc)
860
{
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
861
    DWORD lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
862
    LONG *iRightBottom, *iLeftTop;
863

864 865
    TRACE ("hwnd=%p fLarger=%ld (%s)\n", infoPtr->hwnd, fLarger,
           wine_dbgstr_rect(prc));
866

867 868 869 870
    if(lStyle & TCS_VERTICAL)
    {
	iRightBottom = &(prc->right);
	iLeftTop     = &(prc->left);
871
    }
872
    else
873
    {
874 875
	iRightBottom = &(prc->bottom);
	iLeftTop     = &(prc->top);
876
    }
877

878 879
    if (fLarger) /* Go from display rectangle */
    {
880 881 882 883 884 885
        /* Add the height of the tabs. */
	if (lStyle & TCS_BOTTOM)
	    *iRightBottom += infoPtr->tabHeight * infoPtr->uNumRows;
	else
	    *iLeftTop -= infoPtr->tabHeight * infoPtr->uNumRows +
			 ((lStyle & TCS_BUTTONS)? 3 * (infoPtr->uNumRows - 1) : 0);
886

887 888
	/* Inflate the rectangle for the padding */
	InflateRect(prc, DISPLAY_AREA_PADDINGX, DISPLAY_AREA_PADDINGY); 
889

890 891
	/* Inflate for the border */
	InflateRect(prc, CONTROL_BORDER_SIZEX, CONTROL_BORDER_SIZEY);
892 893 894
    }
    else /* Go from window rectangle. */
    {
895 896
	/* Deflate the rectangle for the border */
	InflateRect(prc, -CONTROL_BORDER_SIZEX, -CONTROL_BORDER_SIZEY);
897

898 899
	/* Deflate the rectangle for the padding */
	InflateRect(prc, -DISPLAY_AREA_PADDINGX, -DISPLAY_AREA_PADDINGY);
900

901 902 903 904 905 906
	/* Remove the height of the tabs. */
	if (lStyle & TCS_BOTTOM)
	    *iRightBottom -= infoPtr->tabHeight * infoPtr->uNumRows;
	else
	    *iLeftTop += (infoPtr->tabHeight) * infoPtr->uNumRows +
			 ((lStyle & TCS_BUTTONS)? 3 * (infoPtr->uNumRows - 1) : 0);
907 908
    }

Francis Beaudet's avatar
Francis Beaudet committed
909
  return 0;
910 911
}

Francis Beaudet's avatar
Francis Beaudet committed
912 913 914 915 916 917
/******************************************************************************
 * TAB_OnHScroll
 *
 * This method will handle the notification from the scroll control and
 * perform the scrolling operation on the tab control.
 */
918
static LRESULT TAB_OnHScroll(TAB_INFO *infoPtr, int nScrollCode, int nPos, HWND hwndScroll)
Alexandre Julliard's avatar
Alexandre Julliard committed
919
{
Alexandre Julliard's avatar
Alexandre Julliard committed
920
  if(nScrollCode == SB_THUMBPOSITION && nPos != infoPtr->leftmostVisible)
Francis Beaudet's avatar
Francis Beaudet committed
921
  {
Alexandre Julliard's avatar
Alexandre Julliard committed
922 923 924 925
     if(nPos < infoPtr->leftmostVisible)
        infoPtr->leftmostVisible--;
     else
        infoPtr->leftmostVisible++;
Francis Beaudet's avatar
Francis Beaudet committed
926

927 928
     TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
     TAB_InvalidateTabArea(infoPtr);
929
     SendMessageW(infoPtr->hwndUpDown, UDM_SETPOS, 0,
Alexandre Julliard's avatar
Alexandre Julliard committed
930 931
                   MAKELONG(infoPtr->leftmostVisible, 0));
   }
Alexandre Julliard's avatar
Alexandre Julliard committed
932

Alexandre Julliard's avatar
Alexandre Julliard committed
933
   return 0;
Alexandre Julliard's avatar
Alexandre Julliard committed
934
}
Francis Beaudet's avatar
Francis Beaudet committed
935 936

/******************************************************************************
Gerard Patel's avatar
Gerard Patel committed
937
 * TAB_SetupScrolling
Francis Beaudet's avatar
Francis Beaudet committed
938
 *
939
 * This method will check the current scrolling state and make sure the
Francis Beaudet's avatar
Francis Beaudet committed
940 941 942 943 944 945
 * scrolling control is displayed (or not).
 */
static void TAB_SetupScrolling(
  HWND        hwnd,
  TAB_INFO*   infoPtr,
  const RECT* clientRect)
Alexandre Julliard's avatar
Alexandre Julliard committed
946
{
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
947 948
  static const WCHAR msctls_updown32W[] = { 'm','s','c','t','l','s','_','u','p','d','o','w','n','3','2',0 };
  static const WCHAR emptyW[] = { 0 };
Alexandre Julliard's avatar
Alexandre Julliard committed
949
  INT maxRange = 0;
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
950
  DWORD lStyle = GetWindowLongW(hwnd, GWL_STYLE);
951

Francis Beaudet's avatar
Francis Beaudet committed
952 953 954
  if (infoPtr->needsScrolling)
  {
    RECT controlPos;
Alexandre Julliard's avatar
Alexandre Julliard committed
955
    INT vsize, tabwidth;
956

Francis Beaudet's avatar
Francis Beaudet committed
957 958 959
    /*
     * Calculate the position of the scroll control.
     */
960
    if(lStyle & TCS_VERTICAL)
Francis Beaudet's avatar
Francis Beaudet committed
961
    {
962 963 964 965 966 967 968 969 970 971 972 973 974
      controlPos.right = clientRect->right;
      controlPos.left  = controlPos.right - 2 * GetSystemMetrics(SM_CXHSCROLL);

      if (lStyle & TCS_BOTTOM)
      {
        controlPos.top    = clientRect->bottom - infoPtr->tabHeight;
        controlPos.bottom = controlPos.top + GetSystemMetrics(SM_CYHSCROLL);
      }
      else
      {
        controlPos.bottom = clientRect->top + infoPtr->tabHeight;
        controlPos.top    = controlPos.bottom - GetSystemMetrics(SM_CYHSCROLL);
      }
Francis Beaudet's avatar
Francis Beaudet committed
975 976 977
    }
    else
    {
978 979 980 981 982 983 984 985 986 987 988 989 990
      controlPos.right = clientRect->right;
      controlPos.left  = controlPos.right - 2 * GetSystemMetrics(SM_CXHSCROLL);

      if (lStyle & TCS_BOTTOM)
      {
        controlPos.top    = clientRect->bottom - infoPtr->tabHeight;
        controlPos.bottom = controlPos.top + GetSystemMetrics(SM_CYHSCROLL);
      }
      else
      {
        controlPos.bottom = clientRect->top + infoPtr->tabHeight;
        controlPos.top    = controlPos.bottom - GetSystemMetrics(SM_CYHSCROLL);
      }
Francis Beaudet's avatar
Francis Beaudet committed
991
    }
Alexandre Julliard's avatar
Alexandre Julliard committed
992

Francis Beaudet's avatar
Francis Beaudet committed
993 994
    /*
     * If we don't have a scroll control yet, we want to create one.
995
     * If we have one, we want to make sure it's positioned properly.
Francis Beaudet's avatar
Francis Beaudet committed
996 997 998
     */
    if (infoPtr->hwndUpDown==0)
    {
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
999
      infoPtr->hwndUpDown = CreateWindowW(msctls_updown32W, emptyW,
Alexandre Julliard's avatar
Alexandre Julliard committed
1000
					  WS_VISIBLE | WS_CHILD | UDS_HORZ,
Francis Beaudet's avatar
Francis Beaudet committed
1001 1002 1003
					  controlPos.left, controlPos.top,
					  controlPos.right - controlPos.left,
					  controlPos.bottom - controlPos.top,
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
1004
					  hwnd, NULL, NULL, NULL);
Francis Beaudet's avatar
Francis Beaudet committed
1005 1006 1007
    }
    else
    {
1008
      SetWindowPos(infoPtr->hwndUpDown,
1009
		   NULL,
Francis Beaudet's avatar
Francis Beaudet committed
1010 1011 1012
		   controlPos.left, controlPos.top,
		   controlPos.right - controlPos.left,
		   controlPos.bottom - controlPos.top,
1013
		   SWP_SHOWWINDOW | SWP_NOZORDER);
Francis Beaudet's avatar
Francis Beaudet committed
1014
    }
Alexandre Julliard's avatar
Alexandre Julliard committed
1015 1016 1017 1018 1019 1020 1021 1022 1023

    /* Now calculate upper limit of the updown control range.
     * We do this by calculating how many tabs will be offscreen when the
     * last tab is visible.
     */
    if(infoPtr->uNumItem)
    {
       vsize = clientRect->right - (controlPos.right - controlPos.left + 1);
       maxRange = infoPtr->uNumItem;
1024
       tabwidth = TAB_GetItem(infoPtr, infoPtr->uNumItem - 1)->rect.right;
Alexandre Julliard's avatar
Alexandre Julliard committed
1025 1026 1027

       for(; maxRange > 0; maxRange--)
       {
1028
          if(tabwidth - TAB_GetItem(infoPtr,maxRange - 1)->rect.left > vsize)
Alexandre Julliard's avatar
Alexandre Julliard committed
1029 1030 1031 1032 1033 1034
             break;
       }

       if(maxRange == infoPtr->uNumItem)
          maxRange--;
    }
Francis Beaudet's avatar
Francis Beaudet committed
1035 1036 1037
  }
  else
  {
1038
    /* If we once had a scroll control... hide it */
Francis Beaudet's avatar
Francis Beaudet committed
1039 1040 1041
    if (infoPtr->hwndUpDown!=0)
      ShowWindow(infoPtr->hwndUpDown, SW_HIDE);
  }
Alexandre Julliard's avatar
Alexandre Julliard committed
1042
  if (infoPtr->hwndUpDown)
1043
     SendMessageW(infoPtr->hwndUpDown, UDM_SETRANGE32, 0, maxRange);
Francis Beaudet's avatar
Francis Beaudet committed
1044
}
Alexandre Julliard's avatar
Alexandre Julliard committed
1045

Francis Beaudet's avatar
Francis Beaudet committed
1046 1047 1048 1049 1050 1051 1052 1053
/******************************************************************************
 * TAB_SetItemBounds
 *
 * This method will calculate the position rectangles of all the items in the
 * control. The rectangle calculated starts at 0 for the first item in the
 * list and ignores scrolling and selection.
 * It also uses the current font to determine the height of the tab row and
 * it checks if all the tabs fit in the client area of the window. If they
1054
 * don't, a scrolling control is added.
Francis Beaudet's avatar
Francis Beaudet committed
1055
 */
1056
static void TAB_SetItemBounds (TAB_INFO *infoPtr)
Francis Beaudet's avatar
Francis Beaudet committed
1057
{
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
1058 1059
  LONG        lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
  TEXTMETRICW fontMetrics;
1060
  UINT        curItem;
Francis Beaudet's avatar
Francis Beaudet committed
1061
  INT         curItemLeftPos;
1062
  INT         curItemRowCount;
Francis Beaudet's avatar
Francis Beaudet committed
1063 1064 1065
  HFONT       hFont, hOldFont;
  HDC         hdc;
  RECT        clientRect;
1066 1067 1068
  INT         iTemp;
  RECT*       rcItem;
  INT         iIndex;
1069
  INT         icon_width = 0;
Francis Beaudet's avatar
Francis Beaudet committed
1070 1071 1072 1073 1074

  /*
   * We need to get text information so we need a DC and we need to select
   * a font.
   */
1075
  hdc = GetDC(infoPtr->hwnd);
1076

Francis Beaudet's avatar
Francis Beaudet committed
1077 1078 1079 1080 1081 1082 1083
  hFont = infoPtr->hFont ? infoPtr->hFont : GetStockObject (SYSTEM_FONT);
  hOldFont = SelectObject (hdc, hFont);

  /*
   * We will base the rectangle calculations on the client rectangle
   * of the control.
   */
1084
  GetClientRect(infoPtr->hwnd, &clientRect);
1085

Gerard Patel's avatar
Gerard Patel committed
1086
  /* if TCS_VERTICAL then swap the height and width so this code places the
1087
     tabs along the top of the rectangle and we can just rotate them after
Gerard Patel's avatar
Gerard Patel committed
1088
     rather than duplicate all of the below code */
1089 1090 1091 1092 1093 1094 1095
  if(lStyle & TCS_VERTICAL)
  {
     iTemp = clientRect.bottom;
     clientRect.bottom = clientRect.right;
     clientRect.right = iTemp;
  }

1096 1097 1098 1099
  /* Now use hPadding and vPadding */
  infoPtr->uHItemPadding = infoPtr->uHItemPadding_s;
  infoPtr->uVItemPadding = infoPtr->uVItemPadding_s;
  
1100
  /* The leftmost item will be "0" aligned */
Francis Beaudet's avatar
Francis Beaudet committed
1101
  curItemLeftPos = 0;
Gerard Patel's avatar
Gerard Patel committed
1102
  curItemRowCount = infoPtr->uNumItem ? 1 : 0;
Francis Beaudet's avatar
Francis Beaudet committed
1103

1104
  if (!(infoPtr->fHeightSet))
1105
  {
1106 1107 1108
    int item_height;
    int icon_height = 0;

1109
    /* Use the current font to determine the height of a tab. */
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
1110
    GetTextMetricsW(hdc, &fontMetrics);
1111

1112
    /* Get the icon height */
1113 1114 1115
    if (infoPtr->himl)
      ImageList_GetIconSize(infoPtr->himl, 0, &icon_height);

1116
    /* Take the highest between font or icon */
1117
    if (fontMetrics.tmHeight > icon_height)
1118
      item_height = fontMetrics.tmHeight + 2;
1119 1120 1121 1122
    else
      item_height = icon_height;

    /*
1123 1124
     * Make sure there is enough space for the letters + icon + growing the
     * selected item + extra space for the selected item.
1125
     */
1126
    infoPtr->tabHeight = item_height + 
1127
	                 ((lStyle & TCS_BUTTONS) ? 2 : 1) *
1128
                          infoPtr->uVItemPadding;
1129

1130
    TRACE("tabH=%d, tmH=%d, iconh=%d\n",
1131
	  infoPtr->tabHeight, fontMetrics.tmHeight, icon_height);
1132 1133
  }

1134
  TRACE("client right=%d\n", clientRect.right);
1135

1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147
  /* Get the icon width */
  if (infoPtr->himl)
  {
    ImageList_GetIconSize(infoPtr->himl, &icon_width, 0);

    if (lStyle & TCS_FIXEDWIDTH)
      icon_width += 4;
    else
      /* Add padding if icon is present */
      icon_width += infoPtr->uHItemPadding;
  }

Francis Beaudet's avatar
Francis Beaudet committed
1148 1149
  for (curItem = 0; curItem < infoPtr->uNumItem; curItem++)
  {
1150 1151
    TAB_ITEM *curr = TAB_GetItem(infoPtr, curItem);
	
1152
    /* Set the leftmost position of the tab. */
1153
    curr->rect.left = curItemLeftPos;
Francis Beaudet's avatar
Francis Beaudet committed
1154

1155
    if (lStyle & TCS_FIXEDWIDTH)
1156
    {
1157
      curr->rect.right = curr->rect.left +
1158
        max(infoPtr->tabWidth, icon_width);
1159
    }
1160
    else if (!curr->pszText)
1161
    {
1162 1163 1164 1165 1166 1167
      /* If no text use minimum tab width including padding. */
      if (infoPtr->tabMinWidth < 0)
        curr->rect.right = curr->rect.left + GET_DEFAULT_MIN_TAB_WIDTH(infoPtr);
      else
      {
        curr->rect.right = curr->rect.left + infoPtr->tabMinWidth;
1168

1169 1170 1171 1172 1173 1174 1175 1176 1177
        /* Add extra padding if icon is present */
        if (infoPtr->himl && infoPtr->tabMinWidth > 0 && infoPtr->tabMinWidth < DEFAULT_MIN_TAB_WIDTH
            && infoPtr->uHItemPadding > 1)
          curr->rect.right += EXTRA_ICON_PADDING * (infoPtr->uHItemPadding-1);
      }
    }
    else
    {
      int tabwidth;
1178
      SIZE size;
1179
      /* Calculate how wide the tab is depending on the text it contains */
1180 1181
      GetTextExtentPoint32W(hdc, curr->pszText,
                            lstrlenW(curr->pszText), &size);
1182

1183 1184 1185 1186 1187 1188 1189 1190
      tabwidth = size.cx + icon_width + 2 * infoPtr->uHItemPadding;

      if (infoPtr->tabMinWidth < 0)
        tabwidth = max(tabwidth, GET_DEFAULT_MIN_TAB_WIDTH(infoPtr));
      else
        tabwidth = max(tabwidth, infoPtr->tabMinWidth);

      curr->rect.right = curr->rect.left + tabwidth;
1191
      TRACE("for <%s>, l,r=%d,%d\n",
1192
	  debugstr_w(curr->pszText), curr->rect.left, curr->rect.right);
1193
    }
Francis Beaudet's avatar
Francis Beaudet committed
1194

1195 1196 1197 1198
    /*
     * Check if this is a multiline tab control and if so
     * check to see if we should wrap the tabs
     *
1199
     * Wrap all these tabs. We will arrange them evenly later.
1200 1201 1202
     *
     */

1203
    if (((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)) &&
1204
        (curr->rect.right > 
1205
	(clientRect.right - CONTROL_BORDER_SIZEX - DISPLAY_AREA_PADDINGX)))
1206
    {
1207
        curr->rect.right -= curr->rect.left;
1208

1209
	curr->rect.left = 0;
1210
        curItemRowCount++;
1211
	TRACE("wrapping <%s>, l,r=%d,%d\n", debugstr_w(curr->pszText),
1212
	    curr->rect.left, curr->rect.right);
1213 1214
    }

1215 1216
    curr->rect.bottom = 0;
    curr->rect.top = curItemRowCount - 1;
1217

1218
    TRACE("Rect: %s\n", wine_dbgstr_rect(&curr->rect));
Francis Beaudet's avatar
Francis Beaudet committed
1219 1220 1221 1222 1223

    /*
     * The leftmost position of the next item is the rightmost position
     * of this one.
     */
1224
    if (lStyle & TCS_BUTTONS)
1225
    {
1226
      curItemLeftPos = curr->rect.right + BUTTON_SPACINGX;
1227 1228 1229
      if (lStyle & TCS_FLATBUTTONS)
        curItemLeftPos += FLAT_BTN_SPACINGX;
    }
1230
    else
1231
      curItemLeftPos = curr->rect.right;
Francis Beaudet's avatar
Francis Beaudet committed
1232 1233
  }

1234
  if (!((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)))
1235 1236 1237 1238
  {
    /*
     * Check if we need a scrolling control.
     */
1239
    infoPtr->needsScrolling = (curItemLeftPos + (2 * SELECTED_TAB_OFFSET) >
1240 1241
                               clientRect.right);

1242 1243
    /* Don't need scrolling, then update infoPtr->leftmostVisible */
    if(!infoPtr->needsScrolling)
1244
      infoPtr->leftmostVisible = 0;
1245
  }
1246 1247 1248 1249 1250 1251 1252 1253
  else
  {
    /*
     * No scrolling in Multiline or Vertical styles.
     */
    infoPtr->needsScrolling = FALSE;
    infoPtr->leftmostVisible = 0;
  }
1254
  TAB_SetupScrolling(infoPtr->hwnd, infoPtr, &clientRect);
1255

1256
  /* Set the number of rows */
1257
  infoPtr->uNumRows = curItemRowCount;
Yuxi Zhang's avatar
Yuxi Zhang committed
1258

1259
  /* Arrange all tabs evenly if style says so */
1260 1261 1262 1263
   if (!(lStyle & TCS_RAGGEDRIGHT) &&
       ((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)) &&
       (infoPtr->uNumItem > 0) &&
       (infoPtr->uNumRows > 1))
1264
   {
1265 1266
      INT tabPerRow,remTab,iRow;
      UINT iItm;
1267
      INT iCount=0;
1268 1269

      /*
1270
       * Ok windows tries to even out the rows. place the same
1271 1272 1273
       * number of tabs in each row. So lets give that a shot
       */

Gerard Patel's avatar
Gerard Patel committed
1274 1275
      tabPerRow = infoPtr->uNumItem / (infoPtr->uNumRows);
      remTab = infoPtr->uNumItem % (infoPtr->uNumRows);
1276 1277 1278 1279 1280

      for (iItm=0,iRow=0,iCount=0,curItemLeftPos=0;
           iItm<infoPtr->uNumItem;
           iItm++,iCount++)
      {
1281
          /* normalize the current rect */
1282 1283
          TAB_ITEM *curr = TAB_GetItem(infoPtr, iItm);
 
1284
          /* shift the item to the left side of the clientRect */
1285 1286
          curr->rect.right -= curr->rect.left;
          curr->rect.left = 0;
1287

1288
          TRACE("r=%d, cl=%d, cl.r=%d, iCount=%d, iRow=%d, uNumRows=%d, remTab=%d, tabPerRow=%d\n",
1289
	      curr->rect.right, curItemLeftPos, clientRect.right,
1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309
	      iCount, iRow, infoPtr->uNumRows, remTab, tabPerRow);

          /* if we have reached the maximum number of tabs on this row */
          /* move to the next row, reset our current item left position and */
          /* the count of items on this row */

	  if (lStyle & TCS_VERTICAL) {
	      /* Vert: Add the remaining tabs in the *last* remainder rows */
	      if (iCount >= ((iRow>=(INT)infoPtr->uNumRows - remTab)?tabPerRow + 1:tabPerRow)) {
		  iRow++;
		  curItemLeftPos = 0;
		  iCount = 0;
	      }
	  } else {
	      /* Horz: Add the remaining tabs in the *first* remainder rows */
	      if (iCount >= ((iRow<remTab)?tabPerRow + 1:tabPerRow)) {
		  iRow++;
		  curItemLeftPos = 0;
		  iCount = 0;
	      }
1310
	  }
1311

1312
          /* shift the item to the right to place it as the next item in this row */
1313 1314 1315
          curr->rect.left += curItemLeftPos;
          curr->rect.right += curItemLeftPos;
          curr->rect.top = iRow;
1316
          if (lStyle & TCS_BUTTONS)
1317
	  {
1318
            curItemLeftPos = curr->rect.right + 1;
1319 1320 1321
            if (lStyle & TCS_FLATBUTTONS)
	      curItemLeftPos += FLAT_BTN_SPACINGX;
	  }
1322
          else
1323
            curItemLeftPos = curr->rect.right;
1324

1325
          TRACE("arranging <%s>, l,r=%d,%d, row=%d\n",
1326 1327
	      debugstr_w(curr->pszText), curr->rect.left,
	      curr->rect.right, curr->rect.top);
1328
      }
1329

1330 1331 1332 1333
      /*
       * Justify the rows
       */
      {
1334 1335 1336
	INT widthDiff, iIndexStart=0, iIndexEnd=0;
	INT remainder;
	INT iCount=0;
1337

1338
        while(iIndexStart < infoPtr->uNumItem)
1339
        {
1340 1341
          TAB_ITEM *start = TAB_GetItem(infoPtr, iIndexStart);

1342
          /*
1343
           * find the index of the row
1344 1345 1346 1347
           */
          /* find the first item on the next row */
          for (iIndexEnd=iIndexStart;
              (iIndexEnd < infoPtr->uNumItem) &&
1348 1349
 	      (TAB_GetItem(infoPtr, iIndexEnd)->rect.top ==
                start->rect.top) ;
1350 1351 1352 1353 1354 1355 1356 1357 1358 1359
              iIndexEnd++)
          /* intentionally blank */;

          /*
           * we need to justify these tabs so they fill the whole given
           * client area
           *
           */
          /* find the amount of space remaining on this row */
          widthDiff = clientRect.right - (2 * SELECTED_TAB_OFFSET) -
1360
			TAB_GetItem(infoPtr, iIndexEnd - 1)->rect.right;
1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371

	  /* iCount is the number of tab items on this row */
	  iCount = iIndexEnd - iIndexStart;

	  if (iCount > 1)
	  {
	    remainder = widthDiff % iCount;
	    widthDiff = widthDiff / iCount;
	    /* add widthDiff/iCount, or extra space/items on row, to each item on this row */
	    for (iIndex=iIndexStart, iCount=0; iIndex < iIndexEnd; iIndex++, iCount++)
	    {
1372 1373 1374 1375
              TAB_ITEM *item = TAB_GetItem(infoPtr, iIndex);

	      item->rect.left += iCount * widthDiff;
	      item->rect.right += (iCount + 1) * widthDiff;
1376

1377
              TRACE("adjusting 1 <%s>, l,r=%d,%d\n",
1378 1379
		  debugstr_w(item->pszText),
		  item->rect.left, item->rect.right);
1380

1381
	    }
1382
	    TAB_GetItem(infoPtr, iIndex - 1)->rect.right += remainder;
1383 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;
	    start->rect.right = clientRect.right - 4;
1388

1389
            TRACE("adjusting 2 <%s>, l,r=%d,%d\n",
1390 1391
		debugstr_w(start->pszText),
		start->rect.left, start->rect.right);
1392

1393
	  }
1394

1395

1396 1397
	  iIndexStart = iIndexEnd;
	}
1398 1399 1400
      }
  }

1401 1402 1403 1404 1405 1406
  /* if TCS_VERTICAL rotate the tabs so they are along the side of the clientRect */
  if(lStyle & TCS_VERTICAL)
  {
    RECT rcOriginal;
    for(iIndex = 0; iIndex < infoPtr->uNumItem; iIndex++)
    {
1407
      rcItem = &TAB_GetItem(infoPtr, iIndex)->rect;
1408 1409 1410

      rcOriginal = *rcItem;

1411 1412
      /* this is rotating the items by 90 degrees clockwise around the center of the control */
      rcItem->top = (rcOriginal.left - clientRect.left);
1413 1414 1415 1416 1417 1418
      rcItem->bottom = rcItem->top + (rcOriginal.right - rcOriginal.left);
      rcItem->left = rcOriginal.top;
      rcItem->right = rcOriginal.bottom;
    }
  }

1419 1420
  TAB_EnsureSelectionVisible(infoPtr);
  TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
1421 1422

  /* Cleanup */
Francis Beaudet's avatar
Francis Beaudet committed
1423
  SelectObject (hdc, hOldFont);
1424
  ReleaseDC (infoPtr->hwnd, hdc);
Francis Beaudet's avatar
Francis Beaudet committed
1425 1426
}

1427 1428

static void
1429
TAB_EraseTabInterior(const TAB_INFO *infoPtr, HDC hdc, INT iItem, RECT *drawRect)
1430
{
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
1431
    LONG     lStyle  = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475
    HBRUSH   hbr = CreateSolidBrush (comctl32_color.clrBtnFace);
    BOOL     deleteBrush = TRUE;
    RECT     rTemp = *drawRect;

    InflateRect(&rTemp, -2, -2);
    if (lStyle & TCS_BUTTONS)
    {
	if (iItem == infoPtr->iSelected)
	{
	    /* Background color */
	    if (!(lStyle & TCS_OWNERDRAWFIXED))
	    {
		DeleteObject(hbr);
		hbr = GetSysColorBrush(COLOR_SCROLLBAR);

		SetTextColor(hdc, comctl32_color.clr3dFace);
		SetBkColor(hdc, comctl32_color.clr3dHilight);

		/* if COLOR_WINDOW happens to be the same as COLOR_3DHILIGHT
		* we better use 0x55aa bitmap brush to make scrollbar's background
		* look different from the window background.
		*/
		if (comctl32_color.clr3dHilight == comctl32_color.clrWindow)
		    hbr = COMCTL32_hPattern55AABrush;

		deleteBrush = FALSE;
	    }
	    FillRect(hdc, &rTemp, hbr);
	}
	else  /* ! selected */
	{
	    if (lStyle & TCS_FLATBUTTONS)
	    {
		FillRect(hdc, drawRect, hbr);
		if (iItem == infoPtr->iHotTracked)
		    DrawEdge(hdc, drawRect, EDGE_RAISED, BF_SOFT|BF_RECT);
	    }
	    else
		FillRect(hdc, &rTemp, hbr);
	}

    }
    else /* !TCS_BUTTONS */
    {
1476 1477
        if (!GetWindowTheme (infoPtr->hwnd))
	    FillRect(hdc, &rTemp, hbr);
1478 1479 1480 1481 1482 1483
    }

    /* Cleanup */
    if (deleteBrush) DeleteObject(hbr);
}

1484 1485 1486 1487 1488
/******************************************************************************
 * TAB_DrawItemInterior
 *
 * This method is used to draw the interior (text and icon) of a single tab
 * into the tab control.
1489
 */
1490
static void
1491
TAB_DrawItemInterior(const TAB_INFO *infoPtr, HDC hdc, INT iItem, RECT *drawRect)
1492
{
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
1493
  LONG      lStyle  = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1494 1495 1496

  RECT localRect;

1497
  HPEN   htextPen;
1498 1499
  HPEN   holdPen;
  INT    oldBkMode;
1500 1501
  HFONT  hOldFont;
  
1502
/*  if (drawRect == NULL) */
1503 1504 1505 1506 1507 1508 1509 1510
  {
    BOOL isVisible;
    RECT itemRect;
    RECT selectedRect;

    /*
     * Get the rectangle for the item.
     */
1511
    isVisible = TAB_InternalGetItemRect(infoPtr, iItem, &itemRect, &selectedRect);
1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525
    if (!isVisible)
      return;

    /*
     * Make sure drawRect points to something valid; simplifies code.
     */
    drawRect = &localRect;

    /*
     * This logic copied from the part of TAB_DrawItem which draws
     * the tab background.  It's important to keep it in sync.  I
     * would have liked to avoid code duplication, but couldn't figure
     * out how without making spaghetti of TAB_DrawItem.
     */
1526 1527 1528 1529 1530
    if (iItem == infoPtr->iSelected)
      *drawRect = selectedRect;
    else
      *drawRect = itemRect;
        
1531 1532 1533 1534
    if (lStyle & TCS_BUTTONS)
    {
      if (iItem == infoPtr->iSelected)
      {
1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545
	drawRect->left   += 4;
	drawRect->top    += 4;
	drawRect->right  -= 4;
	drawRect->bottom -= 1;
      }
      else
      {
	drawRect->left   += 2;
	drawRect->top    += 2;
	drawRect->right  -= 2;
	drawRect->bottom -= 2;
1546 1547 1548 1549
      }
    }
    else
    {
1550
      if ((lStyle & TCS_VERTICAL) && (lStyle & TCS_BOTTOM))
1551
      {
1552
        if (iItem != infoPtr->iSelected)
1553
	{
1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581
	  drawRect->left   += 2;
	  drawRect->top    += 2;
	  drawRect->bottom -= 2;
	}
      }
      else if (lStyle & TCS_VERTICAL)
      {
        if (iItem == infoPtr->iSelected)
	{
	  drawRect->right  += 1;
	}
	else
	{
	  drawRect->top    += 2;
	  drawRect->right  -= 2;
	  drawRect->bottom -= 2;
	}
      }
      else if (lStyle & TCS_BOTTOM)
      {
        if (iItem == infoPtr->iSelected)
	{
	  drawRect->top    -= 2;
	}
	else
	{
	  InflateRect(drawRect, -2, -2);
          drawRect->bottom += 2;
1582 1583
	}
      }
1584
      else
1585
      {
1586 1587 1588 1589 1590 1591
        if (iItem == infoPtr->iSelected)
	{
	  drawRect->bottom += 3;
	}
	else
	{
1592
	  drawRect->bottom -= 2;
1593 1594
	  InflateRect(drawRect, -2, 0);
	}
1595
      }
1596 1597
    }
  }
1598
  TRACE("drawRect=(%s)\n", wine_dbgstr_rect(drawRect));
1599 1600

  /* Clear interior */
1601
  TAB_EraseTabInterior (infoPtr, hdc, iItem, drawRect);
1602 1603 1604

  /* Draw the focus rectangle */
  if (!(lStyle & TCS_FOCUSNEVER) &&
1605
      (GetFocus() == infoPtr->hwnd) &&
1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619
      (iItem == infoPtr->uFocus) )
  {
    RECT rFocus = *drawRect;
    InflateRect(&rFocus, -3, -3);
    if (lStyle & TCS_BOTTOM && !(lStyle & TCS_VERTICAL))
      rFocus.top -= 3;
    if (lStyle & TCS_BUTTONS)
    {
      rFocus.left -= 3;
      rFocus.top -= 3;
    }

    DrawFocusRect(hdc, &rFocus);
  }
1620 1621 1622 1623

  /*
   * Text pen
   */
1624
  htextPen = CreatePen( PS_SOLID, 1, GetSysColor(COLOR_BTNTEXT) );
1625 1626
  holdPen  = SelectObject(hdc, htextPen);
  hOldFont = SelectObject(hdc, infoPtr->hFont);
1627

1628 1629 1630
  /*
   * Setup for text output
  */
1631
  oldBkMode = SetBkMode(hdc, TRANSPARENT);
1632 1633 1634 1635
  if (!GetWindowTheme (infoPtr->hwnd) || (lStyle & TCS_BUTTONS))
      SetTextColor(hdc, (((lStyle & TCS_HOTTRACK) && (iItem == infoPtr->iHotTracked) 
                          && !(lStyle & TCS_FLATBUTTONS)) 
                        | (TAB_GetItem(infoPtr, iItem)->dwState & TCIS_HIGHLIGHTED)) ?
1636
                        comctl32_color.clrHighlight : comctl32_color.clrBtnText);
1637 1638 1639 1640

  /*
   * if owner draw, tell the owner to draw
   */
1641
  if ((lStyle & TCS_OWNERDRAWFIXED) && GetParent(infoPtr->hwnd))
1642 1643 1644 1645
  {
    DRAWITEMSTRUCT dis;
    UINT id;

1646 1647 1648 1649 1650 1651 1652 1653
    drawRect->top += 2;
    drawRect->right -= 1;
    if ( iItem == infoPtr->iSelected )
    {
        drawRect->right -= 1;
        drawRect->left += 1;
    }

1654 1655 1656
    /*
     * get the control id
     */
1657
    id = (UINT)GetWindowLongPtrW( infoPtr->hwnd, GWLP_ID );
1658

1659
    /*
1660 1661
     * put together the DRAWITEMSTRUCT
     */
1662 1663 1664 1665
    dis.CtlType    = ODT_TAB;
    dis.CtlID      = id;
    dis.itemID     = iItem;
    dis.itemAction = ODA_DRAWENTIRE;
1666
    dis.itemState = 0;
1667
    if ( iItem == infoPtr->iSelected )
1668 1669 1670
      dis.itemState |= ODS_SELECTED;
    if (infoPtr->uFocus == iItem) 
      dis.itemState |= ODS_FOCUS;
1671
    dis.hwndItem = infoPtr->hwnd;
1672
    dis.hDC      = hdc;
1673
    CopyRect(&dis.rcItem,drawRect);
1674
    dis.itemData = (ULONG_PTR)TAB_GetItem(infoPtr, iItem)->extra;
1675 1676 1677 1678

    /*
     * send the draw message
     */
1679
    SendMessageW( infoPtr->hwndNotify, WM_DRAWITEM, (WPARAM)id, (LPARAM)&dis );
1680 1681 1682
  }
  else
  {
1683
    TAB_ITEM *item = TAB_GetItem(infoPtr, iItem);
1684 1685 1686 1687 1688
    RECT rcTemp;
    RECT rcImage;

    /* used to center the icon and text in the tab */
    RECT rcText;
1689
    INT center_offset_h, center_offset_v;
1690 1691 1692 1693 1694 1695

    /* set rcImage to drawRect, we will use top & left in our ImageList_Draw call */
    rcImage = *drawRect;

    rcTemp = *drawRect;

1696 1697
    rcText.left = rcText.top = rcText.right = rcText.bottom = 0;

1698
    /* get the rectangle that the text fits in */
1699
    if (item->pszText)
1700
    {
1701
      DrawTextW(hdc, item->pszText, -1, &rcText, DT_CALCRECT);
1702
    }
1703 1704 1705 1706 1707
    /*
     * If not owner draw, then do the drawing ourselves.
     *
     * Draw the icon.
     */
1708
    if (infoPtr->himl && item->iImage != -1)
1709
    {
1710 1711 1712
      INT cx;
      INT cy;
      
1713 1714 1715
      ImageList_GetIconSize(infoPtr->himl, &cx, &cy);

      if(lStyle & TCS_VERTICAL)
1716 1717
      {
        center_offset_h = ((drawRect->bottom - drawRect->top) - (cy + infoPtr->uHItemPadding + (rcText.right  - rcText.left))) / 2;
1718
        center_offset_v = ((drawRect->right - drawRect->left) - cx) / 2;
1719
      }
1720
      else
1721 1722
      {
        center_offset_h = ((drawRect->right - drawRect->left) - (cx + infoPtr->uHItemPadding + (rcText.right  - rcText.left))) / 2;
1723
        center_offset_v = ((drawRect->bottom - drawRect->top) - cy) / 2;
1724 1725
      }

1726 1727 1728 1729 1730 1731
      /* if an item is selected, the icon is shifted up instead of down */
      if (iItem == infoPtr->iSelected)
        center_offset_v -= infoPtr->uVItemPadding / 2;
      else
        center_offset_v += infoPtr->uVItemPadding / 2;

1732 1733
      if (lStyle & TCS_FIXEDWIDTH && lStyle & (TCS_FORCELABELLEFT | TCS_FORCEICONLEFT))
	center_offset_h = infoPtr->uHItemPadding;
1734

1735 1736 1737
      if (center_offset_h < 2)
        center_offset_h = 2;
	
1738 1739 1740
      if (center_offset_v < 0)
        center_offset_v = 0;
	
1741
      TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%s), textlen=%d\n",
1742
	  debugstr_w(item->pszText), center_offset_h, center_offset_v,
1743
          wine_dbgstr_rect(drawRect), (rcText.right-rcText.left));
1744

1745 1746
      if((lStyle & TCS_VERTICAL) && (lStyle & TCS_BOTTOM))
      {
1747
        rcImage.top = drawRect->top + center_offset_h;
1748 1749 1750 1751
	/* if tab is TCS_VERTICAL and TCS_BOTTOM, the text is drawn from the */
	/* right side of the tab, but the image still uses the left as its x position */
	/* this keeps the image always drawn off of the same side of the tab */
        rcImage.left = drawRect->right - cx - center_offset_v;
1752
        drawRect->top += cy + infoPtr->uHItemPadding;
1753 1754 1755
      }
      else if(lStyle & TCS_VERTICAL)
      {
1756 1757
        rcImage.top  = drawRect->bottom - cy - center_offset_h;
	rcImage.left = drawRect->left + center_offset_v;
1758
        drawRect->bottom -= cy + infoPtr->uHItemPadding;
1759 1760 1761
      }
      else /* normal style, whether TCS_BOTTOM or not */
      {
1762
        rcImage.left = drawRect->left + center_offset_h;
1763
	rcImage.top = drawRect->top + center_offset_v;
1764
        drawRect->left += cx + infoPtr->uHItemPadding;
1765
      }
1766

1767
      TRACE("drawing image=%d, left=%d, top=%d\n",
1768
	    item->iImage, rcImage.left, rcImage.top-1);
1769 1770 1771
      ImageList_Draw
        (
        infoPtr->himl,
1772
        item->iImage,
1773
        hdc,
1774
        rcImage.left,
1775
        rcImage.top,
1776 1777
        ILD_NORMAL
        );
1778
    }
1779 1780 1781 1782 1783

    /* Now position text */
    if (lStyle & TCS_FIXEDWIDTH && lStyle & TCS_FORCELABELLEFT)
      center_offset_h = infoPtr->uHItemPadding;
    else
1784
      if(lStyle & TCS_VERTICAL)
1785
        center_offset_h = ((drawRect->bottom - drawRect->top) - (rcText.right - rcText.left)) / 2;
1786
      else
1787
        center_offset_h = ((drawRect->right - drawRect->left) - (rcText.right - rcText.left)) / 2;
1788

1789 1790
    if(lStyle & TCS_VERTICAL)
    {
1791 1792 1793 1794 1795
      if(lStyle & TCS_BOTTOM)
        drawRect->top+=center_offset_h;
      else
        drawRect->bottom-=center_offset_h;

1796
      center_offset_v = ((drawRect->right - drawRect->left) - (rcText.bottom - rcText.top)) / 2;
1797
    }
1798
    else
1799
    {
1800
      drawRect->left += center_offset_h;
1801
      center_offset_v = ((drawRect->bottom - drawRect->top) - (rcText.bottom - rcText.top)) / 2;
1802 1803
    }

1804 1805 1806 1807 1808 1809
    /* if an item is selected, the text is shifted up instead of down */
    if (iItem == infoPtr->iSelected)
        center_offset_v -= infoPtr->uVItemPadding / 2;
    else
        center_offset_v += infoPtr->uVItemPadding / 2;

1810 1811 1812 1813 1814 1815 1816 1817
    if (center_offset_v < 0)
      center_offset_v = 0;

    if(lStyle & TCS_VERTICAL)
      drawRect->left += center_offset_v;
    else
      drawRect->top += center_offset_v;

1818
    /* Draw the text */
1819 1820
    if(lStyle & TCS_VERTICAL) /* if we are vertical rotate the text and each character */
    {
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
1821 1822
      static const WCHAR ArialW[] = { 'A','r','i','a','l',0 };
      LOGFONTW logfont;
1823 1824 1825 1826
      HFONT hFont = 0;
      INT nEscapement = 900;
      INT nOrientation = 900;

1827 1828 1829 1830 1831 1832
      if(lStyle & TCS_BOTTOM)
      {
        nEscapement = -900;
        nOrientation = -900;
      }

1833 1834
      /* to get a font with the escapement and orientation we are looking for, we need to */
      /* call CreateFontIndirectA, which requires us to set the values of the logfont we pass in */
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
1835
      if (!GetObjectW((infoPtr->hFont) ?
1836
                infoPtr->hFont : GetStockObject(SYSTEM_FONT),
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
1837
                sizeof(LOGFONTW),&logfont))
1838
      {
1839
        INT iPointSize = 9;
1840

Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
1841
        lstrcpyW(logfont.lfFaceName, ArialW);
1842
        logfont.lfHeight = -MulDiv(iPointSize, GetDeviceCaps(hdc, LOGPIXELSY),
1843 1844 1845 1846 1847 1848
                                    72);
        logfont.lfWeight = FW_NORMAL;
        logfont.lfItalic = 0;
        logfont.lfUnderline = 0;
        logfont.lfStrikeOut = 0;
      }
1849 1850 1851

      logfont.lfEscapement = nEscapement;
      logfont.lfOrientation = nOrientation;
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
1852
      hFont = CreateFontIndirectW(&logfont);
1853
      SelectObject(hdc, hFont);
1854

1855
      if (item->pszText)
1856 1857 1858 1859 1860 1861
      {
        ExtTextOutW(hdc,
        (lStyle & TCS_BOTTOM) ? drawRect->right : drawRect->left,
        (!(lStyle & TCS_BOTTOM)) ? drawRect->bottom : drawRect->top,
        ETO_CLIPPED,
        drawRect,
1862 1863
        item->pszText,
        lstrlenW(item->pszText),
1864 1865
        0);
      }
1866 1867

      DeleteObject(hFont);
1868 1869 1870
    }
    else
    {
1871
      TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%s), textlen=%d\n",
1872
	  debugstr_w(item->pszText), center_offset_h, center_offset_v,
1873
          wine_dbgstr_rect(drawRect), (rcText.right-rcText.left));
1874
      if (item->pszText)
1875 1876 1877 1878
      {
        DrawTextW
        (
          hdc,
1879 1880
          item->pszText,
          lstrlenW(item->pszText),
1881 1882
          drawRect,
          DT_LEFT | DT_SINGLELINE
1883
        );
1884
      }
1885
    }
1886 1887

    *drawRect = rcTemp; /* restore drawRect */
1888 1889 1890 1891 1892
  }

  /*
  * Cleanup
  */
1893
  SelectObject(hdc, hOldFont);
1894 1895
  SetBkMode(hdc, oldBkMode);
  SelectObject(hdc, holdPen);
1896
  DeleteObject( htextPen );
1897 1898
}

Francis Beaudet's avatar
Francis Beaudet committed
1899 1900 1901 1902
/******************************************************************************
 * TAB_DrawItem
 *
 * This method is used to draw a single tab into the tab control.
1903
 */
1904
static void TAB_DrawItem(const TAB_INFO *infoPtr, HDC  hdc, INT  iItem)
Francis Beaudet's avatar
Francis Beaudet committed
1905
{
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
1906
  LONG      lStyle  = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
Francis Beaudet's avatar
Francis Beaudet committed
1907 1908 1909
  RECT      itemRect;
  RECT      selectedRect;
  BOOL      isVisible;
1910 1911 1912 1913
  RECT      r, fillRect, r1;
  INT       clRight = 0;
  INT       clBottom = 0;
  COLORREF  bkgnd, corner;
1914
  HTHEME    theme;
Francis Beaudet's avatar
Francis Beaudet committed
1915 1916 1917 1918

  /*
   * Get the rectangle for the item.
   */
1919
  isVisible = TAB_InternalGetItemRect(infoPtr,
Francis Beaudet's avatar
Francis Beaudet committed
1920 1921 1922 1923 1924 1925
				      iItem,
				      &itemRect,
				      &selectedRect);

  if (isVisible)
  {
1926 1927 1928
    RECT rUD, rC;

    /* Clip UpDown control to not draw over it */
1929 1930
    if (infoPtr->needsScrolling)
    {
1931
      GetWindowRect(infoPtr->hwnd, &rC);
1932 1933 1934
      GetWindowRect(infoPtr->hwndUpDown, &rUD);
      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,
     * then override these variables. They will change what
1938
     * fill colors are used for filling the tabs, and the
1939 1940 1941 1942
     * corners when drawing the edge.
     */
    bkgnd = comctl32_color.clrBtnFace;
    corner = comctl32_color.clrBtnFace;
Francis Beaudet's avatar
Francis Beaudet committed
1943

1944
    if (lStyle & TCS_BUTTONS)
Francis Beaudet's avatar
Francis Beaudet committed
1945
    {
1946
      /* Get item rectangle */
1947 1948
      r = itemRect;

1949
      /* Separators between flat buttons */
1950
      if (lStyle & TCS_FLATBUTTONS)
1951
      {
1952 1953 1954
	r1 = r;
	r1.right += (FLAT_BTN_SPACINGX -2);
	DrawEdge(hdc, &r1, EDGE_ETCHED, BF_RIGHT);
1955 1956
      }

1957 1958
      if (iItem == infoPtr->iSelected)
      {
1959
	DrawEdge(hdc, &r, EDGE_SUNKEN, BF_SOFT|BF_RECT);
1960 1961
	
	OffsetRect(&r, 1, 1);
1962
      }
1963
      else  /* ! selected */
1964
      {
1965
	if (!(lStyle & TCS_FLATBUTTONS))
1966
	  DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_RECT);
1967
      }
Francis Beaudet's avatar
Francis Beaudet committed
1968
    }
1969
    else /* !TCS_BUTTONS */
Francis Beaudet's avatar
Francis Beaudet committed
1970
    {
1971 1972
      /* We draw a rectangle of different sizes depending on the selection
       * state. */
1973 1974
      if (iItem == infoPtr->iSelected) {
	RECT rect;
1975
	GetClientRect (infoPtr->hwnd, &rect);
1976 1977
	clRight = rect.right;
	clBottom = rect.bottom;
1978
        r = selectedRect;
1979
      }
1980 1981
      else
        r = itemRect;
Francis Beaudet's avatar
Francis Beaudet committed
1982

1983
      /*
1984
       * Erase the background. (Delay it but setup rectangle.)
1985 1986
       * This is necessary when drawing the selected item since it is larger
       * than the others, it might overlap with stuff already drawn by the
1987
       * other tabs
1988
       */
1989
      fillRect = r;
1990

1991 1992 1993 1994 1995 1996 1997
      /* Draw themed tabs - but only if they are at the top.
       * Windows draws even side or bottom tabs themed, with wacky results.
       * However, since in Wine apps may get themed that did not opt in via
       * a manifest avoid theming when we know the result will be wrong */
      if ((theme = GetWindowTheme (infoPtr->hwnd)) 
          && ((lStyle & (TCS_VERTICAL | TCS_BOTTOM)) == 0))
      {
1998
          static const int partIds[8] = {
1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024 2025 2026 2027 2028 2029 2030 2031 2032 2033 2034 2035 2036 2037
              /* Normal item */
              TABP_TABITEM,
              TABP_TABITEMLEFTEDGE,
              TABP_TABITEMRIGHTEDGE,
              TABP_TABITEMBOTHEDGE,
              /* Selected tab */
              TABP_TOPTABITEM,
              TABP_TOPTABITEMLEFTEDGE,
              TABP_TOPTABITEMRIGHTEDGE,
              TABP_TOPTABITEMBOTHEDGE,
          };
          int partIndex = 0;
          int stateId = TIS_NORMAL;

          /* selected and unselected tabs have different parts */
          if (iItem == infoPtr->iSelected)
              partIndex += 4;
          /* The part also differs on the position of a tab on a line.
           * "Visually" determining the position works well enough. */
          if(selectedRect.left == 0)
              partIndex += 1;
          if(selectedRect.right == clRight)
              partIndex += 2;

          if (iItem == infoPtr->iSelected)
              stateId = TIS_SELECTED;
          else if (iItem == infoPtr->iHotTracked)
              stateId = TIS_HOT;
          else if (iItem == infoPtr->uFocus)
              stateId = TIS_FOCUSED;

          /* Adjust rectangle for bottommost row */
          if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
            r.bottom += 3;

          DrawThemeBackground (theme, hdc, partIds[partIndex], stateId, &r, NULL);
          GetThemeBackgroundContentRect (theme, hdc, partIds[partIndex], stateId, &r, &r);
      }
      else if(lStyle & TCS_VERTICAL)
2038
      {
2039 2040
	/* These are for adjusting the drawing of a Selected tab      */
	/* The initial values are for the normal case of non-Selected */
2041
	int ZZ = 1;   /* Do not stretch if selected */
2042 2043 2044 2045 2046
	if (iItem == infoPtr->iSelected) {
	    ZZ = 0;

	    /* if leftmost draw the line longer */
	    if(selectedRect.top == 0)
2047
		fillRect.top += CONTROL_BORDER_SIZEY;
2048 2049
	    /* if rightmost draw the line longer */
	    if(selectedRect.bottom == clBottom)
2050
		fillRect.bottom -= CONTROL_BORDER_SIZEY;
2051 2052
	}

2053 2054
        if (lStyle & TCS_BOTTOM)
        {
2055 2056 2057
	  /* Adjust both rectangles to match native */
	  r.left += (1-ZZ);

2058 2059
          TRACE("<right> item=%d, fill=(%s), edge=(%s)\n",
                iItem, wine_dbgstr_rect(&fillRect), wine_dbgstr_rect(&r));
2060 2061 2062

	  /* Clear interior */
	  SetBkColor(hdc, bkgnd);
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
2063
	  ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2064 2065 2066 2067

	  /* Draw rectangular edge around tab */
	  DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_RIGHT|BF_TOP|BF_BOTTOM);

2068
	  /* Now erase the top corner and draw diagonal edge */
2069 2070 2071 2072 2073
	  SetBkColor(hdc, corner);
	  r1.left = r.right - ROUND_CORNER_SIZE - 1;
	  r1.top = r.top;
	  r1.right = r.right;
	  r1.bottom = r1.top + ROUND_CORNER_SIZE;
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
2074
	  ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2075 2076 2077
	  r1.right--;
	  DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPLEFT);

2078
	  /* Now erase the bottom corner and draw diagonal edge */
2079 2080 2081 2082
	  r1.left = r.right - ROUND_CORNER_SIZE - 1;
	  r1.bottom = r.bottom;
	  r1.right = r.right;
	  r1.top = r1.bottom - ROUND_CORNER_SIZE;
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
2083
	  ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2084 2085 2086 2087 2088 2089 2090 2091 2092 2093
	  r1.right--;
	  DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMLEFT);

	  if ((iItem == infoPtr->iSelected) && (selectedRect.top == 0)) {
	      r1 = r;
	      r1.right = r1.left;
	      r1.left--;
	      DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_TOP);
	  }

2094 2095 2096
        }
        else
        {
2097 2098
          TRACE("<left> item=%d, fill=(%s), edge=(%s)\n",
                iItem, wine_dbgstr_rect(&fillRect), wine_dbgstr_rect(&r));
2099 2100 2101

	  /* Clear interior */
	  SetBkColor(hdc, bkgnd);
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
2102
	  ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2103 2104 2105 2106

	  /* Draw rectangular edge around tab */
	  DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_TOP|BF_BOTTOM);

2107
	  /* Now erase the top corner and draw diagonal edge */
2108 2109 2110 2111 2112
	  SetBkColor(hdc, corner);
	  r1.left = r.left;
	  r1.top = r.top;
	  r1.right = r1.left + ROUND_CORNER_SIZE + 1;
	  r1.bottom = r1.top + ROUND_CORNER_SIZE;
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
2113
	  ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2114 2115 2116
	  r1.left++;
	  DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPRIGHT);

2117
	  /* Now erase the bottom corner and draw diagonal edge */
2118 2119 2120 2121
	  r1.left = r.left;
	  r1.bottom = r.bottom;
	  r1.right = r1.left + ROUND_CORNER_SIZE + 1;
	  r1.top = r1.bottom - ROUND_CORNER_SIZE;
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
2122
	  ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2123 2124
	  r1.left++;
	  DrawEdge(hdc, &r1, EDGE_SUNKEN, BF_DIAGONAL_ENDTOPLEFT);
2125
        }
2126
      }
2127
      else  /* ! TCS_VERTICAL */
2128
      {
2129 2130 2131 2132 2133
	/* These are for adjusting the drawing of a Selected tab      */
	/* The initial values are for the normal case of non-Selected */
	if (iItem == infoPtr->iSelected) {
	    /* if leftmost draw the line longer */
	    if(selectedRect.left == 0)
2134
		fillRect.left += CONTROL_BORDER_SIZEX;
2135 2136
	    /* if rightmost draw the line longer */
	    if(selectedRect.right == clRight)
2137
		fillRect.right -= CONTROL_BORDER_SIZEX;
2138 2139
	}

2140 2141
        if (lStyle & TCS_BOTTOM)
        {
2142
	  /* Adjust both rectangles for topmost row */
2143
	  if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2144 2145 2146 2147
	  {
	    fillRect.top -= 2;
	    r.top -= 1;
	  }
2148

2149 2150
          TRACE("<bottom> item=%d, fill=(%s), edge=(%s)\n",
                iItem, wine_dbgstr_rect(&fillRect), wine_dbgstr_rect(&r));
2151 2152 2153

	  /* Clear interior */
	  SetBkColor(hdc, bkgnd);
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
2154
	  ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2155 2156 2157 2158

	  /* Draw rectangular edge around tab */
	  DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_BOTTOM|BF_RIGHT);

2159
	  /* Now erase the righthand corner and draw diagonal edge */
2160 2161 2162 2163 2164
	  SetBkColor(hdc, corner);
	  r1.left = r.right - ROUND_CORNER_SIZE;
	  r1.bottom = r.bottom;
	  r1.right = r.right;
	  r1.top = r1.bottom - ROUND_CORNER_SIZE - 1;
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
2165
	  ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2166 2167 2168
	  r1.bottom--;
	  DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMLEFT);

2169
	  /* Now erase the lefthand corner and draw diagonal edge */
2170 2171 2172 2173
	  r1.left = r.left;
	  r1.bottom = r.bottom;
	  r1.right = r1.left + ROUND_CORNER_SIZE;
	  r1.top = r1.bottom - ROUND_CORNER_SIZE - 1;
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
2174
	  ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2175 2176 2177
	  r1.bottom--;
	  DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPLEFT);

2178 2179 2180 2181 2182 2183
	  if (iItem == infoPtr->iSelected)
	  {
	    r.top += 2;
	    r.left += 1;
	    if (selectedRect.left == 0)
	    {
2184 2185 2186 2187
	      r1 = r;
	      r1.bottom = r1.top;
	      r1.top--;
	      DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_LEFT);
2188
	    }
2189 2190
	  }

2191 2192 2193
        }
        else
        {
2194
	  /* Adjust both rectangles for bottommost row */
2195
	  if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2196 2197 2198 2199
	  {
	    fillRect.bottom += 3;
	    r.bottom += 2;
	  }
2200

2201 2202
          TRACE("<top> item=%d, fill=(%s), edge=(%s)\n",
                iItem, wine_dbgstr_rect(&fillRect), wine_dbgstr_rect(&r));
2203 2204 2205

	  /* Clear interior */
	  SetBkColor(hdc, bkgnd);
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
2206
	  ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2207 2208 2209 2210

	  /* Draw rectangular edge around tab */
	  DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_TOP|BF_RIGHT);

2211
	  /* Now erase the righthand corner and draw diagonal edge */
2212 2213 2214 2215 2216
	  SetBkColor(hdc, corner);
	  r1.left = r.right - ROUND_CORNER_SIZE;
	  r1.top = r.top;
	  r1.right = r.right;
	  r1.bottom = r1.top + ROUND_CORNER_SIZE + 1;
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
2217
	  ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2218 2219 2220
	  r1.top++;
	  DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMRIGHT);

2221
	  /* Now erase the lefthand corner and draw diagonal edge */
2222 2223 2224 2225
	  r1.left = r.left;
	  r1.top = r.top;
	  r1.right = r1.left + ROUND_CORNER_SIZE;
	  r1.bottom = r1.top + ROUND_CORNER_SIZE + 1;
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
2226
	  ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2227 2228
	  r1.top++;
	  DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPRIGHT);
2229
        }
2230 2231
      }
    }
2232

2233 2234
    TAB_DumpItemInternal(infoPtr, iItem);

2235
    /* This modifies r to be the text rectangle. */
2236
    TAB_DrawItemInterior(infoPtr, hdc, iItem, &r);
Francis Beaudet's avatar
Francis Beaudet committed
2237
  }
Alexandre Julliard's avatar
Alexandre Julliard committed
2238 2239
}

Francis Beaudet's avatar
Francis Beaudet committed
2240 2241 2242 2243 2244
/******************************************************************************
 * TAB_DrawBorder
 *
 * This method is used to draw the raised border around the tab control
 * "content" area.
2245
 */
2246
static void TAB_DrawBorder(const TAB_INFO *infoPtr, HDC hdc)
Alexandre Julliard's avatar
Alexandre Julliard committed
2247
{
Francis Beaudet's avatar
Francis Beaudet committed
2248
  RECT rect;
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
2249
  DWORD lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
2250
  HTHEME theme = GetWindowTheme (infoPtr->hwnd);
Alexandre Julliard's avatar
Alexandre Julliard committed
2251

2252
  GetClientRect (infoPtr->hwnd, &rect);
Alexandre Julliard's avatar
Alexandre Julliard committed
2253

Francis Beaudet's avatar
Francis Beaudet committed
2254 2255 2256
  /*
   * Adjust for the style
   */
Gerard Patel's avatar
Gerard Patel committed
2257 2258

  if (infoPtr->uNumItem)
Francis Beaudet's avatar
Francis Beaudet committed
2259
  {
Gerard Patel's avatar
Gerard Patel committed
2260
    if ((lStyle & TCS_BOTTOM) && !(lStyle & TCS_VERTICAL))
2261
      rect.bottom -= infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
Gerard Patel's avatar
Gerard Patel committed
2262
    else if((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
2263
      rect.right  -= infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
Gerard Patel's avatar
Gerard Patel committed
2264
    else if(lStyle & TCS_VERTICAL)
2265
      rect.left   += infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
Gerard Patel's avatar
Gerard Patel committed
2266
    else /* not TCS_VERTICAL and not TCS_BOTTOM */
2267
      rect.top    += infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
Francis Beaudet's avatar
Francis Beaudet committed
2268
  }
Alexandre Julliard's avatar
Alexandre Julliard committed
2269

2270
  TRACE("border=(%s)\n", wine_dbgstr_rect(&rect));
2271

2272 2273 2274 2275
  if (theme)
      DrawThemeBackground (theme, hdc, TABP_PANE, 0, &rect, NULL);
  else
      DrawEdge(hdc, &rect, EDGE_RAISED, BF_SOFT|BF_RECT);
Alexandre Julliard's avatar
Alexandre Julliard committed
2276 2277
}

Francis Beaudet's avatar
Francis Beaudet committed
2278 2279 2280 2281
/******************************************************************************
 * TAB_Refresh
 *
 * This method repaints the tab control..
2282
 */
2283
static void TAB_Refresh (TAB_INFO *infoPtr, HDC hdc)
Alexandre Julliard's avatar
Alexandre Julliard committed
2284
{
Francis Beaudet's avatar
Francis Beaudet committed
2285 2286
  HFONT hOldFont;
  INT i;
Alexandre Julliard's avatar
Alexandre Julliard committed
2287

Francis Beaudet's avatar
Francis Beaudet committed
2288 2289
  if (!infoPtr->DoRedraw)
    return;
Alex Priem's avatar
Alex Priem committed
2290

Francis Beaudet's avatar
Francis Beaudet committed
2291
  hOldFont = SelectObject (hdc, infoPtr->hFont);
Alexandre Julliard's avatar
Alexandre Julliard committed
2292

Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
2293
  if (GetWindowLongW(infoPtr->hwnd, GWL_STYLE) & TCS_BUTTONS)
Francis Beaudet's avatar
Francis Beaudet committed
2294
  {
2295
    for (i = 0; i < infoPtr->uNumItem; i++)
2296
      TAB_DrawItem (infoPtr, hdc, i);
Francis Beaudet's avatar
Francis Beaudet committed
2297
  }
2298 2299
  else
  {
2300
    /* Draw all the non selected item first */
2301
    for (i = 0; i < infoPtr->uNumItem; i++)
2302 2303
    {
      if (i != infoPtr->iSelected)
2304
	TAB_DrawItem (infoPtr, hdc, i);
2305
    }
Francis Beaudet's avatar
Francis Beaudet committed
2306

2307 2308
    /* Now, draw the border, draw it before the selected item
     * since the selected item overwrites part of the border. */
2309
    TAB_DrawBorder (infoPtr, hdc);
Francis Beaudet's avatar
Francis Beaudet committed
2310

2311
    /* Then, draw the selected item */
2312
    TAB_DrawItem (infoPtr, hdc, infoPtr->iSelected);
2313
  }
Francis Beaudet's avatar
Francis Beaudet committed
2314 2315

  SelectObject (hdc, hOldFont);
Alexandre Julliard's avatar
Alexandre Julliard committed
2316 2317
}

2318
static inline DWORD TAB_GetRowCount (const TAB_INFO *infoPtr)
2319 2320 2321 2322
{
  return infoPtr->uNumRows;
}

2323
static inline LRESULT TAB_SetRedraw (TAB_INFO *infoPtr, BOOL doRedraw)
Alex Priem's avatar
Alex Priem committed
2324
{
2325
  infoPtr->DoRedraw = doRedraw;
Francis Beaudet's avatar
Francis Beaudet committed
2326
  return 0;
Alex Priem's avatar
Alex Priem committed
2327 2328
}

Francis Beaudet's avatar
Francis Beaudet committed
2329 2330 2331 2332 2333 2334 2335 2336
/******************************************************************************
 * TAB_EnsureSelectionVisible
 *
 * This method will make sure that the current selection is completely
 * visible by scrolling until it is.
 */
static void TAB_EnsureSelectionVisible(
  TAB_INFO* infoPtr)
Alexandre Julliard's avatar
Alexandre Julliard committed
2337
{
Alexandre Julliard's avatar
Alexandre Julliard committed
2338
  INT iSelected = infoPtr->iSelected;
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
2339
  LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
2340 2341
  INT iOrigLeftmostVisible = infoPtr->leftmostVisible;

2342 2343
  /* set the items row to the bottommost row or topmost row depending on
   * style */
Gerard Patel's avatar
Gerard Patel committed
2344
  if ((infoPtr->uNumRows > 1) && !(lStyle & TCS_BUTTONS))
2345
  {
2346
      TAB_ITEM *selected = TAB_GetItem(infoPtr, iSelected);
2347
      INT newselected;
2348 2349
      INT iTargetRow;

2350
      if(lStyle & TCS_VERTICAL)
2351
        newselected = selected->rect.left;
2352
      else
2353
        newselected = selected->rect.top;
2354

Gerard Patel's avatar
Gerard Patel committed
2355 2356 2357
      /* the target row is always (number of rows - 1)
         as row 0 is furthest from the clientRect */
      iTargetRow = infoPtr->uNumRows - 1;
2358 2359 2360

      if (newselected != iTargetRow)
      {
2361
         UINT i;
2362 2363 2364 2365 2366
         if(lStyle & TCS_VERTICAL)
         {
           for (i=0; i < infoPtr->uNumItem; i++)
           {
             /* move everything in the row of the selected item to the iTargetRow */
2367 2368 2369 2370
             TAB_ITEM *item = TAB_GetItem(infoPtr, i);

             if (item->rect.left == newselected )
                 item->rect.left = iTargetRow;
2371 2372
             else
             {
2373 2374
               if (item->rect.left > newselected)
                 item->rect.left-=1;
2375 2376 2377 2378 2379 2380 2381
             }
           }
         }
         else
         {
           for (i=0; i < infoPtr->uNumItem; i++)
           {
2382 2383 2384 2385
             TAB_ITEM *item = TAB_GetItem(infoPtr, i);

             if (item->rect.top == newselected )
                 item->rect.top = iTargetRow;
2386 2387
             else
             {
2388 2389
               if (item->rect.top > newselected)
                 item->rect.top-=1;
2390 2391 2392
             }
          }
        }
2393
        TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
2394 2395 2396
      }
  }

Francis Beaudet's avatar
Francis Beaudet committed
2397 2398 2399 2400
  /*
   * Do the trivial cases first.
   */
  if ( (!infoPtr->needsScrolling) ||
2401
       (infoPtr->hwndUpDown==0) || (lStyle & TCS_VERTICAL))
Francis Beaudet's avatar
Francis Beaudet committed
2402 2403
    return;

Alexandre Julliard's avatar
Alexandre Julliard committed
2404
  if (infoPtr->leftmostVisible >= iSelected)
Francis Beaudet's avatar
Francis Beaudet committed
2405
  {
Alexandre Julliard's avatar
Alexandre Julliard committed
2406
    infoPtr->leftmostVisible = iSelected;
Francis Beaudet's avatar
Francis Beaudet committed
2407
  }
Alexandre Julliard's avatar
Alexandre Julliard committed
2408
  else
Francis Beaudet's avatar
Francis Beaudet committed
2409
  {
2410
     TAB_ITEM *selected = TAB_GetItem(infoPtr, iSelected);
Alexandre Julliard's avatar
Alexandre Julliard committed
2411
     RECT r;
2412 2413
     INT width;
     UINT i;
2414 2415

     /* Calculate the part of the client area that is visible */
2416
     GetClientRect(infoPtr->hwnd, &r);
Alexandre Julliard's avatar
Alexandre Julliard committed
2417 2418 2419 2420 2421
     width = r.right;

     GetClientRect(infoPtr->hwndUpDown, &r);
     width -= r.right;

2422 2423
     if ((selected->rect.right -
          selected->rect.left) >= width )
Alexandre Julliard's avatar
Alexandre Julliard committed
2424 2425 2426 2427 2428 2429 2430 2431 2432 2433
     {
        /* Special case: width of selected item is greater than visible
         * part of control.
         */
        infoPtr->leftmostVisible = iSelected;
     }
     else
     {
        for (i = infoPtr->leftmostVisible; i < infoPtr->uNumItem; i++)
        {
2434
           if ((selected->rect.right - TAB_GetItem(infoPtr, i)->rect.left) < width)
Alexandre Julliard's avatar
Alexandre Julliard committed
2435 2436 2437 2438
              break;
        }
        infoPtr->leftmostVisible = i;
     }
Francis Beaudet's avatar
Francis Beaudet committed
2439
  }
Alexandre Julliard's avatar
Alexandre Julliard committed
2440

2441
  if (infoPtr->leftmostVisible != iOrigLeftmostVisible)
2442
    TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
2443

2444
  SendMessageW(infoPtr->hwndUpDown, UDM_SETPOS, 0,
Alexandre Julliard's avatar
Alexandre Julliard committed
2445
               MAKELONG(infoPtr->leftmostVisible, 0));
Francis Beaudet's avatar
Francis Beaudet committed
2446
}
Alexandre Julliard's avatar
Alexandre Julliard committed
2447

Francis Beaudet's avatar
Francis Beaudet committed
2448 2449 2450 2451 2452 2453 2454
/******************************************************************************
 * TAB_InvalidateTabArea
 *
 * This method will invalidate the portion of the control that contains the
 * tabs. It is called when the state of the control changes and needs
 * to be redisplayed
 */
2455
static void TAB_InvalidateTabArea(const TAB_INFO *infoPtr)
Francis Beaudet's avatar
Francis Beaudet committed
2456
{
2457
  RECT clientRect, rInvalidate, rAdjClient;
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
2458
  DWORD lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
Gerard Patel's avatar
Gerard Patel committed
2459
  INT lastRow = infoPtr->uNumRows - 1;
2460
  RECT rect;
Gerard Patel's avatar
Gerard Patel committed
2461 2462

  if (lastRow < 0) return;
Alexandre Julliard's avatar
Alexandre Julliard committed
2463

2464
  GetClientRect(infoPtr->hwnd, &clientRect);
2465
  rInvalidate = clientRect;
2466 2467
  rAdjClient = clientRect;

2468
  TAB_AdjustRect(infoPtr, 0, &rAdjClient);
Alexandre Julliard's avatar
Alexandre Julliard committed
2469

2470
  TAB_InternalGetItemRect(infoPtr, infoPtr->uNumItem-1 , &rect, NULL);
2471
  if ((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
2472
  {
2473 2474 2475
    rInvalidate.left = rAdjClient.right;
    if (infoPtr->uNumRows == 1)
      rInvalidate.bottom = clientRect.top + rect.bottom + 2 * SELECTED_TAB_OFFSET;
2476
  }
2477
  else if(lStyle & TCS_VERTICAL)
2478
  {
2479 2480 2481
    rInvalidate.right = rAdjClient.left;
    if (infoPtr->uNumRows == 1)
      rInvalidate.bottom = clientRect.top + rect.bottom + 2 * SELECTED_TAB_OFFSET;
2482
  }
2483
  else if (lStyle & TCS_BOTTOM)
Francis Beaudet's avatar
Francis Beaudet committed
2484
  {
2485 2486 2487
    rInvalidate.top = rAdjClient.bottom;
    if (infoPtr->uNumRows == 1)
      rInvalidate.right = clientRect.left + rect.right + 2 * SELECTED_TAB_OFFSET;
Francis Beaudet's avatar
Francis Beaudet committed
2488
  }
2489
  else 
Francis Beaudet's avatar
Francis Beaudet committed
2490
  {
2491 2492 2493
    rInvalidate.bottom = rAdjClient.top;
    if (infoPtr->uNumRows == 1)
      rInvalidate.right = clientRect.left + rect.right + 2 * SELECTED_TAB_OFFSET;
Francis Beaudet's avatar
Francis Beaudet committed
2494
  }
2495 2496
  
  /* Punch out the updown control */
2497 2498
  if (infoPtr->needsScrolling && (rInvalidate.right > 0)) {
    RECT r;
2499
    GetClientRect(infoPtr->hwndUpDown, &r);
2500 2501 2502 2503
    if (rInvalidate.right > clientRect.right - r.left)
      rInvalidate.right = rInvalidate.right - (r.right - r.left);
    else
      rInvalidate.right = clientRect.right - r.left;
2504
  }
2505 2506 2507

  TRACE("invalidate (%s)\n", wine_dbgstr_rect(&rInvalidate));

2508
  InvalidateRect(infoPtr->hwnd, &rInvalidate, TRUE);
Francis Beaudet's avatar
Francis Beaudet committed
2509
}
Alexandre Julliard's avatar
Alexandre Julliard committed
2510

2511
static inline LRESULT TAB_Paint (TAB_INFO *infoPtr, HDC hdcPaint)
Francis Beaudet's avatar
Francis Beaudet committed
2512 2513 2514
{
  HDC hdc;
  PAINTSTRUCT ps;
2515

2516 2517 2518
  if (hdcPaint)
    hdc = hdcPaint;
  else
2519
  {
2520
    hdc = BeginPaint (infoPtr->hwnd, &ps);
2521
    TRACE("erase %d, rect=(%s)\n", ps.fErase, wine_dbgstr_rect(&ps.rcPaint));
2522
  }
2523

2524 2525 2526 2527
  TAB_Refresh (infoPtr, hdc);

  if (!hdcPaint)
    EndPaint (infoPtr->hwnd, &ps);
Alexandre Julliard's avatar
Alexandre Julliard committed
2528

Francis Beaudet's avatar
Francis Beaudet committed
2529 2530
  return 0;
}
Alexandre Julliard's avatar
Alexandre Julliard committed
2531

Francis Beaudet's avatar
Francis Beaudet committed
2532
static LRESULT
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
2533
TAB_InsertItemT (TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam, BOOL bUnicode)
2534
{
2535
  TAB_ITEM *item;
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
2536
  TCITEMW *pti;
2537
  INT iItem;
Francis Beaudet's avatar
Francis Beaudet committed
2538
  RECT rect;
2539

2540
  GetClientRect (infoPtr->hwnd, &rect);
2541
  TRACE("Rect: %p %s\n", infoPtr->hwnd, wine_dbgstr_rect(&rect));
2542

Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
2543
  pti = (TCITEMW *)lParam;
Francis Beaudet's avatar
Francis Beaudet committed
2544
  iItem = (INT)wParam;
2545

Francis Beaudet's avatar
Francis Beaudet committed
2546 2547 2548
  if (iItem < 0) return -1;
  if (iItem > infoPtr->uNumItem)
    iItem = infoPtr->uNumItem;
2549

Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
2550
  TAB_DumpItemExternalT(pti, iItem, bUnicode);
2551 2552


Francis Beaudet's avatar
Francis Beaudet committed
2553
  if (infoPtr->uNumItem == 0) {
2554
    infoPtr->items = Alloc (TAB_ITEM_SIZE(infoPtr));
Francis Beaudet's avatar
Francis Beaudet committed
2555
    infoPtr->uNumItem++;
2556
    infoPtr->iSelected = 0;
Francis Beaudet's avatar
Francis Beaudet committed
2557 2558
  }
  else {
2559
    LPBYTE oldItems = (LPBYTE)infoPtr->items;
2560

Francis Beaudet's avatar
Francis Beaudet committed
2561
    infoPtr->uNumItem++;
2562
    infoPtr->items = Alloc (TAB_ITEM_SIZE(infoPtr) * infoPtr->uNumItem);
2563

Francis Beaudet's avatar
Francis Beaudet committed
2564 2565
    /* pre insert copy */
    if (iItem > 0) {
2566
      memcpy (infoPtr->items, oldItems,
2567
              iItem * TAB_ITEM_SIZE(infoPtr));
Francis Beaudet's avatar
Francis Beaudet committed
2568
    }
2569

Francis Beaudet's avatar
Francis Beaudet committed
2570 2571
    /* post insert copy */
    if (iItem < infoPtr->uNumItem - 1) {
2572 2573
      memcpy (TAB_GetItem(infoPtr, iItem + 1),
              oldItems + iItem * TAB_ITEM_SIZE(infoPtr),
2574
              (infoPtr->uNumItem - iItem - 1) * TAB_ITEM_SIZE(infoPtr));
2575

Francis Beaudet's avatar
Francis Beaudet committed
2576
    }
2577 2578 2579 2580

    if (iItem <= infoPtr->iSelected)
      infoPtr->iSelected++;

2581
    Free (oldItems);
Francis Beaudet's avatar
Francis Beaudet committed
2582
  }
2583

2584
  item = TAB_GetItem(infoPtr, iItem);
2585

2586
  item->pszText = NULL;
2587

2588
  if (pti->mask & TCIF_TEXT)
2589 2590
  {
    if (bUnicode)
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
2591
      Str_SetPtrW (&item->pszText, pti->pszText);
2592
    else
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
2593
      Str_SetPtrAtoW (&item->pszText, (LPSTR)pti->pszText);
2594
  }
2595

Francis Beaudet's avatar
Francis Beaudet committed
2596
  if (pti->mask & TCIF_IMAGE)
2597 2598 2599
    item->iImage = pti->iImage;
  else
    item->iImage = -1;
2600

Francis Beaudet's avatar
Francis Beaudet committed
2601
  if (pti->mask & TCIF_PARAM)
2602
    memcpy(item->extra, &pti->lParam, infoPtr->cbInfo);
2603
  else
2604 2605
    memset(item->extra, 0, infoPtr->cbInfo);
  
2606
  TAB_SetItemBounds(infoPtr);
2607
  if (infoPtr->uNumItem > 1)
2608
    TAB_InvalidateTabArea(infoPtr);
2609
  else
2610
    InvalidateRect(infoPtr->hwnd, NULL, TRUE);
2611

2612
  TRACE("[%p]: added item %d %s\n",
2613
        infoPtr->hwnd, iItem, debugstr_w(item->pszText));
Alexandre Julliard's avatar
Alexandre Julliard committed
2614

2615 2616 2617 2618
  /* If we haven't set the current focus yet, set it now. */
  if (infoPtr->uFocus == -1)
    TAB_SetCurFocus(infoPtr, iItem);

Francis Beaudet's avatar
Francis Beaudet committed
2619
  return iItem;
Alexandre Julliard's avatar
Alexandre Julliard committed
2620 2621
}

2622
static LRESULT
2623
TAB_SetItemSize (TAB_INFO *infoPtr, LPARAM lParam)
2624
{
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
2625
  LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
2626
  LONG lResult = 0;
2627
  BOOL bNeedPaint = FALSE;
2628

2629 2630 2631
  lResult = MAKELONG(infoPtr->tabWidth, infoPtr->tabHeight);

  /* UNDOCUMENTED: If requested Width or Height is 0 this means that program wants to use auto size. */
2632
  if (lStyle & TCS_FIXEDWIDTH && (infoPtr->tabWidth != (INT)LOWORD(lParam)))
2633
  {
2634
    infoPtr->tabWidth = (INT)LOWORD(lParam);
2635
    bNeedPaint = TRUE;
2636 2637
  }

2638 2639 2640 2641 2642 2643 2644 2645 2646 2647 2648 2649
  if (infoPtr->tabHeight != (INT)HIWORD(lParam))
  {
    if ((infoPtr->fHeightSet = ((INT)HIWORD(lParam) != 0)))
      infoPtr->tabHeight = (INT)HIWORD(lParam);

    bNeedPaint = TRUE;
  }
  TRACE("was h=%d,w=%d, now h=%d,w=%d\n",
       HIWORD(lResult), LOWORD(lResult),
       infoPtr->tabHeight, infoPtr->tabWidth);

  if (bNeedPaint)
2650
  {
2651 2652
    TAB_SetItemBounds(infoPtr);
    RedrawWindow(infoPtr->hwnd, NULL, NULL, RDW_ERASE | RDW_INVALIDATE | RDW_UPDATENOW);
2653
  }
2654

2655 2656 2657
  return lResult;
}

2658
static inline LRESULT TAB_SetMinTabWidth (TAB_INFO *infoPtr, INT cx)
2659
{
2660 2661 2662
  INT oldcx = 0;

  TRACE("(%p,%d)\n", infoPtr, cx);
2663

2664 2665
  oldcx = infoPtr->tabMinWidth;
  infoPtr->tabMinWidth = cx;
2666
  TAB_SetItemBounds(infoPtr);
2667 2668 2669
  return oldcx;
}

2670 2671
static inline LRESULT 
TAB_HighlightItem (TAB_INFO *infoPtr, INT iItem, BOOL fHighlight)
2672
{
2673
  LPDWORD lpState;
2674

2675 2676 2677
  TRACE("(%p,%d,%s)\n", infoPtr, iItem, fHighlight ? "true" : "false");

  if (!infoPtr || iItem < 0 || iItem >= infoPtr->uNumItem)
2678
    return FALSE;
2679 2680 2681 2682 2683 2684 2685
  
  lpState = &TAB_GetItem(infoPtr, iItem)->dwState;

  if (fHighlight)
    *lpState |= TCIS_HIGHLIGHTED;
  else
    *lpState &= ~TCIS_HIGHLIGHTED;
2686 2687 2688 2689

  return TRUE;
}

2690
static LRESULT
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
2691
TAB_SetItemT (TAB_INFO *infoPtr, INT iItem, LPTCITEMW tabItem, BOOL bUnicode)
2692
{
2693
  TAB_ITEM *wineItem;
2694

2695
  TRACE("(%p,%d,%p,%s)\n", infoPtr, iItem, tabItem, bUnicode ? "true" : "false");
2696

2697 2698
  if (iItem < 0 || iItem >= infoPtr->uNumItem)
    return FALSE;
2699

Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
2700
  TAB_DumpItemExternalT(tabItem, iItem, bUnicode);
2701

2702
  wineItem = TAB_GetItem(infoPtr, iItem);
2703

2704
  if (tabItem->mask & TCIF_IMAGE)
2705
    wineItem->iImage = tabItem->iImage;
2706

2707
  if (tabItem->mask & TCIF_PARAM)
2708
    memcpy(wineItem->extra, &tabItem->lParam, infoPtr->cbInfo);
2709

2710
  if (tabItem->mask & TCIF_RTLREADING)
2711
    FIXME("TCIF_RTLREADING\n");
2712

2713
  if (tabItem->mask & TCIF_STATE)
2714
    wineItem->dwState = tabItem->dwState;
2715

2716
  if (tabItem->mask & TCIF_TEXT)
2717
  {
2718 2719
    Free(wineItem->pszText);
    wineItem->pszText = NULL;
2720
    if (bUnicode)
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
2721
      Str_SetPtrW(&wineItem->pszText, tabItem->pszText);
2722
    else
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
2723
      Str_SetPtrAtoW(&wineItem->pszText, (LPSTR)tabItem->pszText);
2724 2725
  }

2726
  /* Update and repaint tabs */
2727 2728
  TAB_SetItemBounds(infoPtr);
  TAB_InvalidateTabArea(infoPtr);
2729

Francis Beaudet's avatar
Francis Beaudet committed
2730
  return TRUE;
2731 2732
}

2733
static inline LRESULT TAB_GetItemCount (const TAB_INFO *infoPtr)
2734 2735 2736 2737 2738
{
   return infoPtr->uNumItem;
}


2739
static LRESULT
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
2740
TAB_GetItemT (TAB_INFO *infoPtr, INT iItem, LPTCITEMW tabItem, BOOL bUnicode)
2741
{
2742
  TAB_ITEM *wineItem;
2743

2744 2745 2746
  TRACE("(%p,%d,%p,%s)\n", infoPtr, iItem, tabItem, bUnicode ? "true" : "false");

  if (iItem < 0 || iItem >= infoPtr->uNumItem)
2747
    return FALSE;
2748

2749
  wineItem = TAB_GetItem(infoPtr, iItem);
2750 2751 2752 2753 2754

  if (tabItem->mask & TCIF_IMAGE)
    tabItem->iImage = wineItem->iImage;

  if (tabItem->mask & TCIF_PARAM)
2755
    memcpy(&tabItem->lParam, wineItem->extra, infoPtr->cbInfo);
2756 2757 2758 2759 2760 2761 2762 2763

  if (tabItem->mask & TCIF_RTLREADING)
    FIXME("TCIF_RTLREADING\n");

  if (tabItem->mask & TCIF_STATE)
    tabItem->dwState = wineItem->dwState;

  if (tabItem->mask & TCIF_TEXT)
2764 2765
  {
    if (bUnicode)
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
2766
      Str_GetPtrW (wineItem->pszText, tabItem->pszText, tabItem->cchTextMax);
2767
    else
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
2768
      Str_GetPtrWtoA (wineItem->pszText, (LPSTR)tabItem->pszText, tabItem->cchTextMax);
2769
  }
2770

Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
2771
  TAB_DumpItemExternalT(tabItem, iItem, bUnicode);
2772

2773 2774 2775 2776
  return TRUE;
}


2777
static LRESULT TAB_DeleteItem (TAB_INFO *infoPtr, INT iItem)
2778
{
2779
    BOOL bResult = FALSE;
2780

2781 2782
    TRACE("(%p, %d)\n", infoPtr, iItem);

2783 2784
    if ((iItem >= 0) && (iItem < infoPtr->uNumItem))
    {
2785 2786 2787
        TAB_ITEM *item = TAB_GetItem(infoPtr, iItem);
        LPBYTE oldItems = (LPBYTE)infoPtr->items;

2788 2789
	TAB_InvalidateTabArea(infoPtr);
        Free(item->pszText);
2790
	infoPtr->uNumItem--;
2791

2792 2793 2794 2795 2796
	if (!infoPtr->uNumItem)
        {
            infoPtr->items = NULL;
            if (infoPtr->iHotTracked >= 0)
            {
2797
                KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
2798 2799 2800 2801 2802 2803
                infoPtr->iHotTracked = -1;
            }
        }
        else
	{
	    infoPtr->items = Alloc(TAB_ITEM_SIZE(infoPtr) * infoPtr->uNumItem);
2804

2805 2806
	    if (iItem > 0)
	        memcpy(infoPtr->items, oldItems, iItem * TAB_ITEM_SIZE(infoPtr));
2807

2808 2809 2810 2811 2812 2813 2814 2815
	    if (iItem < infoPtr->uNumItem)
	        memcpy(TAB_GetItem(infoPtr, iItem),
                       oldItems + (iItem + 1) * TAB_ITEM_SIZE(infoPtr),
		       (infoPtr->uNumItem - iItem) * TAB_ITEM_SIZE(infoPtr));

            if (iItem <= infoPtr->iHotTracked)
            {
                /* When tabs move left/up, the hot track item may change */
2816
                FIXME("Recalc hot track\n");
2817 2818
            }
	}
2819
	Free(oldItems);
2820

2821 2822 2823
	/* Readjust the selected index */
	if ((iItem == infoPtr->iSelected) && (iItem > 0))
	    infoPtr->iSelected--;
2824

2825 2826
	if (iItem < infoPtr->iSelected)
	    infoPtr->iSelected--;
2827

2828 2829
	if (infoPtr->uNumItem == 0)
	    infoPtr->iSelected = -1;
2830

2831
	/* Reposition and repaint tabs */
2832
	TAB_SetItemBounds(infoPtr);
2833

2834 2835
	bResult = TRUE;
    }
2836

2837
    return bResult;
2838
}
Alex Priem's avatar
Alex Priem committed
2839

2840
static inline LRESULT TAB_DeleteAllItems (TAB_INFO *infoPtr)
2841
{
2842
    TRACE("(%p)\n", infoPtr);
2843
    while (infoPtr->uNumItem)
2844
      TAB_DeleteItem (infoPtr, 0);
2845
    return TRUE;
2846 2847 2848
}


2849
static inline LRESULT TAB_GetFont (const TAB_INFO *infoPtr)
2850
{
2851
  TRACE("(%p) returning %p\n", infoPtr, infoPtr->hFont);
2852 2853 2854
  return (LRESULT)infoPtr->hFont;
}

2855
static inline LRESULT TAB_SetFont (TAB_INFO *infoPtr, HFONT hNewFont)
2856
{
2857
  TRACE("(%p,%p)\n", infoPtr, hNewFont);
2858

2859
  infoPtr->hFont = hNewFont;
2860

2861
  TAB_SetItemBounds(infoPtr);
2862

2863
  TAB_InvalidateTabArea(infoPtr);
2864

Francis Beaudet's avatar
Francis Beaudet committed
2865
  return 0;
2866 2867 2868
}


2869
static inline LRESULT TAB_GetImageList (const TAB_INFO *infoPtr)
2870
{
2871
  TRACE("\n");
2872 2873 2874
  return (LRESULT)infoPtr->himl;
}

2875
static inline LRESULT TAB_SetImageList (TAB_INFO *infoPtr, HIMAGELIST himlNew)
2876
{
2877
    HIMAGELIST himlPrev = infoPtr->himl;
2878
    TRACE("\n");
2879
    infoPtr->himl = himlNew;
2880 2881
    TAB_SetItemBounds(infoPtr);
    InvalidateRect(infoPtr->hwnd, NULL, TRUE);
2882 2883 2884
    return (LRESULT)himlPrev;
}

2885
static inline LRESULT TAB_GetUnicodeFormat (const TAB_INFO *infoPtr)
2886 2887 2888 2889
{
    return infoPtr->bUnicode;
}

2890
static inline LRESULT TAB_SetUnicodeFormat (TAB_INFO *infoPtr, BOOL bUnicode)
2891 2892 2893
{
    BOOL bTemp = infoPtr->bUnicode;

2894
    infoPtr->bUnicode = bUnicode;
2895 2896 2897

    return bTemp;
}
Alex Priem's avatar
Alex Priem committed
2898

2899
static inline LRESULT TAB_Size (TAB_INFO *infoPtr)
Alex Priem's avatar
Alex Priem committed
2900
{
2901 2902 2903 2904 2905
/* I'm not really sure what the following code was meant to do.
   This is what it is doing:
   When WM_SIZE is sent with SIZE_RESTORED, the control
   gets positioned in the top left corner.

2906 2907 2908
  RECT parent_rect;
  HWND parent;
  UINT uPosFlags,cx,cy;
Alex Priem's avatar
Alex Priem committed
2909 2910 2911

  uPosFlags=0;
  if (!wParam) {
Francis Beaudet's avatar
Francis Beaudet committed
2912 2913 2914 2915
    parent = GetParent (hwnd);
    GetClientRect(parent, &parent_rect);
    cx=LOWORD (lParam);
    cy=HIWORD (lParam);
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
2916
    if (GetWindowLongW(hwnd, GWL_STYLE) & CCS_NORESIZE)
Alex Priem's avatar
Alex Priem committed
2917 2918
        uPosFlags |= (SWP_NOSIZE | SWP_NOMOVE);

Francis Beaudet's avatar
Francis Beaudet committed
2919
    SetWindowPos (hwnd, 0, parent_rect.left, parent_rect.top,
Alex Priem's avatar
Alex Priem committed
2920
            cx, cy, uPosFlags | SWP_NOZORDER);
2921
  } else {
Andreas Mohr's avatar
Andreas Mohr committed
2922
    FIXME("WM_SIZE flag %x %lx not handled\n", wParam, lParam);
2923
  } */
Alex Priem's avatar
Alex Priem committed
2924

2925
  /* Recompute the size/position of the tabs. */
2926
  TAB_SetItemBounds (infoPtr);
Francis Beaudet's avatar
Francis Beaudet committed
2927

2928
  /* Force a repaint of the control. */
2929
  InvalidateRect(infoPtr->hwnd, NULL, TRUE);
Alex Priem's avatar
Alex Priem committed
2930 2931 2932 2933 2934

  return 0;
}


2935
static LRESULT TAB_Create (HWND hwnd, WPARAM wParam, LPARAM lParam)
Alexandre Julliard's avatar
Alexandre Julliard committed
2936
{
Francis Beaudet's avatar
Francis Beaudet committed
2937
  TAB_INFO *infoPtr;
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
2938
  TEXTMETRICW fontMetrics;
2939 2940
  HDC hdc;
  HFONT hOldFont;
Alexandre Julliard's avatar
Alexandre Julliard committed
2941
  DWORD dwStyle;
Alexandre Julliard's avatar
Alexandre Julliard committed
2942

2943
  infoPtr = (TAB_INFO *)Alloc (sizeof(TAB_INFO));
Francis Beaudet's avatar
Francis Beaudet committed
2944

Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
2945
  SetWindowLongPtrW(hwnd, 0, (DWORD_PTR)infoPtr);
2946

2947
  infoPtr->hwnd            = hwnd;
2948
  infoPtr->hwndNotify      = ((LPCREATESTRUCTW)lParam)->hwndParent;
Francis Beaudet's avatar
Francis Beaudet committed
2949
  infoPtr->uNumItem        = 0;
2950
  infoPtr->uNumRows        = 0;
2951 2952
  infoPtr->uHItemPadding   = 6;
  infoPtr->uVItemPadding   = 3;
2953 2954
  infoPtr->uHItemPadding_s = 6;
  infoPtr->uVItemPadding_s = 3;
Francis Beaudet's avatar
Francis Beaudet committed
2955 2956
  infoPtr->hFont           = 0;
  infoPtr->items           = 0;
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
2957
  infoPtr->hcurArrow       = LoadCursorW (0, (LPWSTR)IDC_ARROW);
2958
  infoPtr->iSelected       = -1;
2959
  infoPtr->iHotTracked     = -1;
2960
  infoPtr->uFocus          = -1;
Francis Beaudet's avatar
Francis Beaudet committed
2961 2962 2963 2964 2965
  infoPtr->hwndToolTip     = 0;
  infoPtr->DoRedraw        = TRUE;
  infoPtr->needsScrolling  = FALSE;
  infoPtr->hwndUpDown      = 0;
  infoPtr->leftmostVisible = 0;
2966
  infoPtr->fHeightSet      = FALSE;
2967 2968
  infoPtr->bUnicode        = IsWindowUnicode (hwnd);
  infoPtr->cbInfo          = sizeof(LPARAM);
Alexandre Julliard's avatar
Alexandre Julliard committed
2969

2970
  TRACE("Created tab control, hwnd [%p]\n", hwnd);
2971 2972 2973

  /* The tab control always has the WS_CLIPSIBLINGS style. Even
     if you don't specify it in CreateWindow. This is necessary in
Alexandre Julliard's avatar
Alexandre Julliard committed
2974
     order for paint to work correctly. This follows windows behaviour. */
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
2975 2976
  dwStyle = GetWindowLongW(hwnd, GWL_STYLE);
  SetWindowLongW(hwnd, GWL_STYLE, dwStyle|WS_CLIPSIBLINGS);
Alexandre Julliard's avatar
Alexandre Julliard committed
2977 2978

  if (dwStyle & TCS_TOOLTIPS) {
Alex Priem's avatar
Alex Priem committed
2979 2980
    /* Create tooltip control */
    infoPtr->hwndToolTip =
2981
      CreateWindowExW (0, TOOLTIPS_CLASSW, NULL, WS_POPUP,
Francis Beaudet's avatar
Francis Beaudet committed
2982 2983 2984
		       CW_USEDEFAULT, CW_USEDEFAULT,
		       CW_USEDEFAULT, CW_USEDEFAULT,
		       hwnd, 0, 0, 0);
2985

Alex Priem's avatar
Alex Priem committed
2986 2987
    /* Send NM_TOOLTIPSCREATED notification */
    if (infoPtr->hwndToolTip) {
Francis Beaudet's avatar
Francis Beaudet committed
2988
      NMTOOLTIPSCREATED nmttc;
2989

Francis Beaudet's avatar
Francis Beaudet committed
2990
      nmttc.hdr.hwndFrom = hwnd;
2991
      nmttc.hdr.idFrom = GetWindowLongPtrW(hwnd, GWLP_ID);
Francis Beaudet's avatar
Francis Beaudet committed
2992 2993
      nmttc.hdr.code = NM_TOOLTIPSCREATED;
      nmttc.hwndToolTips = infoPtr->hwndToolTip;
2994

2995
      SendMessageW (infoPtr->hwndNotify, WM_NOTIFY,
2996
		    (WPARAM)GetWindowLongPtrW(hwnd, GWLP_ID), (LPARAM)&nmttc);
Alex Priem's avatar
Alex Priem committed
2997
    }
2998 2999
  }

3000 3001
  OpenThemeData (infoPtr->hwnd, themeClass);
  
3002 3003 3004 3005
  /*
   * We need to get text information so we need a DC and we need to select
   * a font.
   */
3006
  hdc = GetDC(hwnd);
3007 3008
  hOldFont = SelectObject (hdc, GetStockObject (SYSTEM_FONT));

3009
  /* Use the system font to determine the initial height of a tab. */
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
3010
  GetTextMetricsW(hdc, &fontMetrics);
3011 3012

  /*
3013 3014
   * Make sure there is enough space for the letters + growing the
   * selected item + extra space for the selected item.
3015
   */
3016
  infoPtr->tabHeight = fontMetrics.tmHeight + SELECTED_TAB_OFFSET +
3017
	               ((dwStyle & TCS_BUTTONS) ? 2 : 1) *
3018
                        infoPtr->uVItemPadding;
3019

3020
  /* Initialize the width of a tab. */
3021 3022 3023 3024
  if (dwStyle & TCS_FIXEDWIDTH)
    infoPtr->tabWidth = DEFAULT_TAB_WIDTH_FIXED;

  infoPtr->tabMinWidth = -1;
3025

3026 3027
  TRACE("tabH=%d, tabW=%d\n", infoPtr->tabHeight, infoPtr->tabWidth);

3028 3029 3030
  SelectObject (hdc, hOldFont);
  ReleaseDC(hwnd, hdc);

Francis Beaudet's avatar
Francis Beaudet committed
3031
  return 0;
Alexandre Julliard's avatar
Alexandre Julliard committed
3032 3033 3034
}

static LRESULT
3035
TAB_Destroy (TAB_INFO *infoPtr)
Alexandre Julliard's avatar
Alexandre Julliard committed
3036
{
3037
  UINT iItem;
Alexandre Julliard's avatar
Alexandre Julliard committed
3038

Alexandre Julliard's avatar
Alexandre Julliard committed
3039 3040 3041
  if (!infoPtr)
      return 0;

Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
3042 3043
  SetWindowLongPtrW(infoPtr->hwnd, 0, 0);

Francis Beaudet's avatar
Francis Beaudet committed
3044 3045
  if (infoPtr->items) {
    for (iItem = 0; iItem < infoPtr->uNumItem; iItem++) {
3046
      Free (TAB_GetItem(infoPtr, iItem)->pszText);
Francis Beaudet's avatar
Francis Beaudet committed
3047
    }
3048
    Free (infoPtr->items);
Francis Beaudet's avatar
Francis Beaudet committed
3049
  }
3050 3051

  if (infoPtr->hwndToolTip)
Francis Beaudet's avatar
Francis Beaudet committed
3052
    DestroyWindow (infoPtr->hwndToolTip);
3053

Francis Beaudet's avatar
Francis Beaudet committed
3054 3055
  if (infoPtr->hwndUpDown)
    DestroyWindow(infoPtr->hwndUpDown);
Alex Priem's avatar
Alex Priem committed
3056

3057
  if (infoPtr->iHotTracked >= 0)
3058
    KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
3059

3060 3061
  CloseThemeData (GetWindowTheme (infoPtr->hwnd));
  
3062
  Free (infoPtr);
Francis Beaudet's avatar
Francis Beaudet committed
3063
  return 0;
Alexandre Julliard's avatar
Alexandre Julliard committed
3064 3065
}

3066
/* update theme after a WM_THEMECHANGED message */
3067
static LRESULT theme_changed(const TAB_INFO *infoPtr)
3068 3069 3070 3071 3072 3073 3074
{
    HTHEME theme = GetWindowTheme (infoPtr->hwnd);
    CloseThemeData (theme);
    OpenThemeData (infoPtr->hwnd, themeClass);
    return 0;
}

3075 3076 3077 3078 3079 3080 3081
static LRESULT TAB_NCCalcSize(HWND hwnd, WPARAM wParam, LPARAM lParam)
{
  if (!wParam)
    return 0;
  return WVR_ALIGNTOP;
}

3082 3083
static inline LRESULT
TAB_SetItemExtra (TAB_INFO *infoPtr, INT cbInfo)
3084
{
3085 3086 3087 3088 3089 3090 3091 3092 3093 3094 3095 3096 3097
  if (!infoPtr || cbInfo <= 0)
    return FALSE;

  if (infoPtr->uNumItem)
  {
    /* FIXME: MSDN says this is not allowed, but this hasn't been verified */
    return FALSE;
  }
    
  infoPtr->cbInfo = cbInfo;
  return TRUE;
}

3098
static LRESULT WINAPI
3099
TAB_WindowProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
Alexandre Julliard's avatar
Alexandre Julliard committed
3100
{
3101
    TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
3102

3103
    TRACE("hwnd=%p msg=%x wParam=%lx lParam=%lx\n", hwnd, uMsg, wParam, lParam);
3104
    if (!infoPtr && (uMsg != WM_CREATE))
3105
      return DefWindowProcW (hwnd, uMsg, wParam, lParam);
3106

Alexandre Julliard's avatar
Alexandre Julliard committed
3107 3108
    switch (uMsg)
    {
Francis Beaudet's avatar
Francis Beaudet committed
3109
    case TCM_GETIMAGELIST:
3110
      return TAB_GetImageList (infoPtr);
3111

Francis Beaudet's avatar
Francis Beaudet committed
3112
    case TCM_SETIMAGELIST:
3113
      return TAB_SetImageList (infoPtr, (HIMAGELIST)lParam);
3114

Francis Beaudet's avatar
Francis Beaudet committed
3115
    case TCM_GETITEMCOUNT:
3116
      return TAB_GetItemCount (infoPtr);
3117

Francis Beaudet's avatar
Francis Beaudet committed
3118 3119
    case TCM_GETITEMA:
    case TCM_GETITEMW:
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
3120
      return TAB_GetItemT (infoPtr, (INT)wParam, (LPTCITEMW)lParam, uMsg == TCM_GETITEMW);
3121

Francis Beaudet's avatar
Francis Beaudet committed
3122 3123
    case TCM_SETITEMA:
    case TCM_SETITEMW:
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
3124
      return TAB_SetItemT (infoPtr, (INT)wParam, (LPTCITEMW)lParam, uMsg == TCM_SETITEMW);
3125

Francis Beaudet's avatar
Francis Beaudet committed
3126
    case TCM_DELETEITEM:
3127
      return TAB_DeleteItem (infoPtr, (INT)wParam);
3128

Francis Beaudet's avatar
Francis Beaudet committed
3129
    case TCM_DELETEALLITEMS:
3130
     return TAB_DeleteAllItems (infoPtr);
3131

Francis Beaudet's avatar
Francis Beaudet committed
3132
    case TCM_GETITEMRECT:
3133
     return TAB_GetItemRect (infoPtr, wParam, lParam);
3134

Francis Beaudet's avatar
Francis Beaudet committed
3135
    case TCM_GETCURSEL:
3136
      return TAB_GetCurSel (infoPtr);
3137

Francis Beaudet's avatar
Francis Beaudet committed
3138
    case TCM_HITTEST:
3139
      return TAB_HitTest (infoPtr, (LPTCHITTESTINFO)lParam);
3140

Francis Beaudet's avatar
Francis Beaudet committed
3141
    case TCM_SETCURSEL:
3142
      return TAB_SetCurSel (infoPtr, (INT)wParam);
3143

Francis Beaudet's avatar
Francis Beaudet committed
3144 3145
    case TCM_INSERTITEMA:
    case TCM_INSERTITEMW:
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
3146
      return TAB_InsertItemT (infoPtr, wParam, lParam, uMsg == TCM_INSERTITEMW);
3147

Francis Beaudet's avatar
Francis Beaudet committed
3148
    case TCM_SETITEMEXTRA:
3149
      return TAB_SetItemExtra (infoPtr, (int)wParam);
3150

Francis Beaudet's avatar
Francis Beaudet committed
3151
    case TCM_ADJUSTRECT:
3152
      return TAB_AdjustRect (infoPtr, (BOOL)wParam, (LPRECT)lParam);
3153

Francis Beaudet's avatar
Francis Beaudet committed
3154
    case TCM_SETITEMSIZE:
3155
      return TAB_SetItemSize (infoPtr, lParam);
3156

Francis Beaudet's avatar
Francis Beaudet committed
3157
    case TCM_REMOVEIMAGE:
3158
      FIXME("Unimplemented msg TCM_REMOVEIMAGE\n");
Francis Beaudet's avatar
Francis Beaudet committed
3159
      return 0;
3160

Francis Beaudet's avatar
Francis Beaudet committed
3161
    case TCM_SETPADDING:
3162
      return TAB_SetPadding (infoPtr, lParam);
3163

Francis Beaudet's avatar
Francis Beaudet committed
3164
    case TCM_GETROWCOUNT:
3165
      return TAB_GetRowCount(infoPtr);
3166 3167

    case TCM_GETUNICODEFORMAT:
3168
      return TAB_GetUnicodeFormat (infoPtr);
3169 3170

    case TCM_SETUNICODEFORMAT:
3171
      return TAB_SetUnicodeFormat (infoPtr, (BOOL)wParam);
3172 3173

    case TCM_HIGHLIGHTITEM:
3174
      return TAB_HighlightItem (infoPtr, (INT)wParam, (BOOL)LOWORD(lParam));
3175

Francis Beaudet's avatar
Francis Beaudet committed
3176
    case TCM_GETTOOLTIPS:
3177
      return TAB_GetToolTips (infoPtr);
3178

Francis Beaudet's avatar
Francis Beaudet committed
3179
    case TCM_SETTOOLTIPS:
3180
      return TAB_SetToolTips (infoPtr, (HWND)wParam);
3181

Francis Beaudet's avatar
Francis Beaudet committed
3182
    case TCM_GETCURFOCUS:
3183
      return TAB_GetCurFocus (infoPtr);
3184

Francis Beaudet's avatar
Francis Beaudet committed
3185
    case TCM_SETCURFOCUS:
3186
      return TAB_SetCurFocus (infoPtr, (INT)wParam);
3187

Alexandre Julliard's avatar
Alexandre Julliard committed
3188
    case TCM_SETMINTABWIDTH:
3189
      return TAB_SetMinTabWidth(infoPtr, (INT)lParam);
3190

Francis Beaudet's avatar
Francis Beaudet committed
3191
    case TCM_DESELECTALL:
3192
      FIXME("Unimplemented msg TCM_DESELECTALL\n");
Francis Beaudet's avatar
Francis Beaudet committed
3193
      return 0;
3194

3195
    case TCM_GETEXTENDEDSTYLE:
3196
      FIXME("Unimplemented msg TCM_GETEXTENDEDSTYLE\n");
3197 3198 3199
      return 0;

    case TCM_SETEXTENDEDSTYLE:
3200
      FIXME("Unimplemented msg TCM_SETEXTENDEDSTYLE\n");
3201 3202
      return 0;

Francis Beaudet's avatar
Francis Beaudet committed
3203
    case WM_GETFONT:
3204
      return TAB_GetFont (infoPtr);
3205

Francis Beaudet's avatar
Francis Beaudet committed
3206
    case WM_SETFONT:
3207
      return TAB_SetFont (infoPtr, (HFONT)wParam);
3208

Francis Beaudet's avatar
Francis Beaudet committed
3209 3210
    case WM_CREATE:
      return TAB_Create (hwnd, wParam, lParam);
3211

Francis Beaudet's avatar
Francis Beaudet committed
3212
    case WM_NCDESTROY:
3213
      return TAB_Destroy (infoPtr);
3214

3215
    case WM_GETDLGCODE:
Francis Beaudet's avatar
Francis Beaudet committed
3216
      return DLGC_WANTARROWS | DLGC_WANTCHARS;
3217

Francis Beaudet's avatar
Francis Beaudet committed
3218
    case WM_LBUTTONDOWN:
3219
      return TAB_LButtonDown (infoPtr, wParam, lParam);
3220

Francis Beaudet's avatar
Francis Beaudet committed
3221
    case WM_LBUTTONUP:
3222
      return TAB_LButtonUp (infoPtr);
3223

3224
    case WM_NOTIFY:
3225
      return SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, wParam, lParam);
3226

Francis Beaudet's avatar
Francis Beaudet committed
3227
    case WM_RBUTTONDOWN:
3228
      return TAB_RButtonDown (infoPtr);
3229

Francis Beaudet's avatar
Francis Beaudet committed
3230
    case WM_MOUSEMOVE:
3231
      return TAB_MouseMove (infoPtr, wParam, lParam);
3232

3233
    case WM_PRINTCLIENT:
Francis Beaudet's avatar
Francis Beaudet committed
3234
    case WM_PAINT:
3235
      return TAB_Paint (infoPtr, (HDC)wParam);
Francis Beaudet's avatar
Francis Beaudet committed
3236 3237

    case WM_SIZE:
3238
      return TAB_Size (infoPtr);
3239

Francis Beaudet's avatar
Francis Beaudet committed
3240
    case WM_SETREDRAW:
3241
      return TAB_SetRedraw (infoPtr, (BOOL)wParam);
Francis Beaudet's avatar
Francis Beaudet committed
3242 3243

    case WM_HSCROLL:
3244
      return TAB_OnHScroll(infoPtr, (int)LOWORD(wParam), (int)HIWORD(wParam), (HWND)lParam);
Alexandre Julliard's avatar
Alexandre Julliard committed
3245 3246

    case WM_STYLECHANGED:
3247
      TAB_SetItemBounds (infoPtr);
Alexandre Julliard's avatar
Alexandre Julliard committed
3248 3249
      InvalidateRect(hwnd, NULL, TRUE);
      return 0;
3250 3251 3252 3253

    case WM_SYSCOLORCHANGE:
      COMCTL32_RefreshSysColors();
      return 0;
3254

3255 3256 3257
    case WM_THEMECHANGED:
      return theme_changed (infoPtr);

Francis Beaudet's avatar
Francis Beaudet committed
3258 3259
    case WM_KILLFOCUS:
    case WM_SETFOCUS:
3260 3261
      TAB_FocusChanging(infoPtr);
      break;   /* Don't disturb normal focus behavior */
Francis Beaudet's avatar
Francis Beaudet committed
3262 3263

    case WM_KEYUP:
3264
      return TAB_KeyUp(infoPtr, wParam);
3265
    case WM_NCHITTEST:
3266
      return TAB_NCHitTest(infoPtr, lParam);
Francis Beaudet's avatar
Francis Beaudet committed
3267

3268 3269 3270
    case WM_NCCALCSIZE:
      return TAB_NCCalcSize(hwnd, wParam, lParam);

Francis Beaudet's avatar
Francis Beaudet committed
3271
    default:
3272
      if (uMsg >= WM_USER && uMsg < WM_APP && !COMCTL32_IsReflectedMessage(uMsg))
3273
	WARN("unknown msg %04x wp=%08lx lp=%08lx\n",
Francis Beaudet's avatar
Francis Beaudet committed
3274
	     uMsg, wParam, lParam);
3275
      break;
Alexandre Julliard's avatar
Alexandre Julliard committed
3276
    }
3277
    return DefWindowProcW(hwnd, uMsg, wParam, lParam);
Alexandre Julliard's avatar
Alexandre Julliard committed
3278 3279 3280
}


Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
3281
void
3282
TAB_Register (void)
Alexandre Julliard's avatar
Alexandre Julliard committed
3283
{
3284
  WNDCLASSW wndClass;
Francis Beaudet's avatar
Francis Beaudet committed
3285

3286
  ZeroMemory (&wndClass, sizeof(WNDCLASSW));
3287
  wndClass.style         = CS_GLOBALCLASS | CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
3288
  wndClass.lpfnWndProc   = TAB_WindowProc;
Francis Beaudet's avatar
Francis Beaudet committed
3289 3290
  wndClass.cbClsExtra    = 0;
  wndClass.cbWndExtra    = sizeof(TAB_INFO *);
3291 3292 3293
  wndClass.hCursor       = LoadCursorW (0, (LPWSTR)IDC_ARROW);
  wndClass.hbrBackground = (HBRUSH)(COLOR_BTNFACE+1);
  wndClass.lpszClassName = WC_TABCONTROLW;
3294

3295
  RegisterClassW (&wndClass);
Alexandre Julliard's avatar
Alexandre Julliard committed
3296 3297
}

3298

Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
3299
void
3300
TAB_Unregister (void)
3301
{
3302
    UnregisterClassW (WC_TABCONTROLW, NULL);
3303
}