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