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