tab.c 97.5 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:
35
 *   TCS_MULTISELECT - implement for VK_SPACE selection
James Hawkins's avatar
James Hawkins committed
36 37 38 39
 *   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
 *  Extended Styles:
 *   TCS_EX_REGISTERDROP
 *
 *  Notifications:
 *   NM_RELEASEDCAPTURE
 *   TCN_FOCUSCHANGE
 *   TCN_GETOBJECT
 *
 *  Macros:
 *   TabCtrl_AdjustRect
 *
Alexandre Julliard's avatar
Alexandre Julliard committed
53 54
 */

55
#include <assert.h>
56
#include <stdarg.h>
57 58
#include <string.h>

59
#include "windef.h"
60
#include "winbase.h"
61 62 63
#include "wingdi.h"
#include "winuser.h"
#include "winnls.h"
Alexandre Julliard's avatar
Alexandre Julliard committed
64
#include "commctrl.h"
65
#include "comctl32.h"
66
#include "uxtheme.h"
67
#include "vssym32.h"
68
#include "wine/debug.h"
69
#include <math.h>
Alexandre Julliard's avatar
Alexandre Julliard committed
70

71
WINE_DEFAULT_DEBUG_CHANNEL(tab);
72 73 74 75

typedef struct
{
  DWORD  dwState;
76
  LPWSTR pszText;
77
  INT    iImage;
78 79 80 81
  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)
                     *
82
                     * additionally the top member holds the row number
83 84
                     * and bottom is unused and should be 0 */
  BYTE   extra[1];  /* Space for caller supplied info, variable size */
85 86
} TAB_ITEM;

87 88 89 90
/* The size of a tab item depends on how much extra data is requested.
   TCM_INSERTITEM always stores at least LPARAM sized data. */
#define EXTRA_ITEM_SIZE(infoPtr) (max((infoPtr)->cbInfo, sizeof(LPARAM)))
#define TAB_ITEM_SIZE(infoPtr) FIELD_OFFSET(TAB_ITEM, extra[EXTRA_ITEM_SIZE(infoPtr)])
91

92 93
typedef struct
{
94
  HWND       hwnd;            /* Tab control window */
95
  HWND       hwndNotify;      /* notification window (parent) */
96 97 98 99
  UINT       uNumItem;        /* number of tab items */
  UINT       uNumRows;	      /* number of tab rows */
  INT        tabHeight;       /* height of the tab row */
  INT        tabWidth;        /* width of tabs */
100
  INT        tabMinWidth;     /* minimum width of items */
101 102
  USHORT     uHItemPadding;   /* amount of horizontal padding, in pixels */
  USHORT     uVItemPadding;   /* amount of vertical padding, in pixels */
103 104
  USHORT     uHItemPadding_s; /* Set amount of horizontal padding, in pixels */
  USHORT     uVItemPadding_s; /* Set amount of vertical padding, in pixels */
105 106
  HFONT      hFont;           /* handle to the current font */
  HCURSOR    hcurArrow;       /* handle to the current cursor */
107
  HIMAGELIST himl;            /* handle to an image list (may be 0) */
108 109
  HWND       hwndToolTip;     /* handle to tab's tooltip */
  INT        leftmostVisible; /* Used for scrolling, this member contains
110
                               * the index of the first visible item */
111 112 113 114
  INT        iSelected;       /* the currently selected item */
  INT        iHotTracked;     /* the highlighted item under the mouse */
  INT        uFocus;          /* item which has the focus */
  BOOL       DoRedraw;        /* flag for redrawing when tab contents is changed*/
115
  BOOL       needsScrolling;  /* TRUE if the size of the tabs is greater than
116
                               * the size of the control */
117
  BOOL       fHeightSet;      /* was the height of the tabs explicitly set? */
118
  BOOL       bUnicode;        /* Unicode control? */
119
  HWND       hwndUpDown;      /* Updown control used for scrolling */
120
  INT        cbInfo;          /* Number of bytes of caller supplied info per tab */
121 122 123

  DWORD      exStyle;         /* Extended style used, currently:
                                 TCS_EX_FLATSEPARATORS, TCS_EX_REGISTERDROP */
124
  DWORD      dwStyle;         /* the cached window GWL_STYLE */
125 126

  HDPA       items;           /* dynamic array of TAB_ITEM* pointers */
127
} TAB_INFO;
128

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

145
#define TAB_GetInfoPtr(hwnd) ((TAB_INFO *)GetWindowLongPtrW(hwnd,0))
146

147 148
#define GET_DEFAULT_MIN_TAB_WIDTH(infoPtr) (DEFAULT_MIN_TAB_WIDTH - (DEFAULT_PADDING_X - (infoPtr)->uHItemPadding) * 2)

149 150 151 152 153 154
/******************************************************************************
 * Hot-tracking timer constants
 */
#define TAB_HOTTRACK_TIMER            1
#define TAB_HOTTRACK_TIMER_INTERVAL   100   /* milliseconds */

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

157 158 159 160 161 162
static inline TAB_ITEM* TAB_GetItem(const TAB_INFO *infoPtr, INT i)
{
    assert(i >= 0 && i < infoPtr->uNumItem);
    return DPA_GetPtr(infoPtr->items, i);
}

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

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

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

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

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

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

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

202
static void
203
TAB_DumpItemExternalT(const TCITEMW *pti, UINT iItem, BOOL isW)
204 205
{
    if (TRACE_ON(tab)) {
206
	TRACE("external tab %d, mask=0x%08x, dwState=0x%08x, dwStateMask=0x%08x, cchTextMax=0x%08x\n",
207 208
	      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
209
	      iItem, pti->iImage, pti->lParam, isW ? debugstr_w(pti->pszText) : debugstr_a((LPSTR)pti->pszText));
210 211 212 213
    }
}

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

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
    TRACE("(%p)\n", infoPtr);
Alexandre Julliard's avatar
Alexandre Julliard committed
231 232 233
    return infoPtr->iSelected;
}

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

243
static inline LRESULT TAB_GetToolTips (const TAB_INFO *infoPtr)
Alex Priem's avatar
Alex Priem committed
244
{
245
    TRACE("(%p)\n", infoPtr);
246
    return (LRESULT)infoPtr->hwndToolTip;
Alex Priem's avatar
Alex Priem committed
247 248
}

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

253 254
  TRACE("(%p %d)\n", infoPtr, iItem);

255
  if (iItem < 0)
256
      infoPtr->iSelected = -1;
257 258 259
  else if (iItem >= infoPtr->uNumItem)
      return -1;
  else {
260 261 262
      if (prevItem != iItem) {
          if (prevItem != -1)
              TAB_GetItem(infoPtr, prevItem)->dwState &= ~TCIS_BUTTONPRESSED;
263
          TAB_GetItem(infoPtr, iItem)->dwState |= TCIS_BUTTONPRESSED;
264

265 266
          infoPtr->iSelected = iItem;
          infoPtr->uFocus = iItem;
267 268 269
          TAB_EnsureSelectionVisible(infoPtr);
          TAB_InvalidateTabArea(infoPtr);
      }
Francis Beaudet's avatar
Francis Beaudet committed
270 271
  }
  return prevItem;
272 273
}

274
static LRESULT TAB_SetCurFocus (TAB_INFO *infoPtr, INT iItem)
Alex Priem's avatar
Alex Priem committed
275
{
276 277
  TRACE("(%p %d)\n", infoPtr, iItem);

278
  if (iItem < 0) {
279
      infoPtr->uFocus = -1;
280 281 282 283 284 285
      if (infoPtr->iSelected != -1) {
          infoPtr->iSelected = -1;
          TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGE);
          TAB_InvalidateTabArea(infoPtr);
      }
  }
286
  else if (iItem < infoPtr->uNumItem) {
287
    if (infoPtr->dwStyle & TCS_BUTTONS) {
288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304
      /* set focus to new item, leave selection as is */
      if (infoPtr->uFocus != iItem) {
        INT prev_focus = infoPtr->uFocus;
        RECT r;

        infoPtr->uFocus = iItem;

        if (prev_focus != infoPtr->iSelected) {
          if (TAB_InternalGetItemRect(infoPtr, prev_focus, &r, NULL))
            InvalidateRect(infoPtr->hwnd, &r, FALSE);
        }

        if (TAB_InternalGetItemRect(infoPtr, iItem, &r, NULL))
            InvalidateRect(infoPtr->hwnd, &r, FALSE);

        TAB_SendSimpleNotify(infoPtr, TCN_FOCUSCHANGE);
      }
305
    } else {
306
      INT oldFocus = infoPtr->uFocus;
307 308 309 310 311 312 313 314 315 316 317
      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);
318
        }
Francis Beaudet's avatar
Francis Beaudet committed
319 320 321
      }
    }
  }
Alex Priem's avatar
Alex Priem committed
322 323 324
  return 0;
}

325 326
static inline LRESULT
TAB_SetToolTips (TAB_INFO *infoPtr, HWND hwndToolTip)
Alex Priem's avatar
Alex Priem committed
327
{
328
    TRACE("%p %p\n", infoPtr, hwndToolTip);
329
    infoPtr->hwndToolTip = hwndToolTip;
Alex Priem's avatar
Alex Priem committed
330 331 332
    return 0;
}

333 334
static inline LRESULT
TAB_SetPadding (TAB_INFO *infoPtr, LPARAM lParam)
335
{
336 337 338 339
    TRACE("(%p %d %d)\n", infoPtr, LOWORD(lParam), HIWORD(lParam));
    infoPtr->uHItemPadding_s = LOWORD(lParam);
    infoPtr->uVItemPadding_s = HIWORD(lParam);

340 341 342
    return 0;
}

Francis Beaudet's avatar
Francis Beaudet committed
343 344 345 346 347 348 349 350 351 352
/******************************************************************************
 * 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(
353
  const TAB_INFO* infoPtr,
Francis Beaudet's avatar
Francis Beaudet committed
354 355 356 357
  INT         itemIndex,
  RECT*       itemRect,
  RECT*       selectedRect)
{
358
  RECT tmpItemRect,clientRect;
359

360 361
  /* Perform a sanity check and a trivial visibility check. */
  if ( (infoPtr->uNumItem <= 0) ||
Francis Beaudet's avatar
Francis Beaudet committed
362
       (itemIndex >= infoPtr->uNumItem) ||
363 364
       (!(((infoPtr->dwStyle & TCS_MULTILINE) || (infoPtr->dwStyle & TCS_VERTICAL))) &&
         (itemIndex < infoPtr->leftmostVisible)))
