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

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

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

81
WINE_DEFAULT_DEBUG_CHANNEL(tab);
82 83 84 85 86

typedef struct
{
  UINT   mask;
  DWORD  dwState;
87
  LPWSTR pszText;
88
  INT    iImage;
89 90 91 92
  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)
                     *
93
                     * additionally the top member holds the row number
94 95
                     * and bottom is unused and should be 0 */
  BYTE   extra[1];  /* Space for caller supplied info, variable size */
96 97
} TAB_ITEM;

98 99 100
/* The size of a tab item depends on how much extra data is requested */
#define TAB_ITEM_SIZE(infoPtr) (sizeof(TAB_ITEM) - sizeof(BYTE) + infoPtr->cbInfo)

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

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

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

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

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

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

Francis Beaudet's avatar
Francis Beaudet committed
165 166 167
/******************************************************************************
 * Prototypes
 */
168 169 170
static void TAB_InvalidateTabArea(TAB_INFO *);
static void TAB_EnsureSelectionVisible(TAB_INFO *);
static void TAB_DrawItemInterior(TAB_INFO *, HDC, INT, 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,
Francis Beaudet's avatar
Francis Beaudet committed
182
            (WPARAM) 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 196 197 198

    msg.hwnd = hwndMsg;
    msg.message = uMsg;
    msg.wParam = wParam;
    msg.lParam = lParam;
    msg.time = GetMessageTime ();
    msg.pt.x = LOWORD(GetMessagePos ());
    msg.pt.y = HIWORD(GetMessagePos ());

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

202
static void
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
203
TAB_DumpItemExternalT(TCITEMW *pti, UINT iItem, BOOL isW)
204 205 206 207 208
{
    if (TRACE_ON(tab)) {
	TRACE("external tab %d, mask=0x%08x, dwState=0x%08lx, dwStateMask=0x%08lx, cchTextMax=0x%08x\n",
	      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 214 215 216 217 218
    }
}

static void
TAB_DumpItemInternal(TAB_INFO *infoPtr, UINT iItem)
{
    if (TRACE_ON(tab)) {
	TAB_ITEM *ti;

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

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

235 236 237 238 239 240
/* RETURNS
 *   the index of the tab item that has the focus
 * NOTE
 *   we have not to return negative value
 * TODO
 *   test for windows */
241 242
static inline LRESULT
TAB_GetCurFocus (const TAB_INFO *infoPtr)
Alex Priem's avatar
Alex Priem committed
243
{
244 245 246 247 248
    if (infoPtr->uFocus<0)
    {
        FIXME("we have not to return negative value");
        return 0;
    }
Alex Priem's avatar
Alex Priem committed
249 250 251
    return infoPtr->uFocus;
}

252
static inline LRESULT TAB_GetToolTips (const TAB_INFO *infoPtr)
Alex Priem's avatar
Alex Priem committed
253 254
{
    if (infoPtr == NULL) return 0;
255
    return (LRESULT)infoPtr->hwndToolTip;
Alex Priem's avatar
Alex Priem committed
256 257
}

258
static inline LRESULT TAB_SetCurSel (TAB_INFO *infoPtr, INT iItem)
259
{
260
  INT prevItem = -1;
261

262 263
  if (iItem >= 0 && iItem < infoPtr->uNumItem) {
      prevItem=infoPtr->iSelected;
264 265 266 267 268
      if (infoPtr->iSelected != iItem) {
          infoPtr->iSelected=iItem;
          TAB_EnsureSelectionVisible(infoPtr);
          TAB_InvalidateTabArea(infoPtr);
      }
Francis Beaudet's avatar
Francis Beaudet committed
269 270
  }
  return prevItem;
271 272
}

273
static LRESULT TAB_SetCurFocus (TAB_INFO *infoPtr, INT iItem)
Alex Priem's avatar
Alex Priem committed
274
{
275
  if (iItem < 0 || iItem >= infoPtr->uNumItem) return 0;
Francis Beaudet's avatar
Francis Beaudet committed
276

Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
277
  if (GetWindowLongW(infoPtr->hwnd, GWL_STYLE) & TCS_BUTTONS) {
278
    FIXME("Should set input focus\n");
279
  } else {
280
    int oldFocus = infoPtr->uFocus;
281
    if (infoPtr->iSelected != iItem || oldFocus == -1 ) {
282
      infoPtr->uFocus = iItem;
283
      if (oldFocus != -1) {
284
        if (!TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGING))  {
285
          infoPtr->iSelected = iItem;
286
          TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGE);
287 288 289
        }
        else
          infoPtr->iSelected = iItem;
290 291
        TAB_EnsureSelectionVisible(infoPtr);
        TAB_InvalidateTabArea(infoPtr);
Francis Beaudet's avatar
Francis Beaudet committed
292 293 294
      }
    }
  }
Alex Priem's avatar
Alex Priem committed
295 296 297
  return 0;
}

298 299
static inline LRESULT
TAB_SetToolTips (TAB_INFO *infoPtr, HWND hwndToolTip)
Alex Priem's avatar
Alex Priem committed
300
{
301 302
    if (infoPtr)
        infoPtr->hwndToolTip = hwndToolTip;
Alex Priem's avatar
Alex Priem committed
303 304 305
    return 0;
}

306 307
static inline LRESULT
TAB_SetPadding (TAB_INFO *infoPtr, LPARAM lParam)
308
{
309 310 311 312 313
    if (infoPtr)
    {
        infoPtr->uHItemPadding_s=LOWORD(lParam);
        infoPtr->uVItemPadding_s=HIWORD(lParam);
    }
314 315 316
    return 0;
}

Francis Beaudet's avatar
Francis Beaudet committed
317 318 319 320 321 322 323 324 325 326
/******************************************************************************
 * 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(
327
  const TAB_INFO* infoPtr,
Francis Beaudet's avatar
Francis Beaudet committed
328 329 330 331
  INT         itemIndex,
  RECT*       itemRect,
  RECT*       selectedRect)
{
332
  RECT tmpItemRect,clientRect;
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
333
  LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
334

335 336
  /* Perform a sanity check and a trivial visibility check. */
  if ( (infoPtr->uNumItem <= 0) ||
Francis Beaudet's avatar
Francis Beaudet committed
337
       (itemIndex >= infoPtr->uNumItem) ||
338
       (!((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)) && (itemIndex < infoPtr->leftmostVisible)) )
339 340 341 342 343 344 345 346 347 348 349 350
    {
        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
351 352 353 354 355

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

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

362
  /* calculate the times bottom and top based on the row */
363
  GetClientRect(infoPtr->hwnd, &clientRect);
364

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

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

    /*
     * Move the rectangle so the first item is slightly offset from
     * the bottom of the tab control.
     */
    OffsetRect(itemRect,
	     0,
406
	     SELECTED_TAB_OFFSET);
407 408 409 410

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

414 415 416 417 418
    /*
     * 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
419 420
	     SELECTED_TAB_OFFSET,
	     0);
421
  }
422
  TRACE("item %d tab h=%d, rect=(%ld,%ld)-(%ld,%ld)\n",
423 424
	itemIndex, infoPtr->tabHeight,
	itemRect->left, itemRect->top, itemRect->right, itemRect->bottom);
Francis Beaudet's avatar
Francis Beaudet committed
425

426
  /* Now, calculate the position of the item as if it were selected. */
Francis Beaudet's avatar
Francis Beaudet committed
427 428 429 430
  if (selectedRect!=NULL)
  {
    CopyRect(selectedRect, itemRect);

431 432 433 434 435
    /* The rectangle of a selected item is a bit wider. */
    if(lStyle & TCS_VERTICAL)
      InflateRect(selectedRect, 0, SELECTED_TAB_OFFSET);
    else
      InflateRect(selectedRect, SELECTED_TAB_OFFSET, 0);
Francis Beaudet's avatar
Francis Beaudet committed
436

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

459 460 461 462 463
  /* Check for visibility */
  if (lStyle & TCS_VERTICAL)
    return (itemRect->top < clientRect.bottom) && (itemRect->bottom > clientRect.top);
  else
    return (itemRect->left < clientRect.right) && (itemRect->right > clientRect.left);
Francis Beaudet's avatar
Francis Beaudet committed
464 465
}

466 467
static inline BOOL
TAB_GetItemRect(TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
468
{
469
  return TAB_InternalGetItemRect(infoPtr, (INT)wParam, (LPRECT)lParam, (LPRECT)NULL);
470 471
}

Francis Beaudet's avatar
Francis Beaudet committed
472 473 474 475 476
/******************************************************************************
 * TAB_KeyUp
 *
 * This method is called to handle keyboard input
 */
477
static LRESULT TAB_KeyUp(TAB_INFO* infoPtr, WPARAM keyCode)
Francis Beaudet's avatar
Francis Beaudet committed
478 479 480 481 482 483
{
  int       newItem = -1;

  switch (keyCode)
  {
    case VK_LEFT:
484
      newItem = infoPtr->uFocus - 1;
Francis Beaudet's avatar
Francis Beaudet committed
485 486
      break;
    case VK_RIGHT:
487
      newItem = infoPtr->uFocus + 1;
Francis Beaudet's avatar
Francis Beaudet committed
488 489
      break;
  }
490

Francis Beaudet's avatar
Francis Beaudet committed
491 492 493
  /*
   * If we changed to a valid item, change the selection
   */
494 495 496
  if (newItem >= 0 &&
      newItem < infoPtr->uNumItem &&
      infoPtr->uFocus != newItem)
Francis Beaudet's avatar
Francis Beaudet committed
497
  {
498
    if (!TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGING))
Francis Beaudet's avatar
Francis Beaudet committed
499 500 501
    {
      infoPtr->iSelected = newItem;
      infoPtr->uFocus    = newItem;
502
      TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGE);
Francis Beaudet's avatar
Francis Beaudet committed
503

504 505
      TAB_EnsureSelectionVisible(infoPtr);
      TAB_InvalidateTabArea(infoPtr);
Francis Beaudet's avatar
Francis Beaudet committed
506 507 508 509 510 511 512 513 514 515 516 517
    }
  }

  return 0;
}

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

  /*
   * Get the rectangle for the item.
   */
526
  isVisible = TAB_InternalGetItemRect(infoPtr,
Francis Beaudet's avatar
Francis Beaudet committed
527 528 529
				      infoPtr->uFocus,
				      NULL,
				      &selectedRect);
530

Francis Beaudet's avatar
Francis Beaudet committed
531 532 533 534 535 536
  /*
   * If the rectangle is not completely invisible, invalidate that
   * portion of the window.
   */
  if (isVisible)
  {
537
      TRACE("invalidate (%ld,%ld)-(%ld,%ld)\n",
538 539
	    selectedRect.left,selectedRect.top,
	    selectedRect.right,selectedRect.bottom);
540
    InvalidateRect(infoPtr->hwnd, &selectedRect, TRUE);
Francis Beaudet's avatar
Francis Beaudet committed
541 542
  }
}
Alex Priem's avatar
Alex Priem committed
543

544
static INT TAB_InternalHitTest (
545 546
  TAB_INFO* infoPtr,
  POINT     pt,
Francis Beaudet's avatar
Francis Beaudet committed
547
  UINT*     flags)
Alex Priem's avatar
Alex Priem committed
548 549

{
550
  RECT rect;
551
  INT iCount;
552

553
  for (iCount = 0; iCount < infoPtr->uNumItem; iCount++)
Francis Beaudet's avatar
Francis Beaudet committed
554
  {
555
    TAB_InternalGetItemRect(infoPtr, iCount, &rect, NULL);
Francis Beaudet's avatar
Francis Beaudet committed
556

557
    if (PtInRect(&rect, pt))
Francis Beaudet's avatar
Francis Beaudet committed
558 559 560 561 562 563
    {
      *flags = TCHT_ONITEM;
      return iCount;
    }
  }

564
  *flags = TCHT_NOWHERE;
Alex Priem's avatar
Alex Priem committed
565 566 567
  return -1;
}

568 569
static inline LRESULT
TAB_HitTest (TAB_INFO *infoPtr, LPTCHITTESTINFO lptest)
Alex Priem's avatar
Alex Priem committed
570
{
571
  return TAB_InternalHitTest (infoPtr, lptest->pt, &lptest->flags);
Alex Priem's avatar
Alex Priem committed
572 573
}

574 575 576 577 578 579 580 581 582 583 584 585
/******************************************************************************
 * 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 ?
 */
586 587
static inline LRESULT
TAB_NCHitTest (TAB_INFO *infoPtr, LPARAM lParam)
588 589 590 591 592 593
{
  POINT pt;
  UINT dummyflag;

  pt.x = LOWORD(lParam);
  pt.y = HIWORD(lParam);
594
  ScreenToClient(infoPtr->hwnd, &pt);
595

596
  if (TAB_InternalHitTest(infoPtr, pt, &dummyflag) == -1)
597 598 599 600
    return HTTRANSPARENT;
  else
    return HTCLIENT;
}
Alex Priem's avatar
Alex Priem committed
601 602

static LRESULT
603
TAB_LButtonDown (TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
Alex Priem's avatar
Alex Priem committed
604
{
605
  POINT pt;
Mike McCormack's avatar
Mike McCormack committed
606 607
  INT newItem;
  UINT dummy;
Alex Priem's avatar
Alex Priem committed
608

Francis Beaudet's avatar
Francis Beaudet committed
609
  if (infoPtr->hwndToolTip)
610
    TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
Francis Beaudet's avatar
Francis Beaudet committed
611
		    WM_LBUTTONDOWN, wParam, lParam);
Alex Priem's avatar
Alex Priem committed
612

Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
613
  if (GetWindowLongW(infoPtr->hwnd, GWL_STYLE) & TCS_FOCUSONBUTTONDOWN ) {
614
    SetFocus (infoPtr->hwnd);
Francis Beaudet's avatar
Francis Beaudet committed
615
  }
Alex Priem's avatar
Alex Priem committed
616

Francis Beaudet's avatar
Francis Beaudet committed
617
  if (infoPtr->hwndToolTip)
618
    TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
Francis Beaudet's avatar
Francis Beaudet committed
619
		    WM_LBUTTONDOWN, wParam, lParam);
620

Francis Beaudet's avatar
Francis Beaudet committed
621 622
  pt.x = (INT)LOWORD(lParam);
  pt.y = (INT)HIWORD(lParam);
623

624
  newItem = TAB_InternalHitTest (infoPtr, pt, &dummy);
625

626
  TRACE("On Tab, item %d\n", newItem);
627

628
  if (newItem != -1 && infoPtr->iSelected != newItem)
Francis Beaudet's avatar
Francis Beaudet committed
629
  {
630
    if (!TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGING))
Francis Beaudet's avatar
Francis Beaudet committed
631 632 633
    {
      infoPtr->iSelected = newItem;
      infoPtr->uFocus    = newItem;
634
      TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGE);
Alexandre Julliard's avatar
Alexandre Julliard committed
635

636
      TAB_EnsureSelectionVisible(infoPtr);
Francis Beaudet's avatar
Francis Beaudet committed
637

638
      TAB_InvalidateTabArea(infoPtr);
Francis Beaudet's avatar
Francis Beaudet committed
639 640
    }
  }
