tab.c 81.7 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
Alexandre Julliard's avatar
Alexandre Julliard committed
7 8 9
 *
 * TODO:
 *  Image list support
10
 *  Unicode support (under construction)
11 12 13
 *
 * FIXME:
 *  UpDown control not displayed until after a tab is clicked on
Alexandre Julliard's avatar
Alexandre Julliard committed
14 15
 */

16 17
#include <string.h>

18
#include "winbase.h"
Alexandre Julliard's avatar
Alexandre Julliard committed
19
#include "commctrl.h"
20
#include "comctl32.h"
21
#include "debugtools.h"
22
#include <math.h>
Alexandre Julliard's avatar
Alexandre Julliard committed
23

24 25 26 27 28 29
DEFAULT_DEBUG_CHANNEL(tab);

typedef struct
{
  UINT   mask;
  DWORD  dwState;
30
  LPWSTR pszText;
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
  INT    iImage;
  LPARAM lParam;
  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) 
                   *  
                   * additionally the top member hold the row number
                   * and bottom is unused and should be 0 */
} TAB_ITEM;

typedef struct
{
  UINT       uNumItem;        /* number of tab items */
  UINT       uNumRows;	      /* number of tab rows */
  INT        tabHeight;       /* height of the tab row */
  INT        tabWidth;        /* width of tabs */
  HFONT      hFont;           /* handle to the current font */
  HCURSOR    hcurArrow;       /* handle to the current cursor */
  HIMAGELIST himl;            /* handle to a image list (may be 0) */
  HWND       hwndToolTip;     /* handle to tab's tooltip */
  INT        leftmostVisible; /* Used for scrolling, this member contains
			       * the index of the first visible item */
  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*/
  BOOL       needsScrolling;  /* TRUE if the size of the tabs is greater than 
			       * the size of the control */
  BOOL	     fSizeSet;	      /* was the size of the tabs explicitly set? */
61
  BOOL       bUnicode;        /* Unicode control? */
62 63
  HWND       hwndUpDown;      /* Updown control used for scrolling */
} TAB_INFO;
64

Francis Beaudet's avatar
Francis Beaudet committed
65 66 67 68 69 70 71
/******************************************************************************
 * Positioning constants
 */
#define SELECTED_TAB_OFFSET     2
#define HORIZONTAL_ITEM_PADDING 5
#define VERTICAL_ITEM_PADDING   3
#define ROUND_CORNER_SIZE       2
Alexandre Julliard's avatar
Alexandre Julliard committed
72 73
#define DISPLAY_AREA_PADDINGX   2
#define DISPLAY_AREA_PADDINGY   2
74 75
#define CONTROL_BORDER_SIZEX    2
#define CONTROL_BORDER_SIZEY    2
76
#define BUTTON_SPACINGX         4 
77 78
#define BUTTON_SPACINGY         4
#define FLAT_BTN_SPACINGX       8
79
#define DEFAULT_TAB_WIDTH       96
Alexandre Julliard's avatar
Alexandre Julliard committed
80

81
#define TAB_GetInfoPtr(hwnd) ((TAB_INFO *)GetWindowLongA(hwnd,0))
82

83 84 85 86 87 88
/******************************************************************************
 * Hot-tracking timer constants
 */
#define TAB_HOTTRACK_TIMER            1
#define TAB_HOTTRACK_TIMER_INTERVAL   100   /* milliseconds */

Francis Beaudet's avatar
Francis Beaudet committed
89 90 91
/******************************************************************************
 * Prototypes
 */
92
static void TAB_Refresh (HWND hwnd, HDC hdc);
93
static void TAB_InvalidateTabArea(HWND hwnd, TAB_INFO* infoPtr);
Francis Beaudet's avatar
Francis Beaudet committed
94
static void TAB_EnsureSelectionVisible(HWND hwnd, TAB_INFO* infoPtr);
95 96
static void TAB_DrawItem(HWND hwnd, HDC hdc, INT iItem);
static void TAB_DrawItemInterior(HWND hwnd, HDC hdc, INT iItem, RECT* drawRect);
97

98
static BOOL
99
TAB_SendSimpleNotify (HWND hwnd, UINT code)
Alexandre Julliard's avatar
Alexandre Julliard committed
100 101 102
{
    NMHDR nmhdr;

103 104
    nmhdr.hwndFrom = hwnd;
    nmhdr.idFrom = GetWindowLongA(hwnd, GWL_ID);
Alexandre Julliard's avatar
Alexandre Julliard committed
105 106
    nmhdr.code = code;

107
    return (BOOL) SendMessageA (GetParent (hwnd), WM_NOTIFY,
Francis Beaudet's avatar
Francis Beaudet committed
108
            (WPARAM) nmhdr.idFrom, (LPARAM) &nmhdr);
Alexandre Julliard's avatar
Alexandre Julliard committed
109 110
}

Alex Priem's avatar
Alex Priem committed
111
static VOID
112 113
TAB_RelayEvent (HWND hwndTip, HWND hwndMsg, UINT uMsg,
            WPARAM wParam, LPARAM lParam)
Alex Priem's avatar
Alex Priem committed
114
{
115
    MSG msg;
Alex Priem's avatar
Alex Priem committed
116 117 118 119 120 121 122 123 124

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

125
    SendMessageA (hwndTip, TTM_RELAYEVENT, 0, (LPARAM)&msg);
Alex Priem's avatar
Alex Priem committed
126 127
}

Alexandre Julliard's avatar
Alexandre Julliard committed
128
static LRESULT
129
TAB_GetCurSel (HWND hwnd)
Alexandre Julliard's avatar
Alexandre Julliard committed
130
{
131
    TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
Alexandre Julliard's avatar
Alexandre Julliard committed
132 133 134 135
 
    return infoPtr->iSelected;
}

Alex Priem's avatar
Alex Priem committed
136
static LRESULT
137
TAB_GetCurFocus (HWND hwnd)
Alex Priem's avatar
Alex Priem committed
138
{
139
    TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
Alex Priem's avatar
Alex Priem committed
140 141 142 143 144
 
    return infoPtr->uFocus;
}

static LRESULT
145
TAB_GetToolTips (HWND hwnd, WPARAM wParam, LPARAM lParam)
Alex Priem's avatar
Alex Priem committed
146
{
147
    TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
Alex Priem's avatar
Alex Priem committed
148 149 150 151 152

    if (infoPtr == NULL) return 0;
    return infoPtr->hwndToolTip;
}

153
static LRESULT
154
TAB_SetCurSel (HWND hwnd,WPARAM wParam)
155
{
156
    TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
157
  INT iItem = (INT)wParam;
Francis Beaudet's avatar
Francis Beaudet committed
158
  INT prevItem;
159
 
160
  prevItem = -1;
Francis Beaudet's avatar
Francis Beaudet committed
161 162 163
  if ((iItem >= 0) && (iItem < infoPtr->uNumItem)) {
    prevItem=infoPtr->iSelected;
      infoPtr->iSelected=iItem;
164 165
      TAB_EnsureSelectionVisible(hwnd, infoPtr);
      TAB_InvalidateTabArea(hwnd, infoPtr);
Francis Beaudet's avatar
Francis Beaudet committed
166 167
  }
  return prevItem;
168 169
}

Alex Priem's avatar
Alex Priem committed
170
static LRESULT
171
TAB_SetCurFocus (HWND hwnd,WPARAM wParam)
Alex Priem's avatar
Alex Priem committed
172
{
Francis Beaudet's avatar
Francis Beaudet committed
173 174
  TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
  INT iItem=(INT) wParam;
Alex Priem's avatar
Alex Priem committed
175
 
176
  if ((iItem < 0) || (iItem >= infoPtr->uNumItem)) return 0;
Francis Beaudet's avatar
Francis Beaudet committed
177 178

  if (GetWindowLongA(hwnd, GWL_STYLE) & TCS_BUTTONS) {
179
    FIXME("Should set input focus\n");
Francis Beaudet's avatar
Francis Beaudet committed
180
  } else { 
181
    int oldFocus = infoPtr->uFocus;
Yuxi Zhang's avatar
Yuxi Zhang committed
182
    if (infoPtr->iSelected != iItem || infoPtr->uFocus == -1 ) {
183
      infoPtr->uFocus = iItem;
184 185 186 187 188 189 190 191 192
      if (oldFocus != -1) {
        if (TAB_SendSimpleNotify(hwnd, TCN_SELCHANGING)!=TRUE)  {
          infoPtr->iSelected = iItem;
          TAB_SendSimpleNotify(hwnd, TCN_SELCHANGE);
        }
        else
          infoPtr->iSelected = iItem;
        TAB_EnsureSelectionVisible(hwnd, infoPtr);
        TAB_InvalidateTabArea(hwnd, infoPtr);
Francis Beaudet's avatar
Francis Beaudet committed
193 194 195
      }
    }
  }
Alex Priem's avatar
Alex Priem committed
196 197 198 199
  return 0;
}

static LRESULT
200
TAB_SetToolTips (HWND hwnd, WPARAM wParam, LPARAM lParam)
Alex Priem's avatar
Alex Priem committed
201
{
202
    TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
Alex Priem's avatar
Alex Priem committed
203 204

    if (infoPtr == NULL) return 0;
205
    infoPtr->hwndToolTip = (HWND)wParam;
Alex Priem's avatar
Alex Priem committed
206 207 208
    return 0;
}

Francis Beaudet's avatar
Francis Beaudet committed
209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224
/******************************************************************************
 * 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(
  HWND        hwnd,
  TAB_INFO*   infoPtr,
  INT         itemIndex,
  RECT*       itemRect,
  RECT*       selectedRect)
{
225 226 227
  RECT tmpItemRect,clientRect;
  LONG        lStyle  = GetWindowLongA(hwnd, GWL_STYLE);
  
228 229
  /* Perform a sanity check and a trivial visibility check. */
  if ( (infoPtr->uNumItem <= 0) ||
Francis Beaudet's avatar
Francis Beaudet committed
230
       (itemIndex >= infoPtr->uNumItem) ||
231
       (!((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)) && (itemIndex < infoPtr->leftmostVisible)) )
Francis Beaudet's avatar
Francis Beaudet committed
232 233 234 235 236 237
    return FALSE;

  /*
   * Avoid special cases in this procedure by assigning the "out"
   * parameters if the caller didn't supply them
   */
238
  if (itemRect == NULL)
Francis Beaudet's avatar
Francis Beaudet committed
239 240
    itemRect = &tmpItemRect;
  
241
  /* Retrieve the unmodified item rect. */
Francis Beaudet's avatar
Francis Beaudet committed
242 243
  *itemRect = infoPtr->items[itemIndex].rect;

244
  /* calculate the times bottom and top based on the row */
245 246
  GetClientRect(hwnd, &clientRect);

247
  if ((lStyle & TCS_BOTTOM) && !(lStyle & TCS_VERTICAL))
