tab.c 95.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:
35
 *   TCS_MULTISELECT - implement for VK_SPACE selection
James Hawkins's avatar
James Hawkins committed
36 37 38 39
 *   TCS_RIGHT
 *   TCS_RIGHTJUSTIFY
 *   TCS_SCROLLOPPOSITE
 *   TCS_SINGLELINE
40 41
 *   TCIF_RTLREADING
 *
James Hawkins's avatar
James Hawkins committed
42 43 44 45 46 47 48 49 50 51 52 53
 *  Extended Styles:
 *   TCS_EX_REGISTERDROP
 *
 *  Notifications:
 *   NM_RELEASEDCAPTURE
 *   TCN_FOCUSCHANGE
 *   TCN_GETOBJECT
 *   TCN_KEYDOWN
 *
 *  Macros:
 *   TabCtrl_AdjustRect
 *
Alexandre Julliard's avatar
Alexandre Julliard committed
54 55
 */

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

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

71
WINE_DEFAULT_DEBUG_CHANNEL(tab);
72 73 74 75

typedef struct
{
  DWORD  dwState;
76
  LPWSTR pszText;
77
  INT    iImage;
78 79 80 81
  RECT   rect;      /* bounding rectangle of the item relative to the
                     * leftmost item (the leftmost item, 0, would have a
                     * "left" member of 0 in this rectangle)
                     *
82
                     * additionally the top member holds the row number
83 84
                     * and bottom is unused and should be 0 */
  BYTE   extra[1];  /* Space for caller supplied info, variable size */
85 86
} TAB_ITEM;

87
/* The size of a tab item depends on how much extra data is requested */
88
#define TAB_ITEM_SIZE(infoPtr) (FIELD_OFFSET(TAB_ITEM, extra[(infoPtr)->cbInfo]))
89

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

  DWORD      exStyle;         /* Extended style used, currently:
                                 TCS_EX_FLATSEPARATORS, TCS_EX_REGISTERDROP */
123
} TAB_INFO;
124

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

141
#define TAB_GetInfoPtr(hwnd) ((TAB_INFO *)GetWindowLongPtrW(hwnd,0))
142 143 144
/* Since items are variable sized, cannot directly access them */
#define TAB_GetItem(info,i) \
  ((TAB_ITEM*)((LPBYTE)info->items + (i) * TAB_ITEM_SIZE(info)))
145

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

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

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

Francis Beaudet's avatar
Francis Beaudet committed
156 157 158
/******************************************************************************
 * Prototypes
 */
159
static void TAB_InvalidateTabArea(const TAB_INFO *);
160
static void TAB_EnsureSelectionVisible(TAB_INFO *);
161
static void TAB_DrawItemInterior(const TAB_INFO *, HDC, INT, RECT*);
162
static LRESULT TAB_DeselectAll(TAB_INFO *, BOOL);
163

164
static BOOL
165
TAB_SendSimpleNotify (const TAB_INFO *infoPtr, UINT code)
Alexandre Julliard's avatar
Alexandre Julliard committed
166 167 168
{
    NMHDR nmhdr;

169 170
    nmhdr.hwndFrom = infoPtr->hwnd;
    nmhdr.idFrom = GetWindowLongPtrW(infoPtr->hwnd, GWLP_ID);
Alexandre Julliard's avatar
Alexandre Julliard committed
171 172
    nmhdr.code = code;

173
    return (BOOL) SendMessageW (infoPtr->hwndNotify, WM_NOTIFY,
174
            nmhdr.idFrom, (LPARAM) &nmhdr);
Alexandre Julliard's avatar
Alexandre Julliard committed
175 176
}

Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
177
static void
178 179
TAB_RelayEvent (HWND hwndTip, HWND hwndMsg, UINT uMsg,
            WPARAM wParam, LPARAM lParam)
Alex Priem's avatar
Alex Priem committed
180
{
181
    MSG msg;
Alex Priem's avatar
Alex Priem committed
182 183 184 185 186 187

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

191
    SendMessageW (hwndTip, TTM_RELAYEVENT, 0, (LPARAM)&msg);
Alex Priem's avatar
Alex Priem committed
192 193
}

194
static void
195
TAB_DumpItemExternalT(const TCITEMW *pti, UINT iItem, BOOL isW)
196 197
{
    if (TRACE_ON(tab)) {
198
	TRACE("external tab %d, mask=0x%08x, dwState=0x%08x, dwStateMask=0x%08x, cchTextMax=0x%08x\n",
199 200
	      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
201
	      iItem, pti->iImage, pti->lParam, isW ? debugstr_w(pti->pszText) : debugstr_a((LPSTR)pti->pszText));
202 203 204 205
    }
}

static void
206
TAB_DumpItemInternal(const TAB_INFO *infoPtr, UINT iItem)
207 208 209 210
{
    if (TRACE_ON(tab)) {
	TAB_ITEM *ti;

211
	ti = TAB_GetItem(infoPtr, iItem);
212 213
	TRACE("tab %d, dwState=0x%08x, pszText=%s, iImage=%d\n",
	      iItem, ti->dwState, debugstr_w(ti->pszText), ti->iImage);
214
	TRACE("tab %d, rect.left=%d, rect.top(row)=%d\n",
215
	      iItem, ti->rect.left, ti->rect.top);
216 217 218
    }
}

219 220
/* RETURNS
 *   the index of the selected tab, or -1 if no tab is selected. */
221
static inline LRESULT TAB_GetCurSel (const TAB_INFO *infoPtr)
Alexandre Julliard's avatar
Alexandre Julliard committed
222 223 224 225
{
    return infoPtr->iSelected;
}

226
/* RETURNS
227
 *   the index of the tab item that has the focus. */
228 229
static inline LRESULT
TAB_GetCurFocus (const TAB_INFO *infoPtr)
Alex Priem's avatar
Alex Priem committed
230 231 232 233
{
    return infoPtr->uFocus;
}

234
static inline LRESULT TAB_GetToolTips (const TAB_INFO *infoPtr)
Alex Priem's avatar
Alex Priem committed
235 236
{
    if (infoPtr == NULL) return 0;
237
    return (LRESULT)infoPtr->hwndToolTip;
Alex Priem's avatar
Alex Priem committed
238 239
}

240
static inline LRESULT TAB_SetCurSel (TAB_INFO *infoPtr, INT iItem)
241
{
242
  INT prevItem = infoPtr->iSelected;
243

244 245 246 247 248
  if (iItem < 0)
      infoPtr->iSelected=-1;
  else if (iItem >= infoPtr->uNumItem)
      return -1;
  else {
249
      if (infoPtr->iSelected != iItem) {
250 251
          TAB_GetItem(infoPtr, prevItem)->dwState &= ~TCIS_BUTTONPRESSED;
          TAB_GetItem(infoPtr, iItem)->dwState |= TCIS_BUTTONPRESSED;
252

253
          infoPtr->iSelected=iItem;
254
          infoPtr->uFocus=iItem;
255 256 257
          TAB_EnsureSelectionVisible(infoPtr);
          TAB_InvalidateTabArea(infoPtr);
      }
Francis Beaudet's avatar
Francis Beaudet committed
258 259
  }
  return prevItem;
260 261
}

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

289 290
static inline LRESULT
TAB_SetToolTips (TAB_INFO *infoPtr, HWND hwndToolTip)
Alex Priem's avatar
Alex Priem committed
291
{
292 293
    if (infoPtr)
        infoPtr->hwndToolTip = hwndToolTip;
Alex Priem's avatar
Alex Priem committed
294 295 296
    return 0;
}

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

Francis Beaudet's avatar
Francis Beaudet committed
308 309 310 311 312 313 314 315 316 317
/******************************************************************************
 * 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(
318
  const TAB_INFO* infoPtr,
Francis Beaudet's avatar
Francis Beaudet committed
319 320 321 322
  INT         itemIndex,
  RECT*       itemRect,
  RECT*       selectedRect)
{
323
  RECT tmpItemRect,clientRect;
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
324
  LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
325

326 327
  /* Perform a sanity check and a trivial visibility check. */
  if ( (infoPtr->uNumItem <= 0) ||
Francis Beaudet's avatar
Francis Beaudet committed
328
       (itemIndex >= infoPtr->uNumItem) ||
329
       (!((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)) && (itemIndex < infoPtr->leftmostVisible)) )
