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