365 366 367 368 369 370 371 372 373 374 375 376
    {
        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
377 378 379 380 381

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

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

388
  /* calculate the times bottom and top based on the row */
389
  GetClientRect(infoPtr->hwnd, &clientRect);
390

391
  if ((infoPtr->dwStyle & TCS_BOTTOM) && (infoPtr->dwStyle & TCS_VERTICAL))
392
  {
393
    itemRect->right  = clientRect.right - SELECTED_TAB_OFFSET - itemRect->left * infoPtr->tabHeight -
394
                       ((infoPtr->dwStyle & TCS_BUTTONS) ? itemRect->left * BUTTON_SPACINGX : 0);
395
    itemRect->left   = itemRect->right - infoPtr->tabHeight;
396
  }
397
  else if (infoPtr->dwStyle & TCS_VERTICAL)
398
  {
399
    itemRect->left   = clientRect.left + SELECTED_TAB_OFFSET + itemRect->left * infoPtr->tabHeight +
400
                       ((infoPtr->dwStyle & TCS_BUTTONS) ? itemRect->left * BUTTON_SPACINGX : 0);
401
    itemRect->right  = itemRect->left + infoPtr->tabHeight;
402
  }
403
  else if (infoPtr->dwStyle & TCS_BOTTOM)
404
  {
405
    itemRect->bottom = clientRect.bottom - itemRect->top * infoPtr->tabHeight -
406
                       ((infoPtr->dwStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : SELECTED_TAB_OFFSET);
407
    itemRect->top    = itemRect->bottom - infoPtr->tabHeight;
408
  }
409
  else /* not TCS_BOTTOM and not TCS_VERTICAL */
410
  {
411
    itemRect->top    = clientRect.top + itemRect->top * infoPtr->tabHeight +
412
                       ((infoPtr->dwStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : SELECTED_TAB_OFFSET);
413
    itemRect->bottom = itemRect->top + infoPtr->tabHeight;
414 415
 }

Francis Beaudet's avatar
Francis Beaudet committed
416
  /*
417
   * "scroll" it to make sure the item at the very left of the
Francis Beaudet's avatar
Francis Beaudet committed
418 419
   * tab control is the leftmost visible tab.
   */
420
  if(infoPtr->dwStyle & TCS_VERTICAL)
421 422 423
  {
    OffsetRect(itemRect,
	     0,
424
	     -TAB_GetItem(infoPtr, infoPtr->leftmostVisible)->rect.top);
425 426 427 428 429 430 431

    /*
     * Move the rectangle so the first item is slightly offset from
     * the bottom of the tab control.
     */
    OffsetRect(itemRect,
	     0,
432
	     SELECTED_TAB_OFFSET);
433 434 435 436

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

440 441 442 443 444
    /*
     * 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
445 446
	     SELECTED_TAB_OFFSET,
	     0);
447
  }
448 449
  TRACE("item %d tab h=%d, rect=(%s)\n",
        itemIndex, infoPtr->tabHeight, wine_dbgstr_rect(itemRect));
Francis Beaudet's avatar
Francis Beaudet committed
450

451
  /* Now, calculate the position of the item as if it were selected. */
Francis Beaudet's avatar
Francis Beaudet committed
452 453 454 455
  if (selectedRect!=NULL)
  {
    CopyRect(selectedRect, itemRect);

456
    /* The rectangle of a selected item is a bit wider. */
457
    if(infoPtr->dwStyle & TCS_VERTICAL)
458 459 460
      InflateRect(selectedRect, 0, SELECTED_TAB_OFFSET);
    else
      InflateRect(selectedRect, SELECTED_TAB_OFFSET, 0);
Francis Beaudet's avatar
Francis Beaudet committed
461

462
    /* If it also a bit higher. */
463
    if ((infoPtr->dwStyle & TCS_BOTTOM) && (infoPtr->dwStyle & TCS_VERTICAL))
464
    {
465 466
      selectedRect->left   -= 2; /* the border is thicker on the right */
      selectedRect->right  += SELECTED_TAB_OFFSET;
467
    }
468
    else if (infoPtr->dwStyle & TCS_VERTICAL)
469
    {
470 471
      selectedRect->left   -= SELECTED_TAB_OFFSET;
      selectedRect->right  += 1;
472
    }
473
    else if (infoPtr->dwStyle & TCS_BOTTOM)
474
    {
475
      selectedRect->bottom += SELECTED_TAB_OFFSET;
Francis Beaudet's avatar
Francis Beaudet committed
476
    }
477
    else /* not TCS_BOTTOM and not TCS_VERTICAL */
Francis Beaudet's avatar
Francis Beaudet committed
478
    {
479
      selectedRect->top    -= SELECTED_TAB_OFFSET;
480
      selectedRect->bottom -= 1;
Francis Beaudet's avatar
Francis Beaudet committed
481 482 483
    }
  }

484
  /* Check for visibility */
485
  if (infoPtr->dwStyle & TCS_VERTICAL)
486 487 488
    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
489 490
}

491
static inline BOOL
492
TAB_GetItemRect(const TAB_INFO *infoPtr, INT item, RECT *rect)
493
{
494 495
  TRACE("(%p, %d, %p)\n", infoPtr, item, rect);
  return TAB_InternalGetItemRect(infoPtr, item, rect, NULL);
496 497
}

Francis Beaudet's avatar
Francis Beaudet committed
498
/******************************************************************************
499
 * TAB_KeyDown
Francis Beaudet's avatar
Francis Beaudet committed
500 501 502
 *
 * This method is called to handle keyboard input
 */
503
static LRESULT TAB_KeyDown(TAB_INFO* infoPtr, WPARAM keyCode, LPARAM lParam)
Francis Beaudet's avatar
Francis Beaudet committed
504
{
505
  INT newItem = -1;
506 507 508 509 510 511 512 513 514
  NMTCKEYDOWN nm;

  /* TCN_KEYDOWN notification sent always */
  nm.hdr.hwndFrom = infoPtr->hwnd;
  nm.hdr.idFrom = GetWindowLongPtrW(infoPtr->hwnd, GWLP_ID);
  nm.hdr.code = TCN_KEYDOWN;
  nm.wVKey = keyCode;
  nm.flags = lParam;
  SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, nm.hdr.idFrom, (LPARAM)&nm);
Francis Beaudet's avatar
Francis Beaudet committed
515 516 517 518

  switch (keyCode)
  {
    case VK_LEFT:
519
      newItem = infoPtr->uFocus - 1;
Francis Beaudet's avatar
Francis Beaudet committed
520 521
      break;
    case VK_RIGHT:
522
      newItem = infoPtr->uFocus + 1;
Francis Beaudet's avatar
Francis Beaudet committed
523 524
      break;
  }
525

526 527 528
  /* If we changed to a valid item, change focused item */
  if (newItem >= 0 && newItem < infoPtr->uNumItem && infoPtr->uFocus != newItem)
      TAB_SetCurFocus(infoPtr, newItem);
Francis Beaudet's avatar
Francis Beaudet committed
529 530 531 532

  return 0;
}

533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549
/*
 * WM_KILLFOCUS handler
 */
static void TAB_KillFocus(TAB_INFO *infoPtr)
{
  /* clear current focused item back to selected for TCS_BUTTONS */
  if ((infoPtr->dwStyle & TCS_BUTTONS) && (infoPtr->uFocus != infoPtr->iSelected))
  {
    RECT r;

    if (TAB_InternalGetItemRect(infoPtr, infoPtr->uFocus, &r, NULL))
      InvalidateRect(infoPtr->hwnd, &r, FALSE);

    infoPtr->uFocus = infoPtr->iSelected;
  }
}

Francis Beaudet's avatar
Francis Beaudet committed
550 551 552 553 554 555
/******************************************************************************
 * 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
556
static void TAB_FocusChanging(const TAB_INFO *infoPtr)
Francis Beaudet's avatar
Francis Beaudet committed
557 558 559 560 561 562 563
{
  RECT      selectedRect;
  BOOL      isVisible;

  /*
   * Get the rectangle for the item.
   */
564
  isVisible = TAB_InternalGetItemRect(infoPtr,
Francis Beaudet's avatar
Francis Beaudet committed
565 566 567
				      infoPtr->uFocus,
				      NULL,
				      &selectedRect);
568

Francis Beaudet's avatar
Francis Beaudet committed
569 570 571 572 573 574
  /*
   * If the rectangle is not completely invisible, invalidate that
   * portion of the window.
   */
  if (isVisible)
  {
575
    TRACE("invalidate (%s)\n", wine_dbgstr_rect(&selectedRect));
576
    InvalidateRect(infoPtr->hwnd, &selectedRect, TRUE);
Francis Beaudet's avatar
Francis Beaudet committed
577 578
  }
}
Alex Priem's avatar
Alex Priem committed
579

580
static INT TAB_InternalHitTest (const TAB_INFO *infoPtr, POINT pt, UINT *flags)
Alex Priem's avatar
Alex Priem committed
581
{
582
  RECT rect;
583
  INT iCount;
584

585
  for (iCount = 0; iCount < infoPtr->uNumItem; iCount++)
Francis Beaudet's avatar
Francis Beaudet committed
586
  {
587
    TAB_InternalGetItemRect(infoPtr, iCount, &rect, NULL);
Francis Beaudet's avatar
Francis Beaudet committed
588

589
    if (PtInRect(&rect, pt))
Francis Beaudet's avatar
Francis Beaudet committed
590 591 592 593 594 595
    {
      *flags = TCHT_ONITEM;
      return iCount;
    }
  }

596
  *flags = TCHT_NOWHERE;
Alex Priem's avatar
Alex Priem committed
597 598 599
  return -1;
}

600
static inline LRESULT
601
TAB_HitTest (const TAB_INFO *infoPtr, LPTCHITTESTINFO lptest)
Alex Priem's avatar
Alex Priem committed
602
{
603
  TRACE("(%p, %p)\n", infoPtr, lptest);
604
  return TAB_InternalHitTest (infoPtr, lptest->pt, &lptest->flags);
Alex Priem's avatar
Alex Priem committed
605 606
}

607 608 609 610 611 612 613 614 615 616 617 618
/******************************************************************************
 * 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 ?
 */
619
static inline LRESULT
620
TAB_NCHitTest (const TAB_INFO *infoPtr, LPARAM lParam)
621 622 623 624
{
  POINT pt;
  UINT dummyflag;

625 626
  pt.x = (short)LOWORD(lParam);
  pt.y = (short)HIWORD(lParam);
627
  ScreenToClient(infoPtr->hwnd, &pt);
628

629
  if (TAB_InternalHitTest(infoPtr, pt, &dummyflag) == -1)
630 631 632 633
    return HTTRANSPARENT;
  else
    return HTCLIENT;
}
Alex Priem's avatar
Alex Priem committed
634 635

static LRESULT
636
TAB_LButtonDown (TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
Alex Priem's avatar
Alex Priem committed
637
{
638
  POINT pt;
Mike McCormack's avatar
Mike McCormack committed
639 640
  INT newItem;
  UINT dummy;
Alex Priem's avatar
Alex Priem committed
641

Francis Beaudet's avatar
Francis Beaudet committed
642
  if (infoPtr->hwndToolTip)
643
    TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
Francis Beaudet's avatar
Francis Beaudet committed
644
		    WM_LBUTTONDOWN, wParam, lParam);
Alex Priem's avatar
Alex Priem committed
645

646
  if (!(infoPtr->dwStyle & TCS_FOCUSNEVER)) {
647
    SetFocus (infoPtr->hwnd);
Francis Beaudet's avatar
Francis Beaudet committed
648
  }
Alex Priem's avatar
Alex Priem committed
649

Francis Beaudet's avatar
Francis Beaudet committed
650
  if (infoPtr->hwndToolTip)
651
    TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
Francis Beaudet's avatar
Francis Beaudet committed
652
		    WM_LBUTTONDOWN, wParam, lParam);
653

654 655
  pt.x = (short)LOWORD(lParam);
  pt.y = (short)HIWORD(lParam);
656

657
  newItem = TAB_InternalHitTest (infoPtr, pt, &dummy);
658

659
  TRACE("On Tab, item %d\n", newItem);
660

661
  if ((newItem != -1) && (infoPtr->iSelected != newItem))
Francis Beaudet's avatar
Francis Beaudet committed
662
  {
663
    if ((infoPtr->dwStyle & TCS_BUTTONS) && (infoPtr->dwStyle & TCS_MULTISELECT) &&
664
        (wParam & MK_CONTROL))
Francis Beaudet's avatar
Francis Beaudet committed
665
    {
666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691
      RECT r;

      /* toggle multiselection */
      TAB_GetItem(infoPtr, newItem)->dwState ^= TCIS_BUTTONPRESSED;
      if (TAB_InternalGetItemRect (infoPtr, newItem, &r, NULL))
        InvalidateRect (infoPtr->hwnd, &r, TRUE);
    }
    else
    {
      INT i;
      BOOL pressed = FALSE;

      /* any button pressed ? */
      for (i = 0; i < infoPtr->uNumItem; i++)
        if ((TAB_GetItem (infoPtr, i)->dwState & TCIS_BUTTONPRESSED) &&
            (infoPtr->iSelected != i))
        {
          pressed = TRUE;
          break;
        }

      TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGING);

      if (pressed)
        TAB_DeselectAll (infoPtr, FALSE);
      else
692
        TAB_SetCurSel(infoPtr, newItem);
693

694
      TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGE);
Francis Beaudet's avatar
Francis Beaudet committed
695 696
    }
  }
697

698 699 700
  return 0;
}

701 702
static inline LRESULT
TAB_LButtonUp (const TAB_INFO *infoPtr)
703
{
704
  TAB_SendSimpleNotify(infoPtr, NM_CLICK);
Francis Beaudet's avatar
Francis Beaudet committed
705 706

  return 0;
Alexandre Julliard's avatar
Alexandre Julliard committed
707 708
}

709
static inline void
710
TAB_RButtonUp (const TAB_INFO *infoPtr)
Alex Priem's avatar
Alex Priem committed
711
{
712
  TAB_SendSimpleNotify(infoPtr, NM_RCLICK);
Alex Priem's avatar
Alex Priem committed
713 714
}

715 716 717 718 719 720 721 722 723
/******************************************************************************
 * 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
724
TAB_DrawLoneItemInterior(const TAB_INFO* infoPtr, int iItem)
725
{
726
  HDC hdc = GetDC(infoPtr->hwnd);
727 728
  RECT r, rC;

729 730 731
  /* Clip UpDown control to not draw over it */
  if (infoPtr->needsScrolling)
  {
732
    GetWindowRect(infoPtr->hwnd, &rC);
733 734 735
    GetWindowRect(infoPtr->hwndUpDown, &r);
    ExcludeClipRect(hdc, r.left - rC.left, r.top - rC.top, r.right - rC.left, r.bottom - rC.top);
  }
736 737
  TAB_DrawItemInterior(infoPtr, hdc, iItem, NULL);
  ReleaseDC(infoPtr->hwnd, hdc);
738 739
}

740 741
/* update a tab after hottracking - invalidate it or just redraw the interior,
 * based on whether theming is used or not */
742
static inline void hottrack_refresh(const TAB_INFO *infoPtr, int tabIndex)
743 744 745 746 747 748 749 750 751 752 753 754 755
{
    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);
}

756 757 758 759 760 761 762 763 764 765 766
/******************************************************************************
 * 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
767
static void CALLBACK
768 769
TAB_HotTrackTimerProc
  (
770 771
  HWND hwnd,    /* handle of window for timer messages */
  UINT uMsg,    /* WM_TIMER message */
Frank Richter's avatar
Frank Richter committed
772
  UINT_PTR idEvent, /* timer identifier */
773
  DWORD dwTime  /* current system time */