330 331 332 333 334 335 336 337 338 339 340 341
    {
        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
342 343 344 345 346

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

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

353
  /* calculate the times bottom and top based on the row */
354
  GetClientRect(infoPtr->hwnd, &clientRect);
355

356
  if ((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
357
  {
358 359 360
    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;
361
  }
362
  else if (lStyle & TCS_VERTICAL)
363
  {
364 365 366
    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;
367
  }
368
  else if (lStyle & TCS_BOTTOM)
369
  {
370 371 372
    itemRect->bottom = clientRect.bottom - itemRect->top * infoPtr->tabHeight -
                       ((lStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : SELECTED_TAB_OFFSET);
    itemRect->top    = itemRect->bottom - infoPtr->tabHeight;
373
  }
374
  else /* not TCS_BOTTOM and not TCS_VERTICAL */
375
  {
376 377 378
    itemRect->top    = clientRect.top + itemRect->top * infoPtr->tabHeight +
                       ((lStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : SELECTED_TAB_OFFSET);
    itemRect->bottom = itemRect->top + infoPtr->tabHeight;
379 380
 }

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

    /*
     * Move the rectangle so the first item is slightly offset from
     * the bottom of the tab control.
     */
    OffsetRect(itemRect,
	     0,
397
	     SELECTED_TAB_OFFSET);
398 399 400 401

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

405 406 407 408 409
    /*
     * 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
410 411
	     SELECTED_TAB_OFFSET,
	     0);
412
  }
413 414
  TRACE("item %d tab h=%d, rect=(%s)\n",
        itemIndex, infoPtr->tabHeight, wine_dbgstr_rect(itemRect));
Francis Beaudet's avatar
Francis Beaudet committed
415

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

421 422 423 424 425
    /* 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
426

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

449 450 451 452 453
  /* 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
454 455
}

456
static inline BOOL
457
TAB_GetItemRect(const TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
458
{
459
  return TAB_InternalGetItemRect(infoPtr, wParam, (LPRECT)lParam, NULL);
460 461
}

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

  switch (keyCode)
  {
    case VK_LEFT:
474
      newItem = infoPtr->uFocus - 1;
Francis Beaudet's avatar
Francis Beaudet committed
475 476
      break;
    case VK_RIGHT:
477
      newItem = infoPtr->uFocus + 1;
Francis Beaudet's avatar
Francis Beaudet committed
478 479
      break;
  }
480

Francis Beaudet's avatar
Francis Beaudet committed
481 482 483
  /*
   * If we changed to a valid item, change the selection
   */
484 485 486
  if (newItem >= 0 &&
      newItem < infoPtr->uNumItem &&
      infoPtr->uFocus != newItem)
Francis Beaudet's avatar
Francis Beaudet committed
487
  {
488
    if (!TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGING))
Francis Beaudet's avatar
Francis Beaudet committed
489
    {
490
      TAB_SetCurSel(infoPtr, newItem);
491
      TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGE);
Francis Beaudet's avatar
Francis Beaudet committed
492 493 494 495 496 497 498 499 500 501 502 503
    }
  }

  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
504
static void TAB_FocusChanging(const TAB_INFO *infoPtr)
Francis Beaudet's avatar
Francis Beaudet committed
505 506 507 508 509 510 511
{
  RECT      selectedRect;
  BOOL      isVisible;

  /*
   * Get the rectangle for the item.
   */
512
  isVisible = TAB_InternalGetItemRect(infoPtr,
Francis Beaudet's avatar
Francis Beaudet committed
513 514 515
				      infoPtr->uFocus,
				      NULL,
				      &selectedRect);
516

Francis Beaudet's avatar
Francis Beaudet committed
517 518 519 520 521 522
  /*
   * If the rectangle is not completely invisible, invalidate that
   * portion of the window.
   */
  if (isVisible)
  {
523
    TRACE("invalidate (%s)\n", wine_dbgstr_rect(&selectedRect));
524
    InvalidateRect(infoPtr->hwnd, &selectedRect, TRUE);
Francis Beaudet's avatar
Francis Beaudet committed
525 526
  }
}
Alex Priem's avatar
Alex Priem committed
527

528
static INT TAB_InternalHitTest (const TAB_INFO *infoPtr, POINT pt, UINT *flags)
Alex Priem's avatar
Alex Priem committed
529
{
530
  RECT rect;
531
  INT iCount;
532

533
  for (iCount = 0; iCount < infoPtr->uNumItem; iCount++)
Francis Beaudet's avatar
Francis Beaudet committed
534
  {
535
    TAB_InternalGetItemRect(infoPtr, iCount, &rect, NULL);
Francis Beaudet's avatar
Francis Beaudet committed
536

537
    if (PtInRect(&rect, pt))
Francis Beaudet's avatar
Francis Beaudet committed
538 539 540 541 542 543
    {
      *flags = TCHT_ONITEM;
      return iCount;
    }
  }

544
  *flags = TCHT_NOWHERE;
Alex Priem's avatar
Alex Priem committed
545 546 547
  return -1;
}

548
static inline LRESULT
549
TAB_HitTest (const TAB_INFO *infoPtr, LPTCHITTESTINFO lptest)
Alex Priem's avatar
Alex Priem committed
550
{
551
  return TAB_InternalHitTest (infoPtr, lptest->pt, &lptest->flags);
Alex Priem's avatar
Alex Priem committed
552 553
}

554 555 556 557 558 559 560 561 562 563 564 565
/******************************************************************************
 * 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 ?
 */
566
static inline LRESULT
567
TAB_NCHitTest (const TAB_INFO *infoPtr, LPARAM lParam)
568 569 570 571
{
  POINT pt;
  UINT dummyflag;

572 573
  pt.x = (short)LOWORD(lParam);
  pt.y = (short)HIWORD(lParam);
574
  ScreenToClient(infoPtr->hwnd, &pt);
575

576
  if (TAB_InternalHitTest(infoPtr, pt, &dummyflag) == -1)
577 578 579 580
    return HTTRANSPARENT;
  else
    return HTCLIENT;
}
Alex Priem's avatar
Alex Priem committed
581 582

static LRESULT
583
TAB_LButtonDown (TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
Alex Priem's avatar
Alex Priem committed
584
{
585
  POINT pt;
Mike McCormack's avatar
Mike McCormack committed
586 587
  INT newItem;
  UINT dummy;
588
  LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
Alex Priem's avatar
Alex Priem committed
589

Francis Beaudet's avatar
Francis Beaudet committed
590
  if (infoPtr->hwndToolTip)
591
    TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
Francis Beaudet's avatar
Francis Beaudet committed
592
		    WM_LBUTTONDOWN, wParam, lParam);
Alex Priem's avatar
Alex Priem committed
593

Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
594
  if (GetWindowLongW(infoPtr->hwnd, GWL_STYLE) & TCS_FOCUSONBUTTONDOWN ) {
595
    SetFocus (infoPtr->hwnd);
Francis Beaudet's avatar
Francis Beaudet committed
596
  }
Alex Priem's avatar
Alex Priem committed
597

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

602 603
  pt.x = (short)LOWORD(lParam);
  pt.y = (short)HIWORD(lParam);
604

605
  newItem = TAB_InternalHitTest (infoPtr, pt, &dummy);
606

607
  TRACE("On Tab, item %d\n", newItem);
608

609
  if ((newItem != -1) && (infoPtr->iSelected != newItem))
Francis Beaudet's avatar
Francis Beaudet committed
610
  {
611 612
    if ((lStyle & TCS_BUTTONS) && (lStyle & TCS_MULTISELECT) &&
        (wParam & MK_CONTROL))
Francis Beaudet's avatar
Francis Beaudet committed
613
    {
614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641
      RECT r;

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

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

      TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGING);

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

642
      TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGE);
Francis Beaudet's avatar
Francis Beaudet committed
643 644
    }
  }
645

646 647 648
  return 0;
}

649 650
static inline LRESULT
TAB_LButtonUp (const TAB_INFO *infoPtr)
651
{
652
  TAB_SendSimpleNotify(infoPtr, NM_CLICK);
Francis Beaudet's avatar
Francis Beaudet committed
653 654

  return 0;
Alexandre Julliard's avatar
Alexandre Julliard committed
655 656
}

657 658
static inline LRESULT
TAB_RButtonDown (const TAB_INFO *infoPtr)
Alex Priem's avatar
Alex Priem committed
659
{
660
  TAB_SendSimpleNotify(infoPtr, NM_RCLICK);
Francis Beaudet's avatar
Francis Beaudet committed
661
  return 0;
Alex Priem's avatar
Alex Priem committed
662 663
}

664 665 666 667 668 669 670 671 672
/******************************************************************************
 * 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
673
TAB_DrawLoneItemInterior(const TAB_INFO* infoPtr, int iItem)
674
{
675
  HDC hdc = GetDC(infoPtr->hwnd);
676 677
  RECT r, rC;

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

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

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

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

785 786
  if ((GetWindowLongW(infoPtr->hwnd, GWL_STYLE) & TCS_HOTTRACK)
      || GetWindowTheme (infoPtr->hwnd))
787 788 789 790 791 792 793
  {
    POINT pt;
    UINT  flags;

    if (pos == NULL)
    {
      GetCursorPos(&pt);
794
      ScreenToClient(infoPtr->hwnd, &pt);
795 796 797
    }
    else
    {
798 799
      pt.x = (short)LOWORD(*pos);
      pt.y = (short)HIWORD(*pos);
800 801
    }

802
    item = TAB_InternalHitTest(infoPtr, pt, &flags);
803 804 805 806 807 808
  }

  if (item != infoPtr->iHotTracked)
  {
    if (infoPtr->iHotTracked >= 0)
    {
809
      /* Mark currently hot-tracked to be redrawn to look normal */
810 811 812 813 814
      if (out_redrawLeave != NULL)
        *out_redrawLeave = infoPtr->iHotTracked;

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

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

    infoPtr->iHotTracked = item;

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

Francis Beaudet's avatar
Francis Beaudet committed
856
  if (infoPtr->hwndToolTip)
857
    TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
Francis Beaudet's avatar
Francis Beaudet committed
858
		    WM_LBUTTONDOWN, wParam, lParam);
859 860 861

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

864 865
  hottrack_refresh (infoPtr, redrawLeave);
  hottrack_refresh (infoPtr, redrawEnter);
866

Francis Beaudet's avatar
Francis Beaudet committed
867
  return 0;
Alex Priem's avatar
Alex Priem committed
868
}
869

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

881 882
    TRACE ("hwnd=%p fLarger=%ld (%s)\n", infoPtr->hwnd, fLarger,
           wine_dbgstr_rect(prc));
883

884 885
    if (!prc) return -1;

886 887 888 889
    if(lStyle & TCS_VERTICAL)
    {
	iRightBottom = &(prc->right);
	iLeftTop     = &(prc->left);
890
    }
891
    else
892
    {
893 894
	iRightBottom = &(prc->bottom);
	iLeftTop     = &(prc->top);
895
    }
896

897 898
    if (fLarger) /* Go from display rectangle */
    {
899 900 901 902 903 904
        /* 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);
905

906 907
	/* Inflate the rectangle for the padding */
	InflateRect(prc, DISPLAY_AREA_PADDINGX, DISPLAY_AREA_PADDINGY); 
908

909 910
	/* Inflate for the border */
	InflateRect(prc, CONTROL_BORDER_SIZEX, CONTROL_BORDER_SIZEY);
911 912 913
    }
    else /* Go from window rectangle. */
    {
914 915
	/* Deflate the rectangle for the border */
	InflateRect(prc, -CONTROL_BORDER_SIZEX, -CONTROL_BORDER_SIZEY);
916

917 918
	/* Deflate the rectangle for the padding */
	InflateRect(prc, -DISPLAY_AREA_PADDINGX, -DISPLAY_AREA_PADDINGY);
919

920 921 922 923 924 925
	/* 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);
926 927
    }

Francis Beaudet's avatar
Francis Beaudet committed
928
  return 0;
929 930
}

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

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

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

/******************************************************************************
Gerard Patel's avatar
Gerard Patel committed
956
 * TAB_SetupScrolling
Francis Beaudet's avatar
Francis Beaudet committed
957
 *
958
 * This method will check the current scrolling state and make sure the
Francis Beaudet's avatar
Francis Beaudet committed
959 960 961 962 963 964
 * 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
965
{
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
966 967
  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
968
  INT maxRange = 0;
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
969
  DWORD lStyle = GetWindowLongW(hwnd, GWL_STYLE);
970

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

Francis Beaudet's avatar
Francis Beaudet committed
976 977 978
    /*
     * Calculate the position of the scroll control.
     */
979
    if(lStyle & TCS_VERTICAL)
Francis Beaudet's avatar
Francis Beaudet committed
980
    {
981 982 983 984 985 986 987 988 989 990 991 992 993
      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
994 995 996
    }
    else
    {
997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009
      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
1010
    }
Alexandre Julliard's avatar
Alexandre Julliard committed
1011

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1153
  TRACE("client right=%d\n", clientRect.right);
1154

1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166
  /* 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
1167 1168
  for (curItem = 0; curItem < infoPtr->uNumItem; curItem++)
  {
1169 1170
    TAB_ITEM *curr = TAB_GetItem(infoPtr, curItem);
	
1171
    /* Set the leftmost position of the tab. */
1172
    curr->rect.left = curItemLeftPos;
Francis Beaudet's avatar
Francis Beaudet committed
1173

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

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

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

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

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

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

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

1237
    TRACE("Rect: %s\n", wine_dbgstr_rect(&curr->rect));
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=%d, cl=%d, cl.r=%d, 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=%d,%d, row=%d\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=%d,%d\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=%d,%d\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

static void
1448
TAB_EraseTabInterior(const TAB_INFO *infoPtr, HDC hdc, INT iItem, const RECT *drawRect)
1449
{
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
1450
    LONG     lStyle  = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482
    HBRUSH   hbr = CreateSolidBrush (comctl32_color.clrBtnFace);
    BOOL     deleteBrush = TRUE;
    RECT     rTemp = *drawRect;

    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)
	    {
1483 1484
		InflateRect(&rTemp, 2, 2);
		FillRect(hdc, &rTemp, hbr);
1485
		if (iItem == infoPtr->iHotTracked)
1486
		    DrawEdge(hdc, &rTemp, BDR_RAISEDINNER, BF_RECT);
1487 1488 1489 1490 1491 1492 1493 1494
	    }
	    else
		FillRect(hdc, &rTemp, hbr);
	}

    }
    else /* !TCS_BUTTONS */
    {
1495
        InflateRect(&rTemp, -2, -2);
1496 1497
        if (!GetWindowTheme (infoPtr->hwnd))
	    FillRect(hdc, &rTemp, hbr);
1498 1499
    }