641 642 643
  return 0;
}

644 645
static inline LRESULT
TAB_LButtonUp (const TAB_INFO *infoPtr)
646
{
647
  TAB_SendSimpleNotify(infoPtr, NM_CLICK);
Francis Beaudet's avatar
Francis Beaudet committed
648 649

  return 0;
Alexandre Julliard's avatar
Alexandre Julliard committed
650 651
}

652 653
static inline LRESULT
TAB_RButtonDown (const TAB_INFO *infoPtr)
Alex Priem's avatar
Alex Priem committed
654
{
655
  TAB_SendSimpleNotify(infoPtr, NM_RCLICK);
Francis Beaudet's avatar
Francis Beaudet committed
656
  return 0;
Alex Priem's avatar
Alex Priem committed
657 658
}

659 660 661 662 663 664 665 666 667
/******************************************************************************
 * 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
668
TAB_DrawLoneItemInterior(TAB_INFO* infoPtr, int iItem)
669
{
670
  HDC hdc = GetDC(infoPtr->hwnd);
671 672
  RECT r, rC;

673 674 675
  /* Clip UpDown control to not draw over it */
  if (infoPtr->needsScrolling)
  {
676
    GetWindowRect(infoPtr->hwnd, &rC);
677 678 679
    GetWindowRect(infoPtr->hwndUpDown, &r);
    ExcludeClipRect(hdc, r.left - rC.left, r.top - rC.top, r.right - rC.left, r.bottom - rC.top);
  }
680 681
  TAB_DrawItemInterior(infoPtr, hdc, iItem, NULL);
  ReleaseDC(infoPtr->hwnd, hdc);
682 683
}

684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699
/* update a tab after hottracking - invalidate it or just redraw the interior,
 * based on whether theming is used or not */
static inline void hottrack_refresh (TAB_INFO* infoPtr, int tabIndex)
{
    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);
}

700 701 702 703 704 705 706 707 708 709 710
/******************************************************************************
 * 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
711
static void CALLBACK
712 713
TAB_HotTrackTimerProc
  (
714 715
  HWND hwnd,    /* handle of window for timer messages */
  UINT uMsg,    /* WM_TIMER message */
Frank Richter's avatar
Frank Richter committed
716
  UINT_PTR idEvent, /* timer identifier */
717
  DWORD dwTime  /* current system time */
