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