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