1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511
    /* highlighting is drawn on top of previous fills */
    if (TAB_GetItem(infoPtr, iItem)->dwState & TCIS_HIGHLIGHTED)
    {
        if (deleteBrush)
        {
            DeleteObject(hbr);
            deleteBrush = FALSE;
        }
        hbr = GetSysColorBrush(COLOR_HIGHLIGHT);
        FillRect(hdc, &rTemp, hbr);
    }

1512 1513 1514 1515
    /* Cleanup */
    if (deleteBrush) DeleteObject(hbr);
}

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

  RECT localRect;

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

    /*
     * Get the rectangle for the item.
     */
1543
    isVisible = TAB_InternalGetItemRect(infoPtr, iItem, &itemRect, &selectedRect);
1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557
    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.
     */
1558 1559 1560 1561 1562
    if (iItem == infoPtr->iSelected)
      *drawRect = selectedRect;
    else
      *drawRect = itemRect;
        
1563 1564 1565 1566
    if (lStyle & TCS_BUTTONS)
    {
      if (iItem == infoPtr->iSelected)
      {
1567 1568 1569
	drawRect->left   += 4;
	drawRect->top    += 4;
	drawRect->right  -= 4;
1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585

	if (lStyle & TCS_VERTICAL)
	{
	  if (!(lStyle & TCS_BOTTOM)) drawRect->right  += 1;
	  drawRect->bottom   -= 4;
	}
	else
	{
	  if (lStyle & TCS_BOTTOM)
	  {
	    drawRect->top    -= 2;
	    drawRect->bottom -= 4;
	  }
	  else
	    drawRect->bottom -= 1;
	}
1586 1587 1588 1589 1590 1591 1592
      }
      else
      {
	drawRect->left   += 2;
	drawRect->top    += 2;
	drawRect->right  -= 2;
	drawRect->bottom -= 2;
1593 1594 1595 1596
      }
    }
    else
    {
1597
      if ((lStyle & TCS_VERTICAL) && (lStyle & TCS_BOTTOM))
1598
      {
1599
        if (iItem != infoPtr->iSelected)
1600
	{
1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628
	  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;
1629 1630
	}
      }
1631
      else
1632
      {
1633 1634 1635 1636 1637 1638
        if (iItem == infoPtr->iSelected)
	{
	  drawRect->bottom += 3;
	}
	else
	{
1639
	  drawRect->bottom -= 2;
1640 1641
	  InflateRect(drawRect, -2, 0);
	}
1642
      }
1643 1644
    }
  }
1645
  TRACE("drawRect=(%s)\n", wine_dbgstr_rect(drawRect));
1646 1647

  /* Clear interior */
1648
  TAB_EraseTabInterior (infoPtr, hdc, iItem, drawRect);
1649 1650 1651

  /* Draw the focus rectangle */
  if (!(lStyle & TCS_FOCUSNEVER) &&
1652
      (GetFocus() == infoPtr->hwnd) &&
1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666
      (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);
  }
1667 1668 1669 1670

  /*
   * Text pen
   */
1671
  htextPen = CreatePen( PS_SOLID, 1, comctl32_color.clrBtnText );
1672 1673
  holdPen  = SelectObject(hdc, htextPen);
  hOldFont = SelectObject(hdc, infoPtr->hFont);
1674

1675 1676 1677
  /*
   * Setup for text output
  */
1678
  oldBkMode = SetBkMode(hdc, TRANSPARENT);
1679
  if (!GetWindowTheme (infoPtr->hwnd) || (lStyle & TCS_BUTTONS))
1680 1681 1682 1683 1684 1685 1686 1687 1688
  {
    if ((lStyle & TCS_HOTTRACK) && (iItem == infoPtr->iHotTracked) &&
        !(lStyle & TCS_FLATBUTTONS))
      SetTextColor(hdc, comctl32_color.clrHighlight);
    else if (TAB_GetItem(infoPtr, iItem)->dwState & TCIS_HIGHLIGHTED)
      SetTextColor(hdc, comctl32_color.clrHighlightText);
    else
      SetTextColor(hdc, comctl32_color.clrBtnText);
  }
1689 1690 1691 1692

  /*
   * if owner draw, tell the owner to draw
   */
1693
  if ((lStyle & TCS_OWNERDRAWFIXED) && GetParent(infoPtr->hwnd))
1694 1695 1696 1697
  {
    DRAWITEMSTRUCT dis;
    UINT id;

1698 1699 1700 1701 1702 1703 1704 1705
    drawRect->top += 2;
    drawRect->right -= 1;
    if ( iItem == infoPtr->iSelected )
    {
        drawRect->right -= 1;
        drawRect->left += 1;
    }

1706 1707 1708
    /*
     * get the control id
     */
1709
    id = (UINT)GetWindowLongPtrW( infoPtr->hwnd, GWLP_ID );
1710

1711
    /*
1712 1713
     * put together the DRAWITEMSTRUCT
     */
1714 1715 1716 1717
    dis.CtlType    = ODT_TAB;
    dis.CtlID      = id;
    dis.itemID     = iItem;
    dis.itemAction = ODA_DRAWENTIRE;
1718
    dis.itemState = 0;
1719
    if ( iItem == infoPtr->iSelected )
1720 1721 1722
      dis.itemState |= ODS_SELECTED;
    if (infoPtr->uFocus == iItem) 
      dis.itemState |= ODS_FOCUS;
1723
    dis.hwndItem = infoPtr->hwnd;
1724
    dis.hDC      = hdc;
1725
    CopyRect(&dis.rcItem,drawRect);
1726
    dis.itemData = (ULONG_PTR)TAB_GetItem(infoPtr, iItem)->extra;
1727 1728 1729 1730

    /*
     * send the draw message
     */
1731
    SendMessageW( infoPtr->hwndNotify, WM_DRAWITEM, (WPARAM)id, (LPARAM)&dis );
1732 1733 1734
  }
  else
  {
1735
    TAB_ITEM *item = TAB_GetItem(infoPtr, iItem);
1736 1737 1738 1739 1740
    RECT rcTemp;
    RECT rcImage;

    /* used to center the icon and text in the tab */
    RECT rcText;
1741
    INT center_offset_h, center_offset_v;
1742 1743 1744 1745 1746 1747

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

    rcTemp = *drawRect;

1748 1749
    rcText.left = rcText.top = rcText.right = rcText.bottom = 0;

1750
    /* get the rectangle that the text fits in */
1751
    if (item->pszText)
1752
    {
1753
      DrawTextW(hdc, item->pszText, -1, &rcText, DT_CALCRECT);
1754
    }
1755 1756 1757 1758 1759
    /*
     * If not owner draw, then do the drawing ourselves.
     *
     * Draw the icon.
     */
1760
    if (infoPtr->himl && item->iImage != -1)
1761
    {
1762 1763 1764
      INT cx;
      INT cy;
      
1765 1766 1767
      ImageList_GetIconSize(infoPtr->himl, &cx, &cy);

      if(lStyle & TCS_VERTICAL)
1768 1769
      {
        center_offset_h = ((drawRect->bottom - drawRect->top) - (cy + infoPtr->uHItemPadding + (rcText.right  - rcText.left))) / 2;
1770
        center_offset_v = ((drawRect->right - drawRect->left) - cx) / 2;
1771
      }
1772
      else
1773 1774
      {
        center_offset_h = ((drawRect->right - drawRect->left) - (cx + infoPtr->uHItemPadding + (rcText.right  - rcText.left))) / 2;
1775
        center_offset_v = ((drawRect->bottom - drawRect->top) - cy) / 2;
1776 1777
      }

1778 1779 1780 1781 1782 1783
      /* 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;

1784 1785
      if (lStyle & TCS_FIXEDWIDTH && lStyle & (TCS_FORCELABELLEFT | TCS_FORCEICONLEFT))
	center_offset_h = infoPtr->uHItemPadding;
1786

1787 1788 1789
      if (center_offset_h < 2)
        center_offset_h = 2;
	
1790 1791 1792
      if (center_offset_v < 0)
        center_offset_v = 0;
	
1793
      TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%s), textlen=%d\n",
1794
	  debugstr_w(item->pszText), center_offset_h, center_offset_v,
1795
          wine_dbgstr_rect(drawRect), (rcText.right-rcText.left));
1796

1797 1798
      if((lStyle & TCS_VERTICAL) && (lStyle & TCS_BOTTOM))
      {
1799
        rcImage.top = drawRect->top + center_offset_h;
1800 1801 1802 1803
	/* 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;
1804
        drawRect->top += cy + infoPtr->uHItemPadding;
1805 1806 1807
      }
      else if(lStyle & TCS_VERTICAL)
      {
1808 1809
        rcImage.top  = drawRect->bottom - cy - center_offset_h;
	rcImage.left = drawRect->left + center_offset_v;
1810
        drawRect->bottom -= cy + infoPtr->uHItemPadding;
1811 1812 1813
      }
      else /* normal style, whether TCS_BOTTOM or not */
      {
1814
        rcImage.left = drawRect->left + center_offset_h;
1815
	rcImage.top = drawRect->top + center_offset_v;
1816
        drawRect->left += cx + infoPtr->uHItemPadding;
1817
      }
1818

1819
      TRACE("drawing image=%d, left=%d, top=%d\n",
1820
	    item->iImage, rcImage.left, rcImage.top-1);
1821 1822 1823
      ImageList_Draw
        (
        infoPtr->himl,
1824
        item->iImage,
1825
        hdc,
1826
        rcImage.left,
1827
        rcImage.top,
1828 1829
        ILD_NORMAL
        );
1830
    }
1831 1832 1833 1834 1835

    /* Now position text */
    if (lStyle & TCS_FIXEDWIDTH && lStyle & TCS_FORCELABELLEFT)
      center_offset_h = infoPtr->uHItemPadding;
    else
1836
      if(lStyle & TCS_VERTICAL)
1837
        center_offset_h = ((drawRect->bottom - drawRect->top) - (rcText.right - rcText.left)) / 2;
1838
      else
1839
        center_offset_h = ((drawRect->right - drawRect->left) - (rcText.right - rcText.left)) / 2;
1840

1841 1842
    if(lStyle & TCS_VERTICAL)
    {
1843 1844 1845 1846 1847
      if(lStyle & TCS_BOTTOM)
        drawRect->top+=center_offset_h;
      else
        drawRect->bottom-=center_offset_h;

1848
      center_offset_v = ((drawRect->right - drawRect->left) - (rcText.bottom - rcText.top)) / 2;
1849
    }
1850
    else
1851
    {
1852
      drawRect->left += center_offset_h;
1853
      center_offset_v = ((drawRect->bottom - drawRect->top) - (rcText.bottom - rcText.top)) / 2;
1854 1855
    }

1856 1857 1858 1859 1860 1861
    /* 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;

1862 1863 1864 1865 1866 1867 1868 1869
    if (center_offset_v < 0)
      center_offset_v = 0;

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

1870
    /* Draw the text */
1871 1872
    if(lStyle & TCS_VERTICAL) /* if we are vertical rotate the text and each character */
    {
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
1873 1874
      static const WCHAR ArialW[] = { 'A','r','i','a','l',0 };
      LOGFONTW logfont;
1875 1876 1877 1878
      HFONT hFont = 0;
      INT nEscapement = 900;
      INT nOrientation = 900;

1879 1880 1881 1882 1883 1884
      if(lStyle & TCS_BOTTOM)
      {
        nEscapement = -900;
        nOrientation = -900;
      }

1885 1886
      /* 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
1887
      if (!GetObjectW((infoPtr->hFont) ?
1888
                infoPtr->hFont : GetStockObject(SYSTEM_FONT),
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
1889
                sizeof(LOGFONTW),&logfont))
1890
      {
1891
        INT iPointSize = 9;
1892

Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
1893
        lstrcpyW(logfont.lfFaceName, ArialW);
1894
        logfont.lfHeight = -MulDiv(iPointSize, GetDeviceCaps(hdc, LOGPIXELSY),
1895 1896 1897 1898 1899 1900
                                    72);
        logfont.lfWeight = FW_NORMAL;
        logfont.lfItalic = 0;
        logfont.lfUnderline = 0;
        logfont.lfStrikeOut = 0;
      }
1901 1902 1903

      logfont.lfEscapement = nEscapement;
      logfont.lfOrientation = nOrientation;
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
1904
      hFont = CreateFontIndirectW(&logfont);
1905
      SelectObject(hdc, hFont);
1906

1907
      if (item->pszText)
1908 1909 1910 1911 1912 1913
      {
        ExtTextOutW(hdc,
        (lStyle & TCS_BOTTOM) ? drawRect->right : drawRect->left,
        (!(lStyle & TCS_BOTTOM)) ? drawRect->bottom : drawRect->top,
        ETO_CLIPPED,
        drawRect,
1914 1915
        item->pszText,
        lstrlenW(item->pszText),
1916 1917
        0);
      }
1918 1919

      DeleteObject(hFont);
1920 1921 1922
    }
    else
    {
1923
      TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%s), textlen=%d\n",
1924
	  debugstr_w(item->pszText), center_offset_h, center_offset_v,
1925
          wine_dbgstr_rect(drawRect), (rcText.right-rcText.left));
1926
      if (item->pszText)
1927 1928 1929 1930
      {
        DrawTextW
        (
          hdc,
1931 1932
          item->pszText,
          lstrlenW(item->pszText),
1933 1934
          drawRect,
          DT_LEFT | DT_SINGLELINE
1935
        );
1936
      }
1937
    }
1938 1939

    *drawRect = rcTemp; /* restore drawRect */
1940 1941 1942 1943 1944
  }

  /*
  * Cleanup
  */
1945
  SelectObject(hdc, hOldFont);
1946 1947
  SetBkMode(hdc, oldBkMode);
  SelectObject(hdc, holdPen);
1948
  DeleteObject( htextPen );
1949 1950
}

