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