~ [ source navigation ] ~ [ diff markup ] ~ [ identifier search ] ~ [ freetext search ] ~ [ file search ] ~

Wine Cross Reference
wine/dlls/comctl32/tab.c

Version: ~ [ wine-1.1.3 ] ~ [ wine-1.1.2 ] ~ [ wine-1.1.1 ] ~ [ wine-1.1.0 ] ~ [ wine-1.0 ] ~ [ wine-1.0-rc5 ] ~ [ wine-1.0-rc4 ] ~ [ wine-1.0-rc3 ] ~ [ wine-1.0-rc2 ] ~ [ wine-1.0-rc1 ] ~ [ wine-0.9.61 ] ~ [ wine-0.9.60 ] ~ [ wine-0.9.59 ] ~ [ wine-0.9.58 ] ~ [ wine-0.9.57 ] ~ [ wine-0.9.56 ] ~ [ wine-0.9.55 ] ~ [ wine-0.9.54 ] ~ [ wine-0.9.53 ] ~ [ wine-0.9.52 ] ~ [ wine-0.9.51 ] ~ [ wine-0.9.50 ] ~ [ wine-0.9.49 ] ~ [ wine-0.9.48 ] ~ [ wine-0.9.47 ] ~ [ wine-0.9.46 ] ~ [ wine-0.9.45 ] ~ [ wine-0.9.44 ] ~ [ wine-0.9.43 ] ~ [ wine-0.9.42 ] ~ [ wine-0.9.41 ] ~ [ wine-0.9.40 ] ~ [ wine-0.9.39 ] ~ [ wine-0.9.38 ] ~ [ wine-0.9.37 ] ~ [ wine-0.9.36 ] ~ [ wine-0.9.35 ] ~ [ wine-0.9.34 ] ~ [ wine-0.9.33 ] ~ [ wine-0.9.32 ] ~ [ wine-0.9.31 ] ~ [ wine-0.9.30 ] ~ [ wine-0.9.29 ] ~ [ wine-0.9.28 ] ~ [ wine-0.9.27 ] ~ [ wine-0.9.26 ] ~ [ wine-0.9.25 ] ~ [ wine-0.9.24 ] ~ [ wine-0.9.23 ] ~ [ wine-0.9.22 ] ~ [ wine-0.9.21 ] ~ [ wine-0.9.20 ] ~ [ wine-0.9.19 ] ~ [ wine-0.9.18 ] ~ [ wine-0.9.17 ] ~ [ wine-0.9.16 ] ~ [ wine-0.9.15 ] ~ [ wine-0.9.14 ] ~ [ wine-0.9.13 ] ~ [ wine-0.9.12 ] ~ [ wine-0.9.11 ] ~ [ wine-0.9.10 ] ~ [ wine-0.9.9 ] ~ [ wine-0.9.8 ] ~ [ wine-0.9.7 ] ~ [ wine-0.9.6 ] ~ [ wine-0.9.5 ] ~ [ wine-0.9.4 ] ~ [ wine-0.9.3 ] ~ [ wine-0.9.2 ] ~ [ wine-0.9.1 ] ~ [ wine-0.9 ] ~ [ wine20050930 ] ~ [ wine20050830 ] ~ [ wine20050725 ] ~ [ wine20050628 ] ~ [ wine20050524 ] ~ [ wine20050419 ] ~ [ wine20050310 ] ~ [ wine20050211 ] ~ [ wine20050111 ] ~ [ wine20041201 ] ~ [ wine20041019 ] ~ [ wine20040914 ] ~ [ wine20040813 ] ~ [ wine20040716 ] ~ [ wine20040615 ] ~ [ wine20040505 ] ~ [ wine20040408 ] ~ [ wine20040309 ] ~ [ wine20040213 ] ~ [ wine20040121 ] ~ [ wine20031212 ] ~ [ wine20031118 ] ~ [ wine20031016 ] ~ [ wine20030911 ] ~ [ wine20030813 ] ~ [ wine20030709 ] ~ [ wine20030618 ] ~ [ wine20030508 ] ~ [ wine20030408 ] ~ [ wine20030318 ] ~ [ wine20030219 ] ~ [ wine20030115 ] ~ [ wine20021219 ] ~ [ wine20021125 ] ~ [ wine20021031 ] ~ [ wine20021007 ] ~ [ wine20020904 ] ~ [ wine20020804 ] ~ [ wine20020710 ] ~ [ wine20020605 ] ~ [ wine20020509 ] ~ [ wine20020411 ] ~ [ wine20020310 ] ~ [ wine20020228 ] ~ [ wine20011226 ] ~ [ wine20011108 ] ~ [ wine20011004 ] ~ [ wine20010824 ] ~ [ wine20010731 ] ~ [ wine20010629 ] ~ [ wine20010510 ] ~ [ wine20010418 ] ~ [ wine20010326 ] ~ [ wine20010305 ] ~ [ wine20010216 ] ~ [ wine20010112 ] ~ [ wine20001222 ] ~ [ wine20001202 ] ~ [ wine20001026 ] ~ [ wine20001002 ] ~ [ wine20000909 ] ~ [ wine20000821 ] ~ [ wine20000801 ] ~ [ wine20000716 ] ~ [ wine20000326 ] ~ [ wine20000227 ] ~ [ wine20000130 ] ~ [ wine20000109 ] ~

  1 /*
  2  * Tab control
  3  *
  4  * Copyright 1998 Anders Carlsson
  5  * Copyright 1999 Alex Priem <alexp@sci.kun.nl>
  6  * Copyright 1999 Francis Beaudet
  7  * Copyright 2003 Vitaliy Margolen
  8  *
  9  * This library is free software; you can redistribute it and/or
 10  * modify it under the terms of the GNU Lesser General Public
 11  * License as published by the Free Software Foundation; either
 12  * version 2.1 of the License, or (at your option) any later version.
 13  *
 14  * This library is distributed in the hope that it will be useful,
 15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
 16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 17  * Lesser General Public License for more details.
 18  *
 19  * You should have received a copy of the GNU Lesser General Public
 20  * License along with this library; if not, write to the Free Software
 21  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 22  *
 23  * NOTES
 24  *
 25  * This code was audited for completeness against the documented features
 26  * of Comctl32.dll version 6.0 on May. 20, 2005, by James Hawkins.
 27  *
 28  * Unless otherwise noted, we believe this code to be complete, as per
 29  * the specification mentioned above.
 30  * If you discover missing features, or bugs, please note them below.
 31  *
 32  * TODO:
 33  *
 34  *  Styles:
 35  *   TCS_MULTISELECT
 36  *   TCS_RIGHT
 37  *   TCS_RIGHTJUSTIFY
 38  *   TCS_SCROLLOPPOSITE
 39  *   TCS_SINGLELINE
 40  *   TCIF_RTLREADING
 41  *
 42  *  Extended Styles:
 43  *   TCS_EX_FLATSEPARATORS
 44  *   TCS_EX_REGISTERDROP
 45  *
 46  *  States:
 47  *   TCIS_BUTTONPRESSED
 48  *
 49  *  Notifications:
 50  *   NM_RELEASEDCAPTURE
 51  *   TCN_FOCUSCHANGE
 52  *   TCN_GETOBJECT
 53  *   TCN_KEYDOWN
 54  *
 55  *  Messages:
 56  *   TCM_REMOVEIMAGE
 57  *   TCM_DESELECTALL
 58  *   TCM_GETEXTENDEDSTYLE
 59  *   TCM_SETEXTENDEDSTYLE
 60  *
 61  *  Macros:
 62  *   TabCtrl_AdjustRect
 63  *
 64  */
 65 
 66 #include <stdarg.h>
 67 #include <string.h>
 68 
 69 #include "windef.h"
 70 #include "winbase.h"
 71 #include "wingdi.h"
 72 #include "winuser.h"
 73 #include "winnls.h"
 74 #include "commctrl.h"
 75 #include "comctl32.h"
 76 #include "uxtheme.h"
 77 #include "tmschema.h"
 78 #include "wine/debug.h"
 79 #include <math.h>
 80 
 81 WINE_DEFAULT_DEBUG_CHANNEL(tab);
 82 
 83 typedef struct
 84 {
 85   DWORD  dwState;
 86   LPWSTR pszText;
 87   INT    iImage;
 88   RECT   rect;      /* bounding rectangle of the item relative to the
 89                      * leftmost item (the leftmost item, 0, would have a
 90                      * "left" member of 0 in this rectangle)
 91                      *
 92                      * additionally the top member holds the row number
 93                      * and bottom is unused and should be 0 */
 94   BYTE   extra[1];  /* Space for caller supplied info, variable size */
 95 } TAB_ITEM;
 96 
 97 /* The size of a tab item depends on how much extra data is requested */
 98 #define TAB_ITEM_SIZE(infoPtr) (FIELD_OFFSET(TAB_ITEM, extra[(infoPtr)->cbInfo]))
 99 
