1 /*
2 * Toolbar control
3 *
4 * Copyright 1998,1999 Eric Kohl
5 * Copyright 2000 Eric Kohl for CodeWeavers
6 * Copyright 2004 Robert Shearman
7 *
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public
10 * License as published by the Free Software Foundation; either
11 * version 2.1 of the License, or (at your option) any later version.
12 *
13 * This library is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Lesser General Public License for more details.
17 *
18 * You should have received a copy of the GNU Lesser General Public
19 * License along with this library; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
21 *
22 * NOTES
23 *
24 * This code was audited for completeness against the documented features
25 * of Comctl32.dll version 6.0 on Mar. 14, 2004, by Robert Shearman.
26 *
27 * Unless otherwise noted, we believe this code to be complete, as per
28 * the specification mentioned above.
29 * If you discover missing features or bugs please note them below.
30 *
31 * TODO:
32 * - Styles:
33 * - TBSTYLE_REGISTERDROP
34 * - TBSTYLE_EX_DOUBLEBUFFER
35 * - Messages:
36 * - TB_GETMETRICS
37 * - TB_GETOBJECT
38 * - TB_INSERTMARKHITTEST
39 * - TB_SAVERESTORE
40 * - TB_SETMETRICS
41 * - WM_WININICHANGE
42 * - Notifications:
43 * - NM_CHAR
44 * - TBN_GETOBJECT
45 * - TBN_SAVE
46 * - Button wrapping (under construction).
47 * - Fix TB_SETROWS and Separators.
48 * - iListGap custom draw support.
49 *
50 * Testing:
51 * - Run tests using Waite Group Windows95 API Bible Volume 2.
52 * The second cdrom contains executables addstr.exe, btncount.exe,
53 * btnstate.exe, butstrsz.exe, chkbtn.exe, chngbmp.exe, customiz.exe,
54 * enablebtn.exe, getbmp.exe, getbtn.exe, getflags.exe, hidebtn.exe,
55 * indetbtn.exe, insbtn.exe, pressbtn.exe, setbtnsz.exe, setcmdid.exe,
56 * setparnt.exe, setrows.exe, toolwnd.exe.
57 * - Microsoft's controlspy examples.
58 * - Charles Petzold's 'Programming Windows': gadgets.exe
59 *
60 * Differences between MSDN and actual native control operation:
61 * 1. MSDN says: "TBSTYLE_LIST: Creates a flat toolbar with button text
62 * to the right of the bitmap. Otherwise, this style is
63 * identical to TBSTYLE_FLAT."
64 * As implemented by both v4.71 and v5.80 of the native COMCTL32.DLL
65 * you can create a TBSTYLE_LIST without TBSTYLE_FLAT and the result
66 * is non-flat non-transparent buttons. Therefore TBSTYLE_LIST does
67 * *not* imply TBSTYLE_FLAT as documented. (GA 8/2001)
68 *
69 */
70
71 #include <stdarg.h>
72 #include <string.h>
73
74 #include "windef.h"
75 #include "winbase.h"
76 #include "winreg.h"
77 #include "wingdi.h"
78 #include "winuser.h"
79 #include "wine/unicode.h"
80 #include "winnls.h"
81 #include "commctrl.h"
82 #include "comctl32.h"
83 #include "uxtheme.h"
84 #include "tmschema.h"
85 #include "wine/debug.h"
86
87 WINE_DEFAULT_DEBUG_CHANNEL(toolbar);
88
89 static HCURSOR hCursorDrag = NULL;
90
91 typedef struct
92 {
93 INT iBitmap;
94 INT idCommand;
95 BYTE fsState;
96 BYTE fsStyle;
97 BYTE bHot;
98 BYTE bDropDownPressed;
99 DWORD_PTR dwData;
100 INT_PTR iString;
101 INT nRow;
102 RECT rect;
103 INT cx; /* manually set size */
104 } TBUTTON_INFO;
105
106 typedef struct
107 {
108 UINT nButtons;
109 HINSTANCE hInst;
110 UINT nID;
111 } TBITMAP_INFO;
112
113 typedef struct
114 {
115 HIMAGELIST himl;
116 INT id;
117 } IMLENTRY, *PIMLENTRY;
118
119 typedef struct
120 {
121 DWORD dwStructSize; /* size of TBBUTTON struct */
122 INT nWidth; /* width of the toolbar */
123 RECT client_rect;
124 RECT rcBound; /* bounding rectangle */
125 INT nButtonHeight;
126 INT nButtonWidth;
127 INT nBitmapHeight;
128 INT nBitmapWidth;
129 INT nIndent;
130 INT nRows; /* number of button rows */
131 INT nMaxTextRows; /* maximum number of text rows */
132 INT cxMin; /* minimum button width */
133 INT cxMax; /* maximum button width */
134 INT nNumButtons; /* number of buttons */
135 INT nNumBitmaps; /* number of bitmaps */
136 INT nNumStrings; /* number of strings */
137 INT nNumBitmapInfos;
138 INT nButtonDown; /* toolbar button being pressed or -1 if none */
139 INT nButtonDrag; /* toolbar button being dragged or -1 if none */
140 INT nOldHit;
141 INT nHotItem; /* index of the "hot" item */
142 SIZE szPadding; /* padding values around button */
143 INT iTopMargin; /* the top margin */
144 INT iListGap; /* default gap between text and image for toolbar with list style */
145 HFONT hDefaultFont;
146 HFONT hFont; /* text font */
147 HIMAGELIST himlInt; /* image list created internally */
148 PIMLENTRY *himlDef; /* default image list array */
149 INT cimlDef; /* default image list array count */
150 PIMLENTRY *himlHot; /* hot image list array */
151 INT cimlHot; /* hot image list array count */
152 PIMLENTRY *himlDis; /* disabled image list array */
153 INT cimlDis; /* disabled image list array count */
154 HWND hwndToolTip; /* handle to tool tip control */
155 HWND hwndNotify; /* handle to the window that gets notifications */
156 HWND hwndSelf; /* my own handle */
157 BOOL bAnchor; /* anchor highlight enabled */
158 BOOL bDoRedraw; /* Redraw status */
159 BOOL bDragOutSent; /* has TBN_DRAGOUT notification been sent for this drag? */
160 BOOL bUnicode; /* Notifications are ASCII (FALSE) or Unicode (TRUE)? */
161 BOOL bCaptured; /* mouse captured? */
162 DWORD dwStyle; /* regular toolbar style */
163 DWORD dwExStyle; /* extended toolbar style */
164 DWORD dwDTFlags; /* DrawText flags */
165
166 COLORREF clrInsertMark; /* insert mark color */
167 COLORREF clrBtnHighlight; /* color for Flat Separator */
168 COLORREF clrBtnShadow; /* color for Flag Separator */
169 INT iVersion;
170 LPWSTR pszTooltipText; /* temporary store for a string > 80 characters
171 * for TTN_GETDISPINFOW notification */
172 TBINSERTMARK tbim; /* info on insertion mark */
173 TBUTTON_INFO *buttons; /* pointer to button array */
174 LPWSTR *strings; /* pointer to string array */
175 TBITMAP_INFO *bitmaps;
176 } TOOLBAR_INFO, *PTOOLBAR_INFO;
177
178
179 /* used by customization dialog */
180 typedef struct
181 {
182 PTOOLBAR_INFO tbInfo;
183 HWND tbHwnd;
184 } CUSTDLG_INFO, *PCUSTDLG_INFO;
185
186 typedef struct
187 {
188 TBBUTTON btn;
189 BOOL bVirtual;
190 BOOL bRemovable;
191 WCHAR text[64];
192 } CUSTOMBUTTON, *PCUSTOMBUTTON;
193
194 typedef enum
195 {
196 IMAGE_LIST_DEFAULT,
197 IMAGE_LIST_HOT,
198 IMAGE_LIST_DISABLED
199 } IMAGE_LIST_TYPE;
200
201 #define SEPARATOR_WIDTH 8
202 #define TOP_BORDER 2
203 #define BOTTOM_BORDER 2
204 #define DDARROW_WIDTH 11
205 #define ARROW_HEIGHT 3
206 #define INSERTMARK_WIDTH 2
207
208 #define DEFPAD_CX 7
209 #define DEFPAD_CY 6
210 #define DEFLISTGAP 4
211
212 /* vertical padding used in list mode when image is present */
213 #define LISTPAD_CY 9
214
215 /* how wide to treat the bitmap if it isn't present */
216 #define NONLIST_NOTEXT_OFFSET 2
217
218 #define TOOLBAR_NOWHERE (-1)
219
220 #define TOOLBAR_GetInfoPtr(hwnd) ((TOOLBAR_INFO *)GetWindowLongPtrW(hwnd,0))
221 #define TOOLBAR_HasText(x, y) (TOOLBAR_GetText(x, y) ? TRUE : FALSE)
222 #define TOOLBAR_HasDropDownArrows(exStyle) ((exStyle & TBSTYLE_EX_DRAWDDARROWS) ? TRUE : FALSE)
223
224 /* Used to find undocumented extended styles */
225 #define TBSTYLE_EX_ALL (TBSTYLE_EX_DRAWDDARROWS | \
226 TBSTYLE_EX_UNDOC1 | \
227 TBSTYLE_EX_MIXEDBUTTONS | \
228 TBSTYLE_EX_HIDECLIPPEDBUTTONS)
229
230 /* all of the CCS_ styles */
231 #define COMMON_STYLES (CCS_TOP|CCS_NOMOVEY|CCS_BOTTOM|CCS_NORESIZE| \
232 CCS_NOPARENTALIGN|CCS_ADJUSTABLE|CCS_NODIVIDER|CCS_VERT)
233
234 #define GETIBITMAP(infoPtr, i) (infoPtr->iVersion >= 5 ? LOWORD(i) : i)
235 #define GETHIMLID(infoPtr, i) (infoPtr->iVersion >= 5 ? HIWORD(i) : 0)
236 #define GETDEFIMAGELIST(infoPtr, id) TOOLBAR_GetImageList(infoPtr->himlDef, infoPtr->cimlDef, id)
237 #define GETHOTIMAGELIST(infoPtr, id) TOOLBAR_GetImageList(infoPtr->himlHot, infoPtr->cimlHot, id)
238 #define GETDISIMAGELIST(infoPtr, id) TOOLBAR_GetImageList(infoPtr->himlDis, infoPtr->cimlDis, id)
239
240 static const WCHAR themeClass[] = { 'T','o','o','l','b','a','r',0 };
241
242 static BOOL TOOLBAR_GetButtonInfo(const TOOLBAR_INFO *infoPtr, NMTOOLBARW *nmtb);
243 static BOOL TOOLBAR_IsButtonRemovable(const TOOLBAR_INFO *infoPtr, int iItem, PCUSTOMBUTTON btnInfo);
244 static HIMAGELIST TOOLBAR_GetImageList(const PIMLENTRY *pies, INT cies, INT id);
245 static PIMLENTRY TOOLBAR_GetImageListEntry(const PIMLENTRY *pies, INT cies, INT id);
246 static VOID TOOLBAR_DeleteImageList(PIMLENTRY **pies, INT *cies);
247 static HIMAGELIST TOOLBAR_InsertImageList(PIMLENTRY **pies, INT *cies, HIMAGELIST himl, INT id);
248 static LRESULT TOOLBAR_LButtonDown(HWND hwnd, WPARAM wParam, LPARAM lParam);
249 static void TOOLBAR_SetHotItemEx (TOOLBAR_INFO *infoPtr, INT nHit, DWORD dwReason);
250 static void TOOLBAR_LayoutToolbar(HWND hwnd);
251 static LRESULT TOOLBAR_AutoSize(HWND hwnd);
252 static void TOOLBAR_CheckImageListIconSize(TOOLBAR_INFO *infoPtr);
253 static void TOOLBAR_TooltipSetRect(const TOOLBAR_INFO *infoPtr, const TBUTTON_INFO *button);
254
255 static LRESULT
256 TOOLBAR_NotifyFormat(const TOOLBAR_INFO *infoPtr, WPARAM wParam, LPARAM lParam);
257
258 static inline int default_top_margin(const TOOLBAR_INFO *infoPtr)
259 {
260 return (infoPtr->dwStyle & TBSTYLE_FLAT ? 0 : TOP_BORDER);
261 }
262
263 static LPWSTR
264 TOOLBAR_GetText(const TOOLBAR_INFO *infoPtr, const TBUTTON_INFO *btnPtr)
265 {
266 LPWSTR lpText = NULL;
267
268 /* NOTE: iString == -1 is undocumented */
269 if ((HIWORD(btnPtr->iString) != 0) && (btnPtr->iString != -1))
270 lpText = (LPWSTR)btnPtr->iString;
271 else if ((btnPtr->iString >= 0) && (btnPtr->iString < infoPtr->nNumStrings))
272 lpText = infoPtr->strings[btnPtr->iString];
273
274 return lpText;
275 }
276
277 static void
278 TOOLBAR_DumpButton(const TOOLBAR_INFO *infoPtr, const TBUTTON_INFO *bP, INT btn_num, BOOL internal)
279 {
280 if (TRACE_ON(toolbar)){
281 TRACE("button %d id %d, bitmap=%d, state=%02x, style=%02x, data=%08lx, stringid=0x%08lx\n",
282 btn_num, bP->idCommand, GETIBITMAP(infoPtr, bP->iBitmap),
283 bP->fsState, bP->fsStyle, bP->dwData, bP->iString);
284 TRACE("string %s\n", debugstr_w(TOOLBAR_GetText(infoPtr,bP)));
285 if (internal)
286 TRACE("button %d id %d, hot=%s, row=%d, rect=(%s)\n",
287 btn_num, bP->idCommand,
288 (bP->bHot) ? "TRUE":"FALSE", bP->nRow,
289 wine_dbgstr_rect(&bP->rect));
290 }
291 }
292
293
294 static void
295 TOOLBAR_DumpToolbar(const TOOLBAR_INFO *iP, INT line)
296 {
297 if (TRACE_ON(toolbar)) {
298 INT i;
299
300 TRACE("toolbar %p at line %d, exStyle=%08x, buttons=%d, bitmaps=%d, strings=%d, style=%08x\n",
301 iP->hwndSelf, line,
302 iP->dwExStyle, iP->nNumButtons, iP->nNumBitmaps,
303 iP->nNumStrings, iP->dwStyle);
304 TRACE("toolbar %p at line %d, himlInt=%p, himlDef=%p, himlHot=%p, himlDis=%p, redrawable=%s\n",
305 iP->hwndSelf, line,
306 iP->himlInt, iP->himlDef, iP->himlHot, iP->himlDis,
307 (iP->bDoRedraw) ? "TRUE" : "FALSE");
308 for(i=0; i<iP->nNumButtons; i++) {
309 TOOLBAR_DumpButton(iP, &iP->buttons[i], i, TRUE);
310 }
311 }
312 }
313
314
315 /***********************************************************************
316 * TOOLBAR_CheckStyle
317 *
318 * This function validates that the styles set are implemented and
319 * issues FIXME's warning of possible problems. In a perfect world this
320 * function should be null.
321 */
322 static void
323 TOOLBAR_CheckStyle (HWND hwnd, DWORD dwStyle)
324 {
325 if (dwStyle & TBSTYLE_REGISTERDROP)
326 FIXME("[%p] TBSTYLE_REGISTERDROP not implemented\n", hwnd);
327 }
328
329
330 static INT
331 TOOLBAR_SendNotify (NMHDR *nmhdr, const TOOLBAR_INFO *infoPtr, UINT code)
332 {
333 if(!IsWindow(infoPtr->hwndSelf))
334 return 0; /* we have just been destroyed */
335
336 nmhdr->idFrom = GetDlgCtrlID (infoPtr->hwndSelf);
337 nmhdr->hwndFrom = infoPtr->hwndSelf;
338 nmhdr->code = code;
339
340 TRACE("to window %p, code=%08x, %s\n", infoPtr->hwndNotify, code,
341 (infoPtr->bUnicode) ? "via Unicode" : "via ANSI");
342
343 return SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, nmhdr->idFrom, (LPARAM)nmhdr);
344 }
345
346 /***********************************************************************
347 * TOOLBAR_GetBitmapIndex
348 *
349 * This function returns the bitmap index associated with a button.
350 * If the button specifies I_IMAGECALLBACK, then the TBN_GETDISPINFO
351 * is issued to retrieve the index.
352 */
353 static INT
354 TOOLBAR_GetBitmapIndex(const TOOLBAR_INFO *infoPtr, TBUTTON_INFO *btnPtr)
355 {
356 INT ret = btnPtr->iBitmap;
357
358 if (ret == I_IMAGECALLBACK)
359 {
360 /* issue TBN_GETDISPINFO */
361 NMTBDISPINFOW nmgd;
362
363 memset(&nmgd, 0, sizeof(nmgd));
364 nmgd.idCommand = btnPtr->idCommand;
365 nmgd.lParam = btnPtr->dwData;
366 nmgd.dwMask = TBNF_IMAGE;
367 nmgd.iImage = -1;
368 /* Windows also send TBN_GETDISPINFOW even if the control is ANSI */
369 TOOLBAR_SendNotify(&nmgd.hdr, infoPtr, TBN_GETDISPINFOW);
370 if (nmgd.dwMask & TBNF_DI_SETITEM)
371 btnPtr->iBitmap = nmgd.iImage;
372 ret = nmgd.iImage;
373 TRACE("TBN_GETDISPINFO returned bitmap id %d, mask=%08x, nNumBitmaps=%d\n",
374 ret, nmgd.dwMask, infoPtr->nNumBitmaps);
375 }
376
377 if (ret != I_IMAGENONE)
378 ret = GETIBITMAP(infoPtr, ret);
379
380 return ret;
381 }
382
383
384 static BOOL
385 TOOLBAR_IsValidBitmapIndex(const TOOLBAR_INFO *infoPtr, INT index)
386 {
387 HIMAGELIST himl;
388 INT id = GETHIMLID(infoPtr, index);
389 INT iBitmap = GETIBITMAP(infoPtr, index);
390
391 if (((himl = GETDEFIMAGELIST(infoPtr, id)) &&
392 iBitmap >= 0 && iBitmap < ImageList_GetImageCount(himl)) ||
393 (index == I_IMAGECALLBACK))
394 return TRUE;
395 else
396 return FALSE;
397 }
398
399
400 static inline BOOL
401 TOOLBAR_IsValidImageList(const TOOLBAR_INFO *infoPtr, INT index)
402 {
403 HIMAGELIST himl = GETDEFIMAGELIST(infoPtr, GETHIMLID(infoPtr, index));
404 return (himl != NULL) && (ImageList_GetImageCount(himl) > 0);
405 }
406
407
408 /***********************************************************************
409 * TOOLBAR_GetImageListForDrawing
410 *
411 * This function validates the bitmap index (including I_IMAGECALLBACK
412 * functionality) and returns the corresponding image list.
413 */
414 static HIMAGELIST
415 TOOLBAR_GetImageListForDrawing (const TOOLBAR_INFO *infoPtr, TBUTTON_INFO *btnPtr,
416 IMAGE_LIST_TYPE imagelist, INT * index)
417 {
418 HIMAGELIST himl;
419
420 if (!TOOLBAR_IsValidBitmapIndex(infoPtr,btnPtr->iBitmap)) {
421 if (btnPtr->iBitmap == I_IMAGENONE) return NULL;
422 ERR("bitmap for ID %d, index %d is not valid, number of bitmaps in imagelist: %d\n",
423 HIWORD(btnPtr->iBitmap), LOWORD(btnPtr->iBitmap), infoPtr->nNumBitmaps);
424 return NULL;
425 }
426
427 if ((*index = TOOLBAR_GetBitmapIndex(infoPtr, btnPtr)) < 0) {
428 if ((*index == I_IMAGECALLBACK) ||
429 (*index == I_IMAGENONE)) return NULL;
430 ERR("TBN_GETDISPINFO returned invalid index %d\n",
431 *index);
432 return NULL;
433 }
434
435 switch(imagelist)
436 {
437 case IMAGE_LIST_DEFAULT:
438 himl = GETDEFIMAGELIST(infoPtr, GETHIMLID(infoPtr, btnPtr->iBitmap));
439 break;
440 case IMAGE_LIST_HOT:
441 himl = GETHOTIMAGELIST(infoPtr, GETHIMLID(infoPtr, btnPtr->iBitmap));
442 break;
443 case IMAGE_LIST_DISABLED:
444 himl = GETDISIMAGELIST(infoPtr, GETHIMLID(infoPtr, btnPtr->iBitmap));
445 break;
446 default:
447 himl = NULL;
448 FIXME("Shouldn't reach here\n");
449 }
450
451 if (!himl)
452 TRACE("no image list\n");
453
454 return himl;
455 }
456
457
458 static void
459 TOOLBAR_DrawFlatSeparator (const RECT *lpRect, HDC hdc, const TOOLBAR_INFO *infoPtr)
460 {
461 RECT myrect;
462 COLORREF oldcolor, newcolor;
463
464 myrect.left = (lpRect->left + lpRect->right) / 2 - 1;
465 myrect.right = myrect.left + 1;
466 myrect.top = lpRect->top + 2;
467 myrect.bottom = lpRect->bottom - 2;
468
469 newcolor = (infoPtr->clrBtnShadow == CLR_DEFAULT) ?
470 comctl32_color.clrBtnShadow : infoPtr->clrBtnShadow;
471 oldcolor = SetBkColor (hdc, newcolor);
472 ExtTextOutW (hdc, 0, 0, ETO_OPAQUE, &myrect, 0, 0, 0);
473
474 myrect.left = myrect.right;
475 myrect.right = myrect.left + 1;
476
477 newcolor = (infoPtr->clrBtnHighlight == CLR_DEFAULT) ?
478 comctl32_color.clrBtnHighlight : infoPtr->clrBtnHighlight;
479 SetBkColor (hdc, newcolor);
480 ExtTextOutW (hdc, 0, 0, ETO_OPAQUE, &myrect, 0, 0, 0);
481
482 SetBkColor (hdc, oldcolor);
483 }
484
485
486 /***********************************************************************
487 * TOOLBAR_DrawDDFlatSeparator
488 *
489 * This function draws the separator that was flagged as BTNS_DROPDOWN.
490 * In this case, the separator is a pixel high line of COLOR_BTNSHADOW,
491 * followed by a pixel high line of COLOR_BTNHIGHLIGHT. These separators
492 * are horizontal as opposed to the vertical separators for not dropdown
493 * type.
494 *
495 * FIXME: It is possible that the height of each line is really SM_CYBORDER.
496 */
497 static void
498 TOOLBAR_DrawDDFlatSeparator (const RECT *lpRect, HDC hdc, const TBUTTON_INFO *btnPtr,
499 const TOOLBAR_INFO *infoPtr)
500 {
501 RECT myrect;
502 COLORREF oldcolor, newcolor;
503
504 myrect.left = lpRect->left;
505 myrect.right = lpRect->right;
506 myrect.top = lpRect->top + (lpRect->bottom - lpRect->top - 2)/2;
507 myrect.bottom = myrect.top + 1;
508
509 InflateRect (&myrect, -2, 0);
510
511 TRACE("rect=(%s)\n", wine_dbgstr_rect(&myrect));
512
513 newcolor = (infoPtr->clrBtnShadow == CLR_DEFAULT) ?
514 comctl32_color.clrBtnShadow : infoPtr->clrBtnShadow;
515 oldcolor = SetBkColor (hdc, newcolor);
516 ExtTextOutW (hdc, 0, 0, ETO_OPAQUE, &myrect, 0, 0, 0);
517
518 myrect.top = myrect.bottom;
519 myrect.bottom = myrect.top + 1;
520
521 newcolor = (infoPtr->clrBtnHighlight == CLR_DEFAULT) ?
522 comctl32_color.clrBtnHighlight : infoPtr->clrBtnHighlight;
523 SetBkColor (hdc, newcolor);
524 ExtTextOutW (hdc, 0, 0, ETO_OPAQUE, &myrect, 0, 0, 0);
525
526 SetBkColor (hdc, oldcolor);
527 }
528
529
530 static void
531 TOOLBAR_DrawArrow (HDC hdc, INT left, INT top, COLORREF clr)
532 {
533 INT x, y;
534 HPEN hPen, hOldPen;
535
536 if (!(hPen = CreatePen( PS_SOLID, 1, clr))) return;
537 hOldPen = SelectObject ( hdc, hPen );
538 x = left + 2;
539 y = top;
540 MoveToEx (hdc, x, y, NULL);
541 LineTo (hdc, x+5, y++); x++;
542 MoveToEx (hdc, x, y, NULL);
543 LineTo (hdc, x+3, y++); x++;
544 MoveToEx (hdc, x, y, NULL);
545 LineTo (hdc, x+1, y++);
546 SelectObject( hdc, hOldPen );
547 DeleteObject( hPen );
548 }
549
550 /*
551 * Draw the text string for this button.
552 * note: infoPtr->himlDis *SHOULD* be non-zero when infoPtr->himlDef
553 * is non-zero, so we can simply check himlDef to see if we have
554 * an image list
555 */
556 static void
557 TOOLBAR_DrawString (const TOOLBAR_INFO *infoPtr, RECT *rcText, LPCWSTR lpText,
558 const NMTBCUSTOMDRAW *tbcd, DWORD dwItemCDFlag)
559 {
560 HDC hdc = tbcd->nmcd.hdc;
561 HFONT hOldFont = 0;
562 COLORREF clrOld = 0;
563 COLORREF clrOldBk = 0;
564 int oldBkMode = 0;
565 UINT state = tbcd->nmcd.uItemState;
566
567 /* draw text */
568 if (lpText) {
569 TRACE("string=%s rect=(%s)\n", debugstr_w(lpText),
570 wine_dbgstr_rect(rcText));
571
572 hOldFont = SelectObject (hdc, infoPtr->hFont);
573 if ((state & CDIS_HOT) && (dwItemCDFlag & TBCDRF_HILITEHOTTRACK )) {
574 clrOld = SetTextColor (hdc, tbcd->clrTextHighlight);
575 }
576 else if (state & CDIS_DISABLED) {
577 clrOld = SetTextColor (hdc, tbcd->clrBtnHighlight);
578 OffsetRect (rcText, 1, 1);
579 DrawTextW (hdc, lpText, -1, rcText, infoPtr->dwDTFlags);
580 SetTextColor (hdc, comctl32_color.clr3dShadow);
581 OffsetRect (rcText, -1, -1);
582 }
583 else if (state & CDIS_INDETERMINATE) {
584 clrOld = SetTextColor (hdc, comctl32_color.clr3dShadow);
585 }
586 else if ((state & CDIS_MARKED) && !(dwItemCDFlag & TBCDRF_NOMARK)) {
587 clrOld = SetTextColor (hdc, tbcd->clrTextHighlight);
588 clrOldBk = SetBkColor (hdc, tbcd->clrMark);
589 oldBkMode = SetBkMode (hdc, tbcd->nHLStringBkMode);
590 }
591 else {
592 clrOld = SetTextColor (hdc, tbcd->clrText);
593 }
594
595 DrawTextW (hdc, lpText, -1, rcText, infoPtr->dwDTFlags);
596 SetTextColor (hdc, clrOld);
597 if ((state & CDIS_MARKED) && !(dwItemCDFlag & TBCDRF_NOMARK))
598 {
599 SetBkColor (hdc, clrOldBk);
600 SetBkMode (hdc, oldBkMode);
601 }
602 SelectObject (hdc, hOldFont);
603 }
604 }
605
606
607 static void
608 TOOLBAR_DrawPattern (const RECT *lpRect, const NMTBCUSTOMDRAW *tbcd)
609 {
610 HDC hdc = tbcd->nmcd.hdc;
611 HBRUSH hbr = SelectObject (hdc, tbcd->hbrMonoDither);
612 COLORREF clrTextOld;
613 COLORREF clrBkOld;
614 INT cx = lpRect->right - lpRect->left;
615 INT cy = lpRect->bottom - lpRect->top;
616 INT cxEdge = GetSystemMetrics(SM_CXEDGE);
617 INT cyEdge = GetSystemMetrics(SM_CYEDGE);
618 clrTextOld = SetTextColor(hdc, tbcd->clrBtnHighlight);
619 clrBkOld = SetBkColor(hdc, tbcd->clrBtnFace);
620 PatBlt (hdc, lpRect->left + cxEdge, lpRect->top + cyEdge,
621 cx - (2 * cxEdge), cy - (2 * cyEdge), PATCOPY);
622 SetBkColor(hdc, clrBkOld);
623 SetTextColor(hdc, clrTextOld);
624 SelectObject (hdc, hbr);
625 }
626
627
628 static void TOOLBAR_DrawMasked(HIMAGELIST himl, int index, HDC hdc, INT x, INT y, UINT draw_flags)
629 {
630 INT cx, cy;
631 HBITMAP hbmMask, hbmImage;
632 HDC hdcMask, hdcImage;
633
634 ImageList_GetIconSize(himl, &cx, &cy);
635
636 /* Create src image */
637 hdcImage = CreateCompatibleDC(hdc);
638 hbmImage = CreateCompatibleBitmap(hdc, cx, cy);
639 SelectObject(hdcImage, hbmImage);
640 ImageList_DrawEx(himl, index, hdcImage, 0, 0, cx, cy,
641 RGB(0xff, 0xff, 0xff), RGB(0,0,0), draw_flags);
642
643 /* Create Mask */
644 hdcMask = CreateCompatibleDC(0);
645 hbmMask = CreateBitmap(cx, cy, 1, 1, NULL);
646 SelectObject(hdcMask, hbmMask);
647
648 /* Remove the background and all white pixels */
649 ImageList_DrawEx(himl, index, hdcMask, 0, 0, cx, cy,
650 RGB(0xff, 0xff, 0xff), RGB(0,0,0), ILD_MASK);
651 SetBkColor(hdcImage, RGB(0xff, 0xff, 0xff));
652 BitBlt(hdcMask, 0, 0, cx, cy, hdcImage, 0, 0, NOTSRCERASE);
653
654 /* draw the new mask 'etched' to hdc */
655 SetBkColor(hdc, RGB(255, 255, 255));
656 SelectObject(hdc, GetSysColorBrush(COLOR_3DHILIGHT));
657 /* E20746 op code is (Dst ^ (Src & (Pat ^ Dst))) */
658 BitBlt(hdc, x + 1, y + 1, cx, cy, hdcMask, 0, 0, 0xE20746);
659 SelectObject(hdc, GetSysColorBrush(COLOR_3DSHADOW));
660 BitBlt(hdc, x, y, cx, cy, hdcMask, 0, 0, 0xE20746);
661
662 /* Cleanup */
663 DeleteObject(hbmImage);
664 DeleteDC(hdcImage);
665 DeleteObject (hbmMask);
666 DeleteDC(hdcMask);
667 }
668
669
670 static UINT
671 TOOLBAR_TranslateState(const TBUTTON_INFO *btnPtr)
672 {
673 UINT retstate = 0;
674
675 retstate |= (btnPtr->fsState & TBSTATE_CHECKED) ? CDIS_CHECKED : 0;
676 retstate |= (btnPtr->fsState & TBSTATE_PRESSED) ? CDIS_SELECTED : 0;
677 retstate |= (btnPtr->fsState & TBSTATE_ENABLED) ? 0 : CDIS_DISABLED;
678 retstate |= (btnPtr->fsState & TBSTATE_MARKED ) ? CDIS_MARKED : 0;
679 retstate |= (btnPtr->bHot ) ? CDIS_HOT : 0;
680 retstate |= ((btnPtr->fsState & (TBSTATE_ENABLED|TBSTATE_INDETERMINATE)) == (TBSTATE_ENABLED|TBSTATE_INDETERMINATE)) ? CDIS_INDETERMINATE : 0;
681 /* NOTE: we don't set CDIS_GRAYED, CDIS_FOCUS, CDIS_DEFAULT */
682 return retstate;
683 }
684
685 /* draws the image on a toolbar button */
686 static void
687 TOOLBAR_DrawImage(const TOOLBAR_INFO *infoPtr, TBUTTON_INFO *btnPtr, INT left, INT top,
688 const NMTBCUSTOMDRAW *tbcd, DWORD dwItemCDFlag)
689 {
690 HIMAGELIST himl = NULL;
691 BOOL draw_masked = FALSE;
692 INT index;
693 INT offset = 0;
694 UINT draw_flags = ILD_TRANSPARENT;
695
696 if (tbcd->nmcd.uItemState & (CDIS_DISABLED | CDIS_INDETERMINATE))
697 {
698 himl = TOOLBAR_GetImageListForDrawing(infoPtr, btnPtr, IMAGE_LIST_DISABLED, &index);
699 if (!himl)
700 {
701 himl = TOOLBAR_GetImageListForDrawing(infoPtr, btnPtr, IMAGE_LIST_DEFAULT, &index);
702 draw_masked = TRUE;
703 }
704 }
705 else if (tbcd->nmcd.uItemState & CDIS_CHECKED ||
706 ((tbcd->nmcd.uItemState & CDIS_HOT)
707 && ((infoPtr->dwStyle & TBSTYLE_FLAT) || GetWindowTheme (infoPtr->hwndSelf))))
708 {
709 /* if hot, attempt to draw with hot image list, if fails,
710 use default image list */
711 himl = TOOLBAR_GetImageListForDrawing(infoPtr, btnPtr, IMAGE_LIST_HOT, &index);
712 if (!himl)
713 himl = TOOLBAR_GetImageListForDrawing(infoPtr, btnPtr, IMAGE_LIST_DEFAULT, &index);
714 }
715 else
716 himl = TOOLBAR_GetImageListForDrawing(infoPtr, btnPtr, IMAGE_LIST_DEFAULT, &index);
717
718 if (!himl)
719 return;
720
721 if (!(dwItemCDFlag & TBCDRF_NOOFFSET) &&
722 (tbcd->nmcd.uItemState & (CDIS_SELECTED | CDIS_CHECKED)))
723 offset = 1;
724
725 if (!(dwItemCDFlag & TBCDRF_NOMARK) &&
726 (tbcd->nmcd.uItemState & CDIS_MARKED))
727 draw_flags |= ILD_BLEND50;
728
729 TRACE("drawing index=%d, himl=%p, left=%d, top=%d, offset=%d\n",
730 index, himl, left, top, offset);
731
732 if (draw_masked)
733 TOOLBAR_DrawMasked (himl, index, tbcd->nmcd.hdc, left + offset, top + offset, draw_flags);
734 else
735 ImageList_Draw (himl, index, tbcd->nmcd.hdc, left + offset, top + offset, draw_flags);
736 }
737
738 /* draws a blank frame for a toolbar button */
739 static void
740 TOOLBAR_DrawFrame(const TOOLBAR_INFO *infoPtr, const NMTBCUSTOMDRAW *tbcd, DWORD dwItemCDFlag)
741 {
742 HDC hdc = tbcd->nmcd.hdc;
743 RECT rc = tbcd->nmcd.rc;
744 /* if the state is disabled or indeterminate then the button
745 * cannot have an interactive look like pressed or hot */
746 BOOL non_interactive_state = (tbcd->nmcd.uItemState & CDIS_DISABLED) ||
747 (tbcd->nmcd.uItemState & CDIS_INDETERMINATE);
748 BOOL pressed_look = !non_interactive_state &&
749 ((tbcd->nmcd.uItemState & CDIS_SELECTED) ||
750 (tbcd->nmcd.uItemState & CDIS_CHECKED));
751
752 /* app don't want us to draw any edges */
753 if (dwItemCDFlag & TBCDRF_NOEDGES)
754 return;
755
756 if (infoPtr->dwStyle & TBSTYLE_FLAT)
757 {
758 if (pressed_look)
759 DrawEdge (hdc, &rc, BDR_SUNKENOUTER, BF_RECT);
760 else if ((tbcd->nmcd.uItemState & CDIS_HOT) && !non_interactive_state)
761 DrawEdge (hdc, &rc, BDR_RAISEDINNER, BF_RECT);
762 }
763 else
764 {
765 if (pressed_look)
766 DrawEdge (hdc, &rc, EDGE_SUNKEN, BF_RECT | BF_MIDDLE);
767 else
768 DrawEdge (hdc, &rc, EDGE_RAISED,
769 BF_SOFT | BF_RECT | BF_MIDDLE);
770 }
771 }
772
773 static void
774 TOOLBAR_DrawSepDDArrow(const TOOLBAR_INFO *infoPtr, const NMTBCUSTOMDRAW *tbcd, RECT *rcArrow, BOOL bDropDownPressed, DWORD dwItemCDFlag)
775 {
776 HDC hdc = tbcd->nmcd.hdc;
777 int offset = 0;
778 BOOL pressed = bDropDownPressed ||
779 (tbcd->nmcd.uItemState & (CDIS_SELECTED | CDIS_CHECKED));
780
781 if (infoPtr->dwStyle & TBSTYLE_FLAT)
782 {
783 if (pressed)
784 DrawEdge (hdc, rcArrow, BDR_SUNKENOUTER, BF_RECT);
785 else if ( (tbcd->nmcd.uItemState & CDIS_HOT) &&
786 !(tbcd->nmcd.uItemState & CDIS_DISABLED) &&
787 !(tbcd->nmcd.uItemState & CDIS_INDETERMINATE))
788 DrawEdge (hdc, rcArrow, BDR_RAISEDINNER, BF_RECT);
789 }
790 else
791 {
792 if (pressed)
793 DrawEdge (hdc, rcArrow, EDGE_SUNKEN, BF_RECT | BF_MIDDLE);
794 else
795 DrawEdge (hdc, rcArrow, EDGE_RAISED,
796 BF_SOFT | BF_RECT | BF_MIDDLE);
797 }
798
799 if (pressed)
800 offset = (dwItemCDFlag & TBCDRF_NOOFFSET) ? 0 : 1;
801
802 if (tbcd->nmcd.uItemState & (CDIS_DISABLED | CDIS_INDETERMINATE))
803 {
804 TOOLBAR_DrawArrow(hdc, rcArrow->left+1, rcArrow->top+1 + (rcArrow->bottom - rcArrow->top - ARROW_HEIGHT) / 2, comctl32_color.clrBtnHighlight);
805 TOOLBAR_DrawArrow(hdc, rcArrow->left, rcArrow->top + (rcArrow->bottom - rcArrow->top - ARROW_HEIGHT) / 2, comctl32_color.clr3dShadow);
806 }
807 else
808 TOOLBAR_DrawArrow(hdc, rcArrow->left + offset, rcArrow->top + offset + (rcArrow->bottom - rcArrow->top - ARROW_HEIGHT) / 2, comctl32_color.clrBtnText);
809 }
810
811 /* draws a complete toolbar button */
812 static void
813 TOOLBAR_DrawButton (HWND hwnd, TBUTTON_INFO *btnPtr, HDC hdc, DWORD dwBaseCustDraw)
814 {
815 TOOLBAR_INFO *infoPtr = TOOLBAR_GetInfoPtr (hwnd);
816 DWORD dwStyle = infoPtr->dwStyle;
817 BOOL hasDropDownArrow = (TOOLBAR_HasDropDownArrows(infoPtr->dwExStyle) &&
818 (btnPtr->fsStyle & BTNS_DROPDOWN)) ||
819 (btnPtr->fsStyle & BTNS_WHOLEDROPDOWN);
820 BOOL drawSepDropDownArrow = hasDropDownArrow &&
821 (~btnPtr->fsStyle & BTNS_WHOLEDROPDOWN);
822 RECT rc, rcArrow, rcBitmap, rcText;
823 LPWSTR lpText = NULL;
824 NMTBCUSTOMDRAW tbcd;
825 DWORD ntfret;
826 INT offset;
827 INT oldBkMode;
828 DWORD dwItemCustDraw;
829 DWORD dwItemCDFlag;
830 HTHEME theme = GetWindowTheme (hwnd);
831
832 rc = btnPtr->rect;
833 CopyRect (&rcArrow, &rc);
834
835 /* separator - doesn't send NM_CUSTOMDRAW */
836 if (btnPtr->fsStyle & BTNS_SEP) {
837 if (theme)
838 {
839 DrawThemeBackground (theme, hdc,
840 (dwStyle & CCS_VERT) ? TP_SEPARATORVERT : TP_SEPARATOR, 0,
841 &rc, NULL);
842 }
843 else
844 /* with the FLAT style, iBitmap is the width and has already */
845 /* been taken into consideration in calculating the width */
846 /* so now we need to draw the vertical separator */
847 /* empirical tests show that iBitmap can/will be non-zero */
848 /* when drawing the vertical bar... */
849 if ((dwStyle & TBSTYLE_FLAT) /* && (btnPtr->iBitmap == 0) */) {
850 if (btnPtr->fsStyle & BTNS_DROPDOWN)
851 TOOLBAR_DrawDDFlatSeparator (&rc, hdc, btnPtr, infoPtr);
852 else
853 TOOLBAR_DrawFlatSeparator (&rc, hdc, infoPtr);
854 }
855 else if (btnPtr->fsStyle != BTNS_SEP) {
856 FIXME("Draw some kind of separator: fsStyle=%x\n",
857 btnPtr->fsStyle);
858 }
859 return;
860 }
861
862 /* get a pointer to the text */
863 lpText = TOOLBAR_GetText(infoPtr, btnPtr);
864
865 if (hasDropDownArrow)
866 {
867 int right;
868
869 if (dwStyle & TBSTYLE_FLAT)
870 right = max(rc.left, rc.right - DDARROW_WIDTH);
871 else
872 right = max(rc.left, rc.right - DDARROW_WIDTH - 2);
873
874 if (drawSepDropDownArrow)
875 rc.right = right;
876
877 rcArrow.left = right;
878 }
879
880 /* copy text & bitmap rects after adjusting for drop-down arrow
881 * so that text & bitmap is centered in the rectangle not containing
882 * the arrow */
883 CopyRect(&rcText, &rc);
884 CopyRect(&rcBitmap, &rc);
885
886 /* Center the bitmap horizontally and vertically */
887 if (dwStyle & TBSTYLE_LIST)
888 {
889 if (lpText &&
890 infoPtr->nMaxTextRows > 0 &&
891 (!(infoPtr->dwExStyle & TBSTYLE_EX_MIXEDBUTTONS) ||
892 (btnPtr->fsStyle & BTNS_SHOWTEXT)) )
893 rcBitmap.left += GetSystemMetrics(SM_CXEDGE) + infoPtr->szPadding.cx / 2;
894 else
895 rcBitmap.left += GetSystemMet