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