Francis Beaudet's avatar
Francis Beaudet committed
1951 1952 1953 1954
/******************************************************************************
 * TAB_DrawItem
 *
 * This method is used to draw a single tab into the tab control.
1955
 */
1956
static void TAB_DrawItem(const TAB_INFO *infoPtr, HDC  hdc, INT  iItem)
Francis Beaudet's avatar
Francis Beaudet committed
1957
{
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
1958
  LONG      lStyle  = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
Francis Beaudet's avatar
Francis Beaudet committed
1959 1960 1961
  RECT      itemRect;
  RECT      selectedRect;
  BOOL      isVisible;
1962 1963 1964 1965
  RECT      r, fillRect, r1;
  INT       clRight = 0;
  INT       clBottom = 0;
  COLORREF  bkgnd, corner;
1966
  HTHEME    theme;
Francis Beaudet's avatar
Francis Beaudet committed
1967 1968 1969 1970

  /*
   * Get the rectangle for the item.
   */
1971
  isVisible = TAB_InternalGetItemRect(infoPtr,
Francis Beaudet's avatar
Francis Beaudet committed
1972 1973 1974 1975 1976 1977
				      iItem,
				      &itemRect,
				      &selectedRect);

  if (isVisible)
  {
1978 1979 1980
    RECT rUD, rC;

    /* Clip UpDown control to not draw over it */
1981 1982
    if (infoPtr->needsScrolling)
    {
1983
      GetWindowRect(infoPtr->hwnd, &rC);
1984 1985 1986
      GetWindowRect(infoPtr->hwndUpDown, &rUD);
      ExcludeClipRect(hdc, rUD.left - rC.left, rUD.top - rC.top, rUD.right - rC.left, rUD.bottom - rC.top);
    }
1987

1988 1989
    /* If you need to see what the control is doing,
     * then override these variables. They will change what
1990
     * fill colors are used for filling the tabs, and the
1991 1992 1993 1994
     * corners when drawing the edge.
     */
    bkgnd = comctl32_color.clrBtnFace;
    corner = comctl32_color.clrBtnFace;
Francis Beaudet's avatar
Francis Beaudet committed
1995

1996
    if (lStyle & TCS_BUTTONS)
Francis Beaudet's avatar
Francis Beaudet committed
1997
    {
1998
      /* Get item rectangle */
1999 2000
      r = itemRect;

2001
      /* Separators between flat buttons */
2002
      if ((lStyle & TCS_FLATBUTTONS) && (infoPtr->exStyle & TCS_EX_FLATSEPARATORS))
2003
      {
2004 2005 2006
	r1 = r;
	r1.right += (FLAT_BTN_SPACINGX -2);
	DrawEdge(hdc, &r1, EDGE_ETCHED, BF_RIGHT);
2007 2008
      }

2009 2010
      if (iItem == infoPtr->iSelected)
      {
2011
	DrawEdge(hdc, &r, EDGE_SUNKEN, BF_SOFT|BF_RECT);
2012 2013
	
	OffsetRect(&r, 1, 1);
2014
      }
2015
      else  /* ! selected */
2016
      {
2017
        DWORD state = TAB_GetItem(infoPtr, iItem)->dwState;
2018 2019 2020 2021 2022 2023

        if (state & TCIS_BUTTONPRESSED)
          DrawEdge(hdc, &r, EDGE_SUNKEN, BF_SOFT|BF_RECT);
        else
          if (!(lStyle & TCS_FLATBUTTONS))
            DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_RECT);
2024
      }
Francis Beaudet's avatar
Francis Beaudet committed
2025
    }
2026
    else /* !TCS_BUTTONS */
Francis Beaudet's avatar
Francis Beaudet committed
2027
    {
2028 2029
      /* We draw a rectangle of different sizes depending on the selection
       * state. */
2030 2031
      if (iItem == infoPtr->iSelected) {
	RECT rect;
2032
	GetClientRect (infoPtr->hwnd, &rect);
2033 2034
	clRight = rect.right;
	clBottom = rect.bottom;
2035
        r = selectedRect;
2036
      }
2037 2038
      else
        r = itemRect;
Francis Beaudet's avatar
Francis Beaudet committed
2039

2040
      /*
2041
       * Erase the background. (Delay it but setup rectangle.)
2042 2043
       * This is necessary when drawing the selected item since it is larger
       * than the others, it might overlap with stuff already drawn by the
2044
       * other tabs
2045
       */
2046
      fillRect = r;
2047

2048 2049 2050 2051 2052 2053 2054
      /* 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))
      {
2055
          static const int partIds[8] = {
2056 2057 2058 2059 2060 2061 2062 2063 2064 2065 2066 2067 2068 2069 2070 2071 2072 2073 2074 2075 2076 2077 2078 2079 2080 2081 2082 2083 2084 2085 2086 2087 2088 2089 2090 2091 2092 2093 2094
              /* 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)
2095
      {
2096 2097
	/* These are for adjusting the drawing of a Selected tab      */
	/* The initial values are for the normal case of non-Selected */
2098
	int ZZ = 1;   /* Do not stretch if selected */
2099 2100 2101 2102 2103
	if (iItem == infoPtr->iSelected) {
	    ZZ = 0;

	    /* if leftmost draw the line longer */
	    if(selectedRect.top == 0)
2104
		fillRect.top += CONTROL_BORDER_SIZEY;
2105 2106
	    /* if rightmost draw the line longer */
	    if(selectedRect.bottom == clBottom)
2107
		fillRect.bottom -= CONTROL_BORDER_SIZEY;
2108 2109
	}

2110 2111
        if (lStyle & TCS_BOTTOM)
        {
2112 2113 2114
	  /* Adjust both rectangles to match native */
	  r.left += (1-ZZ);

2115 2116
          TRACE("<right> item=%d, fill=(%s), edge=(%s)\n",
                iItem, wine_dbgstr_rect(&fillRect), wine_dbgstr_rect(&r));
2117 2118 2119

	  /* Clear interior */
	  SetBkColor(hdc, bkgnd);
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
2120
	  ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2121 2122 2123 2124

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

2125
	  /* Now erase the top corner and draw diagonal edge */
2126 2127 2128 2129 2130
	  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
2131
	  ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2132 2133 2134
	  r1.right--;
	  DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPLEFT);

2135
	  /* Now erase the bottom corner and draw diagonal edge */
2136 2137 2138 2139
	  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
2140
	  ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2141 2142 2143 2144 2145 2146 2147 2148 2149 2150
	  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);
	  }

2151 2152 2153
        }
        else
        {
2154 2155
          TRACE("<left> item=%d, fill=(%s), edge=(%s)\n",
                iItem, wine_dbgstr_rect(&fillRect), wine_dbgstr_rect(&r));
2156 2157 2158

	  /* Clear interior */
	  SetBkColor(hdc, bkgnd);
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
2159
	  ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2160 2161 2162 2163

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

2164
	  /* Now erase the top corner and draw diagonal edge */
2165 2166 2167 2168 2169
	  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
2170
	  ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2171 2172 2173
	  r1.left++;
	  DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPRIGHT);

2174
	  /* Now erase the bottom corner and draw diagonal edge */
2175 2176 2177 2178
	  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
2179
	  ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2180 2181
	  r1.left++;
	  DrawEdge(hdc, &r1, EDGE_SUNKEN, BF_DIAGONAL_ENDTOPLEFT);
2182
        }
2183
      }
2184
      else  /* ! TCS_VERTICAL */
2185
      {
2186 2187 2188 2189 2190
	/* 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)
2191
		fillRect.left += CONTROL_BORDER_SIZEX;
2192 2193
	    /* if rightmost draw the line longer */
	    if(selectedRect.right == clRight)
2194
		fillRect.right -= CONTROL_BORDER_SIZEX;
2195 2196
	}

