tab.c 97.6 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
      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;
        }

687 688
      if (TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGING))
        return 0;
689 690 691 692

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

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

699 700 701
  return 0;
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    infoPtr->iHotTracked = item;

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

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

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

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

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

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

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

934 935
    if (!prc) return -1;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1460
	  }
1461

1462

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

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

      rcOriginal = *rcItem;

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

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

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

1494 1495

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

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

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

1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559
    /* 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);
    }

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

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

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

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

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

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

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

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

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

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

1719 1720 1721
  /*
   * Setup for text output
  */
1722
  oldBkMode = SetBkMode(hdc, TRANSPARENT);
1723
  if (!GetWindowTheme (infoPtr->hwnd) || (infoPtr->dwStyle & TCS_BUTTONS))
1724
  {
1725 1726
    if ((infoPtr->dwStyle & TCS_HOTTRACK) && (iItem == infoPtr->iHotTracked) &&
        !(infoPtr->dwStyle & TCS_FLATBUTTONS))
1727 1728 1729 1730 1731 1732
      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);
  }
1733 1734 1735 1736

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

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

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

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

1766 1767 1768 1769 1770 1771 1772
    /* 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 */
1773
        dis.itemData = 0;
1774 1775 1776 1777
        memcpy(&dis.itemData, (ULONG_PTR*)TAB_GetItem(infoPtr, iItem)->extra, 4);
    }

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

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

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

    rcTemp = *drawRect;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2337 2338
    TAB_DumpItemInternal(infoPtr, iItem);

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2445 2446 2447
  if (iSelected < 0)
    return;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2629 2630 2631 2632
  TAB_Refresh (infoPtr, hdc);

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

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

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

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

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

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

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

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

2664
  infoPtr->uNumItem++;
2665

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

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

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

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

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

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

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

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

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

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

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

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

2733 2734 2735
  return lResult;
}

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

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

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

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

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

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

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

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

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

2774 2775 2776
  return TRUE;
}

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

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

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

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

2789
  wineItem = TAB_GetItem(infoPtr, iItem);
2790

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

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

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

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

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

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

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

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


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

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

2835 2836
  if (!tabItem) return FALSE;

2837
  if (iItem < 0 || iItem >= infoPtr->uNumItem)
2838 2839 2840 2841 2842
  {
    /* 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;
2843
    return FALSE;
2844
  }
2845

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

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

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

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

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

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

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

2870 2871 2872 2873
  return TRUE;
}


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

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

2880 2881
    if (iItem < 0 || iItem >= infoPtr->uNumItem) return FALSE;

2882
    TAB_InvalidateTabArea(infoPtr);
2883 2884 2885 2886 2887
    item = TAB_GetItem(infoPtr, iItem);
    Free(item->pszText);
    Free(item);
    infoPtr->uNumItem--;
    DPA_DeletePtr(infoPtr->items, iItem);
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;
3023
  DWORD style;
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. */
3057 3058 3059 3060
  style = GetWindowLongW(hwnd, GWL_STYLE);
  if (style & TCS_VERTICAL) style |= TCS_MULTILINE;
  style |= WS_CLIPSIBLINGS;
  SetWindowLongW(hwnd, GWL_STYLE, style);
Alexandre Julliard's avatar
Alexandre Julliard committed
3061

3062 3063
  infoPtr->dwStyle = style;
  infoPtr->exStyle = (style & TCS_FLATBUTTONS) ? TCS_EX_FLATSEPARATORS : 0;
3064

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

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

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

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

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

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

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

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

  infoPtr->tabMinWidth = -1;
3112

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

3115 3116 3117
  SelectObject (hdc, hOldFont);
  ReleaseDC(hwnd, hdc);

Francis Beaudet's avatar
Francis Beaudet committed
3118
  return 0;
Alexandre Julliard's avatar
Alexandre Julliard committed
3119 3120 3121
}

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

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

3128 3129 3130 3131 3132 3133 3134 3135 3136
  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
3137
  }
3138 3139
  DPA_Destroy(infoPtr->items);
  infoPtr->items = NULL;
3140 3141

  if (infoPtr->hwndToolTip)
Francis Beaudet's avatar
Francis Beaudet committed
3142
    DestroyWindow (infoPtr->hwndToolTip);
3143

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

3147
  if (infoPtr->iHotTracked >= 0)
3148
    KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
3149

3150
  CloseThemeData (GetWindowTheme (infoPtr->hwnd));
3151

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

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

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

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

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

  infoPtr->cbInfo = cbInfo;
  return TRUE;
}

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

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

    /* shift indices, repaint items if needed */
    for (i = 0; i < infoPtr->uNumItem; i++)
    {
3195
      idx = &TAB_GetItem(infoPtr, i)->iImage;
3196 3197 3198 3199 3200 3201 3202 3203 3204 3205 3206 3207 3208 3209 3210 3211 3212
      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;
}

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 3239 3240
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
3241
TAB_GetExtendedStyle (const TAB_INFO *infoPtr)
3242 3243 3244 3245
{
  return infoPtr->exStyle;
}

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

3252 3253
  TRACE("(%p, %d)\n", infoPtr, excludesel);

3254
  if (!(infoPtr->dwStyle & TCS_BUTTONS))
3255 3256 3257 3258 3259 3260 3261 3262 3263 3264 3265 3266 3267 3268 3269 3270 3271 3272 3273 3274 3275 3276 3277 3278 3279
    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;
}

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 3306 3307
/***
 * 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;
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    case TCM_GETUNICODEFORMAT:
3377
      return TAB_GetUnicodeFormat (infoPtr);
3378 3379

    case TCM_SETUNICODEFORMAT:
3380
      return TAB_SetUnicodeFormat (infoPtr, (BOOL)wParam);
3381 3382

    case TCM_HIGHLIGHTITEM:
3383
      return TAB_HighlightItem (infoPtr, (INT)wParam, (BOOL)LOWORD(lParam));
3384

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

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

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

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

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

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

3403
    case TCM_GETEXTENDEDSTYLE:
3404
      return TAB_GetExtendedStyle (infoPtr);
3405 3406

    case TCM_SETEXTENDEDSTYLE:
3407
      return TAB_SetExtendedStyle (infoPtr, wParam, lParam);
3408

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

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

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

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

3421
    case WM_GETDLGCODE:
Francis Beaudet's avatar
Francis Beaudet committed
3422
      return DLGC_WANTARROWS | DLGC_WANTCHARS;
3423

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

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

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

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

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

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

    case WM_SIZE:
3445
      return TAB_Size (infoPtr);
3446

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

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

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

    case WM_SYSCOLORCHANGE:
      COMCTL32_RefreshSysColors();
      return 0;
3459

3460 3461 3462
    case WM_THEMECHANGED:
      return theme_changed (infoPtr);

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

3469
    case WM_KEYDOWN:
3470
      return TAB_KeyDown(infoPtr, wParam, lParam);
3471

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

3475
    case WM_NCCALCSIZE:
3476
      return TAB_NCCalcSize(wParam);
3477

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


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

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

3502
  RegisterClassW (&wndClass);
Alexandre Julliard's avatar
Alexandre Julliard committed
3503 3504
}

3505

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