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