774 775 776 777 778 779 780 781 782 783 784 785 786 787
  )
{
  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
788
    ** WM_MOUSEMOVE event.
789 790 791
    */
    if (!GetCursorPos(&pt) || WindowFromPoint(pt) != hwnd)
    {
792
      /* Redraw iHotTracked to look normal */
793 794
      INT iRedraw = infoPtr->iHotTracked;
      infoPtr->iHotTracked = -1;
795
      hottrack_refresh (infoPtr, iRedraw);
796

797
      /* Kill this timer */
798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821
      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
  (
822
  TAB_INFO*       infoPtr,
823 824 825 826 827 828 829 830 831 832 833 834 835
  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;

836
  if ((infoPtr->dwStyle & TCS_HOTTRACK) || GetWindowTheme(infoPtr->hwnd))
837 838 839 840 841 842 843
  {
    POINT pt;
    UINT  flags;

    if (pos == NULL)
    {
      GetCursorPos(&pt);
844
      ScreenToClient(infoPtr->hwnd, &pt);
845 846 847
    }
    else
    {
848 849
      pt.x = (short)LOWORD(*pos);
      pt.y = (short)HIWORD(*pos);
850 851
    }

852
    item = TAB_InternalHitTest(infoPtr, pt, &flags);
853 854 855 856 857 858
  }

  if (item != infoPtr->iHotTracked)
  {
    if (infoPtr->iHotTracked >= 0)
    {
859
      /* Mark currently hot-tracked to be redrawn to look normal */
860 861 862 863 864
      if (out_redrawLeave != NULL)
        *out_redrawLeave = infoPtr->iHotTracked;

      if (item < 0)
      {
865
        /* Kill timer which forces recheck of mouse pos */
866
        KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
867 868 869 870
      }
    }
    else
    {
871
      /* Start timer so we recheck mouse pos */
872 873
      UINT timerID = SetTimer
        (
874
        infoPtr->hwnd,
875 876 877 878 879 880 881 882 883 884 885 886 887
        TAB_HOTTRACK_TIMER,
        TAB_HOTTRACK_TIMER_INTERVAL,
        TAB_HotTrackTimerProc
        );

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

    infoPtr->iHotTracked = item;

    if (item >= 0)
    {
888
	/* Mark new hot-tracked to be redrawn to look highlighted */
889 890 891 892 893 894 895 896 897 898 899
      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
900
static LRESULT
901
TAB_MouseMove (TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
Alex Priem's avatar
Alex Priem committed
902
{
903 904 905
  int redrawLeave;
  int redrawEnter;

Francis Beaudet's avatar
Francis Beaudet committed
906
  if (infoPtr->hwndToolTip)
907
    TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
Francis Beaudet's avatar
Francis Beaudet committed
908
		    WM_LBUTTONDOWN, wParam, lParam);
909 910 911

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

914 915
  hottrack_refresh (infoPtr, redrawLeave);
  hottrack_refresh (infoPtr, redrawEnter);
916

Francis Beaudet's avatar
Francis Beaudet committed
917
  return 0;
Alex Priem's avatar
Alex Priem committed
918
}
919

920 921 922
/******************************************************************************
 * TAB_AdjustRect
 *
Andreas Mohr's avatar
Andreas Mohr committed
923
 * Calculates the tab control's display area given the window rectangle or
924 925
 * the window rectangle given the requested display rectangle.
 */
926
static LRESULT TAB_AdjustRect(const TAB_INFO *infoPtr, WPARAM fLarger, LPRECT prc)
927
{
928
    LONG *iRightBottom, *iLeftTop;
929

930 931
    TRACE ("hwnd=%p fLarger=%ld (%s)\n", infoPtr->hwnd, fLarger,
           wine_dbgstr_rect(prc));
932

933 934
    if (!prc) return -1;

935
    if(infoPtr->dwStyle & TCS_VERTICAL)
936 937 938
    {
	iRightBottom = &(prc->right);
	iLeftTop     = &(prc->left);
939
    }
940
    else
941
    {
942 943
	iRightBottom = &(prc->bottom);
	iLeftTop     = &(prc->top);
944
    }
945

946 947
    if (fLarger) /* Go from display rectangle */
    {
948
        /* Add the height of the tabs. */
949
	if (infoPtr->dwStyle & TCS_BOTTOM)
950 951 952
	    *iRightBottom += infoPtr->tabHeight * infoPtr->uNumRows;
	else
	    *iLeftTop -= infoPtr->tabHeight * infoPtr->uNumRows +
953
			 ((infoPtr->dwStyle & TCS_BUTTONS)? 3 * (infoPtr->uNumRows - 1) : 0);
954

955 956
	/* Inflate the rectangle for the padding */
	InflateRect(prc, DISPLAY_AREA_PADDINGX, DISPLAY_AREA_PADDINGY); 
957

958 959
	/* Inflate for the border */
	InflateRect(prc, CONTROL_BORDER_SIZEX, CONTROL_BORDER_SIZEY);
960 961 962
    }
    else /* Go from window rectangle. */
    {
963 964
	/* Deflate the rectangle for the border */
	InflateRect(prc, -CONTROL_BORDER_SIZEX, -CONTROL_BORDER_SIZEY);
965

966 967
	/* Deflate the rectangle for the padding */
	InflateRect(prc, -DISPLAY_AREA_PADDINGX, -DISPLAY_AREA_PADDINGY);
968

969
	/* Remove the height of the tabs. */
970
	if (infoPtr->dwStyle & TCS_BOTTOM)
971 972 973
	    *iRightBottom -= infoPtr->tabHeight * infoPtr->uNumRows;
	else
	    *iLeftTop += (infoPtr->tabHeight) * infoPtr->uNumRows +
974
			 ((infoPtr->dwStyle & TCS_BUTTONS)? 3 * (infoPtr->uNumRows - 1) : 0);
975 976
    }

Francis Beaudet's avatar
Francis Beaudet committed
977
  return 0;
978 979
}

Francis Beaudet's avatar
Francis Beaudet committed
980 981 982 983 984 985
/******************************************************************************
 * TAB_OnHScroll
 *
 * This method will handle the notification from the scroll control and
 * perform the scrolling operation on the tab control.
 */
986
static LRESULT TAB_OnHScroll(TAB_INFO *infoPtr, int nScrollCode, int nPos)
Alexandre Julliard's avatar
Alexandre Julliard committed
987
{
Alexandre Julliard's avatar
Alexandre Julliard committed
988
  if(nScrollCode == SB_THUMBPOSITION && nPos != infoPtr->leftmostVisible)
Francis Beaudet's avatar
Francis Beaudet committed
989
  {
Alexandre Julliard's avatar
Alexandre Julliard committed
990 991 992 993
     if(nPos < infoPtr->leftmostVisible)
        infoPtr->leftmostVisible--;
     else
        infoPtr->leftmostVisible++;
Francis Beaudet's avatar
Francis Beaudet committed
994

995 996
     TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
     TAB_InvalidateTabArea(infoPtr);
997
     SendMessageW(infoPtr->hwndUpDown, UDM_SETPOS, 0,
Alexandre Julliard's avatar
Alexandre Julliard committed
998 999
                   MAKELONG(infoPtr->leftmostVisible, 0));
   }
Alexandre Julliard's avatar
Alexandre Julliard committed
1000

Alexandre Julliard's avatar
Alexandre Julliard committed
1001
   return 0;
Alexandre Julliard's avatar
Alexandre Julliard committed
1002
}
Francis Beaudet's avatar
Francis Beaudet committed
1003 1004

/******************************************************************************
Gerard Patel's avatar
Gerard Patel committed
1005
 * TAB_SetupScrolling
Francis Beaudet's avatar
Francis Beaudet committed
1006
 *
1007
 * This method will check the current scrolling state and make sure the
Francis Beaudet's avatar
Francis Beaudet committed
1008 1009 1010 1011 1012
 * scrolling control is displayed (or not).
 */
static void TAB_SetupScrolling(
  TAB_INFO*   infoPtr,
  const RECT* clientRect)
Alexandre Julliard's avatar
Alexandre Julliard committed
1013
{
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
1014
  static const WCHAR emptyW[] = { 0 };
Alexandre Julliard's avatar
Alexandre Julliard committed
1015
  INT maxRange = 0;
1016

Francis Beaudet's avatar
Francis Beaudet committed
1017 1018 1019
  if (infoPtr->needsScrolling)
  {
    RECT controlPos;
Alexandre Julliard's avatar
Alexandre Julliard committed
1020
    INT vsize, tabwidth;
1021

Francis Beaudet's avatar
Francis Beaudet committed
1022 1023 1024
    /*
     * Calculate the position of the scroll control.
     */
1025
    if(infoPtr->dwStyle & TCS_VERTICAL)
Francis Beaudet's avatar
Francis Beaudet committed
1026
    {
1027 1028 1029
      controlPos.right = clientRect->right;
      controlPos.left  = controlPos.right - 2 * GetSystemMetrics(SM_CXHSCROLL);

1030
      if (infoPtr->dwStyle & TCS_BOTTOM)
1031 1032 1033 1034 1035 1036 1037 1038 1039
      {
        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
1040 1041 1042
    }
    else
    {
1043 1044 1045
      controlPos.right = clientRect->right;
      controlPos.left  = controlPos.right - 2 * GetSystemMetrics(SM_CXHSCROLL);

1046
      if (infoPtr->dwStyle & TCS_BOTTOM)
1047 1048 1049 1050 1051 1052 1053 1054 1055
      {
        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
1056
    }
Alexandre Julliard's avatar
Alexandre Julliard committed
1057

Francis Beaudet's avatar
Francis Beaudet committed
1058 1059
    /*
     * If we don't have a scroll control yet, we want to create one.
1060
     * If we have one, we want to make sure it's positioned properly.
Francis Beaudet's avatar
Francis Beaudet committed
1061 1062 1063
     */
    if (infoPtr->hwndUpDown==0)
    {
1064
      infoPtr->hwndUpDown = CreateWindowW(UPDOWN_CLASSW, emptyW,
Alexandre Julliard's avatar
Alexandre Julliard committed
1065
					  WS_VISIBLE | WS_CHILD | UDS_HORZ,
Francis Beaudet's avatar
Francis Beaudet committed
1066 1067 1068
					  controlPos.left, controlPos.top,
					  controlPos.right - controlPos.left,
					  controlPos.bottom - controlPos.top,
1069
					  infoPtr->hwnd, NULL, NULL, NULL);
Francis Beaudet's avatar
Francis Beaudet committed
1070 1071 1072
    }
    else
    {
1073
      SetWindowPos(infoPtr->hwndUpDown,
1074
		   NULL,
Francis Beaudet's avatar
Francis Beaudet committed
1075 1076 1077
		   controlPos.left, controlPos.top,
		   controlPos.right - controlPos.left,
		   controlPos.bottom - controlPos.top,
1078
		   SWP_SHOWWINDOW | SWP_NOZORDER);
Francis Beaudet's avatar
Francis Beaudet committed
1079
    }
Alexandre Julliard's avatar
Alexandre Julliard committed
1080 1081 1082 1083 1084 1085 1086 1087 1088

    /* 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;
1089
       tabwidth = TAB_GetItem(infoPtr, infoPtr->uNumItem - 1)->rect.right;
Alexandre Julliard's avatar
Alexandre Julliard committed
1090 1091 1092

       for(; maxRange > 0; maxRange--)
       {
1093
          if(tabwidth - TAB_GetItem(infoPtr,maxRange - 1)->rect.left > vsize)
Alexandre Julliard's avatar
Alexandre Julliard committed
1094 1095 1096 1097 1098 1099
             break;
       }

       if(maxRange == infoPtr->uNumItem)
          maxRange--;
    }
Francis Beaudet's avatar
Francis Beaudet committed
1100 1101 1102
  }
  else
  {
1103
    /* If we once had a scroll control... hide it */
1104
    if (infoPtr->hwndUpDown)
Francis Beaudet's avatar
Francis Beaudet committed
1105 1106
      ShowWindow(infoPtr->hwndUpDown, SW_HIDE);
  }
Alexandre Julliard's avatar
Alexandre Julliard committed
1107
  if (infoPtr->hwndUpDown)
1108
     SendMessageW(infoPtr->hwndUpDown, UDM_SETRANGE32, 0, maxRange);
Francis Beaudet's avatar
Francis Beaudet committed
1109
}
Alexandre Julliard's avatar
Alexandre Julliard committed
1110

Francis Beaudet's avatar
Francis Beaudet committed
1111 1112 1113 1114 1115 1116 1117 1118
/******************************************************************************
 * 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
1119
 * don't, a scrolling control is added.
Francis Beaudet's avatar
Francis Beaudet committed
1120
 */
1121
static void TAB_SetItemBounds (TAB_INFO *infoPtr)
Francis Beaudet's avatar
Francis Beaudet committed
1122
{
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
1123
  TEXTMETRICW fontMetrics;
1124
  UINT        curItem;
Francis Beaudet's avatar
Francis Beaudet committed
1125
  INT         curItemLeftPos;
1126
  INT         curItemRowCount;
Francis Beaudet's avatar
Francis Beaudet committed
1127 1128 1129
  HFONT       hFont, hOldFont;
  HDC         hdc;
  RECT        clientRect;
1130 1131 1132
  INT         iTemp;
  RECT*       rcItem;
  INT         iIndex;
1133
  INT         icon_width = 0;
Francis Beaudet's avatar
Francis Beaudet committed
1134 1135 1136 1137 1138

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

Francis Beaudet's avatar
Francis Beaudet committed
1141 1142 1143 1144 1145 1146 1147
  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.
   */
1148
  GetClientRect(infoPtr->hwnd, &clientRect);
1149

Gerard Patel's avatar
Gerard Patel committed
1150
  /* if TCS_VERTICAL then swap the height and width so this code places the
1151
     tabs along the top of the rectangle and we can just rotate them after
Gerard Patel's avatar
Gerard Patel committed
1152
     rather than duplicate all of the below code */
1153
  if(infoPtr->dwStyle & TCS_VERTICAL)
1154 1155 1156 1157 1158 1159
  {
     iTemp = clientRect.bottom;
     clientRect.bottom = clientRect.right;
     clientRect.right = iTemp;
  }

1160 1161 1162 1163
  /* Now use hPadding and vPadding */
  infoPtr->uHItemPadding = infoPtr->uHItemPadding_s;
  infoPtr->uVItemPadding = infoPtr->uVItemPadding_s;
  
1164
  /* The leftmost item will be "0" aligned */
Francis Beaudet's avatar
Francis Beaudet committed
1165
  curItemLeftPos = 0;
Gerard Patel's avatar
Gerard Patel committed
1166
  curItemRowCount = infoPtr->uNumItem ? 1 : 0;
Francis Beaudet's avatar
Francis Beaudet committed
1167

1168
  if (!(infoPtr->fHeightSet))
1169
  {
1170
    int item_height;
1171
    INT icon_height = 0, cx;
1172

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

1176
    /* Get the icon height */
1177
    if (infoPtr->himl)
1178
      ImageList_GetIconSize(infoPtr->himl, &cx, &icon_height);
1179

1180
    /* Take the highest between font or icon */
1181
    if (fontMetrics.tmHeight > icon_height)
1182
      item_height = fontMetrics.tmHeight + 2;
1183 1184 1185 1186
    else
      item_height = icon_height;

    /*
1187 1188
     * Make sure there is enough space for the letters + icon + growing the
     * selected item + extra space for the selected item.
1189
     */
1190
    infoPtr->tabHeight = item_height + 
1191
	                 ((infoPtr->dwStyle & TCS_BUTTONS) ? 2 : 1) *
1192
                          infoPtr->uVItemPadding;
1193

1194
    TRACE("tabH=%d, tmH=%d, iconh=%d\n",
1195
	  infoPtr->tabHeight, fontMetrics.tmHeight, icon_height);
1196 1197
  }

1198
  TRACE("client right=%d\n", clientRect.right);
1199

1200 1201 1202
  /* Get the icon width */
  if (infoPtr->himl)
  {
1203 1204 1205
    INT cy;

    ImageList_GetIconSize(infoPtr->himl, &icon_width, &cy);
1206

1207
    if (infoPtr->dwStyle & TCS_FIXEDWIDTH)
1208 1209 1210 1211 1212 1213
      icon_width += 4;
    else
      /* Add padding if icon is present */
      icon_width += infoPtr->uHItemPadding;
  }

Francis Beaudet's avatar
Francis Beaudet committed
1214 1215
  for (curItem = 0; curItem < infoPtr->uNumItem; curItem++)
  {
1216 1217
    TAB_ITEM *curr = TAB_GetItem(infoPtr, curItem);
	
1218
    /* Set the leftmost position of the tab. */
1219
    curr->rect.left = curItemLeftPos;
Francis Beaudet's avatar
Francis Beaudet committed
1220

1221
    if (infoPtr->dwStyle & TCS_FIXEDWIDTH)
1222
    {
1223
      curr->rect.right = curr->rect.left +
1224
        max(infoPtr->tabWidth, icon_width);
1225
    }
1226
    else if (!curr->pszText)
1227
    {
1228 1229 1230 1231 1232 1233
      /* 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;
1234

1235 1236 1237 1238 1239 1240 1241 1242 1243
        /* 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;
1244
      SIZE size;
1245
      /* Calculate how wide the tab is depending on the text it contains */
1246 1247
      GetTextExtentPoint32W(hdc, curr->pszText,
                            lstrlenW(curr->pszText), &size);
1248

1249 1250 1251 1252 1253 1254 1255 1256
      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;
1257
      TRACE("for <%s>, l,r=%d,%d\n",
1258
	  debugstr_w(curr->pszText), curr->rect.left, curr->rect.right);
1259
    }
Francis Beaudet's avatar
Francis Beaudet committed
1260

1261 1262 1263 1264
    /*
     * Check if this is a multiline tab control and if so
     * check to see if we should wrap the tabs
     *
1265
     * Wrap all these tabs. We will arrange them evenly later.
1266 1267 1268
     *
     */

1269
    if (((infoPtr->dwStyle & TCS_MULTILINE) || (infoPtr->dwStyle & TCS_VERTICAL)) &&
1270
        (curr->rect.right > 
1271
	(clientRect.right - CONTROL_BORDER_SIZEX - DISPLAY_AREA_PADDINGX)))
1272
    {
1273
        curr->rect.right -= curr->rect.left;
1274

1275
	curr->rect.left = 0;
1276
        curItemRowCount++;
1277
	TRACE("wrapping <%s>, l,r=%d,%d\n", debugstr_w(curr->pszText),
1278
	    curr->rect.left, curr->rect.right);
1279 1280
    }

1281 1282
    curr->rect.bottom = 0;
    curr->rect.top = curItemRowCount - 1;
1283

1284
    TRACE("Rect: %s\n", wine_dbgstr_rect(&curr->rect));
Francis Beaudet's avatar
Francis Beaudet committed
1285 1286 1287 1288 1289

    /*
     * The leftmost position of the next item is the rightmost position
     * of this one.
     */
1290
    if (infoPtr->dwStyle & TCS_BUTTONS)
1291
    {
1292
      curItemLeftPos = curr->rect.right + BUTTON_SPACINGX;
1293
      if (infoPtr->dwStyle & TCS_FLATBUTTONS)
1294 1295
        curItemLeftPos += FLAT_BTN_SPACINGX;
    }
1296
    else
1297
      curItemLeftPos = curr->rect.right;
Francis Beaudet's avatar
Francis Beaudet committed
1298 1299
  }

1300
  if (!((infoPtr->dwStyle & TCS_MULTILINE) || (infoPtr->dwStyle & TCS_VERTICAL)))
1301 1302 1303 1304
  {
    /*
     * Check if we need a scrolling control.
     */
1305
    infoPtr->needsScrolling = (curItemLeftPos + (2 * SELECTED_TAB_OFFSET) >
1306 1307
                               clientRect.right);

1308 1309
    /* Don't need scrolling, then update infoPtr->leftmostVisible */
    if(!infoPtr->needsScrolling)
1310
      infoPtr->leftmostVisible = 0;
1311
  }
1312 1313 1314 1315 1316 1317 1318 1319
  else
  {
    /*
     * No scrolling in Multiline or Vertical styles.
     */
    infoPtr->needsScrolling = FALSE;
    infoPtr->leftmostVisible = 0;
  }
1320
  TAB_SetupScrolling(infoPtr, &clientRect);
1321

1322
  /* Set the number of rows */
1323
  infoPtr->uNumRows = curItemRowCount;
Yuxi Zhang's avatar
Yuxi Zhang committed
1324

1325
  /* Arrange all tabs evenly if style says so */
1326 1327
   if (!(infoPtr->dwStyle & TCS_RAGGEDRIGHT) &&
       ((infoPtr->dwStyle & TCS_MULTILINE) || (infoPtr->dwStyle & TCS_VERTICAL)) &&
1328 1329
       (infoPtr->uNumItem > 0) &&
       (infoPtr->uNumRows > 1))
1330
   {
1331 1332
      INT tabPerRow,remTab,iRow;
      UINT iItm;
1333
      INT iCount=0;
1334 1335

      /*
1336
       * Ok windows tries to even out the rows. place the same
1337 1338 1339
       * number of tabs in each row. So lets give that a shot
       */

Gerard Patel's avatar
Gerard Patel committed
1340 1341
      tabPerRow = infoPtr->uNumItem / (infoPtr->uNumRows);
      remTab = infoPtr->uNumItem % (infoPtr->uNumRows);
1342 1343 1344 1345 1346

      for (iItm=0,iRow=0,iCount=0,curItemLeftPos=0;
           iItm<infoPtr->uNumItem;
           iItm++,iCount++)
      {
1347
          /* normalize the current rect */
1348 1349
          TAB_ITEM *curr = TAB_GetItem(infoPtr, iItm);
 
1350
          /* shift the item to the left side of the clientRect */
1351 1352
          curr->rect.right -= curr->rect.left;
          curr->rect.left = 0;
1353

1354
          TRACE("r=%d, cl=%d, cl.r=%d, iCount=%d, iRow=%d, uNumRows=%d, remTab=%d, tabPerRow=%d\n",
1355
	      curr->rect.right, curItemLeftPos, clientRect.right,
1356 1357 1358 1359 1360 1361
	      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 */

1362
	  if (infoPtr->dwStyle & TCS_VERTICAL) {
1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375
	      /* 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;
	      }
1376
	  }
1377

1378
          /* shift the item to the right to place it as the next item in this row */
1379 1380 1381
          curr->rect.left += curItemLeftPos;
          curr->rect.right += curItemLeftPos;
          curr->rect.top = iRow;
1382
          if (infoPtr->dwStyle & TCS_BUTTONS)
1383
	  {
1384
            curItemLeftPos = curr->rect.right + 1;
1385
            if (infoPtr->dwStyle & TCS_FLATBUTTONS)
1386 1387
	      curItemLeftPos += FLAT_BTN_SPACINGX;
	  }
1388
          else
1389
            curItemLeftPos = curr->rect.right;
1390

1391
          TRACE("arranging <%s>, l,r=%d,%d, row=%d\n",
1392 1393
	      debugstr_w(curr->pszText), curr->rect.left,
	      curr->rect.right, curr->rect.top);
1394
      }
1395

1396 1397 1398 1399
      /*
       * Justify the rows
       */
      {
1400 1401 1402
	INT widthDiff, iIndexStart=0, iIndexEnd=0;
	INT remainder;
	INT iCount=0;
1403

1404
        while(iIndexStart < infoPtr->uNumItem)
1405
        {
1406 1407
          TAB_ITEM *start = TAB_GetItem(infoPtr, iIndexStart);

1408
          /*
1409
           * find the index of the row
1410 1411 1412 1413
           */
          /* find the first item on the next row */
          for (iIndexEnd=iIndexStart;
              (iIndexEnd < infoPtr->uNumItem) &&
1414 1415
 	      (TAB_GetItem(infoPtr, iIndexEnd)->rect.top ==
                start->rect.top) ;
1416 1417 1418 1419 1420 1421 1422 1423 1424 1425
              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) -
1426
			TAB_GetItem(infoPtr, iIndexEnd - 1)->rect.right;
1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437

	  /* 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++)
	    {
1438 1439 1440 1441
              TAB_ITEM *item = TAB_GetItem(infoPtr, iIndex);

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

1443
              TRACE("adjusting 1 <%s>, l,r=%d,%d\n",
1444 1445
		  debugstr_w(item->pszText),
		  item->rect.left, item->rect.right);
1446

1447
	    }
1448
	    TAB_GetItem(infoPtr, iIndex - 1)->rect.right += remainder;
1449 1450 1451
	  }
	  else /* we have only one item on this row, make it take up the entire row */
	  {
1452 1453
	    start->rect.left = clientRect.left;
	    start->rect.right = clientRect.right - 4;
1454

1455
            TRACE("adjusting 2 <%s>, l,r=%d,%d\n",
1456 1457
		debugstr_w(start->pszText),
		start->rect.left, start->rect.right);
1458

1459
	  }
1460

1461

1462 1463
	  iIndexStart = iIndexEnd;
	}
1464 1465 1466
      }
  }

1467
  /* if TCS_VERTICAL rotate the tabs so they are along the side of the clientRect */
1468
  if(infoPtr->dwStyle & TCS_VERTICAL)
1469 1470 1471 1472
  {
    RECT rcOriginal;
    for(iIndex = 0; iIndex < infoPtr->uNumItem; iIndex++)
    {
1473
      rcItem = &TAB_GetItem(infoPtr, iIndex)->rect;
1474 1475 1476

      rcOriginal = *rcItem;

1477 1478
      /* this is rotating the items by 90 degrees clockwise around the center of the control */
      rcItem->top = (rcOriginal.left - clientRect.left);
1479 1480 1481 1482 1483 1484
      rcItem->bottom = rcItem->top + (rcOriginal.right - rcOriginal.left);
      rcItem->left = rcOriginal.top;
      rcItem->right = rcOriginal.bottom;
    }
  }

1485 1486
  TAB_EnsureSelectionVisible(infoPtr);
  TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
1487 1488

  /* Cleanup */
Francis Beaudet's avatar
Francis Beaudet committed
1489
  SelectObject (hdc, hOldFont);
1490
  ReleaseDC (infoPtr->hwnd, hdc);
Francis Beaudet's avatar
Francis Beaudet committed
1491 1492
}

1493 1494

static void
1495
TAB_EraseTabInterior(const TAB_INFO *infoPtr, HDC hdc, INT iItem, const RECT *drawRect)
1496 1497 1498 1499 1500
{
    HBRUSH   hbr = CreateSolidBrush (comctl32_color.clrBtnFace);
    BOOL     deleteBrush = TRUE;
    RECT     rTemp = *drawRect;

1501
    if (infoPtr->dwStyle & TCS_BUTTONS)
1502 1503 1504 1505
    {
	if (iItem == infoPtr->iSelected)
	{
	    /* Background color */
1506
	    if (!(infoPtr->dwStyle & TCS_OWNERDRAWFIXED))
1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526
	    {
		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 */
	{
1527
	    if (infoPtr->dwStyle & TCS_FLATBUTTONS)
1528
	    {
1529 1530
		InflateRect(&rTemp, 2, 2);
		FillRect(hdc, &rTemp, hbr);
1531 1532
		if (iItem == infoPtr->iHotTracked ||
                   (iItem != infoPtr->iSelected && iItem == infoPtr->uFocus))
1533
		    DrawEdge(hdc, &rTemp, BDR_RAISEDINNER, BF_RECT);
1534 1535 1536 1537 1538 1539 1540 1541
	    }
	    else
		FillRect(hdc, &rTemp, hbr);
	}

    }
    else /* !TCS_BUTTONS */
    {
1542
        InflateRect(&rTemp, -2, -2);
1543 1544
        if (!GetWindowTheme (infoPtr->hwnd))
	    FillRect(hdc, &rTemp, hbr);
1545 1546
    }

1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558
    /* highlighting is drawn on top of previous fills */
    if (TAB_GetItem(infoPtr, iItem)->dwState & TCIS_HIGHLIGHTED)
    {
        if (deleteBrush)
        {
            DeleteObject(hbr);
            deleteBrush = FALSE;
        }
        hbr = GetSysColorBrush(COLOR_HIGHLIGHT);
        FillRect(hdc, &rTemp, hbr);
    }

1559 1560 1561 1562
    /* Cleanup */
    if (deleteBrush) DeleteObject(hbr);
}

1563 1564 1565 1566 1567
/******************************************************************************
 * TAB_DrawItemInterior
 *
 * This method is used to draw the interior (text and icon) of a single tab
 * into the tab control.
1568
 */
1569
static void
1570
TAB_DrawItemInterior(const TAB_INFO *infoPtr, HDC hdc, INT iItem, RECT *drawRect)
1571 1572 1573
{
  RECT localRect;

1574
  HPEN   htextPen;
1575 1576
  HPEN   holdPen;
  INT    oldBkMode;
1577 1578
  HFONT  hOldFont;
  
1579
/*  if (drawRect == NULL) */
1580 1581 1582 1583 1584 1585 1586 1587
  {
    BOOL isVisible;
    RECT itemRect;
    RECT selectedRect;

    /*
     * Get the rectangle for the item.
     */
1588
    isVisible = TAB_InternalGetItemRect(infoPtr, iItem, &itemRect, &selectedRect);
1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602
    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.
     */
1603 1604 1605 1606 1607
    if (iItem == infoPtr->iSelected)
      *drawRect = selectedRect;
    else
      *drawRect = itemRect;
        
1608
    if (infoPtr->dwStyle & TCS_BUTTONS)
1609 1610 1611
    {
      if (iItem == infoPtr->iSelected)
      {
1612 1613 1614
	drawRect->left   += 4;
	drawRect->top    += 4;
	drawRect->right  -= 4;
1615

1616
	if (infoPtr->dwStyle & TCS_VERTICAL)
1617
	{
1618
	  if (!(infoPtr->dwStyle & TCS_BOTTOM)) drawRect->right  += 1;
1619 1620 1621 1622
	  drawRect->bottom   -= 4;
	}
	else
	{
1623
	  if (infoPtr->dwStyle & TCS_BOTTOM)
1624 1625 1626 1627 1628 1629 1630
	  {
	    drawRect->top    -= 2;
	    drawRect->bottom -= 4;
	  }
	  else
	    drawRect->bottom -= 1;
	}
1631 1632 1633 1634 1635 1636 1637
      }
      else
      {
	drawRect->left   += 2;
	drawRect->top    += 2;
	drawRect->right  -= 2;
	drawRect->bottom -= 2;
1638 1639 1640 1641
      }
    }
    else
    {
1642
      if ((infoPtr->dwStyle & TCS_VERTICAL) && (infoPtr->dwStyle & TCS_BOTTOM))
1643
      {
1644
        if (iItem != infoPtr->iSelected)
1645
	{
1646 1647 1648 1649 1650
	  drawRect->left   += 2;
	  drawRect->top    += 2;
	  drawRect->bottom -= 2;
	}
      }
1651
      else if (infoPtr->dwStyle & TCS_VERTICAL)
1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663
      {
        if (iItem == infoPtr->iSelected)
	{
	  drawRect->right  += 1;
	}
	else
	{
	  drawRect->top    += 2;
	  drawRect->right  -= 2;
	  drawRect->bottom -= 2;
	}
      }
1664
      else if (infoPtr->dwStyle & TCS_BOTTOM)
1665 1666 1667 1668 1669 1670 1671 1672 1673
      {
        if (iItem == infoPtr->iSelected)
	{
	  drawRect->top    -= 2;
	}
	else
	{
	  InflateRect(drawRect, -2, -2);
          drawRect->bottom += 2;
1674 1675
	}
      }
1676
      else
1677
      {
1678 1679 1680 1681 1682 1683
        if (iItem == infoPtr->iSelected)
	{
	  drawRect->bottom += 3;
	}
	else
	{
1684
	  drawRect->bottom -= 2;
1685 1686
	  InflateRect(drawRect, -2, 0);
	}
1687
      }
1688 1689
    }
  }
1690
  TRACE("drawRect=(%s)\n", wine_dbgstr_rect(drawRect));
1691 1692

  /* Clear interior */
1693
  TAB_EraseTabInterior (infoPtr, hdc, iItem, drawRect);
1694 1695

  /* Draw the focus rectangle */
1696
  if (!(infoPtr->dwStyle & TCS_FOCUSNEVER) &&
1697
      (GetFocus() == infoPtr->hwnd) &&
1698 1699 1700
      (iItem == infoPtr->uFocus) )
  {
    RECT rFocus = *drawRect;
1701 1702

    if (!(infoPtr->dwStyle & TCS_BUTTONS)) InflateRect(&rFocus, -3, -3);
1703
    if (infoPtr->dwStyle & TCS_BOTTOM && !(infoPtr->dwStyle & TCS_VERTICAL))
1704 1705
      rFocus.top -= 3;

1706 1707 1708
    /* focus should stay on selected item for TCS_BUTTONS style */
    if (!((infoPtr->dwStyle & TCS_BUTTONS) && (infoPtr->iSelected != iItem)))
      DrawFocusRect(hdc, &rFocus);
1709
  }
1710 1711 1712 1713

  /*
   * Text pen
   */
1714
  htextPen = CreatePen( PS_SOLID, 1, comctl32_color.clrBtnText );
1715 1716
  holdPen  = SelectObject(hdc, htextPen);
  hOldFont = SelectObject(hdc, infoPtr->hFont);
1717

1718 1719 1720
  /*
   * Setup for text output
  */
1721
  oldBkMode = SetBkMode(hdc, TRANSPARENT);
1722
  if (!GetWindowTheme (infoPtr->hwnd) || (infoPtr->dwStyle & TCS_BUTTONS))
1723
  {
1724 1725
    if ((infoPtr->dwStyle & TCS_HOTTRACK) && (iItem == infoPtr->iHotTracked) &&
        !(infoPtr->dwStyle & TCS_FLATBUTTONS))
1726 1727 1728 1729 1730 1731
      SetTextColor(hdc, comctl32_color.clrHighlight);
    else if (TAB_GetItem(infoPtr, iItem)->dwState & TCIS_HIGHLIGHTED)
      SetTextColor(hdc, comctl32_color.clrHighlightText);
    else
      SetTextColor(hdc, comctl32_color.clrBtnText);
  }
1732 1733 1734 1735

  /*
   * if owner draw, tell the owner to draw
   */
1736
  if ((infoPtr->dwStyle & TCS_OWNERDRAWFIXED) && IsWindow(infoPtr->hwndNotify))
1737 1738 1739 1740
  {
    DRAWITEMSTRUCT dis;
    UINT id;

1741 1742 1743 1744 1745 1746 1747 1748
    drawRect->top += 2;
    drawRect->right -= 1;
    if ( iItem == infoPtr->iSelected )
    {
        drawRect->right -= 1;
        drawRect->left += 1;
    }

1749
    id = (UINT)GetWindowLongPtrW( infoPtr->hwnd, GWLP_ID );
1750

1751
    /* fill DRAWITEMSTRUCT */
1752 1753 1754 1755
    dis.CtlType    = ODT_TAB;
    dis.CtlID      = id;
    dis.itemID     = iItem;
    dis.itemAction = ODA_DRAWENTIRE;
1756
    dis.itemState = 0;
1757
    if ( iItem == infoPtr->iSelected )
1758 1759 1760
      dis.itemState |= ODS_SELECTED;
    if (infoPtr->uFocus == iItem) 
      dis.itemState |= ODS_FOCUS;
1761
    dis.hwndItem = infoPtr->hwnd;
1762
    dis.hDC      = hdc;
1763
    CopyRect(&dis.rcItem,drawRect);
1764

1765 1766 1767 1768 1769 1770 1771
    /* when extra data fits ULONG_PTR, store it directly */
    if (infoPtr->cbInfo > sizeof(LPARAM))
        dis.itemData =  (ULONG_PTR) TAB_GetItem(infoPtr, iItem)->extra;
    else
    {
        /* this could be considered broken on 64 bit, but that's how it works -
           only first 4 bytes are copied */
1772
        dis.itemData = 0;
1773 1774 1775 1776
        memcpy(&dis.itemData, (ULONG_PTR*)TAB_GetItem(infoPtr, iItem)->extra, 4);
    }

    /* draw notification */
1777
    SendMessageW( infoPtr->hwndNotify, WM_DRAWITEM, id, (LPARAM)&dis );
1778 1779 1780
  }
  else
  {
1781
    TAB_ITEM *item = TAB_GetItem(infoPtr, iItem);
1782 1783 1784 1785 1786
    RECT rcTemp;
    RECT rcImage;

    /* used to center the icon and text in the tab */
    RECT rcText;
1787
    INT center_offset_h, center_offset_v;
1788 1789 1790 1791 1792 1793

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

    rcTemp = *drawRect;

1794 1795
    rcText.left = rcText.top = rcText.right = rcText.bottom = 0;

1796
    /* get the rectangle that the text fits in */
1797
    if (item->pszText)
1798
    {
1799
      DrawTextW(hdc, item->pszText, -1, &rcText, DT_CALCRECT);
1800
    }
1801 1802 1803 1804 1805
    /*
     * If not owner draw, then do the drawing ourselves.
     *
     * Draw the icon.
     */
1806
    if (infoPtr->himl && item->iImage != -1)
1807
    {
1808 1809 1810
      INT cx;
      INT cy;
      
1811 1812
      ImageList_GetIconSize(infoPtr->himl, &cx, &cy);

1813
      if(infoPtr->dwStyle & TCS_VERTICAL)
1814 1815
      {
        center_offset_h = ((drawRect->bottom - drawRect->top) - (cy + infoPtr->uHItemPadding + (rcText.right  - rcText.left))) / 2;
1816
        center_offset_v = ((drawRect->right - drawRect->left) - cx) / 2;
1817
      }
1818
      else
1819 1820
      {
        center_offset_h = ((drawRect->right - drawRect->left) - (cx + infoPtr->uHItemPadding + (rcText.right  - rcText.left))) / 2;
1821
        center_offset_v = ((drawRect->bottom - drawRect->top) - cy) / 2;
1822 1823
      }

1824 1825 1826 1827 1828 1829
      /* 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;

1830
      if (infoPtr->dwStyle & TCS_FIXEDWIDTH && infoPtr->dwStyle & (TCS_FORCELABELLEFT | TCS_FORCEICONLEFT))
1831
	center_offset_h = infoPtr->uHItemPadding;
1832

1833 1834 1835
      if (center_offset_h < 2)
        center_offset_h = 2;
	
1836 1837 1838
      if (center_offset_v < 0)
        center_offset_v = 0;
	
1839
      TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%s), textlen=%d\n",
1840
	  debugstr_w(item->pszText), center_offset_h, center_offset_v,
1841
          wine_dbgstr_rect(drawRect), (rcText.right-rcText.left));
1842

1843
      if((infoPtr->dwStyle & TCS_VERTICAL) && (infoPtr->dwStyle & TCS_BOTTOM))
1844
      {
1845
        rcImage.top = drawRect->top + center_offset_h;
1846 1847 1848 1849
	/* 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;
1850
        drawRect->top += cy + infoPtr->uHItemPadding;
1851
      }
1852
      else if(infoPtr->dwStyle & TCS_VERTICAL)
1853
      {
1854 1855
        rcImage.top  = drawRect->bottom - cy - center_offset_h;
	rcImage.left = drawRect->left + center_offset_v;
1856
        drawRect->bottom -= cy + infoPtr->uHItemPadding;
1857 1858 1859
      }
      else /* normal style, whether TCS_BOTTOM or not */
      {
1860
        rcImage.left = drawRect->left + center_offset_h;
1861
	rcImage.top = drawRect->top + center_offset_v;
1862
        drawRect->left += cx + infoPtr->uHItemPadding;
1863
      }
1864

1865
      TRACE("drawing image=%d, left=%d, top=%d\n",
1866
	    item->iImage, rcImage.left, rcImage.top-1);
1867 1868 1869
      ImageList_Draw
        (
        infoPtr->himl,
1870
        item->iImage,
1871
        hdc,
1872
        rcImage.left,
1873
        rcImage.top,
1874 1875
        ILD_NORMAL
        );
1876
    }
1877 1878

    /* Now position text */
1879
    if (infoPtr->dwStyle & TCS_FIXEDWIDTH && infoPtr->dwStyle & TCS_FORCELABELLEFT)
1880 1881
      center_offset_h = infoPtr->uHItemPadding;
    else
1882
      if(infoPtr->dwStyle & TCS_VERTICAL)
1883
        center_offset_h = ((drawRect->bottom - drawRect->top) - (rcText.right - rcText.left)) / 2;
1884
      else
1885
        center_offset_h = ((drawRect->right - drawRect->left) - (rcText.right - rcText.left)) / 2;
1886

1887
    if(infoPtr->dwStyle & TCS_VERTICAL)
1888
    {
1889
      if(infoPtr->dwStyle & TCS_BOTTOM)
1890 1891 1892 1893
        drawRect->top+=center_offset_h;
      else
        drawRect->bottom-=center_offset_h;

1894
      center_offset_v = ((drawRect->right - drawRect->left) - (rcText.bottom - rcText.top)) / 2;
1895
    }
1896
    else
1897
    {
1898
      drawRect->left += center_offset_h;
1899
      center_offset_v = ((drawRect->bottom - drawRect->top) - (rcText.bottom - rcText.top)) / 2;
1900 1901
    }

1902 1903 1904 1905 1906 1907
    /* 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;

1908 1909 1910
    if (center_offset_v < 0)
      center_offset_v = 0;

1911
    if(infoPtr->dwStyle & TCS_VERTICAL)
1912 1913 1914 1915
      drawRect->left += center_offset_v;
    else
      drawRect->top += center_offset_v;

1916
    /* Draw the text */
1917
    if(infoPtr->dwStyle & TCS_VERTICAL) /* if we are vertical rotate the text and each character */
1918
    {
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
1919 1920
      static const WCHAR ArialW[] = { 'A','r','i','a','l',0 };
      LOGFONTW logfont;
1921 1922 1923 1924
      HFONT hFont = 0;
      INT nEscapement = 900;
      INT nOrientation = 900;

1925
      if(infoPtr->dwStyle & TCS_BOTTOM)
1926 1927 1928 1929 1930
      {
        nEscapement = -900;
        nOrientation = -900;
      }

1931 1932
      /* 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
1933
      if (!GetObjectW((infoPtr->hFont) ?
1934
                infoPtr->hFont : GetStockObject(SYSTEM_FONT),
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
1935
                sizeof(LOGFONTW),&logfont))
1936
      {
1937
        INT iPointSize = 9;
1938

Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
1939
        lstrcpyW(logfont.lfFaceName, ArialW);
1940
        logfont.lfHeight = -MulDiv(iPointSize, GetDeviceCaps(hdc, LOGPIXELSY),
1941 1942 1943 1944 1945 1946
                                    72);
        logfont.lfWeight = FW_NORMAL;
        logfont.lfItalic = 0;
        logfont.lfUnderline = 0;
        logfont.lfStrikeOut = 0;
      }
1947 1948 1949

      logfont.lfEscapement = nEscapement;
      logfont.lfOrientation = nOrientation;
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
1950
      hFont = CreateFontIndirectW(&logfont);
1951
      SelectObject(hdc, hFont);
1952

1953
      if (item->pszText)
1954 1955
      {
        ExtTextOutW(hdc,
1956 1957
        (infoPtr->dwStyle & TCS_BOTTOM) ? drawRect->right : drawRect->left,
        (!(infoPtr->dwStyle & TCS_BOTTOM)) ? drawRect->bottom : drawRect->top,
1958 1959
        ETO_CLIPPED,
        drawRect,
1960 1961
        item->pszText,
        lstrlenW(item->pszText),
1962 1963
        0);
      }
1964 1965

      DeleteObject(hFont);
1966 1967 1968
    }
    else
    {
1969
      TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%s), textlen=%d\n",
1970
	  debugstr_w(item->pszText), center_offset_h, center_offset_v,
1971
          wine_dbgstr_rect(drawRect), (rcText.right-rcText.left));
1972
      if (item->pszText)
1973 1974 1975 1976
      {
        DrawTextW
        (
          hdc,
1977 1978
          item->pszText,
          lstrlenW(item->pszText),
1979 1980
          drawRect,
          DT_LEFT | DT_SINGLELINE
1981
        );
1982
      }
1983
    }
1984 1985

    *drawRect = rcTemp; /* restore drawRect */
1986 1987 1988 1989 1990
  }

  /*
  * Cleanup
  */
1991
  SelectObject(hdc, hOldFont);
1992 1993
  SetBkMode(hdc, oldBkMode);
  SelectObject(hdc, holdPen);
1994
  DeleteObject( htextPen );
1995 1996
}

Francis Beaudet's avatar
Francis Beaudet committed
1997 1998 1999 2000
/******************************************************************************
 * TAB_DrawItem
 *
 * This method is used to draw a single tab into the tab control.
2001
 */
2002
static void TAB_DrawItem(const TAB_INFO *infoPtr, HDC  hdc, INT  iItem)
Francis Beaudet's avatar
Francis Beaudet committed
2003 2004 2005 2006
{
  RECT      itemRect;
  RECT      selectedRect;
  BOOL      isVisible;
2007 2008 2009 2010
  RECT      r, fillRect, r1;
  INT       clRight = 0;
  INT       clBottom = 0;
  COLORREF  bkgnd, corner;
2011
  HTHEME    theme;
Francis Beaudet's avatar
Francis Beaudet committed
2012 2013 2014 2015

  /*
   * Get the rectangle for the item.
   */
2016
  isVisible = TAB_InternalGetItemRect(infoPtr,
Francis Beaudet's avatar
Francis Beaudet committed
2017 2018 2019 2020 2021 2022
				      iItem,
				      &itemRect,
				      &selectedRect);

  if (isVisible)
  {
2023 2024 2025
    RECT rUD, rC;

    /* Clip UpDown control to not draw over it */
2026 2027
    if (infoPtr->needsScrolling)
    {
2028
      GetWindowRect(infoPtr->hwnd, &rC);
2029 2030 2031
      GetWindowRect(infoPtr->hwndUpDown, &rUD);
      ExcludeClipRect(hdc, rUD.left - rC.left, rUD.top - rC.top, rUD.right - rC.left, rUD.bottom - rC.top);
    }
2032

2033 2034
    /* If you need to see what the control is doing,
     * then override these variables. They will change what
2035
     * fill colors are used for filling the tabs, and the
2036 2037 2038 2039
     * corners when drawing the edge.
     */
    bkgnd = comctl32_color.clrBtnFace;
    corner = comctl32_color.clrBtnFace;
Francis Beaudet's avatar
Francis Beaudet committed
2040

2041
    if (infoPtr->dwStyle & TCS_BUTTONS)
Francis Beaudet's avatar
Francis Beaudet committed
2042
    {
2043
      /* Get item rectangle */
2044 2045
      r = itemRect;

2046
      /* Separators between flat buttons */
2047
      if ((infoPtr->dwStyle & TCS_FLATBUTTONS) && (infoPtr->exStyle & TCS_EX_FLATSEPARATORS))
2048
      {
2049 2050 2051
	r1 = r;
	r1.right += (FLAT_BTN_SPACINGX -2);
	DrawEdge(hdc, &r1, EDGE_ETCHED, BF_RIGHT);
2052 2053
      }

2054 2055
      if (iItem == infoPtr->iSelected)
      {
2056
	DrawEdge(hdc, &r, EDGE_SUNKEN, BF_SOFT|BF_RECT);
2057 2058
	
	OffsetRect(&r, 1, 1);
2059
      }
2060
      else  /* ! selected */
2061
      {
2062
        DWORD state = TAB_GetItem(infoPtr, iItem)->dwState;
2063

2064
        if ((state & TCIS_BUTTONPRESSED) || (iItem == infoPtr->uFocus))
2065 2066
          DrawEdge(hdc, &r, EDGE_SUNKEN, BF_SOFT|BF_RECT);
        else
2067
          if (!(infoPtr->dwStyle & TCS_FLATBUTTONS))
2068
            DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_RECT);
2069
      }
Francis Beaudet's avatar
Francis Beaudet committed
2070
    }
2071
    else /* !TCS_BUTTONS */
Francis Beaudet's avatar
Francis Beaudet committed
2072
    {
2073 2074
      /* We draw a rectangle of different sizes depending on the selection
       * state. */
2075 2076
      if (iItem == infoPtr->iSelected) {
	RECT rect;
2077
	GetClientRect (infoPtr->hwnd, &rect);
2078 2079
	clRight = rect.right;
	clBottom = rect.bottom;
2080
        r = selectedRect;
2081
      }
2082 2083
      else
        r = itemRect;
Francis Beaudet's avatar
Francis Beaudet committed
2084

2085
      /*
2086
       * Erase the background. (Delay it but setup rectangle.)
2087 2088
       * This is necessary when drawing the selected item since it is larger
       * than the others, it might overlap with stuff already drawn by the
2089
       * other tabs
2090
       */
2091
      fillRect = r;
2092

2093 2094 2095 2096 2097
      /* 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)) 
2098
          && ((infoPtr->dwStyle & (TCS_VERTICAL | TCS_BOTTOM)) == 0))
2099
      {
2100
          static const int partIds[8] = {
2101 2102 2103 2104 2105 2106 2107 2108 2109 2110 2111 2112 2113 2114 2115 2116 2117 2118 2119
              /* 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. */
2120
          GetClientRect(infoPtr->hwnd, &r1);
2121 2122
          if(selectedRect.left == 0)
              partIndex += 1;
2123
          if(selectedRect.right == r1.right)
2124 2125 2126 2127 2128 2129 2130 2131 2132 2133 2134 2135 2136 2137 2138 2139
              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);
      }
2140
      else if(infoPtr->dwStyle & TCS_VERTICAL)
2141
      {
2142 2143
	/* These are for adjusting the drawing of a Selected tab      */
	/* The initial values are for the normal case of non-Selected */
2144
	int ZZ = 1;   /* Do not stretch if selected */
2145 2146 2147 2148 2149
	if (iItem == infoPtr->iSelected) {
	    ZZ = 0;

	    /* if leftmost draw the line longer */
	    if(selectedRect.top == 0)
2150
		fillRect.top += CONTROL_BORDER_SIZEY;
2151 2152
	    /* if rightmost draw the line longer */
	    if(selectedRect.bottom == clBottom)
2153
		fillRect.bottom -= CONTROL_BORDER_SIZEY;
2154 2155
	}

2156
        if (infoPtr->dwStyle & TCS_BOTTOM)
2157
        {
2158 2159 2160
	  /* Adjust both rectangles to match native */
	  r.left += (1-ZZ);

2161 2162
          TRACE("<right> item=%d, fill=(%s), edge=(%s)\n",
                iItem, wine_dbgstr_rect(&fillRect), wine_dbgstr_rect(&r));
2163 2164 2165

	  /* Clear interior */
	  SetBkColor(hdc, bkgnd);
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
2166
	  ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2167 2168 2169 2170

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

2171
	  /* Now erase the top corner and draw diagonal edge */
2172 2173 2174 2175 2176
	  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
2177
	  ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2178 2179 2180
	  r1.right--;
	  DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPLEFT);

2181
	  /* Now erase the bottom corner and draw diagonal edge */
2182 2183 2184 2185
	  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
2186
	  ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2187 2188 2189 2190 2191 2192 2193 2194 2195 2196
	  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);
	  }

2197 2198 2199
        }
        else
        {
2200 2201
          TRACE("<left> item=%d, fill=(%s), edge=(%s)\n",
                iItem, wine_dbgstr_rect(&fillRect), wine_dbgstr_rect(&r));
2202 2203 2204

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

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

2210
	  /* Now erase the top corner and draw diagonal edge */
2211 2212 2213 2214 2215
	  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
2216
	  ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2217 2218 2219
	  r1.left++;
	  DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPRIGHT);

2220
	  /* Now erase the bottom corner and draw diagonal edge */
2221 2222 2223 2224
	  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
2225
	  ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2226 2227
	  r1.left++;
	  DrawEdge(hdc, &r1, EDGE_SUNKEN, BF_DIAGONAL_ENDTOPLEFT);
2228
        }
2229
      }
2230
      else  /* ! TCS_VERTICAL */
2231
      {
2232 2233 2234 2235 2236
	/* 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)
2237
		fillRect.left += CONTROL_BORDER_SIZEX;
2238 2239
	    /* if rightmost draw the line longer */
	    if(selectedRect.right == clRight)
2240
		fillRect.right -= CONTROL_BORDER_SIZEX;
2241 2242
	}

2243
        if (infoPtr->dwStyle & TCS_BOTTOM)
2244
        {
2245
	  /* Adjust both rectangles for topmost row */
2246
	  if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2247 2248 2249 2250
	  {
	    fillRect.top -= 2;
	    r.top -= 1;
	  }
2251

2252 2253
          TRACE("<bottom> item=%d, fill=(%s), edge=(%s)\n",
                iItem, wine_dbgstr_rect(&fillRect), wine_dbgstr_rect(&r));
2254 2255 2256

	  /* Clear interior */
	  SetBkColor(hdc, bkgnd);
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
2257
	  ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2258 2259 2260 2261

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

2262
	  /* Now erase the righthand corner and draw diagonal edge */
2263 2264 2265 2266 2267
	  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
2268
	  ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2269 2270 2271
	  r1.bottom--;
	  DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMLEFT);

2272
	  /* Now erase the lefthand corner and draw diagonal edge */
2273 2274 2275 2276
	  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
2277
	  ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2278 2279 2280
	  r1.bottom--;
	  DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPLEFT);

2281 2282 2283 2284 2285 2286
	  if (iItem == infoPtr->iSelected)
	  {
	    r.top += 2;
	    r.left += 1;
	    if (selectedRect.left == 0)
	    {
2287 2288 2289 2290
	      r1 = r;
	      r1.bottom = r1.top;
	      r1.top--;
	      DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_LEFT);
2291
	    }
2292 2293
	  }

2294 2295 2296
        }
        else
        {
2297
	  /* Adjust both rectangles for bottommost row */
2298
	  if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2299 2300 2301 2302
	  {
	    fillRect.bottom += 3;
	    r.bottom += 2;
	  }
2303

2304 2305
          TRACE("<top> item=%d, fill=(%s), edge=(%s)\n",
                iItem, wine_dbgstr_rect(&fillRect), wine_dbgstr_rect(&r));
2306 2307 2308

	  /* Clear interior */
	  SetBkColor(hdc, bkgnd);
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
2309
	  ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2310 2311 2312 2313

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

2314
	  /* Now erase the righthand corner and draw diagonal edge */
2315 2316 2317 2318 2319
	  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
2320
	  ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2321 2322 2323
	  r1.top++;
	  DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMRIGHT);

2324
	  /* Now erase the lefthand corner and draw diagonal edge */
2325 2326 2327 2328
	  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
2329
	  ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2330 2331
	  r1.top++;
	  DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPRIGHT);
2332
        }
2333 2334
      }
    }
2335

2336 2337
    TAB_DumpItemInternal(infoPtr, iItem);

2338
    /* This modifies r to be the text rectangle. */
2339
    TAB_DrawItemInterior(infoPtr, hdc, iItem, &r);
Francis Beaudet's avatar
Francis Beaudet committed
2340
  }
Alexandre Julliard's avatar
Alexandre Julliard committed
2341 2342
}

