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

Last change on this file since 6644 was 6644, checked in by bird, 24 years ago

Added $Id:$ keyword.

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