718 719 720 721 722 723 724 725 726 727 728 729 730 731
  )
{
  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
732
    ** WM_MOUSEMOVE event.
733 734 735
    */
    if (!GetCursorPos(&pt) || WindowFromPoint(pt) != hwnd)
    {
736
      /* Redraw iHotTracked to look normal */
737 738
      INT iRedraw = infoPtr->iHotTracked;
      infoPtr->iHotTracked = -1;
739
      hottrack_refresh (infoPtr, iRedraw);
740

741
      /* Kill this timer */
742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765
      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
  (
766
  TAB_INFO*       infoPtr,
767 768 769 770 771 772 773 774 775 776 777 778 779
  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;

780 781
  if ((GetWindowLongW(infoPtr->hwnd, GWL_STYLE) & TCS_HOTTRACK)
      || GetWindowTheme (infoPtr->hwnd))
782 783 784 785 786 787 788
  {
    POINT pt;
    UINT  flags;

    if (pos == NULL)
    {
      GetCursorPos(&pt);
789
      ScreenToClient(infoPtr->hwnd, &pt);
790 791 792 793 794 795 796
    }
    else
    {
      pt.x = LOWORD(*pos);
      pt.y = HIWORD(*pos);
    }

797
    item = TAB_InternalHitTest(infoPtr, pt, &flags);
798 799 800 801 802 803
  }

  if (item != infoPtr->iHotTracked)
  {
    if (infoPtr->iHotTracked >= 0)
    {
804
      /* Mark currently hot-tracked to be redrawn to look normal */
805 806 807 808 809
      if (out_redrawLeave != NULL)
        *out_redrawLeave = infoPtr->iHotTracked;

      if (item < 0)
      {
810
        /* Kill timer which forces recheck of mouse pos */
811
        KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
812 813 814 815
      }
    }
    else
    {
816
      /* Start timer so we recheck mouse pos */
817 818
      UINT timerID = SetTimer
        (
819
        infoPtr->hwnd,
820 821 822 823 824 825 826 827 828 829 830 831 832
        TAB_HOTTRACK_TIMER,
        TAB_HOTTRACK_TIMER_INTERVAL,
        TAB_HotTrackTimerProc
        );

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

    infoPtr->iHotTracked = item;

    if (item >= 0)
    {
833
	/* Mark new hot-tracked to be redrawn to look highlighted */
834 835 836 837 838 839 840 841 842 843 844
      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
845
static LRESULT
846
TAB_MouseMove (TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
Alex Priem's avatar
Alex Priem committed
847
{
848 849 850
  int redrawLeave;
  int redrawEnter;

Francis Beaudet's avatar
Francis Beaudet committed
851
  if (infoPtr->hwndToolTip)
852
    TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
Francis Beaudet's avatar
Francis Beaudet committed
853
		    WM_LBUTTONDOWN, wParam, lParam);
854 855 856

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

859 860
  hottrack_refresh (infoPtr, redrawLeave);
  hottrack_refresh (infoPtr, redrawEnter);
861

Francis Beaudet's avatar
Francis Beaudet committed
862
  return 0;
Alex Priem's avatar
Alex Priem committed
863
}
864

865 866 867
/******************************************************************************
 * TAB_AdjustRect
 *
Andreas Mohr's avatar
Andreas Mohr committed
868
 * Calculates the tab control's display area given the window rectangle or
869 870 871
 * the window rectangle given the requested display rectangle.
 */
static LRESULT TAB_AdjustRect(
872
  TAB_INFO *infoPtr,
873
  WPARAM fLarger,
874
  LPRECT prc)
875
{
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
876
    DWORD lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
877
    LONG *iRightBottom, *iLeftTop;
878

879
    TRACE ("hwnd=%p fLarger=%d (%ld,%ld)-(%ld,%ld)\n", infoPtr->hwnd, fLarger, prc->left, prc->top, prc->right, prc->bottom);
880

881 882 883 884
    if(lStyle & TCS_VERTICAL)
    {
	iRightBottom = &(prc->right);
	iLeftTop     = &(prc->left);
885
    }
886
    else
887
    {
888 889
	iRightBottom = &(prc->bottom);
	iLeftTop     = &(prc->top);
890
    }
891

892 893
    if (fLarger) /* Go from display rectangle */
    {
894 895 896 897 898 899
        /* Add the height of the tabs. */
	if (lStyle & TCS_BOTTOM)
	    *iRightBottom += infoPtr->tabHeight * infoPtr->uNumRows;
	else
	    *iLeftTop -= infoPtr->tabHeight * infoPtr->uNumRows +
			 ((lStyle & TCS_BUTTONS)? 3 * (infoPtr->uNumRows - 1) : 0);
900

901 902
	/* Inflate the rectangle for the padding */
	InflateRect(prc, DISPLAY_AREA_PADDINGX, DISPLAY_AREA_PADDINGY); 
903

904 905
	/* Inflate for the border */
	InflateRect(prc, CONTROL_BORDER_SIZEX, CONTROL_BORDER_SIZEY);
906 907 908
    }
    else /* Go from window rectangle. */
    {
909 910
	/* Deflate the rectangle for the border */
	InflateRect(prc, -CONTROL_BORDER_SIZEX, -CONTROL_BORDER_SIZEY);
911

912 913
	/* Deflate the rectangle for the padding */
	InflateRect(prc, -DISPLAY_AREA_PADDINGX, -DISPLAY_AREA_PADDINGY);
914

915 916 917 918 919 920
	/* Remove the height of the tabs. */
	if (lStyle & TCS_BOTTOM)
	    *iRightBottom -= infoPtr->tabHeight * infoPtr->uNumRows;
	else
	    *iLeftTop += (infoPtr->tabHeight) * infoPtr->uNumRows +
			 ((lStyle & TCS_BUTTONS)? 3 * (infoPtr->uNumRows - 1) : 0);
921 922
    }

Francis Beaudet's avatar
Francis Beaudet committed
923
  return 0;
924 925
}

Francis Beaudet's avatar
Francis Beaudet committed
926 927 928 929 930 931 932
/******************************************************************************
 * TAB_OnHScroll
 *
 * This method will handle the notification from the scroll control and
 * perform the scrolling operation on the tab control.
 */
static LRESULT TAB_OnHScroll(
933
  TAB_INFO *infoPtr,
Francis Beaudet's avatar
Francis Beaudet committed
934 935 936
  int     nScrollCode,
  int     nPos,
  HWND    hwndScroll)
Alexandre Julliard's avatar
Alexandre Julliard committed
937
{
Alexandre Julliard's avatar
Alexandre Julliard committed
938
  if(nScrollCode == SB_THUMBPOSITION && nPos != infoPtr->leftmostVisible)
Francis Beaudet's avatar
Francis Beaudet committed
939
  {
Alexandre Julliard's avatar
Alexandre Julliard committed
940 941 942 943
     if(nPos < infoPtr->leftmostVisible)
        infoPtr->leftmostVisible--;
     else
        infoPtr->leftmostVisible++;
Francis Beaudet's avatar
Francis Beaudet committed
944

945 946
     TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
     TAB_InvalidateTabArea(infoPtr);
947
     SendMessageW(infoPtr->hwndUpDown, UDM_SETPOS, 0,
Alexandre Julliard's avatar
Alexandre Julliard committed
948 949
                   MAKELONG(infoPtr->leftmostVisible, 0));
   }
Alexandre Julliard's avatar
Alexandre Julliard committed
950

Alexandre Julliard's avatar
Alexandre Julliard committed
951
   return 0;
Alexandre Julliard's avatar
Alexandre Julliard committed
952
}
Francis Beaudet's avatar
Francis Beaudet committed
953 954

/******************************************************************************
Gerard Patel's avatar
Gerard Patel committed
955
 * TAB_SetupScrolling
Francis Beaudet's avatar
Francis Beaudet committed
956
 *
957
 * This method will check the current scrolling state and make sure the
Francis Beaudet's avatar
Francis Beaudet committed
958 959 960 961 962 963
 * scrolling control is displayed (or not).
 */
static void TAB_SetupScrolling(
  HWND        hwnd,
  TAB_INFO*   infoPtr,
  const RECT* clientRect)
Alexandre Julliard's avatar
Alexandre Julliard committed
964
{
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
965 966
  static const WCHAR msctls_updown32W[] = { 'm','s','c','t','l','s','_','u','p','d','o','w','n','3','2',0 };
  static const WCHAR emptyW[] = { 0 };
Alexandre Julliard's avatar
Alexandre Julliard committed
967
  INT maxRange = 0;
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
968
  DWORD lStyle = GetWindowLongW(hwnd, GWL_STYLE);
969

Francis Beaudet's avatar
Francis Beaudet committed
970 971 972
  if (infoPtr->needsScrolling)
  {
    RECT controlPos;
Alexandre Julliard's avatar
Alexandre Julliard committed
973
    INT vsize, tabwidth;
974

Francis Beaudet's avatar
Francis Beaudet committed
975 976 977
    /*
     * Calculate the position of the scroll control.
     */
978
    if(lStyle & TCS_VERTICAL)
Francis Beaudet's avatar
Francis Beaudet committed
979
    {
980 981 982 983 984 985 986 987 988 989 990 991 992
      controlPos.right = clientRect->right;
      controlPos.left  = controlPos.right - 2 * GetSystemMetrics(SM_CXHSCROLL);

      if (lStyle & TCS_BOTTOM)
      {
        controlPos.top    = clientRect->bottom - infoPtr->tabHeight;
        controlPos.bottom = controlPos.top + GetSystemMetrics(SM_CYHSCROLL);
      }
      else
      {
        controlPos.bottom = clientRect->top + infoPtr->tabHeight;
        controlPos.top    = controlPos.bottom - GetSystemMetrics(SM_CYHSCROLL);
      }
Francis Beaudet's avatar
Francis Beaudet committed
993 994 995
    }
    else
    {
996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008
      controlPos.right = clientRect->right;
      controlPos.left  = controlPos.right - 2 * GetSystemMetrics(SM_CXHSCROLL);

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

Francis Beaudet's avatar
Francis Beaudet committed
1011 1012
    /*
     * If we don't have a scroll control yet, we want to create one.
1013
     * If we have one, we want to make sure it's positioned properly.
Francis Beaudet's avatar
Francis Beaudet committed
1014 1015 1016
     */
    if (infoPtr->hwndUpDown==0)
    {
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
1017
      infoPtr->hwndUpDown = CreateWindowW(msctls_updown32W, emptyW,
Alexandre Julliard's avatar
Alexandre Julliard committed
1018
					  WS_VISIBLE | WS_CHILD | UDS_HORZ,
Francis Beaudet's avatar
Francis Beaudet committed
1019 1020 1021
					  controlPos.left, controlPos.top,
					  controlPos.right - controlPos.left,
					  controlPos.bottom - controlPos.top,
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
1022
					  hwnd, NULL, NULL, NULL);
Francis Beaudet's avatar
Francis Beaudet committed
1023 1024 1025
    }
    else
    {
1026
      SetWindowPos(infoPtr->hwndUpDown,
1027
		   NULL,
Francis Beaudet's avatar
Francis Beaudet committed
1028 1029 1030
		   controlPos.left, controlPos.top,
		   controlPos.right - controlPos.left,
		   controlPos.bottom - controlPos.top,
1031
		   SWP_SHOWWINDOW | SWP_NOZORDER);
Francis Beaudet's avatar
Francis Beaudet committed
1032
    }
Alexandre Julliard's avatar
Alexandre Julliard committed
1033 1034 1035 1036 1037 1038 1039 1040 1041

    /* 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;
1042
       tabwidth = TAB_GetItem(infoPtr, infoPtr->uNumItem - 1)->rect.right;
Alexandre Julliard's avatar
Alexandre Julliard committed
1043 1044 1045

       for(; maxRange > 0; maxRange--)
       {
1046
          if(tabwidth - TAB_GetItem(infoPtr,maxRange - 1)->rect.left > vsize)
Alexandre Julliard's avatar
Alexandre Julliard committed
1047 1048 1049 1050 1051 1052
             break;
       }

       if(maxRange == infoPtr->uNumItem)
          maxRange--;
    }
Francis Beaudet's avatar
Francis Beaudet committed
1053 1054 1055
  }
  else
  {
1056
    /* If we once had a scroll control... hide it */
Francis Beaudet's avatar
Francis Beaudet committed
1057 1058 1059
    if (infoPtr->hwndUpDown!=0)
      ShowWindow(infoPtr->hwndUpDown, SW_HIDE);
  }
Alexandre Julliard's avatar
Alexandre Julliard committed
1060
  if (infoPtr->hwndUpDown)
1061
     SendMessageW(infoPtr->hwndUpDown, UDM_SETRANGE32, 0, maxRange);
Francis Beaudet's avatar
Francis Beaudet committed
1062
}
Alexandre Julliard's avatar
Alexandre Julliard committed
1063

Francis Beaudet's avatar
Francis Beaudet committed
1064 1065 1066 1067 1068 1069 1070 1071
/******************************************************************************
 * 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
1072
 * don't, a scrolling control is added.
Francis Beaudet's avatar
Francis Beaudet committed
1073
 */
1074
static void TAB_SetItemBounds (TAB_INFO *infoPtr)
Francis Beaudet's avatar
Francis Beaudet committed
1075
{
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
1076 1077
  LONG        lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
  TEXTMETRICW fontMetrics;
1078
  UINT        curItem;
Francis Beaudet's avatar
Francis Beaudet committed
1079
  INT         curItemLeftPos;
1080
  INT         curItemRowCount;
Francis Beaudet's avatar
Francis Beaudet committed
1081 1082 1083
  HFONT       hFont, hOldFont;
  HDC         hdc;
  RECT        clientRect;
1084 1085 1086
  INT         iTemp;
  RECT*       rcItem;
  INT         iIndex;
1087
  INT         icon_width = 0;
Francis Beaudet's avatar
Francis Beaudet committed
1088 1089 1090 1091 1092

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

Francis Beaudet's avatar
Francis Beaudet committed
1095 1096 1097 1098 1099 1100 1101
  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.
   */
1102
  GetClientRect(infoPtr->hwnd, &clientRect);
1103

Gerard Patel's avatar
Gerard Patel committed
1104
  /* if TCS_VERTICAL then swap the height and width so this code places the
1105
     tabs along the top of the rectangle and we can just rotate them after
Gerard Patel's avatar
Gerard Patel committed
1106
     rather than duplicate all of the below code */
1107 1108 1109 1110 1111 1112 1113
  if(lStyle & TCS_VERTICAL)
  {
     iTemp = clientRect.bottom;
     clientRect.bottom = clientRect.right;
     clientRect.right = iTemp;
  }

1114 1115 1116 1117
  /* Now use hPadding and vPadding */
  infoPtr->uHItemPadding = infoPtr->uHItemPadding_s;
  infoPtr->uVItemPadding = infoPtr->uVItemPadding_s;
  
1118
  /* The leftmost item will be "0" aligned */
Francis Beaudet's avatar
Francis Beaudet committed
1119
  curItemLeftPos = 0;
Gerard Patel's avatar
Gerard Patel committed
1120
  curItemRowCount = infoPtr->uNumItem ? 1 : 0;
Francis Beaudet's avatar
Francis Beaudet committed
1121

1122
  if (!(infoPtr->fHeightSet))
1123
  {
1124 1125 1126
    int item_height;
    int icon_height = 0;

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

1130
    /* Get the icon height */
1131 1132 1133
    if (infoPtr->himl)
      ImageList_GetIconSize(infoPtr->himl, 0, &icon_height);

1134
    /* Take the highest between font or icon */
1135
    if (fontMetrics.tmHeight > icon_height)
1136
      item_height = fontMetrics.tmHeight + 2;
1137 1138 1139 1140
    else
      item_height = icon_height;

    /*
1141 1142
     * Make sure there is enough space for the letters + icon + growing the
     * selected item + extra space for the selected item.
1143
     */
1144
    infoPtr->tabHeight = item_height + 
1145
	                 ((lStyle & TCS_BUTTONS) ? 2 : 1) *
1146
                          infoPtr->uVItemPadding;
1147

1148 1149
    TRACE("tabH=%d, tmH=%ld, iconh=%d\n",
	  infoPtr->tabHeight, fontMetrics.tmHeight, icon_height);
1150 1151
  }

1152
  TRACE("client right=%ld\n", clientRect.right);
1153

1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165
  /* Get the icon width */
  if (infoPtr->himl)
  {
    ImageList_GetIconSize(infoPtr->himl, &icon_width, 0);

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

Francis Beaudet's avatar
Francis Beaudet committed
1166 1167
  for (curItem = 0; curItem < infoPtr->uNumItem; curItem++)
  {
1168 1169
    TAB_ITEM *curr = TAB_GetItem(infoPtr, curItem);
	
1170
    /* Set the leftmost position of the tab. */
1171
    curr->rect.left = curItemLeftPos;
Francis Beaudet's avatar
Francis Beaudet committed
1172

1173
    if (lStyle & TCS_FIXEDWIDTH)
1174
    {
1175
      curr->rect.right = curr->rect.left +
1176
        max(infoPtr->tabWidth, icon_width);
1177
    }
1178
    else if (!curr->pszText)
1179
    {
1180 1181 1182 1183 1184 1185
      /* 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;
1186

1187 1188 1189 1190 1191 1192 1193 1194 1195
        /* 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;
1196
      SIZE size;
1197
      /* Calculate how wide the tab is depending on the text it contains */
1198 1199
      GetTextExtentPoint32W(hdc, curr->pszText,
                            lstrlenW(curr->pszText), &size);
1200

1201 1202 1203 1204 1205 1206 1207 1208 1209 1210
      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;
      TRACE("for <%s>, l,r=%ld,%ld\n",
	  debugstr_w(curr->pszText), curr->rect.left, curr->rect.right);
1211
    }
Francis Beaudet's avatar
Francis Beaudet committed
1212

1213 1214 1215 1216
    /*
     * Check if this is a multiline tab control and if so
     * check to see if we should wrap the tabs
     *
1217
     * Wrap all these tabs. We will arrange them evenly later.
1218 1219 1220
     *
     */

1221
    if (((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)) &&
1222
        (curr->rect.right > 
1223
	(clientRect.right - CONTROL_BORDER_SIZEX - DISPLAY_AREA_PADDINGX)))
1224
    {
1225
        curr->rect.right -= curr->rect.left;
1226

1227
	curr->rect.left = 0;
1228
        curItemRowCount++;
1229 1230
	TRACE("wrapping <%s>, l,r=%ld,%ld\n", debugstr_w(curr->pszText),
	    curr->rect.left, curr->rect.right);
1231 1232
    }

1233 1234
    curr->rect.bottom = 0;
    curr->rect.top = curItemRowCount - 1;
1235

1236 1237
    TRACE("Rect: T %li, L %li, B %li, R %li\n", curr->rect.top,
	  curr->rect.left, curr->rect.bottom, curr->rect.right);
Francis Beaudet's avatar
Francis Beaudet committed
1238 1239 1240 1241 1242

    /*
     * The leftmost position of the next item is the rightmost position
     * of this one.
     */
1243
    if (lStyle & TCS_BUTTONS)
1244
    {
1245
      curItemLeftPos = curr->rect.right + BUTTON_SPACINGX;
1246 1247 1248
      if (lStyle & TCS_FLATBUTTONS)
        curItemLeftPos += FLAT_BTN_SPACINGX;
    }
1249
    else
1250
      curItemLeftPos = curr->rect.right;
Francis Beaudet's avatar
Francis Beaudet committed
1251 1252
  }

1253
  if (!((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)))
1254 1255 1256 1257
  {
    /*
     * Check if we need a scrolling control.
     */
1258
    infoPtr->needsScrolling = (curItemLeftPos + (2 * SELECTED_TAB_OFFSET) >
1259 1260
                               clientRect.right);

1261 1262
    /* Don't need scrolling, then update infoPtr->leftmostVisible */
    if(!infoPtr->needsScrolling)
1263
      infoPtr->leftmostVisible = 0;
1264
  }
1265 1266 1267 1268 1269 1270 1271 1272
  else
  {
    /*
     * No scrolling in Multiline or Vertical styles.
     */
    infoPtr->needsScrolling = FALSE;
    infoPtr->leftmostVisible = 0;
  }
1273
  TAB_SetupScrolling(infoPtr->hwnd, infoPtr, &clientRect);
1274

1275
  /* Set the number of rows */
1276
  infoPtr->uNumRows = curItemRowCount;
Yuxi Zhang's avatar
Yuxi Zhang committed
1277

1278
  /* Arrange all tabs evenly if style says so */
1279 1280 1281 1282
   if (!(lStyle & TCS_RAGGEDRIGHT) &&
       ((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)) &&
       (infoPtr->uNumItem > 0) &&
       (infoPtr->uNumRows > 1))
1283
   {
1284 1285
      INT tabPerRow,remTab,iRow;
      UINT iItm;
1286
      INT iCount=0;
1287 1288

      /*
1289
       * Ok windows tries to even out the rows. place the same
1290 1291 1292
       * number of tabs in each row. So lets give that a shot
       */

Gerard Patel's avatar
Gerard Patel committed
1293 1294
      tabPerRow = infoPtr->uNumItem / (infoPtr->uNumRows);
      remTab = infoPtr->uNumItem % (infoPtr->uNumRows);
1295 1296 1297 1298 1299

      for (iItm=0,iRow=0,iCount=0,curItemLeftPos=0;
           iItm<infoPtr->uNumItem;
           iItm++,iCount++)
      {
1300
          /* normalize the current rect */
1301 1302
          TAB_ITEM *curr = TAB_GetItem(infoPtr, iItm);
 
1303
          /* shift the item to the left side of the clientRect */
1304 1305
          curr->rect.right -= curr->rect.left;
          curr->rect.left = 0;
1306

1307
	  TRACE("r=%ld, cl=%d, cl.r=%ld, iCount=%d, iRow=%d, uNumRows=%d, remTab=%d, tabPerRow=%d\n",
1308
	      curr->rect.right, curItemLeftPos, clientRect.right,
1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328
	      iCount, iRow, infoPtr->uNumRows, remTab, tabPerRow);

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

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

1331
          /* shift the item to the right to place it as the next item in this row */
1332 1333 1334
          curr->rect.left += curItemLeftPos;
          curr->rect.right += curItemLeftPos;
          curr->rect.top = iRow;
1335
          if (lStyle & TCS_BUTTONS)
1336
	  {
1337
            curItemLeftPos = curr->rect.right + 1;
1338 1339 1340
            if (lStyle & TCS_FLATBUTTONS)
	      curItemLeftPos += FLAT_BTN_SPACINGX;
	  }
1341
          else
1342
            curItemLeftPos = curr->rect.right;
1343

1344
	  TRACE("arranging <%s>, l,r=%ld,%ld, row=%ld\n",
1345 1346
	      debugstr_w(curr->pszText), curr->rect.left,
	      curr->rect.right, curr->rect.top);
1347
      }
1348

1349 1350 1351 1352
      /*
       * Justify the rows
       */
      {
1353 1354 1355
	INT widthDiff, iIndexStart=0, iIndexEnd=0;
	INT remainder;
	INT iCount=0;
1356

1357
        while(iIndexStart < infoPtr->uNumItem)
1358
        {
1359 1360
          TAB_ITEM *start = TAB_GetItem(infoPtr, iIndexStart);

1361
          /*
1362
           * find the index of the row
1363 1364 1365 1366
           */
          /* find the first item on the next row */
          for (iIndexEnd=iIndexStart;
              (iIndexEnd < infoPtr->uNumItem) &&
1367 1368
 	      (TAB_GetItem(infoPtr, iIndexEnd)->rect.top ==
                start->rect.top) ;
1369 1370 1371 1372 1373 1374 1375 1376 1377 1378
              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) -
1379
			TAB_GetItem(infoPtr, iIndexEnd - 1)->rect.right;
1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390

	  /* 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++)
	    {
1391 1392 1393 1394
              TAB_ITEM *item = TAB_GetItem(infoPtr, iIndex);

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

1396
	      TRACE("adjusting 1 <%s>, l,r=%ld,%ld\n",
1397 1398
		  debugstr_w(item->pszText),
		  item->rect.left, item->rect.right);
1399

1400
	    }
1401
	    TAB_GetItem(infoPtr, iIndex - 1)->rect.right += remainder;
1402 1403 1404
	  }
	  else /* we have only one item on this row, make it take up the entire row */
	  {
1405 1406
	    start->rect.left = clientRect.left;
	    start->rect.right = clientRect.right - 4;
1407

1408
	    TRACE("adjusting 2 <%s>, l,r=%ld,%ld\n",
1409 1410
		debugstr_w(start->pszText),
		start->rect.left, start->rect.right);
1411

1412
	  }
1413

1414

1415 1416
	  iIndexStart = iIndexEnd;
	}
1417 1418 1419
      }
  }

1420 1421 1422 1423 1424 1425
  /* if TCS_VERTICAL rotate the tabs so they are along the side of the clientRect */
  if(lStyle & TCS_VERTICAL)
  {
    RECT rcOriginal;
    for(iIndex = 0; iIndex < infoPtr->uNumItem; iIndex++)
    {
1426
      rcItem = &TAB_GetItem(infoPtr, iIndex)->rect;
1427 1428 1429

      rcOriginal = *rcItem;

1430 1431
      /* this is rotating the items by 90 degrees clockwise around the center of the control */
      rcItem->top = (rcOriginal.left - clientRect.left);
1432 1433 1434 1435 1436 1437
      rcItem->bottom = rcItem->top + (rcOriginal.right - rcOriginal.left);
      rcItem->left = rcOriginal.top;
      rcItem->right = rcOriginal.bottom;
    }
  }

1438 1439
  TAB_EnsureSelectionVisible(infoPtr);
  TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
1440 1441

  /* Cleanup */
Francis Beaudet's avatar
Francis Beaudet committed
1442
  SelectObject (hdc, hOldFont);
1443
  ReleaseDC (infoPtr->hwnd, hdc);
Francis Beaudet's avatar
Francis Beaudet committed
1444 1445
}

1446 1447 1448 1449

static void
TAB_EraseTabInterior
    (
1450
    TAB_INFO*   infoPtr,
1451 1452 1453 1454 1455
    HDC         hdc,
    INT         iItem,
    RECT*       drawRect
    )
{
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
1456
    LONG     lStyle  = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500
    HBRUSH   hbr = CreateSolidBrush (comctl32_color.clrBtnFace);
    BOOL     deleteBrush = TRUE;
    RECT     rTemp = *drawRect;

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

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

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

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

    }
    else /* !TCS_BUTTONS */
    {
1501 1502
        if (!GetWindowTheme (infoPtr->hwnd))
	    FillRect(hdc, &rTemp, hbr);
1503 1504 1505 1506 1507 1508
    }

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

1509 1510 1511 1512 1513
/******************************************************************************
 * TAB_DrawItemInterior
 *
 * This method is used to draw the interior (text and icon) of a single tab
 * into the tab control.
1514
 */
1515 1516 1517
static void
TAB_DrawItemInterior
  (
1518
  TAB_INFO*   infoPtr,
1519 1520 1521 1522 1523
  HDC         hdc,
  INT         iItem,
  RECT*       drawRect
  )
{
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
1524
  LONG      lStyle  = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1525 1526 1527

  RECT localRect;

1528
  HPEN   htextPen;
1529 1530
  HPEN   holdPen;
  INT    oldBkMode;
1531 1532
  HFONT  hOldFont;
  
1533
/*  if (drawRect == NULL) */
1534 1535 1536 1537 1538 1539 1540 1541
  {
    BOOL isVisible;
    RECT itemRect;
    RECT selectedRect;

    /*
     * Get the rectangle for the item.
     */
1542
    isVisible = TAB_InternalGetItemRect(infoPtr, iItem, &itemRect, &selectedRect);
1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556
    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.
     */
1557 1558 1559 1560 1561
    if (iItem == infoPtr->iSelected)
      *drawRect = selectedRect;
    else
      *drawRect = itemRect;
        
1562 1563 1564 1565
    if (lStyle & TCS_BUTTONS)
    {
      if (iItem == infoPtr->iSelected)
      {
1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576
	drawRect->left   += 4;
	drawRect->top    += 4;
	drawRect->right  -= 4;
	drawRect->bottom -= 1;
      }
      else
      {
	drawRect->left   += 2;
	drawRect->top    += 2;
	drawRect->right  -= 2;
	drawRect->bottom -= 2;
1577 1578 1579 1580
      }
    }
    else
    {
1581
      if ((lStyle & TCS_VERTICAL) && (lStyle & TCS_BOTTOM))
1582
      {
1583
        if (iItem != infoPtr->iSelected)
1584
	{
1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612
	  drawRect->left   += 2;
	  drawRect->top    += 2;
	  drawRect->bottom -= 2;
	}
      }
      else if (lStyle & TCS_VERTICAL)
      {
        if (iItem == infoPtr->iSelected)
	{
	  drawRect->right  += 1;
	}
	else
	{
	  drawRect->top    += 2;
	  drawRect->right  -= 2;
	  drawRect->bottom -= 2;
	}
      }
      else if (lStyle & TCS_BOTTOM)
      {
        if (iItem == infoPtr->iSelected)
	{
	  drawRect->top    -= 2;
	}
	else
	{
	  InflateRect(drawRect, -2, -2);
          drawRect->bottom += 2;
1613 1614
	}
      }
1615
      else
1616
      {
1617 1618 1619 1620 1621 1622
        if (iItem == infoPtr->iSelected)
	{
	  drawRect->bottom += 3;
	}
	else
	{
1623
	  drawRect->bottom -= 2;
1624 1625
	  InflateRect(drawRect, -2, 0);
	}
1626
      }
1627 1628
    }
  }
1629 1630 1631 1632
  TRACE("drawRect=(%ld,%ld)-(%ld,%ld)\n",
	  drawRect->left, drawRect->top, drawRect->right, drawRect->bottom);

  /* Clear interior */
1633
  TAB_EraseTabInterior (infoPtr, hdc, iItem, drawRect);
1634 1635 1636

  /* Draw the focus rectangle */
  if (!(lStyle & TCS_FOCUSNEVER) &&
1637
      (GetFocus() == infoPtr->hwnd) &&
1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651
      (iItem == infoPtr->uFocus) )
  {
    RECT rFocus = *drawRect;
    InflateRect(&rFocus, -3, -3);
    if (lStyle & TCS_BOTTOM && !(lStyle & TCS_VERTICAL))
      rFocus.top -= 3;
    if (lStyle & TCS_BUTTONS)
    {
      rFocus.left -= 3;
      rFocus.top -= 3;
    }

    DrawFocusRect(hdc, &rFocus);
  }
1652 1653 1654 1655

  /*
   * Text pen
   */
1656
  htextPen = CreatePen( PS_SOLID, 1, GetSysColor(COLOR_BTNTEXT) );
1657 1658
  holdPen  = SelectObject(hdc, htextPen);
  hOldFont = SelectObject(hdc, infoPtr->hFont);
1659

1660 1661 1662
  /*
   * Setup for text output
  */
1663
  oldBkMode = SetBkMode(hdc, TRANSPARENT);
1664 1665 1666 1667
  if (!GetWindowTheme (infoPtr->hwnd) || (lStyle & TCS_BUTTONS))
      SetTextColor(hdc, (((lStyle & TCS_HOTTRACK) && (iItem == infoPtr->iHotTracked) 
                          && !(lStyle & TCS_FLATBUTTONS)) 
                        | (TAB_GetItem(infoPtr, iItem)->dwState & TCIS_HIGHLIGHTED)) ?
1668
                        comctl32_color.clrHighlight : comctl32_color.clrBtnText);
1669 1670 1671 1672

  /*
   * if owner draw, tell the owner to draw
   */
1673
  if ((lStyle & TCS_OWNERDRAWFIXED) && GetParent(infoPtr->hwnd))
1674 1675 1676 1677
  {
    DRAWITEMSTRUCT dis;
    UINT id;

1678 1679 1680 1681 1682 1683 1684 1685
    drawRect->top += 2;
    drawRect->right -= 1;
    if ( iItem == infoPtr->iSelected )
    {
        drawRect->right -= 1;
        drawRect->left += 1;
    }

1686 1687 1688
    /*
     * get the control id
     */
1689
    id = (UINT)GetWindowLongPtrW( infoPtr->hwnd, GWLP_ID );
1690

1691
    /*
1692 1693
     * put together the DRAWITEMSTRUCT
     */
1694 1695 1696 1697
    dis.CtlType    = ODT_TAB;
    dis.CtlID      = id;
    dis.itemID     = iItem;
    dis.itemAction = ODA_DRAWENTIRE;
1698
    dis.itemState = 0;
1699
    if ( iItem == infoPtr->iSelected )
1700 1701 1702
      dis.itemState |= ODS_SELECTED;
    if (infoPtr->uFocus == iItem) 
      dis.itemState |= ODS_FOCUS;
1703
    dis.hwndItem = infoPtr->hwnd;
1704
    dis.hDC      = hdc;
1705
    CopyRect(&dis.rcItem,drawRect);
1706
    dis.itemData = (ULONG_PTR)TAB_GetItem(infoPtr, iItem)->extra;
1707 1708 1709 1710

    /*
     * send the draw message
     */
1711
    SendMessageW( infoPtr->hwndNotify, WM_DRAWITEM, (WPARAM)id, (LPARAM)&dis );
1712 1713 1714
  }
  else
  {
1715
    TAB_ITEM *item = TAB_GetItem(infoPtr, iItem);
1716 1717 1718 1719 1720
    RECT rcTemp;
    RECT rcImage;

    /* used to center the icon and text in the tab */
    RECT rcText;
1721
    INT center_offset_h, center_offset_v;
1722 1723 1724 1725 1726 1727

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

    rcTemp = *drawRect;

1728 1729
    rcText.left = rcText.top = rcText.right = rcText.bottom = 0;

1730
    /* get the rectangle that the text fits in */
1731
    if (item->pszText)
1732
    {
1733
      DrawTextW(hdc, item->pszText, -1, &rcText, DT_CALCRECT);
1734
    }
1735 1736 1737 1738 1739
    /*
     * If not owner draw, then do the drawing ourselves.
     *
     * Draw the icon.
     */
1740
    if (infoPtr->himl && (item->mask & TCIF_IMAGE))
1741
    {
1742 1743 1744
      INT cx;
      INT cy;
      
1745 1746 1747
      ImageList_GetIconSize(infoPtr->himl, &cx, &cy);

      if(lStyle & TCS_VERTICAL)
1748 1749
      {
        center_offset_h = ((drawRect->bottom - drawRect->top) - (cy + infoPtr->uHItemPadding + (rcText.right  - rcText.left))) / 2;
1750
        center_offset_v = (drawRect->left + (drawRect->right - drawRect->left) - cx) / 2;
1751
      }
1752
      else
1753 1754
      {
        center_offset_h = ((drawRect->right - drawRect->left) - (cx + infoPtr->uHItemPadding + (rcText.right  - rcText.left))) / 2;
1755
        center_offset_v = (drawRect->top + (drawRect->bottom - drawRect->top) - cy) / 2;
1756 1757
      }

1758 1759 1760 1761 1762 1763
      /* 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;

1764 1765
      if (lStyle & TCS_FIXEDWIDTH && lStyle & (TCS_FORCELABELLEFT | TCS_FORCEICONLEFT))
	center_offset_h = infoPtr->uHItemPadding;
1766

1767 1768 1769
      if (center_offset_h < 2)
        center_offset_h = 2;
	
1770 1771 1772
      if (center_offset_v < 0)
        center_offset_v = 0;
	
1773
      TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%ld,%ld)-(%ld,%ld), textlen=%ld\n",
1774
	  debugstr_w(item->pszText), center_offset_h, center_offset_v,
1775 1776 1777
	  drawRect->left, drawRect->top, drawRect->right, drawRect->bottom,
	  (rcText.right-rcText.left));

1778 1779
      if((lStyle & TCS_VERTICAL) && (lStyle & TCS_BOTTOM))
      {
1780
        rcImage.top = drawRect->top + center_offset_h;
1781 1782 1783 1784
	/* 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;
1785
        drawRect->top += cy + infoPtr->uHItemPadding;
1786 1787 1788
      }
      else if(lStyle & TCS_VERTICAL)
      {
1789 1790
        rcImage.top  = drawRect->bottom - cy - center_offset_h;
	rcImage.left = drawRect->left + center_offset_v;
1791
        drawRect->bottom -= cy + infoPtr->uHItemPadding;
1792 1793 1794
      }
      else /* normal style, whether TCS_BOTTOM or not */
      {
1795
        rcImage.left = drawRect->left + center_offset_h;
1796
	rcImage.top = drawRect->top + center_offset_v;
1797
        drawRect->left += cx + infoPtr->uHItemPadding;
1798
      }
1799

1800
      TRACE("drawing image=%d, left=%ld, top=%ld\n",
1801
	    item->iImage, rcImage.left, rcImage.top-1);
1802 1803 1804
      ImageList_Draw
        (
        infoPtr->himl,
1805
        item->iImage,
1806
        hdc,
1807
        rcImage.left,
1808
        rcImage.top,
1809 1810
        ILD_NORMAL
        );
1811
    }
1812 1813 1814 1815 1816

    /* Now position text */
    if (lStyle & TCS_FIXEDWIDTH && lStyle & TCS_FORCELABELLEFT)
      center_offset_h = infoPtr->uHItemPadding;
    else
1817
      if(lStyle & TCS_VERTICAL)
1818
        center_offset_h = ((drawRect->bottom - drawRect->top) - (rcText.right - rcText.left)) / 2;
1819
      else
1820
        center_offset_h = ((drawRect->right - drawRect->left) - (rcText.right - rcText.left)) / 2;
1821

1822 1823
    if(lStyle & TCS_VERTICAL)
    {
1824 1825 1826 1827 1828
      if(lStyle & TCS_BOTTOM)
        drawRect->top+=center_offset_h;
      else
        drawRect->bottom-=center_offset_h;

1829
      center_offset_v = ((drawRect->right - drawRect->left) - (rcText.bottom - rcText.top)) / 2;
1830
    }
1831
    else
1832
    {
1833
      drawRect->left += center_offset_h;
1834
      center_offset_v = ((drawRect->bottom - drawRect->top) - (rcText.bottom - rcText.top)) / 2;
1835 1836
    }

1837 1838 1839 1840 1841 1842
    /* 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;

1843 1844 1845 1846 1847 1848 1849 1850
    if (center_offset_v < 0)
      center_offset_v = 0;

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

1851
    /* Draw the text */
1852 1853
    if(lStyle & TCS_VERTICAL) /* if we are vertical rotate the text and each character */
    {
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
1854 1855
      static const WCHAR ArialW[] = { 'A','r','i','a','l',0 };
      LOGFONTW logfont;
1856 1857 1858 1859
      HFONT hFont = 0;
      INT nEscapement = 900;
      INT nOrientation = 900;

1860 1861 1862 1863 1864 1865
      if(lStyle & TCS_BOTTOM)
      {
        nEscapement = -900;
        nOrientation = -900;
      }

1866 1867
      /* 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
1868
      if (!GetObjectW((infoPtr->hFont) ?
1869
                infoPtr->hFont : GetStockObject(SYSTEM_FONT),
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
1870
                sizeof(LOGFONTW),&logfont))
1871
      {
1872
        INT iPointSize = 9;
1873

Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
1874
        lstrcpyW(logfont.lfFaceName, ArialW);
1875
        logfont.lfHeight = -MulDiv(iPointSize, GetDeviceCaps(hdc, LOGPIXELSY),
1876 1877 1878 1879 1880 1881
                                    72);
        logfont.lfWeight = FW_NORMAL;
        logfont.lfItalic = 0;
        logfont.lfUnderline = 0;
        logfont.lfStrikeOut = 0;
      }
1882 1883 1884

      logfont.lfEscapement = nEscapement;
      logfont.lfOrientation = nOrientation;
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
1885
      hFont = CreateFontIndirectW(&logfont);
1886
      SelectObject(hdc, hFont);
1887

1888
      if (item->pszText)
1889 1890 1891 1892 1893 1894
      {
        ExtTextOutW(hdc,
        (lStyle & TCS_BOTTOM) ? drawRect->right : drawRect->left,
        (!(lStyle & TCS_BOTTOM)) ? drawRect->bottom : drawRect->top,
        ETO_CLIPPED,
        drawRect,
1895 1896
        item->pszText,
        lstrlenW(item->pszText),
1897 1898
        0);
      }
1899 1900

      DeleteObject(hFont);
1901 1902 1903
    }
    else
    {
1904
      TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%ld,%ld)-(%ld,%ld), textlen=%ld\n",
1905
	  debugstr_w(item->pszText), center_offset_h, center_offset_v,
1906 1907
	  drawRect->left, drawRect->top, drawRect->right, drawRect->bottom,
	  (rcText.right-rcText.left));
1908
      if (item->pszText)
1909 1910 1911 1912
      {
        DrawTextW
        (
          hdc,
1913 1914
          item->pszText,
          lstrlenW(item->pszText),
1915 1916
          drawRect,
          DT_LEFT | DT_SINGLELINE
1917
        );
1918
      }
1919
    }
1920 1921

    *drawRect = rcTemp; /* restore drawRect */
1922 1923 1924 1925 1926
  }

  /*
  * Cleanup
  */
1927
  SelectObject(hdc, hOldFont);
1928 1929
  SetBkMode(hdc, oldBkMode);
  SelectObject(hdc, holdPen);
1930
  DeleteObject( htextPen );
1931 1932
}

Francis Beaudet's avatar
Francis Beaudet committed
1933 1934 1935 1936
/******************************************************************************
 * TAB_DrawItem
 *
 * This method is used to draw a single tab into the tab control.
1937
 */
Francis Beaudet's avatar
Francis Beaudet committed
1938
static void TAB_DrawItem(
1939
  TAB_INFO *infoPtr,
1940
  HDC  hdc,
Francis Beaudet's avatar
Francis Beaudet committed
1941 1942
  INT  iItem)
{
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
1943
  LONG      lStyle  = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
Francis Beaudet's avatar
Francis Beaudet committed
1944 1945 1946
  RECT      itemRect;
  RECT      selectedRect;
  BOOL      isVisible;
1947 1948 1949 1950
  RECT      r, fillRect, r1;
  INT       clRight = 0;
  INT       clBottom = 0;
  COLORREF  bkgnd, corner;
1951
  HTHEME    theme;
Francis Beaudet's avatar
Francis Beaudet committed
1952 1953 1954 1955

  /*
   * Get the rectangle for the item.
   */
1956
  isVisible = TAB_InternalGetItemRect(infoPtr,
Francis Beaudet's avatar
Francis Beaudet committed
1957 1958 1959 1960 1961 1962
				      iItem,
				      &itemRect,
				      &selectedRect);

  if (isVisible)
  {
1963 1964 1965
    RECT rUD, rC;

    /* Clip UpDown control to not draw over it */
1966 1967
    if (infoPtr->needsScrolling)
    {
1968
      GetWindowRect(infoPtr->hwnd, &rC);
1969 1970 1971
      GetWindowRect(infoPtr->hwndUpDown, &rUD);
      ExcludeClipRect(hdc, rUD.left - rC.left, rUD.top - rC.top, rUD.right - rC.left, rUD.bottom - rC.top);
    }
1972

1973 1974
    /* If you need to see what the control is doing,
     * then override these variables. They will change what
1975
     * fill colors are used for filling the tabs, and the
1976 1977 1978 1979
     * corners when drawing the edge.
     */
    bkgnd = comctl32_color.clrBtnFace;
    corner = comctl32_color.clrBtnFace;
Francis Beaudet's avatar
Francis Beaudet committed
1980

1981
    if (lStyle & TCS_BUTTONS)
Francis Beaudet's avatar
Francis Beaudet committed
1982
    {
1983
      /* Get item rectangle */
1984 1985
      r = itemRect;

1986
      /* Separators between flat buttons */
1987
      if (lStyle & TCS_FLATBUTTONS)
1988
      {
1989 1990 1991
	r1 = r;
	r1.right += (FLAT_BTN_SPACINGX -2);
	DrawEdge(hdc, &r1, EDGE_ETCHED, BF_RIGHT);
1992 1993
      }

1994 1995
      if (iItem == infoPtr->iSelected)
      {
1996
	DrawEdge(hdc, &r, EDGE_SUNKEN, BF_SOFT|BF_RECT);
1997 1998
	
	OffsetRect(&r, 1, 1);
1999
      }
2000
      else  /* ! selected */
2001
      {
2002
	if (!(lStyle & TCS_FLATBUTTONS))
2003
	  DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_RECT);
2004
      }
Francis Beaudet's avatar
Francis Beaudet committed
2005
    }
2006
    else /* !TCS_BUTTONS */
Francis Beaudet's avatar
Francis Beaudet committed
2007
    {
2008 2009
      /* We draw a rectangle of different sizes depending on the selection
       * state. */
2010 2011
      if (iItem == infoPtr->iSelected) {
	RECT rect;
2012
	GetClientRect (infoPtr->hwnd, &rect);
2013 2014
	clRight = rect.right;
	clBottom = rect.bottom;
2015
        r = selectedRect;
2016
      }
2017 2018
      else
        r = itemRect;
Francis Beaudet's avatar
Francis Beaudet committed
2019

2020
      /*
2021
       * Erase the background. (Delay it but setup rectangle.)
2022 2023
       * This is necessary when drawing the selected item since it is larger
       * than the others, it might overlap with stuff already drawn by the
2024
       * other tabs
2025
       */
2026
      fillRect = r;
2027

2028 2029 2030 2031 2032 2033 2034
      /* Draw themed tabs - but only if they are at the top.
       * Windows draws even side or bottom tabs themed, with wacky results.
       * However, since in Wine apps may get themed that did not opt in via
       * a manifest avoid theming when we know the result will be wrong */
      if ((theme = GetWindowTheme (infoPtr->hwnd)) 
          && ((lStyle & (TCS_VERTICAL | TCS_BOTTOM)) == 0))
      {
2035
          static const int partIds[8] = {
2036 2037 2038 2039 2040 2041 2042 2043 2044 2045 2046 2047 2048 2049 2050 2051 2052 2053 2054 2055 2056 2057 2058 2059 2060 2061 2062 2063 2064 2065 2066 2067 2068 2069 2070 2071 2072 2073 2074
              /* Normal item */
              TABP_TABITEM,
              TABP_TABITEMLEFTEDGE,
              TABP_TABITEMRIGHTEDGE,
              TABP_TABITEMBOTHEDGE,
              /* Selected tab */
              TABP_TOPTABITEM,
              TABP_TOPTABITEMLEFTEDGE,
              TABP_TOPTABITEMRIGHTEDGE,
              TABP_TOPTABITEMBOTHEDGE,
          };
          int partIndex = 0;
          int stateId = TIS_NORMAL;

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

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

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

          DrawThemeBackground (theme, hdc, partIds[partIndex], stateId, &r, NULL);
          GetThemeBackgroundContentRect (theme, hdc, partIds[partIndex], stateId, &r, &r);
      }
      else if(lStyle & TCS_VERTICAL)
2075
      {
2076 2077 2078 2079 2080 2081 2082 2083
	/* These are for adjusting the drawing of a Selected tab      */
	/* The initial values are for the normal case of non-Selected */
	int ZZ = 1;   /* Do not strech if selected */
	if (iItem == infoPtr->iSelected) {
	    ZZ = 0;

	    /* if leftmost draw the line longer */
	    if(selectedRect.top == 0)
2084
		fillRect.top += CONTROL_BORDER_SIZEY;
2085 2086
	    /* if rightmost draw the line longer */
	    if(selectedRect.bottom == clBottom)
2087
		fillRect.bottom -= CONTROL_BORDER_SIZEY;
2088 2089
	}

2090 2091
        if (lStyle & TCS_BOTTOM)
        {
2092 2093 2094
	  /* Adjust both rectangles to match native */
	  r.left += (1-ZZ);

2095
	  TRACE("<right> item=%d, fill=(%ld,%ld)-(%ld,%ld), edge=(%ld,%ld)-(%ld,%ld)\n",
2096 2097 2098 2099 2100 2101
		iItem,
		fillRect.left,fillRect.top,fillRect.right,fillRect.bottom,
		r.left,r.top,r.right,r.bottom);

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

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

2107
	  /* Now erase the top corner and draw diagonal edge */
2108 2109 2110 2111 2112
	  SetBkColor(hdc, corner);
	  r1.left = r.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
2113
	  ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2114 2115 2116
	  r1.right--;
	  DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPLEFT);

2117
	  /* Now erase the bottom corner and draw diagonal edge */
2118 2119 2120 2121
	  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
2122
	  ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2123 2124 2125 2126 2127 2128 2129 2130 2131 2132
	  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);
	  }

2133 2134 2135
        }
        else
        {
2136
	  TRACE("<left> item=%d, fill=(%ld,%ld)-(%ld,%ld), edge=(%ld,%ld)-(%ld,%ld)\n",
2137 2138 2139 2140 2141 2142
		iItem,
		fillRect.left,fillRect.top,fillRect.right,fillRect.bottom,
		r.left,r.top,r.right,r.bottom);

	  /* Clear interior */
	  SetBkColor(hdc, bkgnd);
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
2143
	  ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2144 2145 2146 2147

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

2148
	  /* Now erase the top corner and draw diagonal edge */
2149 2150 2151 2152 2153
	  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
2154
	  ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2155 2156 2157
	  r1.left++;
	  DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPRIGHT);

2158
	  /* Now erase the bottom corner and draw diagonal edge */
2159 2160 2161 2162
	  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
2163
	  ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2164 2165
	  r1.left++;
	  DrawEdge(hdc, &r1, EDGE_SUNKEN, BF_DIAGONAL_ENDTOPLEFT);
2166
        }
2167
      }
2168
      else  /* ! TCS_VERTICAL */
2169
      {
2170 2171 2172 2173 2174
	/* 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)
2175
		fillRect.left += CONTROL_BORDER_SIZEX;
2176 2177
	    /* if rightmost draw the line longer */
	    if(selectedRect.right == clRight)
2178
		fillRect.right -= CONTROL_BORDER_SIZEX;
2179 2180
	}

2181 2182
        if (lStyle & TCS_BOTTOM)
        {
2183
	  /* Adjust both rectangles for topmost row */
2184
	  if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2185 2186 2187 2188
	  {
	    fillRect.top -= 2;
	    r.top -= 1;
	  }
2189

2190
	  TRACE("<bottom> item=%d, fill=(%ld,%ld)-(%ld,%ld), edge=(%ld,%ld)-(%ld,%ld)\n",
2191 2192 2193 2194 2195 2196
		iItem,
		fillRect.left,fillRect.top,fillRect.right,fillRect.bottom,
		r.left,r.top,r.right,r.bottom);

	  /* Clear interior */
	  SetBkColor(hdc, bkgnd);
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
2197
	  ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2198 2199 2200 2201

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

2202
	  /* Now erase the righthand corner and draw diagonal edge */
2203 2204 2205 2206 2207
	  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
2208
	  ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2209 2210 2211
	  r1.bottom--;
	  DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMLEFT);

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

2221 2222 2223 2224 2225 2226
	  if (iItem == infoPtr->iSelected)
	  {
	    r.top += 2;
	    r.left += 1;
	    if (selectedRect.left == 0)
	    {
2227 2228 2229 2230
	      r1 = r;
	      r1.bottom = r1.top;
	      r1.top--;
	      DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_LEFT);
2231
	    }
2232 2233
	  }

2234 2235 2236
        }
        else
        {
2237
	  /* Adjust both rectangles for bottommost row */
2238
	  if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2239 2240 2241 2242
	  {
	    fillRect.bottom += 3;
	    r.bottom += 2;
	  }
2243

2244
	  TRACE("<top> item=%d, fill=(%ld,%ld)-(%ld,%ld), edge=(%ld,%ld)-(%ld,%ld)\n",
2245 2246 2247 2248 2249 2250
		iItem,
		fillRect.left,fillRect.top,fillRect.right,fillRect.bottom,
		r.left,r.top,r.right,r.bottom);

	  /* Clear interior */
	  SetBkColor(hdc, bkgnd);
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
2251
	  ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2252 2253 2254 2255

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

2256
	  /* Now erase the righthand corner and draw diagonal edge */
2257 2258 2259 2260 2261
	  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
2262
	  ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2263 2264 2265
	  r1.top++;
	  DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMRIGHT);

2266
	  /* Now erase the lefthand corner and draw diagonal edge */
2267 2268 2269 2270
	  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
2271
	  ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2272 2273
	  r1.top++;
	  DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPRIGHT);
2274
        }
2275 2276
      }
    }
2277

2278 2279
    TAB_DumpItemInternal(infoPtr, iItem);

2280
    /* This modifies r to be the text rectangle. */
2281
    TAB_DrawItemInterior(infoPtr, hdc, iItem, &r);
Francis Beaudet's avatar
Francis Beaudet committed
2282
  }