Francis Beaudet's avatar
Francis Beaudet committed
2343 2344 2345 2346 2347
/******************************************************************************
 * TAB_DrawBorder
 *
 * This method is used to draw the raised border around the tab control
 * "content" area.
2348
 */
2349
static void TAB_DrawBorder(const TAB_INFO *infoPtr, HDC hdc)
Alexandre Julliard's avatar
Alexandre Julliard committed
2350
{
Francis Beaudet's avatar
Francis Beaudet committed
2351
  RECT rect;
2352
  HTHEME theme = GetWindowTheme (infoPtr->hwnd);
Alexandre Julliard's avatar
Alexandre Julliard committed
2353

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

Francis Beaudet's avatar
Francis Beaudet committed
2356 2357 2358
  /*
   * Adjust for the style
   */
Gerard Patel's avatar
Gerard Patel committed
2359 2360

  if (infoPtr->uNumItem)
Francis Beaudet's avatar
Francis Beaudet committed
2361
  {
2362
    if ((infoPtr->dwStyle & TCS_BOTTOM) && !(infoPtr->dwStyle & TCS_VERTICAL))
2363
      rect.bottom -= infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2364
    else if((infoPtr->dwStyle & TCS_BOTTOM) && (infoPtr->dwStyle & TCS_VERTICAL))
2365
      rect.right  -= infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2366
    else if(infoPtr->dwStyle & TCS_VERTICAL)
2367
      rect.left   += infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
Gerard Patel's avatar
Gerard Patel committed
2368
    else /* not TCS_VERTICAL and not TCS_BOTTOM */
2369
      rect.top    += infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
Francis Beaudet's avatar
Francis Beaudet committed
2370
  }
Alexandre Julliard's avatar
Alexandre Julliard committed
2371

2372
  TRACE("border=(%s)\n", wine_dbgstr_rect(&rect));
2373

2374 2375 2376 2377
  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
2378 2379
}

