Merge second part of the comctl32 Wine 1.2 sync.
[reactos.git] / reactos / dll / win32 / comctl32 / tab.c
1 /*
2 * Tab control
3 *
4 * Copyright 1998 Anders Carlsson
5 * Copyright 1999 Alex Priem <alexp@sci.kun.nl>
6 * Copyright 1999 Francis Beaudet
7 * Copyright 2003 Vitaliy Margolen
8 *
9 * This library is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Lesser General Public
11 * License as published by the Free Software Foundation; either
12 * version 2.1 of the License, or (at your option) any later version.
13 *
14 * This library is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Lesser General Public License for more details.
18 *
19 * You should have received a copy of the GNU Lesser General Public
20 * License along with this library; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
22 *
23 * NOTES
24 *
25 * This code was audited for completeness against the documented features
26 * of Comctl32.dll version 6.0 on May. 20, 2005, by James Hawkins.
27 *
28 * Unless otherwise noted, we believe this code to be complete, as per
29 * the specification mentioned above.
30 * If you discover missing features, or bugs, please note them below.
31 *
32 * TODO:
33 *
34 * Styles:
35 * TCS_MULTISELECT - implement for VK_SPACE selection
36 * TCS_RIGHT
37 * TCS_RIGHTJUSTIFY
38 * TCS_SCROLLOPPOSITE
39 * TCS_SINGLELINE
40 * TCIF_RTLREADING
41 *
42 * Extended Styles:
43 * TCS_EX_REGISTERDROP
44 *
45 * Notifications:
46 * NM_RELEASEDCAPTURE
47 * TCN_FOCUSCHANGE
48 * TCN_GETOBJECT
49 *
50 * Macros:
51 * TabCtrl_AdjustRect
52 *
53 */
54
55 #include <stdarg.h>
56 #include <string.h>
57
58 #include "windef.h"
59 #include "winbase.h"
60 #include "wingdi.h"
61 #include "winuser.h"
62 #include "winnls.h"
63 #include "commctrl.h"
64 #include "comctl32.h"
65 #include "uxtheme.h"
66 #include "tmschema.h"
67 #include "wine/debug.h"
68 #include <math.h>
69
70 WINE_DEFAULT_DEBUG_CHANNEL(tab);
71
72 typedef struct
73 {
74 DWORD dwState;
75 LPWSTR pszText;
76 INT iImage;
77 RECT rect; /* bounding rectangle of the item relative to the
78 * leftmost item (the leftmost item, 0, would have a
79 * "left" member of 0 in this rectangle)
80 *
81 * additionally the top member holds the row number
82 * and bottom is unused and should be 0 */
83 BYTE extra[1]; /* Space for caller supplied info, variable size */
84 } TAB_ITEM;
85
86 /* The size of a tab item depends on how much extra data is requested */
87 #define TAB_ITEM_SIZE(infoPtr) (FIELD_OFFSET(TAB_ITEM, extra[(infoPtr)->cbInfo]))
88
89 typedef struct
90 {
91 HWND hwnd; /* Tab control window */
92 HWND hwndNotify; /* notification window (parent) */
93 UINT uNumItem; /* number of tab items */
94 UINT uNumRows; /* number of tab rows */
95 INT tabHeight; /* height of the tab row */
96 INT tabWidth; /* width of tabs */
97 INT tabMinWidth; /* minimum width of items */
98 USHORT uHItemPadding; /* amount of horizontal padding, in pixels */
99 USHORT uVItemPadding; /* amount of vertical padding, in pixels */
100 USHORT uHItemPadding_s; /* Set amount of horizontal padding, in pixels */
101 USHORT uVItemPadding_s; /* Set amount of vertical padding, in pixels */
102 HFONT hFont; /* handle to the current font */
103 HCURSOR hcurArrow; /* handle to the current cursor */
104 HIMAGELIST himl; /* handle to an image list (may be 0) */
105 HWND hwndToolTip; /* handle to tab's tooltip */
106 INT leftmostVisible; /* Used for scrolling, this member contains
107 * the index of the first visible item */
108 INT iSelected; /* the currently selected item */
109 INT iHotTracked; /* the highlighted item under the mouse */
110 INT uFocus; /* item which has the focus */
111 TAB_ITEM* items; /* pointer to an array of TAB_ITEM's */
112 BOOL DoRedraw; /* flag for redrawing when tab contents is changed*/
113 BOOL needsScrolling; /* TRUE if the size of the tabs is greater than
114 * the size of the control */
115 BOOL fHeightSet; /* was the height of the tabs explicitly set? */
116 BOOL bUnicode; /* Unicode control? */
117 HWND hwndUpDown; /* Updown control used for scrolling */
118 INT cbInfo; /* Number of bytes of caller supplied info per tab */
119
120 DWORD exStyle; /* Extended style used, currently:
121 TCS_EX_FLATSEPARATORS, TCS_EX_REGISTERDROP */
122 DWORD dwStyle; /* the cached window GWL_STYLE */
123 } TAB_INFO;
124
125 /******************************************************************************
126 * Positioning constants
127 */
128 #define SELECTED_TAB_OFFSET 2
129 #define ROUND_CORNER_SIZE 2
130 #define DISPLAY_AREA_PADDINGX 2
131 #define DISPLAY_AREA_PADDINGY 2
132 #define CONTROL_BORDER_SIZEX 2
133 #define CONTROL_BORDER_SIZEY 2
134 #define BUTTON_SPACINGX 3
135 #define BUTTON_SPACINGY 3
136 #define FLAT_BTN_SPACINGX 8
137 #define DEFAULT_MIN_TAB_WIDTH 54
138 #define DEFAULT_PADDING_X 6
139 #define EXTRA_ICON_PADDING 3
140
141 #define TAB_GetInfoPtr(hwnd) ((TAB_INFO *)GetWindowLongPtrW(hwnd,0))
142 /* Since items are variable sized, cannot directly access them */
143 #define TAB_GetItem(info,i) \
144 ((TAB_ITEM*)((LPBYTE)info->items + (i) * TAB_ITEM_SIZE(info)))
145
146 #define GET_DEFAULT_MIN_TAB_WIDTH(infoPtr) (DEFAULT_MIN_TAB_WIDTH - (DEFAULT_PADDING_X - (infoPtr)->uHItemPadding) * 2)
147
148 /******************************************************************************
149 * Hot-tracking timer constants
150 */
151 #define TAB_HOTTRACK_TIMER 1
152 #define TAB_HOTTRACK_TIMER_INTERVAL 100 /* milliseconds */
153
154 static const WCHAR themeClass[] = { 'T','a','b',0 };
155
156 /******************************************************************************
157 * Prototypes
158 */
159 static void TAB_InvalidateTabArea(const TAB_INFO *);
160 static void TAB_EnsureSelectionVisible(TAB_INFO *);
161 static void TAB_DrawItemInterior(const TAB_INFO *, HDC, INT, RECT*);
162 static LRESULT TAB_DeselectAll(TAB_INFO *, BOOL);
163 static BOOL TAB_InternalGetItemRect(const TAB_INFO *, INT, RECT*, RECT*);
164
165 static BOOL
166 TAB_SendSimpleNotify (const TAB_INFO *infoPtr, UINT code)
167 {
168 NMHDR nmhdr;
169
170 nmhdr.hwndFrom = infoPtr->hwnd;
171 nmhdr.idFrom = GetWindowLongPtrW(infoPtr->hwnd, GWLP_ID);
172 nmhdr.code = code;
173
174 return (BOOL) SendMessageW (infoPtr->hwndNotify, WM_NOTIFY,
175 nmhdr.idFrom, (LPARAM) &nmhdr);
176 }
177
178 static void
179 TAB_RelayEvent (HWND hwndTip, HWND hwndMsg, UINT uMsg,
180 WPARAM wParam, LPARAM lParam)
181 {
182 MSG msg;
183
184 msg.hwnd = hwndMsg;
185 msg.message = uMsg;
186 msg.wParam = wParam;
187 msg.lParam = lParam;
188 msg.time = GetMessageTime ();
189 msg.pt.x = (short)LOWORD(GetMessagePos ());
190 msg.pt.y = (short)HIWORD(GetMessagePos ());
191
192 SendMessageW (hwndTip, TTM_RELAYEVENT, 0, (LPARAM)&msg);
193 }
194
195 static void
196 TAB_DumpItemExternalT(const TCITEMW *pti, UINT iItem, BOOL isW)
197 {
198 if (TRACE_ON(tab)) {
199 TRACE("external tab %d, mask=0x%08x, dwState=0x%08x, dwStateMask=0x%08x, cchTextMax=0x%08x\n",
200 iItem, pti->mask, pti->dwState, pti->dwStateMask, pti->cchTextMax);
201 TRACE("external tab %d, iImage=%d, lParam=0x%08lx, pszTextW=%s\n",
202 iItem, pti->iImage, pti->lParam, isW ? debugstr_w(pti->pszText) : debugstr_a((LPSTR)pti->pszText));
203 }
204 }
205
206 static void
207 TAB_DumpItemInternal(const TAB_INFO *infoPtr, UINT iItem)
208 {
209 if (TRACE_ON(tab)) {
210 TAB_ITEM *ti;
211
212 ti = TAB_GetItem(infoPtr, iItem);
213 TRACE("tab %d, dwState=0x%08x, pszText=%s, iImage=%d\n",
214 iItem, ti->dwState, debugstr_w(ti->pszText), ti->iImage);
215 TRACE("tab %d, rect.left=%d, rect.top(row)=%d\n",
216 iItem, ti->rect.left, ti->rect.top);
217 }
218 }
219
220 /* RETURNS
221 * the index of the selected tab, or -1 if no tab is selected. */
222 static inline LRESULT TAB_GetCurSel (const TAB_INFO *infoPtr)
223 {
224 TRACE("(%p)\n", infoPtr);
225 return infoPtr->iSelected;
226 }
227
228 /* RETURNS
229 * the index of the tab item that has the focus. */
230 static inline LRESULT
231 TAB_GetCurFocus (const TAB_INFO *infoPtr)
232 {
233 TRACE("(%p)\n", infoPtr);
234 return infoPtr->uFocus;
235 }
236
237 static inline LRESULT TAB_GetToolTips (const TAB_INFO *infoPtr)
238 {
239 TRACE("(%p)\n", infoPtr);
240 return (LRESULT)infoPtr->hwndToolTip;
241 }
242
243 static inline LRESULT TAB_SetCurSel (TAB_INFO *infoPtr, INT iItem)
244 {
245 INT prevItem = infoPtr->iSelected;
246
247 TRACE("(%p %d)\n", infoPtr, iItem);
248
249 if (iItem < 0)
250 infoPtr->iSelected = -1;
251 else if (iItem >= infoPtr->uNumItem)
252 return -1;
253 else {
254 if (prevItem != iItem) {
255 if (prevItem != -1)
256 TAB_GetItem(infoPtr, prevItem)->dwState &= ~TCIS_BUTTONPRESSED;
257 TAB_GetItem(infoPtr, iItem)->dwState |= TCIS_BUTTONPRESSED;
258
259 infoPtr->iSelected = iItem;
260 infoPtr->uFocus = iItem;
261 TAB_EnsureSelectionVisible(infoPtr);
262 TAB_InvalidateTabArea(infoPtr);
263 }
264 }
265 return prevItem;
266 }
267
268 static LRESULT TAB_SetCurFocus (TAB_INFO *infoPtr, INT iItem)
269 {
270 TRACE("(%p %d)\n", infoPtr, iItem);
271
272 if (iItem < 0) {
273 infoPtr->uFocus = -1;
274 if (infoPtr->iSelected != -1) {
275 infoPtr->iSelected = -1;
276 TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGE);
277 TAB_InvalidateTabArea(infoPtr);
278 }
279 }
280 else if (iItem < infoPtr->uNumItem) {
281 if (infoPtr->dwStyle & TCS_BUTTONS) {
282 /* set focus to new item, leave selection as is */
283 if (infoPtr->uFocus != iItem) {
284 INT prev_focus = infoPtr->uFocus;
285 RECT r;
286
287 infoPtr->uFocus = iItem;
288
289 if (prev_focus != infoPtr->iSelected) {
290 if (TAB_InternalGetItemRect(infoPtr, prev_focus, &r, NULL))
291 InvalidateRect(infoPtr->hwnd, &r, FALSE);
292 }
293
294 if (TAB_InternalGetItemRect(infoPtr, iItem, &r, NULL))
295 InvalidateRect(infoPtr->hwnd, &r, FALSE);
296
297 TAB_SendSimpleNotify(infoPtr, TCN_FOCUSCHANGE);
298 }
299 } else {
300 INT oldFocus = infoPtr->uFocus;
301 if (infoPtr->iSelected != iItem || oldFocus == -1 ) {
302 infoPtr->uFocus = iItem;
303 if (oldFocus != -1) {
304 if (!TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGING)) {
305 infoPtr->iSelected = iItem;
306 TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGE);
307 }
308 else
309 infoPtr->iSelected = iItem;
310 TAB_EnsureSelectionVisible(infoPtr);
311 TAB_InvalidateTabArea(infoPtr);
312 }
313 }
314 }
315 }
316 return 0;
317 }
318
319 static inline LRESULT
320 TAB_SetToolTips (TAB_INFO *infoPtr, HWND hwndToolTip)
321 {
322 TRACE("%p %p\n", infoPtr, hwndToolTip);
323 infoPtr->hwndToolTip = hwndToolTip;
324 return 0;
325 }
326
327 static inline LRESULT
328 TAB_SetPadding (TAB_INFO *infoPtr, LPARAM lParam)
329 {
330 TRACE("(%p %d %d)\n", infoPtr, LOWORD(lParam), HIWORD(lParam));
331 infoPtr->uHItemPadding_s = LOWORD(lParam);
332 infoPtr->uVItemPadding_s = HIWORD(lParam);
333
334 return 0;
335 }
336
337 /******************************************************************************
338 * TAB_InternalGetItemRect
339 *
340 * This method will calculate the rectangle representing a given tab item in
341 * client coordinates. This method takes scrolling into account.
342 *
343 * This method returns TRUE if the item is visible in the window and FALSE
344 * if it is completely outside the client area.
345 */
346 static BOOL TAB_InternalGetItemRect(
347 const TAB_INFO* infoPtr,
348 INT itemIndex,
349 RECT* itemRect,
350 RECT* selectedRect)
351 {
352 RECT tmpItemRect,clientRect;
353
354 /* Perform a sanity check and a trivial visibility check. */
355 if ( (infoPtr->uNumItem <= 0) ||
356 (itemIndex >= infoPtr->uNumItem) ||
357 (!(((infoPtr->dwStyle & TCS_MULTILINE) || (infoPtr->dwStyle & TCS_VERTICAL))) &&
358 (itemIndex < infoPtr->leftmostVisible)))
359 {
360 TRACE("Not Visible\n");
361 /* need to initialize these to empty rects */
362 if (itemRect)
363 {
364 memset(itemRect,0,sizeof(RECT));
365 itemRect->bottom = infoPtr->tabHeight;
366 }
367 if (selectedRect)
368 memset(selectedRect,0,sizeof(RECT));
369 return FALSE;
370 }
371
372 /*
373 * Avoid special cases in this procedure by assigning the "out"
374 * parameters if the caller didn't supply them
375 */
376 if (itemRect == NULL)
377 itemRect = &tmpItemRect;
378
379 /* Retrieve the unmodified item rect. */
380 *itemRect = TAB_GetItem(infoPtr,itemIndex)->rect;
381
382 /* calculate the times bottom and top based on the row */
383 GetClientRect(infoPtr->hwnd, &clientRect);
384
385 if ((infoPtr->dwStyle & TCS_BOTTOM) && (infoPtr->dwStyle & TCS_VERTICAL))
386 {
387 itemRect->right = clientRect.right - SELECTED_TAB_OFFSET - itemRect->left * infoPtr->tabHeight -
388 ((infoPtr->dwStyle & TCS_BUTTONS) ? itemRect->left * BUTTON_SPACINGX : 0);
389 itemRect->left = itemRect->right - infoPtr->tabHeight;
390 }
391 else if (infoPtr->dwStyle & TCS_VERTICAL)
392 {
393 itemRect->left = clientRect.left + SELECTED_TAB_OFFSET + itemRect->left * infoPtr->tabHeight +
394 ((infoPtr->dwStyle & TCS_BUTTONS) ? itemRect->left * BUTTON_SPACINGX : 0);
395 itemRect->right = itemRect->left + infoPtr->tabHeight;
396 }
397 else if (infoPtr->dwStyle & TCS_BOTTOM)
398 {
399 itemRect->bottom = clientRect.bottom - itemRect->top * infoPtr->tabHeight -
400 ((infoPtr->dwStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : SELECTED_TAB_OFFSET);
401 itemRect->top = itemRect->bottom - infoPtr->tabHeight;
402 }
403 else /* not TCS_BOTTOM and not TCS_VERTICAL */
404 {
405 itemRect->top = clientRect.top + itemRect->top * infoPtr->tabHeight +
406 ((infoPtr->dwStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : SELECTED_TAB_OFFSET);
407 itemRect->bottom = itemRect->top + infoPtr->tabHeight;
408 }
409
410 /*
411 * "scroll" it to make sure the item at the very left of the
412 * tab control is the leftmost visible tab.
413 */
414 if(infoPtr->dwStyle & TCS_VERTICAL)
415 {
416 OffsetRect(itemRect,
417 0,
418 -TAB_GetItem(infoPtr, infoPtr->leftmostVisible)->rect.top);
419
420 /*
421 * Move the rectangle so the first item is slightly offset from
422 * the bottom of the tab control.
423 */
424 OffsetRect(itemRect,
425 0,
426 SELECTED_TAB_OFFSET);
427
428 } else
429 {
430 OffsetRect(itemRect,
431 -TAB_GetItem(infoPtr, infoPtr->leftmostVisible)->rect.left,
432 0);
433
434 /*
435 * Move the rectangle so the first item is slightly offset from
436 * the left of the tab control.
437 */
438 OffsetRect(itemRect,
439 SELECTED_TAB_OFFSET,
440 0);
441 }
442 TRACE("item %d tab h=%d, rect=(%s)\n",
443 itemIndex, infoPtr->tabHeight, wine_dbgstr_rect(itemRect));
444
445 /* Now, calculate the position of the item as if it were selected. */
446 if (selectedRect!=NULL)
447 {
448 CopyRect(selectedRect, itemRect);
449
450 /* The rectangle of a selected item is a bit wider. */
451 if(infoPtr->dwStyle & TCS_VERTICAL)
452 InflateRect(selectedRect, 0, SELECTED_TAB_OFFSET);
453 else
454 InflateRect(selectedRect, SELECTED_TAB_OFFSET, 0);
455
456 /* If it also a bit higher. */
457 if ((infoPtr->dwStyle & TCS_BOTTOM) && (infoPtr->dwStyle & TCS_VERTICAL))
458 {
459 selectedRect->left -= 2; /* the border is thicker on the right */
460 selectedRect->right += SELECTED_TAB_OFFSET;
461 }
462 else if (infoPtr->dwStyle & TCS_VERTICAL)
463 {
464 selectedRect->left -= SELECTED_TAB_OFFSET;
465 selectedRect->right += 1;
466 }
467 else if (infoPtr->dwStyle & TCS_BOTTOM)
468 {
469 selectedRect->bottom += SELECTED_TAB_OFFSET;
470 }
471 else /* not TCS_BOTTOM and not TCS_VERTICAL */
472 {
473 selectedRect->top -= SELECTED_TAB_OFFSET;
474 selectedRect->bottom -= 1;
475 }
476 }
477
478 /* Check for visibility */
479 if (infoPtr->dwStyle & TCS_VERTICAL)
480 return (itemRect->top < clientRect.bottom) && (itemRect->bottom > clientRect.top);
481 else
482 return (itemRect->left < clientRect.right) && (itemRect->right > clientRect.left);
483 }
484
485 static inline BOOL
486 TAB_GetItemRect(const TAB_INFO *infoPtr, INT item, RECT *rect)
487 {
488 TRACE("(%p, %d, %p)\n", infoPtr, item, rect);
489 return TAB_InternalGetItemRect(infoPtr, item, rect, NULL);
490 }
491
492 /******************************************************************************
493 * TAB_KeyDown
494 *
495 * This method is called to handle keyboard input
496 */
497 static LRESULT TAB_KeyDown(TAB_INFO* infoPtr, WPARAM keyCode, LPARAM lParam)
498 {
499 INT newItem = -1;
500 NMTCKEYDOWN nm;
501
502 /* TCN_KEYDOWN notification sent always */
503 nm.hdr.hwndFrom = infoPtr->hwnd;
504 nm.hdr.idFrom = GetWindowLongPtrW(infoPtr->hwnd, GWLP_ID);
505 nm.hdr.code = TCN_KEYDOWN;
506 nm.wVKey = keyCode;
507 nm.flags = lParam;
508 SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, nm.hdr.idFrom, (LPARAM)&nm);
509
510 switch (keyCode)
511 {
512 case VK_LEFT:
513 newItem = infoPtr->uFocus - 1;
514 break;
515 case VK_RIGHT:
516 newItem = infoPtr->uFocus + 1;
517 break;
518 }
519
520 /* If we changed to a valid item, change focused item */
521 if (newItem >= 0 && newItem < infoPtr->uNumItem && infoPtr->uFocus != newItem)
522 TAB_SetCurFocus(infoPtr, newItem);
523
524 return 0;
525 }
526
527 /*
528 * WM_KILLFOCUS handler
529 */
530 static void TAB_KillFocus(TAB_INFO *infoPtr)
531 {
532 /* clear current focused item back to selected for TCS_BUTTONS */
533 if ((infoPtr->dwStyle & TCS_BUTTONS) && (infoPtr->uFocus != infoPtr->iSelected))
534 {
535 RECT r;
536
537 if (TAB_InternalGetItemRect(infoPtr, infoPtr->uFocus, &r, NULL))
538 InvalidateRect(infoPtr->hwnd, &r, FALSE);
539
540 infoPtr->uFocus = infoPtr->iSelected;
541 }
542 }
543
544 /******************************************************************************
545 * TAB_FocusChanging
546 *
547 * This method is called whenever the focus goes in or out of this control
548 * it is used to update the visual state of the control.
549 */
550 static void TAB_FocusChanging(const TAB_INFO *infoPtr)
551 {
552 RECT selectedRect;
553 BOOL isVisible;
554
555 /*
556 * Get the rectangle for the item.
557 */
558 isVisible = TAB_InternalGetItemRect(infoPtr,
559 infoPtr->uFocus,
560 NULL,
561 &selectedRect);
562
563 /*
564 * If the rectangle is not completely invisible, invalidate that
565 * portion of the window.
566 */
567 if (isVisible)
568 {
569 TRACE("invalidate (%s)\n", wine_dbgstr_rect(&selectedRect));
570 InvalidateRect(infoPtr->hwnd, &selectedRect, TRUE);
571 }
572 }
573
574 static INT TAB_InternalHitTest (const TAB_INFO *infoPtr, POINT pt, UINT *flags)
575 {
576 RECT rect;
577 INT iCount;
578
579 for (iCount = 0; iCount < infoPtr->uNumItem; iCount++)
580 {
581 TAB_InternalGetItemRect(infoPtr, iCount, &rect, NULL);
582
583 if (PtInRect(&rect, pt))
584 {
585 *flags = TCHT_ONITEM;
586 return iCount;
587 }
588 }
589
590 *flags = TCHT_NOWHERE;
591 return -1;
592 }
593
594 static inline LRESULT
595 TAB_HitTest (const TAB_INFO *infoPtr, LPTCHITTESTINFO lptest)
596 {
597 TRACE("(%p, %p)\n", infoPtr, lptest);
598 return TAB_InternalHitTest (infoPtr, lptest->pt, &lptest->flags);
599 }
600
601 /******************************************************************************
602 * TAB_NCHitTest
603 *
604 * Napster v2b5 has a tab control for its main navigation which has a client
605 * area that covers the whole area of the dialog pages.
606 * That's why it receives all msgs for that area and the underlying dialog ctrls
607 * are dead.
608 * So I decided that we should handle WM_NCHITTEST here and return
609 * HTTRANSPARENT if we don't hit the tab control buttons.
610 * FIXME: WM_NCHITTEST handling correct ? Fix it if you know that Windows
611 * doesn't do it that way. Maybe depends on tab control styles ?
612 */
613 static inline LRESULT
614 TAB_NCHitTest (const TAB_INFO *infoPtr, LPARAM lParam)
615 {
616 POINT pt;
617 UINT dummyflag;
618
619 pt.x = (short)LOWORD(lParam);
620 pt.y = (short)HIWORD(lParam);
621 ScreenToClient(infoPtr->hwnd, &pt);
622
623 if (TAB_InternalHitTest(infoPtr, pt, &dummyflag) == -1)
624 return HTTRANSPARENT;
625 else
626 return HTCLIENT;
627 }
628
629 static LRESULT
630 TAB_LButtonDown (TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
631 {
632 POINT pt;
633 INT newItem;
634 UINT dummy;
635
636 if (infoPtr->hwndToolTip)
637 TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
638 WM_LBUTTONDOWN, wParam, lParam);
639
640 if (!(infoPtr->dwStyle & TCS_FOCUSNEVER)) {
641 SetFocus (infoPtr->hwnd);
642 }
643
644 if (infoPtr->hwndToolTip)
645 TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
646 WM_LBUTTONDOWN, wParam, lParam);
647
648 pt.x = (short)LOWORD(lParam);
649 pt.y = (short)HIWORD(lParam);
650
651 newItem = TAB_InternalHitTest (infoPtr, pt, &dummy);
652
653 TRACE("On Tab, item %d\n", newItem);
654
655 if ((newItem != -1) && (infoPtr->iSelected != newItem))
656 {
657 if ((infoPtr->dwStyle & TCS_BUTTONS) && (infoPtr->dwStyle & TCS_MULTISELECT) &&
658 (wParam & MK_CONTROL))
659 {
660 RECT r;
661
662 /* toggle multiselection */
663 TAB_GetItem(infoPtr, newItem)->dwState ^= TCIS_BUTTONPRESSED;
664 if (TAB_InternalGetItemRect (infoPtr, newItem, &r, NULL))
665 InvalidateRect (infoPtr->hwnd, &r, TRUE);
666 }
667 else
668 {
669 INT i;
670 BOOL pressed = FALSE;
671
672 /* any button pressed ? */
673 for (i = 0; i < infoPtr->uNumItem; i++)
674 if ((TAB_GetItem (infoPtr, i)->dwState & TCIS_BUTTONPRESSED) &&
675 (infoPtr->iSelected != i))
676 {
677 pressed = TRUE;
678 break;
679 }
680
681 TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGING);
682
683 if (pressed)
684 TAB_DeselectAll (infoPtr, FALSE);
685 else
686 TAB_SetCurSel(infoPtr, newItem);
687
688 TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGE);
689 }
690 }
691
692 return 0;
693 }
694
695 static inline LRESULT
696 TAB_LButtonUp (const TAB_INFO *infoPtr)
697 {
698 TAB_SendSimpleNotify(infoPtr, NM_CLICK);
699
700 return 0;
701 }
702
703 static inline LRESULT
704 TAB_RButtonDown (const TAB_INFO *infoPtr)
705 {
706 TAB_SendSimpleNotify(infoPtr, NM_RCLICK);
707 return 0;
708 }
709
710 /******************************************************************************
711 * TAB_DrawLoneItemInterior
712 *
713 * This calls TAB_DrawItemInterior. However, TAB_DrawItemInterior is normally
714 * called by TAB_DrawItem which is normally called by TAB_Refresh which sets
715 * up the device context and font. This routine does the same setup but
716 * only calls TAB_DrawItemInterior for the single specified item.
717 */
718 static void
719 TAB_DrawLoneItemInterior(const TAB_INFO* infoPtr, int iItem)
720 {
721 HDC hdc = GetDC(infoPtr->hwnd);
722 RECT r, rC;
723
724 /* Clip UpDown control to not draw over it */
725 if (infoPtr->needsScrolling)
726 {
727 GetWindowRect(infoPtr->hwnd, &rC);
728 GetWindowRect(infoPtr->hwndUpDown, &r);
729 ExcludeClipRect(hdc, r.left - rC.left, r.top - rC.top, r.right - rC.left, r.bottom - rC.top);
730 }
731 TAB_DrawItemInterior(infoPtr, hdc, iItem, NULL);
732 ReleaseDC(infoPtr->hwnd, hdc);
733 }
734
735 /* update a tab after hottracking - invalidate it or just redraw the interior,
736 * based on whether theming is used or not */
737 static inline void hottrack_refresh(const TAB_INFO *infoPtr, int tabIndex)
738 {
739 if (tabIndex == -1) return;
740
741 if (GetWindowTheme (infoPtr->hwnd))
742 {
743 RECT rect;
744 TAB_InternalGetItemRect(infoPtr, tabIndex, &rect, NULL);
745 InvalidateRect (infoPtr->hwnd, &rect, FALSE);
746 }
747 else
748 TAB_DrawLoneItemInterior(infoPtr, tabIndex);
749 }
750
751 /******************************************************************************
752 * TAB_HotTrackTimerProc
753 *
754 * When a mouse-move event causes a tab to be highlighted (hot-tracking), a
755 * timer is setup so we can check if the mouse is moved out of our window.
756 * (We don't get an event when the mouse leaves, the mouse-move events just
757 * stop being delivered to our window and just start being delivered to
758 * another window.) This function is called when the timer triggers so
759 * we can check if the mouse has left our window. If so, we un-highlight
760 * the hot-tracked tab.
761 */
762 static void CALLBACK
763 TAB_HotTrackTimerProc
764 (
765 HWND hwnd, /* handle of window for timer messages */
766 UINT uMsg, /* WM_TIMER message */
767 UINT_PTR idEvent, /* timer identifier */
768 DWORD dwTime /* current system time */
769 )
770 {
771 TAB_INFO* infoPtr = TAB_GetInfoPtr(hwnd);
772
773 if (infoPtr != NULL && infoPtr->iHotTracked >= 0)
774 {
775 POINT pt;
776
777 /*
778 ** If we can't get the cursor position, or if the cursor is outside our
779 ** window, we un-highlight the hot-tracked tab. Note that the cursor is
780 ** "outside" even if it is within our bounding rect if another window
781 ** overlaps. Note also that the case where the cursor stayed within our
782 ** window but has moved off the hot-tracked tab will be handled by the
783 ** WM_MOUSEMOVE event.
784 */
785 if (!GetCursorPos(&pt) || WindowFromPoint(pt) != hwnd)
786 {
787 /* Redraw iHotTracked to look normal */
788 INT iRedraw = infoPtr->iHotTracked;
789 infoPtr->iHotTracked = -1;
790 hottrack_refresh (infoPtr, iRedraw);
791
792 /* Kill this timer */
793 KillTimer(hwnd, TAB_HOTTRACK_TIMER);
794 }
795 }
796 }
797
798 /******************************************************************************
799 * TAB_RecalcHotTrack
800 *
801 * If a tab control has the TCS_HOTTRACK style, then the tab under the mouse
802 * should be highlighted. This function determines which tab in a tab control,
803 * if any, is under the mouse and records that information. The caller may
804 * supply output parameters to receive the item number of the tab item which
805 * was highlighted but isn't any longer and of the tab item which is now
806 * highlighted but wasn't previously. The caller can use this information to
807 * selectively redraw those tab items.
808 *
809 * If the caller has a mouse position, it can supply it through the pos
810 * parameter. For example, TAB_MouseMove does this. Otherwise, the caller
811 * supplies NULL and this function determines the current mouse position
812 * itself.
813 */
814 static void
815 TAB_RecalcHotTrack
816 (
817 TAB_INFO* infoPtr,
818 const LPARAM* pos,
819 int* out_redrawLeave,
820 int* out_redrawEnter
821 )
822 {
823 int item = -1;
824
825
826 if (out_redrawLeave != NULL)
827 *out_redrawLeave = -1;
828 if (out_redrawEnter != NULL)
829 *out_redrawEnter = -1;
830
831 if ((infoPtr->dwStyle & TCS_HOTTRACK) || GetWindowTheme(infoPtr->hwnd))
832 {
833 POINT pt;
834 UINT flags;
835
836 if (pos == NULL)
837 {
838 GetCursorPos(&pt);
839 ScreenToClient(infoPtr->hwnd, &pt);
840 }
841 else
842 {
843 pt.x = (short)LOWORD(*pos);
844 pt.y = (short)HIWORD(*pos);
845 }
846
847 item = TAB_InternalHitTest(infoPtr, pt, &flags);
848 }
849
850 if (item != infoPtr->iHotTracked)
851 {
852 if (infoPtr->iHotTracked >= 0)
853 {
854 /* Mark currently hot-tracked to be redrawn to look normal */
855 if (out_redrawLeave != NULL)
856 *out_redrawLeave = infoPtr->iHotTracked;
857
858 if (item < 0)
859 {
860 /* Kill timer which forces recheck of mouse pos */
861 KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
862 }
863 }
864 else
865 {
866 /* Start timer so we recheck mouse pos */
867 UINT timerID = SetTimer
868 (
869 infoPtr->hwnd,
870 TAB_HOTTRACK_TIMER,
871 TAB_HOTTRACK_TIMER_INTERVAL,
872 TAB_HotTrackTimerProc
873 );
874
875 if (timerID == 0)
876 return; /* Hot tracking not available */
877 }
878
879 infoPtr->iHotTracked = item;
880
881 if (item >= 0)
882 {
883 /* Mark new hot-tracked to be redrawn to look highlighted */
884 if (out_redrawEnter != NULL)
885 *out_redrawEnter = item;
886 }
887 }
888 }
889
890 /******************************************************************************
891 * TAB_MouseMove
892 *
893 * Handles the mouse-move event. Updates tooltips. Updates hot-tracking.
894 */
895 static LRESULT
896 TAB_MouseMove (TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
897 {
898 int redrawLeave;
899 int redrawEnter;
900
901 if (infoPtr->hwndToolTip)
902 TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
903 WM_LBUTTONDOWN, wParam, lParam);
904
905 /* Determine which tab to highlight. Redraw tabs which change highlight
906 ** status. */
907 TAB_RecalcHotTrack(infoPtr, &lParam, &redrawLeave, &redrawEnter);
908
909 hottrack_refresh (infoPtr, redrawLeave);
910 hottrack_refresh (infoPtr, redrawEnter);
911
912 return 0;
913 }
914
915 /******************************************************************************
916 * TAB_AdjustRect
917 *
918 * Calculates the tab control's display area given the window rectangle or
919 * the window rectangle given the requested display rectangle.
920 */
921 static LRESULT TAB_AdjustRect(const TAB_INFO *infoPtr, WPARAM fLarger, LPRECT prc)
922 {
923 LONG *iRightBottom, *iLeftTop;
924
925 TRACE ("hwnd=%p fLarger=%ld (%s)\n", infoPtr->hwnd, fLarger,
926 wine_dbgstr_rect(prc));
927
928 if (!prc) return -1;
929
930 if(infoPtr->dwStyle & TCS_VERTICAL)
931 {
932 iRightBottom = &(prc->right);
933 iLeftTop = &(prc->left);
934 }
935 else
936 {
937 iRightBottom = &(prc->bottom);
938 iLeftTop = &(prc->top);
939 }
940
941 if (fLarger) /* Go from display rectangle */
942 {
943 /* Add the height of the tabs. */
944 if (infoPtr->dwStyle & TCS_BOTTOM)
945 *iRightBottom += infoPtr->tabHeight * infoPtr->uNumRows;
946 else
947 *iLeftTop -= infoPtr->tabHeight * infoPtr->uNumRows +
948 ((infoPtr->dwStyle & TCS_BUTTONS)? 3 * (infoPtr->uNumRows - 1) : 0);
949
950 /* Inflate the rectangle for the padding */
951 InflateRect(prc, DISPLAY_AREA_PADDINGX, DISPLAY_AREA_PADDINGY);
952
953 /* Inflate for the border */
954 InflateRect(prc, CONTROL_BORDER_SIZEX, CONTROL_BORDER_SIZEY);
955 }
956 else /* Go from window rectangle. */
957 {
958 /* Deflate the rectangle for the border */
959 InflateRect(prc, -CONTROL_BORDER_SIZEX, -CONTROL_BORDER_SIZEY);
960
961 /* Deflate the rectangle for the padding */
962 InflateRect(prc, -DISPLAY_AREA_PADDINGX, -DISPLAY_AREA_PADDINGY);
963
964 /* Remove the height of the tabs. */
965 if (infoPtr->dwStyle & TCS_BOTTOM)
966 *iRightBottom -= infoPtr->tabHeight * infoPtr->uNumRows;
967 else
968 *iLeftTop += (infoPtr->tabHeight) * infoPtr->uNumRows +
969 ((infoPtr->dwStyle & TCS_BUTTONS)? 3 * (infoPtr->uNumRows - 1) : 0);
970 }
971
972 return 0;
973 }
974
975 /******************************************************************************
976 * TAB_OnHScroll
977 *
978 * This method will handle the notification from the scroll control and
979 * perform the scrolling operation on the tab control.
980 */
981 static LRESULT TAB_OnHScroll(TAB_INFO *infoPtr, int nScrollCode, int nPos)
982 {
983 if(nScrollCode == SB_THUMBPOSITION && nPos != infoPtr->leftmostVisible)
984 {
985 if(nPos < infoPtr->leftmostVisible)
986 infoPtr->leftmostVisible--;
987 else
988 infoPtr->leftmostVisible++;
989
990 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
991 TAB_InvalidateTabArea(infoPtr);
992 SendMessageW(infoPtr->hwndUpDown, UDM_SETPOS, 0,
993 MAKELONG(infoPtr->leftmostVisible, 0));
994 }
995
996 return 0;
997 }
998
999 /******************************************************************************
1000 * TAB_SetupScrolling
1001 *
1002 * This method will check the current scrolling state and make sure the
1003 * scrolling control is displayed (or not).
1004 */
1005 static void TAB_SetupScrolling(
1006 TAB_INFO* infoPtr,
1007 const RECT* clientRect)
1008 {
1009 static const WCHAR emptyW[] = { 0 };
1010 INT maxRange = 0;
1011
1012 if (infoPtr->needsScrolling)
1013 {
1014 RECT controlPos;
1015 INT vsize, tabwidth;
1016
1017 /*
1018 * Calculate the position of the scroll control.
1019 */
1020 if(infoPtr->dwStyle & TCS_VERTICAL)
1021 {
1022 controlPos.right = clientRect->right;
1023 controlPos.left = controlPos.right - 2 * GetSystemMetrics(SM_CXHSCROLL);
1024
1025 if (infoPtr->dwStyle & TCS_BOTTOM)
1026 {
1027 controlPos.top = clientRect->bottom - infoPtr->tabHeight;
1028 controlPos.bottom = controlPos.top + GetSystemMetrics(SM_CYHSCROLL);
1029 }
1030 else
1031 {
1032 controlPos.bottom = clientRect->top + infoPtr->tabHeight;
1033 controlPos.top = controlPos.bottom - GetSystemMetrics(SM_CYHSCROLL);
1034 }
1035 }
1036 else
1037 {
1038 controlPos.right = clientRect->right;
1039 controlPos.left = controlPos.right - 2 * GetSystemMetrics(SM_CXHSCROLL);
1040
1041 if (infoPtr->dwStyle & TCS_BOTTOM)
1042 {
1043 controlPos.top = clientRect->bottom - infoPtr->tabHeight;
1044 controlPos.bottom = controlPos.top + GetSystemMetrics(SM_CYHSCROLL);
1045 }
1046 else
1047 {
1048 controlPos.bottom = clientRect->top + infoPtr->tabHeight;
1049 controlPos.top = controlPos.bottom - GetSystemMetrics(SM_CYHSCROLL);
1050 }
1051 }
1052
1053 /*
1054 * If we don't have a scroll control yet, we want to create one.
1055 * If we have one, we want to make sure it's positioned properly.
1056 */
1057 if (infoPtr->hwndUpDown==0)
1058 {
1059 infoPtr->hwndUpDown = CreateWindowW(UPDOWN_CLASSW, emptyW,
1060 WS_VISIBLE | WS_CHILD | UDS_HORZ,
1061 controlPos.left, controlPos.top,
1062 controlPos.right - controlPos.left,
1063 controlPos.bottom - controlPos.top,
1064 infoPtr->hwnd, NULL, NULL, NULL);
1065 }
1066 else
1067 {
1068 SetWindowPos(infoPtr->hwndUpDown,
1069 NULL,
1070 controlPos.left, controlPos.top,
1071 controlPos.right - controlPos.left,
1072 controlPos.bottom - controlPos.top,
1073 SWP_SHOWWINDOW | SWP_NOZORDER);
1074 }
1075
1076 /* Now calculate upper limit of the updown control range.
1077 * We do this by calculating how many tabs will be offscreen when the
1078 * last tab is visible.
1079 */
1080 if(infoPtr->uNumItem)
1081 {
1082 vsize = clientRect->right - (controlPos.right - controlPos.left + 1);
1083 maxRange = infoPtr->uNumItem;
1084 tabwidth = TAB_GetItem(infoPtr, infoPtr->uNumItem - 1)->rect.right;
1085
1086 for(; maxRange > 0; maxRange--)
1087 {
1088 if(tabwidth - TAB_GetItem(infoPtr,maxRange - 1)->rect.left > vsize)
1089 break;
1090 }
1091
1092 if(maxRange == infoPtr->uNumItem)
1093 maxRange--;
1094 }
1095 }
1096 else
1097 {
1098 /* If we once had a scroll control... hide it */
1099 if (infoPtr->hwndUpDown)
1100 ShowWindow(infoPtr->hwndUpDown, SW_HIDE);
1101 }
1102 if (infoPtr->hwndUpDown)
1103 SendMessageW(infoPtr->hwndUpDown, UDM_SETRANGE32, 0, maxRange);
1104 }
1105
1106 /******************************************************************************
1107 * TAB_SetItemBounds
1108 *
1109 * This method will calculate the position rectangles of all the items in the
1110 * control. The rectangle calculated starts at 0 for the first item in the
1111 * list and ignores scrolling and selection.
1112 * It also uses the current font to determine the height of the tab row and
1113 * it checks if all the tabs fit in the client area of the window. If they
1114 * don't, a scrolling control is added.
1115 */
1116 static void TAB_SetItemBounds (TAB_INFO *infoPtr)
1117 {
1118 TEXTMETRICW fontMetrics;
1119 UINT curItem;
1120 INT curItemLeftPos;
1121 INT curItemRowCount;
1122 HFONT hFont, hOldFont;
1123 HDC hdc;
1124 RECT clientRect;
1125 INT iTemp;
1126 RECT* rcItem;
1127 INT iIndex;
1128 INT icon_width = 0;
1129
1130 /*
1131 * We need to get text information so we need a DC and we need to select
1132 * a font.
1133 */
1134 hdc = GetDC(infoPtr->hwnd);
1135
1136 hFont = infoPtr->hFont ? infoPtr->hFont : GetStockObject (SYSTEM_FONT);
1137 hOldFont = SelectObject (hdc, hFont);
1138
1139 /*
1140 * We will base the rectangle calculations on the client rectangle
1141 * of the control.
1142 */
1143 GetClientRect(infoPtr->hwnd, &clientRect);
1144
1145 /* if TCS_VERTICAL then swap the height and width so this code places the
1146 tabs along the top of the rectangle and we can just rotate them after
1147 rather than duplicate all of the below code */
1148 if(infoPtr->dwStyle & TCS_VERTICAL)
1149 {
1150 iTemp = clientRect.bottom;
1151 clientRect.bottom = clientRect.right;
1152 clientRect.right = iTemp;
1153 }
1154
1155 /* Now use hPadding and vPadding */
1156 infoPtr->uHItemPadding = infoPtr->uHItemPadding_s;
1157 infoPtr->uVItemPadding = infoPtr->uVItemPadding_s;
1158
1159 /* The leftmost item will be "0" aligned */
1160 curItemLeftPos = 0;
1161 curItemRowCount = infoPtr->uNumItem ? 1 : 0;
1162
1163 if (!(infoPtr->fHeightSet))
1164 {
1165 int item_height;
1166 int icon_height = 0;
1167
1168 /* Use the current font to determine the height of a tab. */
1169 GetTextMetricsW(hdc, &fontMetrics);
1170
1171 /* Get the icon height */
1172 if (infoPtr->himl)
1173 ImageList_GetIconSize(infoPtr->himl, 0, &icon_height);
1174
1175 /* Take the highest between font or icon */
1176 if (fontMetrics.tmHeight > icon_height)
1177 item_height = fontMetrics.tmHeight + 2;
1178 else
1179 item_height = icon_height;
1180
1181 /*
1182 * Make sure there is enough space for the letters + icon + growing the
1183 * selected item + extra space for the selected item.
1184 */
1185 infoPtr->tabHeight = item_height +
1186 ((infoPtr->dwStyle & TCS_BUTTONS) ? 2 : 1) *
1187 infoPtr->uVItemPadding;
1188
1189 TRACE("tabH=%d, tmH=%d, iconh=%d\n",
1190 infoPtr->tabHeight, fontMetrics.tmHeight, icon_height);
1191 }
1192
1193 TRACE("client right=%d\n", clientRect.right);
1194
1195 /* Get the icon width */
1196 if (infoPtr->himl)
1197 {
1198 ImageList_GetIconSize(infoPtr->himl, &icon_width, 0);
1199
1200 if (infoPtr->dwStyle & TCS_FIXEDWIDTH)
1201 icon_width += 4;
1202 else
1203 /* Add padding if icon is present */
1204 icon_width += infoPtr->uHItemPadding;
1205 }
1206
1207 for (curItem = 0; curItem < infoPtr->uNumItem; curItem++)
1208 {
1209 TAB_ITEM *curr = TAB_GetItem(infoPtr, curItem);
1210
1211 /* Set the leftmost position of the tab. */
1212 curr->rect.left = curItemLeftPos;
1213
1214 if (infoPtr->dwStyle & TCS_FIXEDWIDTH)
1215 {
1216 curr->rect.right = curr->rect.left +
1217 max(infoPtr->tabWidth, icon_width);
1218 }
1219 else if (!curr->pszText)
1220 {
1221 /* If no text use minimum tab width including padding. */
1222 if (infoPtr->tabMinWidth < 0)
1223 curr->rect.right = curr->rect.left + GET_DEFAULT_MIN_TAB_WIDTH(infoPtr);
1224 else
1225 {
1226 curr->rect.right = curr->rect.left + infoPtr->tabMinWidth;
1227
1228 /* Add extra padding if icon is present */
1229 if (infoPtr->himl && infoPtr->tabMinWidth > 0 && infoPtr->tabMinWidth < DEFAULT_MIN_TAB_WIDTH
1230 && infoPtr->uHItemPadding > 1)
1231 curr->rect.right += EXTRA_ICON_PADDING * (infoPtr->uHItemPadding-1);
1232 }
1233 }
1234 else
1235 {
1236 int tabwidth;
1237 SIZE size;
1238 /* Calculate how wide the tab is depending on the text it contains */
1239 GetTextExtentPoint32W(hdc, curr->pszText,
1240 lstrlenW(curr->pszText), &size);
1241
1242 tabwidth = size.cx + icon_width + 2 * infoPtr->uHItemPadding;
1243
1244 if (infoPtr->tabMinWidth < 0)
1245 tabwidth = max(tabwidth, GET_DEFAULT_MIN_TAB_WIDTH(infoPtr));
1246 else
1247 tabwidth = max(tabwidth, infoPtr->tabMinWidth);
1248
1249 curr->rect.right = curr->rect.left + tabwidth;
1250 TRACE("for <%s>, l,r=%d,%d\n",
1251 debugstr_w(curr->pszText), curr->rect.left, curr->rect.right);
1252 }
1253
1254 /*
1255 * Check if this is a multiline tab control and if so
1256 * check to see if we should wrap the tabs
1257 *
1258 * Wrap all these tabs. We will arrange them evenly later.
1259 *
1260 */
1261
1262 if (((infoPtr->dwStyle & TCS_MULTILINE) || (infoPtr->dwStyle & TCS_VERTICAL)) &&
1263 (curr->rect.right >
1264 (clientRect.right - CONTROL_BORDER_SIZEX - DISPLAY_AREA_PADDINGX)))
1265 {
1266 curr->rect.right -= curr->rect.left;
1267
1268 curr->rect.left = 0;
1269 curItemRowCount++;
1270 TRACE("wrapping <%s>, l,r=%d,%d\n", debugstr_w(curr->pszText),
1271 curr->rect.left, curr->rect.right);
1272 }
1273
1274 curr->rect.bottom = 0;
1275 curr->rect.top = curItemRowCount - 1;
1276
1277 TRACE("Rect: %s\n", wine_dbgstr_rect(&curr->rect));
1278
1279 /*
1280 * The leftmost position of the next item is the rightmost position
1281 * of this one.
1282 */
1283 if (infoPtr->dwStyle & TCS_BUTTONS)
1284 {
1285 curItemLeftPos = curr->rect.right + BUTTON_SPACINGX;
1286 if (infoPtr->dwStyle & TCS_FLATBUTTONS)
1287 curItemLeftPos += FLAT_BTN_SPACINGX;
1288 }
1289 else
1290 curItemLeftPos = curr->rect.right;
1291 }
1292
1293 if (!((infoPtr->dwStyle & TCS_MULTILINE) || (infoPtr->dwStyle & TCS_VERTICAL)))
1294 {
1295 /*
1296 * Check if we need a scrolling control.
1297 */
1298 infoPtr->needsScrolling = (curItemLeftPos + (2 * SELECTED_TAB_OFFSET) >
1299 clientRect.right);
1300
1301 /* Don't need scrolling, then update infoPtr->leftmostVisible */
1302 if(!infoPtr->needsScrolling)
1303 infoPtr->leftmostVisible = 0;
1304 }
1305 else
1306 {
1307 /*
1308 * No scrolling in Multiline or Vertical styles.
1309 */
1310 infoPtr->needsScrolling = FALSE;
1311 infoPtr->leftmostVisible = 0;
1312 }
1313 TAB_SetupScrolling(infoPtr, &clientRect);
1314
1315 /* Set the number of rows */
1316 infoPtr->uNumRows = curItemRowCount;
1317
1318 /* Arrange all tabs evenly if style says so */
1319 if (!(infoPtr->dwStyle & TCS_RAGGEDRIGHT) &&
1320 ((infoPtr->dwStyle & TCS_MULTILINE) || (infoPtr->dwStyle & TCS_VERTICAL)) &&
1321 (infoPtr->uNumItem > 0) &&
1322 (infoPtr->uNumRows > 1))
1323 {
1324 INT tabPerRow,remTab,iRow;
1325 UINT iItm;
1326 INT iCount=0;
1327
1328 /*
1329 * Ok windows tries to even out the rows. place the same
1330 * number of tabs in each row. So lets give that a shot
1331 */
1332
1333 tabPerRow = infoPtr->uNumItem / (infoPtr->uNumRows);
1334 remTab = infoPtr->uNumItem % (infoPtr->uNumRows);
1335
1336 for (iItm=0,iRow=0,iCount=0,curItemLeftPos=0;
1337 iItm<infoPtr->uNumItem;
1338 iItm++,iCount++)
1339 {
1340 /* normalize the current rect */
1341 TAB_ITEM *curr = TAB_GetItem(infoPtr, iItm);
1342
1343 /* shift the item to the left side of the clientRect */
1344 curr->rect.right -= curr->rect.left;
1345 curr->rect.left = 0;
1346
1347 TRACE("r=%d, cl=%d, cl.r=%d, iCount=%d, iRow=%d, uNumRows=%d, remTab=%d, tabPerRow=%d\n",
1348 curr->rect.right, curItemLeftPos, clientRect.right,
1349 iCount, iRow, infoPtr->uNumRows, remTab, tabPerRow);
1350
1351 /* if we have reached the maximum number of tabs on this row */
1352 /* move to the next row, reset our current item left position and */
1353 /* the count of items on this row */
1354
1355 if (infoPtr->dwStyle & TCS_VERTICAL) {
1356 /* Vert: Add the remaining tabs in the *last* remainder rows */
1357 if (iCount >= ((iRow>=(INT)infoPtr->uNumRows - remTab)?tabPerRow + 1:tabPerRow)) {
1358 iRow++;
1359 curItemLeftPos = 0;
1360 iCount = 0;
1361 }
1362 } else {
1363 /* Horz: Add the remaining tabs in the *first* remainder rows */
1364 if (iCount >= ((iRow<remTab)?tabPerRow + 1:tabPerRow)) {
1365 iRow++;
1366 curItemLeftPos = 0;
1367 iCount = 0;
1368 }
1369 }
1370
1371 /* shift the item to the right to place it as the next item in this row */
1372 curr->rect.left += curItemLeftPos;
1373 curr->rect.right += curItemLeftPos;
1374 curr->rect.top = iRow;
1375 if (infoPtr->dwStyle & TCS_BUTTONS)
1376 {
1377 curItemLeftPos = curr->rect.right + 1;
1378 if (infoPtr->dwStyle & TCS_FLATBUTTONS)
1379 curItemLeftPos += FLAT_BTN_SPACINGX;
1380 }
1381 else
1382 curItemLeftPos = curr->rect.right;
1383
1384 TRACE("arranging <%s>, l,r=%d,%d, row=%d\n",
1385 debugstr_w(curr->pszText), curr->rect.left,
1386 curr->rect.right, curr->rect.top);
1387 }
1388
1389 /*
1390 * Justify the rows
1391 */
1392 {
1393 INT widthDiff, iIndexStart=0, iIndexEnd=0;
1394 INT remainder;
1395 INT iCount=0;
1396
1397 while(iIndexStart < infoPtr->uNumItem)
1398 {
1399 TAB_ITEM *start = TAB_GetItem(infoPtr, iIndexStart);
1400
1401 /*
1402 * find the index of the row
1403 */
1404 /* find the first item on the next row */
1405 for (iIndexEnd=iIndexStart;
1406 (iIndexEnd < infoPtr->uNumItem) &&
1407 (TAB_GetItem(infoPtr, iIndexEnd)->rect.top ==
1408 start->rect.top) ;
1409 iIndexEnd++)
1410 /* intentionally blank */;
1411
1412 /*
1413 * we need to justify these tabs so they fill the whole given
1414 * client area
1415 *
1416 */
1417 /* find the amount of space remaining on this row */
1418 widthDiff = clientRect.right - (2 * SELECTED_TAB_OFFSET) -
1419 TAB_GetItem(infoPtr, iIndexEnd - 1)->rect.right;
1420
1421 /* iCount is the number of tab items on this row */
1422 iCount = iIndexEnd - iIndexStart;
1423
1424 if (iCount > 1)
1425 {
1426 remainder = widthDiff % iCount;
1427 widthDiff = widthDiff / iCount;
1428 /* add widthDiff/iCount, or extra space/items on row, to each item on this row */
1429 for (iIndex=iIndexStart, iCount=0; iIndex < iIndexEnd; iIndex++, iCount++)
1430 {
1431 TAB_ITEM *item = TAB_GetItem(infoPtr, iIndex);
1432
1433 item->rect.left += iCount * widthDiff;
1434 item->rect.right += (iCount + 1) * widthDiff;
1435
1436 TRACE("adjusting 1 <%s>, l,r=%d,%d\n",
1437 debugstr_w(item->pszText),
1438 item->rect.left, item->rect.right);
1439
1440 }
1441 TAB_GetItem(infoPtr, iIndex - 1)->rect.right += remainder;
1442 }
1443 else /* we have only one item on this row, make it take up the entire row */
1444 {
1445 start->rect.left = clientRect.left;
1446 start->rect.right = clientRect.right - 4;
1447
1448 TRACE("adjusting 2 <%s>, l,r=%d,%d\n",
1449 debugstr_w(start->pszText),
1450 start->rect.left, start->rect.right);
1451
1452 }
1453
1454
1455 iIndexStart = iIndexEnd;
1456 }
1457 }
1458 }
1459
1460 /* if TCS_VERTICAL rotate the tabs so they are along the side of the clientRect */
1461 if(infoPtr->dwStyle & TCS_VERTICAL)
1462 {
1463 RECT rcOriginal;
1464 for(iIndex = 0; iIndex < infoPtr->uNumItem; iIndex++)
1465 {
1466 rcItem = &TAB_GetItem(infoPtr, iIndex)->rect;
1467
1468 rcOriginal = *rcItem;
1469
1470 /* this is rotating the items by 90 degrees clockwise around the center of the control */
1471 rcItem->top = (rcOriginal.left - clientRect.left);
1472 rcItem->bottom = rcItem->top + (rcOriginal.right - rcOriginal.left);
1473 rcItem->left = rcOriginal.top;
1474 rcItem->right = rcOriginal.bottom;
1475 }
1476 }
1477
1478 TAB_EnsureSelectionVisible(infoPtr);
1479 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
1480
1481 /* Cleanup */
1482 SelectObject (hdc, hOldFont);
1483 ReleaseDC (infoPtr->hwnd, hdc);
1484 }
1485
1486
1487 static void
1488 TAB_EraseTabInterior(const TAB_INFO *infoPtr, HDC hdc, INT iItem, const RECT *drawRect)
1489 {
1490 HBRUSH hbr = CreateSolidBrush (comctl32_color.clrBtnFace);
1491 BOOL deleteBrush = TRUE;
1492 RECT rTemp = *drawRect;
1493
1494 if (infoPtr->dwStyle & TCS_BUTTONS)
1495 {
1496 if (iItem == infoPtr->iSelected)
1497 {
1498 /* Background color */
1499 if (!(infoPtr->dwStyle & TCS_OWNERDRAWFIXED))
1500 {
1501 DeleteObject(hbr);
1502 hbr = GetSysColorBrush(COLOR_SCROLLBAR);
1503
1504 SetTextColor(hdc, comctl32_color.clr3dFace);
1505 SetBkColor(hdc, comctl32_color.clr3dHilight);
1506
1507 /* if COLOR_WINDOW happens to be the same as COLOR_3DHILIGHT
1508 * we better use 0x55aa bitmap brush to make scrollbar's background
1509 * look different from the window background.
1510 */
1511 if (comctl32_color.clr3dHilight == comctl32_color.clrWindow)
1512 hbr = COMCTL32_hPattern55AABrush;
1513
1514 deleteBrush = FALSE;
1515 }
1516 FillRect(hdc, &rTemp, hbr);
1517 }
1518 else /* ! selected */
1519 {
1520 if (infoPtr->dwStyle & TCS_FLATBUTTONS)
1521 {
1522 InflateRect(&rTemp, 2, 2);
1523 FillRect(hdc, &rTemp, hbr);
1524 if (iItem == infoPtr->iHotTracked ||
1525 (iItem != infoPtr->iSelected && iItem == infoPtr->uFocus))
1526 DrawEdge(hdc, &rTemp, BDR_RAISEDINNER, BF_RECT);
1527 }
1528 else
1529 FillRect(hdc, &rTemp, hbr);
1530 }
1531
1532 }
1533 else /* !TCS_BUTTONS */
1534 {
1535 InflateRect(&rTemp, -2, -2);
1536 if (!GetWindowTheme (infoPtr->hwnd))
1537 FillRect(hdc, &rTemp, hbr);
1538 }
1539
1540 /* highlighting is drawn on top of previous fills */
1541 if (TAB_GetItem(infoPtr, iItem)->dwState & TCIS_HIGHLIGHTED)
1542 {
1543 if (deleteBrush)
1544 {
1545 DeleteObject(hbr);
1546 deleteBrush = FALSE;
1547 }
1548 hbr = GetSysColorBrush(COLOR_HIGHLIGHT);
1549 FillRect(hdc, &rTemp, hbr);
1550 }
1551
1552 /* Cleanup */
1553 if (deleteBrush) DeleteObject(hbr);
1554 }
1555
1556 /******************************************************************************
1557 * TAB_DrawItemInterior
1558 *
1559 * This method is used to draw the interior (text and icon) of a single tab
1560 * into the tab control.
1561 */
1562 static void
1563 TAB_DrawItemInterior(const TAB_INFO *infoPtr, HDC hdc, INT iItem, RECT *drawRect)
1564 {
1565 RECT localRect;
1566
1567 HPEN htextPen;
1568 HPEN holdPen;
1569 INT oldBkMode;
1570 HFONT hOldFont;
1571
1572 /* if (drawRect == NULL) */
1573 {
1574 BOOL isVisible;
1575 RECT itemRect;
1576 RECT selectedRect;
1577
1578 /*
1579 * Get the rectangle for the item.
1580 */
1581 isVisible = TAB_InternalGetItemRect(infoPtr, iItem, &itemRect, &selectedRect);
1582 if (!isVisible)
1583 return;
1584
1585 /*
1586 * Make sure drawRect points to something valid; simplifies code.
1587 */
1588 drawRect = &localRect;
1589
1590 /*
1591 * This logic copied from the part of TAB_DrawItem which draws
1592 * the tab background. It's important to keep it in sync. I
1593 * would have liked to avoid code duplication, but couldn't figure
1594 * out how without making spaghetti of TAB_DrawItem.
1595 */
1596 if (iItem == infoPtr->iSelected)
1597 *drawRect = selectedRect;
1598 else
1599 *drawRect = itemRect;
1600
1601 if (infoPtr->dwStyle & TCS_BUTTONS)
1602 {
1603 if (iItem == infoPtr->iSelected)
1604 {
1605 drawRect->left += 4;
1606 drawRect->top += 4;
1607 drawRect->right -= 4;
1608
1609 if (infoPtr->dwStyle & TCS_VERTICAL)
1610 {
1611 if (!(infoPtr->dwStyle & TCS_BOTTOM)) drawRect->right += 1;
1612 drawRect->bottom -= 4;
1613 }
1614 else
1615 {
1616 if (infoPtr->dwStyle & TCS_BOTTOM)
1617 {
1618 drawRect->top -= 2;
1619 drawRect->bottom -= 4;
1620 }
1621 else
1622 drawRect->bottom -= 1;
1623 }
1624 }
1625 else
1626 {
1627 drawRect->left += 2;
1628 drawRect->top += 2;
1629 drawRect->right -= 2;
1630 drawRect->bottom -= 2;
1631 }
1632 }
1633 else
1634 {
1635 if ((infoPtr->dwStyle & TCS_VERTICAL) && (infoPtr->dwStyle & TCS_BOTTOM))
1636 {
1637 if (iItem != infoPtr->iSelected)
1638 {
1639 drawRect->left += 2;
1640 drawRect->top += 2;
1641 drawRect->bottom -= 2;
1642 }
1643 }
1644 else if (infoPtr->dwStyle & TCS_VERTICAL)
1645 {
1646 if (iItem == infoPtr->iSelected)
1647 {
1648 drawRect->right += 1;
1649 }
1650 else
1651 {
1652 drawRect->top += 2;
1653 drawRect->right -= 2;
1654 drawRect->bottom -= 2;
1655 }
1656 }
1657 else if (infoPtr->dwStyle & TCS_BOTTOM)
1658 {
1659 if (iItem == infoPtr->iSelected)
1660 {
1661 drawRect->top -= 2;
1662 }
1663 else
1664 {
1665 InflateRect(drawRect, -2, -2);
1666 drawRect->bottom += 2;
1667 }
1668 }
1669 else
1670 {
1671 if (iItem == infoPtr->iSelected)
1672 {
1673 drawRect->bottom += 3;
1674 }
1675 else
1676 {
1677 drawRect->bottom -= 2;
1678 InflateRect(drawRect, -2, 0);
1679 }
1680 }
1681 }
1682 }
1683 TRACE("drawRect=(%s)\n", wine_dbgstr_rect(drawRect));
1684
1685 /* Clear interior */
1686 TAB_EraseTabInterior (infoPtr, hdc, iItem, drawRect);
1687
1688 /* Draw the focus rectangle */
1689 if (!(infoPtr->dwStyle & TCS_FOCUSNEVER) &&
1690 (GetFocus() == infoPtr->hwnd) &&
1691 (iItem == infoPtr->uFocus) )
1692 {
1693 RECT rFocus = *drawRect;
1694
1695 if (!(infoPtr->dwStyle & TCS_BUTTONS)) InflateRect(&rFocus, -3, -3);
1696 if (infoPtr->dwStyle & TCS_BOTTOM && !(infoPtr->dwStyle & TCS_VERTICAL))
1697 rFocus.top -= 3;
1698
1699 /* focus should stay on selected item for TCS_BUTTONS style */
1700 if (!((infoPtr->dwStyle & TCS_BUTTONS) && (infoPtr->iSelected != iItem)))
1701 DrawFocusRect(hdc, &rFocus);
1702 }
1703
1704 /*
1705 * Text pen
1706 */
1707 htextPen = CreatePen( PS_SOLID, 1, comctl32_color.clrBtnText );
1708 holdPen = SelectObject(hdc, htextPen);
1709 hOldFont = SelectObject(hdc, infoPtr->hFont);
1710
1711 /*
1712 * Setup for text output
1713 */
1714 oldBkMode = SetBkMode(hdc, TRANSPARENT);
1715 if (!GetWindowTheme (infoPtr->hwnd) || (infoPtr->dwStyle & TCS_BUTTONS))
1716 {
1717 if ((infoPtr->dwStyle & TCS_HOTTRACK) && (iItem == infoPtr->iHotTracked) &&
1718 !(infoPtr->dwStyle & TCS_FLATBUTTONS))
1719 SetTextColor(hdc, comctl32_color.clrHighlight);
1720 else if (TAB_GetItem(infoPtr, iItem)->dwState & TCIS_HIGHLIGHTED)
1721 SetTextColor(hdc, comctl32_color.clrHighlightText);
1722 else
1723 SetTextColor(hdc, comctl32_color.clrBtnText);
1724 }
1725
1726 /*
1727 * if owner draw, tell the owner to draw
1728 */
1729 if ((infoPtr->dwStyle & TCS_OWNERDRAWFIXED) && GetParent(infoPtr->hwnd))
1730 {
1731 DRAWITEMSTRUCT dis;
1732 UINT id;
1733
1734 drawRect->top += 2;
1735 drawRect->right -= 1;
1736 if ( iItem == infoPtr->iSelected )
1737 {
1738 drawRect->right -= 1;
1739 drawRect->left += 1;
1740 }
1741
1742 /*
1743 * get the control id
1744 */
1745 id = (UINT)GetWindowLongPtrW( infoPtr->hwnd, GWLP_ID );
1746
1747 /*
1748 * put together the DRAWITEMSTRUCT
1749 */
1750 dis.CtlType = ODT_TAB;
1751 dis.CtlID = id;
1752 dis.itemID = iItem;
1753 dis.itemAction = ODA_DRAWENTIRE;
1754 dis.itemState = 0;
1755 if ( iItem == infoPtr->iSelected )
1756 dis.itemState |= ODS_SELECTED;
1757 if (infoPtr->uFocus == iItem)
1758 dis.itemState |= ODS_FOCUS;
1759 dis.hwndItem = infoPtr->hwnd;
1760 dis.hDC = hdc;
1761 CopyRect(&dis.rcItem,drawRect);
1762 dis.itemData = (ULONG_PTR)TAB_GetItem(infoPtr, iItem)->extra;
1763
1764 /*
1765 * send the draw message
1766 */
1767 SendMessageW( infoPtr->hwndNotify, WM_DRAWITEM, id, (LPARAM)&dis );
1768 }
1769 else
1770 {
1771 TAB_ITEM *item = TAB_GetItem(infoPtr, iItem);
1772 RECT rcTemp;
1773 RECT rcImage;
1774
1775 /* used to center the icon and text in the tab */
1776 RECT rcText;
1777 INT center_offset_h, center_offset_v;
1778
1779 /* set rcImage to drawRect, we will use top & left in our ImageList_Draw call */
1780 rcImage = *drawRect;
1781
1782 rcTemp = *drawRect;
1783
1784 rcText.left = rcText.top = rcText.right = rcText.bottom = 0;
1785
1786 /* get the rectangle that the text fits in */
1787 if (item->pszText)
1788 {
1789 DrawTextW(hdc, item->pszText, -1, &rcText, DT_CALCRECT);
1790 }
1791 /*
1792 * If not owner draw, then do the drawing ourselves.
1793 *
1794 * Draw the icon.
1795 */
1796 if (infoPtr->himl && item->iImage != -1)
1797 {
1798 INT cx;
1799 INT cy;
1800
1801 ImageList_GetIconSize(infoPtr->himl, &cx, &cy);
1802
1803 if(infoPtr->dwStyle & TCS_VERTICAL)
1804 {
1805 center_offset_h = ((drawRect->bottom - drawRect->top) - (cy + infoPtr->uHItemPadding + (rcText.right - rcText.left))) / 2;
1806 center_offset_v = ((drawRect->right - drawRect->left) - cx) / 2;
1807 }
1808 else
1809 {
1810 center_offset_h = ((drawRect->right - drawRect->left) - (cx + infoPtr->uHItemPadding + (rcText.right - rcText.left))) / 2;
1811 center_offset_v = ((drawRect->bottom - drawRect->top) - cy) / 2;
1812 }
1813
1814 /* if an item is selected, the icon is shifted up instead of down */
1815 if (iItem == infoPtr->iSelected)
1816 center_offset_v -= infoPtr->uVItemPadding / 2;
1817 else
1818 center_offset_v += infoPtr->uVItemPadding / 2;
1819
1820 if (infoPtr->dwStyle & TCS_FIXEDWIDTH && infoPtr->dwStyle & (TCS_FORCELABELLEFT | TCS_FORCEICONLEFT))
1821 center_offset_h = infoPtr->uHItemPadding;
1822
1823 if (center_offset_h < 2)
1824 center_offset_h = 2;
1825
1826 if (center_offset_v < 0)
1827 center_offset_v = 0;
1828
1829 TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%s), textlen=%d\n",
1830 debugstr_w(item->pszText), center_offset_h, center_offset_v,
1831 wine_dbgstr_rect(drawRect), (rcText.right-rcText.left));
1832
1833 if((infoPtr->dwStyle & TCS_VERTICAL) && (infoPtr->dwStyle & TCS_BOTTOM))
1834 {
1835 rcImage.top = drawRect->top + center_offset_h;
1836 /* if tab is TCS_VERTICAL and TCS_BOTTOM, the text is drawn from the */
1837 /* right side of the tab, but the image still uses the left as its x position */
1838 /* this keeps the image always drawn off of the same side of the tab */
1839 rcImage.left = drawRect->right - cx - center_offset_v;
1840 drawRect->top += cy + infoPtr->uHItemPadding;
1841 }
1842 else if(infoPtr->dwStyle & TCS_VERTICAL)
1843 {
1844 rcImage.top = drawRect->bottom - cy - center_offset_h;
1845 rcImage.left = drawRect->left + center_offset_v;
1846 drawRect->bottom -= cy + infoPtr->uHItemPadding;
1847 }
1848 else /* normal style, whether TCS_BOTTOM or not */
1849 {
1850 rcImage.left = drawRect->left + center_offset_h;
1851 rcImage.top = drawRect->top + center_offset_v;
1852 drawRect->left += cx + infoPtr->uHItemPadding;
1853 }
1854
1855 TRACE("drawing image=%d, left=%d, top=%d\n",
1856 item->iImage, rcImage.left, rcImage.top-1);
1857 ImageList_Draw
1858 (
1859 infoPtr->himl,
1860 item->iImage,
1861 hdc,
1862 rcImage.left,
1863 rcImage.top,
1864 ILD_NORMAL
1865 );
1866 }
1867
1868 /* Now position text */
1869 if (infoPtr->dwStyle & TCS_FIXEDWIDTH && infoPtr->dwStyle & TCS_FORCELABELLEFT)
1870 center_offset_h = infoPtr->uHItemPadding;
1871 else
1872 if(infoPtr->dwStyle & TCS_VERTICAL)
1873 center_offset_h = ((drawRect->bottom - drawRect->top) - (rcText.right - rcText.left)) / 2;
1874 else
1875 center_offset_h = ((drawRect->right - drawRect->left) - (rcText.right - rcText.left)) / 2;
1876
1877 if(infoPtr->dwStyle & TCS_VERTICAL)
1878 {
1879 if(infoPtr->dwStyle & TCS_BOTTOM)
1880 drawRect->top+=center_offset_h;
1881 else
1882 drawRect->bottom-=center_offset_h;
1883
1884 center_offset_v = ((drawRect->right - drawRect->left) - (rcText.bottom - rcText.top)) / 2;
1885 }
1886 else
1887 {
1888 drawRect->left += center_offset_h;
1889 center_offset_v = ((drawRect->bottom - drawRect->top) - (rcText.bottom - rcText.top)) / 2;
1890 }
1891
1892 /* if an item is selected, the text is shifted up instead of down */
1893 if (iItem == infoPtr->iSelected)
1894 center_offset_v -= infoPtr->uVItemPadding / 2;
1895 else
1896 center_offset_v += infoPtr->uVItemPadding / 2;
1897
1898 if (center_offset_v < 0)
1899 center_offset_v = 0;
1900
1901 if(infoPtr->dwStyle & TCS_VERTICAL)
1902 drawRect->left += center_offset_v;
1903 else
1904 drawRect->top += center_offset_v;
1905
1906 /* Draw the text */
1907 if(infoPtr->dwStyle & TCS_VERTICAL) /* if we are vertical rotate the text and each character */
1908 {
1909 static const WCHAR ArialW[] = { 'A','r','i','a','l',0 };
1910 LOGFONTW logfont;
1911 HFONT hFont = 0;
1912 INT nEscapement = 900;
1913 INT nOrientation = 900;
1914
1915 if(infoPtr->dwStyle & TCS_BOTTOM)
1916 {
1917 nEscapement = -900;
1918 nOrientation = -900;
1919 }
1920
1921 /* to get a font with the escapement and orientation we are looking for, we need to */
1922 /* call CreateFontIndirectA, which requires us to set the values of the logfont we pass in */
1923 if (!GetObjectW((infoPtr->hFont) ?
1924 infoPtr->hFont : GetStockObject(SYSTEM_FONT),
1925 sizeof(LOGFONTW),&logfont))
1926 {
1927 INT iPointSize = 9;
1928
1929 lstrcpyW(logfont.lfFaceName, ArialW);
1930 logfont.lfHeight = -MulDiv(iPointSize, GetDeviceCaps(hdc, LOGPIXELSY),
1931 72);
1932 logfont.lfWeight = FW_NORMAL;
1933 logfont.lfItalic = 0;
1934 logfont.lfUnderline = 0;
1935 logfont.lfStrikeOut = 0;
1936 }
1937
1938 logfont.lfEscapement = nEscapement;
1939 logfont.lfOrientation = nOrientation;
1940 hFont = CreateFontIndirectW(&logfont);
1941 SelectObject(hdc, hFont);
1942
1943 if (item->pszText)
1944 {
1945 ExtTextOutW(hdc,
1946 (infoPtr->dwStyle & TCS_BOTTOM) ? drawRect->right : drawRect->left,
1947 (!(infoPtr->dwStyle & TCS_BOTTOM)) ? drawRect->bottom : drawRect->top,
1948 ETO_CLIPPED,
1949 drawRect,
1950 item->pszText,
1951 lstrlenW(item->pszText),
1952 0);
1953 }
1954
1955 DeleteObject(hFont);
1956 }
1957 else
1958 {
1959 TRACE("for <%s>, c_o_h=%d, c_o_v=%d, draw=(%s), textlen=%d\n",
1960 debugstr_w(item->pszText), center_offset_h, center_offset_v,
1961 wine_dbgstr_rect(drawRect), (rcText.right-rcText.left));
1962 if (item->pszText)
1963 {
1964 DrawTextW
1965 (
1966 hdc,
1967 item->pszText,
1968 lstrlenW(item->pszText),
1969 drawRect,
1970 DT_LEFT | DT_SINGLELINE
1971 );
1972 }
1973 }
1974
1975 *drawRect = rcTemp; /* restore drawRect */
1976 }
1977
1978 /*
1979 * Cleanup
1980 */
1981 SelectObject(hdc, hOldFont);
1982 SetBkMode(hdc, oldBkMode);
1983 SelectObject(hdc, holdPen);
1984 DeleteObject( htextPen );
1985 }
1986
1987 /******************************************************************************
1988 * TAB_DrawItem
1989 *
1990 * This method is used to draw a single tab into the tab control.
1991 */
1992 static void TAB_DrawItem(const TAB_INFO *infoPtr, HDC hdc, INT iItem)
1993 {
1994 RECT itemRect;
1995 RECT selectedRect;
1996 BOOL isVisible;
1997 RECT r, fillRect, r1;
1998 INT clRight = 0;
1999 INT clBottom = 0;
2000 COLORREF bkgnd, corner;
2001 HTHEME theme;
2002
2003 /*
2004 * Get the rectangle for the item.
2005 */
2006 isVisible = TAB_InternalGetItemRect(infoPtr,
2007 iItem,
2008 &itemRect,
2009 &selectedRect);
2010
2011 if (isVisible)
2012 {
2013 RECT rUD, rC;
2014
2015 /* Clip UpDown control to not draw over it */
2016 if (infoPtr->needsScrolling)
2017 {
2018 GetWindowRect(infoPtr->hwnd, &rC);
2019 GetWindowRect(infoPtr->hwndUpDown, &rUD);
2020 ExcludeClipRect(hdc, rUD.left - rC.left, rUD.top - rC.top, rUD.right - rC.left, rUD.bottom - rC.top);
2021 }
2022
2023 /* If you need to see what the control is doing,
2024 * then override these variables. They will change what
2025 * fill colors are used for filling the tabs, and the
2026 * corners when drawing the edge.
2027 */
2028 bkgnd = comctl32_color.clrBtnFace;
2029 corner = comctl32_color.clrBtnFace;
2030
2031 if (infoPtr->dwStyle & TCS_BUTTONS)
2032 {
2033 /* Get item rectangle */
2034 r = itemRect;
2035
2036 /* Separators between flat buttons */
2037 if ((infoPtr->dwStyle & TCS_FLATBUTTONS) && (infoPtr->exStyle & TCS_EX_FLATSEPARATORS))
2038 {
2039 r1 = r;
2040 r1.right += (FLAT_BTN_SPACINGX -2);
2041 DrawEdge(hdc, &r1, EDGE_ETCHED, BF_RIGHT);
2042 }
2043
2044 if (iItem == infoPtr->iSelected)
2045 {
2046 DrawEdge(hdc, &r, EDGE_SUNKEN, BF_SOFT|BF_RECT);
2047
2048 OffsetRect(&r, 1, 1);
2049 }
2050 else /* ! selected */
2051 {
2052 DWORD state = TAB_GetItem(infoPtr, iItem)->dwState;
2053
2054 if ((state & TCIS_BUTTONPRESSED) || (iItem == infoPtr->uFocus))
2055 DrawEdge(hdc, &r, EDGE_SUNKEN, BF_SOFT|BF_RECT);
2056 else
2057 if (!(infoPtr->dwStyle & TCS_FLATBUTTONS))
2058 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_RECT);
2059 }
2060 }
2061 else /* !TCS_BUTTONS */
2062 {
2063 /* We draw a rectangle of different sizes depending on the selection
2064 * state. */
2065 if (iItem == infoPtr->iSelected) {
2066 RECT rect;
2067 GetClientRect (infoPtr->hwnd, &rect);
2068 clRight = rect.right;
2069 clBottom = rect.bottom;
2070 r = selectedRect;
2071 }
2072 else
2073 r = itemRect;
2074
2075 /*
2076 * Erase the background. (Delay it but setup rectangle.)
2077 * This is necessary when drawing the selected item since it is larger
2078 * than the others, it might overlap with stuff already drawn by the
2079 * other tabs
2080 */
2081 fillRect = r;
2082
2083 /* Draw themed tabs - but only if they are at the top.
2084 * Windows draws even side or bottom tabs themed, with wacky results.
2085 * However, since in Wine apps may get themed that did not opt in via
2086 * a manifest avoid theming when we know the result will be wrong */
2087 if ((theme = GetWindowTheme (infoPtr->hwnd))
2088 && ((infoPtr->dwStyle & (TCS_VERTICAL | TCS_BOTTOM)) == 0))
2089 {
2090 static const int partIds[8] = {
2091 /* Normal item */
2092 TABP_TABITEM,
2093 TABP_TABITEMLEFTEDGE,
2094 TABP_TABITEMRIGHTEDGE,
2095 TABP_TABITEMBOTHEDGE,
2096 /* Selected tab */
2097 TABP_TOPTABITEM,
2098 TABP_TOPTABITEMLEFTEDGE,
2099 TABP_TOPTABITEMRIGHTEDGE,
2100 TABP_TOPTABITEMBOTHEDGE,
2101 };
2102 int partIndex = 0;
2103 int stateId = TIS_NORMAL;
2104
2105 /* selected and unselected tabs have different parts */
2106 if (iItem == infoPtr->iSelected)
2107 partIndex += 4;
2108 /* The part also differs on the position of a tab on a line.
2109 * "Visually" determining the position works well enough. */
2110 if(selectedRect.left == 0)
2111 partIndex += 1;
2112 if(selectedRect.right == clRight)
2113 partIndex += 2;
2114
2115 if (iItem == infoPtr->iSelected)
2116 stateId = TIS_SELECTED;
2117 else if (iItem == infoPtr->iHotTracked)
2118 stateId = TIS_HOT;
2119 else if (iItem == infoPtr->uFocus)
2120 stateId = TIS_FOCUSED;
2121
2122 /* Adjust rectangle for bottommost row */
2123 if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2124 r.bottom += 3;
2125
2126 DrawThemeBackground (theme, hdc, partIds[partIndex], stateId, &r, NULL);
2127 GetThemeBackgroundContentRect (theme, hdc, partIds[partIndex], stateId, &r, &r);
2128 }
2129 else if(infoPtr->dwStyle & TCS_VERTICAL)
2130 {
2131 /* These are for adjusting the drawing of a Selected tab */
2132 /* The initial values are for the normal case of non-Selected */
2133 int ZZ = 1; /* Do not stretch if selected */
2134 if (iItem == infoPtr->iSelected) {
2135 ZZ = 0;
2136
2137 /* if leftmost draw the line longer */
2138 if(selectedRect.top == 0)
2139 fillRect.top += CONTROL_BORDER_SIZEY;
2140 /* if rightmost draw the line longer */
2141 if(selectedRect.bottom == clBottom)
2142 fillRect.bottom -= CONTROL_BORDER_SIZEY;
2143 }
2144
2145 if (infoPtr->dwStyle & TCS_BOTTOM)
2146 {
2147 /* Adjust both rectangles to match native */
2148 r.left += (1-ZZ);
2149
2150 TRACE("<right> item=%d, fill=(%s), edge=(%s)\n",
2151 iItem, wine_dbgstr_rect(&fillRect), wine_dbgstr_rect(&r));
2152
2153 /* Clear interior */
2154 SetBkColor(hdc, bkgnd);
2155 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2156
2157 /* Draw rectangular edge around tab */
2158 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_RIGHT|BF_TOP|BF_BOTTOM);
2159
2160 /* Now erase the top corner and draw diagonal edge */
2161 SetBkColor(hdc, corner);
2162 r1.left = r.right - ROUND_CORNER_SIZE - 1;
2163 r1.top = r.top;
2164 r1.right = r.right;
2165 r1.bottom = r1.top + ROUND_CORNER_SIZE;
2166 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2167 r1.right--;
2168 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPLEFT);
2169
2170 /* Now erase the bottom corner and draw diagonal edge */
2171 r1.left = r.right - ROUND_CORNER_SIZE - 1;
2172 r1.bottom = r.bottom;
2173 r1.right = r.right;
2174 r1.top = r1.bottom - ROUND_CORNER_SIZE;
2175 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2176 r1.right--;
2177 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMLEFT);
2178
2179 if ((iItem == infoPtr->iSelected) && (selectedRect.top == 0)) {
2180 r1 = r;
2181 r1.right = r1.left;
2182 r1.left--;
2183 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_TOP);
2184 }
2185
2186 }
2187 else
2188 {
2189 TRACE("<left> item=%d, fill=(%s), edge=(%s)\n",
2190 iItem, wine_dbgstr_rect(&fillRect), wine_dbgstr_rect(&r));
2191
2192 /* Clear interior */
2193 SetBkColor(hdc, bkgnd);
2194 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2195
2196 /* Draw rectangular edge around tab */
2197 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_TOP|BF_BOTTOM);
2198
2199 /* Now erase the top corner and draw diagonal edge */
2200 SetBkColor(hdc, corner);
2201 r1.left = r.left;
2202 r1.top = r.top;
2203 r1.right = r1.left + ROUND_CORNER_SIZE + 1;
2204 r1.bottom = r1.top + ROUND_CORNER_SIZE;
2205 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2206 r1.left++;
2207 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPRIGHT);
2208
2209 /* Now erase the bottom corner and draw diagonal edge */
2210 r1.left = r.left;
2211 r1.bottom = r.bottom;
2212 r1.right = r1.left + ROUND_CORNER_SIZE + 1;
2213 r1.top = r1.bottom - ROUND_CORNER_SIZE;
2214 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2215 r1.left++;
2216 DrawEdge(hdc, &r1, EDGE_SUNKEN, BF_DIAGONAL_ENDTOPLEFT);
2217 }
2218 }
2219 else /* ! TCS_VERTICAL */
2220 {
2221 /* These are for adjusting the drawing of a Selected tab */
2222 /* The initial values are for the normal case of non-Selected */
2223 if (iItem == infoPtr->iSelected) {
2224 /* if leftmost draw the line longer */
2225 if(selectedRect.left == 0)
2226 fillRect.left += CONTROL_BORDER_SIZEX;
2227 /* if rightmost draw the line longer */
2228 if(selectedRect.right == clRight)
2229 fillRect.right -= CONTROL_BORDER_SIZEX;
2230 }
2231
2232 if (infoPtr->dwStyle & TCS_BOTTOM)
2233 {
2234 /* Adjust both rectangles for topmost row */
2235 if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2236 {
2237 fillRect.top -= 2;
2238 r.top -= 1;
2239 }
2240
2241 TRACE("<bottom> item=%d, fill=(%s), edge=(%s)\n",
2242 iItem, wine_dbgstr_rect(&fillRect), wine_dbgstr_rect(&r));
2243
2244 /* Clear interior */
2245 SetBkColor(hdc, bkgnd);
2246 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2247
2248 /* Draw rectangular edge around tab */
2249 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_BOTTOM|BF_RIGHT);
2250
2251 /* Now erase the righthand corner and draw diagonal edge */
2252 SetBkColor(hdc, corner);
2253 r1.left = r.right - ROUND_CORNER_SIZE;
2254 r1.bottom = r.bottom;
2255 r1.right = r.right;
2256 r1.top = r1.bottom - ROUND_CORNER_SIZE - 1;
2257 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2258 r1.bottom--;
2259 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMLEFT);
2260
2261 /* Now erase the lefthand corner and draw diagonal edge */
2262 r1.left = r.left;
2263 r1.bottom = r.bottom;
2264 r1.right = r1.left + ROUND_CORNER_SIZE;
2265 r1.top = r1.bottom - ROUND_CORNER_SIZE - 1;
2266 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2267 r1.bottom--;
2268 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPLEFT);
2269
2270 if (iItem == infoPtr->iSelected)
2271 {
2272 r.top += 2;
2273 r.left += 1;
2274 if (selectedRect.left == 0)
2275 {
2276 r1 = r;
2277 r1.bottom = r1.top;
2278 r1.top--;
2279 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_LEFT);
2280 }
2281 }
2282
2283 }
2284 else
2285 {
2286 /* Adjust both rectangles for bottommost row */
2287 if (TAB_GetItem(infoPtr, iItem)->rect.top == infoPtr->uNumRows-1)
2288 {
2289 fillRect.bottom += 3;
2290 r.bottom += 2;
2291 }
2292
2293 TRACE("<top> item=%d, fill=(%s), edge=(%s)\n",
2294 iItem, wine_dbgstr_rect(&fillRect), wine_dbgstr_rect(&r));
2295
2296 /* Clear interior */
2297 SetBkColor(hdc, bkgnd);
2298 ExtTextOutW(hdc, 0, 0, 2, &fillRect, NULL, 0, 0);
2299
2300 /* Draw rectangular edge around tab */
2301 DrawEdge(hdc, &r, EDGE_RAISED, BF_SOFT|BF_LEFT|BF_TOP|BF_RIGHT);
2302
2303 /* Now erase the righthand corner and draw diagonal edge */
2304 SetBkColor(hdc, corner);
2305 r1.left = r.right - ROUND_CORNER_SIZE;
2306 r1.top = r.top;
2307 r1.right = r.right;
2308 r1.bottom = r1.top + ROUND_CORNER_SIZE + 1;
2309 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2310 r1.top++;
2311 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMRIGHT);
2312
2313 /* Now erase the lefthand corner and draw diagonal edge */
2314 r1.left = r.left;
2315 r1.top = r.top;
2316 r1.right = r1.left + ROUND_CORNER_SIZE;
2317 r1.bottom = r1.top + ROUND_CORNER_SIZE + 1;
2318 ExtTextOutW(hdc, 0, 0, 2, &r1, NULL, 0, 0);
2319 r1.top++;
2320 DrawEdge(hdc, &r1, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPRIGHT);
2321 }
2322 }
2323 }
2324
2325 TAB_DumpItemInternal(infoPtr, iItem);
2326
2327 /* This modifies r to be the text rectangle. */
2328 TAB_DrawItemInterior(infoPtr, hdc, iItem, &r);
2329 }
2330 }
2331
2332 /******************************************************************************
2333 * TAB_DrawBorder
2334 *
2335 * This method is used to draw the raised border around the tab control
2336 * "content" area.
2337 */
2338 static void TAB_DrawBorder(const TAB_INFO *infoPtr, HDC hdc)
2339 {
2340 RECT rect;
2341 HTHEME theme = GetWindowTheme (infoPtr->hwnd);
2342
2343 GetClientRect (infoPtr->hwnd, &rect);
2344
2345 /*
2346 * Adjust for the style
2347 */
2348
2349 if (infoPtr->uNumItem)
2350 {
2351 if ((infoPtr->dwStyle & TCS_BOTTOM) && !(infoPtr->dwStyle & TCS_VERTICAL))
2352 rect.bottom -= infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2353 else if((infoPtr->dwStyle & TCS_BOTTOM) && (infoPtr->dwStyle & TCS_VERTICAL))
2354 rect.right -= infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2355 else if(infoPtr->dwStyle & TCS_VERTICAL)
2356 rect.left += infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2357 else /* not TCS_VERTICAL and not TCS_BOTTOM */
2358 rect.top += infoPtr->tabHeight * infoPtr->uNumRows + CONTROL_BORDER_SIZEX;
2359 }
2360
2361 TRACE("border=(%s)\n", wine_dbgstr_rect(&rect));
2362
2363 if (theme)
2364 DrawThemeBackground (theme, hdc, TABP_PANE, 0, &rect, NULL);
2365 else
2366 DrawEdge(hdc, &rect, EDGE_RAISED, BF_SOFT|BF_RECT);
2367 }
2368
2369 /******************************************************************************
2370 * TAB_Refresh
2371 *
2372 * This method repaints the tab control..
2373 */
2374 static void TAB_Refresh (const TAB_INFO *infoPtr, HDC hdc)
2375 {
2376 HFONT hOldFont;
2377 INT i;
2378
2379 if (!infoPtr->DoRedraw)
2380 return;
2381
2382 hOldFont = SelectObject (hdc, infoPtr->hFont);
2383
2384 if (infoPtr->dwStyle & TCS_BUTTONS)
2385 {
2386 for (i = 0; i < infoPtr->uNumItem; i++)
2387 TAB_DrawItem (infoPtr, hdc, i);
2388 }
2389 else
2390 {
2391 /* Draw all the non selected item first */
2392 for (i = 0; i < infoPtr->uNumItem; i++)
2393 {
2394 if (i != infoPtr->iSelected)
2395 TAB_DrawItem (infoPtr, hdc, i);
2396 }
2397
2398 /* Now, draw the border, draw it before the selected item
2399 * since the selected item overwrites part of the border. */
2400 TAB_DrawBorder (infoPtr, hdc);
2401
2402 /* Then, draw the selected item */
2403 TAB_DrawItem (infoPtr, hdc, infoPtr->iSelected);
2404 }
2405
2406 SelectObject (hdc, hOldFont);
2407 }
2408
2409 static inline DWORD TAB_GetRowCount (const TAB_INFO *infoPtr)
2410 {
2411 TRACE("(%p)\n", infoPtr);
2412 return infoPtr->uNumRows;
2413 }
2414
2415 static inline LRESULT TAB_SetRedraw (TAB_INFO *infoPtr, BOOL doRedraw)
2416 {
2417 infoPtr->DoRedraw = doRedraw;
2418 return 0;
2419 }
2420
2421 /******************************************************************************
2422 * TAB_EnsureSelectionVisible
2423 *
2424 * This method will make sure that the current selection is completely
2425 * visible by scrolling until it is.
2426 */
2427 static void TAB_EnsureSelectionVisible(
2428 TAB_INFO* infoPtr)
2429 {
2430 INT iSelected = infoPtr->iSelected;
2431 INT iOrigLeftmostVisible = infoPtr->leftmostVisible;
2432
2433 /* set the items row to the bottommost row or topmost row depending on
2434 * style */
2435 if ((infoPtr->uNumRows > 1) && !(infoPtr->dwStyle & TCS_BUTTONS))
2436 {
2437 TAB_ITEM *selected = TAB_GetItem(infoPtr, iSelected);
2438 INT newselected;
2439 INT iTargetRow;
2440
2441 if(infoPtr->dwStyle & TCS_VERTICAL)
2442 newselected = selected->rect.left;
2443 else
2444 newselected = selected->rect.top;
2445
2446 /* the target row is always (number of rows - 1)
2447 as row 0 is furthest from the clientRect */
2448 iTargetRow = infoPtr->uNumRows - 1;
2449
2450 if (newselected != iTargetRow)
2451 {
2452 UINT i;
2453 if(infoPtr->dwStyle & TCS_VERTICAL)
2454 {
2455 for (i=0; i < infoPtr->uNumItem; i++)
2456 {
2457 /* move everything in the row of the selected item to the iTargetRow */
2458 TAB_ITEM *item = TAB_GetItem(infoPtr, i);
2459
2460 if (item->rect.left == newselected )
2461 item->rect.left = iTargetRow;
2462 else
2463 {
2464 if (item->rect.left > newselected)
2465 item->rect.left-=1;
2466 }
2467 }
2468 }
2469 else
2470 {
2471 for (i=0; i < infoPtr->uNumItem; i++)
2472 {
2473 TAB_ITEM *item = TAB_GetItem(infoPtr, i);
2474
2475 if (item->rect.top == newselected )
2476 item->rect.top = iTargetRow;
2477 else
2478 {
2479 if (item->rect.top > newselected)
2480 item->rect.top-=1;
2481 }
2482 }
2483 }
2484 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
2485 }
2486 }
2487
2488 /*
2489 * Do the trivial cases first.
2490 */
2491 if ( (!infoPtr->needsScrolling) ||
2492 (infoPtr->hwndUpDown==0) || (infoPtr->dwStyle & TCS_VERTICAL))
2493 return;
2494
2495 if (infoPtr->leftmostVisible >= iSelected)
2496 {
2497 infoPtr->leftmostVisible = iSelected;
2498 }
2499 else
2500 {
2501 TAB_ITEM *selected = TAB_GetItem(infoPtr, iSelected);
2502 RECT r;
2503 INT width;
2504 UINT i;
2505
2506 /* Calculate the part of the client area that is visible */
2507 GetClientRect(infoPtr->hwnd, &r);
2508 width = r.right;
2509
2510 GetClientRect(infoPtr->hwndUpDown, &r);
2511 width -= r.right;
2512
2513 if ((selected->rect.right -
2514 selected->rect.left) >= width )
2515 {
2516 /* Special case: width of selected item is greater than visible
2517 * part of control.
2518 */
2519 infoPtr->leftmostVisible = iSelected;
2520 }
2521 else
2522 {
2523 for (i = infoPtr->leftmostVisible; i < infoPtr->uNumItem; i++)
2524 {
2525 if ((selected->rect.right - TAB_GetItem(infoPtr, i)->rect.left) < width)
2526 break;
2527 }
2528 infoPtr->leftmostVisible = i;
2529 }
2530 }
2531
2532 if (infoPtr->leftmostVisible != iOrigLeftmostVisible)
2533 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
2534
2535 SendMessageW(infoPtr->hwndUpDown, UDM_SETPOS, 0,
2536 MAKELONG(infoPtr->leftmostVisible, 0));
2537 }
2538
2539 /******************************************************************************
2540 * TAB_InvalidateTabArea
2541 *
2542 * This method will invalidate the portion of the control that contains the
2543 * tabs. It is called when the state of the control changes and needs
2544 * to be redisplayed
2545 */
2546 static void TAB_InvalidateTabArea(const TAB_INFO *infoPtr)
2547 {
2548 RECT clientRect, rInvalidate, rAdjClient;
2549 INT lastRow = infoPtr->uNumRows - 1;
2550 RECT rect;
2551
2552 if (lastRow < 0) return;
2553
2554 GetClientRect(infoPtr->hwnd, &clientRect);
2555 rInvalidate = clientRect;
2556 rAdjClient = clientRect;
2557
2558 TAB_AdjustRect(infoPtr, 0, &rAdjClient);
2559
2560 TAB_InternalGetItemRect(infoPtr, infoPtr->uNumItem-1 , &rect, NULL);
2561 if ((infoPtr->dwStyle & TCS_BOTTOM) && (infoPtr->dwStyle & TCS_VERTICAL))
2562 {
2563 rInvalidate.left = rAdjClient.right;
2564 if (infoPtr->uNumRows == 1)
2565 rInvalidate.bottom = clientRect.top + rect.bottom + 2 * SELECTED_TAB_OFFSET;
2566 }
2567 else if(infoPtr->dwStyle & TCS_VERTICAL)
2568 {
2569 rInvalidate.right = rAdjClient.left;
2570 if (infoPtr->uNumRows == 1)
2571 rInvalidate.bottom = clientRect.top + rect.bottom + 2 * SELECTED_TAB_OFFSET;
2572 }
2573 else if (infoPtr->dwStyle & TCS_BOTTOM)
2574 {
2575 rInvalidate.top = rAdjClient.bottom;
2576 if (infoPtr->uNumRows == 1)
2577 rInvalidate.right = clientRect.left + rect.right + 2 * SELECTED_TAB_OFFSET;
2578 }
2579 else
2580 {
2581 rInvalidate.bottom = rAdjClient.top;
2582 if (infoPtr->uNumRows == 1)
2583 rInvalidate.right = clientRect.left + rect.right + 2 * SELECTED_TAB_OFFSET;
2584 }
2585
2586 /* Punch out the updown control */
2587 if (infoPtr->needsScrolling && (rInvalidate.right > 0)) {
2588 RECT r;
2589 GetClientRect(infoPtr->hwndUpDown, &r);
2590 if (rInvalidate.right > clientRect.right - r.left)
2591 rInvalidate.right = rInvalidate.right - (r.right - r.left);
2592 else
2593 rInvalidate.right = clientRect.right - r.left;
2594 }
2595
2596 TRACE("invalidate (%s)\n", wine_dbgstr_rect(&rInvalidate));
2597
2598 InvalidateRect(infoPtr->hwnd, &rInvalidate, TRUE);
2599 }
2600
2601 static inline LRESULT TAB_Paint (TAB_INFO *infoPtr, HDC hdcPaint)
2602 {
2603 HDC hdc;
2604 PAINTSTRUCT ps;
2605
2606 if (hdcPaint)
2607 hdc = hdcPaint;
2608 else
2609 {
2610 hdc = BeginPaint (infoPtr->hwnd, &ps);
2611 TRACE("erase %d, rect=(%s)\n", ps.fErase, wine_dbgstr_rect(&ps.rcPaint));
2612 }
2613
2614 TAB_Refresh (infoPtr, hdc);
2615
2616 if (!hdcPaint)
2617 EndPaint (infoPtr->hwnd, &ps);
2618
2619 return 0;
2620 }
2621
2622 static LRESULT
2623 TAB_InsertItemT (TAB_INFO *infoPtr, INT iItem, TCITEMW *pti, BOOL bUnicode)
2624 {
2625 TAB_ITEM *item;
2626 RECT rect;
2627
2628 GetClientRect (infoPtr->hwnd, &rect);
2629 TRACE("Rect: %p %s\n", infoPtr->hwnd, wine_dbgstr_rect(&rect));
2630
2631 if (iItem < 0) return -1;
2632 if (iItem > infoPtr->uNumItem)
2633 iItem = infoPtr->uNumItem;
2634
2635 TAB_DumpItemExternalT(pti, iItem, bUnicode);
2636
2637
2638 if (infoPtr->uNumItem == 0) {
2639 infoPtr->items = Alloc (TAB_ITEM_SIZE(infoPtr));
2640 infoPtr->uNumItem++;
2641 infoPtr->iSelected = 0;
2642 }
2643 else {
2644 LPBYTE oldItems = (LPBYTE)infoPtr->items;
2645
2646 infoPtr->uNumItem++;
2647 infoPtr->items = Alloc (TAB_ITEM_SIZE(infoPtr) * infoPtr->uNumItem);
2648
2649 /* pre insert copy */
2650 if (iItem > 0) {
2651 memcpy (infoPtr->items, oldItems,
2652 iItem * TAB_ITEM_SIZE(infoPtr));
2653 }
2654
2655 /* post insert copy */
2656 if (iItem < infoPtr->uNumItem - 1) {
2657 memcpy (TAB_GetItem(infoPtr, iItem + 1),
2658 oldItems + iItem * TAB_ITEM_SIZE(infoPtr),
2659 (infoPtr->uNumItem - iItem - 1) * TAB_ITEM_SIZE(infoPtr));
2660
2661 }
2662
2663 if (iItem <= infoPtr->iSelected)
2664 infoPtr->iSelected++;
2665
2666 Free (oldItems);
2667 }
2668
2669 item = TAB_GetItem(infoPtr, iItem);
2670
2671 item->pszText = NULL;
2672
2673 if (pti->mask & TCIF_TEXT)
2674 {
2675 if (bUnicode)
2676 Str_SetPtrW (&item->pszText, pti->pszText);
2677 else
2678 Str_SetPtrAtoW (&item->pszText, (LPSTR)pti->pszText);
2679 }
2680
2681 if (pti->mask & TCIF_IMAGE)
2682 item->iImage = pti->iImage;
2683 else
2684 item->iImage = -1;
2685
2686 if (pti->mask & TCIF_PARAM)
2687 memcpy(item->extra, &pti->lParam, infoPtr->cbInfo);
2688 else
2689 memset(item->extra, 0, infoPtr->cbInfo);
2690
2691 TAB_SetItemBounds(infoPtr);
2692 if (infoPtr->uNumItem > 1)
2693 TAB_InvalidateTabArea(infoPtr);
2694 else
2695 InvalidateRect(infoPtr->hwnd, NULL, TRUE);
2696
2697 TRACE("[%p]: added item %d %s\n",
2698 infoPtr->hwnd, iItem, debugstr_w(item->pszText));
2699
2700 /* If we haven't set the current focus yet, set it now. */
2701 if (infoPtr->uFocus == -1)
2702 TAB_SetCurFocus(infoPtr, iItem);
2703
2704 return iItem;
2705 }
2706
2707 static LRESULT
2708 TAB_SetItemSize (TAB_INFO *infoPtr, INT cx, INT cy)
2709 {
2710 LONG lResult = 0;
2711 BOOL bNeedPaint = FALSE;
2712
2713 lResult = MAKELONG(infoPtr->tabWidth, infoPtr->tabHeight);
2714
2715 /* UNDOCUMENTED: If requested Width or Height is 0 this means that program wants to use auto size. */
2716 if (infoPtr->dwStyle & TCS_FIXEDWIDTH && (infoPtr->tabWidth != cx))
2717 {
2718 infoPtr->tabWidth = cx;
2719 bNeedPaint = TRUE;
2720 }
2721
2722 if (infoPtr->tabHeight != cy)
2723 {
2724 if ((infoPtr->fHeightSet = (cy != 0)))
2725 infoPtr->tabHeight = cy;
2726
2727 bNeedPaint = TRUE;
2728 }
2729 TRACE("was h=%d,w=%d, now h=%d,w=%d\n",
2730 HIWORD(lResult), LOWORD(lResult),
2731 infoPtr->tabHeight, infoPtr->tabWidth);
2732
2733 if (bNeedPaint)
2734 {
2735 TAB_SetItemBounds(infoPtr);
2736 RedrawWindow(infoPtr->hwnd, NULL, NULL, RDW_ERASE | RDW_INVALIDATE | RDW_UPDATENOW);
2737 }
2738
2739 return lResult;
2740 }
2741
2742 static inline LRESULT TAB_SetMinTabWidth (TAB_INFO *infoPtr, INT cx)
2743 {
2744 INT oldcx = 0;
2745
2746 TRACE("(%p,%d)\n", infoPtr, cx);
2747
2748 if (infoPtr->tabMinWidth < 0)
2749 oldcx = DEFAULT_MIN_TAB_WIDTH;
2750 else
2751 oldcx = infoPtr->tabMinWidth;
2752 infoPtr->tabMinWidth = cx;
2753 TAB_SetItemBounds(infoPtr);
2754 return oldcx;
2755 }
2756
2757 static inline LRESULT
2758 TAB_HighlightItem (TAB_INFO *infoPtr, INT iItem, BOOL fHighlight)
2759 {
2760 LPDWORD lpState;
2761 DWORD oldState;
2762 RECT r;
2763
2764 TRACE("(%p,%d,%s)\n", infoPtr, iItem, fHighlight ? "true" : "false");
2765
2766 if (iItem < 0 || iItem >= infoPtr->uNumItem)
2767 return FALSE;
2768
2769 lpState = &TAB_GetItem(infoPtr, iItem)->dwState;
2770 oldState = *lpState;
2771
2772 if (fHighlight)
2773 *lpState |= TCIS_HIGHLIGHTED;
2774 else
2775 *lpState &= ~TCIS_HIGHLIGHTED;
2776
2777 if ((oldState != *lpState) && TAB_InternalGetItemRect (infoPtr, iItem, &r, NULL))
2778 InvalidateRect (infoPtr->hwnd, &r, TRUE);
2779
2780 return TRUE;
2781 }
2782
2783 static LRESULT
2784 TAB_SetItemT (TAB_INFO *infoPtr, INT iItem, LPTCITEMW tabItem, BOOL bUnicode)
2785 {
2786 TAB_ITEM *wineItem;
2787
2788 TRACE("(%p,%d,%p,%s)\n", infoPtr, iItem, tabItem, bUnicode ? "true" : "false");
2789
2790 if (iItem < 0 || iItem >= infoPtr->uNumItem)
2791 return FALSE;
2792
2793 TAB_DumpItemExternalT(tabItem, iItem, bUnicode);
2794
2795 wineItem = TAB_GetItem(infoPtr, iItem);
2796
2797 if (tabItem->mask & TCIF_IMAGE)
2798 wineItem->iImage = tabItem->iImage;
2799
2800 if (tabItem->mask & TCIF_PARAM)
2801 memcpy(wineItem->extra, &tabItem->lParam, infoPtr->cbInfo);
2802
2803 if (tabItem->mask & TCIF_RTLREADING)
2804 FIXME("TCIF_RTLREADING\n");
2805
2806 if (tabItem->mask & TCIF_STATE)
2807 wineItem->dwState = (wineItem->dwState & ~tabItem->dwStateMask) |
2808 ( tabItem->dwState & tabItem->dwStateMask);
2809
2810 if (tabItem->mask & TCIF_TEXT)
2811 {
2812 Free(wineItem->pszText);
2813 wineItem->pszText = NULL;
2814 if (bUnicode)
2815 Str_SetPtrW(&wineItem->pszText, tabItem->pszText);
2816 else
2817 Str_SetPtrAtoW(&wineItem->pszText, (LPSTR)tabItem->pszText);
2818 }
2819
2820 /* Update and repaint tabs */
2821 TAB_SetItemBounds(infoPtr);
2822 TAB_InvalidateTabArea(infoPtr);
2823
2824 return TRUE;
2825 }
2826
2827 static inline LRESULT TAB_GetItemCount (const TAB_INFO *infoPtr)
2828 {
2829 TRACE("\n");
2830 return infoPtr->uNumItem;
2831 }
2832
2833
2834 static LRESULT
2835 TAB_GetItemT (TAB_INFO *infoPtr, INT iItem, LPTCITEMW tabItem, BOOL bUnicode)
2836 {
2837 TAB_ITEM *wineItem;
2838
2839 TRACE("(%p,%d,%p,%s)\n", infoPtr, iItem, tabItem, bUnicode ? "true" : "false");
2840
2841 if (!tabItem) return FALSE;
2842
2843 if (iItem < 0 || iItem >= infoPtr->uNumItem)
2844 {
2845 /* init requested fields */
2846 if (tabItem->mask & TCIF_IMAGE) tabItem->iImage = 0;
2847 if (tabItem->mask & TCIF_PARAM) tabItem->lParam = 0;
2848 if (tabItem->mask & TCIF_STATE) tabItem->dwState = 0;
2849 return FALSE;
2850 }
2851
2852 wineItem = TAB_GetItem(infoPtr, iItem);
2853
2854 if (tabItem->mask & TCIF_IMAGE)
2855 tabItem->iImage = wineItem->iImage;
2856
2857 if (tabItem->mask & TCIF_PARAM)
2858 memcpy(&tabItem->lParam, wineItem->extra, infoPtr->cbInfo);
2859
2860 if (tabItem->mask & TCIF_RTLREADING)
2861 FIXME("TCIF_RTLREADING\n");
2862
2863 if (tabItem->mask & TCIF_STATE)
2864 tabItem->dwState = wineItem->dwState & tabItem->dwStateMask;
2865
2866 if (tabItem->mask & TCIF_TEXT)
2867 {
2868 if (bUnicode)
2869 Str_GetPtrW (wineItem->pszText, tabItem->pszText, tabItem->cchTextMax);
2870 else
2871 Str_GetPtrWtoA (wineItem->pszText, (LPSTR)tabItem->pszText, tabItem->cchTextMax);
2872 }
2873
2874 TAB_DumpItemExternalT(tabItem, iItem, bUnicode);
2875
2876 return TRUE;
2877 }
2878
2879
2880 static LRESULT TAB_DeleteItem (TAB_INFO *infoPtr, INT iItem)
2881 {
2882 BOOL bResult = FALSE;
2883
2884 TRACE("(%p, %d)\n", infoPtr, iItem);
2885
2886 if ((iItem >= 0) && (iItem < infoPtr->uNumItem))
2887 {
2888 TAB_ITEM *item = TAB_GetItem(infoPtr, iItem);
2889 LPBYTE oldItems = (LPBYTE)infoPtr->items;
2890
2891 TAB_InvalidateTabArea(infoPtr);
2892 Free(item->pszText);
2893 infoPtr->uNumItem--;
2894
2895 if (!infoPtr->uNumItem)
2896 {
2897 infoPtr->items = NULL;
2898 if (infoPtr->iHotTracked >= 0)
2899 {
2900 KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
2901 infoPtr->iHotTracked = -1;
2902 }
2903 }
2904 else
2905 {
2906 infoPtr->items = Alloc(TAB_ITEM_SIZE(infoPtr) * infoPtr->uNumItem);
2907
2908 if (iItem > 0)
2909 memcpy(infoPtr->items, oldItems, iItem * TAB_ITEM_SIZE(infoPtr));
2910
2911 if (iItem < infoPtr->uNumItem)
2912 memcpy(TAB_GetItem(infoPtr, iItem),
2913 oldItems + (iItem + 1) * TAB_ITEM_SIZE(infoPtr),
2914 (infoPtr->uNumItem - iItem) * TAB_ITEM_SIZE(infoPtr));
2915
2916 if (iItem <= infoPtr->iHotTracked)
2917 {
2918 /* When tabs move left/up, the hot track item may change */
2919 FIXME("Recalc hot track\n");
2920 }
2921 }
2922 Free(oldItems);
2923
2924 /* Readjust the selected index */
2925 if (iItem == infoPtr->iSelected)
2926 infoPtr->iSelected = -1;
2927 else if (iItem < infoPtr->iSelected)
2928 infoPtr->iSelected--;
2929
2930 if (infoPtr->uNumItem == 0)
2931 infoPtr->iSelected = -1;
2932
2933 /* Reposition and repaint tabs */
2934 TAB_SetItemBounds(infoPtr);
2935
2936 bResult = TRUE;
2937 }
2938
2939 return bResult;
2940 }
2941
2942 static inline LRESULT TAB_DeleteAllItems (TAB_INFO *infoPtr)
2943 {
2944 TRACE("(%p)\n", infoPtr);
2945 while (infoPtr->uNumItem)
2946 TAB_DeleteItem (infoPtr, 0);
2947 return TRUE;
2948 }
2949
2950
2951 static inline LRESULT TAB_GetFont (const TAB_INFO *infoPtr)
2952 {
2953 TRACE("(%p) returning %p\n", infoPtr, infoPtr->hFont);
2954 return (LRESULT)infoPtr->hFont;
2955 }
2956
2957 static inline LRESULT TAB_SetFont (TAB_INFO *infoPtr, HFONT hNewFont)
2958 {
2959 TRACE("(%p,%p)\n", infoPtr, hNewFont);
2960
2961 infoPtr->hFont = hNewFont;
2962
2963 TAB_SetItemBounds(infoPtr);
2964
2965 TAB_InvalidateTabArea(infoPtr);
2966
2967 return 0;
2968 }
2969
2970
2971 static inline LRESULT TAB_GetImageList (const TAB_INFO *infoPtr)
2972 {
2973 TRACE("\n");
2974 return (LRESULT)infoPtr->himl;
2975 }
2976
2977 static inline LRESULT TAB_SetImageList (TAB_INFO *infoPtr, HIMAGELIST himlNew)
2978 {
2979 HIMAGELIST himlPrev = infoPtr->himl;
2980 TRACE("himl=%p\n", himlNew);
2981 infoPtr->himl = himlNew;
2982 TAB_SetItemBounds(infoPtr);
2983 InvalidateRect(infoPtr->hwnd, NULL, TRUE);
2984 return (LRESULT)himlPrev;
2985 }
2986
2987 static inline LRESULT TAB_GetUnicodeFormat (const TAB_INFO *infoPtr)
2988 {
2989 TRACE("(%p)\n", infoPtr);
2990 return infoPtr->bUnicode;
2991 }
2992
2993 static inline LRESULT TAB_SetUnicodeFormat (TAB_INFO *infoPtr, BOOL bUnicode)
2994 {
2995 BOOL bTemp = infoPtr->bUnicode;
2996
2997 TRACE("(%p %d)\n", infoPtr, bUnicode);
2998 infoPtr->bUnicode = bUnicode;
2999
3000 return bTemp;
3001 }
3002
3003 static inline LRESULT TAB_Size (TAB_INFO *infoPtr)
3004 {
3005 /* I'm not really sure what the following code was meant to do.
3006 This is what it is doing:
3007 When WM_SIZE is sent with SIZE_RESTORED, the control
3008 gets positioned in the top left corner.
3009
3010 RECT parent_rect;
3011 HWND parent;
3012 UINT uPosFlags,cx,cy;
3013
3014 uPosFlags=0;
3015 if (!wParam) {
3016 parent = GetParent (hwnd);
3017 GetClientRect(parent, &parent_rect);
3018 cx=LOWORD (lParam);
3019 cy=HIWORD (lParam);
3020 if (GetWindowLongW(hwnd, GWL_STYLE) & CCS_NORESIZE)
3021 uPosFlags |= (SWP_NOSIZE | SWP_NOMOVE);
3022
3023 SetWindowPos (hwnd, 0, parent_rect.left, parent_rect.top,
3024 cx, cy, uPosFlags | SWP_NOZORDER);
3025 } else {
3026 FIXME("WM_SIZE flag %x %lx not handled\n", wParam, lParam);
3027 } */
3028
3029 /* Recompute the size/position of the tabs. */
3030 TAB_SetItemBounds (infoPtr);
3031
3032 /* Force a repaint of the control. */
3033 InvalidateRect(infoPtr->hwnd, NULL, TRUE);
3034
3035 return 0;
3036 }
3037
3038
3039 static LRESULT TAB_Create (HWND hwnd, LPARAM lParam)
3040 {
3041 TAB_INFO *infoPtr;
3042 TEXTMETRICW fontMetrics;
3043 HDC hdc;
3044 HFONT hOldFont;
3045 DWORD dwStyle;
3046
3047 infoPtr = Alloc (sizeof(TAB_INFO));
3048
3049 SetWindowLongPtrW(hwnd, 0, (DWORD_PTR)infoPtr);
3050
3051 infoPtr->hwnd = hwnd;
3052 infoPtr->hwndNotify = ((LPCREATESTRUCTW)lParam)->hwndParent;
3053 infoPtr->uNumItem = 0;
3054 infoPtr->uNumRows = 0;
3055 infoPtr->uHItemPadding = 6;
3056 infoPtr->uVItemPadding = 3;
3057 infoPtr->uHItemPadding_s = 6;
3058 infoPtr->uVItemPadding_s = 3;
3059 infoPtr->hFont = 0;
3060 infoPtr->items = 0;
3061 infoPtr->hcurArrow = LoadCursorW (0, (LPWSTR)IDC_ARROW);
3062 infoPtr->iSelected = -1;
3063 infoPtr->iHotTracked = -1;
3064 infoPtr->uFocus = -1;
3065 infoPtr->hwndToolTip = 0;
3066 infoPtr->DoRedraw = TRUE;
3067 infoPtr->needsScrolling = FALSE;
3068 infoPtr->hwndUpDown = 0;
3069 infoPtr->leftmostVisible = 0;
3070 infoPtr->fHeightSet = FALSE;
3071 infoPtr->bUnicode = IsWindowUnicode (hwnd);
3072 infoPtr->cbInfo = sizeof(LPARAM);
3073
3074 TRACE("Created tab control, hwnd [%p]\n", hwnd);
3075
3076 /* The tab control always has the WS_CLIPSIBLINGS style. Even
3077 if you don't specify it in CreateWindow. This is necessary in
3078 order for paint to work correctly. This follows windows behaviour. */
3079 dwStyle = GetWindowLongW(hwnd, GWL_STYLE);
3080 SetWindowLongW(hwnd, GWL_STYLE, dwStyle|WS_CLIPSIBLINGS);
3081
3082 infoPtr->dwStyle = dwStyle | WS_CLIPSIBLINGS;
3083 infoPtr->exStyle = (dwStyle & TCS_FLATBUTTONS) ? TCS_EX_FLATSEPARATORS : 0;
3084
3085 if (infoPtr->dwStyle & TCS_TOOLTIPS) {
3086 /* Create tooltip control */
3087 infoPtr->hwndToolTip =
3088 CreateWindowExW (0, TOOLTIPS_CLASSW, NULL, WS_POPUP,
3089 CW_USEDEFAULT, CW_USEDEFAULT,
3090 CW_USEDEFAULT, CW_USEDEFAULT,
3091 hwnd, 0, 0, 0);
3092
3093 /* Send NM_TOOLTIPSCREATED notification */
3094 if (infoPtr->hwndToolTip) {
3095 NMTOOLTIPSCREATED nmttc;
3096
3097 nmttc.hdr.hwndFrom = hwnd;
3098 nmttc.hdr.idFrom = GetWindowLongPtrW(hwnd, GWLP_ID);
3099 nmttc.hdr.code = NM_TOOLTIPSCREATED;
3100 nmttc.hwndToolTips = infoPtr->hwndToolTip;
3101
3102 SendMessageW (infoPtr->hwndNotify, WM_NOTIFY,
3103 GetWindowLongPtrW(hwnd, GWLP_ID), (LPARAM)&nmttc);
3104 }
3105 }
3106
3107 OpenThemeData (infoPtr->hwnd, themeClass);
3108
3109 /*
3110 * We need to get text information so we need a DC and we need to select
3111 * a font.
3112 */
3113 hdc = GetDC(hwnd);
3114 hOldFont = SelectObject (hdc, GetStockObject (SYSTEM_FONT));
3115
3116 /* Use the system font to determine the initial height of a tab. */
3117 GetTextMetricsW(hdc, &fontMetrics);
3118
3119 /*
3120 * Make sure there is enough space for the letters + growing the
3121 * selected item + extra space for the selected item.
3122 */
3123 infoPtr->tabHeight = fontMetrics.tmHeight + SELECTED_TAB_OFFSET +
3124 ((infoPtr->dwStyle & TCS_BUTTONS) ? 2 : 1) *
3125 infoPtr->uVItemPadding;
3126
3127 /* Initialize the width of a tab. */
3128 if (infoPtr->dwStyle & TCS_FIXEDWIDTH)
3129 infoPtr->tabWidth = GetDeviceCaps(hdc, LOGPIXELSX);
3130
3131 infoPtr->tabMinWidth = -1;
3132
3133 TRACE("tabH=%d, tabW=%d\n", infoPtr->tabHeight, infoPtr->tabWidth);
3134
3135 SelectObject (hdc, hOldFont);
3136 ReleaseDC(hwnd, hdc);
3137
3138 return 0;
3139 }
3140
3141 static LRESULT
3142 TAB_Destroy (TAB_INFO *infoPtr)
3143 {
3144 UINT iItem;
3145
3146 SetWindowLongPtrW(infoPtr->hwnd, 0, 0);
3147
3148 if (infoPtr->items) {
3149 for (iItem = 0; iItem < infoPtr->uNumItem; iItem++) {
3150 Free (TAB_GetItem(infoPtr, iItem)->pszText);
3151 }
3152 Free (infoPtr->items);
3153 }
3154
3155 if (infoPtr->hwndToolTip)
3156 DestroyWindow (infoPtr->hwndToolTip);
3157
3158 if (infoPtr->hwndUpDown)
3159 DestroyWindow(infoPtr->hwndUpDown);
3160
3161 if (infoPtr->iHotTracked >= 0)
3162 KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
3163
3164 CloseThemeData (GetWindowTheme (infoPtr->hwnd));
3165
3166 Free (infoPtr);
3167 return 0;
3168 }
3169
3170 /* update theme after a WM_THEMECHANGED message */
3171 static LRESULT theme_changed(const TAB_INFO *infoPtr)
3172 {
3173 HTHEME theme = GetWindowTheme (infoPtr->hwnd);
3174 CloseThemeData (theme);
3175 OpenThemeData (infoPtr->hwnd, themeClass);
3176 return 0;
3177 }
3178
3179 static LRESULT TAB_NCCalcSize(WPARAM wParam)
3180 {
3181 if (!wParam)
3182 return 0;
3183 return WVR_ALIGNTOP;
3184 }
3185
3186 static inline LRESULT
3187 TAB_SetItemExtra (TAB_INFO *infoPtr, INT cbInfo)
3188 {
3189 TRACE("(%p %d)\n", infoPtr, cbInfo);
3190
3191 if (cbInfo <= 0)
3192 return FALSE;
3193
3194 if (infoPtr->uNumItem)
3195 {
3196 /* FIXME: MSDN says this is not allowed, but this hasn't been verified */
3197 return FALSE;
3198 }
3199
3200 infoPtr->cbInfo = cbInfo;
3201 return TRUE;
3202 }
3203
3204 static LRESULT TAB_RemoveImage (TAB_INFO *infoPtr, INT image)
3205 {
3206 TRACE("%p %d\n", infoPtr, image);
3207
3208 if (ImageList_Remove (infoPtr->himl, image))
3209 {
3210 INT i, *idx;
3211 RECT r;
3212
3213 /* shift indices, repaint items if needed */
3214 for (i = 0; i < infoPtr->uNumItem; i++)