100 typedef struct
101 {
102   HWND       hwnd;            /* Tab control window */
103   HWND       hwndNotify;      /* notification window (parent) */
104   UINT       uNumItem;        /* number of tab items */
105   UINT       uNumRows;        /* number of tab rows */
106   INT        tabHeight;       /* height of the tab row */
107   INT        tabWidth;        /* width of tabs */
108   INT        tabMinWidth;     /* minimum width of items */
109   USHORT     uHItemPadding;   /* amount of horizontal padding, in pixels */
110   USHORT     uVItemPadding;   /* amount of vertical padding, in pixels */
111   USHORT     uHItemPadding_s; /* Set amount of horizontal padding, in pixels */
112   USHORT     uVItemPadding_s; /* Set amount of vertical padding, in pixels */
113   HFONT      hFont;           /* handle to the current font */
114   HCURSOR    hcurArrow;       /* handle to the current cursor */
115   HIMAGELIST himl;            /* handle to an image list (may be 0) */
116   HWND       hwndToolTip;     /* handle to tab's tooltip */
117   INT        leftmostVisible; /* Used for scrolling, this member contains
118                                * the index of the first visible item */
119   INT        iSelected;       /* the currently selected item */
120   INT        iHotTracked;     /* the highlighted item under the mouse */
121   INT        uFocus;          /* item which has the focus */
122   TAB_ITEM*  items;           /* pointer to an array of TAB_ITEM's */
123   BOOL       DoRedraw;        /* flag for redrawing when tab contents is changed*/
124   BOOL       needsScrolling;  /* TRUE if the size of the tabs is greater than
125                                * the size of the control */
126   BOOL       fHeightSet;      /* was the height of the tabs explicitly set? */
127   BOOL       bUnicode;        /* Unicode control? */
128   HWND       hwndUpDown;      /* Updown control used for scrolling */
129   INT        cbInfo;          /* Number of bytes of caller supplied info per tab */
130 } TAB_INFO;
131 
132 /******************************************************************************
133  * Positioning constants
134  */
135 #define SELECTED_TAB_OFFSET     2
136 #define ROUND_CORNER_SIZE       2
137 #define DISPLAY_AREA_PADDINGX   2
138 #define DISPLAY_AREA_PADDINGY   2
139 #define CONTROL_BORDER_SIZEX    2
140 #define CONTROL_BORDER_SIZEY    2
141 #define BUTTON_SPACINGX         3
142 #define BUTTON_SPACINGY         3
143 #define FLAT_BTN_SPACINGX       8
144 #define DEFAULT_MIN_TAB_WIDTH   54
145 #define DEFAULT_TAB_WIDTH_FIXED 96
146 #define DEFAULT_PADDING_X       6
147 #define EXTRA_ICON_PADDING      3
148 
149 #define TAB_GetInfoPtr(hwnd) ((TAB_INFO *)GetWindowLongPtrW(hwnd,0))
150 /* Since items are variable sized, cannot directly access them */
151 #define TAB_GetItem(info,i) \
152   ((TAB_ITEM*)((LPBYTE)info->items + (i) * TAB_ITEM_SIZE(info)))
153 
154 #define GET_DEFAULT_MIN_TAB_WIDTH(infoPtr) (DEFAULT_MIN_TAB_WIDTH - (DEFAULT_PADDING_X - (infoPtr)->uHItemPadding) * 2)
155 
156 /******************************************************************************
157  * Hot-tracking timer constants
158  */
159 #define TAB_HOTTRACK_TIMER            1
160 #define TAB_HOTTRACK_TIMER_INTERVAL   100   /* milliseconds */
161 
162 static const WCHAR themeClass[] = { 'T','a','b',0 };
163 
164 /******************************************************************************
165  * Prototypes
166  */
167 static void TAB_InvalidateTabArea(const TAB_INFO *);
168 static void TAB_EnsureSelectionVisible(TAB_INFO *);
169 static void TAB_DrawItemInterior(const TAB_INFO *, HDC, INT, RECT*);
170 
171 static BOOL
172 TAB_SendSimpleNotify (const TAB_INFO *infoPtr, UINT code)
173 {
174     NMHDR nmhdr;
175 
176     nmhdr.hwndFrom = infoPtr->hwnd;
177     nmhdr.idFrom = GetWindowLongPtrW(infoPtr->hwnd, GWLP_ID);
178     nmhdr.code = code;
179 
180     return (BOOL) SendMessageW (infoPtr->hwndNotify, WM_NOTIFY,
181             nmhdr.idFrom, (LPARAM) &nmhdr);
182 }
183 
184 static void
185 TAB_RelayEvent (HWND hwndTip, HWND hwndMsg, UINT uMsg,
186             WPARAM wParam, LPARAM lParam)
187 {
188     MSG msg;
189 
190     msg.hwnd = hwndMsg;
191     msg.message = uMsg;
192     msg.wParam = wParam;
193     msg.lParam = lParam;
194     msg.time = GetMessageTime ();
195     msg.pt.x = (short)LOWORD(GetMessagePos ());
196     msg.pt.y = (short)HIWORD(GetMessagePos ());
197 
198     SendMessageW (hwndTip, TTM_RELAYEVENT, 0, (LPARAM)&msg);
199 }
200 
201 static void
202 TAB_DumpItemExternalT(const TCITEMW *pti, UINT iItem, BOOL isW)
203 {
204     if (TRACE_ON(tab)) {
205         TRACE("external tab %d, mask=0x%08x, dwState=0x%08x, dwStateMask=0x%08x, cchTextMax=0x%08x\n",
206               iItem, pti->mask, pti->dwState, pti->dwStateMask, pti->cchTextMax);
207         TRACE("external tab %d,   iImage=%d, lParam=0x%08lx, pszTextW=%s\n",
208               iItem, pti->iImage, pti->lParam, isW ? debugstr_w(pti->pszText) : debugstr_a((LPSTR)pti->pszText));
209     }
210 }
211 
212 static void
213 TAB_DumpItemInternal(const TAB_INFO *infoPtr, UINT iItem)
214 {
215     if (TRACE_ON(tab)) {
216         TAB_ITEM *ti;
217 
218         ti = TAB_GetItem(infoPtr, iItem);
219         TRACE("tab %d, dwState=0x%08x, pszText=%s, iImage=%d\n",
220               iItem, ti->dwState, debugstr_w(ti->pszText), ti->iImage);
221         TRACE("tab %d, rect.left=%d, rect.top(row)=%d\n",
222               iItem, ti->rect.left, ti->rect.top);
223     }
224 }
225 
226 /* RETURNS
227  *   the index of the selected tab, or -1 if no tab is selected. */
228 static inline LRESULT TAB_GetCurSel (const TAB_INFO *infoPtr)
229 {
230     return infoPtr->iSelected;
231 }
232 
233 /* RETURNS
234  *   the index of the tab item that has the focus. */
235 static inline LRESULT
236 TAB_GetCurFocus (const TAB_INFO *infoPtr)
237 {
238     return infoPtr->uFocus;
239 }
240 
241 static inline LRESULT TAB_GetToolTips (const TAB_INFO *infoPtr)
242 {
243     if (infoPtr == NULL) return 0;
244     return (LRESULT)infoPtr->hwndToolTip;
245 }
246 
247 static inline LRESULT TAB_SetCurSel (TAB_INFO *infoPtr, INT iItem)
248 {
249   INT prevItem = infoPtr->iSelected;
250 
251   if (iItem < 0)
252       infoPtr->iSelected=-1;
253   else if (iItem >= infoPtr->uNumItem)
254       return -1;
255   else {
256       if (infoPtr->iSelected != iItem) {
257           infoPtr->iSelected=iItem;
258           infoPtr->uFocus=iItem;
259           TAB_EnsureSelectionVisible(infoPtr);
260           TAB_InvalidateTabArea(infoPtr);
261       }
262   }
263   return prevItem;
264 }
265 
266 static LRESULT TAB_SetCurFocus (TAB_INFO *infoPtr, INT iItem)
267 {
268   if (iItem < 0)
269       infoPtr->uFocus = -1;
270   else if (iItem < infoPtr->uNumItem) {
271     if (GetWindowLongW(infoPtr->hwnd, GWL_STYLE) & TCS_BUTTONS) {
272       FIXME("Should set input focus\n");
273     } else {
274       int oldFocus = infoPtr->uFocus;
275       if (infoPtr->iSelected != iItem || oldFocus == -1 ) {
276         infoPtr->uFocus = iItem;
277         if (oldFocus != -1) {
278           if (!TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGING))  {
279             infoPtr->iSelected = iItem;
280             TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGE);
281           }
282           else
283             infoPtr->iSelected = iItem;
284           TAB_EnsureSelectionVisible(infoPtr);
285           TAB_InvalidateTabArea(infoPtr);
286         }
287       }
288     }
289   }
290   return 0;
291 }
292 
293 static inline LRESULT
294 TAB_SetToolTips (TAB_INFO *infoPtr, HWND hwndToolTip)
295 {
296     if (infoPtr)
297         infoPtr->hwndToolTip = hwndToolTip;
298     return 0;
299 }
300 
301 static inline LRESULT
302 TAB_SetPadding (TAB_INFO *infoPtr, LPARAM lParam)
303 {
304     if (infoPtr)
305     {
306         infoPtr->uHItemPadding_s=LOWORD(lParam);
307         infoPtr->uVItemPadding_s=HIWORD(lParam);
308     }
309     return 0;
310 }
311 
312 /******************************************************************************
313  * TAB_InternalGetItemRect
314  *
315  * This method will calculate the rectangle representing a given tab item in
316  * client coordinates. This method takes scrolling into account.
317  *
318  * This method returns TRUE if the item is visible in the window and FALSE
319  * if it is completely outside the client area.
320  */
321 static BOOL TAB_InternalGetItemRect(
322   const TAB_INFO* infoPtr,
323   INT         itemIndex,
324   RECT*       itemRect,
325   RECT*       selectedRect)
326 {
327   RECT tmpItemRect,clientRect;
328   LONG lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
329 
330   /* Perform a sanity check and a trivial visibility check. */
331   if ( (infoPtr->uNumItem <= 0) ||
332        (itemIndex >= infoPtr->uNumItem) ||
333        (!((lStyle & TCS_MULTILINE) || (lStyle & TCS_VERTICAL)) && (itemIndex < infoPtr->leftmostVisible)) )
334     {
335         TRACE("Not Visible\n");
336         /* need to initialize these to empty rects */
337         if (itemRect)
338         {
339             memset(itemRect,0,sizeof(RECT));
340             itemRect->bottom = infoPtr->tabHeight;
341         }
342         if (selectedRect)
343             memset(selectedRect,0,sizeof(RECT));
344         return FALSE;
345     }
346 
347   /*
348    * Avoid special cases in this procedure by assigning the "out"
349    * parameters if the caller didn't supply them
350    */
351   if (itemRect == NULL)
352     itemRect = &tmpItemRect;
353 
354   /* Retrieve the unmodified item rect. */
355   *itemRect = TAB_GetItem(infoPtr,itemIndex)->rect;
356 
357   /* calculate the times bottom and top based on the row */
358   GetClientRect(infoPtr->hwnd, &clientRect);
359 
360   if ((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
361   {
362     itemRect->right  = clientRect.right - SELECTED_TAB_OFFSET - itemRect->left * infoPtr->tabHeight -
363                        ((lStyle & TCS_BUTTONS) ? itemRect->left * BUTTON_SPACINGX : 0);
364     itemRect->left   = itemRect->right - infoPtr->tabHeight;
365   }
366   else if (lStyle & TCS_VERTICAL)
367   {
368     itemRect->left   = clientRect.left + SELECTED_TAB_OFFSET + itemRect->left * infoPtr->tabHeight +
369                        ((lStyle & TCS_BUTTONS) ? itemRect->left * BUTTON_SPACINGX : 0);
370     itemRect->right  = itemRect->left + infoPtr->tabHeight;
371   }
372   else if (lStyle & TCS_BOTTOM)
373   {
374     itemRect->bottom = clientRect.bottom - itemRect->top * infoPtr->tabHeight -
375                        ((lStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : SELECTED_TAB_OFFSET);
376     itemRect->top    = itemRect->bottom - infoPtr->tabHeight;
377   }
378   else /* not TCS_BOTTOM and not TCS_VERTICAL */
379   {
380     itemRect->top    = clientRect.top + itemRect->top * infoPtr->tabHeight +
381                        ((lStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : SELECTED_TAB_OFFSET);
382     itemRect->bottom = itemRect->top + infoPtr->tabHeight;
383  }
384 
385   /*
386    * "scroll" it to make sure the item at the very left of the
387    * tab control is the leftmost visible tab.
388    */
389   if(lStyle & TCS_VERTICAL)
390   {
391     OffsetRect(itemRect,
392              0,
393              -TAB_GetItem(infoPtr, infoPtr->leftmostVisible)->rect.top);
394 
395     /*
396      * Move the rectangle so the first item is slightly offset from
397      * the bottom of the tab control.
398      */
399     OffsetRect(itemRect,
400              0,
401              SELECTED_TAB_OFFSET);
402 
403   } else
404   {
405     OffsetRect(itemRect,
406              -TAB_GetItem(infoPtr, infoPtr->leftmostVisible)->rect.left,
407              0);
408 
409     /*
410      * Move the rectangle so the first item is slightly offset from
411      * the left of the tab control.
412      */
413     OffsetRect(itemRect,
414              SELECTED_TAB_OFFSET,
415              0);
416   }
417   TRACE("item %d tab h=%d, rect=(%s)\n",
418         itemIndex, infoPtr->tabHeight, wine_dbgstr_rect(itemRect));
419 
420   /* Now, calculate the position of the item as if it were selected. */
421   if (selectedRect!=NULL)
422   {
423     CopyRect(selectedRect, itemRect);
424 
425     /* The rectangle of a selected item is a bit wider. */
426     if(lStyle & TCS_VERTICAL)
427       InflateRect(selectedRect, 0, SELECTED_TAB_OFFSET);
428     else
429       InflateRect(selectedRect, SELECTED_TAB_OFFSET, 0);
430 
431     /* If it also a bit higher. */
432     if ((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL))
433     {
434       selectedRect->left   -= 2; /* the border is thicker on the right */
435       selectedRect->right  += SELECTED_TAB_OFFSET;
436     }
437     else if (lStyle & TCS_VERTICAL)
438     {
439       selectedRect->left   -= SELECTED_TAB_OFFSET;
440       selectedRect->right  += 1;
441     }
442     else if (lStyle & TCS_BOTTOM)
443     {
444       selectedRect->bottom += SELECTED_TAB_OFFSET;
445     }
446     else /* not TCS_BOTTOM and not TCS_VERTICAL */
447     {
448       selectedRect->top    -= SELECTED_TAB_OFFSET;
449       selectedRect->bottom -= 1;
450     }
451   }
452 
453   /* Check for visibility */
454   if (lStyle & TCS_VERTICAL)
455     return (itemRect->top < clientRect.bottom) && (itemRect->bottom > clientRect.top);
456   else
457     return (itemRect->left < clientRect.right) && (itemRect->right > clientRect.left);
458 }
459 
460 static inline BOOL
461 TAB_GetItemRect(const TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
462 {
463   return TAB_InternalGetItemRect(infoPtr, (INT)wParam, (LPRECT)lParam, (LPRECT)NULL);
464 }
465 
466 /******************************************************************************
467  * TAB_KeyUp
468  *
469  * This method is called to handle keyboard input
470  */
471 static LRESULT TAB_KeyUp(TAB_INFO* infoPtr, WPARAM keyCode)
472 {
473   int       newItem = -1;
474 
475   switch (keyCode)
476   {
477     case VK_LEFT:
478       newItem = infoPtr->uFocus - 1;
479       break;
480     case VK_RIGHT:
481       newItem = infoPtr->uFocus + 1;
482       break;
483   }
484 
485   /*
486    * If we changed to a valid item, change the selection
487    */
488   if (newItem >= 0 &&
489       newItem < infoPtr->uNumItem &&
490       infoPtr->uFocus != newItem)
491   {
492     if (!TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGING))
493     {
494       infoPtr->iSelected = newItem;
495       infoPtr->uFocus    = newItem;
496       TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGE);
497 
498       TAB_EnsureSelectionVisible(infoPtr);
499       TAB_InvalidateTabArea(infoPtr);
500     }
501   }
502 
503   return 0;
504 }
505 
506 /******************************************************************************
507  * TAB_FocusChanging
508  *
509  * This method is called whenever the focus goes in or out of this control
510  * it is used to update the visual state of the control.
511  */
512 static void TAB_FocusChanging(const TAB_INFO *infoPtr)
513 {
514   RECT      selectedRect;
515   BOOL      isVisible;
516 
517   /*
518    * Get the rectangle for the item.
519    */
520   isVisible = TAB_InternalGetItemRect(infoPtr,
521                                       infoPtr->uFocus,
522                                       NULL,
523                                       &selectedRect);
524 
525   /*
526    * If the rectangle is not completely invisible, invalidate that
527    * portion of the window.
528    */
529   if (isVisible)
530   {
531     TRACE("invalidate (%s)\n", wine_dbgstr_rect(&selectedRect));
532     InvalidateRect(infoPtr->hwnd, &selectedRect, TRUE);
533   }
534 }
535 
536 static INT TAB_InternalHitTest (const TAB_INFO *infoPtr, POINT pt, UINT *flags)
537 {
538   RECT rect;
539   INT iCount;
540 
541   for (iCount = 0; iCount < infoPtr->uNumItem; iCount++)
542   {
543     TAB_InternalGetItemRect(infoPtr, iCount, &rect, NULL);
544 
545     if (PtInRect(&rect, pt))
546     {
547       *flags = TCHT_ONITEM;
548       return iCount;
549     }
550   }
551 
552   *flags = TCHT_NOWHERE;
553   return -1;
554 }
555 
556 static inline LRESULT
557 TAB_HitTest (const TAB_INFO *infoPtr, LPTCHITTESTINFO lptest)
558 {
559   return TAB_InternalHitTest (infoPtr, lptest->pt, &lptest->flags);
560 }
561 
562 /******************************************************************************
563  * TAB_NCHitTest
564  *
565  * Napster v2b5 has a tab control for its main navigation which has a client
566  * area that covers the whole area of the dialog pages.
567  * That's why it receives all msgs for that area and the underlying dialog ctrls
568  * are dead.
569  * So I decided that we should handle WM_NCHITTEST here and return
570  * HTTRANSPARENT if we don't hit the tab control buttons.
571  * FIXME: WM_NCHITTEST handling correct ? Fix it if you know that Windows
572  * doesn't do it that way. Maybe depends on tab control styles ?
573  */
574 static inline LRESULT
575 TAB_NCHitTest (const TAB_INFO *infoPtr, LPARAM lParam)
576 {
577   POINT pt;
578   UINT dummyflag;
579 
580   pt.x = (short)LOWORD(lParam);
581   pt.y = (short)HIWORD(lParam);
582   ScreenToClient(infoPtr->hwnd, &pt);
583 
584   if (TAB_InternalHitTest(infoPtr, pt, &dummyflag) == -1)
585     return HTTRANSPARENT;
586   else
587     return HTCLIENT;
588 }
589 
590 static LRESULT
591 TAB_LButtonDown (TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
592 {
593   POINT pt;
594   INT newItem;
595   UINT dummy;
596 
597   if (infoPtr->hwndToolTip)
598     TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
599                     WM_LBUTTONDOWN, wParam, lParam);
600 
601   if (GetWindowLongW(infoPtr->hwnd, GWL_STYLE) & TCS_FOCUSONBUTTONDOWN ) {
602     SetFocus (infoPtr->hwnd);
603   }
604 
605   if (infoPtr->hwndToolTip)
606     TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
607                     WM_LBUTTONDOWN, wParam, lParam);
608 
609   pt.x = (short)LOWORD(lParam);
610   pt.y = (short)HIWORD(lParam);
611 
612   newItem = TAB_InternalHitTest (infoPtr, pt, &dummy);
613 
614   TRACE("On Tab, item %d\n", newItem);
615 
616   if (newItem != -1 && infoPtr->iSelected != newItem)
617   {
618     if (!TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGING))
619     {
620       infoPtr->iSelected = newItem;
621       infoPtr->uFocus    = newItem;
622       TAB_SendSimpleNotify(infoPtr, TCN_SELCHANGE);
623 
624       TAB_EnsureSelectionVisible(infoPtr);
625 
626       TAB_InvalidateTabArea(infoPtr);
627     }
628   }
629   return 0;
630 }
631 
632 static inline LRESULT
633 TAB_LButtonUp (const TAB_INFO *infoPtr)
634 {
635   TAB_SendSimpleNotify(infoPtr, NM_CLICK);
636 
637   return 0;
638 }
639 
640 static inline LRESULT
641 TAB_RButtonDown (const TAB_INFO *infoPtr)
642 {
643   TAB_SendSimpleNotify(infoPtr, NM_RCLICK);
644   return 0;
645 }
646 
647 /******************************************************************************
648  * TAB_DrawLoneItemInterior
649  *
650  * This calls TAB_DrawItemInterior.  However, TAB_DrawItemInterior is normally
651  * called by TAB_DrawItem which is normally called by TAB_Refresh which sets
652  * up the device context and font.  This routine does the same setup but
653  * only calls TAB_DrawItemInterior for the single specified item.
654  */
655 static void
656 TAB_DrawLoneItemInterior(const TAB_INFO* infoPtr, int iItem)
657 {
658   HDC hdc = GetDC(infoPtr->hwnd);
659   RECT r, rC;
660 
661   /* Clip UpDown control to not draw over it */
662   if (infoPtr->needsScrolling)
663   {
664     GetWindowRect(infoPtr->hwnd, &rC);
665     GetWindowRect(infoPtr->hwndUpDown, &r);
666     ExcludeClipRect(hdc, r.left - rC.left, r.top - rC.top, r.right - rC.left, r.bottom - rC.top);
667   }
668   TAB_DrawItemInterior(infoPtr, hdc, iItem, NULL);
669   ReleaseDC(infoPtr->hwnd, hdc);
670 }
671 
672 /* update a tab after hottracking - invalidate it or just redraw the interior,
673  * based on whether theming is used or not */
674 static inline void hottrack_refresh(const TAB_INFO *infoPtr, int tabIndex)
675 {
676     if (tabIndex == -1) return;
677 
678     if (GetWindowTheme (infoPtr->hwnd))
679     {
680         RECT rect;
681         TAB_InternalGetItemRect(infoPtr, tabIndex, &rect, NULL);
682         InvalidateRect (infoPtr->hwnd, &rect, FALSE);
683     }
684     else
685         TAB_DrawLoneItemInterior(infoPtr, tabIndex);
686 }
687 
688 /******************************************************************************
689  * TAB_HotTrackTimerProc
690  *
691  * When a mouse-move event causes a tab to be highlighted (hot-tracking), a
692  * timer is setup so we can check if the mouse is moved out of our window.
693  * (We don't get an event when the mouse leaves, the mouse-move events just
694  * stop being delivered to our window and just start being delivered to
695  * another window.)  This function is called when the timer triggers so
696  * we can check if the mouse has left our window.  If so, we un-highlight
697  * the hot-tracked tab.
698  */
699 static void CALLBACK
700 TAB_HotTrackTimerProc
701   (
702   HWND hwnd,    /* handle of window for timer messages */
703   UINT uMsg,    /* WM_TIMER message */
704   UINT_PTR idEvent, /* timer identifier */
705   DWORD dwTime  /* current system time */
706   )
707 {
708   TAB_INFO* infoPtr = TAB_GetInfoPtr(hwnd);
709 
710   if (infoPtr != NULL && infoPtr->iHotTracked >= 0)
711   {
712     POINT pt;
713 
714     /*
715     ** If we can't get the cursor position, or if the cursor is outside our
716     ** window, we un-highlight the hot-tracked tab.  Note that the cursor is
717     ** "outside" even if it is within our bounding rect if another window
718     ** overlaps.  Note also that the case where the cursor stayed within our
719     ** window but has moved off the hot-tracked tab will be handled by the
720     ** WM_MOUSEMOVE event.
721     */
722     if (!GetCursorPos(&pt) || WindowFromPoint(pt) != hwnd)
723     {
724       /* Redraw iHotTracked to look normal */
725       INT iRedraw = infoPtr->iHotTracked;
726       infoPtr->iHotTracked = -1;
727       hottrack_refresh (infoPtr, iRedraw);
728 
729       /* Kill this timer */
730       KillTimer(hwnd, TAB_HOTTRACK_TIMER);
731     }
732   }
733 }
734 
735 /******************************************************************************
736  * TAB_RecalcHotTrack
737  *
738  * If a tab control has the TCS_HOTTRACK style, then the tab under the mouse
739  * should be highlighted.  This function determines which tab in a tab control,
740  * if any, is under the mouse and records that information.  The caller may
741  * supply output parameters to receive the item number of the tab item which
742  * was highlighted but isn't any longer and of the tab item which is now
743  * highlighted but wasn't previously.  The caller can use this information to
744  * selectively redraw those tab items.
745  *
746  * If the caller has a mouse position, it can supply it through the pos
747  * parameter.  For example, TAB_MouseMove does this.  Otherwise, the caller
748  * supplies NULL and this function determines the current mouse position
749  * itself.
750  */
751 static void
752 TAB_RecalcHotTrack
753   (
754   TAB_INFO*       infoPtr,
755   const LPARAM*   pos,
756   int*            out_redrawLeave,
757   int*            out_redrawEnter
758   )
759 {
760   int item = -1;
761 
762 
763   if (out_redrawLeave != NULL)
764     *out_redrawLeave = -1;
765   if (out_redrawEnter != NULL)
766     *out_redrawEnter = -1;
767 
768   if ((GetWindowLongW(infoPtr->hwnd, GWL_STYLE) & TCS_HOTTRACK)
769       || GetWindowTheme (infoPtr->hwnd))
770   {
771     POINT pt;
772     UINT  flags;
773 
774     if (pos == NULL)
775     {
776       GetCursorPos(&pt);
777       ScreenToClient(infoPtr->hwnd, &pt);
778     }
779     else
780     {
781       pt.x = (short)LOWORD(*pos);
782       pt.y = (short)HIWORD(*pos);
783     }
784 
785     item = TAB_InternalHitTest(infoPtr, pt, &flags);
786   }
787 
788   if (item != infoPtr->iHotTracked)
789   {
790     if (infoPtr->iHotTracked >= 0)
791     {
792       /* Mark currently hot-tracked to be redrawn to look normal */
793       if (out_redrawLeave != NULL)
794         *out_redrawLeave = infoPtr->iHotTracked;
795 
796       if (item < 0)
797       {
798         /* Kill timer which forces recheck of mouse pos */
799         KillTimer(infoPtr->hwnd, TAB_HOTTRACK_TIMER);
800       }
801     }
802     else
803     {
804       /* Start timer so we recheck mouse pos */
805       UINT timerID = SetTimer
806         (
807         infoPtr->hwnd,
808         TAB_HOTTRACK_TIMER,
809         TAB_HOTTRACK_TIMER_INTERVAL,
810         TAB_HotTrackTimerProc
811         );
812 
813       if (timerID == 0)
814         return; /* Hot tracking not available */
815     }
816 
817     infoPtr->iHotTracked = item;
818 
819     if (item >= 0)
820     {
821         /* Mark new hot-tracked to be redrawn to look highlighted */
822       if (out_redrawEnter != NULL)
823         *out_redrawEnter = item;
824     }
825   }
826 }
827 
828 /******************************************************************************
829  * TAB_MouseMove
830  *
831  * Handles the mouse-move event.  Updates tooltips.  Updates hot-tracking.
832  */
833 static LRESULT
834 TAB_MouseMove (TAB_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
835 {
836   int redrawLeave;
837   int redrawEnter;
838 
839   if (infoPtr->hwndToolTip)
840     TAB_RelayEvent (infoPtr->hwndToolTip, infoPtr->hwnd,
841                     WM_LBUTTONDOWN, wParam, lParam);
842 
843   /* Determine which tab to highlight.  Redraw tabs which change highlight
844   ** status. */
845   TAB_RecalcHotTrack(infoPtr, &lParam, &redrawLeave, &redrawEnter);
846 
847   hottrack_refresh (infoPtr, redrawLeave);
848   hottrack_refresh (infoPtr, redrawEnter);
849 
850   return 0;
851 }
852 
853 /******************************************************************************
854  * TAB_AdjustRect
855  *
856  * Calculates the tab control's display area given the window rectangle or
857  * the window rectangle given the requested display rectangle.
858  */
859 static LRESULT TAB_AdjustRect(const TAB_INFO *infoPtr, WPARAM fLarger, LPRECT prc)
860 {
861     DWORD lStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
862     LONG *iRightBottom, *iLeftTop;
863 
864     TRACE ("hwnd=%p fLarger=%ld (%s)\n", infoPtr->hwnd, fLarger,
865            wine_dbgstr_rect(prc));
866 
867     if(lStyle & TCS_VERTICAL)
868     {
869         iRightBottom = &(prc->right);
870         iLeftTop     = &(prc->left);
871     }
872     else
873     {
874         iRightBottom = &(prc->bottom);
875         iLeftTop     = &(prc->top);
876     }
877 
878     if (fLarger) /* Go from display rectangle */
879     {
880         /* Add the height of the tabs. */
881         if (lStyle & TCS_BOTTOM)
882             *iRightBottom += infoPtr->tabHeight * infoPtr->uNumRows;
883         else
884             *iLeftTop -= infoPtr->tabHeight * infoPtr->uNumRows +
885                          ((lStyle & TCS_BUTTONS)? 3 * (infoPtr->uNumRows - 1) : 0);
886 
887         /* Inflate the rectangle for the padding */
888         InflateRect(prc, DISPLAY_AREA_PADDINGX, DISPLAY_AREA_PADDINGY); 
889 
890         /* Inflate for the border */
891         InflateRect(prc, CONTROL_BORDER_SIZEX, CONTROL_BORDER_SIZEY);
892     }
893     else /* Go from window rectangle. */
894     {
895         /* Deflate the rectangle for the border */
896         InflateRect(prc, -CONTROL_BORDER_SIZEX, -CONTROL_BORDER_SIZEY);
897 
898         /* Deflate the rectangle for the padding */
899         InflateRect(prc, -DISPLAY_AREA_PADDINGX, -DISPLAY_AREA_PADDINGY);
900 
901         /* Remove the height of the tabs. */
902         if (lStyle & TCS_BOTTOM)
903             *iRightBottom -= infoPtr->tabHeight * infoPtr->uNumRows;
904         else
905             *iLeftTop += (infoPtr->tabHeight) * infoPtr->uNumRows +
906                          ((lStyle & TCS_BUTTONS)? 3 * (infoPtr->uNumRows - 1) : 0);
907     }
908 
909   return 0;
910 }
911 
912 /******************************************************************************
913  * TAB_OnHScroll
914  *
915  * This method will handle the notification from the scroll control and
916  * perform the scrolling operation on the tab control.
917  */
918 static LRESULT TAB_OnHScroll(TAB_INFO *infoPtr, int nScrollCode, int nPos, HWND hwndScroll)
919 {
920   if(nScrollCode == SB_THUMBPOSITION && nPos != infoPtr->leftmostVisible)
921   {
922      if(nPos < infoPtr->leftmostVisible)
923         infoPtr->leftmostVisible--;
924      else
925         infoPtr->leftmostVisible++;
926 
927      TAB_RecalcHotTrack(infoPtr, NULL, NULL, NULL);
928      TAB_InvalidateTabArea(infoPtr);
929      SendMessageW(infoPtr->hwndUpDown, UDM_SETPOS, 0,
930                    MAKELONG(infoPtr->leftmostVisible, 0));
931    }
932 
933    return 0;
934 }
935 
936 /******************************************************************************
937  * TAB_SetupScrolling
938  *
939  * This method will check the current scrolling state and make sure the
940  * scrolling control is displayed (or not).
941  */
942 static void TAB_SetupScrolling(
943   HWND        hwnd,
944   TAB_INFO*   infoPtr,
945   const RECT* clientRect)
946 {
947   static const WCHAR msctls_updown32W[] = { 'm','s','c','t','l','s','_','u','p','d','o','w','n','3','2',0 };
948   static const WCHAR emptyW[] = { 0 };
949   INT maxRange = 0;
950   DWORD lStyle = GetWindowLongW(hwnd, GWL_STYLE);
951 
952   if (infoPtr->needsScrolling)
953   {
954     RECT controlPos;
955     INT vsize, tabwidth;
956 
957     /*
958      * Calculate the position of the scroll control.
959      */
960     if(lStyle & TCS_VERTICAL)
961     {
962       controlPos.right = clientRect->right;
963       controlPos.left  = controlPos.right - 2 * GetSystemMetrics(SM_CXHSCROLL);
964 
965       if (lStyle & TCS_BOTTOM)
966       {
967         controlPos.top    = clientRect->bottom - infoPtr->tabHeight;
968         controlPos.bottom = controlPos.top + GetSystemMetrics(SM_CYHSCROLL);
969       }
970       else
971       {
972         controlPos.bottom = clientRect->top + infoPtr->tabHeight;
973         controlPos.top    = controlPos.bottom - GetSystemMetrics(SM_CYHSCROLL);
974       }
975     }
976     else
977     {
978       controlPos.right = clientRect->right;
979       controlPos.left  = controlPos.right - 2 * GetSystemMetrics(SM_CXHSCROLL);
980 
981       if (lStyle & TCS_BOTTOM)
982       {
983         controlPos.top    = clientRect->bottom - infoPtr->tabHeight;
984         controlPos.bottom = controlPos.top + GetSystemMetrics(SM_CYHSCROLL);
985       }
986       else
987       {
988         controlPos.bottom = clientRect->top + infoPtr->tabHeight;
989         controlPos.top    = controlPos.bottom - GetSystemMetrics(SM_CYHSCROLL);
990       }
991     }
992 
993     /*
994      * If we don't have a scroll control yet, we want to create one.
995      * If we have one, we want to make sure it's positioned properly.
996      */
997     if (infoPtr->hwndUpDown==0)
998     {
999       infoPtr->hwndUpDown = CreateWindowW(msctls_updown32W, emptyW,
1000                                           WS_VISIBLE | WS_CHILD | UDS_HORZ,
1001                                           controlPos.left, controlPos.top,
1002                                           controlPos.right - controlPos.left,
1003                                           controlPos.bottom - controlPos.top,
1004                                           hwnd, NULL, NULL, NULL);
1005     }
1006     else
1007     {
1008       SetWindowPos(infoPtr->hwndUpDown,
1009                    NULL,
1010                    controlPos.left, controlPos.top,
1011                    controlPos.right - controlPos.left,
1012                    controlPos.bottom - controlPos.top,
1013                    SWP_SHOWWINDOW | SWP_NOZORDER);
1014     }
1015 
1016     /* Now calculate upper limit of the updown control range.
1017      * We do this by calculating how many tabs will be offscreen when the
1018      * last tab is visible.
1019      */
1020     if(infoPtr->uNumItem)
1021     {
1022        vsize = clientRect->right - (controlPos.right - controlPos.left + 1);
1023        maxRange = infoPtr->uNumItem;
1024        tabwidth = TAB_GetItem(infoPtr, infoPtr->uNumItem - 1)->rect.right;
1025 
1026        for(; maxRange > 0; maxRange--)
1027        {
1028           if(tabwidth - TAB_GetItem(infoPtr,maxRange - 1)->rect.left > vsize)
1029              break;
1030        }
1031 
1032        if(maxRange == infoPtr->uNumItem)
1033           maxRange--;
1034     }
1035   }
1036   else
1037   {
1038     /* If we once had a scroll control... hide it */
1039     if (infoPtr->hwndUpDown!=0)
1040       ShowWindow(infoPtr->hwndUpDown, SW_HIDE);
1041   }
1042   if (infoPtr->hwndUpDown)
1043      SendMessageW(infoPtr->hwndUpDown, UDM_SETRANGE32, 0, maxRange);