Francis Beaudet's avatar
Francis Beaudet committed
2380 2381 2382 2383
/******************************************************************************
 * TAB_Refresh
 *
 * This method repaints the tab control..
2384
 */
2385
static void TAB_Refresh (const TAB_INFO *infoPtr, HDC hdc)
Alexandre Julliard's avatar
Alexandre Julliard committed
2386
{
Francis Beaudet's avatar
Francis Beaudet committed
2387 2388
  HFONT hOldFont;
  INT i;
Alexandre Julliard's avatar
Alexandre Julliard committed
2389

Francis Beaudet's avatar
Francis Beaudet committed
2390 2391
  if (!infoPtr->DoRedraw)
    return;
Alex Priem's avatar
Alex Priem committed
2392

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

2395
  if (infoPtr->dwStyle & TCS_BUTTONS)
Francis Beaudet's avatar
Francis Beaudet committed
2396
  {
2397
    for (i = 0; i < infoPtr->uNumItem; i++)
2398
      TAB_DrawItem (infoPtr, hdc, i);
Francis Beaudet's avatar
Francis Beaudet committed
2399
  }
2400 2401
  else
  {
2402
    /* Draw all the non selected item first */
2403
    for (i = 0; i < infoPtr->uNumItem; i++)
2404 2405
    {
      if (i != infoPtr->iSelected)
2406
	TAB_DrawItem (infoPtr, hdc, i);
2407
    }
Francis Beaudet's avatar
Francis Beaudet committed
2408

2409 2410
    /* Now, draw the border, draw it before the selected item
     * since the selected item overwrites part of the border. */
2411
    TAB_DrawBorder (infoPtr, hdc);
Francis Beaudet's avatar
Francis Beaudet committed
2412

2413
    /* Then, draw the selected item */
2414
    TAB_DrawItem (infoPtr, hdc, infoPtr->iSelected);
2415
  }
Francis Beaudet's avatar
Francis Beaudet committed
2416 2417

  SelectObject (hdc, hOldFont);
Alexandre Julliard's avatar
Alexandre Julliard committed
2418 2419
}

