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