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