248
  {
249 250 251 252
    itemRect->bottom = clientRect.bottom -
                   SELECTED_TAB_OFFSET -
                   itemRect->top * (infoPtr->tabHeight - 2) -
                   ((lStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : 0);
253

254
    itemRect->top = clientRect.bottom -
255
                   infoPtr->tabHeight -
256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271
                   itemRect->top * (infoPtr->tabHeight - 2) -
                   ((lStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : 0);
  }
  else if((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
  {
    itemRect->right = clientRect.right - SELECTED_TAB_OFFSET - itemRect->left * (infoPtr->tabHeight - 2) -
                      ((lStyle & TCS_BUTTONS) ? itemRect->left * BUTTON_SPACINGY : 0);
    itemRect->left = clientRect.right - infoPtr->tabHeight - itemRect->left * (infoPtr->tabHeight - 2) -
                      ((lStyle & TCS_BUTTONS) ? itemRect->left * BUTTON_SPACINGY : 0);
  }
  else if((lStyle & TCS_VERTICAL) && !(lStyle & TCS_BOTTOM))
  {
    itemRect->right = clientRect.left + infoPtr->tabHeight + itemRect->left * (infoPtr->tabHeight - 2) +
                      ((lStyle & TCS_BUTTONS) ? itemRect->left * BUTTON_SPACINGY : 0);
    itemRect->left = clientRect.left + SELECTED_TAB_OFFSET + itemRect->left * (infoPtr->tabHeight - 2) +
                      ((lStyle & TCS_BUTTONS) ? itemRect->left * BUTTON_SPACINGY : 0);
272
  }
273
  else if(!(lStyle & TCS_VERTICAL) && !(lStyle & TCS_BOTTOM)) /* not TCS_BOTTOM and not TCS_VERTICAL */
274 275 276
  {
    itemRect->bottom = clientRect.top + 
                      infoPtr->tabHeight +
277 278
                      itemRect->top * (infoPtr->tabHeight - 2) +
                      ((lStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : 0);
279
    itemRect->top = clientRect.top + 
280 281 282
                   SELECTED_TAB_OFFSET +
                   itemRect->top * (infoPtr->tabHeight - 2) +
                   ((lStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : 0);
283 284
 }

Francis Beaudet's avatar
Francis Beaudet committed
285 286 287 288
  /*
   * "scroll" it to make sure the item at the very left of the 
   * tab control is the leftmost visible tab.
   */
289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305
  if(lStyle & TCS_VERTICAL)
  {
    OffsetRect(itemRect,
	     0,
	     -(clientRect.bottom - infoPtr->items[infoPtr->leftmostVisible].rect.bottom));

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

  } else
  {
    OffsetRect(itemRect,
Francis Beaudet's avatar
Francis Beaudet committed
306 307 308
	     -infoPtr->items[infoPtr->leftmostVisible].rect.left, 
	     0);

309 310 311 312 313
    /*
     * 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
314 315
	     SELECTED_TAB_OFFSET,
	     0);
316
  }
Francis Beaudet's avatar
Francis Beaudet committed
317

318
  /* Now, calculate the position of the item as if it were selected. */
Francis Beaudet's avatar
Francis Beaudet committed
319 320 321 322
  if (selectedRect!=NULL)
  {
    CopyRect(selectedRect, itemRect);

323 324 325 326 327
    /* 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
328

329 330
    /* If it also a bit higher. */
    if ((lStyle & TCS_BOTTOM) && !(lStyle & TCS_VERTICAL))
Francis Beaudet's avatar
Francis Beaudet committed
331
    {      
332 333 334 335 336 337 338 339 340 341 342 343
      selectedRect->top -= 2; /* the border is thicker on the bottom */
      selectedRect->bottom += SELECTED_TAB_OFFSET;
    }
    else if((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
    {
      selectedRect->left -= 2; /* the border is thicker on the right */
      selectedRect->right += SELECTED_TAB_OFFSET;
    }
    else if(lStyle & TCS_VERTICAL)
    {
      selectedRect->left -= SELECTED_TAB_OFFSET;
      selectedRect->right += 1;
Francis Beaudet's avatar
Francis Beaudet committed
344 345 346
    }
    else
    {
347 348
      selectedRect->top -= SELECTED_TAB_OFFSET;
      selectedRect->bottom += 1;
Francis Beaudet's avatar
Francis Beaudet committed
349 350 351 352 353 354
    }
  }

  return TRUE;
}

355 356 357 358 359 360
static BOOL TAB_GetItemRect(HWND hwnd, WPARAM wParam, LPARAM lParam)
{
  return TAB_InternalGetItemRect(hwnd, TAB_GetInfoPtr(hwnd), (INT)wParam, 
                                 (LPRECT)lParam, (LPRECT)NULL);
}

Francis Beaudet's avatar
Francis Beaudet committed
361 362 363 364 365 366 367 368 369 370 371 372 373 374 375
/******************************************************************************
 * TAB_KeyUp
 *
 * This method is called to handle keyboard input
 */
static LRESULT TAB_KeyUp(
  HWND   hwnd, 
  WPARAM keyCode)
{
  TAB_INFO* infoPtr = TAB_GetInfoPtr(hwnd);
  int       newItem = -1;

  switch (keyCode)
  {
    case VK_LEFT:
376
      newItem = infoPtr->uFocus - 1;
Francis Beaudet's avatar
Francis Beaudet committed
377 378
      break;
    case VK_RIGHT:
379
      newItem = infoPtr->uFocus + 1;
Francis Beaudet's avatar
Francis Beaudet committed
380 381 382 383 384 385
      break;
  }
  
  /*
   * If we changed to a valid item, change the selection
   */
386
  if ((newItem >= 0) &&
Francis Beaudet's avatar
Francis Beaudet committed
387
       (newItem < infoPtr->uNumItem) &&
388
       (infoPtr->uFocus != newItem))
Francis Beaudet's avatar
Francis Beaudet committed
389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442
  {
    if (!TAB_SendSimpleNotify(hwnd, TCN_SELCHANGING))
    {
      infoPtr->iSelected = newItem;
      infoPtr->uFocus    = newItem;
      TAB_SendSimpleNotify(hwnd, TCN_SELCHANGE);

      TAB_EnsureSelectionVisible(hwnd, infoPtr);
      TAB_InvalidateTabArea(hwnd, infoPtr);
    }
  }

  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.
 */
static LRESULT TAB_FocusChanging(
  HWND   hwnd, 
  UINT   uMsg, 
  WPARAM wParam, 
  LPARAM lParam)
{
  TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
  RECT      selectedRect;
  BOOL      isVisible;

  /*
   * Get the rectangle for the item.
   */
  isVisible = TAB_InternalGetItemRect(hwnd,
				      infoPtr,
				      infoPtr->uFocus,
				      NULL,
				      &selectedRect);
  
  /*
   * If the rectangle is not completely invisible, invalidate that
   * portion of the window.
   */
  if (isVisible)
  {
    InvalidateRect(hwnd, &selectedRect, TRUE);
  }

  /*
   * Don't otherwise disturb normal behavior.
   */
  return DefWindowProcA (hwnd, uMsg, wParam, lParam);
}
Alex Priem's avatar
Alex Priem committed
443

Francis Beaudet's avatar
Francis Beaudet committed
444 445 446 447 448
static HWND TAB_InternalHitTest (
  HWND      hwnd,
  TAB_INFO* infoPtr, 
  POINT     pt, 
  UINT*     flags)
Alex Priem's avatar
Alex Priem committed
449 450

{
451
  RECT rect;
Alex Priem's avatar
Alex Priem committed
452 453
  int iCount; 
  
454
  for (iCount = 0; iCount < infoPtr->uNumItem; iCount++)
Francis Beaudet's avatar
Francis Beaudet committed
455
  {
456
    TAB_InternalGetItemRect(hwnd, infoPtr, iCount, &rect, NULL);
Francis Beaudet's avatar
Francis Beaudet committed
457

458
    if (PtInRect(&rect, pt))
Francis Beaudet's avatar
Francis Beaudet committed
459 460 461 462 463 464
    {
      *flags = TCHT_ONITEM;
      return iCount;
    }
  }

465
  *flags = TCHT_NOWHERE;
Alex Priem's avatar
Alex Priem committed
466 467 468 469
  return -1;
}

static LRESULT
470
TAB_HitTest (HWND hwnd, WPARAM wParam, LPARAM lParam)
Alex Priem's avatar
Alex Priem committed
471
{
Francis Beaudet's avatar
Francis Beaudet committed
472
  TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
473
  LPTCHITTESTINFO lptest = (LPTCHITTESTINFO) lParam;
Francis Beaudet's avatar
Francis Beaudet committed
474
  
475
  return TAB_InternalHitTest (hwnd, infoPtr, lptest->pt, &lptest->flags);
Alex Priem's avatar
Alex Priem committed
476 477
}

478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505
/******************************************************************************
 * 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 ?
 */
static LRESULT
TAB_NCHitTest (HWND hwnd, LPARAM lParam)
{
  TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
  POINT pt;
  UINT dummyflag;

  pt.x = LOWORD(lParam);
  pt.y = HIWORD(lParam);
  ScreenToClient(hwnd, &pt);

  if (TAB_InternalHitTest(hwnd, infoPtr, pt, &dummyflag) == -1)
    return HTTRANSPARENT;
  else
    return HTCLIENT;
}
Alex Priem's avatar
Alex Priem committed
506 507

static LRESULT
508
TAB_LButtonDown (HWND hwnd, WPARAM wParam, LPARAM lParam)
Alex Priem's avatar
Alex Priem committed
509
{
Francis Beaudet's avatar
Francis Beaudet committed
510
  TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
511
  POINT pt;
512
  INT newItem, dummy;
Alex Priem's avatar
Alex Priem committed
513

Francis Beaudet's avatar
Francis Beaudet committed
514
  if (infoPtr->hwndToolTip)
515
    TAB_RelayEvent (infoPtr->hwndToolTip, hwnd,
Francis Beaudet's avatar
Francis Beaudet committed
516
		    WM_LBUTTONDOWN, wParam, lParam);
Alex Priem's avatar
Alex Priem committed
517

Francis Beaudet's avatar
Francis Beaudet committed
518 519 520
  if (GetWindowLongA(hwnd, GWL_STYLE) & TCS_FOCUSONBUTTONDOWN ) {
    SetFocus (hwnd);
  }
Alex Priem's avatar
Alex Priem committed
521

Francis Beaudet's avatar
Francis Beaudet committed
522
  if (infoPtr->hwndToolTip)
523
    TAB_RelayEvent (infoPtr->hwndToolTip, hwnd,
Francis Beaudet's avatar
Francis Beaudet committed
524 525 526 527 528
		    WM_LBUTTONDOWN, wParam, lParam);
  
  pt.x = (INT)LOWORD(lParam);
  pt.y = (INT)HIWORD(lParam);
  
529
  newItem = TAB_InternalHitTest (hwnd, infoPtr, pt, &dummy);
Francis Beaudet's avatar
Francis Beaudet committed
530
  
531
  TRACE("On Tab, item %d\n", newItem);
532 533

  if ((newItem != -1) && (infoPtr->iSelected != newItem))
Francis Beaudet's avatar
Francis Beaudet committed
534
  {
535
    if (TAB_SendSimpleNotify(hwnd, TCN_SELCHANGING) != TRUE)
Francis Beaudet's avatar
Francis Beaudet committed
536 537 538 539
    {
      infoPtr->iSelected = newItem;
      infoPtr->uFocus    = newItem;
      TAB_SendSimpleNotify(hwnd, TCN_SELCHANGE);
Alexandre Julliard's avatar
Alexandre Julliard committed
540

Francis Beaudet's avatar
Francis Beaudet committed
541 542 543 544 545
      TAB_EnsureSelectionVisible(hwnd, infoPtr);

      TAB_InvalidateTabArea(hwnd, infoPtr);
    }
  }
546 547 548 549 550 551
  return 0;
}

static LRESULT
TAB_LButtonUp (HWND hwnd, WPARAM wParam, LPARAM lParam)
{
Francis Beaudet's avatar
Francis Beaudet committed
552 553 554
  TAB_SendSimpleNotify(hwnd, NM_CLICK);

  return 0;
Alexandre Julliard's avatar
Alexandre Julliard committed
555 556
}

Alex Priem's avatar
Alex Priem committed
557
static LRESULT
558
TAB_RButtonDown (HWND hwnd, WPARAM wParam, LPARAM lParam)
Alex Priem's avatar
Alex Priem committed
559
{
Francis Beaudet's avatar
Francis Beaudet committed
560 561
  TAB_SendSimpleNotify(hwnd, NM_RCLICK);
  return 0;
Alex Priem's avatar
Alex Priem committed
562 563
}

564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595
/******************************************************************************
 * 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
TAB_DrawLoneItemInterior(HWND hwnd, TAB_INFO* infoPtr, int iItem)
{
  HDC hdc = GetDC(hwnd);
  HFONT hOldFont = SelectObject(hdc, infoPtr->hFont);
  TAB_DrawItemInterior(hwnd, hdc, iItem, NULL);
  SelectObject(hdc, hOldFont);
  ReleaseDC(hwnd, hdc);
}

/******************************************************************************
 * 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.
 */
static VOID CALLBACK
TAB_HotTrackTimerProc
  (
596 597 598 599
  HWND hwnd,    /* handle of window for timer messages */
  UINT uMsg,    /* WM_TIMER message */
  UINT idEvent, /* timer identifier */
  DWORD dwTime  /* current system time */
600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617
  )
{
  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
    ** WM_MOUSEMOVE event. 
    */
    if (!GetCursorPos(&pt) || WindowFromPoint(pt) != hwnd)
    {
618
      /* Redraw iHotTracked to look normal */
619 620 621 622
      INT iRedraw = infoPtr->iHotTracked;
      infoPtr->iHotTracked = -1;
      TAB_DrawLoneItemInterior(hwnd, infoPtr, iRedraw);

623
      /* Kill this timer */
624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686
      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
  (
  HWND            hwnd,
  const LPARAM*   pos,
  int*            out_redrawLeave,
  int*            out_redrawEnter
  )
{
  TAB_INFO* infoPtr = TAB_GetInfoPtr(hwnd);

  int item = -1;


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

  if (GetWindowLongA(hwnd, GWL_STYLE) & TCS_HOTTRACK)
  {
    POINT pt;
    UINT  flags;

    if (pos == NULL)
    {
      GetCursorPos(&pt);
      ScreenToClient(hwnd, &pt);
    }
    else
    {
      pt.x = LOWORD(*pos);
      pt.y = HIWORD(*pos);
    }

    item = TAB_InternalHitTest(hwnd, infoPtr, pt, &flags);
  }

  if (item != infoPtr->iHotTracked)
  {
    if (infoPtr->iHotTracked >= 0)
    {
687
      /* Mark currently hot-tracked to be redrawn to look normal */
688 689 690 691 692
      if (out_redrawLeave != NULL)
        *out_redrawLeave = infoPtr->iHotTracked;

      if (item < 0)
      {
693
        /* Kill timer which forces recheck of mouse pos */
694 695 696 697 698
        KillTimer(hwnd, TAB_HOTTRACK_TIMER);
      }
    }
    else
    {
699
      /* Start timer so we recheck mouse pos */
700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715
      UINT timerID = SetTimer
        (
        hwnd,
        TAB_HOTTRACK_TIMER,
        TAB_HOTTRACK_TIMER_INTERVAL,
        TAB_HotTrackTimerProc
        );

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

    infoPtr->iHotTracked = item;

    if (item >= 0)
    {
716
	/* Mark new hot-tracked to be redrawn to look highlighted */
717 718 719 720 721 722 723 724 725 726 727
      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
728
static LRESULT
729
TAB_MouseMove (HWND hwnd, WPARAM wParam, LPARAM lParam)
Alex Priem's avatar
Alex Priem committed
730
{
731 732 733
  int redrawLeave;
  int redrawEnter;

Francis Beaudet's avatar
Francis Beaudet committed
734
  TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
Alex Priem's avatar
Alex Priem committed
735

Francis Beaudet's avatar
Francis Beaudet committed
736
  if (infoPtr->hwndToolTip)
737
    TAB_RelayEvent (infoPtr->hwndToolTip, hwnd,
Francis Beaudet's avatar
Francis Beaudet committed
738
		    WM_LBUTTONDOWN, wParam, lParam);
739 740 741 742 743 744 745 746 747 748

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

  if (redrawLeave != -1)
    TAB_DrawLoneItemInterior(hwnd, infoPtr, redrawLeave);
  if (redrawEnter != -1)
    TAB_DrawLoneItemInterior(hwnd, infoPtr, redrawEnter);

Francis Beaudet's avatar
Francis Beaudet committed
749
  return 0;
Alex Priem's avatar
Alex Priem committed
750
}
751

752 753 754
/******************************************************************************
 * TAB_AdjustRect
 *
Andreas Mohr's avatar
Andreas Mohr committed
755
 * Calculates the tab control's display area given the window rectangle or
756 757 758 759 760 761
 * the window rectangle given the requested display rectangle.
 */
static LRESULT TAB_AdjustRect(
  HWND   hwnd, 
  WPARAM fLarger, 
  LPRECT prc)
762
{
763
  TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
764
  DWORD lStyle = GetWindowLongA(hwnd, GWL_STYLE);
765

766
  if(lStyle & TCS_VERTICAL)
767
  {
768 769 770 771
    if (fLarger) /* Go from display rectangle */
    {
      /* Add the height of the tabs. */
      if (lStyle & TCS_BOTTOM)
Gerard Patel's avatar
Gerard Patel committed
772
        prc->right += (infoPtr->tabHeight - 2) * infoPtr->uNumRows + 2;
773
      else
Gerard Patel's avatar
Gerard Patel committed
774
        prc->left -= (infoPtr->tabHeight - 2) * infoPtr->uNumRows + 2;
775

776 777 778
      /* FIXME: not sure if these InflateRect's need to have different values for TCS_VERTICAL */
      /* Inflate the rectangle for the padding */
      InflateRect(prc, DISPLAY_AREA_PADDINGX, DISPLAY_AREA_PADDINGY);
779

780 781 782 783 784 785 786 787
      /* Inflate for the border */
      InflateRect(prc, CONTROL_BORDER_SIZEX, CONTROL_BORDER_SIZEX);
    }
    else /* Go from window rectangle. */
    {
      /* FIXME: not sure if these InflateRect's need to have different values for TCS_VERTICAL */
      /* Deflate the rectangle for the border */
      InflateRect(prc, -CONTROL_BORDER_SIZEX, -CONTROL_BORDER_SIZEX);
788

789 790 791 792 793
      /* Deflate the rectangle for the padding */
      InflateRect(prc, -DISPLAY_AREA_PADDINGX, -DISPLAY_AREA_PADDINGY);

      /* Remove the height of the tabs. */
      if (lStyle & TCS_BOTTOM)
Gerard Patel's avatar
Gerard Patel committed
794
        prc->right -= (infoPtr->tabHeight - 2) * infoPtr->uNumRows + 2;
795
      else
Gerard Patel's avatar
Gerard Patel committed
796
        prc->left += (infoPtr->tabHeight - 2) * infoPtr->uNumRows + 2;
797
    }
798
  }
799 800 801 802 803
  else {
    if (fLarger) /* Go from display rectangle */
    {
      /* Add the height of the tabs. */
      if (lStyle & TCS_BOTTOM)
Gerard Patel's avatar
Gerard Patel committed
804
        prc->bottom += (infoPtr->tabHeight - 2) * infoPtr->uNumRows + 2;
805
      else
Gerard Patel's avatar
Gerard Patel committed
806
        prc->top -= (infoPtr->tabHeight - 2) * infoPtr->uNumRows + 2;
807

808 809
      /* Inflate the rectangle for the padding */
      InflateRect(prc, DISPLAY_AREA_PADDINGX, DISPLAY_AREA_PADDINGY);
810

811 812 813 814 815 816 817 818 819 820
      /* Inflate for the border */
      InflateRect(prc, CONTROL_BORDER_SIZEX, CONTROL_BORDER_SIZEX);
    }
    else /* Go from window rectangle. */
    {
      /* Deflate the rectangle for the border */
      InflateRect(prc, -CONTROL_BORDER_SIZEX, -CONTROL_BORDER_SIZEX);

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

822 823
      /* Remove the height of the tabs. */
      if (lStyle & TCS_BOTTOM)
Gerard Patel's avatar
Gerard Patel committed
824
        prc->bottom -= (infoPtr->tabHeight - 2) * infoPtr->uNumRows + 2;
825
      else
Gerard Patel's avatar
Gerard Patel committed
826
        prc->top += (infoPtr->tabHeight - 2) * infoPtr->uNumRows + 2;
827
    }
Francis Beaudet's avatar
Francis Beaudet committed
828
  }
829

Francis Beaudet's avatar
Francis Beaudet committed
830
  return 0;
831 832
}

Francis Beaudet's avatar
Francis Beaudet committed
833 834 835 836 837 838 839 840 841 842 843
/******************************************************************************
 * TAB_OnHScroll
 *
 * This method will handle the notification from the scroll control and
 * perform the scrolling operation on the tab control.
 */
static LRESULT TAB_OnHScroll(
  HWND    hwnd, 
  int     nScrollCode,
  int     nPos,
  HWND    hwndScroll)
Alexandre Julliard's avatar
Alexandre Julliard committed
844
{
Francis Beaudet's avatar
Francis Beaudet committed
845
  TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
Alexandre Julliard's avatar
Alexandre Julliard committed
846

Alexandre Julliard's avatar
Alexandre Julliard committed
847
  if(nScrollCode == SB_THUMBPOSITION && nPos != infoPtr->leftmostVisible)
Francis Beaudet's avatar
Francis Beaudet committed
848
  {
Alexandre Julliard's avatar
Alexandre Julliard committed
849 850 851 852
     if(nPos < infoPtr->leftmostVisible)
        infoPtr->leftmostVisible--;
     else
        infoPtr->leftmostVisible++;
Francis Beaudet's avatar
Francis Beaudet committed
853

854
     TAB_RecalcHotTrack(hwnd, NULL, NULL, NULL);
Alexandre Julliard's avatar
Alexandre Julliard committed
855 856 857 858
     TAB_InvalidateTabArea(hwnd, infoPtr);
     SendMessageA(infoPtr->hwndUpDown, UDM_SETPOS, 0,
                   MAKELONG(infoPtr->leftmostVisible, 0));
   }
Alexandre Julliard's avatar
Alexandre Julliard committed
859

Alexandre Julliard's avatar
Alexandre Julliard committed
860
   return 0;
Alexandre Julliard's avatar
Alexandre Julliard committed
861
}
Francis Beaudet's avatar
Francis Beaudet committed
862 863

/******************************************************************************
Gerard Patel's avatar
Gerard Patel committed
864
 * TAB_SetupScrolling
Francis Beaudet's avatar
Francis Beaudet committed
865 866 867 868 869 870 871 872
 *
 * This method will check the current scrolling state and make sure the 
 * 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
873
{
Alexandre Julliard's avatar
Alexandre Julliard committed
874
  INT maxRange = 0;
875 876
  DWORD lStyle = GetWindowLongA(hwnd, GWL_STYLE);

Francis Beaudet's avatar
Francis Beaudet committed
877 878 879
  if (infoPtr->needsScrolling)
  {
    RECT controlPos;
Alexandre Julliard's avatar
Alexandre Julliard committed
880
    INT vsize, tabwidth;
881

Francis Beaudet's avatar
Francis Beaudet committed
882 883 884
    /*
     * Calculate the position of the scroll control.
     */
885
    if(lStyle & TCS_VERTICAL)
Francis Beaudet's avatar
Francis Beaudet committed
886
    {
887 888 889 890 891 892 893 894 895 896 897 898 899
      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
900 901 902
    }
    else
    {
903 904 905 906 907 908 909 910 911 912 913 914 915
      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
916
    }
Alexandre Julliard's avatar
Alexandre Julliard committed
917

Francis Beaudet's avatar
Francis Beaudet committed
918 919
    /*
     * If we don't have a scroll control yet, we want to create one.
920
     * If we have one, we want to make sure it's positioned properly.
Francis Beaudet's avatar
Francis Beaudet committed
921 922 923
     */
    if (infoPtr->hwndUpDown==0)
    {
Alexandre Julliard's avatar
Alexandre Julliard committed
924
      infoPtr->hwndUpDown = CreateWindowA("msctls_updown32",
Francis Beaudet's avatar
Francis Beaudet committed
925
					  "",
Alexandre Julliard's avatar
Alexandre Julliard committed
926
					  WS_VISIBLE | WS_CHILD | UDS_HORZ,
Francis Beaudet's avatar
Francis Beaudet committed
927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943
					  controlPos.left, controlPos.top,
					  controlPos.right - controlPos.left,
					  controlPos.bottom - controlPos.top,
					  hwnd,
					  (HMENU)NULL, 
					  (HINSTANCE)NULL, 
					  NULL);	
    }
    else
    {
      SetWindowPos(infoPtr->hwndUpDown, 
		   (HWND)NULL,
		   controlPos.left, controlPos.top,
		   controlPos.right - controlPos.left,
		   controlPos.bottom - controlPos.top,
		   SWP_SHOWWINDOW | SWP_NOZORDER);		   
    }
Alexandre Julliard's avatar
Alexandre Julliard committed
944 945 946 947 948 949 950 951 952

    /* 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;
953
       tabwidth = infoPtr->items[maxRange - 1].rect.right;
Alexandre Julliard's avatar
Alexandre Julliard committed
954 955 956 957 958 959 960 961 962 963

       for(; maxRange > 0; maxRange--)
       {
          if(tabwidth - infoPtr->items[maxRange - 1].rect.left > vsize)
             break;
       }

       if(maxRange == infoPtr->uNumItem)
          maxRange--;
    }
Francis Beaudet's avatar
Francis Beaudet committed
964 965 966
  }
  else
  {
967
    /* If we once had a scroll control... hide it */
Francis Beaudet's avatar
Francis Beaudet committed
968 969 970
    if (infoPtr->hwndUpDown!=0)
      ShowWindow(infoPtr->hwndUpDown, SW_HIDE);
  }
Alexandre Julliard's avatar
Alexandre Julliard committed
971 972
  if (infoPtr->hwndUpDown)
     SendMessageA(infoPtr->hwndUpDown, UDM_SETRANGE32, 0, maxRange);
Francis Beaudet's avatar
Francis Beaudet committed
973
}
Alexandre Julliard's avatar
Alexandre Julliard committed
974

Francis Beaudet's avatar
Francis Beaudet committed
975 976 977 978 979 980 981 982 983 984 985 986 987
/******************************************************************************
 * 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
 * dont, a scrolling control is added.
 */
static void TAB_SetItemBounds (HWND hwnd)
{
  TAB_INFO*   infoPtr = TAB_GetInfoPtr(hwnd);
988
  LONG        lStyle  = GetWindowLongA(hwnd, GWL_STYLE);
Francis Beaudet's avatar
Francis Beaudet committed
989 990 991
  TEXTMETRICA fontMetrics;
  INT         curItem;
  INT         curItemLeftPos;
992
  INT         curItemRowCount;
Francis Beaudet's avatar
Francis Beaudet committed
993 994 995 996
  HFONT       hFont, hOldFont;
  HDC         hdc;
  RECT        clientRect;
  SIZE        size;
997 998 999
  INT         iTemp;
  RECT*       rcItem;
  INT         iIndex;
Francis Beaudet's avatar
Francis Beaudet committed
1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014

  /*
   * We need to get text information so we need a DC and we need to select
   * a font.
   */
  hdc = GetDC(hwnd); 
    
  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.
   */
  GetClientRect(hwnd, &clientRect);
1015

Gerard Patel's avatar
Gerard Patel committed
1016 1017 1018
  /* if TCS_VERTICAL then swap the height and width so this code places the
     tabs along the top of the rectangle and we can just rotate them after 
     rather than duplicate all of the below code */
1019 1020 1021 1022 1023 1024 1025 1026
  if(lStyle & TCS_VERTICAL)
  {
     iTemp = clientRect.bottom;
     clientRect.bottom = clientRect.right;
     clientRect.right = iTemp;
  }

  /* The leftmost item will be "0" aligned */
Francis Beaudet's avatar
Francis Beaudet committed
1027
  curItemLeftPos = 0;
Gerard Patel's avatar
Gerard Patel committed
1028
  curItemRowCount = infoPtr->uNumItem ? 1 : 0;
Francis Beaudet's avatar
Francis Beaudet committed
1029

1030
  if (!(lStyle & TCS_FIXEDWIDTH) && !((lStyle & TCS_OWNERDRAWFIXED) && infoPtr->fSizeSet) )
1031
  {
1032 1033 1034
    int item_height;
    int icon_height = 0;

1035
    /* Use the current font to determine the height of a tab. */
1036 1037
    GetTextMetricsA(hdc, &fontMetrics);

1038
    /* Get the icon height */
1039 1040 1041
    if (infoPtr->himl)
      ImageList_GetIconSize(infoPtr->himl, 0, &icon_height);

1042
    /* Take the highest between font or icon */
1043 1044 1045 1046 1047 1048 1049
    if (fontMetrics.tmHeight > icon_height)
      item_height = fontMetrics.tmHeight;
    else
      item_height = icon_height;

    /*
     * Make sure there is enough space for the letters + icon + growing the 
1050 1051
     * selected item + extra space for the selected item.   
     */
1052 1053 1054
    infoPtr->tabHeight = item_height + 2 * VERTICAL_ITEM_PADDING +
        SELECTED_TAB_OFFSET;

1055 1056
  }

Francis Beaudet's avatar
Francis Beaudet committed
1057 1058
  for (curItem = 0; curItem < infoPtr->uNumItem; curItem++)
  {
1059
    /* Set the leftmost position of the tab. */
Francis Beaudet's avatar
Francis Beaudet committed
1060 1061
    infoPtr->items[curItem].rect.left = curItemLeftPos;

Alexandre Julliard's avatar
Alexandre Julliard committed
1062
    if ( (lStyle & TCS_FIXEDWIDTH) || ((lStyle & TCS_OWNERDRAWFIXED) && infoPtr->fSizeSet))
1063 1064 1065
    {
      infoPtr->items[curItem].rect.right = infoPtr->items[curItem].rect.left +
                                           infoPtr->tabWidth +
1066
                                           2 * HORIZONTAL_ITEM_PADDING;
1067 1068 1069
    }
    else
    {
1070 1071 1072
      int icon_width  = 0;
      int num = 2;

1073
      /* Calculate how wide the tab is depending on the text it contains */
1074 1075
      GetTextExtentPoint32W(hdc, infoPtr->items[curItem].pszText,
                            lstrlenW(infoPtr->items[curItem].pszText), &size);
1076 1077 1078

      /* under Windows, there seems to be a minimum width of 2x the height
       * for button style tabs */
1079
      if (lStyle & TCS_BUTTONS)
1080
	      size.cx = max(size.cx, 2 * (infoPtr->tabHeight - 2));
1081

1082
      /* Add the icon width */
1083 1084 1085 1086 1087 1088
      if (infoPtr->himl)
      {
        ImageList_GetIconSize(infoPtr->himl, &icon_width, 0);
        num++;
      }

1089
      infoPtr->items[curItem].rect.right = infoPtr->items[curItem].rect.left +
1090
                                           size.cx + icon_width + 
1091
                                           num * HORIZONTAL_ITEM_PADDING;
1092
    }
Francis Beaudet's avatar
Francis Beaudet committed
1093

1094 1095 1096 1097 1098 1099 1100 1101 1102
    /*
     * Check if this is a multiline tab control and if so
     * check to see if we should wrap the tabs
     *
     * Because we are going to arange all these tabs evenly
     * really we are basically just counting rows at this point
     *
     */

1103
    if (((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)) &&
1104 1105
        (infoPtr->items[curItem].rect.right > clientRect.right))
    {
1106
        infoPtr->items[curItem].rect.right -=
1107
                                      infoPtr->items[curItem].rect.left;
1108

1109
	infoPtr->items[curItem].rect.left = 0;
1110
        curItemRowCount++;
1111 1112 1113
    }

    infoPtr->items[curItem].rect.bottom = 0;
Gerard Patel's avatar
Gerard Patel committed
1114
    infoPtr->items[curItem].rect.top = curItemRowCount - 1;
1115

1116
    TRACE("TextSize: %li\n", size.cx);
1117
    TRACE("Rect: T %i, L %i, B %i, R %i\n", 
Francis Beaudet's avatar
Francis Beaudet committed
1118 1119 1120 1121 1122 1123 1124 1125 1126
	  infoPtr->items[curItem].rect.top,
	  infoPtr->items[curItem].rect.left,
	  infoPtr->items[curItem].rect.bottom,
	  infoPtr->items[curItem].rect.right);  

    /*
     * The leftmost position of the next item is the rightmost position
     * of this one.
     */
1127
    if (lStyle & TCS_BUTTONS)
1128
    {
1129
      curItemLeftPos = infoPtr->items[curItem].rect.right + 1;
1130 1131 1132
      if (lStyle & TCS_FLATBUTTONS)
        curItemLeftPos += FLAT_BTN_SPACINGX;
    }
1133 1134
    else
      curItemLeftPos = infoPtr->items[curItem].rect.right;
Francis Beaudet's avatar
Francis Beaudet committed
1135 1136
  }

1137
  if (!((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)))
1138 1139 1140 1141
  {
    /*
     * Check if we need a scrolling control.
     */
1142
    infoPtr->needsScrolling = (curItemLeftPos + (2 * SELECTED_TAB_OFFSET) >
1143 1144
                               clientRect.right);

1145 1146 1147 1148
    /* Don't need scrolling, then update infoPtr->leftmostVisible */
    if(!infoPtr->needsScrolling)
      infoPtr->leftmostVisible = 0; 

1149
    TAB_SetupScrolling(hwnd, infoPtr, &clientRect);
1150 1151
  }

1152
  /* Set the number of rows */
1153
  infoPtr->uNumRows = curItemRowCount;
Yuxi Zhang's avatar
Yuxi Zhang committed
1154

1155
   if (((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)) && (infoPtr->uNumItem > 0))
1156
   {
1157
      INT widthDiff, remainder;
1158 1159 1160 1161 1162 1163 1164 1165 1166 1167
      INT tabPerRow,remTab;
      INT iRow,iItm;
      INT iIndexStart=0,iIndexEnd=0, iCount=0;

      /*
       * Ok Microsoft trys to even out the rows. place the same
       * number of tabs in each row. So lets give that a shot
       *
       */

Gerard Patel's avatar
Gerard Patel committed
1168 1169
      tabPerRow = infoPtr->uNumItem / (infoPtr->uNumRows);
      remTab = infoPtr->uNumItem % (infoPtr->uNumRows);
1170 1171 1172 1173 1174

      for (iItm=0,iRow=0,iCount=0,curItemLeftPos=0;
           iItm<infoPtr->uNumItem;
           iItm++,iCount++)
      {
1175 1176 1177 1178
          /* 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 (iCount >= ((iRow<remTab)?tabPerRow + 1:tabPerRow))
1179 1180 1181 1182 1183
          {
              iRow++;
              curItemLeftPos = 0;
              iCount = 0;
          }
1184 1185 1186 1187

          /* normalize the current rect */

          /* shift the item to the left side of the clientRect */
1188 1189 1190 1191
          infoPtr->items[iItm].rect.right -= 
            infoPtr->items[iItm].rect.left;
          infoPtr->items[iItm].rect.left = 0;

1192 1193 1194
          /* shift the item to the right to place it as the next item in this row */
          infoPtr->items[iItm].rect.left += curItemLeftPos;
          infoPtr->items[iItm].rect.right += curItemLeftPos;
1195 1196
          infoPtr->items[iItm].rect.top = iRow;
          if (lStyle & TCS_BUTTONS)
1197
	  {
1198
            curItemLeftPos = infoPtr->items[iItm].rect.right + 1;
1199 1200 1201
            if (lStyle & TCS_FLATBUTTONS)
	      curItemLeftPos += FLAT_BTN_SPACINGX;
	  }
1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214
          else
            curItemLeftPos = infoPtr->items[iItm].rect.right;
      }
          
      /*
       * Justify the rows
       */
      {
         while(iIndexStart < infoPtr->uNumItem)
        {
        /* 
         * find the indexs of the row
         */
1215
        /* find the first item on the next row */
1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227
        for (iIndexEnd=iIndexStart;
             (iIndexEnd < infoPtr->uNumItem) && 
 	       (infoPtr->items[iIndexEnd].rect.top ==
                infoPtr->items[iIndexStart].rect.top) ;
            iIndexEnd++)
        /* intentionaly blank */;

        /* 
         * we need to justify these tabs so they fill the whole given
         * client area
         *
         */
1228 1229 1230 1231 1232 1233
        /* find the amount of space remaining on this row */
        widthDiff = clientRect.right - (2 * SELECTED_TAB_OFFSET) -
                            infoPtr->items[iIndexEnd - 1].rect.right;

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

1235 1236

        if (iCount > 1)
1237 1238 1239
        {
           remainder = widthDiff % iCount;
           widthDiff = widthDiff / iCount;
1240
           /* add widthDiff/iCount, or extra space/items on row, to each item on this row */
1241 1242 1243
           for (iIndex=iIndexStart,iCount=0; iIndex < iIndexEnd; 
                iIndex++,iCount++)
           {
1244 1245
              infoPtr->items[iIndex].rect.left += iCount * widthDiff;
              infoPtr->items[iIndex].rect.right += (iCount + 1) * widthDiff;
1246
           }
1247 1248 1249 1250 1251 1252
           infoPtr->items[iIndex - 1].rect.right += remainder;
        }
        else /* we have only one item on this row, make it take up the entire row */
        {
          infoPtr->items[iIndexStart].rect.left = clientRect.left;
          infoPtr->items[iIndexStart].rect.right = clientRect.right - 4;
1253 1254
        }

1255 1256

        iIndexStart = iIndexEnd;
1257 1258 1259 1260
        }
      }
  }

1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278
  /* 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++)
    {
      rcItem = &(infoPtr->items[iIndex].rect);

      rcOriginal = *rcItem;

      /* this is rotating the items by 90 degrees around the center of the control */
      rcItem->top = (clientRect.right - (rcOriginal.left - clientRect.left)) - (rcOriginal.right - rcOriginal.left);
      rcItem->bottom = rcItem->top + (rcOriginal.right - rcOriginal.left);
      rcItem->left = rcOriginal.top;
      rcItem->right = rcOriginal.bottom;
    }
  }

1279
  TAB_EnsureSelectionVisible(hwnd,infoPtr);
1280
  TAB_RecalcHotTrack(hwnd, NULL, NULL, NULL);
1281 1282

  /* Cleanup */
Francis Beaudet's avatar
Francis Beaudet committed
1283 1284 1285 1286
  SelectObject (hdc, hOldFont);
  ReleaseDC (hwnd, hdc);
}

1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319
/******************************************************************************
 * TAB_DrawItemInterior
 *
 * This method is used to draw the interior (text and icon) of a single tab
 * into the tab control.
 */         
static void
TAB_DrawItemInterior
  (
  HWND        hwnd,
  HDC         hdc,
  INT         iItem,
  RECT*       drawRect
  )
{
  TAB_INFO* infoPtr = TAB_GetInfoPtr(hwnd);
  LONG      lStyle  = GetWindowLongA(hwnd, GWL_STYLE);

  RECT localRect;

  HPEN   htextPen   = GetSysColorPen (COLOR_BTNTEXT);
  HPEN   holdPen;
  INT    oldBkMode;

  if (drawRect == NULL)
  {
    BOOL isVisible;
    RECT itemRect;
    RECT selectedRect;

    /*
     * Get the rectangle for the item.
     */
1320
    isVisible = TAB_InternalGetItemRect(hwnd, infoPtr, iItem, &itemRect, &selectedRect);
1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357
    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.
     */
    if (lStyle & TCS_BUTTONS)
    {
      *drawRect = itemRect;
      if (iItem == infoPtr->iSelected)
      {
        drawRect->right--;
        drawRect->bottom--;
      }
    }
    else
    {
      if (iItem == infoPtr->iSelected)
        *drawRect = selectedRect;
      else
        *drawRect = itemRect;
      drawRect->right--;
      drawRect->bottom--;
    }
  }

  /*
   * Text pen
   */
1358 1359 1360 1361
  holdPen = SelectObject(hdc, htextPen);

  oldBkMode = SetBkMode(hdc, TRANSPARENT);
  SetTextColor(hdc, GetSysColor((iItem == infoPtr->iHotTracked) ? COLOR_HIGHLIGHT : COLOR_BTNTEXT));
1362 1363 1364 1365

  /*
   * Deflate the rectangle to acount for the padding
   */
1366 1367 1368 1369 1370
  if(lStyle & TCS_VERTICAL)
    InflateRect(drawRect, -VERTICAL_ITEM_PADDING, -HORIZONTAL_ITEM_PADDING);
  else
    InflateRect(drawRect, -HORIZONTAL_ITEM_PADDING, -VERTICAL_ITEM_PADDING);

1371 1372 1373 1374

  /*
   * if owner draw, tell the owner to draw
   */
1375
  if ((lStyle & TCS_OWNERDRAWFIXED) && GetParent(hwnd))
1376 1377 1378 1379 1380 1381 1382
  {
    DRAWITEMSTRUCT dis;
    UINT id;

    /*
     * get the control id
     */
1383
    id = GetWindowLongA( hwnd, GWL_ID );
1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407

    /* 
     * put together the DRAWITEMSTRUCT
     */
    dis.CtlType    = ODT_TAB;	
    dis.CtlID      = id;		
    dis.itemID     = iItem;		
    dis.itemAction = ODA_DRAWENTIRE;	
    if ( iItem == infoPtr->iSelected )
      dis.itemState = ODS_SELECTED;	
    else				
      dis.itemState = 0;		
    dis.hwndItem = hwnd;		/* */
    dis.hDC      = hdc;		
    dis.rcItem   = *drawRect;		/* */
    dis.itemData = infoPtr->items[iItem].lParam;

    /*
     * send the draw message
     */
    SendMessageA( GetParent(hwnd), WM_DRAWITEM, (WPARAM)id, (LPARAM)&dis );
  }
  else
  {
1408 1409
    INT cx;
    INT cy;
1410
    UINT uHorizAlign;
1411 1412 1413
    RECT rcTemp;
    RECT rcImage;
    LOGFONTA logfont;
1414
    HFONT hFont = 0;
1415 1416
    HFONT hOldFont = 0; /* stop uninitialized warning */

1417 1418
    INT nEscapement = 0; /* stop uninitialized warning */
    INT nOrientation = 0; /* stop uninitialized warning */
1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436
    INT iPointSize;

    /* used to center the icon and text in the tab */
    RECT rcText;
    INT center_offset;

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

    rcTemp = *drawRect;

    /*
     * Setup for text output
     */
    oldBkMode = SetBkMode(hdc, TRANSPARENT);
    SetTextColor(hdc, GetSysColor((iItem == infoPtr->iHotTracked) ? COLOR_HIGHLIGHT : COLOR_BTNTEXT));

    /* get the rectangle that the text fits in */
1437
    DrawTextW(hdc, infoPtr->items[iItem].pszText, -1,
1438
              &rcText, DT_CALCRECT);
1439
    rcText.right += 4;
1440 1441 1442 1443 1444 1445 1446
    /*
     * If not owner draw, then do the drawing ourselves.
     *
     * Draw the icon.
     */
    if (infoPtr->himl && (infoPtr->items[iItem].mask & TCIF_IMAGE))
    {
1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476
      ImageList_GetIconSize(infoPtr->himl, &cx, &cy);

      if(lStyle & TCS_VERTICAL)
        center_offset = ((drawRect->bottom - drawRect->top) - (cy + VERTICAL_ITEM_PADDING + (rcText.right - rcText.left))) / 2;
      else
        center_offset = ((drawRect->right - drawRect->left) - (cx + HORIZONTAL_ITEM_PADDING + (rcText.right - rcText.left))) / 2;

      if((lStyle & TCS_VERTICAL) && (lStyle & TCS_BOTTOM))
      {
        /* rcImage.left = drawRect->left; */ /* explicit from above rcImage = *drawRect */
        rcImage.top = drawRect->top + center_offset;
        rcImage.left = drawRect->right - cx; /* 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 */
        drawRect->top = rcImage.top + (cx + VERTICAL_ITEM_PADDING);
      }
      else if(lStyle & TCS_VERTICAL)
      {
        /* rcImage.left = drawRect->left; */ /* explicit from above rcImage = *drawRect */
        rcImage.top = drawRect->bottom - cy - center_offset;

        drawRect->bottom = rcImage.top - VERTICAL_ITEM_PADDING;
      }
      else /* normal style, whether TCS_BOTTOM or not */
      {
        rcImage.left = drawRect->left + center_offset;
        /* rcImage.top = drawRect->top; */ /* explicit from above rcImage = *drawRect */

        drawRect->left = rcImage.left + cx + HORIZONTAL_ITEM_PADDING;
      }
1477 1478 1479 1480 1481 1482

      ImageList_Draw
        (
        infoPtr->himl,
        infoPtr->items[iItem].iImage,
        hdc,
1483 1484
        rcImage.left,
        rcImage.top + 1,
1485 1486
        ILD_NORMAL
        );
1487 1488 1489 1490
    } else /* no image, so just shift the drawRect borders around */
    {
      if(lStyle & TCS_VERTICAL)
      {
1491 1492 1493 1494 1495 1496
        center_offset = 0;
        /*
        currently the rcText rect is flawed because the rotated font does not
        often match the horizontal font. So leave this as 0
        ((drawRect->bottom - drawRect->top) - (rcText.right - rcText.left)) / 2;
        */
1497 1498 1499 1500 1501 1502 1503 1504 1505 1506
        if(lStyle & TCS_BOTTOM)
          drawRect->top+=center_offset;
        else
          drawRect->bottom-=center_offset;
      }
      else
      {
        center_offset = ((drawRect->right - drawRect->left) - (rcText.right - rcText.left)) / 2;
        drawRect->left+=center_offset;
      }
1507 1508
    }

1509
    /* Draw the text */
1510 1511 1512 1513 1514
    if (lStyle & TCS_RIGHTJUSTIFY)
      uHorizAlign = DT_CENTER;
    else
      uHorizAlign = DT_LEFT;

1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532
    if(lStyle & TCS_VERTICAL) /* if we are vertical rotate the text and each character */
    {
      if(lStyle & TCS_BOTTOM)
      {
        nEscapement = -900;
        nOrientation = -900;
      }
      else
      {
        nEscapement = 900;
        nOrientation = 900;
      }
    }

    /* 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 */
    if(lStyle & TCS_VERTICAL)
    {
1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546
      if (!GetObjectA((infoPtr->hFont) ? 
                infoPtr->hFont : GetStockObject(SYSTEM_FONT),
                sizeof(LOGFONTA),&logfont))
      {
        iPointSize = 9;

        lstrcpyA(logfont.lfFaceName, "Arial");
        logfont.lfHeight = -MulDiv(iPointSize, GetDeviceCaps(hdc, LOGPIXELSY), 
                                    72);
        logfont.lfWeight = FW_NORMAL;
        logfont.lfItalic = 0;
        logfont.lfUnderline = 0;
        logfont.lfStrikeOut = 0;
      }
1547 1548 1549 1550 1551 1552 1553

      logfont.lfEscapement = nEscapement;
      logfont.lfOrientation = nOrientation;
      hFont = CreateFontIndirectA(&logfont);
      hOldFont = SelectObject(hdc, hFont);
    }

1554 1555 1556 1557 1558
    if (lStyle & TCS_VERTICAL)
    {
      ExtTextOutW(hdc,
      (lStyle & TCS_BOTTOM) ? drawRect->right : drawRect->left,
      (!(lStyle & TCS_BOTTOM)) ? drawRect->bottom : drawRect->top,
1559 1560
      ETO_CLIPPED,
      drawRect,
1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575
      infoPtr->items[iItem].pszText,
      lstrlenW(infoPtr->items[iItem].pszText),
      0);
    }
    else
    {
      DrawTextW
      (
        hdc,
        infoPtr->items[iItem].pszText,
        lstrlenW(infoPtr->items[iItem].pszText),
        drawRect,
        uHorizAlign | DT_SINGLELINE
        );
    }
1576 1577 1578 1579 1580

    /* clean things up */
    *drawRect = rcTemp; /* restore drawRect */

    if(lStyle & TCS_VERTICAL)
1581
    {
1582
      SelectObject(hdc, hOldFont); /* restore the original font */
1583 1584 1585
      if (hFont)
        DeleteObject(hFont);
    }
1586 1587 1588 1589 1590 1591 1592 1593 1594
  }

  /*
  * Cleanup
  */
  SetBkMode(hdc, oldBkMode);
  SelectObject(hdc, holdPen);
}

Francis Beaudet's avatar
Francis Beaudet committed
1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605
/******************************************************************************
 * TAB_DrawItem
 *
 * This method is used to draw a single tab into the tab control.
 */         
static void TAB_DrawItem(
  HWND hwnd, 
  HDC  hdc, 
  INT  iItem)
{
  TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
1606
  LONG      lStyle  = GetWindowLongA(hwnd, GWL_STYLE);
Francis Beaudet's avatar
Francis Beaudet committed
1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622
  RECT      itemRect;
  RECT      selectedRect;
  BOOL      isVisible;
  RECT      r;

  /*
   * Get the rectangle for the item.
   */
  isVisible = TAB_InternalGetItemRect(hwnd,
				      infoPtr,
				      iItem,
				      &itemRect,
				      &selectedRect);

  if (isVisible)
  {
1623
    HBRUSH hbr       = CreateSolidBrush (GetSysColor(COLOR_BTNFACE));    
Francis Beaudet's avatar
Francis Beaudet committed
1624
    HPEN   hwPen     = GetSysColorPen (COLOR_3DHILIGHT);
1625 1626 1627
    HPEN hbPen  = GetSysColorPen (COLOR_3DDKSHADOW);
    HPEN hShade = GetSysColorPen (COLOR_BTNSHADOW);

Francis Beaudet's avatar
Francis Beaudet committed
1628
    HPEN   holdPen;
Andreas Mohr's avatar
Andreas Mohr committed
1629
    BOOL   deleteBrush = TRUE;
Francis Beaudet's avatar
Francis Beaudet committed
1630

1631
    if (lStyle & TCS_BUTTONS)
Francis Beaudet's avatar
Francis Beaudet committed
1632
    {
1633
      /* Get item rectangle */
1634 1635 1636 1637
      r = itemRect;

      holdPen = SelectObject (hdc, hwPen);

1638 1639
      /* Separators between flat buttons */
      /* FIXME: test and correct this if necessary for TCS_FLATBUTTONS style */
1640 1641
      if (lStyle & TCS_FLATBUTTONS) 
      {
1642 1643
        int x = r.right + FLAT_BTN_SPACINGX - 2;

1644
        /* highlight */
1645 1646
        MoveToEx (hdc, x, r.bottom - 1, NULL);
        LineTo   (hdc, x, r.top - 1);
1647
        x--;
1648

1649 1650
        /* shadow */
        SelectObject(hdc, hbPen);
1651 1652 1653 1654 1655 1656 1657
        MoveToEx (hdc, x, r.bottom - 1, NULL);
        LineTo   (hdc, x, r.top - 1);

        /* shade */
        SelectObject (hdc, hShade );
        MoveToEx (hdc, x - 1, r.bottom - 1, NULL);
        LineTo   (hdc, x - 1, r.top - 1);
1658 1659
      }

1660 1661
      if (iItem == infoPtr->iSelected)
      {
1662
        /* Background color */
Alexandre Julliard's avatar
Alexandre Julliard committed
1663
        if (!((lStyle & TCS_OWNERDRAWFIXED) && infoPtr->fSizeSet))
Francis Beaudet's avatar
Francis Beaudet committed
1664
	{
1665
              COLORREF bk = GetSysColor(COLOR_3DHILIGHT);
1666
              DeleteObject(hbr);
1667
              hbr = GetSysColorBrush(COLOR_SCROLLBAR);
1668

1669 1670 1671 1672 1673 1674 1675
              SetTextColor(hdc, GetSysColor(COLOR_3DFACE));
              SetBkColor(hdc, bk);

              /* 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.
               */
1676
               if (bk == GetSysColor(COLOR_WINDOW))
1677
                  hbr = COMCTL32_hPattern55AABrush;
1678 1679

              deleteBrush = FALSE;
Francis Beaudet's avatar
Francis Beaudet committed
1680
	}
1681

1682
        /* Erase the background */
1683 1684 1685 1686 1687
        FillRect(hdc, &r, hbr);

        /*
         * Draw the tab now.
         * The rectangles calculated exclude the right and bottom
1688
         * borders of the rectangle. To simplify the following code, those
1689 1690 1691 1692 1693 1694
         * borders are shaved-off beforehand.
         */
        r.right--;
        r.bottom--;

        /* highlight */
1695
        SelectObject(hdc, hwPen);
1696 1697
        MoveToEx (hdc, r.left, r.bottom, NULL);
        LineTo   (hdc, r.right, r.bottom);
1698
        LineTo   (hdc, r.right, r.top + 1);
1699 1700 1701
        
        /* shadow */
        SelectObject(hdc, hbPen);
1702 1703 1704 1705 1706 1707 1708 1709
        LineTo  (hdc, r.left + 1, r.top + 1);
        LineTo  (hdc, r.left + 1, r.bottom);

        /* shade */
        SelectObject (hdc, hShade );
        MoveToEx (hdc, r.right, r.top, NULL);
        LineTo   (hdc, r.left, r.top);
        LineTo   (hdc, r.left, r.bottom);
1710 1711 1712
      }
      else
      {
1713
        /* Erase the background */
1714 1715
        FillRect(hdc, &r, hbr);

1716
	if (!(lStyle & TCS_FLATBUTTONS))
1717 1718 1719 1720 1721 1722 1723 1724 1725 1726
	{
          /* highlight */
          MoveToEx (hdc, r.left, r.bottom, NULL);
          LineTo   (hdc, r.left, r.top);
          LineTo   (hdc, r.right, r.top);
         
          /* shadow */
          SelectObject(hdc, hbPen);
          LineTo  (hdc, r.right, r.bottom);
          LineTo  (hdc, r.left, r.bottom);
1727 1728 1729 1730 1731 1732

          /* shade */
          SelectObject (hdc, hShade );
          MoveToEx (hdc, r.right - 1, r.top, NULL);
          LineTo   (hdc, r.right - 1, r.bottom - 1);
          LineTo   (hdc, r.left + 1, r.bottom - 1);
1733
	}
1734
      }
Francis Beaudet's avatar
Francis Beaudet committed
1735
    }
1736
    else /* !TCS_BUTTONS */
Francis Beaudet's avatar
Francis Beaudet committed
1737
    {
1738
      /* Background color */
Francis Beaudet's avatar
Francis Beaudet committed
1739
      DeleteObject(hbr);
1740
      hbr = CreateSolidBrush(GetSysColor(COLOR_BTNFACE));    
1741

1742 1743
      /* We draw a rectangle of different sizes depending on the selection
       * state. */
1744 1745 1746 1747
      if (iItem == infoPtr->iSelected)
        r = selectedRect;
      else
        r = itemRect;
Francis Beaudet's avatar
Francis Beaudet committed
1748

1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759
      /*
       * Erase the background.
       * This is necessary when drawing the selected item since it is larger 
       * than the others, it might overlap with stuff already drawn by the 
       * other tabs
       */     
      FillRect(hdc, &r, hbr);

      /*
       * Draw the tab now.
       * The rectangles calculated exclude the right and bottom
1760
       * borders of the rectangle. To simplify the following code, those
1761 1762 1763 1764 1765 1766
       * borders are shaved-off beforehand.
       */
      r.right--;
      r.bottom--;
      
      holdPen = SelectObject (hdc, hwPen);
1767
      if(lStyle & TCS_VERTICAL)
1768
      {
1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805
        if (lStyle & TCS_BOTTOM)
        {
          /* highlight */
          MoveToEx (hdc, r.left, r.top, NULL);
          LineTo   (hdc, r.right - ROUND_CORNER_SIZE, r.top);
          LineTo   (hdc, r.right, r.top + ROUND_CORNER_SIZE);

          /* shadow */
          SelectObject(hdc, hbPen);
          LineTo  (hdc, r.right, r.bottom - ROUND_CORNER_SIZE);
          LineTo  (hdc, r.right - ROUND_CORNER_SIZE, r.bottom);
          LineTo  (hdc, r.left - 1, r.bottom);

          /* shade */
          SelectObject (hdc, hShade );
          MoveToEx (hdc, r.right - 1, r.top, NULL);
          LineTo   (hdc, r.right - 1, r.bottom - 1);
          LineTo   (hdc, r.left - 1,    r.bottom - 1);
        }
        else
        {
          /* highlight */
          MoveToEx (hdc, r.right, r.top, NULL);
          LineTo   (hdc, r.left + ROUND_CORNER_SIZE, r.top);
          LineTo   (hdc, r.left, r.top + ROUND_CORNER_SIZE);
          LineTo   (hdc, r.left, r.bottom - ROUND_CORNER_SIZE);

          /* shadow */
          SelectObject(hdc, hbPen);
          LineTo (hdc, r.left + ROUND_CORNER_SIZE,  r.bottom);
          LineTo (hdc, r.right + 1, r.bottom);

          /* shade */
          SelectObject (hdc, hShade );
          MoveToEx (hdc, r.left + ROUND_CORNER_SIZE - 1, r.bottom - 1, NULL);
          LineTo   (hdc, r.right + 1, r.bottom - 1);
        }
1806
      }
1807
      else
1808
      {
1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 1827 1828 1829 1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851
        if (lStyle & TCS_BOTTOM)
        {
          /* highlight */
          MoveToEx (hdc, r.left, r.top, NULL);
          LineTo   (hdc, r.left, r.bottom - ROUND_CORNER_SIZE);
          LineTo   (hdc, r.left + ROUND_CORNER_SIZE, r.bottom);

          /* shadow */
          SelectObject(hdc, hbPen);
          LineTo  (hdc, r.right - ROUND_CORNER_SIZE, r.bottom);
          LineTo  (hdc, r.right, r.bottom - ROUND_CORNER_SIZE);
          LineTo  (hdc, r.right, r.top - 1);

          /* shade */
          SelectObject (hdc, hShade );
          MoveToEx   (hdc, r.left, r.bottom - 1, NULL);
          LineTo   (hdc, r.right - ROUND_CORNER_SIZE - 1, r.bottom - 1);
          LineTo   (hdc, r.right - 1, r.bottom - ROUND_CORNER_SIZE - 1);
          LineTo  (hdc, r.right - 1, r.top - 1);
        }
        else
        {
          /* highlight */
          if(infoPtr->items[iItem].rect.left == 0) /* if leftmost draw the line longer */
            MoveToEx (hdc, r.left, r.bottom, NULL);
          else
            MoveToEx (hdc, r.left, r.bottom - 1, NULL);

          LineTo   (hdc, r.left, r.top + ROUND_CORNER_SIZE);
          LineTo   (hdc, r.left + ROUND_CORNER_SIZE, r.top);
          LineTo   (hdc, r.right - ROUND_CORNER_SIZE, r.top);

          /* shadow */
          SelectObject(hdc, hbPen);
          LineTo (hdc, r.right,  r.top + ROUND_CORNER_SIZE);
          LineTo (hdc, r.right,  r.bottom + 1);


          /* shade */
          SelectObject (hdc, hShade );
          MoveToEx (hdc, r.right - 1, r.top + ROUND_CORNER_SIZE, NULL);
          LineTo   (hdc, r.right - 1, r.bottom + 1);
        }
1852 1853 1854
      }
    }
  
1855
    /* This modifies r to be the text rectangle. */
1856 1857
{
    HFONT hOldFont = SelectObject(hdc, infoPtr->hFont);
1858
    TAB_DrawItemInterior(hwnd, hdc, iItem, &r);
1859 1860
    SelectObject(hdc,hOldFont);
}
1861
    /* Draw the focus rectangle */
1862
    if (((lStyle & TCS_FOCUSNEVER) == 0) &&
Francis Beaudet's avatar
Francis Beaudet committed
1863 1864 1865
	 (GetFocus() == hwnd) &&
	 (iItem == infoPtr->uFocus) )
    {
1866 1867
      r = itemRect;
      InflateRect(&r, -1, -1);
Francis Beaudet's avatar
Francis Beaudet committed
1868

1869
      DrawFocusRect(hdc, &r);
Francis Beaudet's avatar
Francis Beaudet committed
1870 1871
    }

1872
    /* Cleanup */
Francis Beaudet's avatar
Francis Beaudet committed
1873
    SelectObject(hdc, holdPen);
1874
    if (deleteBrush) DeleteObject(hbr);
Francis Beaudet's avatar
Francis Beaudet committed
1875
  }
Alexandre Julliard's avatar
Alexandre Julliard committed
1876 1877
}

Francis Beaudet's avatar
Francis Beaudet committed
1878 1879 1880 1881 1882 1883 1884
/******************************************************************************
 * TAB_DrawBorder
 *
 * This method is used to draw the raised border around the tab control
 * "content" area.
 */         
static void TAB_DrawBorder (HWND hwnd, HDC hdc)
Alexandre Julliard's avatar
Alexandre Julliard committed
1885
{
Francis Beaudet's avatar
Francis Beaudet committed
1886 1887 1888 1889 1890 1891
  TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
  HPEN htmPen;
  HPEN hwPen  = GetSysColorPen (COLOR_3DHILIGHT);
  HPEN hbPen  = GetSysColorPen (COLOR_3DDKSHADOW);
  HPEN hShade = GetSysColorPen (COLOR_BTNSHADOW);
  RECT rect;
1892
  DWORD lStyle = GetWindowLongA(hwnd, GWL_STYLE);
Alexandre Julliard's avatar
Alexandre Julliard committed
1893

Francis Beaudet's avatar
Francis Beaudet committed
1894
  GetClientRect (hwnd, &rect);
Alexandre Julliard's avatar
Alexandre Julliard committed
1895

Francis Beaudet's avatar
Francis Beaudet committed
1896 1897 1898
  /*
   * Adjust for the style
   */
Gerard Patel's avatar
Gerard Patel committed
1899 1900

  if (infoPtr->uNumItem)
Francis Beaudet's avatar
Francis Beaudet committed
1901
  {
Gerard Patel's avatar
Gerard Patel committed
1902 1903 1904 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914 1915 1916 1917
    if ((lStyle & TCS_BOTTOM) && !(lStyle & TCS_VERTICAL))
    {
      rect.bottom -= (infoPtr->tabHeight - 2) * infoPtr->uNumRows + 2;
    }
    else if((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
    {
      rect.right -= (infoPtr->tabHeight - 2) * infoPtr->uNumRows + 2;
    }
    else if(lStyle & TCS_VERTICAL)
    {
      rect.left += (infoPtr->tabHeight - 2) * infoPtr->uNumRows + 2;
    }
    else /* not TCS_VERTICAL and not TCS_BOTTOM */
    {
      rect.top += (infoPtr->tabHeight - 2) * infoPtr->uNumRows + 1;
    }
Francis Beaudet's avatar
Francis Beaudet committed
1918
  }
Alexandre Julliard's avatar
Alexandre Julliard committed
1919

Francis Beaudet's avatar
Francis Beaudet committed
1920 1921 1922 1923 1924 1925
  /*
   * Shave-off the right and bottom margins (exluded in the
   * rect)
   */
  rect.right--;
  rect.bottom--;
Alexandre Julliard's avatar
Alexandre Julliard committed
1926

Francis Beaudet's avatar
Francis Beaudet committed
1927 1928
  /* highlight */
  htmPen = SelectObject (hdc, hwPen);
1929

Francis Beaudet's avatar
Francis Beaudet committed
1930
  MoveToEx (hdc, rect.left, rect.bottom, NULL);
1931
  LineTo (hdc, rect.left, rect.top);
Francis Beaudet's avatar
Francis Beaudet committed
1932 1933 1934 1935 1936 1937 1938 1939 1940
  LineTo (hdc, rect.right, rect.top);

  /* Dark Shadow */
  SelectObject (hdc, hbPen);
  LineTo (hdc, rect.right, rect.bottom );
  LineTo (hdc, rect.left, rect.bottom);

  /* shade */
  SelectObject (hdc, hShade );
1941 1942 1943
  MoveToEx (hdc, rect.right - 1, rect.top, NULL);
  LineTo   (hdc, rect.right - 1, rect.bottom - 1);
  LineTo   (hdc, rect.left,    rect.bottom - 1);
Francis Beaudet's avatar
Francis Beaudet committed
1944 1945

  SelectObject(hdc, htmPen);
Alexandre Julliard's avatar
Alexandre Julliard committed
1946 1947
}

Francis Beaudet's avatar
Francis Beaudet committed
1948 1949 1950 1951 1952 1953
/******************************************************************************
 * TAB_Refresh
 *
 * This method repaints the tab control..
 */             
static void TAB_Refresh (HWND hwnd, HDC hdc)
Alexandre Julliard's avatar
Alexandre Julliard committed
1954
{
Francis Beaudet's avatar
Francis Beaudet committed
1955 1956 1957
  TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
  HFONT hOldFont;
  INT i;
Alexandre Julliard's avatar
Alexandre Julliard committed
1958

Francis Beaudet's avatar
Francis Beaudet committed
1959 1960
  if (!infoPtr->DoRedraw)
    return;
Alex Priem's avatar
Alex Priem committed
1961

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

1964
  if (GetWindowLongA(hwnd, GWL_STYLE) & TCS_BUTTONS)
Francis Beaudet's avatar
Francis Beaudet committed
1965
  {
1966
    for (i = 0; i < infoPtr->uNumItem; i++) 
1967
      TAB_DrawItem (hwnd, hdc, i);
Francis Beaudet's avatar
Francis Beaudet committed
1968
  }
1969 1970
  else
  {
1971
    /* Draw all the non selected item first */
1972 1973 1974 1975 1976
    for (i = 0; i < infoPtr->uNumItem; i++) 
    {
      if (i != infoPtr->iSelected)
	TAB_DrawItem (hwnd, hdc, i);
    }
Francis Beaudet's avatar
Francis Beaudet committed
1977

1978 1979
    /* Now, draw the border, draw it before the selected item
     * since the selected item overwrites part of the border. */
1980
    TAB_DrawBorder (hwnd, hdc);
Francis Beaudet's avatar
Francis Beaudet committed
1981

1982
    /* Then, draw the selected item */
1983
    TAB_DrawItem (hwnd, hdc, infoPtr->iSelected);
1984

1985 1986
    /* If we haven't set the current focus yet, set it now.
     * Only happens when we first paint the tab controls */
1987 1988
    if (infoPtr->uFocus == -1)
      TAB_SetCurFocus(hwnd, infoPtr->iSelected);
1989
  }
Francis Beaudet's avatar
Francis Beaudet committed
1990 1991

  SelectObject (hdc, hOldFont);
Alexandre Julliard's avatar
Alexandre Julliard committed
1992 1993
}

1994 1995 1996 1997
static DWORD
TAB_GetRowCount (HWND hwnd )
{
  TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
Gerard Patel's avatar
Gerard Patel committed
1998

1999 2000 2001
  return infoPtr->uNumRows;
}

Alex Priem's avatar
Alex Priem committed
2002
static LRESULT
2003
TAB_SetRedraw (HWND hwnd, WPARAM wParam)
Alex Priem's avatar
Alex Priem committed
2004
{
2005
    TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
Francis Beaudet's avatar
Francis Beaudet committed
2006 2007 2008
  
  infoPtr->DoRedraw=(BOOL) wParam;
  return 0;
Alex Priem's avatar
Alex Priem committed
2009 2010
}

Francis Beaudet's avatar
Francis Beaudet committed
2011 2012 2013
static LRESULT TAB_EraseBackground(
  HWND hwnd, 
  HDC  givenDC)
Alexandre Julliard's avatar
Alexandre Julliard committed
2014
{
Francis Beaudet's avatar
Francis Beaudet committed
2015 2016 2017
  HDC  hdc;
  RECT clientRect;

2018
  HBRUSH brush = CreateSolidBrush(GetSysColor(COLOR_BTNFACE));
Francis Beaudet's avatar
Francis Beaudet committed
2019 2020 2021 2022 2023 2024 2025 2026 2027 2028

  hdc = givenDC ? givenDC : GetDC(hwnd);

  GetClientRect(hwnd, &clientRect);

  FillRect(hdc, &clientRect, brush);

  if (givenDC==0)
    ReleaseDC(hwnd, hdc);

Francis Beaudet's avatar
Francis Beaudet committed
2029 2030
  DeleteObject(brush);

Francis Beaudet's avatar
Francis Beaudet committed
2031
  return 0;
Alexandre Julliard's avatar
Alexandre Julliard committed
2032 2033
}

Francis Beaudet's avatar
Francis Beaudet committed
2034 2035 2036 2037 2038 2039 2040 2041 2042
/******************************************************************************
 * TAB_EnsureSelectionVisible
 *
 * This method will make sure that the current selection is completely
 * visible by scrolling until it is.
 */
static void TAB_EnsureSelectionVisible(
  HWND      hwnd,
  TAB_INFO* infoPtr)
Alexandre Julliard's avatar
Alexandre Julliard committed
2043
{
Alexandre Julliard's avatar
Alexandre Julliard committed
2044
  INT iSelected = infoPtr->iSelected;
2045
  LONG lStyle = GetWindowLongA(hwnd, GWL_STYLE);
2046 2047
  INT iOrigLeftmostVisible = infoPtr->leftmostVisible;

2048 2049
  /* set the items row to the bottommost row or topmost row depending on
   * style */
Gerard Patel's avatar
Gerard Patel committed
2050
  if ((infoPtr->uNumRows > 1) && !(lStyle & TCS_BUTTONS))
2051
  {
2052
      INT newselected;
2053 2054
      INT iTargetRow;

2055 2056
      if(lStyle & TCS_VERTICAL)
        newselected = infoPtr->items[iSelected].rect.left;
2057
      else
2058 2059
        newselected = infoPtr->items[iSelected].rect.top;

Gerard Patel's avatar
Gerard Patel committed
2060 2061 2062
      /* the target row is always (number of rows - 1)
         as row 0 is furthest from the clientRect */
      iTargetRow = infoPtr->uNumRows - 1;
2063 2064 2065 2066

      if (newselected != iTargetRow)
      {
         INT i;
2067 2068 2069 2070 2071 2072 2073 2074 2075 2076 2077 2078 2079 2080 2081 2082 2083 2084
         if(lStyle & TCS_VERTICAL)
         {
           for (i=0; i < infoPtr->uNumItem; i++)
           {
             /* move everything in the row of the selected item to the iTargetRow */
             if (infoPtr->items[i].rect.left == newselected )
                 infoPtr->items[i].rect.left = iTargetRow;
             else
             {
               if (infoPtr->items[i].rect.left > newselected)
                 infoPtr->items[i].rect.left-=1;
             }
           }
         }
         else
         {
           for (i=0; i < infoPtr->uNumItem; i++)
           {
2085 2086
             if (infoPtr->items[i].rect.top == newselected )
                 infoPtr->items[i].rect.top = iTargetRow;
2087 2088 2089 2090 2091 2092 2093
             else
             {
               if (infoPtr->items[i].rect.top > newselected)
                 infoPtr->items[i].rect.top-=1;
             }
          }
        }
2094
        TAB_RecalcHotTrack(hwnd, NULL, NULL, NULL);
2095 2096 2097
      }
  }

Francis Beaudet's avatar
Francis Beaudet committed
2098 2099 2100 2101
  /*
   * Do the trivial cases first.
   */
  if ( (!infoPtr->needsScrolling) ||
2102
       (infoPtr->hwndUpDown==0) || (lStyle & TCS_VERTICAL))
Francis Beaudet's avatar
Francis Beaudet committed
2103 2104
    return;

Alexandre Julliard's avatar
Alexandre Julliard committed
2105
  if (infoPtr->leftmostVisible >= iSelected)
Francis Beaudet's avatar
Francis Beaudet committed
2106
  {
Alexandre Julliard's avatar
Alexandre Julliard committed
2107
    infoPtr->leftmostVisible = iSelected;
Francis Beaudet's avatar
Francis Beaudet committed
2108
  }
Alexandre Julliard's avatar
Alexandre Julliard committed
2109
  else
Francis Beaudet's avatar
Francis Beaudet committed
2110
  {
Alexandre Julliard's avatar
Alexandre Julliard committed
2111 2112
     RECT r;
     INT  width, i;
2113 2114

     /* Calculate the part of the client area that is visible */
Alexandre Julliard's avatar
Alexandre Julliard committed
2115 2116 2117 2118 2119 2120 2121 2122 2123 2124 2125 2126 2127 2128 2129 2130 2131 2132 2133 2134 2135 2136 2137 2138
     GetClientRect(hwnd, &r);
     width = r.right;

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

     if ((infoPtr->items[iSelected].rect.right -
          infoPtr->items[iSelected].rect.left) >= width )
     {
        /* 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++)
        {
           if ((infoPtr->items[iSelected].rect.right -
                infoPtr->items[i].rect.left) < width)
              break;
        }
        infoPtr->leftmostVisible = i;
     }
Francis Beaudet's avatar
Francis Beaudet committed
2139
  }
Alexandre Julliard's avatar
Alexandre Julliard committed
2140

2141 2142 2143
  if (infoPtr->leftmostVisible != iOrigLeftmostVisible)
    TAB_RecalcHotTrack(hwnd, NULL, NULL, NULL);

Alexandre Julliard's avatar
Alexandre Julliard committed
2144 2145
  SendMessageA(infoPtr->hwndUpDown, UDM_SETPOS, 0,
               MAKELONG(infoPtr->leftmostVisible, 0));
Francis Beaudet's avatar
Francis Beaudet committed
2146
}
Alexandre Julliard's avatar
Alexandre Julliard committed
2147

Francis Beaudet's avatar
Francis Beaudet committed
2148 2149 2150 2151 2152 2153 2154 2155 2156 2157 2158 2159
/******************************************************************************
 * 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
 */
static void TAB_InvalidateTabArea(
  HWND      hwnd,
  TAB_INFO* infoPtr)
{
  RECT clientRect;
2160
  DWORD lStyle = GetWindowLongA(hwnd, GWL_STYLE);
Gerard Patel's avatar
Gerard Patel committed
2161 2162 2163
  INT lastRow = infoPtr->uNumRows - 1;

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

Francis Beaudet's avatar
Francis Beaudet committed
2165
  GetClientRect(hwnd, &clientRect);
Alexandre Julliard's avatar
Alexandre Julliard committed
2166

2167 2168 2169 2170
  if ((lStyle & TCS_BOTTOM) && !(lStyle & TCS_VERTICAL))
  {
    clientRect.top = clientRect.bottom -
                   infoPtr->tabHeight -
Gerard Patel's avatar
Gerard Patel committed
2171 2172
                   lastRow * (infoPtr->tabHeight - 2) -
                   ((lStyle & TCS_BUTTONS) ? lastRow * BUTTON_SPACINGY : 0) - 2;
2173 2174 2175 2176
  }
  else if((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
  {
    clientRect.left = clientRect.right - infoPtr->tabHeight -
Gerard Patel's avatar
Gerard Patel committed
2177 2178
                      lastRow * (infoPtr->tabHeight - 2) -
                      ((lStyle & TCS_BUTTONS) ? lastRow * BUTTON_SPACINGY : 0) - 2;
2179 2180
  }
  else if(lStyle & TCS_VERTICAL)
Francis Beaudet's avatar
Francis Beaudet committed
2181
  {
2182
    clientRect.right = clientRect.left + infoPtr->tabHeight +
Gerard Patel's avatar
Gerard Patel committed
2183 2184
                       lastRow * (infoPtr->tabHeight - 2) -
                      ((lStyle & TCS_BUTTONS) ? lastRow * BUTTON_SPACINGY : 0) + 1;
2185

Francis Beaudet's avatar
Francis Beaudet committed
2186 2187 2188
  }
  else
  {
2189
    clientRect.bottom = clientRect.top + infoPtr->tabHeight +
Gerard Patel's avatar
Gerard Patel committed
2190 2191
                      lastRow * (infoPtr->tabHeight - 2) +
                      ((lStyle & TCS_BUTTONS) ? lastRow * BUTTON_SPACINGY : 0) + 1;
Francis Beaudet's avatar
Francis Beaudet committed
2192
  }
Alexandre Julliard's avatar
Alexandre Julliard committed
2193

Francis Beaudet's avatar
Francis Beaudet committed
2194 2195
  InvalidateRect(hwnd, &clientRect, TRUE);
}
Alexandre Julliard's avatar
Alexandre Julliard committed
2196

Francis Beaudet's avatar
Francis Beaudet committed
2197 2198 2199 2200 2201 2202 2203 2204 2205 2206 2207
static LRESULT
TAB_Paint (HWND hwnd, WPARAM wParam)
{
  HDC hdc;
  PAINTSTRUCT ps;
    
  hdc = wParam== 0 ? BeginPaint (hwnd, &ps) : (HDC)wParam;
  TAB_Refresh (hwnd, hdc);
    
  if(!wParam)
    EndPaint (hwnd, &ps);
Alexandre Julliard's avatar
Alexandre Julliard committed
2208

Francis Beaudet's avatar
Francis Beaudet committed
2209 2210
  return 0;
}
Alexandre Julliard's avatar
Alexandre Julliard committed
2211

Francis Beaudet's avatar
Francis Beaudet committed
2212
static LRESULT
2213
TAB_InsertItemA (HWND hwnd, WPARAM wParam, LPARAM lParam)
Francis Beaudet's avatar
Francis Beaudet committed
2214 2215 2216
{    
  TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
  TCITEMA *pti;
2217
  INT iItem;
Francis Beaudet's avatar
Francis Beaudet committed
2218 2219 2220
  RECT rect;
  
  GetClientRect (hwnd, &rect);
2221
  TRACE("Rect: %x T %i, L %i, B %i, R %i\n", hwnd,
Francis Beaudet's avatar
Francis Beaudet committed
2222 2223 2224 2225 2226 2227 2228 2229 2230 2231 2232 2233
        rect.top, rect.left, rect.bottom, rect.right);  
  
  pti = (TCITEMA *)lParam;
  iItem = (INT)wParam;
  
  if (iItem < 0) return -1;
  if (iItem > infoPtr->uNumItem)
    iItem = infoPtr->uNumItem;
  
  if (infoPtr->uNumItem == 0) {
    infoPtr->items = COMCTL32_Alloc (sizeof (TAB_ITEM));
    infoPtr->uNumItem++;
2234
    infoPtr->iSelected = 0;
Francis Beaudet's avatar
Francis Beaudet committed
2235 2236 2237 2238 2239 2240 2241 2242 2243 2244 2245 2246 2247 2248 2249 2250 2251 2252 2253
  }
  else {
    TAB_ITEM *oldItems = infoPtr->items;
    
    infoPtr->uNumItem++;
    infoPtr->items = COMCTL32_Alloc (sizeof (TAB_ITEM) * infoPtr->uNumItem);
    
    /* pre insert copy */
    if (iItem > 0) {
      memcpy (&infoPtr->items[0], &oldItems[0],
	      iItem * sizeof(TAB_ITEM));
    }
    
    /* post insert copy */
    if (iItem < infoPtr->uNumItem - 1) {
      memcpy (&infoPtr->items[iItem+1], &oldItems[iItem],
	      (infoPtr->uNumItem - iItem - 1) * sizeof(TAB_ITEM));
      
    }
2254 2255 2256 2257

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

Francis Beaudet's avatar
Francis Beaudet committed
2258 2259 2260 2261
    COMCTL32_Free (oldItems);
  }
  
  infoPtr->items[iItem].mask = pti->mask;
2262 2263 2264 2265 2266 2267 2268 2269 2270 2271 2272 2273
  if (pti->mask & TCIF_TEXT)
    Str_SetPtrAtoW (&infoPtr->items[iItem].pszText, pti->pszText);

  if (pti->mask & TCIF_IMAGE)
    infoPtr->items[iItem].iImage = pti->iImage;
  
  if (pti->mask & TCIF_PARAM)
    infoPtr->items[iItem].lParam = pti->lParam;
  
  TAB_SetItemBounds(hwnd);
  TAB_InvalidateTabArea(hwnd, infoPtr);
  
2274
  TRACE("[%04x]: added item %d %s\n",
2275 2276 2277 2278 2279 2280 2281 2282 2283 2284 2285 2286 2287 2288 2289 2290 2291 2292 2293 2294 2295 2296 2297 2298 2299 2300 2301 2302 2303 2304 2305 2306 2307 2308 2309 2310 2311 2312 2313 2314 2315 2316 2317 2318 2319 2320 2321
	hwnd, iItem, debugstr_w(infoPtr->items[iItem].pszText));

  return iItem;
}


static LRESULT
TAB_InsertItemW (HWND hwnd, WPARAM wParam, LPARAM lParam)
{
  TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
  TCITEMW *pti;
  INT iItem;
  RECT rect;

  GetClientRect (hwnd, &rect);
  TRACE("Rect: %x T %i, L %i, B %i, R %i\n", hwnd,
        rect.top, rect.left, rect.bottom, rect.right);

  pti = (TCITEMW *)lParam;
  iItem = (INT)wParam;

  if (iItem < 0) return -1;
  if (iItem > infoPtr->uNumItem)
    iItem = infoPtr->uNumItem;

  if (infoPtr->uNumItem == 0) {
    infoPtr->items = COMCTL32_Alloc (sizeof (TAB_ITEM));
    infoPtr->uNumItem++;
    infoPtr->iSelected = 0;
  }
  else {
    TAB_ITEM *oldItems = infoPtr->items;

    infoPtr->uNumItem++;
    infoPtr->items = COMCTL32_Alloc (sizeof (TAB_ITEM) * infoPtr->uNumItem);

    /* pre insert copy */
    if (iItem > 0) {
      memcpy (&infoPtr->items[0], &oldItems[0],
	      iItem * sizeof(TAB_ITEM));
    }

    /* post insert copy */
    if (iItem < infoPtr->uNumItem - 1) {
      memcpy (&infoPtr->items[iItem+1], &oldItems[iItem],
	      (infoPtr->uNumItem - iItem - 1) * sizeof(TAB_ITEM));

Francis Beaudet's avatar
Francis Beaudet committed
2322 2323
  }
  
2324 2325 2326 2327 2328 2329 2330 2331 2332 2333
    if (iItem <= infoPtr->iSelected)
      infoPtr->iSelected++;

    COMCTL32_Free (oldItems);
  }

  infoPtr->items[iItem].mask = pti->mask;
  if (pti->mask & TCIF_TEXT)
    Str_SetPtrW (&infoPtr->items[iItem].pszText, pti->pszText);

Francis Beaudet's avatar
Francis Beaudet committed
2334 2335 2336 2337 2338 2339
  if (pti->mask & TCIF_IMAGE)
    infoPtr->items[iItem].iImage = pti->iImage;
  
  if (pti->mask & TCIF_PARAM)
    infoPtr->items[iItem].lParam = pti->lParam;
  
2340
  TAB_SetItemBounds(hwnd);
Francis Beaudet's avatar
Francis Beaudet committed
2341 2342
  TAB_InvalidateTabArea(hwnd, infoPtr);
  
2343
  TRACE("[%04x]: added item %d %s\n",
2344
	hwnd, iItem, debugstr_w(infoPtr->items[iItem].pszText));
Alexandre Julliard's avatar
Alexandre Julliard committed
2345

Francis Beaudet's avatar
Francis Beaudet committed
2346
  return iItem;
Alexandre Julliard's avatar
Alexandre Julliard committed
2347 2348
}

2349

2350 2351 2352 2353 2354 2355 2356 2357 2358 2359 2360 2361 2362
static LRESULT 
TAB_SetItemSize (HWND hwnd, WPARAM wParam, LPARAM lParam)
{
  TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
  LONG lStyle = GetWindowLongA(hwnd, GWL_STYLE);
  LONG lResult = 0;

  if ((lStyle & TCS_FIXEDWIDTH) || (lStyle & TCS_OWNERDRAWFIXED))
  {
    lResult = MAKELONG(infoPtr->tabWidth, infoPtr->tabHeight);
    infoPtr->tabWidth = (INT)LOWORD(lParam);
    infoPtr->tabHeight = (INT)HIWORD(lParam);
  }
Alexandre Julliard's avatar
Alexandre Julliard committed
2363
  infoPtr->fSizeSet = TRUE;
2364 2365 2366 2367

  return lResult;
}

2368
static LRESULT 
2369
TAB_SetItemA (HWND hwnd, WPARAM wParam, LPARAM lParam)
2370
{
2371
  TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2372
  TCITEMA *tabItem; 
2373
  TAB_ITEM *wineItem;
2374
  INT    iItem;
2375

2376 2377 2378 2379
  iItem = (INT)wParam;
  tabItem = (LPTCITEMA)lParam;

  TRACE("%d %p\n", iItem, tabItem);
2380
  if ((iItem<0) || (iItem>=infoPtr->uNumItem)) return FALSE;
2381

2382
  wineItem = &infoPtr->items[iItem];
2383

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

2387
  if (tabItem->mask & TCIF_PARAM)
2388
    wineItem->lParam = tabItem->lParam;
2389 2390

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

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

2396 2397
  if (tabItem->mask & TCIF_TEXT)
   Str_SetPtrAtoW(&wineItem->pszText, tabItem->pszText);
2398

2399 2400 2401
  /* Update and repaint tabs */
  TAB_SetItemBounds(hwnd);
  TAB_InvalidateTabArea(hwnd,infoPtr);
2402

2403
  return TRUE;
2404 2405
  }

2406 2407 2408 2409 2410 2411 2412 2413 2414 2415 2416 2417 2418 2419 2420 2421 2422 2423 2424 2425 2426 2427 2428 2429 2430 2431 2432 2433 2434 2435 2436 2437

static LRESULT
TAB_SetItemW (HWND hwnd, WPARAM wParam, LPARAM lParam)
{
  TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
  TCITEMW *tabItem;
  TAB_ITEM *wineItem;
  INT    iItem;

  iItem = (INT)wParam;
  tabItem = (LPTCITEMW)lParam;

  TRACE("%d %p\n", iItem, tabItem);
  if ((iItem<0) || (iItem>=infoPtr->uNumItem)) return FALSE;

  wineItem = &infoPtr->items[iItem];

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

  if (tabItem->mask & TCIF_PARAM)
    wineItem->lParam = tabItem->lParam;

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

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

  if (tabItem->mask & TCIF_TEXT)
   Str_SetPtrW(&wineItem->pszText, tabItem->pszText);

2438
  /* Update and repaint tabs */
2439 2440 2441
  TAB_SetItemBounds(hwnd);
  TAB_InvalidateTabArea(hwnd,infoPtr);

Francis Beaudet's avatar
Francis Beaudet committed
2442
  return TRUE;
2443 2444
}

2445

2446
static LRESULT 
2447
TAB_GetItemCount (HWND hwnd, WPARAM wParam, LPARAM lParam)
2448
{
2449
   TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2450 2451 2452 2453 2454 2455

   return infoPtr->uNumItem;
}


static LRESULT 
2456
TAB_GetItemA (HWND hwnd, WPARAM wParam, LPARAM lParam)
2457
{
2458
   TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2459
   TCITEMA *tabItem;
2460
   TAB_ITEM *wineItem;
2461
   INT    iItem;
2462

2463 2464
  iItem = (INT)wParam;
  tabItem = (LPTCITEMA)lParam;
2465
  TRACE("\n");
2466 2467
  if ((iItem<0) || (iItem>=infoPtr->uNumItem))
    return FALSE;
2468

2469
  wineItem = &infoPtr->items[iItem];
2470 2471

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

  if (tabItem->mask & TCIF_PARAM) 
2475
    tabItem->lParam = wineItem->lParam;
2476 2477

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

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

  if (tabItem->mask & TCIF_TEXT) 
2484
   Str_GetPtrWtoA (wineItem->pszText, tabItem->pszText, tabItem->cchTextMax);
2485

Francis Beaudet's avatar
Francis Beaudet committed
2486
  return TRUE;
2487 2488
}

2489 2490 2491 2492 2493 2494 2495 2496 2497 2498 2499 2500 2501 2502 2503 2504 2505 2506 2507 2508 2509 2510 2511 2512 2513 2514 2515 2516 2517 2518 2519 2520 2521 2522 2523 2524

static LRESULT 
TAB_GetItemW (HWND hwnd, WPARAM wParam, LPARAM lParam)
{
  TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
  TCITEMW *tabItem;
  TAB_ITEM *wineItem;
  INT    iItem;

  iItem = (INT)wParam;
  tabItem = (LPTCITEMW)lParam;
  TRACE("\n");
  if ((iItem<0) || (iItem>=infoPtr->uNumItem))
    return FALSE;

  wineItem=& infoPtr->items[iItem];

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

  if (tabItem->mask & TCIF_PARAM)
    tabItem->lParam = wineItem->lParam;

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

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

  if (tabItem->mask & TCIF_TEXT)
   Str_GetPtrW (wineItem->pszText, tabItem->pszText, tabItem->cchTextMax);

  return TRUE;
}


2525
static LRESULT 
2526
TAB_DeleteItem (HWND hwnd, WPARAM wParam, LPARAM lParam)
2527
{
2528 2529 2530 2531 2532 2533 2534 2535 2536 2537 2538 2539 2540 2541 2542 2543 2544 2545
  TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
  INT iItem = (INT) wParam;
  BOOL bResult = FALSE;

  if ((iItem >= 0) && (iItem < infoPtr->uNumItem))
  {
    TAB_ITEM *oldItems = infoPtr->items;
    
    infoPtr->uNumItem--;
    infoPtr->items = COMCTL32_Alloc(sizeof (TAB_ITEM) * infoPtr->uNumItem);
    
    if (iItem > 0) 
      memcpy(&infoPtr->items[0], &oldItems[0], iItem * sizeof(TAB_ITEM));
    
    if (iItem < infoPtr->uNumItem) 
      memcpy(&infoPtr->items[iItem], &oldItems[iItem + 1],
              (infoPtr->uNumItem - iItem) * sizeof(TAB_ITEM));
    
2546
    COMCTL32_Free(oldItems);
2547

2548
    /* Readjust the selected index */
2549 2550 2551 2552 2553 2554
    if ((iItem == infoPtr->iSelected) && (iItem > 0))
      infoPtr->iSelected--;
      
    if (iItem < infoPtr->iSelected)
      infoPtr->iSelected--;

2555 2556 2557
    if (infoPtr->uNumItem == 0)
      infoPtr->iSelected = -1;

2558
    /* Reposition and repaint tabs */
2559 2560 2561
    TAB_SetItemBounds(hwnd);
    TAB_InvalidateTabArea(hwnd,infoPtr);

2562 2563 2564 2565
    bResult = TRUE;
  }

  return bResult;
2566
}
Alex Priem's avatar
Alex Priem committed
2567

2568
static LRESULT 
2569
TAB_DeleteAllItems (HWND hwnd, WPARAM wParam, LPARAM lParam)
2570
{
2571
   TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2572

Francis Beaudet's avatar
Francis Beaudet committed
2573
  COMCTL32_Free (infoPtr->items);
2574 2575
  infoPtr->uNumItem = 0;
  infoPtr->iSelected = -1;
2576 2577 2578
  if (infoPtr->iHotTracked >= 0)
    KillTimer(hwnd, TAB_HOTTRACK_TIMER);
  infoPtr->iHotTracked = -1;
Alexandre Julliard's avatar
Alexandre Julliard committed
2579 2580 2581
 
  TAB_SetItemBounds(hwnd);
  TAB_InvalidateTabArea(hwnd,infoPtr);
Francis Beaudet's avatar
Francis Beaudet committed
2582
  return TRUE;
2583 2584 2585 2586
}


static LRESULT
2587
TAB_GetFont (HWND hwnd, WPARAM wParam, LPARAM lParam)
2588
{
2589
  TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2590

2591
  TRACE("\n");
2592 2593 2594 2595
  return (LRESULT)infoPtr->hFont;
}

static LRESULT
2596
TAB_SetFont (HWND hwnd, WPARAM wParam, LPARAM lParam)
2597 2598

{
Francis Beaudet's avatar
Francis Beaudet committed
2599 2600
  TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
  
2601
  TRACE("%x %lx\n",wParam, lParam);
Francis Beaudet's avatar
Francis Beaudet committed
2602 2603 2604 2605
  
  infoPtr->hFont = (HFONT)wParam;
  
  TAB_SetItemBounds(hwnd);
2606

Francis Beaudet's avatar
Francis Beaudet committed
2607
  TAB_InvalidateTabArea(hwnd, infoPtr);
2608

Francis Beaudet's avatar
Francis Beaudet committed
2609
  return 0;
2610 2611 2612 2613
}


static LRESULT
2614
TAB_GetImageList (HWND hwnd, WPARAM wParam, LPARAM lParam)
2615
{
2616
  TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2617

2618
  TRACE("\n");
2619 2620 2621 2622
  return (LRESULT)infoPtr->himl;
}

static LRESULT
2623
TAB_SetImageList (HWND hwnd, WPARAM wParam, LPARAM lParam)
2624
{
2625
    TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
2626 2627
    HIMAGELIST himlPrev;

2628
    TRACE("\n");
2629
    himlPrev = infoPtr->himl;
Francis Beaudet's avatar
Francis Beaudet committed
2630
    infoPtr->himl= (HIMAGELIST)lParam;
2631 2632 2633
    return (LRESULT)himlPrev;
}

2634 2635 2636 2637 2638 2639 2640 2641 2642 2643 2644 2645 2646 2647 2648 2649 2650
static LRESULT
TAB_GetUnicodeFormat (HWND hwnd)
{
    TAB_INFO *infoPtr = TAB_GetInfoPtr (hwnd);
    return infoPtr->bUnicode;
}

static LRESULT
TAB_SetUnicodeFormat (HWND hwnd, WPARAM wParam)
{
    TAB_INFO *infoPtr = TAB_GetInfoPtr (hwnd);
    BOOL bTemp = infoPtr->bUnicode;

    infoPtr->bUnicode = (BOOL)wParam;

    return bTemp;
}
Alex Priem's avatar
Alex Priem committed
2651 2652

static LRESULT
2653
TAB_Size (HWND hwnd, WPARAM wParam, LPARAM lParam)
Alex Priem's avatar
Alex Priem committed
2654 2655

{
2656 2657 2658 2659 2660
/* 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.

2661 2662 2663
  RECT parent_rect;
  HWND parent;
  UINT uPosFlags,cx,cy;
Alex Priem's avatar
Alex Priem committed
2664 2665 2666

  uPosFlags=0;
  if (!wParam) {
Francis Beaudet's avatar
Francis Beaudet committed
2667 2668 2669 2670 2671
    parent = GetParent (hwnd);
    GetClientRect(parent, &parent_rect);
    cx=LOWORD (lParam);
    cy=HIWORD (lParam);
    if (GetWindowLongA(hwnd, GWL_STYLE) & CCS_NORESIZE) 
Alex Priem's avatar
Alex Priem committed
2672 2673
        uPosFlags |= (SWP_NOSIZE | SWP_NOMOVE);

Francis Beaudet's avatar
Francis Beaudet committed
2674
    SetWindowPos (hwnd, 0, parent_rect.left, parent_rect.top,
Alex Priem's avatar
Alex Priem committed
2675
            cx, cy, uPosFlags | SWP_NOZORDER);
2676
  } else {
Andreas Mohr's avatar
Andreas Mohr committed
2677
    FIXME("WM_SIZE flag %x %lx not handled\n", wParam, lParam);
2678
  } */
Alex Priem's avatar
Alex Priem committed
2679

2680
  /* Recompute the size/position of the tabs. */
2681
  TAB_SetItemBounds (hwnd);
Francis Beaudet's avatar
Francis Beaudet committed
2682

2683
  /* Force a repaint of the control. */
Francis Beaudet's avatar
Francis Beaudet committed
2684
  InvalidateRect(hwnd, NULL, TRUE);
Alex Priem's avatar
Alex Priem committed
2685 2686 2687 2688 2689

  return 0;
}


Alexandre Julliard's avatar
Alexandre Julliard committed
2690
static LRESULT 
2691
TAB_Create (HWND hwnd, WPARAM wParam, LPARAM lParam)
Alexandre Julliard's avatar
Alexandre Julliard committed
2692
{
Francis Beaudet's avatar
Francis Beaudet committed
2693
  TAB_INFO *infoPtr;
2694 2695 2696
  TEXTMETRICA fontMetrics;
  HDC hdc;
  HFONT hOldFont;
Alexandre Julliard's avatar
Alexandre Julliard committed
2697
  DWORD dwStyle;
Alexandre Julliard's avatar
Alexandre Julliard committed
2698

Francis Beaudet's avatar
Francis Beaudet committed
2699 2700 2701
  infoPtr = (TAB_INFO *)COMCTL32_Alloc (sizeof(TAB_INFO));

  SetWindowLongA(hwnd, 0, (DWORD)infoPtr);
Alexandre Julliard's avatar
Alexandre Julliard committed
2702
   
Francis Beaudet's avatar
Francis Beaudet committed
2703
  infoPtr->uNumItem        = 0;
2704
  infoPtr->uNumRows        = 0;
Francis Beaudet's avatar
Francis Beaudet committed
2705 2706 2707
  infoPtr->hFont           = 0;
  infoPtr->items           = 0;
  infoPtr->hcurArrow       = LoadCursorA (0, IDC_ARROWA);
2708
  infoPtr->iSelected       = -1;
2709
  infoPtr->iHotTracked     = -1;
Yuxi Zhang's avatar
Yuxi Zhang committed
2710
  infoPtr->uFocus          = -1;  
Francis Beaudet's avatar
Francis Beaudet committed
2711 2712 2713 2714 2715
  infoPtr->hwndToolTip     = 0;
  infoPtr->DoRedraw        = TRUE;
  infoPtr->needsScrolling  = FALSE;
  infoPtr->hwndUpDown      = 0;
  infoPtr->leftmostVisible = 0;
Alexandre Julliard's avatar
Alexandre Julliard committed
2716
  infoPtr->fSizeSet	   = FALSE;
2717
  infoPtr->bUnicode	   = IsWindowUnicode (hwnd);
Alexandre Julliard's avatar
Alexandre Julliard committed
2718
  
2719
  TRACE("Created tab control, hwnd [%04x]\n", hwnd); 
Alexandre Julliard's avatar
Alexandre Julliard committed
2720 2721

  /* The tab control always has the WS_CLIPSIBLINGS style. Even 
Andreas Mohr's avatar
Andreas Mohr committed
2722
     if you don't specify it in CreateWindow. This is necessary in 
Alexandre Julliard's avatar
Alexandre Julliard committed
2723 2724 2725 2726 2727
     order for paint to work correctly. This follows windows behaviour. */
  dwStyle = GetWindowLongA(hwnd, GWL_STYLE);
  SetWindowLongA(hwnd, GWL_STYLE, dwStyle|WS_CLIPSIBLINGS);

  if (dwStyle & TCS_TOOLTIPS) {
Alex Priem's avatar
Alex Priem committed
2728 2729
    /* Create tooltip control */
    infoPtr->hwndToolTip =
Francis Beaudet's avatar
Francis Beaudet committed
2730 2731 2732 2733 2734
      CreateWindowExA (0, TOOLTIPS_CLASSA, NULL, 0,
		       CW_USEDEFAULT, CW_USEDEFAULT,
		       CW_USEDEFAULT, CW_USEDEFAULT,
		       hwnd, 0, 0, 0);
    
Alex Priem's avatar
Alex Priem committed
2735 2736
    /* Send NM_TOOLTIPSCREATED notification */
    if (infoPtr->hwndToolTip) {
Francis Beaudet's avatar
Francis Beaudet committed
2737 2738 2739 2740 2741 2742 2743 2744 2745
      NMTOOLTIPSCREATED nmttc;
      
      nmttc.hdr.hwndFrom = hwnd;
      nmttc.hdr.idFrom = GetWindowLongA(hwnd, GWL_ID);
      nmttc.hdr.code = NM_TOOLTIPSCREATED;
      nmttc.hwndToolTips = infoPtr->hwndToolTip;
      
      SendMessageA (GetParent (hwnd), WM_NOTIFY,
		    (WPARAM)GetWindowLongA(hwnd, GWL_ID), (LPARAM)&nmttc);
Alex Priem's avatar
Alex Priem committed
2746
    }
Francis Beaudet's avatar
Francis Beaudet committed
2747 2748
  }  
    
2749 2750 2751 2752 2753 2754 2755
  /*
   * We need to get text information so we need a DC and we need to select
   * a font.
   */
  hdc = GetDC(hwnd); 
  hOldFont = SelectObject (hdc, GetStockObject (SYSTEM_FONT));

2756
  /* Use the system font to determine the initial height of a tab. */
2757 2758 2759 2760 2761 2762
  GetTextMetricsA(hdc, &fontMetrics);

  /*
   * Make sure there is enough space for the letters + growing the 
   * selected item + extra space for the selected item.   
   */
2763
  infoPtr->tabHeight = fontMetrics.tmHeight + 2 * VERTICAL_ITEM_PADDING +
2764 2765
                       SELECTED_TAB_OFFSET;

2766
  /* Initialize the width of a tab. */
2767 2768 2769 2770 2771
  infoPtr->tabWidth = DEFAULT_TAB_WIDTH;

  SelectObject (hdc, hOldFont);
  ReleaseDC(hwnd, hdc);

Francis Beaudet's avatar
Francis Beaudet committed
2772
  return 0;
Alexandre Julliard's avatar
Alexandre Julliard committed
2773 2774 2775
}

static LRESULT
2776
TAB_Destroy (HWND hwnd, WPARAM wParam, LPARAM lParam)
Alexandre Julliard's avatar
Alexandre Julliard committed
2777
{
Francis Beaudet's avatar
Francis Beaudet committed
2778 2779
  TAB_INFO *infoPtr = TAB_GetInfoPtr(hwnd);
  INT iItem;
Alexandre Julliard's avatar
Alexandre Julliard committed
2780

Alexandre Julliard's avatar
Alexandre Julliard committed
2781 2782 2783
  if (!infoPtr)
      return 0;

Francis Beaudet's avatar
Francis Beaudet committed
2784 2785 2786 2787 2788 2789 2790 2791 2792 2793 2794 2795 2796
  if (infoPtr->items) {
    for (iItem = 0; iItem < infoPtr->uNumItem; iItem++) {
      if (infoPtr->items[iItem].pszText)
	COMCTL32_Free (infoPtr->items[iItem].pszText);
    }
    COMCTL32_Free (infoPtr->items);
  }
  
  if (infoPtr->hwndToolTip) 
    DestroyWindow (infoPtr->hwndToolTip);
 
  if (infoPtr->hwndUpDown)
    DestroyWindow(infoPtr->hwndUpDown);
Alex Priem's avatar
Alex Priem committed
2797

2798 2799 2800
  if (infoPtr->iHotTracked >= 0)
    KillTimer(hwnd, TAB_HOTTRACK_TIMER);

Francis Beaudet's avatar
Francis Beaudet committed
2801
  COMCTL32_Free (infoPtr);
2802
  SetWindowLongA(hwnd, 0, 0);
Francis Beaudet's avatar
Francis Beaudet committed
2803
  return 0;
Alexandre Julliard's avatar
Alexandre Julliard committed
2804 2805
}

2806
static LRESULT WINAPI
2807
TAB_WindowProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
Alexandre Julliard's avatar
Alexandre Julliard committed
2808
{
2809 2810 2811 2812 2813

    TRACE("hwnd=%x msg=%x wParam=%x lParam=%lx\n", hwnd, uMsg, wParam, lParam);
    if (!TAB_GetInfoPtr(hwnd) && (uMsg != WM_CREATE))
      return DefWindowProcA (hwnd, uMsg, wParam, lParam);

Alexandre Julliard's avatar
Alexandre Julliard committed
2814 2815
    switch (uMsg)
    {
Francis Beaudet's avatar
Francis Beaudet committed
2816 2817 2818 2819 2820 2821 2822 2823 2824 2825 2826 2827 2828
    case TCM_GETIMAGELIST:
      return TAB_GetImageList (hwnd, wParam, lParam);
      
    case TCM_SETIMAGELIST:
      return TAB_SetImageList (hwnd, wParam, lParam);
      
    case TCM_GETITEMCOUNT:
      return TAB_GetItemCount (hwnd, wParam, lParam);
      
    case TCM_GETITEMA:
      return TAB_GetItemA (hwnd, wParam, lParam);
      
    case TCM_GETITEMW:
2829
      return TAB_GetItemW (hwnd, wParam, lParam);
Francis Beaudet's avatar
Francis Beaudet committed
2830 2831 2832 2833 2834
      
    case TCM_SETITEMA:
      return TAB_SetItemA (hwnd, wParam, lParam);
      
    case TCM_SETITEMW:
2835
      return TAB_SetItemW (hwnd, wParam, lParam);
Francis Beaudet's avatar
Francis Beaudet committed
2836 2837 2838 2839 2840 2841 2842 2843
      
    case TCM_DELETEITEM:
      return TAB_DeleteItem (hwnd, wParam, lParam);
      
    case TCM_DELETEALLITEMS:
     return TAB_DeleteAllItems (hwnd, wParam, lParam);
     
    case TCM_GETITEMRECT:
2844
     return TAB_GetItemRect (hwnd, wParam, lParam);
Francis Beaudet's avatar
Francis Beaudet committed
2845 2846 2847 2848 2849 2850 2851 2852 2853 2854 2855
      
    case TCM_GETCURSEL:
      return TAB_GetCurSel (hwnd);
      
    case TCM_HITTEST:
      return TAB_HitTest (hwnd, wParam, lParam);
      
    case TCM_SETCURSEL:
      return TAB_SetCurSel (hwnd, wParam);
      
    case TCM_INSERTITEMA:
2856
      return TAB_InsertItemA (hwnd, wParam, lParam);
Francis Beaudet's avatar
Francis Beaudet committed
2857 2858
      
    case TCM_INSERTITEMW:
2859
      return TAB_InsertItemW (hwnd, wParam, lParam);
Francis Beaudet's avatar
Francis Beaudet committed
2860 2861
      
    case TCM_SETITEMEXTRA:
2862
      FIXME("Unimplemented msg TCM_SETITEMEXTRA\n");
Francis Beaudet's avatar
Francis Beaudet committed
2863 2864 2865
      return 0;
      
    case TCM_ADJUSTRECT:
2866
      return TAB_AdjustRect (hwnd, (BOOL)wParam, (LPRECT)lParam);
Francis Beaudet's avatar
Francis Beaudet committed
2867 2868
      
    case TCM_SETITEMSIZE:
2869
      return TAB_SetItemSize (hwnd, wParam, lParam);
Francis Beaudet's avatar
Francis Beaudet committed
2870 2871
      
    case TCM_REMOVEIMAGE:
2872
      FIXME("Unimplemented msg TCM_REMOVEIMAGE\n");
Francis Beaudet's avatar
Francis Beaudet committed
2873 2874 2875
      return 0;
      
    case TCM_SETPADDING:
2876
      FIXME("Unimplemented msg TCM_SETPADDING\n");
Francis Beaudet's avatar
Francis Beaudet committed
2877 2878 2879
      return 0;
      
    case TCM_GETROWCOUNT:
2880
      return TAB_GetRowCount(hwnd);
2881 2882

    case TCM_GETUNICODEFORMAT:
2883
      return TAB_GetUnicodeFormat (hwnd);
2884 2885

    case TCM_SETUNICODEFORMAT:
2886
      return TAB_SetUnicodeFormat (hwnd, wParam);
2887 2888

    case TCM_HIGHLIGHTITEM:
2889
      FIXME("Unimplemented msg TCM_HIGHLIGHTITEM\n");
2890
      return 0;
Francis Beaudet's avatar
Francis Beaudet committed
2891 2892 2893 2894 2895 2896 2897 2898 2899 2900 2901 2902 2903
      
    case TCM_GETTOOLTIPS:
      return TAB_GetToolTips (hwnd, wParam, lParam);
      
    case TCM_SETTOOLTIPS:
      return TAB_SetToolTips (hwnd, wParam, lParam);
      
    case TCM_GETCURFOCUS:
      return TAB_GetCurFocus (hwnd);
      
    case TCM_SETCURFOCUS:
      return TAB_SetCurFocus (hwnd, wParam);
      
Alexandre Julliard's avatar
Alexandre Julliard committed
2904 2905
    case TCM_SETMINTABWIDTH:
      FIXME("Unimplemented msg TCM_SETMINTABWIDTH\n");
Francis Beaudet's avatar
Francis Beaudet committed
2906 2907 2908
      return 0;
      
    case TCM_DESELECTALL:
2909
      FIXME("Unimplemented msg TCM_DESELECTALL\n");
Francis Beaudet's avatar
Francis Beaudet committed
2910 2911
      return 0;
      
2912
    case TCM_GETEXTENDEDSTYLE:
2913
      FIXME("Unimplemented msg TCM_GETEXTENDEDSTYLE\n");
2914 2915 2916
      return 0;

    case TCM_SETEXTENDEDSTYLE:
2917
      FIXME("Unimplemented msg TCM_SETEXTENDEDSTYLE\n");
2918 2919
      return 0;

Francis Beaudet's avatar
Francis Beaudet committed
2920 2921 2922 2923 2924 2925 2926 2927 2928 2929 2930 2931
    case WM_GETFONT:
      return TAB_GetFont (hwnd, wParam, lParam);
      
    case WM_SETFONT:
      return TAB_SetFont (hwnd, wParam, lParam);
      
    case WM_CREATE:
      return TAB_Create (hwnd, wParam, lParam);
      
    case WM_NCDESTROY:
      return TAB_Destroy (hwnd, wParam, lParam);
      
2932
    case WM_GETDLGCODE:
Francis Beaudet's avatar
Francis Beaudet committed
2933 2934 2935 2936 2937 2938 2939 2940 2941 2942 2943 2944 2945 2946 2947 2948 2949 2950 2951 2952 2953 2954 2955 2956 2957 2958 2959 2960
      return DLGC_WANTARROWS | DLGC_WANTCHARS;
      
    case WM_LBUTTONDOWN:
      return TAB_LButtonDown (hwnd, wParam, lParam);
      
    case WM_LBUTTONUP:
      return TAB_LButtonUp (hwnd, wParam, lParam);
      
    case WM_RBUTTONDOWN:
      return TAB_RButtonDown (hwnd, wParam, lParam);
      
    case WM_MOUSEMOVE:
      return TAB_MouseMove (hwnd, wParam, lParam);
      
    case WM_ERASEBKGND:
      return TAB_EraseBackground (hwnd, (HDC)wParam);

    case WM_PAINT:
      return TAB_Paint (hwnd, wParam);

    case WM_SIZE:
      return TAB_Size (hwnd, wParam, lParam);
      
    case WM_SETREDRAW:
      return TAB_SetRedraw (hwnd, wParam);

    case WM_HSCROLL:
      return TAB_OnHScroll(hwnd, (int)LOWORD(wParam), (int)HIWORD(wParam), (HWND)lParam);
Alexandre Julliard's avatar
Alexandre Julliard committed
2961 2962 2963 2964 2965

    case WM_STYLECHANGED:
      TAB_SetItemBounds (hwnd);
      InvalidateRect(hwnd, NULL, TRUE);
      return 0;
Francis Beaudet's avatar
Francis Beaudet committed
2966 2967 2968 2969 2970 2971 2972
      
    case WM_KILLFOCUS:
    case WM_SETFOCUS:
      return TAB_FocusChanging(hwnd, uMsg, wParam, lParam);

    case WM_KEYUP:
      return TAB_KeyUp(hwnd, wParam);
2973 2974
    case WM_NCHITTEST:
      return TAB_NCHitTest(hwnd, lParam);
Francis Beaudet's avatar
Francis Beaudet committed
2975 2976 2977

    default:
      if (uMsg >= WM_USER)
2978
	WARN("unknown msg %04x wp=%08x lp=%08lx\n",
Francis Beaudet's avatar
Francis Beaudet committed
2979 2980
	     uMsg, wParam, lParam);
      return DefWindowProcA (hwnd, uMsg, wParam, lParam);
Alexandre Julliard's avatar
Alexandre Julliard committed
2981
    }
Francis Beaudet's avatar
Francis Beaudet committed
2982

Alexandre Julliard's avatar
Alexandre Julliard committed
2983 2984 2985 2986
    return 0;
}


2987
VOID
2988
TAB_Register (void)
Alexandre Julliard's avatar
Alexandre Julliard committed
2989
{
Francis Beaudet's avatar
Francis Beaudet committed
2990 2991 2992
  WNDCLASSA wndClass;

  ZeroMemory (&wndClass, sizeof(WNDCLASSA));
2993
  wndClass.style         = CS_GLOBALCLASS | CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
Francis Beaudet's avatar
Francis Beaudet committed
2994 2995 2996 2997 2998 2999 3000 3001
  wndClass.lpfnWndProc   = (WNDPROC)TAB_WindowProc;
  wndClass.cbClsExtra    = 0;
  wndClass.cbWndExtra    = sizeof(TAB_INFO *);
  wndClass.hCursor       = LoadCursorA (0, IDC_ARROWA);
  wndClass.hbrBackground = (HBRUSH)NULL;
  wndClass.lpszClassName = WC_TABCONTROLA;
  
  RegisterClassA (&wndClass);
Alexandre Julliard's avatar
Alexandre Julliard committed
3002 3003
}

3004 3005

VOID
3006
TAB_Unregister (void)
3007
{
Francis Beaudet's avatar
Francis Beaudet committed
3008
    UnregisterClassA (WC_TABCONTROLA, (HINSTANCE)NULL);
3009
}