2197 2198
        if (lStyle & TCS_BOTTOM)
        {
2199
	  /* Adjust both rectangles for topmost row */
2200
	  if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2201 2202 2203 2204
	  {
	    fillRect.top -= 2;
	    r.top -= 1;
	  }
2205

2206 2207
          TRACE("<bottom> item=%d, fill=(%s), edge=(%s)\n",
                iItem, wine_dbgstr_rect(&fillRect), wine_dbgstr_rect(&r));
2208 2209 2210

	  /* Clear interior */
	  SetBkColor(hdc, bkgnd);
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
2211
	  ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2212 2213 2214 2215

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

2216
	  /* Now erase the righthand corner and draw diagonal edge */
2217 2218 2219 2220 2221
	  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
2222
	  ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2223 2224 2225
	  r1.bottom--;
	  DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMLEFT);

2226
	  /* Now erase the lefthand corner and draw diagonal edge */
2227 2228 2229 2230
	  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
2231
	  ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2232 2233 2234
	  r1.bottom--;
	  DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPLEFT);

2235 2236 2237 2238 2239 2240
	  if (iItem == infoPtr->iSelected)
	  {
	    r.top += 2;
	    r.left += 1;
	    if (selectedRect.left == 0)
	    {
2241 2242 2243 2244
	      r1 = r;
	      r1.bottom = r1.top;
	      r1.top--;
	      DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_LEFT);
2245
	    }
2246 2247
	  }

2248 2249 2250
        }
        else
        {
2251
	  /* Adjust both rectangles for bottommost row */
2252
	  if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2253 2254 2255 2256
	  {
	    fillRect.bottom += 3;
	    r.bottom += 2;
	  }
2257

2258 2259
          TRACE("<top> item=%d, fill=(%s), edge=(%s)\n",
                iItem, wine_dbgstr_rect(&fillRect), wine_dbgstr_rect(&r));
2260 2261 2262

	  /* Clear interior */
	  SetBkColor(hdc, bkgnd);
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
2263
	  ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2264 2265 2266 2267

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

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

2278
	  /* Now erase the lefthand corner and draw diagonal edge */
2279 2280 2281 2282
	  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
2283
	  ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2284 2285
	  r1.top++;
	  DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPRIGHT);
2286
        }
2287 2288
      }
    }
2289

2290 2291
    TAB_DumpItemInternal(infoPtr, iItem);

2292
    /* This modifies r to be the text rectangle. */
2293
    TAB_DrawItemInterior(infoPtr, hdc, iItem, &r);
Francis Beaudet's avatar
Francis Beaudet committed
2294
  }
Alexandre Julliard's avatar
Alexandre Julliard committed
2295 2296
}

Francis Beaudet's avatar
Francis Beaudet committed
2297 2298 2299 2300 2301
/******************************************************************************
 * TAB_DrawBorder
 *
 * This method is used to draw the raised border around the tab control
 * "content" area.
2302
 */
2303
static void TAB_DrawBorder(const TAB_INFO *infoPtr, HDC hdc)
Alexandre Julliard's avatar
Alexandre Julliard committed
2304
{
Francis Beaudet's avatar
Francis Beaudet committed
2305
  RECT rect;
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
2306
  DWORD lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
2307
  HTHEME theme = GetWindowTheme (infoPtr->hwnd);
Alexandre Julliard's avatar
Alexandre Julliard committed
2308

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

Francis Beaudet's avatar
Francis Beaudet committed
2311 2312 2313
  /*
   * Adjust for the style
   */
Gerard Patel's avatar
Gerard Patel committed
2314 2315

  if (infoPtr->uNumItem)
Francis Beaudet's avatar
Francis Beaudet committed
2316
  {
Gerard Patel's avatar
Gerard Patel committed
2317
    if ((lStyle & TCS_BOTTOM) && !(lStyle & TCS_VERTICAL))
2318
      rect.bottom -= infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
Gerard Patel's avatar
Gerard Patel committed
2319
    else if((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
2320
      rect.right  -= infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
Gerard Patel's avatar
Gerard Patel committed
2321
    else if(lStyle & TCS_VERTICAL)
2322
      rect.left   += infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
Gerard Patel's avatar
Gerard Patel committed
2323
    else /* not TCS_VERTICAL and not TCS_BOTTOM */
2324
      rect.top    += infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
Francis Beaudet's avatar
Francis Beaudet committed
2325
  }
Alexandre Julliard's avatar
Alexandre Julliard committed
2326

2327
  TRACE("border=(%s)\n", wine_dbgstr_rect(&rect));
2328

2329 2330 2331 2332
  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
2333 2334
}

Francis Beaudet's avatar
Francis Beaudet committed
2335 2336 2337 2338
/******************************************************************************
 * TAB_Refresh
 *
 * This method repaints the tab control..
2339
 */
2340
static void TAB_Refresh (TAB_INFO *infoPtr, HDC hdc)
Alexandre Julliard's avatar
Alexandre Julliard committed
2341
{
Francis Beaudet's avatar
Francis Beaudet committed
2342 2343
  HFONT hOldFont;
  INT i;
Alexandre Julliard's avatar
Alexandre Julliard committed
2344

Francis Beaudet's avatar
Francis Beaudet committed
2345 2346
  if (!infoPtr->DoRedraw)
    return;
Alex Priem's avatar
Alex Priem committed
2347

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

Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
2350
  if (GetWindowLongW(infoPtr->hwnd, GWL_STYLE) & TCS_BUTTONS)
Francis Beaudet's avatar
Francis Beaudet committed
2351
  {
2352
    for (i = 0; i < infoPtr->uNumItem; i++)
2353
      TAB_DrawItem (infoPtr, hdc, i);
Francis Beaudet's avatar
Francis Beaudet committed
2354
  }
2355 2356
  else
  {
2357
    /* Draw all the non selected item first */
2358
    for (i = 0; i < infoPtr->uNumItem; i++)
2359 2360
    {
      if (i != infoPtr->iSelected)
2361
	TAB_DrawItem (infoPtr, hdc, i);
2362
    }
Francis Beaudet's avatar
Francis Beaudet committed
2363

2364 2365
    /* Now, draw the border, draw it before the selected item
     * since the selected item overwrites part of the border. */
2366
    TAB_DrawBorder (infoPtr, hdc);
Francis Beaudet's avatar
Francis Beaudet committed
2367

2368
    /* Then, draw the selected item */
2369
    TAB_DrawItem (infoPtr, hdc, infoPtr->iSelected);
2370
  }
Francis Beaudet's avatar
Francis Beaudet committed
2371 2372

  SelectObject (hdc, hOldFont);
Alexandre Julliard's avatar
Alexandre Julliard committed
2373 2374
}

2375
static inline DWORD TAB_GetRowCount (const TAB_INFO *infoPtr)
2376 2377 2378 2379
{
  return infoPtr->uNumRows;
}

2380
static inline LRESULT TAB_SetRedraw (TAB_INFO *infoPtr, BOOL doRedraw)
Alex Priem's avatar
Alex Priem committed
2381
{
2382
  infoPtr->DoRedraw = doRedraw;
Francis Beaudet's avatar
Francis Beaudet committed
2383
  return 0;
Alex Priem's avatar
Alex Priem committed
2384 2385
}

Francis Beaudet's avatar
Francis Beaudet committed
2386 2387 2388 2389 2390 2391 2392 2393
/******************************************************************************
 * 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
2394
{
Alexandre Julliard's avatar
Alexandre Julliard committed
2395
  INT iSelected = infoPtr->iSelected;
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
2396
  LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
2397 2398
  INT iOrigLeftmostVisible = infoPtr->leftmostVisible;

2399 2400
  /* set the items row to the bottommost row or topmost row depending on
   * style */
Gerard Patel's avatar
Gerard Patel committed
2401
  if ((infoPtr->uNumRows > 1) && !(lStyle & TCS_BUTTONS))
2402
  {
2403
      TAB_ITEM *selected = TAB_GetItem(infoPtr, iSelected);
2404
      INT newselected;
2405 2406
      INT iTargetRow;

2407
      if(lStyle & TCS_VERTICAL)
2408
        newselected = selected->rect.left;
2409
      else
2410
        newselected = selected->rect.top;
2411

Gerard Patel's avatar
Gerard Patel committed
2412 2413 2414
      /* the target row is always (number of rows - 1)
         as row 0 is furthest from the clientRect */
      iTargetRow = infoPtr->uNumRows - 1;
2415 2416 2417

      if (newselected != iTargetRow)
      {
2418
         UINT i;
2419 2420 2421 2422 2423
         if(lStyle & TCS_VERTICAL)
         {
           for (i=0; i < infoPtr->uNumItem; i++)
           {
             /* move everything in the row of the selected item to the iTargetRow */
2424 2425 2426 2427
             TAB_ITEM *item = TAB_GetItem(infoPtr, i);

             if (item->rect.left == newselected )
                 item->rect.left = iTargetRow;
2428 2429
             else
             {
2430 2431
               if (item->rect.left > newselected)
                 item->rect.left-=1;
2432 2433 2434 2435 2436 2437 2438
             }
           }
         }
         else
         {
           for (i=0; i < infoPtr->uNumItem; i++)
           {
2439 2440 2441 2442
             TAB_ITEM *item = TAB_GetItem(infoPtr, i);

             if (item->rect.top == newselected )
                 item->rect.top = iTargetRow;
2443 2444
             else
             {
2445 2446
               if (item->rect.top > newselected)
                 item->rect.top-=1;
2447 2448 2449
             }
          }
        }
2450
        TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
2451 2452 2453
      }
  }

Francis Beaudet's avatar
Francis Beaudet committed
2454 2455 2456 2457
  /*
   * Do the trivial cases first.
   */
  if ( (!infoPtr->needsScrolling) ||
2458
       (infoPtr->hwndUpDown==0) || (lStyle & TCS_VERTICAL))
Francis Beaudet's avatar
Francis Beaudet committed
2459 2460
    return;

Alexandre Julliard's avatar
Alexandre Julliard committed
2461
  if (infoPtr->leftmostVisible >= iSelected)
Francis Beaudet's avatar
Francis Beaudet committed
2462
  {
Alexandre Julliard's avatar
Alexandre Julliard committed
2463
    infoPtr->leftmostVisible = iSelected;
Francis Beaudet's avatar
Francis Beaudet committed
2464
  }
Alexandre Julliard's avatar
Alexandre Julliard committed
2465
  else
Francis Beaudet's avatar
Francis Beaudet committed
2466
  {
2467
     TAB_ITEM *selected = TAB_GetItem(infoPtr, iSelected);
Alexandre Julliard's avatar
Alexandre Julliard committed
2468
     RECT r;
2469 2470
     INT width;
     UINT i;
2471 2472

     /* Calculate the part of the client area that is visible */
2473
     GetClientRect(infoPtr->hwnd, &r);
Alexandre Julliard's avatar
Alexandre Julliard committed
2474 2475 2476 2477 2478
     width = r.right;

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

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

2498
  if (infoPtr->leftmostVisible != iOrigLeftmostVisible)
2499
    TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
2500

2501
  SendMessageW(infoPtr->hwndUpDown, UDM_SETPOS, 0,
Alexandre Julliard's avatar
Alexandre Julliard committed
2502
               MAKELONG(infoPtr->leftmostVisible, 0));
Francis Beaudet's avatar
Francis Beaudet committed
2503
}
Alexandre Julliard's avatar
Alexandre Julliard committed
2504

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

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

2521
  GetClientRect(infoPtr->hwnd, &clientRect);
2522
  rInvalidate = clientRect;
2523 2524
  rAdjClient = clientRect;

2525
  TAB_AdjustRect(infoPtr, 0, &rAdjClient);
Alexandre Julliard's avatar
Alexandre Julliard committed
2526

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

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

2565
  InvalidateRect(infoPtr->hwnd, &rInvalidate, TRUE);
Francis Beaudet's avatar
Francis Beaudet committed
2566
}
Alexandre Julliard's avatar
Alexandre Julliard committed
2567

2568
static inline LRESULT TAB_Paint (TAB_INFO *infoPtr, HDC hdcPaint)
Francis Beaudet's avatar
Francis Beaudet committed
2569 2570 2571
{
  HDC hdc;
  PAINTSTRUCT ps;
2572

2573 2574 2575
  if (hdcPaint)
    hdc = hdcPaint;
  else
2576
  {
2577
    hdc = BeginPaint (infoPtr->hwnd, &ps);
2578
    TRACE("erase %d, rect=(%s)\n", ps.fErase, wine_dbgstr_rect(&ps.rcPaint));
2579
  }
2580

2581 2582 2583 2584
  TAB_Refresh (infoPtr, hdc);

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

Francis Beaudet's avatar
Francis Beaudet committed
2586 2587
  return 0;
}
Alexandre Julliard's avatar
Alexandre Julliard committed
2588

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

2597
  GetClientRect (infoPtr->hwnd, &rect);
2598
  TRACE("Rect: %p %s\n", infoPtr->hwnd, wine_dbgstr_rect(&rect));
2599

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

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

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


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

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

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

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

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

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

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

2641
  item = TAB_GetItem(infoPtr, iItem);
2642

2643
  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

2672 2673 2674 2675
  /* If we haven't set the current focus yet, set it now. */
  if (infoPtr->uFocus == -1)
    TAB_SetCurFocus(infoPtr, iItem);

Francis Beaudet's avatar
Francis Beaudet committed
2676
  return iItem;
Alexandre Julliard's avatar
Alexandre Julliard committed
2677 2678
}

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

