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