source: trunk/src/comctl32/tab.c@ 8382

Last change on this file since 8382 was 8382, checked in by sandervl, 23 years ago

merge with latest Wine

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