2686 2687 2688
  lResult = MAKELONG(infoPtr->tabWidth, infoPtr->tabHeight);

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

2695 2696 2697 2698 2699 2700 2701 2702 2703 2704 2705 2706
  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)
2707
  {
2708 2709
    TAB_SetItemBounds(infoPtr);
    RedrawWindow(infoPtr->hwnd, NULL, NULL, RDW_ERASE | RDW_INVALIDATE | RDW_UPDATENOW);
2710
  }
2711

2712 2713 2714
  return lResult;
}

2715
static inline LRESULT TAB_SetMinTabWidth (TAB_INFO *infoPtr, INT cx)
2716
{
2717 2718 2719
  INT oldcx = 0;

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

2721 2722 2723 2724
  if (infoPtr->tabMinWidth < 0)
    oldcx = DEFAULT_MIN_TAB_WIDTH;
  else
    oldcx = infoPtr->tabMinWidth;
2725
  infoPtr->tabMinWidth = cx;
2726
  TAB_SetItemBounds(infoPtr);
2727 2728 2729
  return oldcx;
}

2730 2731
static inline LRESULT 
TAB_HighlightItem (TAB_INFO *infoPtr, INT iItem, BOOL fHighlight)
2732
{
2733
  LPDWORD lpState;
2734 2735
  DWORD oldState;
  RECT r;
2736

2737 2738 2739
  TRACE("(%p,%d,%s)\n", infoPtr, iItem, fHighlight ? "true" : "false");

  if (!infoPtr || iItem < 0 || iItem >= infoPtr->uNumItem)
2740
    return FALSE;
2741 2742
  
  lpState = &TAB_GetItem(infoPtr, iItem)->dwState;
2743
  oldState = *lpState;
2744 2745 2746 2747 2748

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

2750 2751 2752
  if ((oldState != *lpState) && TAB_InternalGetItemRect (infoPtr, iItem, &r, NULL))
    InvalidateRect (infoPtr->hwnd, &r, TRUE);

2753 2754 2755
  return TRUE;
}

2756
static LRESULT
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
2757
TAB_SetItemT (TAB_INFO *infoPtr, INT iItem, LPTCITEMW tabItem, BOOL bUnicode)
2758
{
2759
  TAB_ITEM *wineItem;
2760

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

2763 2764
  if (iItem < 0 || iItem >= infoPtr->uNumItem)
    return FALSE;
2765

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

2768
  wineItem = TAB_GetItem(infoPtr, iItem);
2769

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

2773
  if (tabItem->mask & TCIF_PARAM)
2774
    memcpy(wineItem->extra, &tabItem->lParam, infoPtr->cbInfo);
2775

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

2779
  if (tabItem->mask & TCIF_STATE)
2780 2781
    wineItem->dwState = (wineItem->dwState & ~tabItem->dwStateMask) |
                        ( tabItem->dwState &  tabItem->dwStateMask);
2782

2783
  if (tabItem->mask & TCIF_TEXT)
2784
  {
2785 2786
    Free(wineItem->pszText);
    wineItem->pszText = NULL;
2787
    if (bUnicode)
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
2788
      Str_SetPtrW(&wineItem->pszText, tabItem->pszText);
2789
    else
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
2790
      Str_SetPtrAtoW(&wineItem->pszText, (LPSTR)tabItem->pszText);
2791 2792
  }

2793
  /* Update and repaint tabs */
2794 2795
  TAB_SetItemBounds(infoPtr);
  TAB_InvalidateTabArea(infoPtr);
2796

Francis Beaudet's avatar
Francis Beaudet committed
2797
  return TRUE;
2798 2799
}

2800
static inline LRESULT TAB_GetItemCount (const TAB_INFO *infoPtr)
2801 2802 2803 2804 2805
{
   return infoPtr->uNumItem;
}


2806
static LRESULT
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
2807
TAB_GetItemT (TAB_INFO *infoPtr, INT iItem, LPTCITEMW tabItem, BOOL bUnicode)
2808
{
2809
  TAB_ITEM *wineItem;
2810

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

2813 2814
  if (!tabItem) return FALSE;

2815
  if (iItem < 0 || iItem >= infoPtr->uNumItem)
2816 2817 2818 2819 2820
  {
    /* init requested fields */
    if (tabItem->mask & TCIF_IMAGE) tabItem->iImage  = 0;
    if (tabItem->mask & TCIF_PARAM) tabItem->lParam  = 0;
    if (tabItem->mask & TCIF_STATE) tabItem->dwState = 0;
2821
    return FALSE;
2822
  }
2823

2824
  wineItem = TAB_GetItem(infoPtr, iItem);
2825 2826 2827 2828 2829

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

  if (tabItem->mask & TCIF_PARAM)
2830
    memcpy(&tabItem->lParam, wineItem->extra, infoPtr->cbInfo);
2831 2832 2833 2834 2835

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

  if (tabItem->mask & TCIF_STATE)
2836
    tabItem->dwState = wineItem->dwState & tabItem->dwStateMask;
2837 2838

  if (tabItem->mask & TCIF_TEXT)
2839 2840
  {
    if (bUnicode)
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
2841
      Str_GetPtrW (wineItem->pszText, tabItem->pszText, tabItem->cchTextMax);
2842
    else
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
2843
      Str_GetPtrWtoA (wineItem->pszText, (LPSTR)tabItem->pszText, tabItem->cchTextMax);
2844
  }
2845

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

2848 2849 2850 2851
  return TRUE;
}


2852
static LRESULT TAB_DeleteItem (TAB_INFO *infoPtr, INT iItem)
2853
{
2854
    BOOL bResult = FALSE;
2855

2856 2857
    TRACE("(%p, %d)\n", infoPtr, iItem);

2858 2859
    if ((iItem >= 0) && (iItem < infoPtr->uNumItem))
    {
2860 2861 2862
        TAB_ITEM *item = TAB_GetItem(infoPtr, iItem);
        LPBYTE oldItems = (LPBYTE)infoPtr->items;

2863 2864
	TAB_InvalidateTabArea(infoPtr);
        Free(item->pszText);
2865
	infoPtr->uNumItem--;
2866

2867 2868 2869 2870 2871
	if (!infoPtr->uNumItem)
        {
            infoPtr->items = NULL;
            if (infoPtr->iHotTracked >= 0)
            {
2872
                KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
2873 2874 2875 2876 2877 2878
                infoPtr->iHotTracked = -1;
            }
        }
        else
	{
	    infoPtr->items = Alloc(TAB_ITEM_SIZE(infoPtr) * infoPtr->uNumItem);
2879

2880 2881
	    if (iItem > 0)
	        memcpy(infoPtr->items, oldItems, iItem * TAB_ITEM_SIZE(infoPtr));
2882

2883 2884 2885 2886 2887 2888 2889 2890
	    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 */
2891
                FIXME("Recalc hot track\n");
2892 2893
            }
	}
2894
	Free(oldItems);
2895

2896 2897 2898
	/* Readjust the selected index */
	if ((iItem == infoPtr->iSelected) && (iItem > 0))
	    infoPtr->iSelected--;
2899

2900 2901
	if (iItem < infoPtr->iSelected)
	    infoPtr->iSelected--;
2902

2903 2904
	if (infoPtr->uNumItem == 0)
	    infoPtr->iSelected = -1;
2905

2906
	/* Reposition and repaint tabs */
2907
	TAB_SetItemBounds(infoPtr);
2908

2909 2910
	bResult = TRUE;
    }
2911

2912
    return bResult;
2913
}
Alex Priem's avatar
Alex Priem committed
2914

2915
static inline LRESULT TAB_DeleteAllItems (TAB_INFO *infoPtr)
2916
{
2917
    TRACE("(%p)\n", infoPtr);
2918
    while (infoPtr->uNumItem)
2919
      TAB_DeleteItem (infoPtr, 0);
2920
    return TRUE;
2921 2922 2923
}


2924
static inline LRESULT TAB_GetFont (const TAB_INFO *infoPtr)
2925
{
2926
  TRACE("(%p) returning %p\n", infoPtr, infoPtr->hFont);
2927 2928 2929
  return (LRESULT)infoPtr->hFont;
}

2930
static inline LRESULT TAB_SetFont (TAB_INFO *infoPtr, HFONT hNewFont)
2931
{
2932
  TRACE("(%p,%p)\n", infoPtr, hNewFont);
2933

2934
  infoPtr->hFont = hNewFont;
2935

2936
  TAB_SetItemBounds(infoPtr);
2937

2938
  TAB_InvalidateTabArea(infoPtr);
2939

Francis Beaudet's avatar
Francis Beaudet committed
2940
  return 0;
2941 2942 2943
}