Alexandre Julliard's avatar
Alexandre Julliard committed
2283 2284
}

Francis Beaudet's avatar
Francis Beaudet committed
2285 2286 2287 2288 2289
/******************************************************************************
 * TAB_DrawBorder
 *
 * This method is used to draw the raised border around the tab control
 * "content" area.
2290
 */
2291
static void TAB_DrawBorder (TAB_INFO *infoPtr, HDC hdc)
Alexandre Julliard's avatar
Alexandre Julliard committed
2292
{
Francis Beaudet's avatar
Francis Beaudet committed
2293
  RECT rect;
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
2294
  DWORD lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
2295
  HTHEME theme = GetWindowTheme (infoPtr->hwnd);
Alexandre Julliard's avatar
Alexandre Julliard committed
2296

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

Francis Beaudet's avatar
Francis Beaudet committed
2299 2300 2301
  /*
   * Adjust for the style
   */
Gerard Patel's avatar
Gerard Patel committed
2302 2303

  if (infoPtr->uNumItem)
Francis Beaudet's avatar
Francis Beaudet committed
2304
  {
Gerard Patel's avatar
Gerard Patel committed
2305
    if ((lStyle & TCS_BOTTOM) && !(lStyle & TCS_VERTICAL))
2306
      rect.bottom -= infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
Gerard Patel's avatar
Gerard Patel committed
2307
    else if((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
2308
      rect.right  -= infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
Gerard Patel's avatar
Gerard Patel committed
2309
    else if(lStyle & TCS_VERTICAL)
2310
      rect.left   += infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
Gerard Patel's avatar
Gerard Patel committed
2311
    else /* not TCS_VERTICAL and not TCS_BOTTOM */
2312
      rect.top    += infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
Francis Beaudet's avatar
Francis Beaudet committed
2313
  }
Alexandre Julliard's avatar
Alexandre Julliard committed
2314

2315
  TRACE("border=(%ld,%ld)-(%ld,%ld)\n",
2316 2317
	rect.left, rect.top, rect.right, rect.bottom);

2318 2319 2320 2321
  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
2322 2323
}

Francis Beaudet's avatar
Francis Beaudet committed
2324 2325 2326 2327
/******************************************************************************
 * TAB_Refresh
 *
 * This method repaints the tab control..
2328
 */
2329
static void TAB_Refresh (TAB_INFO *infoPtr, HDC hdc)
Alexandre Julliard's avatar
Alexandre Julliard committed
2330
{
Francis Beaudet's avatar
Francis Beaudet committed
2331 2332
  HFONT hOldFont;
  INT i;
Alexandre Julliard's avatar
Alexandre Julliard committed
2333

Francis Beaudet's avatar
Francis Beaudet committed
2334 2335
  if (!infoPtr->DoRedraw)
    return;
Alex Priem's avatar
Alex Priem committed
2336

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

Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
2339
  if (GetWindowLongW(infoPtr->hwnd, GWL_STYLE) & TCS_BUTTONS)
Francis Beaudet's avatar
Francis Beaudet committed
2340
  {
2341
    for (i = 0; i < infoPtr->uNumItem; i++)
2342
      TAB_DrawItem (infoPtr, hdc, i);
Francis Beaudet's avatar
Francis Beaudet committed
2343
  }
2344 2345
  else
  {
2346
    /* Draw all the non selected item first */
2347
    for (i = 0; i < infoPtr->uNumItem; i++)
2348 2349
    {
      if (i != infoPtr->iSelected)
2350
	TAB_DrawItem (infoPtr, hdc, i);
2351
    }
Francis Beaudet's avatar
Francis Beaudet committed
2352

2353 2354
    /* Now, draw the border, draw it before the selected item
     * since the selected item overwrites part of the border. */
2355
    TAB_DrawBorder (infoPtr, hdc);
Francis Beaudet's avatar
Francis Beaudet committed
2356

2357
    /* Then, draw the selected item */
2358
    TAB_DrawItem (infoPtr, hdc, infoPtr->iSelected);
2359

2360 2361
    /* If we haven't set the current focus yet, set it now.
     * Only happens when we first paint the tab controls */
2362
    if (infoPtr->uFocus == -1)
2363
      TAB_SetCurFocus(infoPtr, infoPtr->iSelected);
2364
  }
Francis Beaudet's avatar
Francis Beaudet committed
2365 2366

  SelectObject (hdc, hOldFont);
Alexandre Julliard's avatar
Alexandre Julliard committed
2367 2368
}

2369
static inline DWORD TAB_GetRowCount (const TAB_INFO *infoPtr)
2370 2371 2372 2373
{
  return infoPtr->uNumRows;
}

2374
static inline LRESULT TAB_SetRedraw (TAB_INFO *infoPtr, BOOL doRedraw)
Alex Priem's avatar
Alex Priem committed
2375
{
2376
  infoPtr->DoRedraw = doRedraw;
Francis Beaudet's avatar
Francis Beaudet committed
2377
  return 0;
Alex Priem's avatar
Alex Priem committed
2378 2379
}

Francis Beaudet's avatar
Francis Beaudet committed
2380 2381 2382 2383 2384 2385 2386 2387
/******************************************************************************
 * 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
2388
{
Alexandre Julliard's avatar
Alexandre Julliard committed
2389
  INT iSelected = infoPtr->iSelected;
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
2390
  LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
2391 2392
  INT iOrigLeftmostVisible = infoPtr->leftmostVisible;

2393 2394
  /* set the items row to the bottommost row or topmost row depending on
   * style */
Gerard Patel's avatar
Gerard Patel committed
2395
  if ((infoPtr->uNumRows > 1) && !(lStyle & TCS_BUTTONS))
2396
  {
2397
      TAB_ITEM *selected = TAB_GetItem(infoPtr, iSelected);
2398
      INT newselected;
2399 2400
      INT iTargetRow;

2401
      if(lStyle & TCS_VERTICAL)
2402
        newselected = selected->rect.left;
2403
      else
2404
        newselected = selected->rect.top;
2405

Gerard Patel's avatar
Gerard Patel committed
2406 2407 2408
      /* the target row is always (number of rows - 1)
         as row 0 is furthest from the clientRect */
      iTargetRow = infoPtr->uNumRows - 1;
2409 2410 2411

      if (newselected != iTargetRow)
      {
2412
         UINT i;
2413 2414 2415 2416 2417
         if(lStyle & TCS_VERTICAL)
         {
           for (i=0; i < infoPtr->uNumItem; i++)
           {
             /* move everything in the row of the selected item to the iTargetRow */
2418 2419 2420 2421
             TAB_ITEM *item = TAB_GetItem(infoPtr, i);

             if (item->rect.left == newselected )
                 item->rect.left = iTargetRow;
2422 2423
             else
             {
2424 2425
               if (item->rect.left > newselected)
                 item->rect.left-=1;
2426 2427 2428 2429 2430 2431 2432
             }
           }
         }
         else
         {
           for (i=0; i < infoPtr->uNumItem; i++)
           {
2433 2434 2435 2436
             TAB_ITEM *item = TAB_GetItem(infoPtr, i);

             if (item->rect.top == newselected )
                 item->rect.top = iTargetRow;
2437 2438
             else
             {
2439 2440
               if (item->rect.top > newselected)
                 item->rect.top-=1;
2441 2442 2443
             }
          }
        }
2444
        TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
2445 2446 2447
      }
  }

Francis Beaudet's avatar
Francis Beaudet committed
2448 2449 2450 2451
  /*
   * Do the trivial cases first.
   */
  if ( (!infoPtr->needsScrolling) ||
2452
       (infoPtr->hwndUpDown==0) || (lStyle & TCS_VERTICAL))
Francis Beaudet's avatar
Francis Beaudet committed
2453 2454
    return;

Alexandre Julliard's avatar
Alexandre Julliard committed
2455
  if (infoPtr->leftmostVisible >= iSelected)
Francis Beaudet's avatar
Francis Beaudet committed
2456
  {
Alexandre Julliard's avatar
Alexandre Julliard committed
2457
    infoPtr->leftmostVisible = iSelected;
Francis Beaudet's avatar
Francis Beaudet committed
2458
  }
Alexandre Julliard's avatar
Alexandre Julliard committed
2459
  else
Francis Beaudet's avatar
Francis Beaudet committed
2460
  {
2461
     TAB_ITEM *selected = TAB_GetItem(infoPtr, iSelected);
Alexandre Julliard's avatar
Alexandre Julliard committed
2462
     RECT r;
2463 2464
     INT width;
     UINT i;
2465 2466

     /* Calculate the part of the client area that is visible */
2467
     GetClientRect(infoPtr->hwnd, &r);
Alexandre Julliard's avatar
Alexandre Julliard committed
2468 2469 2470 2471 2472
     width = r.right;

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

2473 2474
     if ((selected->rect.right -
          selected->rect.left) >= width )
Alexandre Julliard's avatar
Alexandre Julliard committed
2475 2476 2477 2478 2479 2480 2481 2482 2483 2484
     {
        /* 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++)
        {
2485
           if ((selected->rect.right - TAB_GetItem(infoPtr, i)->rect.left) < width)
Alexandre Julliard's avatar
Alexandre Julliard committed
2486 2487 2488 2489
              break;
        }
        infoPtr->leftmostVisible = i;
     }
Francis Beaudet's avatar
Francis Beaudet committed
2490
  }
Alexandre Julliard's avatar
Alexandre Julliard committed
2491

2492
  if (infoPtr->leftmostVisible != iOrigLeftmostVisible)
2493
    TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
2494

2495
  SendMessageW(infoPtr->hwndUpDown, UDM_SETPOS, 0,
Alexandre Julliard's avatar
Alexandre Julliard committed
2496
               MAKELONG(infoPtr->leftmostVisible, 0));
Francis Beaudet's avatar
Francis Beaudet committed
2497
}
Alexandre Julliard's avatar
Alexandre Julliard committed
2498

Francis Beaudet's avatar
Francis Beaudet committed
2499 2500 2501 2502 2503 2504 2505
/******************************************************************************
 * 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
 */
2506
static void TAB_InvalidateTabArea(TAB_INFO* infoPtr)
Francis Beaudet's avatar
Francis Beaudet committed
2507
{
2508
  RECT clientRect, rInvalidate, rAdjClient;
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
2509
  DWORD lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
Gerard Patel's avatar
Gerard Patel committed
2510
  INT lastRow = infoPtr->uNumRows - 1;
2511
  RECT rect;
Gerard Patel's avatar
Gerard Patel committed
2512 2513

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

2515
  GetClientRect(infoPtr->hwnd, &clientRect);
2516
  rInvalidate = clientRect;
2517 2518
  rAdjClient = clientRect;

2519
  TAB_AdjustRect(infoPtr, 0, &rAdjClient);
Alexandre Julliard's avatar
Alexandre Julliard committed
2520

2521
  TAB_InternalGetItemRect(infoPtr, infoPtr->uNumItem-1 , &rect, NULL);
2522
  if ((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
2523
  {
2524 2525 2526
    rInvalidate.left = rAdjClient.right;
    if (infoPtr->uNumRows == 1)
      rInvalidate.bottom = clientRect.top + rect.bottom + 2 * SELECTED_TAB_OFFSET;
2527
  }
2528
  else if(lStyle & TCS_VERTICAL)
2529
  {
2530 2531 2532
    rInvalidate.right = rAdjClient.left;
    if (infoPtr->uNumRows == 1)
      rInvalidate.bottom = clientRect.top + rect.bottom + 2 * SELECTED_TAB_OFFSET;
2533
  }
2534
  else if (lStyle & TCS_BOTTOM)
Francis Beaudet's avatar
Francis Beaudet committed
2535
  {
2536 2537 2538
    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
2539
  }
2540
  else 
Francis Beaudet's avatar
Francis Beaudet committed
2541
  {
2542 2543 2544
    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
2545
  }
2546 2547
  
  /* Punch out the updown control */
2548 2549
  if (infoPtr->needsScrolling && (rInvalidate.right > 0)) {
    RECT r;
2550
    GetClientRect(infoPtr->hwndUpDown, &r);
2551 2552 2553 2554
    if (rInvalidate.right > clientRect.right - r.left)
      rInvalidate.right = rInvalidate.right - (r.right - r.left);
    else
      rInvalidate.right = clientRect.right - r.left;
2555 2556
  }
  
2557
  TRACE("invalidate (%ld,%ld)-(%ld,%ld)\n",
2558 2559
	rInvalidate.left,  rInvalidate.top,
	rInvalidate.right, rInvalidate.bottom);
2560
 
2561
  InvalidateRect(infoPtr->hwnd, &rInvalidate, TRUE);
Francis Beaudet's avatar
Francis Beaudet committed
2562
}
Alexandre Julliard's avatar
Alexandre Julliard committed
2563

2564
static inline LRESULT TAB_Paint (TAB_INFO *infoPtr, HDC hdcPaint)
Francis Beaudet's avatar
Francis Beaudet committed
2565 2566 2567
{
  HDC hdc;
  PAINTSTRUCT ps;
2568

2569 2570 2571
  if (hdcPaint)
    hdc = hdcPaint;
  else
2572
  {
2573
    hdc = BeginPaint (infoPtr->hwnd, &ps);
2574 2575 2576 2577
    TRACE("erase %d, rect=(%ld,%ld)-(%ld,%ld)\n",
         ps.fErase,
         ps.rcPaint.left,ps.rcPaint.top,ps.rcPaint.right,ps.rcPaint.bottom);
  }
2578

2579 2580 2581 2582
  TAB_Refresh (infoPtr, hdc);

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

Francis Beaudet's avatar
Francis Beaudet committed
2584 2585
  return 0;
}
Alexandre Julliard's avatar
Alexandre Julliard committed
2586

Francis Beaudet's avatar
Francis Beaudet committed
2587
static LRESULT
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
2588
TAB_InsertItemT (TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam, BOOL bUnicode)
2589
{
2590
  TAB_ITEM *item;
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
2591
  TCITEMW *pti;
2592
  INT iItem;
Francis Beaudet's avatar
Francis Beaudet committed
2593
  RECT rect;
2594

2595 2596
  GetClientRect (infoPtr->hwnd, &rect);
  TRACE("Rect: %p T %li, L %li, B %li, R %li\n", infoPtr->hwnd,
2597 2598
        rect.top, rect.left, rect.bottom, rect.right);

Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
2599
  pti = (TCITEMW *)lParam;
Francis Beaudet's avatar
Francis Beaudet committed
2600
  iItem = (INT)wParam;
2601

Francis Beaudet's avatar
Francis Beaudet committed
2602 2603 2604
  if (iItem < 0) return -1;
  if (iItem > infoPtr->uNumItem)
    iItem = infoPtr->uNumItem;
2605

Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
2606
  TAB_DumpItemExternalT(pti, iItem, bUnicode);
2607 2608


Francis Beaudet's avatar
Francis Beaudet committed
2609
  if (infoPtr->uNumItem == 0) {
2610
    infoPtr->items = Alloc (TAB_ITEM_SIZE(infoPtr));
Francis Beaudet's avatar
Francis Beaudet committed
2611
    infoPtr->uNumItem++;
2612
    infoPtr->iSelected = 0;
Francis Beaudet's avatar
Francis Beaudet committed
2613 2614
  }
  else {
2615
    LPBYTE oldItems = (LPBYTE)infoPtr->items;
2616

Francis Beaudet's avatar
Francis Beaudet committed
2617
    infoPtr->uNumItem++;
2618
    infoPtr->items = Alloc (TAB_ITEM_SIZE(infoPtr) * infoPtr->uNumItem);
2619

Francis Beaudet's avatar
Francis Beaudet committed
2620 2621
    /* pre insert copy */
    if (iItem > 0) {
2622
      memcpy (infoPtr->items, oldItems,
2623
              iItem * TAB_ITEM_SIZE(infoPtr));
Francis Beaudet's avatar
Francis Beaudet committed
2624
    }
2625

Francis Beaudet's avatar
Francis Beaudet committed
2626 2627
    /* post insert copy */
    if (iItem < infoPtr->uNumItem - 1) {
2628 2629
      memcpy (TAB_GetItem(infoPtr, iItem + 1),
              oldItems + iItem * TAB_ITEM_SIZE(infoPtr),
2630
              (infoPtr->uNumItem - iItem - 1) * TAB_ITEM_SIZE(infoPtr));
2631

Francis Beaudet's avatar
Francis Beaudet committed
2632
    }
2633 2634 2635 2636

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

2637
    Free (oldItems);
Francis Beaudet's avatar
Francis Beaudet committed
2638
  }
2639

2640
  item = TAB_GetItem(infoPtr, iItem);
2641

2642 2643
  item->mask = pti->mask;
  item->pszText = NULL;
2644

2645
  if (pti->mask & TCIF_TEXT)
2646 2647
  {
    if (bUnicode)
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
2648
      Str_SetPtrW (&item->pszText, pti->pszText);
2649
    else
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
2650
      Str_SetPtrAtoW (&item->pszText, (LPSTR)pti->pszText);
2651
  }
2652

Francis Beaudet's avatar
Francis Beaudet committed
2653
  if (pti->mask & TCIF_IMAGE)
2654 2655 2656
    item->iImage = pti->iImage;
  else
    item->iImage = -1;
2657

Francis Beaudet's avatar
Francis Beaudet committed
2658
  if (pti->mask & TCIF_PARAM)
2659
    memcpy(item->extra, &pti->lParam, infoPtr->cbInfo);
2660
  else
2661 2662
    memset(item->extra, 0, infoPtr->cbInfo);
  
2663
  TAB_SetItemBounds(infoPtr);
2664
  if (infoPtr->uNumItem > 1)
2665
    TAB_InvalidateTabArea(infoPtr);
2666
  else
2667
    InvalidateRect(infoPtr->hwnd, NULL, TRUE);
2668

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

Francis Beaudet's avatar
Francis Beaudet committed
2672
  return iItem;
Alexandre Julliard's avatar
Alexandre Julliard committed
2673 2674
}

2675
static LRESULT
2676
TAB_SetItemSize (TAB_INFO *infoPtr, LPARAM lParam)
2677
{
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
2678
  LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
2679
  LONG lResult = 0;
2680
  BOOL bNeedPaint = FALSE;
2681

2682 2683 2684
  lResult = MAKELONG(infoPtr->tabWidth, infoPtr->tabHeight);

  /* UNDOCUMENTED: If requested Width or Height is 0 this means that program wants to use auto size. */
2685
  if (lStyle & TCS_FIXEDWIDTH && (infoPtr->tabWidth != (INT)LOWORD(lParam)))
2686
  {
2687
    infoPtr->tabWidth = (INT)LOWORD(lParam);
2688
    bNeedPaint = TRUE;
2689 2690
  }

2691 2692 2693 2694 2695 2696 2697 2698 2699 2700 2701 2702
  if (infoPtr->tabHeight != (INT)HIWORD(lParam))
  {
    if ((infoPtr->fHeightSet = ((INT)HIWORD(lParam) != 0)))
      infoPtr->tabHeight = (INT)HIWORD(lParam);

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

  if (bNeedPaint)
2703
  {
2704 2705
    TAB_SetItemBounds(infoPtr);
    RedrawWindow(infoPtr->hwnd, NULL, NULL, RDW_ERASE | RDW_INVALIDATE | RDW_UPDATENOW);
2706
  }
2707

2708 2709 2710
  return lResult;
}

2711
static inline LRESULT TAB_SetMinTabWidth (TAB_INFO *infoPtr, INT cx)
2712
{
2713 2714 2715
  INT oldcx = 0;

  TRACE("(%p,%d)\n", infoPtr, cx);
2716 2717 2718

  if (infoPtr) {
    oldcx = infoPtr->tabMinWidth;
2719
    infoPtr->tabMinWidth = cx;
2720
  }
2721
  TAB_SetItemBounds(infoPtr);
2722 2723 2724 2725

  return oldcx;
}

2726 2727
static inline LRESULT 
TAB_HighlightItem (TAB_INFO *infoPtr, INT iItem, BOOL fHighlight)
2728
{
2729
  LPDWORD lpState;
2730

2731 2732 2733
  TRACE("(%p,%d,%s)\n", infoPtr, iItem, fHighlight ? "true" : "false");

  if (!infoPtr || iItem < 0 || iItem >= infoPtr->uNumItem)
2734
    return FALSE;
2735 2736 2737 2738 2739 2740 2741
  
  lpState = &TAB_GetItem(infoPtr, iItem)->dwState;

  if (fHighlight)
    *lpState |= TCIS_HIGHLIGHTED;
  else
    *lpState &= ~TCIS_HIGHLIGHTED;
2742 2743 2744 2745

  return TRUE;
}

2746
static LRESULT
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
2747
TAB_SetItemT (TAB_INFO *infoPtr, INT iItem, LPTCITEMW tabItem, BOOL bUnicode)
2748
{
2749
  TAB_ITEM *wineItem;
2750

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

2753 2754
  if (iItem < 0 || iItem >= infoPtr->uNumItem)
    return FALSE;
2755

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

2758
  wineItem = TAB_GetItem(infoPtr, iItem);
2759

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

2763
  if (tabItem->mask & TCIF_PARAM)
2764
    memcpy(wineItem->extra, &tabItem->lParam, infoPtr->cbInfo);
2765

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

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

2772
  if (tabItem->mask & TCIF_TEXT)
2773 2774 2775 2776 2777 2778 2779
  {
    if (wineItem->pszText)
    {
      Free(wineItem->pszText);
      wineItem->pszText = NULL;
    }
    if (bUnicode)
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
2780
      Str_SetPtrW(&wineItem->pszText, tabItem->pszText);
2781
    else
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
2782
      Str_SetPtrAtoW(&wineItem->pszText, (LPSTR)tabItem->pszText);
2783 2784
  }

2785
  /* Update and repaint tabs */
2786 2787
  TAB_SetItemBounds(infoPtr);
  TAB_InvalidateTabArea(infoPtr);
2788

Francis Beaudet's avatar
Francis Beaudet committed
2789
  return TRUE;
2790 2791
}

2792
static inline LRESULT TAB_GetItemCount (const TAB_INFO *infoPtr)
2793 2794 2795 2796 2797
{
   return infoPtr->uNumItem;
}


2798
static LRESULT
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
2799
TAB_GetItemT (TAB_INFO *infoPtr, INT iItem, LPTCITEMW tabItem, BOOL bUnicode)
2800
{
2801
  TAB_ITEM *wineItem;
2802

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

  if (iItem < 0 || iItem >= infoPtr->uNumItem)
2806
    return FALSE;
2807

2808
  wineItem = TAB_GetItem(infoPtr, iItem);
2809 2810 2811 2812 2813

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

  if (tabItem->mask & TCIF_PARAM)
2814
    memcpy(&tabItem->lParam, wineItem->extra, infoPtr->cbInfo);
2815 2816 2817 2818 2819 2820 2821 2822

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

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

  if (tabItem->mask & TCIF_TEXT)
2823 2824
  {
    if (bUnicode)
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
2825
      Str_GetPtrW (wineItem->pszText, tabItem->pszText, tabItem->cchTextMax);
2826
    else
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
2827
      Str_GetPtrWtoA (wineItem->pszText, (LPSTR)tabItem->pszText, tabItem->cchTextMax);
2828
  }
2829

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

2832 2833 2834 2835
  return TRUE;
}


2836
static LRESULT TAB_DeleteItem (TAB_INFO *infoPtr, INT iItem)
2837
{
2838
    BOOL bResult = FALSE;
2839

2840 2841
    TRACE("(%p, %d)\n", infoPtr, iItem);

2842 2843
    if ((iItem >= 0) && (iItem < infoPtr->uNumItem))
    {
2844 2845
        TAB_ITEM *item = TAB_GetItem(infoPtr, iItem);
        LPBYTE oldItems = (LPBYTE)infoPtr->items;
2846
	
2847
	TAB_InvalidateTabArea(infoPtr);
2848

2849 2850 2851
	if ((item->mask & TCIF_TEXT) && item->pszText)
            Free(item->pszText);

2852
	infoPtr->uNumItem--;
2853

2854 2855 2856 2857 2858
	if (!infoPtr->uNumItem)
        {
            infoPtr->items = NULL;
            if (infoPtr->iHotTracked >= 0)
            {
2859
                KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
2860 2861 2862 2863 2864 2865
                infoPtr->iHotTracked = -1;
            }
        }
        else
	{
	    infoPtr->items = Alloc(TAB_ITEM_SIZE(infoPtr) * infoPtr->uNumItem);
2866

2867 2868
	    if (iItem > 0)
	        memcpy(infoPtr->items, oldItems, iItem * TAB_ITEM_SIZE(infoPtr));
2869

2870 2871 2872 2873 2874 2875 2876 2877 2878 2879 2880
	    if (iItem < infoPtr->uNumItem)
	        memcpy(TAB_GetItem(infoPtr, iItem),
                       oldItems + (iItem + 1) * TAB_ITEM_SIZE(infoPtr),
		       (infoPtr->uNumItem - iItem) * TAB_ITEM_SIZE(infoPtr));

            if (iItem <= infoPtr->iHotTracked)
            {
                /* When tabs move left/up, the hot track item may change */
                FIXME("Recalc hot track");
            }
	}
2881
	Free(oldItems);
2882

2883 2884 2885
	/* Readjust the selected index */
	if ((iItem == infoPtr->iSelected) && (iItem > 0))
	    infoPtr->iSelected--;
2886

2887 2888
	if (iItem < infoPtr->iSelected)
	    infoPtr->iSelected--;
2889

2890 2891
	if (infoPtr->uNumItem == 0)
	    infoPtr->iSelected = -1;
2892

2893
	/* Reposition and repaint tabs */
2894
	TAB_SetItemBounds(infoPtr);
2895

2896 2897
	bResult = TRUE;
    }
2898

2899
    return bResult;
2900
}
Alex Priem's avatar
Alex Priem committed
2901

2902
static inline LRESULT TAB_DeleteAllItems (TAB_INFO *infoPtr)
2903
{
2904
    TRACE("(%p)\n", infoPtr);
2905
    while (infoPtr->uNumItem)
2906
      TAB_DeleteItem (infoPtr, 0);
2907
    return TRUE;
2908 2909 2910
}


2911
static inline LRESULT TAB_GetFont (const TAB_INFO *infoPtr)
2912
{
2913
  TRACE("(%p) returning %p\n", infoPtr, infoPtr->hFont);
2914 2915 2916
  return (LRESULT)infoPtr->hFont;
}

2917
static inline LRESULT TAB_SetFont (TAB_INFO *infoPtr, HFONT hNewFont)
2918
{
2919
  TRACE("(%p,%p)\n", infoPtr, hNewFont);
2920

2921
  infoPtr->hFont = hNewFont;
2922

2923
  TAB_SetItemBounds(infoPtr);
2924

2925
  TAB_InvalidateTabArea(infoPtr);
2926

Francis Beaudet's avatar
Francis Beaudet committed
2927
  return 0;
2928 2929 2930
}


2931
static inline LRESULT TAB_GetImageList (const TAB_INFO *infoPtr)
2932
{
2933
  TRACE("\n");
2934 2935 2936
  return (LRESULT)infoPtr->himl;
}

2937
static inline LRESULT TAB_SetImageList (TAB_INFO *infoPtr, HIMAGELIST himlNew)
2938
{
2939
    HIMAGELIST himlPrev = infoPtr->himl;
2940
    TRACE("\n");
2941
    infoPtr->himl = himlNew;
2942 2943 2944
    return (LRESULT)himlPrev;
}

2945
static inline LRESULT TAB_GetUnicodeFormat (const TAB_INFO *infoPtr)
2946 2947 2948 2949
{
    return infoPtr->bUnicode;
}

2950
static inline LRESULT TAB_SetUnicodeFormat (TAB_INFO *infoPtr, BOOL bUnicode)
2951 2952 2953
{
    BOOL bTemp = infoPtr->bUnicode;

2954
    infoPtr->bUnicode = bUnicode;
2955 2956 2957

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

2959
static inline LRESULT TAB_Size (TAB_INFO *infoPtr)
Alex Priem's avatar
Alex Priem committed
2960
{
2961 2962 2963 2964 2965
/* 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.

2966 2967 2968
  RECT parent_rect;
  HWND parent;
  UINT uPosFlags,cx,cy;
Alex Priem's avatar
Alex Priem committed
2969 2970 2971

  uPosFlags=0;
  if (!wParam) {
Francis Beaudet's avatar
Francis Beaudet committed
2972 2973 2974 2975
    parent = GetParent (hwnd);
    GetClientRect(parent, &parent_rect);
    cx=LOWORD (lParam);
    cy=HIWORD (lParam);
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
2976
    if (GetWindowLongW(hwnd, GWL_STYLE) & CCS_NORESIZE)
Alex Priem's avatar
Alex Priem committed
2977 2978
        uPosFlags |= (SWP_NOSIZE | SWP_NOMOVE);

Francis Beaudet's avatar
Francis Beaudet committed
2979
    SetWindowPos (hwnd, 0, parent_rect.left, parent_rect.top,
Alex Priem's avatar
Alex Priem committed
2980
            cx, cy, uPosFlags | SWP_NOZORDER);
2981
  } else {
Andreas Mohr's avatar
Andreas Mohr committed
2982
    FIXME("WM_SIZE flag %x %lx not handled\n", wParam, lParam);
2983
  } */
Alex Priem's avatar
Alex Priem committed
2984

2985
  /* Recompute the size/position of the tabs. */
2986
  TAB_SetItemBounds (infoPtr);
Francis Beaudet's avatar
Francis Beaudet committed
2987

2988
  /* Force a repaint of the control. */
2989
  InvalidateRect(infoPtr->hwnd, NULL, TRUE);
Alex Priem's avatar
Alex Priem committed
2990 2991 2992 2993 2994

  return 0;
}


2995
static LRESULT TAB_Create (HWND hwnd, WPARAM wParam, LPARAM lParam)
Alexandre Julliard's avatar
Alexandre Julliard committed
2996
{
Francis Beaudet's avatar
Francis Beaudet committed
2997
  TAB_INFO *infoPtr;
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
2998
  TEXTMETRICW fontMetrics;
2999 3000
  HDC hdc;
  HFONT hOldFont;
Alexandre Julliard's avatar
Alexandre Julliard committed
3001
  DWORD dwStyle;
Alexandre Julliard's avatar
Alexandre Julliard committed
3002

3003
  infoPtr = (TAB_INFO *)Alloc (sizeof(TAB_INFO));
Francis Beaudet's avatar
Francis Beaudet committed
3004

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

3007
  infoPtr->hwnd            = hwnd;
3008
  infoPtr->hwndNotify      = ((LPCREATESTRUCTW)lParam)->hwndParent;
Francis Beaudet's avatar
Francis Beaudet committed
3009
  infoPtr->uNumItem        = 0;
3010
  infoPtr->uNumRows        = 0;
3011 3012
  infoPtr->uHItemPadding   = 6;
  infoPtr->uVItemPadding   = 3;
3013 3014
  infoPtr->uHItemPadding_s = 6;
  infoPtr->uVItemPadding_s = 3;
Francis Beaudet's avatar
Francis Beaudet committed
3015 3016
  infoPtr->hFont           = 0;
  infoPtr->items           = 0;
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
3017
  infoPtr->hcurArrow       = LoadCursorW (0, (LPWSTR)IDC_ARROW);
3018
  infoPtr->iSelected       = -1;
3019
  infoPtr->iHotTracked     = -1;
3020
  infoPtr->uFocus          = -1;
Francis Beaudet's avatar
Francis Beaudet committed
3021 3022 3023 3024 3025
  infoPtr->hwndToolTip     = 0;
  infoPtr->DoRedraw        = TRUE;
  infoPtr->needsScrolling  = FALSE;
  infoPtr->hwndUpDown      = 0;
  infoPtr->leftmostVisible = 0;
3026
  infoPtr->fHeightSet      = FALSE;
3027 3028
  infoPtr->bUnicode        = IsWindowUnicode (hwnd);
  infoPtr->cbInfo          = sizeof(LPARAM);
Alexandre Julliard's avatar
Alexandre Julliard committed
3029

3030
  TRACE("Created tab control, hwnd [%p]\n", hwnd);
3031 3032 3033

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

  if (dwStyle & TCS_TOOLTIPS) {
Alex Priem's avatar
Alex Priem committed
3039 3040
    /* Create tooltip control */
    infoPtr->hwndToolTip =
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
3041
      CreateWindowExW (0, TOOLTIPS_CLASSW, NULL, 0,
Francis Beaudet's avatar
Francis Beaudet committed
3042 3043 3044
		       CW_USEDEFAULT, CW_USEDEFAULT,
		       CW_USEDEFAULT, CW_USEDEFAULT,
		       hwnd, 0, 0, 0);
3045

Alex Priem's avatar
Alex Priem committed
3046 3047
    /* Send NM_TOOLTIPSCREATED notification */
    if (infoPtr->hwndToolTip) {
Francis Beaudet's avatar
Francis Beaudet committed
3048
      NMTOOLTIPSCREATED nmttc;
3049

Francis Beaudet's avatar
Francis Beaudet committed
3050
      nmttc.hdr.hwndFrom = hwnd;
3051
      nmttc.hdr.idFrom = GetWindowLongPtrW(hwnd, GWLP_ID);
Francis Beaudet's avatar
Francis Beaudet committed
3052 3053
      nmttc.hdr.code = NM_TOOLTIPSCREATED;
      nmttc.hwndToolTips = infoPtr->hwndToolTip;
3054

3055
      SendMessageW (infoPtr->hwndNotify, WM_NOTIFY,
3056
		    (WPARAM)GetWindowLongPtrW(hwnd, GWLP_ID), (LPARAM)&nmttc);
Alex Priem's avatar
Alex Priem committed
3057
    }
3058 3059
  }

3060 3061
  OpenThemeData (infoPtr->hwnd, themeClass);
  
3062 3063 3064 3065
  /*
   * We need to get text information so we need a DC and we need to select
   * a font.
   */
3066
  hdc = GetDC(hwnd);
3067 3068
  hOldFont = SelectObject (hdc, GetStockObject (SYSTEM_FONT));

3069
  /* Use the system font to determine the initial height of a tab. */
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
3070
  GetTextMetricsW(hdc, &fontMetrics);
3071 3072

  /*
3073 3074
   * Make sure there is enough space for the letters + growing the
   * selected item + extra space for the selected item.
3075
   */
3076
  infoPtr->tabHeight = fontMetrics.tmHeight + SELECTED_TAB_OFFSET +
3077
	               ((dwStyle & TCS_BUTTONS) ? 2 : 1) *
3078
                        infoPtr->uVItemPadding;
3079

3080
  /* Initialize the width of a tab. */
3081 3082 3083 3084
  if (dwStyle & TCS_FIXEDWIDTH)
    infoPtr->tabWidth = DEFAULT_TAB_WIDTH_FIXED;

  infoPtr->tabMinWidth = -1;
3085

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

3088 3089 3090
  SelectObject (hdc, hOldFont);
  ReleaseDC(hwnd, hdc);

Francis Beaudet's avatar
Francis Beaudet committed
3091
  return 0;
Alexandre Julliard's avatar
Alexandre Julliard committed
3092 3093 3094
}

static LRESULT
3095
TAB_Destroy (TAB_INFO *infoPtr)
Alexandre Julliard's avatar
Alexandre Julliard committed
3096
{
3097
  UINT iItem;
Alexandre Julliard's avatar
Alexandre Julliard committed
3098

Alexandre Julliard's avatar
Alexandre Julliard committed
3099 3100 3101
  if (!infoPtr)
      return 0;

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

Francis Beaudet's avatar
Francis Beaudet committed
3104 3105
  if (infoPtr->items) {
    for (iItem = 0; iItem < infoPtr->uNumItem; iItem++) {
3106 3107
      if (TAB_GetItem(infoPtr, iItem)->pszText)
	Free (TAB_GetItem(infoPtr, iItem)->pszText);
Francis Beaudet's avatar
Francis Beaudet committed
3108
    }
3109
    Free (infoPtr->items);
Francis Beaudet's avatar
Francis Beaudet committed
3110
  }
3111 3112

  if (infoPtr->hwndToolTip)
Francis Beaudet's avatar
Francis Beaudet committed
3113
    DestroyWindow (infoPtr->hwndToolTip);
3114

Francis Beaudet's avatar
Francis Beaudet committed
3115 3116
  if (infoPtr->hwndUpDown)
    DestroyWindow(infoPtr->hwndUpDown);
Alex Priem's avatar
Alex Priem committed
3117

3118
  if (infoPtr->iHotTracked >= 0)
3119
    KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
3120

3121 3122
  CloseThemeData (GetWindowTheme (infoPtr->hwnd));
  
3123
  Free (infoPtr);
Francis Beaudet's avatar
Francis Beaudet committed
3124
  return 0;
Alexandre Julliard's avatar
Alexandre Julliard committed
3125 3126
}

3127 3128 3129 3130 3131 3132 3133 3134 3135
/* update theme after a WM_THEMECHANGED message */
static LRESULT theme_changed (TAB_INFO* infoPtr)
{
    HTHEME theme = GetWindowTheme (infoPtr->hwnd);
    CloseThemeData (theme);
    OpenThemeData (infoPtr->hwnd, themeClass);
    return 0;
}

3136 3137 3138 3139 3140 3141 3142
static LRESULT TAB_NCCalcSize(HWND hwnd, WPARAM wParam, LPARAM lParam)
{
  if (!wParam)
    return 0;
  return WVR_ALIGNTOP;
}

3143 3144
static inline LRESULT
TAB_SetItemExtra (TAB_INFO *infoPtr, INT cbInfo)
3145
{
3146 3147 3148 3149 3150 3151 3152 3153 3154 3155 3156 3157 3158
  if (!infoPtr || cbInfo <= 0)
    return FALSE;

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

3159
static LRESULT WINAPI
3160
TAB_WindowProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
Alexandre Julliard's avatar
Alexandre Julliard committed
3161
{
3162
    TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
3163

3164
    TRACE("hwnd=%p msg=%x wParam=%x lParam=%lx\n", hwnd, uMsg, wParam, lParam);
3165
    if (!infoPtr && (uMsg != WM_CREATE))
3166
      return DefWindowProcW (hwnd, uMsg, wParam, lParam);
3167

Alexandre Julliard's avatar
Alexandre Julliard committed
3168 3169
    switch (uMsg)
    {
Francis Beaudet's avatar
Francis Beaudet committed
3170
    case TCM_GETIMAGELIST:
3171
      return TAB_GetImageList (infoPtr);
3172

Francis Beaudet's avatar
Francis Beaudet committed
3173
    case TCM_SETIMAGELIST:
3174
      return TAB_SetImageList (infoPtr, (HIMAGELIST)lParam);
3175

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

Francis Beaudet's avatar
Francis Beaudet committed
3179 3180
    case TCM_GETITEMA:
    case TCM_GETITEMW:
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
3181
      return TAB_GetItemT (infoPtr, (INT)wParam, (LPTCITEMW)lParam, uMsg == TCM_GETITEMW);
3182

Francis Beaudet's avatar
Francis Beaudet committed
3183 3184
    case TCM_SETITEMA:
    case TCM_SETITEMW:
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
3185
      return TAB_SetItemT (infoPtr, (INT)wParam, (LPTCITEMW)lParam, uMsg == TCM_SETITEMW);
3186

Francis Beaudet's avatar
Francis Beaudet committed
3187
    case TCM_DELETEITEM:
3188
      return TAB_DeleteItem (infoPtr, (INT)wParam);
3189

Francis Beaudet's avatar
Francis Beaudet committed
3190
    case TCM_DELETEALLITEMS:
3191
     return TAB_DeleteAllItems (infoPtr);
3192

Francis Beaudet's avatar
Francis Beaudet committed
3193
    case TCM_GETITEMRECT:
3194
     return TAB_GetItemRect (infoPtr, wParam, lParam);
3195

Francis Beaudet's avatar
Francis Beaudet committed
3196
    case TCM_GETCURSEL:
3197
      return TAB_GetCurSel (infoPtr);
3198

Francis Beaudet's avatar
Francis Beaudet committed
3199
    case TCM_HITTEST:
3200
      return TAB_HitTest (infoPtr, (LPTCHITTESTINFO)lParam);
3201

Francis Beaudet's avatar
Francis Beaudet committed
3202
    case TCM_SETCURSEL:
3203
      return TAB_SetCurSel (infoPtr, (INT)wParam);
3204

Francis Beaudet's avatar
Francis Beaudet committed
3205 3206
    case TCM_INSERTITEMA:
    case TCM_INSERTITEMW:
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
3207
      return TAB_InsertItemT (infoPtr, wParam, lParam, uMsg == TCM_INSERTITEMW);
3208

Francis Beaudet's avatar
Francis Beaudet committed
3209
    case TCM_SETITEMEXTRA:
3210
      return TAB_SetItemExtra (infoPtr, (int)wParam);
3211

Francis Beaudet's avatar
Francis Beaudet committed
3212
    case TCM_ADJUSTRECT:
3213
      return TAB_AdjustRect (infoPtr, (BOOL)wParam, (LPRECT)lParam);
3214

Francis Beaudet's avatar
Francis Beaudet committed
3215
    case TCM_SETITEMSIZE:
3216
      return TAB_SetItemSize (infoPtr, lParam);
3217

Francis Beaudet's avatar
Francis Beaudet committed
3218
    case TCM_REMOVEIMAGE:
3219
      FIXME("Unimplemented msg TCM_REMOVEIMAGE\n");
Francis Beaudet's avatar
Francis Beaudet committed
3220
      return 0;
3221

Francis Beaudet's avatar
Francis Beaudet committed
3222
    case TCM_SETPADDING:
3223
      return TAB_SetPadding (infoPtr, lParam);
3224

Francis Beaudet's avatar
Francis Beaudet committed
3225
    case TCM_GETROWCOUNT:
3226
      return TAB_GetRowCount(infoPtr);
3227 3228

    case TCM_GETUNICODEFORMAT:
3229
      return TAB_GetUnicodeFormat (infoPtr);
3230 3231

    case TCM_SETUNICODEFORMAT:
3232
      return TAB_SetUnicodeFormat (infoPtr, (BOOL)wParam);
3233 3234

    case TCM_HIGHLIGHTITEM:
3235
      return TAB_HighlightItem (infoPtr, (INT)wParam, (BOOL)LOWORD(lParam));
3236

Francis Beaudet's avatar
Francis Beaudet committed
3237
    case TCM_GETTOOLTIPS:
3238
      return TAB_GetToolTips (infoPtr);
3239

Francis Beaudet's avatar
Francis Beaudet committed
3240
    case TCM_SETTOOLTIPS:
3241
      return TAB_SetToolTips (infoPtr, (HWND)wParam);
3242

Francis Beaudet's avatar
Francis Beaudet committed
3243
    case TCM_GETCURFOCUS:
3244
      return TAB_GetCurFocus (infoPtr);
3245

Francis Beaudet's avatar
Francis Beaudet committed
3246
    case TCM_SETCURFOCUS:
3247
      return TAB_SetCurFocus (infoPtr, (INT)wParam);
3248

Alexandre Julliard's avatar
Alexandre Julliard committed
3249
    case TCM_SETMINTABWIDTH:
3250
      return TAB_SetMinTabWidth(infoPtr, (INT)lParam);
3251

Francis Beaudet's avatar
Francis Beaudet committed
3252
    case TCM_DESELECTALL:
3253
      FIXME("Unimplemented msg TCM_DESELECTALL\n");
Francis Beaudet's avatar
Francis Beaudet committed
3254
      return 0;
3255

3256
    case TCM_GETEXTENDEDSTYLE:
3257
      FIXME("Unimplemented msg TCM_GETEXTENDEDSTYLE\n");
3258 3259 3260
      return 0;

    case TCM_SETEXTENDEDSTYLE:
3261
      FIXME("Unimplemented msg TCM_SETEXTENDEDSTYLE\n");
3262 3263
      return 0;

Francis Beaudet's avatar
Francis Beaudet committed
3264
    case WM_GETFONT:
3265
      return TAB_GetFont (infoPtr);
3266

Francis Beaudet's avatar
Francis Beaudet committed
3267
    case WM_SETFONT:
3268
      return TAB_SetFont (infoPtr, (HFONT)wParam);
3269

Francis Beaudet's avatar
Francis Beaudet committed
3270 3271
    case WM_CREATE:
      return TAB_Create (hwnd, wParam, lParam);
3272

Francis Beaudet's avatar
Francis Beaudet committed
3273
    case WM_NCDESTROY:
3274
      return TAB_Destroy (infoPtr);
3275

3276
    case WM_GETDLGCODE:
Francis Beaudet's avatar
Francis Beaudet committed
3277
      return DLGC_WANTARROWS | DLGC_WANTCHARS;
3278

Francis Beaudet's avatar
Francis Beaudet committed
3279
    case WM_LBUTTONDOWN:
3280
      return TAB_LButtonDown (infoPtr, wParam, lParam);
3281

Francis Beaudet's avatar
Francis Beaudet committed
3282
    case WM_LBUTTONUP:
3283
      return TAB_LButtonUp (infoPtr);
3284

3285
    case WM_NOTIFY:
3286
      return SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, wParam, lParam);
3287

Francis Beaudet's avatar
Francis Beaudet committed
3288
    case WM_RBUTTONDOWN:
3289
      return TAB_RButtonDown (infoPtr);
3290

Francis Beaudet's avatar
Francis Beaudet committed
3291
    case WM_MOUSEMOVE:
3292
      return TAB_MouseMove (infoPtr, wParam, lParam);
3293

3294
    case WM_PRINTCLIENT:
Francis Beaudet's avatar
Francis Beaudet committed
3295
    case WM_PAINT:
3296
      return TAB_Paint (infoPtr, (HDC)wParam);
Francis Beaudet's avatar
Francis Beaudet committed
3297 3298

    case WM_SIZE:
3299
      return TAB_Size (infoPtr);
3300

Francis Beaudet's avatar
Francis Beaudet committed
3301
    case WM_SETREDRAW:
3302
      return TAB_SetRedraw (infoPtr, (BOOL)wParam);
Francis Beaudet's avatar
Francis Beaudet committed
3303 3304

    case WM_HSCROLL:
3305
      return TAB_OnHScroll(infoPtr, (int)LOWORD(wParam), (int)HIWORD(wParam), (HWND)lParam);
Alexandre Julliard's avatar
Alexandre Julliard committed
3306 3307

    case WM_STYLECHANGED:
3308
      TAB_SetItemBounds (infoPtr);
Alexandre Julliard's avatar
Alexandre Julliard committed
3309 3310
      InvalidateRect(hwnd, NULL, TRUE);
      return 0;
3311 3312 3313 3314

    case WM_SYSCOLORCHANGE:
      COMCTL32_RefreshSysColors();
      return 0;
3315

3316 3317 3318
    case WM_THEMECHANGED:
      return theme_changed (infoPtr);

Francis Beaudet's avatar
Francis Beaudet committed
3319 3320
    case WM_KILLFOCUS:
    case WM_SETFOCUS:
3321 3322
      TAB_FocusChanging(infoPtr);
      break;   /* Don't disturb normal focus behavior */
Francis Beaudet's avatar
Francis Beaudet committed
3323 3324

    case WM_KEYUP:
3325
      return TAB_KeyUp(infoPtr, wParam);
3326
    case WM_NCHITTEST:
3327
      return TAB_NCHitTest(infoPtr, lParam);
Francis Beaudet's avatar
Francis Beaudet committed
3328

3329 3330 3331
    case WM_NCCALCSIZE:
      return TAB_NCCalcSize(hwnd, wParam, lParam);

Francis Beaudet's avatar
Francis Beaudet committed
3332
    default:
3333
      if (uMsg >= WM_USER && uMsg < WM_APP)
3334
	WARN("unknown msg %04x wp=%08x lp=%08lx\n",
Francis Beaudet's avatar
Francis Beaudet committed
3335
	     uMsg, wParam, lParam);
3336
      break;
Alexandre Julliard's avatar
Alexandre Julliard committed
3337
    }
3338
    return DefWindowProcW(hwnd, uMsg, wParam, lParam);
Alexandre Julliard's avatar
Alexandre Julliard committed
3339 3340 3341
}


Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
3342
void
3343
TAB_Register (void)
Alexandre Julliard's avatar
Alexandre Julliard committed
3344
{
3345
  WNDCLASSW wndClass;
Francis Beaudet's avatar
Francis Beaudet committed
3346

3347
  ZeroMemory (&wndClass, sizeof(WNDCLASSW));
3348
  wndClass.style         = CS_GLOBALCLASS | CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
3349
  wndClass.lpfnWndProc   = TAB_WindowProc;
Francis Beaudet's avatar
Francis Beaudet committed
3350 3351
  wndClass.cbClsExtra    = 0;
  wndClass.cbWndExtra    = sizeof(TAB_INFO *);
3352 3353 3354
  wndClass.hCursor       = LoadCursorW (0, (LPWSTR)IDC_ARROW);
  wndClass.hbrBackground = (HBRUSH)(COLOR_BTNFACE+1);
  wndClass.lpszClassName = WC_TABCONTROLW;
3355

3356
  RegisterClassW (&wndClass);
Alexandre Julliard's avatar
Alexandre Julliard committed
3357 3358
}

3359

Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
3360
void
3361
TAB_Unregister (void)
3362
{
3363
    UnregisterClassW (WC_TABCONTROLW, NULL);
3364
}