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