2420
static inline DWORD TAB_GetRowCount (const TAB_INFO *infoPtr)
2421
{
2422
  TRACE("(%p)\n", infoPtr);
2423 2424 2425
  return infoPtr->uNumRows;
}

2426
static inline LRESULT TAB_SetRedraw (TAB_INFO *infoPtr, BOOL doRedraw)
Alex Priem's avatar
Alex Priem committed
2427
{
2428
  infoPtr->DoRedraw = doRedraw;
Francis Beaudet's avatar
Francis Beaudet committed
2429
  return 0;
Alex Priem's avatar
Alex Priem committed
2430 2431
}

Francis Beaudet's avatar
Francis Beaudet committed
2432 2433 2434 2435 2436 2437 2438 2439
/******************************************************************************
 * 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
2440
{
Alexandre Julliard's avatar
Alexandre Julliard committed
2441
  INT iSelected = infoPtr->iSelected;
2442 2443
  INT iOrigLeftmostVisible = infoPtr->leftmostVisible;

2444 2445 2446
  if (iSelected < 0)
    return;

2447 2448
  /* set the items row to the bottommost row or topmost row depending on
   * style */
2449
  if ((infoPtr->uNumRows > 1) && !(infoPtr->dwStyle & TCS_BUTTONS))
2450
  {
2451
      TAB_ITEM *selected = TAB_GetItem(infoPtr, iSelected);
2452
      INT newselected;
2453 2454
      INT iTargetRow;

2455
      if(infoPtr->dwStyle & TCS_VERTICAL)
2456
        newselected = selected->rect.left;
2457
      else
2458
        newselected = selected->rect.top;
2459

Gerard Patel's avatar
Gerard Patel committed
2460 2461 2462
      /* the target row is always (number of rows - 1)
         as row 0 is furthest from the clientRect */
      iTargetRow = infoPtr->uNumRows - 1;
2463 2464 2465

      if (newselected != iTargetRow)
      {
2466
         UINT i;
2467
         if(infoPtr->dwStyle & TCS_VERTICAL)
2468 2469 2470 2471
         {
           for (i=0; i < infoPtr->uNumItem; i++)
           {
             /* move everything in the row of the selected item to the iTargetRow */
2472 2473 2474 2475
             TAB_ITEM *item = TAB_GetItem(infoPtr, i);

             if (item->rect.left == newselected )
                 item->rect.left = iTargetRow;
2476 2477
             else
             {
2478 2479
               if (item->rect.left > newselected)
                 item->rect.left-=1;
2480 2481 2482 2483 2484 2485 2486
             }
           }
         }
         else
         {
           for (i=0; i < infoPtr->uNumItem; i++)
           {
2487 2488 2489 2490
             TAB_ITEM *item = TAB_GetItem(infoPtr, i);

             if (item->rect.top == newselected )
                 item->rect.top = iTargetRow;
2491 2492
             else
             {
2493 2494
               if (item->rect.top > newselected)
                 item->rect.top-=1;
2495 2496 2497
             }
          }
        }
2498
        TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
2499 2500 2501
      }
  }

Francis Beaudet's avatar
Francis Beaudet committed
2502 2503 2504 2505
  /*
   * Do the trivial cases first.
   */
  if ( (!infoPtr->needsScrolling) ||
2506
       (infoPtr->hwndUpDown==0) || (infoPtr->dwStyle & TCS_VERTICAL))
Francis Beaudet's avatar
Francis Beaudet committed
2507 2508
    return;

Alexandre Julliard's avatar
Alexandre Julliard committed
2509
  if (infoPtr->leftmostVisible >= iSelected)
Francis Beaudet's avatar
Francis Beaudet committed
2510
  {
2511
    infoPtr->leftmostVisible = iSelected;
Francis Beaudet's avatar
Francis Beaudet committed
2512
  }
Alexandre Julliard's avatar
Alexandre Julliard committed
2513
  else