2944
static inline LRESULT TAB_GetImageList (const TAB_INFO *infoPtr)
2945
{
2946
  TRACE("\n");
2947 2948 2949
  return (LRESULT)infoPtr->himl;
}

2950
static inline LRESULT TAB_SetImageList (TAB_INFO *infoPtr, HIMAGELIST himlNew)
2951
{
2952
    HIMAGELIST himlPrev = infoPtr->himl;
2953
    TRACE("\n");
2954
    infoPtr->himl = himlNew;
2955 2956
    TAB_SetItemBounds(infoPtr);
    InvalidateRect(infoPtr->hwnd, NULL, TRUE);
2957 2958 2959
    return (LRESULT)himlPrev;
}

2960
static inline LRESULT TAB_GetUnicodeFormat (const TAB_INFO *infoPtr)
2961 2962 2963 2964
{
    return infoPtr->bUnicode;
}

2965
static inline LRESULT TAB_SetUnicodeFormat (TAB_INFO *infoPtr, BOOL bUnicode)
2966 2967 2968
{
    BOOL bTemp = infoPtr->bUnicode;

2969
    infoPtr->bUnicode = bUnicode;
2970 2971 2972

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

2974
static inline LRESULT TAB_Size (TAB_INFO *infoPtr)
Alex Priem's avatar
Alex Priem committed
2975
{
2976 2977 2978 2979 2980
/* 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.

2981 2982 2983
  RECT parent_rect;
  HWND parent;
  UINT uPosFlags,cx,cy;
Alex Priem's avatar
Alex Priem committed
2984 2985 2986

  uPosFlags=0;
  if (!wParam) {
Francis Beaudet's avatar
Francis Beaudet committed
2987 2988 2989 2990
    parent = GetParent (hwnd);
    GetClientRect(parent, &parent_rect);
    cx=LOWORD (lParam);
    cy=HIWORD (lParam);
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
2991
    if (GetWindowLongW(hwnd, GWL_STYLE) & CCS_NORESIZE)
Alex Priem's avatar
Alex Priem committed
2992 2993
        uPosFlags |= (SWP_NOSIZE | SWP_NOMOVE);

Francis Beaudet's avatar
Francis Beaudet committed
2994
    SetWindowPos (hwnd, 0, parent_rect.left, parent_rect.top,
Alex Priem's avatar
Alex Priem committed
2995
            cx, cy, uPosFlags | SWP_NOZORDER);
2996
  } else {
Andreas Mohr's avatar
Andreas Mohr committed
2997
    FIXME("WM_SIZE flag %x %lx not handled\n", wParam, lParam);
2998
  } */
Alex Priem's avatar
Alex Priem committed
2999

3000
  /* Recompute the size/position of the tabs. */
3001
  TAB_SetItemBounds (infoPtr);
Francis Beaudet's avatar
Francis Beaudet committed
3002

3003
  /* Force a repaint of the control. */
3004
  InvalidateRect(infoPtr->hwnd, NULL, TRUE);
Alex Priem's avatar
Alex Priem committed
3005 3006 3007 3008 3009

  return 0;
}


3010
static LRESULT TAB_Create (HWND hwnd, LPARAM lParam)
Alexandre Julliard's avatar
Alexandre Julliard committed
3011
{
Francis Beaudet's avatar
Francis Beaudet committed
3012
  TAB_INFO *infoPtr;
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
3013
  TEXTMETRICW fontMetrics;
3014 3015
  HDC hdc;
  HFONT hOldFont;
Alexandre Julliard's avatar
Alexandre Julliard committed
3016
  DWORD dwStyle;
Alexandre Julliard's avatar
Alexandre Julliard committed
3017

3018
  infoPtr = Alloc (sizeof(TAB_INFO));
Francis Beaudet's avatar
Francis Beaudet committed
3019

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

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

3045
  TRACE("Created tab control, hwnd [%p]\n", hwnd);
3046 3047 3048

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

3053 3054
  infoPtr->exStyle = (dwStyle & TCS_FLATBUTTONS) ? TCS_EX_FLATSEPARATORS : 0;

Alexandre Julliard's avatar
Alexandre Julliard committed
3055
  if (dwStyle & TCS_TOOLTIPS) {
Alex Priem's avatar
Alex Priem committed
3056 3057
    /* Create tooltip control */
    infoPtr->hwndToolTip =
3058
      CreateWindowExW (0, TOOLTIPS_CLASSW, NULL, WS_POPUP,
Francis Beaudet's avatar
Francis Beaudet committed
3059 3060 3061
		       CW_USEDEFAULT, CW_USEDEFAULT,
		       CW_USEDEFAULT, CW_USEDEFAULT,
		       hwnd, 0, 0, 0);
3062

Alex Priem's avatar
Alex Priem committed
3063 3064
    /* Send NM_TOOLTIPSCREATED notification */
    if (infoPtr->hwndToolTip) {
Francis Beaudet's avatar
Francis Beaudet committed
3065
      NMTOOLTIPSCREATED nmttc;
3066

Francis Beaudet's avatar
Francis Beaudet committed
3067
      nmttc.hdr.hwndFrom = hwnd;
3068
      nmttc.hdr.idFrom = GetWindowLongPtrW(hwnd, GWLP_ID);
Francis Beaudet's avatar
Francis Beaudet committed
3069 3070
      nmttc.hdr.code = NM_TOOLTIPSCREATED;
      nmttc.hwndToolTips = infoPtr->hwndToolTip;
3071

3072
      SendMessageW (infoPtr->hwndNotify, WM_NOTIFY,
3073
		    (WPARAM)GetWindowLongPtrW(hwnd, GWLP_ID), (LPARAM)&nmttc);
Alex Priem's avatar
Alex Priem committed
3074
    }
3075 3076
  }

3077 3078
  OpenThemeData (infoPtr->hwnd, themeClass);
  
3079 3080 3081 3082
  /*
   * We need to get text information so we need a DC and we need to select
   * a font.
   */
3083
  hdc = GetDC(hwnd);
3084 3085
  hOldFont = SelectObject (hdc, GetStockObject (SYSTEM_FONT));

3086
  /* Use the system font to determine the initial height of a tab. */
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
3087
  GetTextMetricsW(hdc, &fontMetrics);
3088 3089

  /*
3090 3091
   * Make sure there is enough space for the letters + growing the
   * selected item + extra space for the selected item.
3092
   */
3093
  infoPtr->tabHeight = fontMetrics.tmHeight + SELECTED_TAB_OFFSET +
3094
	               ((dwStyle & TCS_BUTTONS) ? 2 : 1) *
3095
                        infoPtr->uVItemPadding;
3096

3097
  /* Initialize the width of a tab. */
3098
  if (dwStyle & TCS_FIXEDWIDTH)
3099
    infoPtr->tabWidth = GetDeviceCaps(hdc, LOGPIXELSX);
3100 3101

  infoPtr->tabMinWidth = -1;
3102

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

3105 3106 3107
  SelectObject (hdc, hOldFont);
  ReleaseDC(hwnd, hdc);

Francis Beaudet's avatar
Francis Beaudet committed
3108
  return 0;
Alexandre Julliard's avatar
Alexandre Julliard committed
3109 3110 3111
}

static LRESULT
3112
TAB_Destroy (TAB_INFO *infoPtr)
Alexandre Julliard's avatar
Alexandre Julliard committed
3113
{
3114
  UINT iItem;
Alexandre Julliard's avatar
Alexandre Julliard committed
3115

Alexandre Julliard's avatar
Alexandre Julliard committed
3116 3117 3118
  if (!infoPtr)
      return 0;

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

Francis Beaudet's avatar
Francis Beaudet committed
3121 3122
  if (infoPtr->items) {
    for (iItem = 0; iItem < infoPtr->uNumItem; iItem++) {
3123
      Free (TAB_GetItem(infoPtr, iItem)->pszText);
Francis Beaudet's avatar
Francis Beaudet committed
3124
    }
3125
    Free (infoPtr->items);
Francis Beaudet's avatar
Francis Beaudet committed
3126
  }
3127 3128

  if (infoPtr->hwndToolTip)
Francis Beaudet's avatar
Francis Beaudet committed
3129
    DestroyWindow (infoPtr->hwndToolTip);
3130

Francis Beaudet's avatar
Francis Beaudet committed
3131 3132
  if (infoPtr->hwndUpDown)
    DestroyWindow(infoPtr->hwndUpDown);
Alex Priem's avatar
Alex Priem committed
3133

3134
  if (infoPtr->iHotTracked >= 0)
3135
    KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
3136

3137 3138
  CloseThemeData (GetWindowTheme (infoPtr->hwnd));
  
3139
  Free (infoPtr);
Francis Beaudet's avatar
Francis Beaudet committed
3140
  return 0;
Alexandre Julliard's avatar
Alexandre Julliard committed
3141 3142
}

3143
/* update theme after a WM_THEMECHANGED message */
3144
static LRESULT theme_changed(const TAB_INFO *infoPtr)
3145 3146 3147 3148 3149 3150 3151
{
    HTHEME theme = GetWindowTheme (infoPtr->hwnd);
    CloseThemeData (theme);
    OpenThemeData (infoPtr->hwnd, themeClass);
    return 0;
}

3152
static LRESULT TAB_NCCalcSize(WPARAM wParam)
3153 3154 3155 3156 3157 3158
{
  if (!wParam)
    return 0;
  return WVR_ALIGNTOP;
}

3159 3160
static inline LRESULT
TAB_SetItemExtra (TAB_INFO *infoPtr, INT cbInfo)
3161
{
3162 3163 3164 3165 3166 3167 3168 3169 3170 3171 3172 3173 3174
  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;
}

3175 3176 3177 3178 3179 3180 3181 3182 3183 3184 3185 3186 3187
static LRESULT TAB_RemoveImage (TAB_INFO *infoPtr, INT image)
{
  if (!infoPtr)
    return 0;

  if (ImageList_Remove (infoPtr->himl, image))
  {
    INT i, *idx;
    RECT r;

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

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

  return 0;
}

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

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

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

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

  return prevstyle;
}

static inline LRESULT
3234
TAB_GetExtendedStyle (const TAB_INFO *infoPtr)
3235 3236 3237 3238
{
  return infoPtr->exStyle;
}

3239 3240 3241 3242 3243 3244 3245 3246 3247 3248 3249 3250 3251 3252 3253 3254 3255 3256 3257 3258 3259 3260 3261 3262 3263 3264 3265 3266 3267 3268 3269 3270 3271
static LRESULT
TAB_DeselectAll (TAB_INFO *infoPtr, BOOL excludesel)
{
  LONG style = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
  BOOL paint = FALSE;
  INT i, selected = infoPtr->iSelected;

  if (!(style & TCS_BUTTONS))
    return 0;

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

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

  if (paint)
    TAB_InvalidateTabArea (infoPtr);

  return 0;
}

3272
static LRESULT WINAPI
3273
TAB_WindowProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
Alexandre Julliard's avatar
Alexandre Julliard committed
3274
{
3275
    TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
3276

3277
    TRACE("hwnd=%p msg=%x wParam=%lx lParam=%lx\n", hwnd, uMsg, wParam, lParam);
3278
    if (!infoPtr && (uMsg != WM_CREATE))
3279
      return DefWindowProcW (hwnd, uMsg, wParam, lParam);
3280

Alexandre Julliard's avatar
Alexandre Julliard committed
3281 3282
    switch (uMsg)
    {
Francis Beaudet's avatar
Francis Beaudet committed
3283
    case TCM_GETIMAGELIST:
3284
      return TAB_GetImageList (infoPtr);
3285

Francis Beaudet's avatar
Francis Beaudet committed
3286
    case TCM_SETIMAGELIST:
3287
      return TAB_SetImageList (infoPtr, (HIMAGELIST)lParam);
3288

Francis Beaudet's avatar
Francis Beaudet committed
3289
    case TCM_GETITEMCOUNT:
3290
      return TAB_GetItemCount (infoPtr);
3291

Francis Beaudet's avatar
Francis Beaudet committed
3292 3293
    case TCM_GETITEMA:
    case TCM_GETITEMW:
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
3294
      return TAB_GetItemT (infoPtr, (INT)wParam, (LPTCITEMW)lParam, uMsg == TCM_GETITEMW);
3295

Francis Beaudet's avatar
Francis Beaudet committed
3296 3297
    case TCM_SETITEMA:
    case TCM_SETITEMW:
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
3298
      return TAB_SetItemT (infoPtr, (INT)wParam, (LPTCITEMW)lParam, uMsg == TCM_SETITEMW);
3299

Francis Beaudet's avatar
Francis Beaudet committed
3300
    case TCM_DELETEITEM:
3301
      return TAB_DeleteItem (infoPtr, (INT)wParam);
3302

Francis Beaudet's avatar
Francis Beaudet committed
3303
    case TCM_DELETEALLITEMS:
3304
     return TAB_DeleteAllItems (infoPtr);
3305

Francis Beaudet's avatar
Francis Beaudet committed
3306
    case TCM_GETITEMRECT:
3307
     return TAB_GetItemRect (infoPtr, wParam, lParam);
3308

Francis Beaudet's avatar
Francis Beaudet committed
3309
    case TCM_GETCURSEL:
3310
      return TAB_GetCurSel (infoPtr);
3311

Francis Beaudet's avatar
Francis Beaudet committed
3312
    case TCM_HITTEST:
3313
      return TAB_HitTest (infoPtr, (LPTCHITTESTINFO)lParam);
3314

Francis Beaudet's avatar
Francis Beaudet committed
3315
    case TCM_SETCURSEL:
3316
      return TAB_SetCurSel (infoPtr, (INT)wParam);
3317

Francis Beaudet's avatar
Francis Beaudet committed
3318 3319
    case TCM_INSERTITEMA:
    case TCM_INSERTITEMW:
Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
3320
      return TAB_InsertItemT (infoPtr, wParam, lParam, uMsg == TCM_INSERTITEMW);
3321

Francis Beaudet's avatar
Francis Beaudet committed
3322
    case TCM_SETITEMEXTRA:
3323
      return TAB_SetItemExtra (infoPtr, (int)wParam);
3324

Francis Beaudet's avatar
Francis Beaudet committed
3325
    case TCM_ADJUSTRECT:
3326
      return TAB_AdjustRect (infoPtr, (BOOL)wParam, (LPRECT)lParam);
3327

Francis Beaudet's avatar
Francis Beaudet committed
3328
    case TCM_SETITEMSIZE:
3329
      return TAB_SetItemSize (infoPtr, lParam);
3330

Francis Beaudet's avatar
Francis Beaudet committed
3331
    case TCM_REMOVEIMAGE:
3332
      return TAB_RemoveImage (infoPtr, wParam);
3333

Francis Beaudet's avatar
Francis Beaudet committed
3334
    case TCM_SETPADDING:
3335
      return TAB_SetPadding (infoPtr, lParam);
3336

Francis Beaudet's avatar
Francis Beaudet committed
3337
    case TCM_GETROWCOUNT:
3338
      return TAB_GetRowCount(infoPtr);
3339 3340

    case TCM_GETUNICODEFORMAT:
3341
      return TAB_GetUnicodeFormat (infoPtr);
3342 3343

    case TCM_SETUNICODEFORMAT:
3344
      return TAB_SetUnicodeFormat (infoPtr, (BOOL)wParam);
3345 3346

    case TCM_HIGHLIGHTITEM:
3347
      return TAB_HighlightItem (infoPtr, (INT)wParam, (BOOL)LOWORD(lParam));
3348

Francis Beaudet's avatar
Francis Beaudet committed
3349
    case TCM_GETTOOLTIPS:
3350
      return TAB_GetToolTips (infoPtr);
3351

Francis Beaudet's avatar
Francis Beaudet committed
3352
    case TCM_SETTOOLTIPS:
3353
      return TAB_SetToolTips (infoPtr, (HWND)wParam);
3354

Francis Beaudet's avatar
Francis Beaudet committed
3355
    case TCM_GETCURFOCUS:
3356
      return TAB_GetCurFocus (infoPtr);
3357

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

Alexandre Julliard's avatar
Alexandre Julliard committed
3361
    case TCM_SETMINTABWIDTH:
3362
      return TAB_SetMinTabWidth(infoPtr, (INT)lParam);
3363

Francis Beaudet's avatar
Francis Beaudet committed
3364
    case TCM_DESELECTALL:
3365
      return TAB_DeselectAll (infoPtr, (BOOL)wParam);
3366

3367
    case TCM_GETEXTENDEDSTYLE:
3368
      return TAB_GetExtendedStyle (infoPtr);
3369 3370

    case TCM_SETEXTENDEDSTYLE:
3371
      return TAB_SetExtendedStyle (infoPtr, wParam, lParam);
3372

Francis Beaudet's avatar
Francis Beaudet committed
3373
    case WM_GETFONT:
3374
      return TAB_GetFont (infoPtr);
3375

Francis Beaudet's avatar
Francis Beaudet committed
3376
    case WM_SETFONT:
3377
      return TAB_SetFont (infoPtr, (HFONT)wParam);
3378

Francis Beaudet's avatar
Francis Beaudet committed
3379
    case WM_CREATE:
3380
      return TAB_Create (hwnd, lParam);
3381

Francis Beaudet's avatar
Francis Beaudet committed
3382
    case WM_NCDESTROY:
3383
      return TAB_Destroy (infoPtr);
3384

3385
    case WM_GETDLGCODE:
Francis Beaudet's avatar
Francis Beaudet committed
3386
      return DLGC_WANTARROWS | DLGC_WANTCHARS;
3387

Francis Beaudet's avatar
Francis Beaudet committed
3388
    case WM_LBUTTONDOWN:
3389
      return TAB_LButtonDown (infoPtr, wParam, lParam);
3390

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

3394
    case WM_NOTIFY:
3395
      return SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, wParam, lParam);
3396

Francis Beaudet's avatar
Francis Beaudet committed
3397
    case WM_RBUTTONDOWN:
3398
      return TAB_RButtonDown (infoPtr);
3399

Francis Beaudet's avatar
Francis Beaudet committed
3400
    case WM_MOUSEMOVE:
3401
      return TAB_MouseMove (infoPtr, wParam, lParam);
3402

3403
    case WM_PRINTCLIENT:
Francis Beaudet's avatar
Francis Beaudet committed
3404
    case WM_PAINT:
3405
      return TAB_Paint (infoPtr, (HDC)wParam);
Francis Beaudet's avatar
Francis Beaudet committed
3406 3407

    case WM_SIZE:
3408
      return TAB_Size (infoPtr);
3409

Francis Beaudet's avatar
Francis Beaudet committed
3410
    case WM_SETREDRAW:
3411
      return TAB_SetRedraw (infoPtr, (BOOL)wParam);
Francis Beaudet's avatar
Francis Beaudet committed
3412 3413

    case WM_HSCROLL:
3414
      return TAB_OnHScroll(infoPtr, (int)LOWORD(wParam), (int)HIWORD(wParam));
Alexandre Julliard's avatar
Alexandre Julliard committed
3415 3416

    case WM_STYLECHANGED:
3417
      TAB_SetItemBounds (infoPtr);
Alexandre Julliard's avatar
Alexandre Julliard committed
3418 3419
      InvalidateRect(hwnd, NULL, TRUE);
      return 0;
3420 3421 3422 3423

    case WM_SYSCOLORCHANGE:
      COMCTL32_RefreshSysColors();
      return 0;
3424

3425 3426 3427
    case WM_THEMECHANGED:
      return theme_changed (infoPtr);

Francis Beaudet's avatar
Francis Beaudet committed
3428 3429
    case WM_KILLFOCUS:
    case WM_SETFOCUS:
3430 3431
      TAB_FocusChanging(infoPtr);
      break;   /* Don't disturb normal focus behavior */
Francis Beaudet's avatar
Francis Beaudet committed
3432 3433

    case WM_KEYUP:
3434
      return TAB_KeyUp(infoPtr, wParam);
3435
    case WM_NCHITTEST:
3436
      return TAB_NCHitTest(infoPtr, lParam);
Francis Beaudet's avatar
Francis Beaudet committed
3437

3438
    case WM_NCCALCSIZE:
3439
      return TAB_NCCalcSize(wParam);
3440

Francis Beaudet's avatar
Francis Beaudet committed
3441
    default:
3442
      if (uMsg >= WM_USER && uMsg < WM_APP && !COMCTL32_IsReflectedMessage(uMsg))
3443
	WARN("unknown msg %04x wp=%08lx lp=%08lx\n",
Francis Beaudet's avatar
Francis Beaudet committed
3444
	     uMsg, wParam, lParam);
3445
      break;
Alexandre Julliard's avatar
Alexandre Julliard committed
3446
    }
3447
    return DefWindowProcW(hwnd, uMsg, wParam, lParam);
Alexandre Julliard's avatar
Alexandre Julliard committed
3448 3449 3450
}


Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
3451
void
3452
TAB_Register (void)
Alexandre Julliard's avatar
Alexandre Julliard committed
3453
{
3454
  WNDCLASSW wndClass;
Francis Beaudet's avatar
Francis Beaudet committed
3455

3456
  ZeroMemory (&wndClass, sizeof(WNDCLASSW));
3457
  wndClass.style         = CS_GLOBALCLASS | CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
3458
  wndClass.lpfnWndProc   = TAB_WindowProc;
Francis Beaudet's avatar
Francis Beaudet committed
3459 3460
  wndClass.cbClsExtra    = 0;
  wndClass.cbWndExtra    = sizeof(TAB_INFO *);
3461 3462 3463
  wndClass.hCursor       = LoadCursorW (0, (LPWSTR)IDC_ARROW);
  wndClass.hbrBackground = (HBRUSH)(COLOR_BTNFACE+1);
  wndClass.lpszClassName = WC_TABCONTROLW;
3464

3465
  RegisterClassW (&wndClass);
Alexandre Julliard's avatar
Alexandre Julliard committed
3466 3467
}

3468

Dimitrie O. Paun's avatar
Dimitrie O. Paun committed
3469
void
3470
TAB_Unregister (void)
3471
{
3472
    UnregisterClassW (WC_TABCONTROLW, NULL);
3473
}