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
36 * TCS_RIGHT
37 * TCS_RIGHTJUSTIFY
38 * TCS_SCROLLOPPOSITE
39 * TCS_SINGLELINE
40 * TCIF_RTLREADING
41 *
42 * Extended Styles:
43 * TCS_EX_FLATSEPARATORS
44 * TCS_EX_REGISTERDROP
45 *
46 * States:
47 * TCIS_BUTTONPRESSED
48 *
49 * Notifications:
50 * NM_RELEASEDCAPTURE
51 * TCN_FOCUSCHANGE
52 * TCN_GETOBJECT
53 * TCN_KEYDOWN
54 *
55 * Messages:
56 * TCM_REMOVEIMAGE
57 * TCM_DESELECTALL
58 * TCM_GETEXTENDEDSTYLE
59 * TCM_SETEXTENDEDSTYLE
60 *
61 * Macros:
62 * TabCtrl_AdjustRect
63 *
64 */
65
66 #include <stdarg.h>
67 #include <string.h>
68
69 #include "windef.h"
70 #include "winbase.h"
71 #include "wingdi.h"
72 #include "winuser.h"
73 #include "winnls.h"
74 #include "commctrl.h"
75 #include "comctl32.h"
76 #include "uxtheme.h"
77 #include "tmschema.h"
78 #include "wine/debug.h"
79 #include <math.h>
80
81 WINE_DEFAULT_DEBUG_CHANNEL(tab);
82
83 typedef struct
84 {
85 DWORD dwState;
86 LPWSTR pszText;
87 INT iImage;
88 RECT rect; /* bounding rectangle of the item relative to the
89 * leftmost item (the leftmost item, 0, would have a
90 * "left" member of 0 in this rectangle)
91 *
92 * additionally the top member holds the row number
93 * and bottom is unused and should be 0 */
94 BYTE extra[1]; /* Space for caller supplied info, variable size */
95 } TAB_ITEM;
96
97 /* The size of a tab item depends on how much extra data is requested */
98 #define TAB_ITEM_SIZE(infoPtr) (FIELD_OFFSET(TAB_ITEM, extra[(infoPtr)->cbInfo]))
99
100 typedef struct
101 {
102 HWND hwnd; /* Tab control window */
103 HWND hwndNotify; /* notification window (parent) */
104 UINT uNumItem; /* number of tab items */
105 UINT uNumRows; /* number of tab rows */
106 INT tabHeight; /* height of the tab row */
107 INT tabWidth; /* width of tabs */
108 INT tabMinWidth; /* minimum width of items */
109 USHORT uHItemPadding; /* amount of horizontal padding, in pixels */
110 USHORT uVItemPadding; /* amount of vertical padding, in pixels */
111 USHORT uHItemPadding_s; /* Set amount of horizontal padding, in pixels */
112 USHORT uVItemPadding_s; /* Set amount of vertical padding, in pixels */
113 HFONT hFont; /* handle to the current font */
114 HCURSOR hcurArrow; /* handle to the current cursor */
115 HIMAGELIST himl; /* handle to an image list (may be 0) */
116 HWND hwndToolTip; /* handle to tab's tooltip */
117 INT leftmostVisible; /* Used for scrolling, this member contains
118 * the index of the first visible item */
119 INT iSelected; /* the currently selected item */
120 INT iHotTracked; /* the highlighted item under the mouse */
121 INT uFocus; /* item which has the focus */
122 TAB_ITEM* items; /* pointer to an array of TAB_ITEM's */
123 BOOL DoRedraw; /* flag for redrawing when tab contents is changed*/
124 BOOL needsScrolling; /* TRUE if the size of the tabs is greater than
125 * the size of the control */
126 BOOL fHeightSet; /* was the height of the tabs explicitly set? */
127 BOOL bUnicode; /* Unicode control? */
128 HWND hwndUpDown; /* Updown control used for scrolling */
129 INT cbInfo; /* Number of bytes of caller supplied info per tab */
130 } TAB_INFO;
131
132 /******************************************************************************
133 * Positioning constants
134 */
135 #define SELECTED_TAB_OFFSET 2
136 #define ROUND_CORNER_SIZE 2
137 #define DISPLAY_AREA_PADDINGX 2
138 #define DISPLAY_AREA_PADDINGY 2
139 #define CONTROL_BORDER_SIZEX 2
140 #define CONTROL_BORDER_SIZEY 2
141 #define BUTTON_SPACINGX 3
142 #define BUTTON_SPACINGY 3
143 #define FLAT_BTN_SPACINGX 8
144 #define DEFAULT_MIN_TAB_WIDTH 54
145 #define DEFAULT_TAB_WIDTH_FIXED 96
146 #define DEFAULT_PADDING_X 6
147 #define EXTRA_ICON_PADDING 3
148
149 #define TAB_GetInfoPtr(hwnd) ((TAB_INFO *)GetWindowLongPtrW(hwnd,0))
150 /* Since items are variable sized, cannot directly access them */
151 #define TAB_GetItem(info,i) \
152 ((TAB_ITEM*)((LPBYTE)info->items + (i) * TAB_ITEM_SIZE(info)))
153
154 #define GET_DEFAULT_MIN_TAB_WIDTH(infoPtr) (DEFAULT_MIN_TAB_WIDTH - (DEFAULT_PADDING_X - (infoPtr)->uHItemPadding) * 2)
155
156 /******************************************************************************
157 * Hot-tracking timer constants
158 */
159 #define TAB_HOTTRACK_TIMER 1
160 #define TAB_HOTTRACK_TIMER_INTERVAL 100 /* milliseconds */
161
162 static const WCHAR themeClass[] = { 'T','a','b',0 };
163
164 /******************************************************************************
165 * Prototypes
166 */
167 static void TAB_InvalidateTabArea(const TAB_INFO *);
168 static void TAB_EnsureSelectionVisible(TAB_INFO *);
169 static void TAB_DrawItemInterior(const TAB_INFO *, HDC, INT, RECT*);
170
171 static BOOL
172 TAB_SendSimpleNotify (const TAB_INFO *infoPtr, UINT code)
173 {
174 NMHDR nmhdr;
175
176 nmhdr.hwndFrom = infoPtr->hwnd;
177 nmhdr.idFrom = GetWindowLongPtrW(infoPtr->hwnd, GWLP_ID);
178 nmhdr.code = code;
179
180 return (BOOL) SendMessageW (infoPtr->hwndNotify, WM_NOTIFY,
181 nmhdr.idFrom, (LPARAM) &nmhdr);
182 }
183
184 static void
185 TAB_RelayEvent (HWND hwndTip, HWND hwndMsg, UINT uMsg,
186 WPARAM wParam, LPARAM lParam)
187 {
188 MSG msg;
189
190 msg.hwnd = hwndMsg;
191 msg.message = uMsg;
192 msg.wParam = wParam;
193 msg.lParam = lParam;
194 msg.time = GetMessageTime ();
195 msg.pt.x = (short)LOWORD(GetMessagePos ());
196 msg.pt.y = (short)HIWORD(GetMessagePos ());
197
198 SendMessageW (hwndTip, TTM_RELAYEVENT, 0, (LPARAM)&msg);
199 }
200
201 static void
202 TAB_DumpItemExternalT(const TCITEMW *pti, UINT iItem, BOOL isW)
203 {
204 if (TRACE_ON(tab)) {
205 TRACE("external tab %d, mask=0x%08x, dwState=0x%08x, dwStateMask=0x%08x, cchTextMax=0x%08x\n",
206 iItem, pti->mask, pti->dwState, pti->dwStateMask, pti->cchTextMax);
207 TRACE("external tab %d, iImage=%d, lParam=0x%08lx, pszTextW=%s\n",
208 iItem, pti->iImage, pti->lParam, isW ? debugstr_w(pti->pszText) : debugstr_a((LPSTR)pti->pszText));
209 }
210 }
211
212 static void
213 TAB_DumpItemInternal(const TAB_INFO *infoPtr, UINT iItem)
214 {
215 if (TRACE_ON(tab)) {
216 TAB_ITEM *ti;
217
218 ti = TAB_GetItem(infoPtr, iItem);
219 TRACE("tab %d, dwState=0x%08x, pszText=%s, iImage=%d\n",
220 iItem, ti->dwState, debugstr_w(ti->pszText), ti->iImage);
221 TRACE("tab %d, rect.left=%d, rect.top(row)=%d\n",
222 iItem, ti->rect.left, ti->rect.top);
223 }
224 }
225
226 /* RETURNS
227 * the index of the selected tab, or -1 if no tab is selected. */
228 static inline LRESULT TAB_GetCurSel (const TAB_INFO *infoPtr)
229 {
230 return infoPtr->iSelected;
231 }
232
233 /* RETURNS
234 * the index of the tab item that has the focus. */
235 static inline LRESULT
236 TAB_GetCurFocus (const TAB_INFO *infoPtr)
237 {
238 return infoPtr->uFocus;
239 }
240
241 static inline LRESULT TAB_GetToolTips (const TAB_INFO *infoPtr)
242 {
243 if (infoPtr == NULL) return 0;
244 return (LRESULT)infoPtr->hwndToolTip;
245 }
246
247 static inline LRESULT TAB_SetCurSel (TAB_INFO *infoPtr, INT iItem)
248 {
249 INT prevItem = infoPtr->iSelected;
250
251 if (iItem < 0)
252 infoPtr->iSelected=-1;
253 else if (iItem >= infoPtr->uNumItem)
254 return -1;
255 else {
256 if (infoPtr->iSelected != iItem) {
257 infoPtr->iSelected=iItem;
258 infoPtr->uFocus=iItem;
259 TAB_EnsureSelectionVisible(infoPtr);
260 TAB_InvalidateTabArea(infoPtr);
261 }
262 }
263 return prevItem;
264 }
265
266 static LRESULT TAB_SetCurFocus (TAB_INFO *infoPtr, INT iItem)
267 {
268 if (iItem < 0)
269 infoPtr->uFocus = -1;
270 else if (iItem < infoPtr->uNumItem) {
271 if (GetWindowLongW(infoPtr->hwnd, GWL_STYLE) & TCS_BUTTONS) {
272 FIXME("Should set input focus\n");
273 } else {
274 int oldFocus = infoPtr->uFocus;
275 if (infoPtr->iSelected != iItem || oldFocus == -1 ) {
276 infoPtr->uFocus = iItem;
277 if (oldFocus != -1) {
278 if (!TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGING)) {
279 infoPtr->iSelected = iItem;
280 TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGE);
281 }
282 else
283 infoPtr->iSelected = iItem;
284 TAB_EnsureSelectionVisible(infoPtr);
285 TAB_InvalidateTabArea(infoPtr);
286 }
287 }
288 }
289 }
290 return 0;
291 }
292
293 static inline LRESULT
294 TAB_SetToolTips (TAB_INFO *infoPtr, HWND hwndToolTip)
295 {
296 if (infoPtr)
297 infoPtr->hwndToolTip = hwndToolTip;
298 return 0;
299 }
300
301 static inline LRESULT
302 TAB_SetPadding (TAB_INFO *infoPtr, LPARAM lParam)
303 {
304 if (infoPtr)
305 {
306 infoPtr->uHItemPadding_s=LOWORD(lParam);
307 infoPtr->uVItemPadding_s=HIWORD(lParam);
308 }
309 return 0;
310 }
311
312 /******************************************************************************
313 * TAB_InternalGetItemRect
314 *
315 * This method will calculate the rectangle representing a given tab item in
316 * client coordinates. This method takes scrolling into account.
317 *
318 * This method returns TRUE if the item is visible in the window and FALSE
319 * if it is completely outside the client area.
320 */
321 static BOOL TAB_InternalGetItemRect(
322 const TAB_INFO* infoPtr,
323 INT itemIndex,
324 RECT* itemRect,
325 RECT* selectedRect)
326 {
327 RECT tmpItemRect,clientRect;
328 LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
329
330 /* Perform a sanity check and a trivial visibility check. */
331 if ( (infoPtr->uNumItem <= 0) ||
332 (itemIndex >= infoPtr->uNumItem) ||
333 (!((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)) && (itemIndex < infoPtr->leftmostVisible)) )
334 {
335 TRACE("Not Visible\n");
336 /* need to initialize these to empty rects */
337 if (itemRect)
338 {
339 memset(itemRect,0,sizeof(RECT));
340 itemRect->bottom = infoPtr->tabHeight;
341 }
342 if (selectedRect)
343 memset(selectedRect,0,sizeof(RECT));
344 return FALSE;
345 }
346
347 /*
348 * Avoid special cases in this procedure by assigning the "out"
349 * parameters if the caller didn't supply them
350 */
351 if (itemRect == NULL)
352 itemRect = &tmpItemRect;
353
354 /* Retrieve the unmodified item rect. */
355 *itemRect = TAB_GetItem(infoPtr,itemIndex)->rect;
356
357 /* calculate the times bottom and top based on the row */
358 GetClientRect(infoPtr->hwnd, &clientRect);
359
360 if ((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
361 {
362 itemRect->right = clientRect.right - SELECTED_TAB_OFFSET - itemRect->left * infoPtr->tabHeight -
363 ((lStyle & TCS_BUTTONS) ? itemRect->left * BUTTON_SPACINGX : 0);
364 itemRect->left = itemRect->right - infoPtr->tabHeight;
365 }
366 else if (lStyle & TCS_VERTICAL)
367 {
368 itemRect->left = clientRect.left + SELECTED_TAB_OFFSET + itemRect->left * infoPtr->tabHeight +
369 ((lStyle & TCS_BUTTONS) ? itemRect->left * BUTTON_SPACINGX : 0);
370 itemRect->right = itemRect->left + infoPtr->tabHeight;
371 }
372 else if (lStyle & TCS_BOTTOM)
373 {
374 itemRect->bottom = clientRect.bottom - itemRect->top * infoPtr->tabHeight -
375 ((lStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : SELECTED_TAB_OFFSET);
376 itemRect->top = itemRect->bottom - infoPtr->tabHeight;
377 }
378 else /* not TCS_BOTTOM and not TCS_VERTICAL */
379 {
380 itemRect->top = clientRect.top + itemRect->top * infoPtr->tabHeight +
381 ((lStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : SELECTED_TAB_OFFSET);
382 itemRect->bottom = itemRect->top + infoPtr->tabHeight;
383 }
384
385 /*
386 * "scroll" it to make sure the item at the very left of the
387 * tab control is the leftmost visible tab.
388 */
389 if(lStyle & TCS_VERTICAL)
390 {
391 OffsetRect(itemRect,
392 0,
393 -TAB_GetItem(infoPtr, infoPtr->leftmostVisible)->rect.top);
394
395 /*
396 * Move the rectangle so the first item is slightly offset from
397 * the bottom of the tab control.
398 */
399 OffsetRect(itemRect,
400 0,
401 SELECTED_TAB_OFFSET);
402
403 } else
404 {
405 OffsetRect(itemRect,
406 -TAB_GetItem(infoPtr, infoPtr->leftmostVisible)->rect.left,
407 0);
408
409 /*
410 * Move the rectangle so the first item is slightly offset from
411 * the left of the tab control.
412 */
413 OffsetRect(itemRect,
414 SELECTED_TAB_OFFSET,
415 0);
416 }
417 TRACE("item %d tab h=%d, rect=(%s)\n",
418 itemIndex, infoPtr->tabHeight, wine_dbgstr_rect(itemRect));
419
420 /* Now, calculate the position of the item as if it were selected. */
421 if (selectedRect!=NULL)
422 {
423 CopyRect(selectedRect, itemRect);
424
425 /* The rectangle of a selected item is a bit wider. */
426 if(lStyle & TCS_VERTICAL)
427 InflateRect(selectedRect, 0, SELECTED_TAB_OFFSET);
428 else
429 InflateRect(selectedRect, SELECTED_TAB_OFFSET, 0);
430
431 /* If it also a bit higher. */
432 if ((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
433 {
434 selectedRect->left -= 2; /* the border is thicker on the right */
435 selectedRect->right += SELECTED_TAB_OFFSET;
436 }
437 else if (lStyle & TCS_VERTICAL)
438 {
439 selectedRect->left -= SELECTED_TAB_OFFSET;
440 selectedRect->right += 1;
441 }
442 else if (lStyle & TCS_BOTTOM)
443 {
444 selectedRect->bottom += SELECTED_TAB_OFFSET;
445 }
446 else /* not TCS_BOTTOM and not TCS_VERTICAL */
447 {
448 selectedRect->top -= SELECTED_TAB_OFFSET;
449 selectedRect->bottom -= 1;
450 }
451 }
452
453 /* Check for visibility */
454 if (lStyle & TCS_VERTICAL)
455 return (itemRect->top < clientRect.bottom) && (itemRect->bottom > clientRect.top);
456 else
457 return (itemRect->left < clientRect.right) && (itemRect->right > clientRect.left);
458 }
459
460 static inline BOOL
461 TAB_GetItemRect(const TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
462 {
463 return TAB_InternalGetItemRect(infoPtr, (INT)wParam, (LPRECT)lParam, (LPRECT)NULL);
464 }
465
466 /******************************************************************************
467 * TAB_KeyUp
468 *
469 * This method is called to handle keyboard input
470 */
471 static LRESULT TAB_KeyUp(TAB_INFO* infoPtr, WPARAM keyCode)
472 {
473 int newItem = -1;
474
475 switch (keyCode)
476 {
477 case VK_LEFT:
478 newItem = infoPtr->uFocus - 1;
479 break;
480 case VK_RIGHT:
481 newItem = infoPtr->uFocus + 1;
482 break;
483 }
484
485 /*
486 * If we changed to a valid item, change the selection
487 */
488 if (newItem >= 0 &&
489 newItem < infoPtr->uNumItem &&
490 infoPtr->uFocus != newItem)
491 {
492 if (!TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGING))
493 {
494 infoPtr->iSelected = newItem;
495 infoPtr->uFocus = newItem;
496 TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGE);
497
498 TAB_EnsureSelectionVisible(infoPtr);
499 TAB_InvalidateTabArea(infoPtr);
500 }
501 }
502
503 return 0;
504 }
505
506 /******************************************************************************
507 * TAB_FocusChanging
508 *
509 * This method is called whenever the focus goes in or out of this control
510 * it is used to update the visual state of the control.
511 */
512 static void TAB_FocusChanging(const TAB_INFO *infoPtr)
513 {
514 RECT selectedRect;
515 BOOL isVisible;
516
517 /*
518 * Get the rectangle for the item.
519 */
520 isVisible = TAB_InternalGetItemRect(infoPtr,
521 infoPtr->uFocus,
522 NULL,
523 &selectedRect);
524
525 /*
526 * If the rectangle is not completely invisible, invalidate that
527 * portion of the window.
528 */
529 if (isVisible)
530 {
531 TRACE("invalidate (%s)\n", wine_dbgstr_rect(&selectedRect));
532 InvalidateRect(infoPtr->hwnd, &selectedRect, TRUE);
533 }
534 }
535
536 static INT TAB_InternalHitTest (const TAB_INFO *infoPtr, POINT pt, UINT *flags)
537 {
538 RECT rect;
539 INT iCount;
540
541 for (iCount = 0; iCount < infoPtr->uNumItem; iCount++)
542 {
543 TAB_InternalGetItemRect(infoPtr, iCount, &rect, NULL);
544
545 if (PtInRect(&rect, pt))
546 {
547 *flags = TCHT_ONITEM;
548 return iCount;
549 }
550 }
551
552 *flags = TCHT_NOWHERE;
553 return -1;
554 }
555
556 static inline LRESULT
557 TAB_HitTest (const TAB_INFO *infoPtr, LPTCHITTESTINFO lptest)
558 {
559 return TAB_InternalHitTest (infoPtr, lptest->pt, &lptest->flags);
560 }
561
562 /******************************************************************************
563 * TAB_NCHitTest
564 *
565 * Napster v2b5 has a tab control for its main navigation which has a client
566 * area that covers the whole area of the dialog pages.
567 * That's why it receives all msgs for that area and the underlying dialog ctrls
568 * are dead.
569 * So I decided that we should handle WM_NCHITTEST here and return
570 * HTTRANSPARENT if we don't hit the tab control buttons.
571 * FIXME: WM_NCHITTEST handling correct ? Fix it if you know that Windows
572 * doesn't do it that way. Maybe depends on tab control styles ?
573 */
574 static inline LRESULT
575 TAB_NCHitTest (const TAB_INFO *infoPtr, LPARAM lParam)
576 {
577 POINT pt;
578 UINT dummyflag;
579
580 pt.x = (short)LOWORD(lParam);
581 pt.y = (short)HIWORD(lParam);
582 ScreenToClient(infoPtr->hwnd, &pt);
583
584 if (TAB_InternalHitTest(infoPtr, pt, &dummyflag) == -1)
585 return HTTRANSPARENT;
586 else
587 return HTCLIENT;
588 }
589
590 static LRESULT
591 TAB_LButtonDown (TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
592 {
593 POINT pt;
594 INT newItem;
595 UINT dummy;
596
597 if (infoPtr->hwndToolTip)
598 TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
599 WM_LBUTTONDOWN, wParam, lParam);
600
601 if (GetWindowLongW(infoPtr->hwnd, GWL_STYLE) & TCS_FOCUSONBUTTONDOWN ) {
602 SetFocus (infoPtr->hwnd);
603 }
604
605 if (infoPtr->hwndToolTip)
606 TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
607 WM_LBUTTONDOWN, wParam, lParam);
608
609 pt.x = (short)LOWORD(lParam);
610 pt.y = (short)HIWORD(lParam);
611
612 newItem = TAB_InternalHitTest (infoPtr, pt, &dummy);
613
614 TRACE("On Tab, item %d\n", newItem);
615
616 if (newItem != -1 && infoPtr->iSelected != newItem)
617 {
618 if (!TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGING))
619 {
620 infoPtr->iSelected = newItem;
621 infoPtr->uFocus = newItem;
622 TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGE);
623
624 TAB_EnsureSelectionVisible(infoPtr);
625
626 TAB_InvalidateTabArea(infoPtr);
627 }
628 }
629 return 0;
630 }
631
632 static inline LRESULT
633 TAB_LButtonUp (const TAB_INFO *infoPtr)
634 {
635 TAB_SendSimpleNotify(infoPtr, NM_CLICK);
636
637 return 0;
638 }
639
640 static inline LRESULT
641 TAB_RButtonDown (const TAB_INFO *infoPtr)
642 {
643 TAB_SendSimpleNotify(infoPtr, NM_RCLICK);
644 return 0;
645 }
646
647 /******************************************************************************
648 * TAB_DrawLoneItemInterior
649 *
650 * This calls TAB_DrawItemInterior. However, TAB_DrawItemInterior is normally
651 * called by TAB_DrawItem which is normally called by TAB_Refresh which sets
652 * up the device context and font. This routine does the same setup but
653 * only calls TAB_DrawItemInterior for the single specified item.
654 */
655 static void
656 TAB_DrawLoneItemInterior(const TAB_INFO* infoPtr, int iItem)
657 {
658 HDC hdc = GetDC(infoPtr->hwnd);
659 RECT r, rC;
660
661 /* Clip UpDown control to not draw over it */
662 if (infoPtr->needsScrolling)
663 {
664 GetWindowRect(infoPtr->hwnd, &rC);
665 GetWindowRect(infoPtr->hwndUpDown, &r);
666 ExcludeClipRect(hdc, r.left - rC.left, r.top - rC.top, r.right - rC.left, r.bottom - rC.top);
667 }
668 TAB_DrawItemInterior(infoPtr, hdc, iItem, NULL);
669 ReleaseDC(infoPtr->hwnd, hdc);
670 }
671
672 /* update a tab after hottracking - invalidate it or just redraw the interior,
673 * based on whether theming is used or not */
674 static inline void hottrack_refresh(const TAB_INFO *infoPtr, int tabIndex)
675 {
676 if (tabIndex == -1) return;
677
678 if (GetWindowTheme (infoPtr->hwnd))
679 {
680 RECT rect;
681 TAB_InternalGetItemRect(infoPtr, tabIndex, &rect, NULL);
682 InvalidateRect (infoPtr->hwnd, &rect, FALSE);
683 }
684 else
685 TAB_DrawLoneItemInterior(infoPtr, tabIndex);
686 }
687
688 /******************************************************************************
689 * TAB_HotTrackTimerProc
690 *
691 * When a mouse-move event causes a tab to be highlighted (hot-tracking), a
692 * timer is setup so we can check if the mouse is moved out of our window.
693 * (We don't get an event when the mouse leaves, the mouse-move events just
694 * stop being delivered to our window and just start being delivered to
695 * another window.) This function is called when the timer triggers so
696 * we can check if the mouse has left our window. If so, we un-highlight
697 * the hot-tracked tab.
698 */
699 static void CALLBACK
700 TAB_HotTrackTimerProc
701 (
702 HWND hwnd, /* handle of window for timer messages */
703 UINT uMsg, /* WM_TIMER message */
704 UINT_PTR idEvent, /* timer identifier */
705 DWORD dwTime /* current system time */
706 )
707 {
708 TAB_INFO* infoPtr = TAB_GetInfoPtr(hwnd);
709
710 if (infoPtr != NULL && infoPtr->iHotTracked >= 0)
711 {
712 POINT pt;
713
714 /*
715 ** If we can't get the cursor position, or if the cursor is outside our
716 ** window, we un-highlight the hot-tracked tab. Note that the cursor is
717 ** "outside" even if it is within our bounding rect if another window
718 ** overlaps. Note also that the case where the cursor stayed within our
719 ** window but has moved off the hot-tracked tab will be handled by the
720 ** WM_MOUSEMOVE event.
721 */
722 if (!GetCursorPos(&pt) || WindowFromPoint(pt) != hwnd)
723 {
724 /* Redraw iHotTracked to look normal */
725 INT iRedraw = infoPtr->iHotTracked;
726 infoPtr->iHotTracked = -1;
727 hottrack_refresh (infoPtr, iRedraw);
728
729 /* Kill this timer */
730 KillTimer(hwnd, TAB_HOTTRACK_TIMER);
731 }
732 }
733 }
734
735 /******************************************************************************
736 * TAB_RecalcHotTrack
737 *
738 * If a tab control has the TCS_HOTTRACK style, then the tab under the mouse
739 * should be highlighted. This function determines which tab in a tab control,
740 * if any, is under the mouse and records that information. The caller may
741 * supply output parameters to receive the item number of the tab item which
742 * was highlighted but isn't any longer and of the tab item which is now
743 * highlighted but wasn't previously. The caller can use this information to
744 * selectively redraw those tab items.
745 *
746 * If the caller has a mouse position, it can supply it through the pos
747 * parameter. For example, TAB_MouseMove does this. Otherwise, the caller
748 * supplies NULL and this function determines the current mouse position
749 * itself.
750 */
751 static void
752 TAB_RecalcHotTrack
753 (
754 TAB_INFO* infoPtr,
755 const LPARAM* pos,
756 int* out_redrawLeave,
757 int* out_redrawEnter
758 )
759 {
760 int item = -1;
761
762
763 if (out_redrawLeave != NULL)
764 *out_redrawLeave = -1;
765 if (out_redrawEnter != NULL)
766 *out_redrawEnter = -1;
767
768 if ((GetWindowLongW(infoPtr->hwnd, GWL_STYLE) & TCS_HOTTRACK)
769 || GetWindowTheme (infoPtr->hwnd))
770 {
771 POINT pt;
772 UINT flags;
773
774 if (pos == NULL)
775 {
776 GetCursorPos(&pt);
777 ScreenToClient(infoPtr->hwnd, &pt);
778 }
779 else
780 {
781 pt.x = (short)LOWORD(*pos);
782 pt.y = (short)HIWORD(*pos);
783 }
784
785 item = TAB_InternalHitTest(infoPtr, pt, &flags);
786 }
787
788 if (item != infoPtr->iHotTracked)
789 {
790 if (infoPtr->iHotTracked >= 0)
791 {
792 /* Mark currently hot-tracked to be redrawn to look normal */
793 if (out_redrawLeave != NULL)
794 *out_redrawLeave = infoPtr->iHotTracked;
795
796 if (item < 0)
797 {
798 /* Kill timer which forces recheck of mouse pos */
799 KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
800 }
801 }
802 else
803 {
804 /* Start timer so we recheck mouse pos */
805 UINT timerID = SetTimer
806 (
807 infoPtr->hwnd,
808 TAB_HOTTRACK_TIMER,
809 TAB_HOTTRACK_TIMER_INTERVAL,
810 TAB_HotTrackTimerProc
811 );
812
813 if (timerID == 0)
814 return; /* Hot tracking not available */
815 }
816
817 infoPtr->iHotTracked = item;
818
819 if (item >= 0)
820 {
821 /* Mark new hot-tracked to be redrawn to look highlighted */
822 if (out_redrawEnter != NULL)
823 *out_redrawEnter = item;
824 }
825 }
826 }
827
828 /******************************************************************************
829 * TAB_MouseMove
830 *
831 * Handles the mouse-move event. Updates tooltips. Updates hot-tracking.
832 */
833 static LRESULT
834 TAB_MouseMove (TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
835 {
836 int redrawLeave;
837 int redrawEnter;
838
839 if (infoPtr->hwndToolTip)
840 TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
841 WM_LBUTTONDOWN, wParam, lParam);
842
843 /* Determine which tab to highlight. Redraw tabs which change highlight
844 ** status. */
845 TAB_RecalcHotTrack(infoPtr, &lParam, &redrawLeave, &redrawEnter);
846
847 hottrack_refresh (infoPtr, redrawLeave);
848 hottrack_refresh (infoPtr, redrawEnter);
849
850 return 0;
851 }
852
853 /******************************************************************************
854 * TAB_AdjustRect
855 *
856 * Calculates the tab control's display area given the window rectangle or
857 * the window rectangle given the requested display rectangle.
858 */
859 static LRESULT TAB_AdjustRect(const TAB_INFO *infoPtr, WPARAM fLarger, LPRECT prc)
860 {
861 DWORD lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
862 LONG *iRightBottom, *iLeftTop;
863
864 TRACE ("hwnd=%p fLarger=%ld (%s)\n", infoPtr->hwnd, fLarger,
865 wine_dbgstr_rect(prc));
866
867 if(lStyle & TCS_VERTICAL)
868 {
869 iRightBottom = &(prc->right);
870 iLeftTop = &(prc->left);
871 }
872 else
873 {
874 iRightBottom = &(prc->bottom);
875 iLeftTop = &(prc->top);
876 }
877
878 if (fLarger) /* Go from display rectangle */
879 {
880 /* Add the height of the tabs. */
881 if (lStyle & TCS_BOTTOM)
882 *iRightBottom += infoPtr->tabHeight * infoPtr->uNumRows;
883 else
884 *iLeftTop -= infoPtr->tabHeight * infoPtr->uNumRows +
885 ((lStyle & TCS_BUTTONS)? 3 * (infoPtr->uNumRows - 1) : 0);
886
887 /* Inflate the rectangle for the padding */
888 InflateRect(prc, DISPLAY_AREA_PADDINGX, DISPLAY_AREA_PADDINGY);
889
890 /* Inflate for the border */
891 InflateRect(prc, CONTROL_BORDER_SIZEX, CONTROL_BORDER_SIZEY);
892 }
893 else /* Go from window rectangle. */
894 {
895 /* Deflate the rectangle for the border */
896 InflateRect(prc, -CONTROL_BORDER_SIZEX, -CONTROL_BORDER_SIZEY);
897
898 /* Deflate the rectangle for the padding */
899 InflateRect(prc, -DISPLAY_AREA_PADDINGX, -DISPLAY_AREA_PADDINGY);
900
901 /* Remove the height of the tabs. */
902 if (lStyle & TCS_BOTTOM)
903 *iRightBottom -= infoPtr->tabHeight * infoPtr->uNumRows;
904 else
905 *iLeftTop += (infoPtr->tabHeight) * infoPtr->uNumRows +
906 ((lStyle & TCS_BUTTONS)? 3 * (infoPtr->uNumRows - 1) : 0);
907 }
908
909 return 0;
910 }
911
912 /******************************************************************************
913 * TAB_OnHScroll
914 *
915 * This method will handle the notification from the scroll control and
916 * perform the scrolling operation on the tab control.
917 */
918 static LRESULT TAB_OnHScroll(TAB_INFO *infoPtr, int nScrollCode, int nPos, HWND hwndScroll)
919 {
920 if(nScrollCode == SB_THUMBPOSITION && nPos != infoPtr->leftmostVisible)
921 {
922 if(nPos < infoPtr->leftmostVisible)
923 infoPtr->leftmostVisible--;
924 else
925 infoPtr->leftmostVisible++;
926
927 TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
928 TAB_InvalidateTabArea(infoPtr);
929 SendMessageW(infoPtr->hwndUpDown, UDM_SETPOS, 0,
930 MAKELONG(infoPtr->leftmostVisible, 0));
931 }
932
933 return 0;
934 }
935
936 /******************************************************************************
937 * TAB_SetupScrolling
938 *
939 * This method will check the current scrolling state and make sure the
940 * scrolling control is displayed (or not).
941 */
942 static void TAB_SetupScrolling(
943 HWND hwnd,
944 TAB_INFO* infoPtr,
945 const RECT* clientRect)
946 {
947 static const WCHAR msctls_updown32W[] = { 'm','s','c','t','l','s','_','u','p','d','o','w','n','3','2',0 };
948 static const WCHAR emptyW[] = { 0 };
949 INT maxRange = 0;
950 DWORD lStyle = GetWindowLongW(hwnd, GWL_STYLE);
951
952 if (infoPtr->needsScrolling)
953 {
954 RECT controlPos;
955 INT vsize, tabwidth;
956
957 /*
958 * Calculate the position of the scroll control.
959 */
960 if(lStyle & TCS_VERTICAL)
961 {
962 controlPos.right = clientRect->right;
963 controlPos.left = controlPos.right - 2 * GetSystemMetrics(SM_CXHSCROLL);
964
965 if (lStyle & TCS_BOTTOM)
966 {
967 controlPos.top = clientRect->bottom - infoPtr->tabHeight;
968 controlPos.bottom = controlPos.top + GetSystemMetrics(SM_CYHSCROLL);
969 }
970 else
971 {
972 controlPos.bottom = clientRect->top + infoPtr->tabHeight;
973 controlPos.top = controlPos.bottom - GetSystemMetrics(SM_CYHSCROLL);
974 }
975 }
976 else
977 {
978 controlPos.right = clientRect->right;
979 controlPos.left = controlPos.right - 2 * GetSystemMetrics(SM_CXHSCROLL);
980
981 if (lStyle & TCS_BOTTOM)
982 {
983 controlPos.top = clientRect->bottom - infoPtr->tabHeight;
984 controlPos.bottom = controlPos.top + GetSystemMetrics(SM_CYHSCROLL);
985 }
986 else
987 {
988 controlPos.bottom = clientRect->top + infoPtr->tabHeight;
989 controlPos.top = controlPos.bottom - GetSystemMetrics(SM_CYHSCROLL);
990 }
991 }
992
993 /*
994 * If we don't have a scroll control yet, we want to create one.
995 * If we have one, we want to make sure it's positioned properly.
996 */
997 if (infoPtr->hwndUpDown==0)
998 {
999 infoPtr->hwndUpDown = CreateWindowW(msctls_updown32W, emptyW,
1000 WS_VISIBLE | WS_CHILD | UDS_HORZ,
1001 controlPos.left, controlPos.top,
1002 controlPos.right - controlPos.left,
1003 controlPos.bottom - controlPos.top,
1004 hwnd, NULL, NULL, NULL);
1005 }
1006 else
1007 {
1008 SetWindowPos(infoPtr->hwndUpDown,
1009 NULL,
1010 controlPos.left, controlPos.top,
1011 controlPos.right - controlPos.left,
1012 controlPos.bottom - controlPos.top,
1013 SWP_SHOWWINDOW | SWP_NOZORDER);
1014 }
1015
1016 /* Now calculate upper limit of the updown control range.
1017 * We do this by calculating how many tabs will be offscreen when the
1018 * last tab is visible.
1019 */
1020 if(infoPtr->uNumItem)
1021 {
1022 vsize = clientRect->right - (controlPos.right - controlPos.left + 1);
1023 maxRange = infoPtr->uNumItem;
1024 tabwidth = TAB_GetItem(infoPtr, infoPtr->uNumItem - 1)->rect.right;
1025
1026 for(; maxRange > 0; maxRange--)
1027 {
1028 if(tabwidth - TAB_GetItem(infoPtr,maxRange - 1)->rect.left > vsize)
1029 break;
1030 }
1031
1032 if(maxRange == infoPtr->uNumItem)
1033 maxRange--;
1034 }
1035 }
1036 else
1037 {
1038 /* If we once had a scroll control... hide it */
1039 if (infoPtr->hwndUpDown!=0)
1040 ShowWindow(infoPtr->hwndUpDown, SW_HIDE);
1041 }
1042 if (infoPtr->hwndUpDown)
1043 SendMessageW(infoPtr->hwndUpDown, UDM_SETRANGE32, 0, maxRange);