Francis Beaudet's avatar
Francis Beaudet committed
2514
  {
2515
     TAB_ITEM *selected = TAB_GetItem(infoPtr, iSelected);
Alexandre Julliard's avatar
Alexandre Julliard committed
2516
     RECT r;
2517 2518
     INT width;
     UINT i;
2519 2520

     /* Calculate the part of the client area that is visible */
2521
     GetClientRect(infoPtr->hwnd, &r);
Alexandre Julliard's avatar
Alexandre Julliard committed
2522 2523 2524 2525 2526
     width = r.right;

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

2527 2528
     if ((selected->rect.right -
          selected->rect.left) >= width )
Alexandre Julliard's avatar
Alexandre Julliard committed
2529 2530 2531 2532 2533 2534 2535 2536 2537 2538
     {
        /* 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++)
        {
2539
           if ((selected->rect.right - TAB_GetItem(infoPtr, i)->rect.left) < width)
Alexandre Julliard's avatar
Alexandre Julliard committed
2540 2541 2542 2543
              break;
        }
        infoPtr->leftmostVisible = i;
     }
Francis Beaudet's avatar
Francis Beaudet committed
2544
  }
Alexandre Julliard's avatar
Alexandre Julliard committed
2545

2546
  if (infoPtr->leftmostVisible != iOrigLeftmostVisible)
2547
    TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
2548

2549
  SendMessageW(infoPtr->hwndUpDown, UDM_SETPOS, 0,
Alexandre Julliard's avatar
Alexandre Julliard committed
2550
               MAKELONG(infoPtr->leftmostVisible, 0));
Francis Beaudet's avatar
Francis Beaudet committed
2551
}
Alexandre Julliard's avatar
Alexandre Julliard committed
2552

Francis Beaudet's avatar
Francis Beaudet committed
2553 2554 2555 2556 2557 2558 2559
/******************************************************************************
 * 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
 */
2560
static void TAB_InvalidateTabArea(const TAB_INFO *infoPtr)
Francis Beaudet's avatar
Francis Beaudet committed
2561
{
2562
  RECT clientRect, rInvalidate, rAdjClient;
Gerard Patel's avatar
Gerard Patel committed
2563
  INT lastRow = infoPtr->uNumRows - 1;
2564
  RECT rect;
Gerard Patel's avatar
Gerard Patel committed
2565 2566

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

2568
  GetClientRect(infoPtr->hwnd, &clientRect);
2569
  rInvalidate = clientRect;
2570 2571
  rAdjClient = clientRect;

2572
  TAB_AdjustRect(infoPtr, 0, &rAdjClient);
Alexandre Julliard's avatar
Alexandre Julliard committed
2573

2574
  TAB_InternalGetItemRect(infoPtr, infoPtr->uNumItem-1 , &rect, NULL);
2575
  if ((infoPtr->dwStyle & TCS_BOTTOM) && (infoPtr->dwStyle & TCS_VERTICAL))
2576
  {
2577 2578 2579
    rInvalidate.left = rAdjClient.right;
    if (infoPtr->uNumRows == 1)
      rInvalidate.bottom = clientRect.top + rect.bottom + 2 * SELECTED_TAB_OFFSET;
2580
  }
2581
  else if(infoPtr->dwStyle & TCS_VERTICAL)
2582
  {
2583 2584 2585
    rInvalidate.right = rAdjClient.left;
    if (infoPtr->uNumRows == 1)
      rInvalidate.bottom = clientRect.top + rect.bottom + 2 * SELECTED_TAB_OFFSET;
2586
  }
2587
  else if (infoPtr->dwStyle & TCS_BOTTOM)
Francis Beaudet's avatar
Francis Beaudet committed
2588
  {
2589 2590 2591
    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
2592
  }
2593
  else 
Francis Beaudet's avatar
Francis Beaudet committed
2594
  {
2595 2596 2597
    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
2598
  }
2599 2600
  
  /* Punch out the updown control */
2601 2602
  if (infoPtr->needsScrolling && (rInvalidate.right > 0)) {
    RECT r;
2603
    GetClientRect(infoPtr->hwndUpDown, &r);
2604 2605 2606 2607
    if (rInvalidate.right > clientRect.right - r.left)
      rInvalidate.right = rInvalidate.right - (r.right - r.left);
    else
      rInvalidate.right = clientRect.right - r.left;
2608
  }
2609 2610 2611

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

2612
  InvalidateRect(infoPtr->hwnd, &rInvalidate, TRUE);
Francis Beaudet's avatar
Francis Beaudet committed
2613
}
Alexandre Julliard's avatar
Alexandre Julliard committed
2614

2615
static inline LRESULT TAB_Paint (TAB_INFO *infoPtr, HDC hdcPaint)
Francis Beaudet's avatar
Francis Beaudet committed
2616 2617 2618
{
  HDC hdc;
  PAINTSTRUCT ps;
2619

2620 2621 2622
  if (hdcPaint)
    hdc = hdcPaint;
  else
2623
  {
2624
    hdc = BeginPaint (infoPtr->hwnd, &ps);
2625
    TRACE("erase %d, rect=(%s)\n", ps.fErase, wine_dbgstr_rect(&ps.rcPaint));
2626
  }
2627

2628 2629 2630 2631
  TAB_Refresh (infoPtr, hdc);

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

Francis Beaudet's avatar
Francis Beaudet committed
2633 2634
  return 0;
}
Alexandre Julliard's avatar
Alexandre Julliard committed
2635

Francis Beaudet's avatar
Francis Beaudet committed
2636
static LRESULT
2637
TAB_InsertItemT (TAB_INFO *infoPtr, INT iItem, const TCITEMW *pti, BOOL bUnicode)
2638
{
2639
  TAB_ITEM *item;
Francis Beaudet's avatar
Francis Beaudet committed
2640
  RECT rect;
2641

2642
  GetClientRect (infoPtr->hwnd, &rect);
2643
  TRACE("Rect: %p %s\n", infoPtr->hwnd, wine_dbgstr_rect(&rect));
2644

Francis Beaudet's avatar
Francis Beaudet committed
2645 2646 2647
  if (iItem < 0) return -1;
  if (iItem > infoPtr->uNumItem)
    iItem = infoPtr->uNumItem;
2648

Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
2649
  TAB_DumpItemExternalT(pti, iItem, bUnicode);
2650

2651
  if (!(item = Alloc(TAB_ITEM_SIZE(infoPtr)))) return FALSE;
2652 2653 2654 2655
  if (DPA_InsertPtr(infoPtr->items, iItem, item) == -1)
  {
      Free(item);
      return FALSE;
Francis Beaudet's avatar
Francis Beaudet committed
2656
  }
2657

2658 2659 2660
  if (infoPtr->uNumItem == 0)
      infoPtr->iSelected = 0;
  else if (iItem <= infoPtr->iSelected)
2661 2662
      infoPtr->iSelected++;

2663
  infoPtr->uNumItem++;
2664

2665
  item->pszText = NULL;
2666
  if (pti->mask & TCIF_TEXT)
2667 2668
  {
    if (bUnicode)
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
2669
      Str_SetPtrW (&item->pszText, pti->pszText);
2670
    else
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
2671
      Str_SetPtrAtoW (&item->pszText, (LPSTR)pti->pszText);
2672
  }
2673

Francis Beaudet's avatar
Francis Beaudet committed
2674
  if (pti->mask & TCIF_IMAGE)
2675 2676 2677
    item->iImage = pti->iImage;
  else
    item->iImage = -1;
2678

Francis Beaudet's avatar
Francis Beaudet committed
2679
  if (pti->mask & TCIF_PARAM)
2680
    memcpy(item->extra, &pti->lParam, EXTRA_ITEM_SIZE(infoPtr));
2681
  else
2682 2683
    memset(item->extra, 0, EXTRA_ITEM_SIZE(infoPtr));

2684
  TAB_SetItemBounds(infoPtr);
2685
  if (infoPtr->uNumItem > 1)
2686
    TAB_InvalidateTabArea(infoPtr);
2687
  else
2688
    InvalidateRect(infoPtr->hwnd, NULL, TRUE);
2689

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

2693 2694 2695 2696
  /* 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
2697
  return iItem;
Alexandre Julliard's avatar
Alexandre Julliard committed
2698 2699
}

2700
static LRESULT
2701
TAB_SetItemSize (TAB_INFO *infoPtr, INT cx, INT cy)
2702 2703
{
  LONG lResult = 0;
2704
  BOOL bNeedPaint = FALSE;
2705

2706 2707 2708
  lResult = MAKELONG(infoPtr->tabWidth, infoPtr->tabHeight);

  /* UNDOCUMENTED: If requested Width or Height is 0 this means that program wants to use auto size. */
2709
  if (infoPtr->dwStyle & TCS_FIXEDWIDTH && (infoPtr->tabWidth != cx))
2710
  {
2711
    infoPtr->tabWidth = cx;
2712
    bNeedPaint = TRUE;
2713 2714
  }

2715
  if (infoPtr->tabHeight != cy)
2716
  {
2717 2718
    if ((infoPtr->fHeightSet = (cy != 0)))
      infoPtr->tabHeight = cy;
2719 2720 2721 2722 2723 2724 2725 2726

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

  if (bNeedPaint)
2727
  {
2728 2729
    TAB_SetItemBounds(infoPtr);
    RedrawWindow(infoPtr->hwnd, NULL, NULL, RDW_ERASE | RDW_INVALIDATE | RDW_UPDATENOW);
2730
  }
2731

2732 2733 2734
  return lResult;
}

2735
static inline LRESULT TAB_SetMinTabWidth (TAB_INFO *infoPtr, INT cx)
2736
{
2737 2738 2739
  INT oldcx = 0;

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

2741 2742 2743 2744
  if (infoPtr->tabMinWidth < 0)
    oldcx = DEFAULT_MIN_TAB_WIDTH;
  else
    oldcx = infoPtr->tabMinWidth;
2745
  infoPtr->tabMinWidth = cx;
2746
  TAB_SetItemBounds(infoPtr);
2747 2748 2749
  return oldcx;
}

2750 2751
static inline LRESULT 
TAB_HighlightItem (TAB_INFO *infoPtr, INT iItem, BOOL fHighlight)
2752
{
2753
  LPDWORD lpState;
2754 2755
  DWORD oldState;
  RECT r;
2756

2757 2758
  TRACE("(%p,%d,%s)\n", infoPtr, iItem, fHighlight ? "true" : "false");

2759
  if (iItem < 0 || iItem >= infoPtr->uNumItem)
2760
    return FALSE;
2761

2762
  lpState = &TAB_GetItem(infoPtr, iItem)->dwState;
2763
  oldState = *lpState;
2764 2765 2766 2767 2768

  if (fHighlight)
    *lpState |= TCIS_HIGHLIGHTED;
  else
    *lpState &= ~TCIS_HIGHLIGHTED;
2769

2770 2771 2772
  if ((oldState != *lpState) && TAB_InternalGetItemRect (infoPtr, iItem, &r, NULL))
    InvalidateRect (infoPtr->hwnd, &r, TRUE);

2773 2774 2775
  return TRUE;
}

2776
static LRESULT
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
2777
TAB_SetItemT (TAB_INFO *infoPtr, INT iItem, LPTCITEMW tabItem, BOOL bUnicode)
2778
{
2779
  TAB_ITEM *wineItem;
2780

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

2783 2784
  if (iItem < 0 || iItem >= infoPtr->uNumItem)
    return FALSE;
2785

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

2788
  wineItem = TAB_GetItem(infoPtr, iItem);
2789

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

2793
  if (tabItem->mask & TCIF_PARAM)
2794
    memcpy(wineItem->extra, &tabItem->lParam, infoPtr->cbInfo);
2795

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

2799
  if (tabItem->mask & TCIF_STATE)
2800 2801
    wineItem->dwState = (wineItem->dwState & ~tabItem->dwStateMask) |
                        ( tabItem->dwState &  tabItem->dwStateMask);
2802

2803
  if (tabItem->mask & TCIF_TEXT)
2804
  {
2805 2806
    Free(wineItem->pszText);
    wineItem->pszText = NULL;
2807
    if (bUnicode)
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
2808
      Str_SetPtrW(&wineItem->pszText, tabItem->pszText);
2809
    else
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
2810
      Str_SetPtrAtoW(&wineItem->pszText, (LPSTR)tabItem->pszText);
2811 2812
  }

2813
  /* Update and repaint tabs */
2814 2815
  TAB_SetItemBounds(infoPtr);
  TAB_InvalidateTabArea(infoPtr);
2816

Francis Beaudet's avatar
Francis Beaudet committed
2817
  return TRUE;
2818 2819
}

2820
static inline LRESULT TAB_GetItemCount (const TAB_INFO *infoPtr)
2821
{
2822 2823
  TRACE("\n");
  return infoPtr->uNumItem;
2824 2825 2826
}


2827
static LRESULT
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
2828
TAB_GetItemT (TAB_INFO *infoPtr, INT iItem, LPTCITEMW tabItem, BOOL bUnicode)
2829
{
2830
  TAB_ITEM *wineItem;
2831

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

2834 2835
  if (!tabItem) return FALSE;

2836
  if (iItem < 0 || iItem >= infoPtr->uNumItem)
2837 2838 2839 2840 2841
  {
    /* init requested fields */
    if (tabItem->mask & TCIF_IMAGE) tabItem->iImage  = 0;
    if (tabItem->mask & TCIF_PARAM) tabItem->lParam  = 0;
    if (tabItem->mask & TCIF_STATE) tabItem->dwState = 0;
2842
    return FALSE;
2843
  }
2844

2845
  wineItem = TAB_GetItem(infoPtr, iItem);
2846 2847 2848 2849 2850

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

  if (tabItem->mask & TCIF_PARAM)
2851
    memcpy(&tabItem->lParam, wineItem->extra, infoPtr->cbInfo);
2852 2853 2854 2855 2856

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

  if (tabItem->mask & TCIF_STATE)
2857
    tabItem->dwState = wineItem->dwState & tabItem->dwStateMask;
2858 2859

  if (tabItem->mask & TCIF_TEXT)
2860 2861
  {
    if (bUnicode)
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
2862
      Str_GetPtrW (wineItem->pszText, tabItem->pszText, tabItem->cchTextMax);
2863
    else
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
2864
      Str_GetPtrWtoA (wineItem->pszText, (LPSTR)tabItem->pszText, tabItem->cchTextMax);
2865
  }
2866

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

2869 2870 2871 2872
  return TRUE;
}


2873
static LRESULT TAB_DeleteItem (TAB_INFO *infoPtr, INT iItem)
2874
{
2875
    TAB_ITEM *item;
2876

2877 2878
    TRACE("(%p, %d)\n", infoPtr, iItem);

2879 2880 2881 2882 2883 2884 2885
    if (iItem < 0 || iItem >= infoPtr->uNumItem) return FALSE;

    item = TAB_GetItem(infoPtr, iItem);
    Free(item->pszText);
    Free(item);
    infoPtr->uNumItem--;
    DPA_DeletePtr(infoPtr->items, iItem);
2886

2887
    TAB_InvalidateTabArea(infoPtr);
2888

2889 2890 2891
    if (infoPtr->uNumItem == 0)
    {
        if (infoPtr->iHotTracked >= 0)
2892
        {
2893 2894
            KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
            infoPtr->iHotTracked = -1;
2895 2896
        }

2897 2898 2899 2900 2901 2902 2903 2904 2905 2906
        infoPtr->iSelected = -1;
    }
    else
    {
        if (iItem <= infoPtr->iHotTracked)
        {
            /* When tabs move left/up, the hot track item may change */
            FIXME("Recalc hot track\n");
        }
    }
2907

2908 2909 2910 2911 2912
    /* adjust the selected index */
    if (iItem == infoPtr->iSelected)
        infoPtr->iSelected = -1;
    else if (iItem < infoPtr->iSelected)
        infoPtr->iSelected--;
2913

2914 2915
    /* reposition and repaint tabs */
    TAB_SetItemBounds(infoPtr);
2916

2917
    return TRUE;
2918
}
Alex Priem's avatar
Alex Priem committed
2919

2920
static inline LRESULT TAB_DeleteAllItems (TAB_INFO *infoPtr)
2921
{
2922
    TRACE("(%p)\n", infoPtr);
2923
    while (infoPtr->uNumItem)
2924
      TAB_DeleteItem (infoPtr, 0);
2925
    return TRUE;
2926 2927 2928
}


2929
static inline LRESULT TAB_GetFont (const TAB_INFO *infoPtr)
2930
{
2931
  TRACE("(%p) returning %p\n", infoPtr, infoPtr->hFont);
2932 2933 2934
  return (LRESULT)infoPtr->hFont;
}

2935
static inline LRESULT TAB_SetFont (TAB_INFO *infoPtr, HFONT hNewFont)
2936
{
2937
  TRACE("(%p,%p)\n", infoPtr, hNewFont);
2938

2939
  infoPtr->hFont = hNewFont;
2940

2941
  TAB_SetItemBounds(infoPtr);
2942

2943
  TAB_InvalidateTabArea(infoPtr);
2944

Francis Beaudet's avatar
Francis Beaudet committed
2945
  return 0;
2946 2947 2948
}


2949
static inline LRESULT TAB_GetImageList (const TAB_INFO *infoPtr)
2950
{
2951
  TRACE("\n");
2952 2953 2954
  return (LRESULT)infoPtr->himl;
}

2955
static inline LRESULT TAB_SetImageList (TAB_INFO *infoPtr, HIMAGELIST himlNew)
2956
{
2957
    HIMAGELIST himlPrev = infoPtr->himl;
2958
    TRACE("himl=%p\n", himlNew);
2959
    infoPtr->himl = himlNew;
2960 2961
    TAB_SetItemBounds(infoPtr);
    InvalidateRect(infoPtr->hwnd, NULL, TRUE);
2962 2963 2964
    return (LRESULT)himlPrev;
}

2965
static inline LRESULT TAB_GetUnicodeFormat (const TAB_INFO *infoPtr)
2966
{
2967
    TRACE("(%p)\n", infoPtr);
2968 2969 2970
    return infoPtr->bUnicode;
}

2971
static inline LRESULT TAB_SetUnicodeFormat (TAB_INFO *infoPtr, BOOL bUnicode)
2972 2973 2974
{
    BOOL bTemp = infoPtr->bUnicode;

2975
    TRACE("(%p %d)\n", infoPtr, bUnicode);
2976
    infoPtr->bUnicode = bUnicode;
2977 2978 2979

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

2981
static inline LRESULT TAB_Size (TAB_INFO *infoPtr)
Alex Priem's avatar
Alex Priem committed
2982
{
2983 2984 2985 2986 2987
/* 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.

2988 2989 2990
  RECT parent_rect;
  HWND parent;
  UINT uPosFlags,cx,cy;
Alex Priem's avatar
Alex Priem committed
2991 2992 2993

  uPosFlags=0;
  if (!wParam) {
Francis Beaudet's avatar
Francis Beaudet committed
2994 2995 2996 2997
    parent = GetParent (hwnd);
    GetClientRect(parent, &parent_rect);
    cx=LOWORD (lParam);
    cy=HIWORD (lParam);
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
2998
    if (GetWindowLongW(hwnd, GWL_STYLE) & CCS_NORESIZE)
Alex Priem's avatar
Alex Priem committed
2999 3000
        uPosFlags |= (SWP_NOSIZE | SWP_NOMOVE);

Francis Beaudet's avatar
Francis Beaudet committed
3001
    SetWindowPos (hwnd, 0, parent_rect.left, parent_rect.top,
Alex Priem's avatar
Alex Priem committed
3002
            cx, cy, uPosFlags | SWP_NOZORDER);
3003
  } else {
Andreas Mohr's avatar
Andreas Mohr committed
3004
    FIXME("WM_SIZE flag %x %lx not handled\n", wParam, lParam);
3005
  } */
Alex Priem's avatar
Alex Priem committed
3006

3007
  /* Recompute the size/position of the tabs. */
3008
  TAB_SetItemBounds (infoPtr);
Francis Beaudet's avatar
Francis Beaudet committed
3009

3010
  /* Force a repaint of the control. */
3011
  InvalidateRect(infoPtr->hwnd, NULL, TRUE);
Alex Priem's avatar
Alex Priem committed
3012 3013 3014 3015 3016

  return 0;
}


3017
static LRESULT TAB_Create (HWND hwnd, LPARAM lParam)
Alexandre Julliard's avatar
Alexandre Julliard committed
3018
{
Francis Beaudet's avatar
Francis Beaudet committed
3019
  TAB_INFO *infoPtr;
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
3020
  TEXTMETRICW fontMetrics;
3021 3022
  HDC hdc;
  HFONT hOldFont;
Alexandre Julliard's avatar
Alexandre Julliard committed
3023
  DWORD dwStyle;
Alexandre Julliard's avatar
Alexandre Julliard committed
3024

3025
  infoPtr = Alloc (sizeof(TAB_INFO));
Francis Beaudet's avatar
Francis Beaudet committed
3026

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

3029
  infoPtr->hwnd            = hwnd;
3030
  infoPtr->hwndNotify      = ((LPCREATESTRUCTW)lParam)->hwndParent;
Francis Beaudet's avatar
Francis Beaudet committed
3031
  infoPtr->uNumItem        = 0;
3032
  infoPtr->uNumRows        = 0;
3033 3034
  infoPtr->uHItemPadding   = 6;
  infoPtr->uVItemPadding   = 3;
3035 3036
  infoPtr->uHItemPadding_s = 6;
  infoPtr->uVItemPadding_s = 3;
Francis Beaudet's avatar
Francis Beaudet committed
3037
  infoPtr->hFont           = 0;
3038
  infoPtr->items           = DPA_Create(8);
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
3039
  infoPtr->hcurArrow       = LoadCursorW (0, (LPWSTR)IDC_ARROW);
3040
  infoPtr->iSelected       = -1;
3041
  infoPtr->iHotTracked     = -1;
3042
  infoPtr->uFocus          = -1;
Francis Beaudet's avatar
Francis Beaudet committed
3043 3044 3045 3046 3047
  infoPtr->hwndToolTip     = 0;
  infoPtr->DoRedraw        = TRUE;
  infoPtr->needsScrolling  = FALSE;
  infoPtr->hwndUpDown      = 0;
  infoPtr->leftmostVisible = 0;
3048
  infoPtr->fHeightSet      = FALSE;
3049 3050
  infoPtr->bUnicode        = IsWindowUnicode (hwnd);
  infoPtr->cbInfo          = sizeof(LPARAM);
Alexandre Julliard's avatar
Alexandre Julliard committed
3051

3052
  TRACE("Created tab control, hwnd [%p]\n", hwnd);
3053 3054 3055

  /* 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
3056
     order for paint to work correctly. This follows windows behaviour. */
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
3057 3058
  dwStyle = GetWindowLongW(hwnd, GWL_STYLE);
  SetWindowLongW(hwnd, GWL_STYLE, dwStyle|WS_CLIPSIBLINGS);
Alexandre Julliard's avatar
Alexandre Julliard committed
3059

3060
  infoPtr->dwStyle = dwStyle | WS_CLIPSIBLINGS;
3061 3062
  infoPtr->exStyle = (dwStyle & TCS_FLATBUTTONS) ? TCS_EX_FLATSEPARATORS : 0;

3063
  if (infoPtr->dwStyle & TCS_TOOLTIPS) {
Alex Priem's avatar
Alex Priem committed
3064 3065
    /* Create tooltip control */
    infoPtr->hwndToolTip =
3066
      CreateWindowExW (0, TOOLTIPS_CLASSW, NULL, WS_POPUP,
Francis Beaudet's avatar
Francis Beaudet committed
3067 3068 3069
		       CW_USEDEFAULT, CW_USEDEFAULT,
		       CW_USEDEFAULT, CW_USEDEFAULT,
		       hwnd, 0, 0, 0);
3070

Alex Priem's avatar
Alex Priem committed
3071 3072
    /* Send NM_TOOLTIPSCREATED notification */
    if (infoPtr->hwndToolTip) {
Francis Beaudet's avatar
Francis Beaudet committed
3073
      NMTOOLTIPSCREATED nmttc;
3074

Francis Beaudet's avatar
Francis Beaudet committed
3075
      nmttc.hdr.hwndFrom = hwnd;
3076
      nmttc.hdr.idFrom = GetWindowLongPtrW(hwnd, GWLP_ID);
Francis Beaudet's avatar
Francis Beaudet committed
3077 3078
      nmttc.hdr.code = NM_TOOLTIPSCREATED;
      nmttc.hwndToolTips = infoPtr->hwndToolTip;
3079

3080
      SendMessageW (infoPtr->hwndNotify, WM_NOTIFY,
3081
                    GetWindowLongPtrW(hwnd, GWLP_ID), (LPARAM)&nmttc);
Alex Priem's avatar
Alex Priem committed
3082
    }
3083 3084
  }

3085 3086
  OpenThemeData (infoPtr->hwnd, themeClass);
  
3087 3088 3089 3090
  /*
   * We need to get text information so we need a DC and we need to select
   * a font.
   */
3091
  hdc = GetDC(hwnd);
3092 3093
  hOldFont = SelectObject (hdc, GetStockObject (SYSTEM_FONT));

3094
  /* Use the system font to determine the initial height of a tab. */
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
3095
  GetTextMetricsW(hdc, &fontMetrics);
3096 3097

  /*
3098 3099
   * Make sure there is enough space for the letters + growing the
   * selected item + extra space for the selected item.
3100
   */
3101
  infoPtr->tabHeight = fontMetrics.tmHeight + SELECTED_TAB_OFFSET +
3102
	               ((infoPtr->dwStyle & TCS_BUTTONS) ? 2 : 1) *
3103
                        infoPtr->uVItemPadding;
3104

3105
  /* Initialize the width of a tab. */
3106
  if (infoPtr->dwStyle & TCS_FIXEDWIDTH)
3107
    infoPtr->tabWidth = GetDeviceCaps(hdc, LOGPIXELSX);
3108 3109

  infoPtr->tabMinWidth = -1;
3110

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

3113 3114 3115
  SelectObject (hdc, hOldFont);
  ReleaseDC(hwnd, hdc);

Francis Beaudet's avatar
Francis Beaudet committed
3116
  return 0;
Alexandre Julliard's avatar
Alexandre Julliard committed
3117 3118 3119
}

static LRESULT
3120
TAB_Destroy (TAB_INFO *infoPtr)
Alexandre Julliard's avatar
Alexandre Julliard committed
3121
{
3122
  INT iItem;
Alexandre Julliard's avatar
Alexandre Julliard committed
3123

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

3126 3127 3128 3129 3130 3131 3132 3133 3134
  for (iItem = infoPtr->uNumItem - 1; iItem >= 0; iItem--)
  {
      TAB_ITEM *tab = TAB_GetItem(infoPtr, iItem);

      DPA_DeletePtr(infoPtr->items, iItem);
      infoPtr->uNumItem--;

      Free(tab->pszText);
      Free(tab);
Francis Beaudet's avatar
Francis Beaudet committed
3135
  }
3136 3137
  DPA_Destroy(infoPtr->items);
  infoPtr->items = NULL;
3138 3139

  if (infoPtr->hwndToolTip)
Francis Beaudet's avatar
Francis Beaudet committed
3140
    DestroyWindow (infoPtr->hwndToolTip);
3141

Francis Beaudet's avatar
Francis Beaudet committed
3142 3143
  if (infoPtr->hwndUpDown)
    DestroyWindow(infoPtr->hwndUpDown);
Alex Priem's avatar
Alex Priem committed
3144

3145
  if (infoPtr->iHotTracked >= 0)
3146
    KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
3147

3148
  CloseThemeData (GetWindowTheme (infoPtr->hwnd));
3149

3150
  Free (infoPtr);
Francis Beaudet's avatar
Francis Beaudet committed
3151
  return 0;
Alexandre Julliard's avatar
Alexandre Julliard committed
3152 3153
}

3154
/* update theme after a WM_THEMECHANGED message */
3155
static LRESULT theme_changed(const TAB_INFO *infoPtr)
3156 3157 3158 3159 3160 3161 3162
{
    HTHEME theme = GetWindowTheme (infoPtr->hwnd);
    CloseThemeData (theme);
    OpenThemeData (infoPtr->hwnd, themeClass);
    return 0;
}

3163
static LRESULT TAB_NCCalcSize(WPARAM wParam)
3164 3165 3166 3167 3168 3169
{
  if (!wParam)
    return 0;
  return WVR_ALIGNTOP;
}

3170 3171
static inline LRESULT
TAB_SetItemExtra (TAB_INFO *infoPtr, INT cbInfo)
3172
{
3173 3174
  TRACE("(%p %d)\n", infoPtr, cbInfo);

3175
  if (cbInfo < 0 || infoPtr->uNumItem) return FALSE;
3176 3177 3178 3179 3180

  infoPtr->cbInfo = cbInfo;
  return TRUE;
}

3181 3182
static LRESULT TAB_RemoveImage (TAB_INFO *infoPtr, INT image)
{
3183 3184
  TRACE("%p %d\n", infoPtr, image);

3185 3186 3187 3188 3189 3190 3191 3192
  if (ImageList_Remove (infoPtr->himl, image))
  {
    INT i, *idx;
    RECT r;

    /* shift indices, repaint items if needed */
    for (i = 0; i < infoPtr->uNumItem; i++)
    {
3193
      idx = &TAB_GetItem(infoPtr, i)->iImage;
3194 3195 3196 3197 3198 3199 3200 3201 3202 3203 3204 3205 3206 3207 3208 3209 3210
      if (*idx >= image)
      {
        if (*idx == image)
          *idx = -1;
        else
          (*idx)--;

        /* repaint item */
        if (TAB_InternalGetItemRect (infoPtr, i, &r, NULL))
          InvalidateRect (infoPtr->hwnd, &r, TRUE);
      }
    }
  }

  return 0;
}

3211 3212 3213 3214 3215 3216 3217 3218 3219 3220 3221 3222 3223 3224 3225 3226 3227 3228 3229 3230 3231 3232 3233 3234 3235 3236 3237 3238
static LRESULT
TAB_SetExtendedStyle (TAB_INFO *infoPtr, DWORD exMask, DWORD exStyle)
{
  DWORD prevstyle = infoPtr->exStyle;

  /* zero mask means all styles */
  if (exMask == 0) exMask = ~0;

  if (exMask & TCS_EX_REGISTERDROP)
  {
    FIXME("TCS_EX_REGISTERDROP style unimplemented\n");
    exMask  &= ~TCS_EX_REGISTERDROP;
    exStyle &= ~TCS_EX_REGISTERDROP;
  }

  if (exMask & TCS_EX_FLATSEPARATORS)
  {
    if ((prevstyle ^ exStyle) & TCS_EX_FLATSEPARATORS)
    {
        infoPtr->exStyle ^= TCS_EX_FLATSEPARATORS;
        TAB_InvalidateTabArea(infoPtr);
    }
  }

  return prevstyle;
}

static inline LRESULT
3239
TAB_GetExtendedStyle (const TAB_INFO *infoPtr)
3240 3241 3242 3243
{
  return infoPtr->exStyle;
}

3244 3245 3246 3247 3248 3249
static LRESULT
TAB_DeselectAll (TAB_INFO *infoPtr, BOOL excludesel)
{
  BOOL paint = FALSE;
  INT i, selected = infoPtr->iSelected;

3250 3251
  TRACE("(%p, %d)\n", infoPtr, excludesel);

3252
  if (!(infoPtr->dwStyle & TCS_BUTTONS))
3253 3254 3255 3256 3257 3258 3259 3260 3261 3262 3263 3264 3265 3266 3267 3268 3269 3270 3271 3272 3273 3274 3275 3276 3277
    return 0;

  for (i = 0; i < infoPtr->uNumItem; i++)
  {
    if ((TAB_GetItem(infoPtr, i)->dwState & TCIS_BUTTONPRESSED) &&
        (selected != i))
    {
      TAB_GetItem(infoPtr, i)->dwState &= ~TCIS_BUTTONPRESSED;
      paint = TRUE;
    }
  }

  if (!excludesel && (selected != -1))
  {
    TAB_GetItem(infoPtr, selected)->dwState &= ~TCIS_BUTTONPRESSED;
    infoPtr->iSelected = -1;
    paint = TRUE;
  }

  if (paint)
    TAB_InvalidateTabArea (infoPtr);

  return 0;
}

3278 3279 3280 3281 3282 3283 3284 3285 3286 3287 3288 3289 3290 3291 3292 3293 3294 3295 3296 3297 3298 3299 3300 3301 3302 3303 3304 3305
/***
 * DESCRIPTION:
 * Processes WM_STYLECHANGED messages.
 *
 * PARAMETER(S):
 * [I] infoPtr : valid pointer to the tab data structure
 * [I] wStyleType : window style type (normal or extended)
 * [I] lpss : window style information
 *
 * RETURN:
 * Zero
 */
static INT TAB_StyleChanged(TAB_INFO *infoPtr, WPARAM wStyleType,
                            const STYLESTRUCT *lpss)
{
    TRACE("(styletype=%lx, styleOld=0x%08x, styleNew=0x%08x)\n",
          wStyleType, lpss->styleOld, lpss->styleNew);

    if (wStyleType != GWL_STYLE) return 0;

    infoPtr->dwStyle = lpss->styleNew;

    TAB_SetItemBounds (infoPtr);
    InvalidateRect(infoPtr->hwnd, NULL, TRUE);

    return 0;
}

3306
static LRESULT WINAPI
3307
TAB_WindowProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
Alexandre Julliard's avatar
Alexandre Julliard committed
3308
{
3309
    TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
3310

3311
    TRACE("hwnd=%p msg=%x wParam=%lx lParam=%lx\n", hwnd, uMsg, wParam, lParam);
3312
    if (!infoPtr && (uMsg != WM_CREATE))
3313
      return DefWindowProcW (hwnd, uMsg, wParam, lParam);
3314

Alexandre Julliard's avatar
Alexandre Julliard committed
3315 3316
    switch (uMsg)
    {
Francis Beaudet's avatar
Francis Beaudet committed
3317
    case TCM_GETIMAGELIST:
3318
      return TAB_GetImageList (infoPtr);
3319

Francis Beaudet's avatar
Francis Beaudet committed
3320
    case TCM_SETIMAGELIST:
3321
      return TAB_SetImageList (infoPtr, (HIMAGELIST)lParam);
3322

Francis Beaudet's avatar
Francis Beaudet committed
3323
    case TCM_GETITEMCOUNT:
3324
      return TAB_GetItemCount (infoPtr);
3325

Francis Beaudet's avatar
Francis Beaudet committed
3326 3327
    case TCM_GETITEMA:
    case TCM_GETITEMW:
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
3328
      return TAB_GetItemT (infoPtr, (INT)wParam, (LPTCITEMW)lParam, uMsg == TCM_GETITEMW);
3329

Francis Beaudet's avatar
Francis Beaudet committed
3330 3331
    case TCM_SETITEMA:
    case TCM_SETITEMW:
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
3332
      return TAB_SetItemT (infoPtr, (INT)wParam, (LPTCITEMW)lParam, uMsg == TCM_SETITEMW);
3333

Francis Beaudet's avatar
Francis Beaudet committed
3334
    case TCM_DELETEITEM:
3335
      return TAB_DeleteItem (infoPtr, (INT)wParam);
3336

Francis Beaudet's avatar
Francis Beaudet committed
3337
    case TCM_DELETEALLITEMS:
3338
     return TAB_DeleteAllItems (infoPtr);
3339

Francis Beaudet's avatar
Francis Beaudet committed
3340
    case TCM_GETITEMRECT:
3341
     return TAB_GetItemRect (infoPtr, (INT)wParam, (LPRECT)lParam);
3342

Francis Beaudet's avatar
Francis Beaudet committed
3343
    case TCM_GETCURSEL:
3344
      return TAB_GetCurSel (infoPtr);
3345

Francis Beaudet's avatar
Francis Beaudet committed
3346
    case TCM_HITTEST:
3347
      return TAB_HitTest (infoPtr, (LPTCHITTESTINFO)lParam);
3348

Francis Beaudet's avatar
Francis Beaudet committed
3349
    case TCM_SETCURSEL:
3350
      return TAB_SetCurSel (infoPtr, (INT)wParam);
3351

Francis Beaudet's avatar
Francis Beaudet committed
3352 3353
    case TCM_INSERTITEMA:
    case TCM_INSERTITEMW:
3354
      return TAB_InsertItemT (infoPtr, (INT)wParam, (TCITEMW*)lParam, uMsg == TCM_INSERTITEMW);
3355

Francis Beaudet's avatar
Francis Beaudet committed
3356
    case TCM_SETITEMEXTRA:
3357
      return TAB_SetItemExtra (infoPtr, (INT)wParam);
3358

Francis Beaudet's avatar
Francis Beaudet committed
3359
    case TCM_ADJUSTRECT:
3360
      return TAB_AdjustRect (infoPtr, (BOOL)wParam, (LPRECT)lParam);
3361

Francis Beaudet's avatar
Francis Beaudet committed
3362
    case TCM_SETITEMSIZE:
3363
      return TAB_SetItemSize (infoPtr, (INT)LOWORD(lParam), (INT)HIWORD(lParam));
3364

Francis Beaudet's avatar
Francis Beaudet committed
3365
    case TCM_REMOVEIMAGE:
3366
      return TAB_RemoveImage (infoPtr, (INT)wParam);
3367

Francis Beaudet's avatar
Francis Beaudet committed
3368
    case TCM_SETPADDING:
3369
      return TAB_SetPadding (infoPtr, lParam);
3370

Francis Beaudet's avatar
Francis Beaudet committed
3371
    case TCM_GETROWCOUNT:
3372
      return TAB_GetRowCount(infoPtr);
3373 3374

    case TCM_GETUNICODEFORMAT:
3375
      return TAB_GetUnicodeFormat (infoPtr);
3376 3377

    case TCM_SETUNICODEFORMAT:
3378
      return TAB_SetUnicodeFormat (infoPtr, (BOOL)wParam);
3379 3380

    case TCM_HIGHLIGHTITEM:
3381
      return TAB_HighlightItem (infoPtr, (INT)wParam, (BOOL)LOWORD(lParam));
3382

Francis Beaudet's avatar
Francis Beaudet committed
3383
    case TCM_GETTOOLTIPS:
3384
      return TAB_GetToolTips (infoPtr);
3385

Francis Beaudet's avatar
Francis Beaudet committed
3386
    case TCM_SETTOOLTIPS:
3387
      return TAB_SetToolTips (infoPtr, (HWND)wParam);
3388

Francis Beaudet's avatar
Francis Beaudet committed
3389
    case TCM_GETCURFOCUS:
3390
      return TAB_GetCurFocus (infoPtr);
3391

Francis Beaudet's avatar
Francis Beaudet committed
3392
    case TCM_SETCURFOCUS:
3393
      return TAB_SetCurFocus (infoPtr, (INT)wParam);
3394

Alexandre Julliard's avatar
Alexandre Julliard committed
3395
    case TCM_SETMINTABWIDTH:
3396
      return TAB_SetMinTabWidth(infoPtr, (INT)lParam);
3397

Francis Beaudet's avatar
Francis Beaudet committed
3398
    case TCM_DESELECTALL:
3399
      return TAB_DeselectAll (infoPtr, (BOOL)wParam);
3400

3401
    case TCM_GETEXTENDEDSTYLE:
3402
      return TAB_GetExtendedStyle (infoPtr);
3403 3404

    case TCM_SETEXTENDEDSTYLE:
3405
      return TAB_SetExtendedStyle (infoPtr, wParam, lParam);
3406

Francis Beaudet's avatar
Francis Beaudet committed
3407
    case WM_GETFONT:
3408
      return TAB_GetFont (infoPtr);
3409

Francis Beaudet's avatar
Francis Beaudet committed
3410
    case WM_SETFONT:
3411
      return TAB_SetFont (infoPtr, (HFONT)wParam);
3412

Francis Beaudet's avatar
Francis Beaudet committed
3413
    case WM_CREATE:
3414
      return TAB_Create (hwnd, lParam);
3415

Francis Beaudet's avatar
Francis Beaudet committed
3416
    case WM_NCDESTROY:
3417
      return TAB_Destroy (infoPtr);
3418

3419
    case WM_GETDLGCODE:
Francis Beaudet's avatar
Francis Beaudet committed
3420
      return DLGC_WANTARROWS | DLGC_WANTCHARS;
3421

Francis Beaudet's avatar
Francis Beaudet committed
3422
    case WM_LBUTTONDOWN:
3423
      return TAB_LButtonDown (infoPtr, wParam, lParam);
3424

Francis Beaudet's avatar
Francis Beaudet committed
3425
    case WM_LBUTTONUP:
3426
      return TAB_LButtonUp (infoPtr);
3427

3428
    case WM_NOTIFY:
3429
      return SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, wParam, lParam);
3430

3431
    case WM_RBUTTONUP:
3432 3433
      TAB_RButtonUp (infoPtr);
      return DefWindowProcW (hwnd, uMsg, wParam, lParam);
3434

Francis Beaudet's avatar
Francis Beaudet committed
3435
    case WM_MOUSEMOVE:
3436
      return TAB_MouseMove (infoPtr, wParam, lParam);
3437

3438
    case WM_PRINTCLIENT:
Francis Beaudet's avatar
Francis Beaudet committed
3439
    case WM_PAINT:
3440
      return TAB_Paint (infoPtr, (HDC)wParam);
Francis Beaudet's avatar
Francis Beaudet committed
3441 3442

    case WM_SIZE:
3443
      return TAB_Size (infoPtr);
3444

Francis Beaudet's avatar
Francis Beaudet committed
3445
    case WM_SETREDRAW:
3446
      return TAB_SetRedraw (infoPtr, (BOOL)wParam);
Francis Beaudet's avatar
Francis Beaudet committed
3447 3448

    case WM_HSCROLL:
3449
      return TAB_OnHScroll(infoPtr, (int)LOWORD(wParam), (int)HIWORD(wParam));
Alexandre Julliard's avatar
Alexandre Julliard committed
3450 3451

    case WM_STYLECHANGED:
3452
      return TAB_StyleChanged(infoPtr, wParam, (LPSTYLESTRUCT)lParam);
3453 3454 3455 3456

    case WM_SYSCOLORCHANGE:
      COMCTL32_RefreshSysColors();
      return 0;
3457

3458 3459 3460
    case WM_THEMECHANGED:
      return theme_changed (infoPtr);

Francis Beaudet's avatar
Francis Beaudet committed
3461
    case WM_KILLFOCUS:
3462
      TAB_KillFocus(infoPtr);
Francis Beaudet's avatar
Francis Beaudet committed
3463
    case WM_SETFOCUS:
3464 3465
      TAB_FocusChanging(infoPtr);
      break;   /* Don't disturb normal focus behavior */
Francis Beaudet's avatar
Francis Beaudet committed
3466

3467
    case WM_KEYDOWN:
3468
      return TAB_KeyDown(infoPtr, wParam, lParam);
3469

3470
    case WM_NCHITTEST:
3471
      return TAB_NCHitTest(infoPtr, lParam);
Francis Beaudet's avatar
Francis Beaudet committed
3472

3473
    case WM_NCCALCSIZE:
3474
      return TAB_NCCalcSize(wParam);
3475

Francis Beaudet's avatar
Francis Beaudet committed
3476
    default:
3477
      if (uMsg >= WM_USER && uMsg < WM_APP && !COMCTL32_IsReflectedMessage(uMsg))
3478
	WARN("unknown msg %04x wp=%08lx lp=%08lx\n",
Francis Beaudet's avatar
Francis Beaudet committed
3479
	     uMsg, wParam, lParam);
3480
      break;
Alexandre Julliard's avatar
Alexandre Julliard committed
3481
    }
3482
    return DefWindowProcW(hwnd, uMsg, wParam, lParam);
Alexandre Julliard's avatar
Alexandre Julliard committed
3483 3484 3485
}


Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
3486
void
3487
TAB_Register (void)
Alexandre Julliard's avatar
Alexandre Julliard committed
3488
{
3489
  WNDCLASSW wndClass;
Francis Beaudet's avatar
Francis Beaudet committed
3490

3491
  ZeroMemory (&wndClass, sizeof(WNDCLASSW));
3492
  wndClass.style         = CS_GLOBALCLASS | CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
3493
  wndClass.lpfnWndProc   = TAB_WindowProc;
Francis Beaudet's avatar
Francis Beaudet committed
3494 3495
  wndClass.cbClsExtra    = 0;
  wndClass.cbWndExtra    = sizeof(TAB_INFO *);
3496 3497 3498
  wndClass.hCursor       = LoadCursorW (0, (LPWSTR)IDC_ARROW);
  wndClass.hbrBackground = (HBRUSH)(COLOR_BTNFACE+1);
  wndClass.lpszClassName = WC_TABCONTROLW;
3499

3500
  RegisterClassW (&wndClass);
Alexandre Julliard's avatar
Alexandre Julliard committed
3501 3502
}

3503

Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
3504
void
3505
TAB_Unregister (void)
3506
{
3507
    UnregisterClassW (WC_TABCONTROLW, NULL);
3508
}